Files
128
Total Lines
28014
Coverage
72.0%
4202 / 5818 lines
Actions
89.0%
lib/erc7540-reusable-properties/src/ERC7540Properties.sol
Lines covered: 111 / 124 (89.0%)
| 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 | 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 | function erc7540_1(address erc7540Target) public virtual returns (bool) { |
| 82 | // Doesn't hold on zero price |
| 83 | if ( |
| 84 | IERC7540Like(erc7540Target).convertToAssets( |
| 85 | 10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
| 86 | ) == 0 |
| 87 | ) return true; |
| 88 | |
| 89 | return |
| 90 | IERC7540Like(erc7540Target).convertToAssets( |
| 91 | IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
| 92 | ) == IERC7540Like(erc7540Target).totalAssets(); |
| 93 | } |
| 94 | |
| 95 | /// @dev 7540-2 convertToShares(totalAssets) == totalSupply unless price is 0.0 |
| 96 | function erc7540_2(address erc7540Target) public virtual returns (bool) { |
| 97 | if ( |
| 98 | IERC7540Like(erc7540Target).convertToAssets( |
| 99 | 10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
| 100 | ) == 0 |
| 101 | ) return true; |
| 102 | |
| 103 | // convertToShares(totalAssets) == totalSupply |
| 104 | return |
| 105 | _diff( |
| 106 | IERC7540Like(erc7540Target).convertToShares( |
| 107 | IERC7540Like(erc7540Target).totalAssets() |
| 108 | ), |
| 109 | IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
| 110 | ) <= MAX_ROUNDING_ERROR; |
| 111 | } |
| 112 | |
| 113 | function _diff(uint256 a, uint256 b) internal pure returns (uint256) { |
| 114 | return a > b ? a - b : b - a; |
| 115 | } |
| 116 | |
| 117 | /// @dev 7540-3 max* never reverts |
| 118 | function erc7540_3(address erc7540Target) public virtual returns (bool) { |
| 119 | // max* never reverts |
| 120 | try IERC7540Like(erc7540Target).maxDeposit(actor) {} catch { |
| 121 | return false; |
| 122 | } |
| 123 | try IERC7540Like(erc7540Target).maxMint(actor) {} catch { |
| 124 | return false; |
| 125 | } |
| 126 | try IERC7540Like(erc7540Target).maxRedeem(actor) {} catch { |
| 127 | return false; |
| 128 | } |
| 129 | try IERC7540Like(erc7540Target).maxWithdraw(actor) {} catch { |
| 130 | return false; |
| 131 | } |
| 132 | |
| 133 | return true; |
| 134 | } |
| 135 | |
| 136 | /// == erc7540_4 == // |
| 137 | |
| 138 | /// @dev 7540-4 claiming more than max always reverts |
| 139 | function erc7540_4_deposit( |
| 140 | address erc7540Target, |
| 141 | uint256 amt |
| 142 | ) public virtual returns (bool) { |
| 143 | // Skip 0 |
| 144 | if (amt == 0) { |
| 145 | return true; // Skip |
| 146 | } |
| 147 | |
| 148 | uint256 maxDep = IERC7540Like(erc7540Target).maxDeposit(actor); |
| 149 | |
| 150 | /// @audit No Revert is proven by erc7540_5 |
| 151 | |
| 152 | uint256 sum = maxDep + amt; |
| 153 | if (sum == 0) { |
| 154 | return true; // Needs to be greater than 0, skip |
| 155 | } |
| 156 | |
| 157 | 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 | function erc7540_4_mint( |
| 168 | address erc7540Target, |
| 169 | uint256 amt |
| 170 | ) public virtual returns (bool) { |
| 171 | // Skip 0 |
| 172 | if (amt == 0) { |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | uint256 maxDep = IERC7540Like(erc7540Target).maxMint(actor); |
| 177 | |
| 178 | uint256 sum = maxDep + amt; |
| 179 | if (sum == 0) { |
| 180 | return true; // Needs to be greater than 0, skip |
| 181 | } |
| 182 | |
| 183 | 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 | function erc7540_4_withdraw( |
| 194 | address erc7540Target, |
| 195 | uint256 amt |
| 196 | ) public virtual returns (bool) { |
| 197 | // Skip 0 |
| 198 | if (amt == 0) { |
| 199 | return true; |
| 200 | } |
| 201 | |
| 202 | uint256 maxDep = IERC7540Like(erc7540Target).maxWithdraw(actor); |
| 203 | |
| 204 | uint256 sum = maxDep + amt; |
| 205 | if (sum == 0) { |
| 206 | return true; // Needs to be greater than 0 |
| 207 | } |
| 208 | |
| 209 | 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 | function erc7540_4_redeem( |
| 220 | address erc7540Target, |
| 221 | uint256 amt |
| 222 | ) public virtual returns (bool) { |
| 223 | // Skip 0 |
| 224 | if (amt == 0) { |
| 225 | return true; |
| 226 | } |
| 227 | |
| 228 | uint256 maxDep = IERC7540Like(erc7540Target).maxRedeem(actor); |
| 229 | |
| 230 | uint256 sum = maxDep + amt; |
| 231 | if (sum == 0) { |
| 232 | return true; // Needs to be greater than 0 |
| 233 | } |
| 234 | |
| 235 | try IERC7540Like(erc7540Target).redeem(maxDep + amt, actor, actor) { |
| 236 | return false; |
| 237 | } catch { |
| 238 | // We want this to be hit |
| 239 | 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 | function erc7540_5( |
| 249 | address erc7540Target, |
| 250 | address shareToken, |
| 251 | uint256 shares |
| 252 | ) public virtual returns (bool) { |
| 253 | if (shares == 0) { |
| 254 | return true; // Skip |
| 255 | } |
| 256 | |
| 257 | uint256 actualBal = IShareLike(shareToken).balanceOf(actor); |
| 258 | uint256 balWeWillUse = actualBal + shares; |
| 259 | |
| 260 | if (balWeWillUse == 0) { |
| 261 | return true; // Skip |
| 262 | } |
| 263 | |
| 264 | try |
| 265 | IERC7540Like(erc7540Target).requestRedeem( |
| 266 | balWeWillUse, |
| 267 | actor, |
| 268 | actor, |
| 269 | "" |
| 270 | ) |
| 271 | { |
| 272 | return false; |
| 273 | } catch { |
| 274 | 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 | function erc7540_6(address erc7540Target) public virtual returns (bool) { |
| 282 | // preview* always reverts |
| 283 | try IERC7540Like(erc7540Target).previewDeposit(0) { |
| 284 | return false; |
| 285 | } catch {} |
| 286 | try IERC7540Like(erc7540Target).previewMint(0) { |
| 287 | return false; |
| 288 | } catch {} |
| 289 | try IERC7540Like(erc7540Target).previewRedeem(0) { |
| 290 | return false; |
| 291 | } catch {} |
| 292 | try IERC7540Like(erc7540Target).previewWithdraw(0) { |
| 293 | 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 | function erc7540_7_deposit( |
| 303 | address erc7540Target, |
| 304 | uint256 amt |
| 305 | ) public virtual returns (bool) { |
| 306 | // Per erc7540_4 |
| 307 | uint256 maxDeposit = IERC7540Like(erc7540Target).maxDeposit(actor); |
| 308 | amt = between(amt, 0, maxDeposit); |
| 309 | |
| 310 | if (amt == 0) { |
| 311 | return true; // Skip |
| 312 | } |
| 313 | |
| 314 | 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 | function erc7540_7_mint( |
| 325 | address erc7540Target, |
| 326 | uint256 amt |
| 327 | ) public virtual returns (bool) { |
| 328 | uint256 maxMint = IERC7540Like(erc7540Target).maxMint(actor); |
| 329 | amt = between(amt, 0, maxMint); |
| 330 | |
| 331 | if (amt == 0) { |
| 332 | return true; // Skip |
| 333 | } |
| 334 | |
| 335 | 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 | function erc7540_7_withdraw( |
| 346 | address erc7540Target, |
| 347 | uint256 amt |
| 348 | ) public virtual returns (bool) { |
| 349 | uint256 maxWithdraw = IERC7540Like(erc7540Target).maxWithdraw(actor); |
| 350 | amt = between(amt, 0, maxWithdraw); |
| 351 | |
| 352 | if (amt == 0) { |
| 353 | return true; // Skip |
| 354 | } |
| 355 | |
| 356 | 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 | function erc7540_7_redeem( |
| 367 | address erc7540Target, |
| 368 | uint256 amt |
| 369 | ) public virtual returns (bool) { |
| 370 | // Per erc7540_4 |
| 371 | uint256 maxRedeem = IERC7540Like(erc7540Target).maxRedeem(actor); |
| 372 | amt = between(amt, 0, maxRedeem); |
| 373 | |
| 374 | if (amt == 0) { |
| 375 | return true; // Skip |
| 376 | } |
| 377 | |
| 378 | 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 | } |
| 389 |
90.0%
lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
Lines covered: 27 / 30 (90.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed |
| 8 | * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an |
| 9 | * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer |
| 10 | * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. |
| 11 | * |
| 12 | * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be |
| 13 | * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in |
| 14 | * case an upgrade adds a module that needs to be initialized. |
| 15 | * |
| 16 | * For example: |
| 17 | * |
| 18 | * [.hljs-theme-light.nopadding] |
| 19 | * ```solidity |
| 20 | * contract MyToken is ERC20Upgradeable { |
| 21 | * function initialize() initializer public { |
| 22 | * __ERC20_init("MyToken", "MTK"); |
| 23 | * } |
| 24 | * } |
| 25 | * |
| 26 | * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { |
| 27 | * function initializeV2() reinitializer(2) public { |
| 28 | * __ERC20Permit_init("MyToken"); |
| 29 | * } |
| 30 | * } |
| 31 | * ``` |
| 32 | * |
| 33 | * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as |
| 34 | * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. |
| 35 | * |
| 36 | * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure |
| 37 | * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. |
| 38 | * |
| 39 | * [CAUTION] |
| 40 | * ==== |
| 41 | * Avoid leaving a contract uninitialized. |
| 42 | * |
| 43 | * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation |
| 44 | * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke |
| 45 | * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: |
| 46 | * |
| 47 | * [.hljs-theme-light.nopadding] |
| 48 | * ``` |
| 49 | * /// @custom:oz-upgrades-unsafe-allow constructor |
| 50 | * constructor() { |
| 51 | * _disableInitializers(); |
| 52 | * } |
| 53 | * ``` |
| 54 | * ==== |
| 55 | */ |
| 56 | abstract contract Initializable { |
| 57 | /** |
| 58 | * @dev Storage of the initializable contract. |
| 59 | * |
| 60 | * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions |
| 61 | * when using with upgradeable contracts. |
| 62 | * |
| 63 | * @custom:storage-location erc7201:openzeppelin.storage.Initializable |
| 64 | */ |
| 65 | struct InitializableStorage { |
| 66 | /** |
| 67 | * @dev Indicates that the contract has been initialized. |
| 68 | */ |
| 69 | uint64 _initialized; |
| 70 | /** |
| 71 | * @dev Indicates that the contract is in the process of being initialized. |
| 72 | */ |
| 73 | bool _initializing; |
| 74 | } |
| 75 | |
| 76 | // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) |
| 77 | bytes32 private constant INITIALIZABLE_STORAGE = 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 | modifier initializer() { |
| 105 | // solhint-disable-next-line var-name-mixedcase |
| 106 | InitializableStorage storage $ = _getInitializableStorage(); |
| 107 | |
| 108 | // Cache values to avoid duplicated sloads |
| 109 | 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 | bool initialSetup = initialized == 0 && isTopLevelCall; |
| 118 | bool construction = initialized == 1 && address(this).code.length == 0; |
| 119 | |
| 120 | if (!initialSetup && !construction) { |
| 121 | revert InvalidInitialization(); |
| 122 | } |
| 123 | $._initialized = 1; |
| 124 | if (isTopLevelCall) { |
| 125 | $._initializing = true; |
| 126 | } |
| 127 | _; |
| 128 | if (isTopLevelCall) { |
| 129 | $._initializing = false; |
| 130 | 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 | _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 | if (!_isInitializing()) { |
| 180 | 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 | if ($._initializing) { |
| 197 | revert InvalidInitialization(); |
| 198 | } |
| 199 | if ($._initialized != type(uint64).max) { |
| 200 | $._initialized = type(uint64).max; |
| 201 | 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 | 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 | } |
| 239 |
83.0%
lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
Lines covered: 56 / 67 (83.0%)
| 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 | $.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 | 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 | function name() public view virtual returns (string memory) { |
| 71 | ERC20Storage storage $ = _getERC20Storage(); |
| 72 | return $._name; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * @dev Returns the symbol of the token, usually a shorter version of the |
| 77 | * name. |
| 78 | */ |
| 79 | function symbol() public view virtual returns (string memory) { |
| 80 | ERC20Storage storage $ = _getERC20Storage(); |
| 81 | 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 | function balanceOf(address account) public view virtual returns (uint256) { |
| 109 | ERC20Storage storage $ = _getERC20Storage(); |
| 110 | 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 | function transfer(address to, uint256 value) public virtual returns (bool) { |
| 122 | address owner = _msgSender(); |
| 123 | _transfer(owner, to, value); |
| 124 | return true; |
| 125 | } |
| 126 | |
| 127 | /// @inheritdoc IERC20 |
| 128 | function allowance(address owner, address spender) public view virtual returns (uint256) { |
| 129 | ERC20Storage storage $ = _getERC20Storage(); |
| 130 | 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 | 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 | function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { |
| 166 | address spender = _msgSender(); |
| 167 | _spendAllowance(from, spender, value); |
| 168 | _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 | if (from == address(0)) { |
| 184 | revert ERC20InvalidSender(address(0)); |
| 185 | } |
| 186 | if (to == address(0)) { |
| 187 | revert ERC20InvalidReceiver(address(0)); |
| 188 | } |
| 189 | _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 | function _update(address from, address to, uint256 value) internal virtual { |
| 200 | ERC20Storage storage $ = _getERC20Storage(); |
| 201 | if (from == address(0)) { |
| 202 | // Overflow check required: The rest of the code assumes that totalSupply never overflows |
| 203 | $._totalSupply += value; |
| 204 | } else { |
| 205 | uint256 fromBalance = $._balances[from]; |
| 206 | if (fromBalance < value) { |
| 207 | revert ERC20InsufficientBalance(from, fromBalance, value); |
| 208 | } |
| 209 | unchecked { |
| 210 | // Overflow not possible: value <= fromBalance <= totalSupply. |
| 211 | $._balances[from] = fromBalance - value; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | if (to == address(0)) { |
| 216 | unchecked { |
| 217 | // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. |
| 218 | $._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 | $._balances[to] += value; |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | 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 | if (account == address(0)) { |
| 240 | revert ERC20InvalidReceiver(address(0)); |
| 241 | } |
| 242 | _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 | 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 | function _approve(address owner, address spender, uint256 value) internal { |
| 276 | _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 | function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { |
| 298 | ERC20Storage storage $ = _getERC20Storage(); |
| 299 | if (owner == address(0)) { |
| 300 | revert ERC20InvalidApprover(address(0)); |
| 301 | } |
| 302 | if (spender == address(0)) { |
| 303 | revert ERC20InvalidSpender(address(0)); |
| 304 | } |
| 305 | $._allowances[owner][spender] = value; |
| 306 | if (emitEvent) { |
| 307 | 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 | function _spendAllowance(address owner, address spender, uint256 value) internal virtual { |
| 320 | uint256 currentAllowance = allowance(owner, spender); |
| 321 | if (currentAllowance < type(uint256).max) { |
| 322 | if (currentAllowance < value) { |
| 323 | revert ERC20InsufficientAllowance(spender, currentAllowance, value); |
| 324 | } |
| 325 | unchecked { |
| 326 | _approve(owner, spender, currentAllowance - value, false); |
| 327 | } |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 |
100.0%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
Lines covered: 1 / 1 (100.0%)
| 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 | 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 | } |
| 35 |
92.0%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
Lines covered: 12 / 13 (92.0%)
| 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 | _nonReentrantBefore(); |
| 78 | _; |
| 79 | _nonReentrantAfter(); |
| 80 | } |
| 81 | |
| 82 | function _nonReentrantBefore() private { |
| 83 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
| 84 | // On the first call to nonReentrant, _status will be NOT_ENTERED |
| 85 | if ($._status == ENTERED) { |
| 86 | revert ReentrancyGuardReentrantCall(); |
| 87 | } |
| 88 | |
| 89 | // Any calls to nonReentrant after this point will fail |
| 90 | $._status = ENTERED; |
| 91 | } |
| 92 | |
| 93 | 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 | } |
| 109 |
17.0%
lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol
Lines covered: 8 / 47 (17.0%)
| 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 | 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 | function _getEIP712Storage() private pure returns (EIP712Storage storage $) { |
| 50 | assembly { |
| 51 | $.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 | function _domainSeparatorV4() internal view returns (bytes32) { |
| 85 | return _buildDomainSeparator(); |
| 86 | } |
| 87 | |
| 88 | function _buildDomainSeparator() private view returns (bytes32) { |
| 89 | 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 | function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { |
| 108 | return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); |
| 109 | } |
| 110 | |
| 111 | /// @inheritdoc IERC5267 |
| 112 | function eip712Domain() |
| 113 | public |
| 114 | view |
| 115 | virtual |
| 116 | returns ( |
| 117 | bytes1 fields, |
| 118 | string memory name, |
| 119 | string memory version, |
| 120 | uint256 chainId, |
| 121 | address verifyingContract, |
| 122 | bytes32 salt, |
| 123 | uint256[] memory extensions |
| 124 | ) |
| 125 | { |
| 126 | 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 | require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized"); |
| 130 | |
| 131 | return ( |
| 132 | hex"0f", // 01111 |
| 133 | _EIP712Name(), |
| 134 | _EIP712Version(), |
| 135 | block.chainid, |
| 136 | address(this), |
| 137 | bytes32(0), |
| 138 | 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 | function _EIP712Name() internal view virtual returns (string memory) { |
| 149 | EIP712Storage storage $ = _getEIP712Storage(); |
| 150 | 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 | function _EIP712Version() internal view virtual returns (string memory) { |
| 160 | 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 | function _EIP712NameHash() internal view returns (bytes32) { |
| 170 | EIP712Storage storage $ = _getEIP712Storage(); |
| 171 | string memory name = _EIP712Name(); |
| 172 | if (bytes(name).length > 0) { |
| 173 | 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 | bytes32 hashedName = $._hashedName; |
| 178 | if (hashedName != 0) { |
| 179 | return hashedName; |
| 180 | } else { |
| 181 | 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 | function _EIP712VersionHash() internal view returns (bytes32) { |
| 192 | EIP712Storage storage $ = _getEIP712Storage(); |
| 193 | string memory version = _EIP712Version(); |
| 194 | if (bytes(version).length > 0) { |
| 195 | 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 | bytes32 hashedVersion = $._hashedVersion; |
| 200 | if (hashedVersion != 0) { |
| 201 | return hashedVersion; |
| 202 | } else { |
| 203 | return keccak256(""); |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 |
0.0%
lib/setup-helpers/lib/chimera/src/Asserts.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | abstract contract Asserts { |
| 5 | function gt(uint256 a, uint256 b, string memory reason) internal virtual; |
| 6 | |
| 7 | function gte(uint256 a, uint256 b, string memory reason) internal virtual; |
| 8 | |
| 9 | function lt(uint256 a, uint256 b, string memory reason) internal virtual; |
| 10 | |
| 11 | function lte(uint256 a, uint256 b, string memory reason) internal virtual; |
| 12 | |
| 13 | function eq(uint256 a, uint256 b, string memory reason) internal virtual; |
| 14 | |
| 15 | function t(bool b, string memory reason) internal virtual; |
| 16 | |
| 17 | function between(uint256 value, uint256 low, uint256 high) internal virtual returns (uint256); |
| 18 | |
| 19 | function between(int256 value, int256 low, int256 high) internal virtual returns (int256); |
| 20 | |
| 21 | function precondition(bool p) internal virtual; |
| 22 | } |
| 23 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseProperties.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {BaseSetup} from "./BaseSetup.sol"; |
| 5 | |
| 6 | abstract contract BaseProperties is BaseSetup {} |
| 7 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseSetup.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | abstract contract BaseSetup { |
| 5 | function setup() internal virtual; |
| 6 | } |
| 7 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseTargetFunctions.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {BaseProperties} from "./BaseProperties.sol"; |
| 5 | import {Asserts} from "./Asserts.sol"; |
| 6 | |
| 7 | abstract contract BaseTargetFunctions is BaseProperties, Asserts {} |
| 8 |
91.0%
lib/setup-helpers/lib/chimera/src/CryticAsserts.sol
Lines covered: 21 / 23 (91.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {Asserts} from "./Asserts.sol"; |
| 5 | |
| 6 | contract CryticAsserts is Asserts { |
| 7 | event Log(string); |
| 8 | |
| 9 | function gt(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 10 | if (!(a > b)) { |
| 11 | emit Log(reason); |
| 12 | assert(false); |
| 13 | } |
| 14 | } |
| 15 | |
| 16 | function gte(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 17 | if (!(a >= b)) { |
| 18 | emit Log(reason); |
| 19 | assert(false); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | function lt(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 24 | if (!(a < b)) { |
| 25 | emit Log(reason); |
| 26 | assert(false); |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | function lte(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 31 | if (!(a <= b)) { |
| 32 | emit Log(reason); |
| 33 | assert(false); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | function eq(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 38 | if (!(a == b)) { |
| 39 | emit Log(reason); |
| 40 | assert(false); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | function t(bool b, string memory reason) internal virtual override { |
| 45 | if (!b) { |
| 46 | emit Log(reason); |
| 47 | assert(false); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | function between(uint256 value, uint256 low, uint256 high) internal virtual override returns (uint256) { |
| 52 | if (value < low || value > high) { |
| 53 | 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 | } |
| 74 |
100.0%
lib/setup-helpers/lib/chimera/src/Hevm.sol
Lines covered: 1 / 1 (100.0%)
| 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 | IHevm constant vm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); |
| 55 | |
| 56 | // slither-disable-end shadowing-local |
| 57 |
86.0%
lib/setup-helpers/src/ActorManager.sol
Lines covered: 13 / 15 (86.0%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
| 5 | import {vm} from "@chimera/Hevm.sol"; |
| 6 | import {EnumerableSet} from "./EnumerableSet.sol"; |
| 7 | |
| 8 | /// @dev This is the source of truth for the actors being used in the test |
| 9 | /// @notice No actors should be used in the suite without being added here first |
| 10 | abstract contract ActorManager { |
| 11 | using EnumerableSet for EnumerableSet.AddressSet; |
| 12 | |
| 13 | ///@notice The current actor being used |
| 14 | address private _actor; |
| 15 | |
| 16 | ///@notice The list of all actors being used |
| 17 | EnumerableSet.AddressSet private _actors; |
| 18 | |
| 19 | // If the current target is address(0) then it has not been setup yet and should revert |
| 20 | error ActorNotSetup(); |
| 21 | // Do not allow duplicates |
| 22 | error ActorExists(); |
| 23 | // If the actor does not exist |
| 24 | error ActorNotAdded(); |
| 25 | // Do not allow the default actor |
| 26 | error DefaultActor(); |
| 27 | |
| 28 | /// @notice address(this) is the default actor |
| 29 | constructor() { |
| 30 | _actors.add(address(this)); |
| 31 | _actor = address(this); |
| 32 | } |
| 33 | |
| 34 | /// @notice Returns the current active actor |
| 35 | function _getActor() internal view returns (address) { |
| 36 | return _actor; |
| 37 | } |
| 38 | |
| 39 | /// @notice Returns all actors being used |
| 40 | function _getActors() internal view returns (address[] memory) { |
| 41 | return _actors.values(); |
| 42 | } |
| 43 | |
| 44 | /// @notice Adds an actor to the list of actors |
| 45 | function _addActor(address target) internal { |
| 46 | if (_actors.contains(target)) { |
| 47 | revert ActorExists(); |
| 48 | } |
| 49 | |
| 50 | if (target == address(this)) { |
| 51 | 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 | target = _actors.at(entropy); |
| 78 | _actor = target; |
| 79 | } |
| 80 | } |
| 81 |
93.0%
lib/setup-helpers/src/AssetManager.sol
Lines covered: 31 / 33 (93.0%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
| 5 | import {vm} from "@chimera/Hevm.sol"; |
| 6 | |
| 7 | import {EnumerableSet} from "./EnumerableSet.sol"; |
| 8 | import {MockERC20} from "./MockERC20.sol"; |
| 9 | |
| 10 | /// @dev Source of truth for the assets being used in the test |
| 11 | /// @notice No assets should be used in the suite without being added here first |
| 12 | abstract contract AssetManager { |
| 13 | using EnumerableSet for EnumerableSet.AddressSet; |
| 14 | |
| 15 | /// @notice The current target for this set of variables |
| 16 | address private __asset; |
| 17 | |
| 18 | /// @notice The list of all assets being used |
| 19 | EnumerableSet.AddressSet private _assets; |
| 20 | |
| 21 | // If the current target is address(0) then it has not been setup yet and should revert |
| 22 | error NotSetup(); |
| 23 | // Do not allow duplicates |
| 24 | error Exists(); |
| 25 | // Enable only added assets |
| 26 | error NotAdded(); |
| 27 | |
| 28 | /// @notice Returns the current active asset |
| 29 | function _getAsset() internal view returns (address) { |
| 30 | if (__asset == address(0)) { |
| 31 | revert NotSetup(); |
| 32 | } |
| 33 | |
| 34 | return __asset; |
| 35 | } |
| 36 | |
| 37 | /// @notice Returns all assets being used |
| 38 | function _getAssets() internal view returns (address[] memory) { |
| 39 | return _assets.values(); |
| 40 | } |
| 41 | |
| 42 | /// @notice Creates a new asset and adds it to the list of assets |
| 43 | /// @param decimals The number of decimals for the asset |
| 44 | /// @return The address of the new asset |
| 45 | function _newAsset(uint8 decimals) internal returns (address) { |
| 46 | address asset_ = address(new MockERC20("Test Token", "TST", decimals)); // If names get confusing, concatenate the decimals to the name |
| 47 | _addAsset(asset_); |
| 48 | __asset = asset_; // sets the asset as the current asset |
| 49 | return asset_; |
| 50 | } |
| 51 | |
| 52 | /// @notice Adds an asset to the list of assets |
| 53 | /// @param target The address of the asset to add |
| 54 | function _addAsset(address target) internal { |
| 55 | if (_assets.contains(target)) { |
| 56 | revert Exists(); |
| 57 | } |
| 58 | |
| 59 | _assets.add(target); |
| 60 | } |
| 61 | |
| 62 | /// @notice Removes an asset from the list of assets |
| 63 | /// @param target The address of the asset to remove |
| 64 | function _removeAsset(address target) internal { |
| 65 | if (!_assets.contains(target)) { |
| 66 | revert NotAdded(); |
| 67 | } |
| 68 | |
| 69 | _assets.remove(target); |
| 70 | } |
| 71 | |
| 72 | /// @notice Switches the current asset based on the entropy |
| 73 | /// NOTE: We revert if the entropy is greater than the number of actors, for Halmos compatibility |
| 74 | /// @param entropy The entropy to choose a random asset in the array for switching |
| 75 | function _switchAsset(uint256 entropy) internal { |
| 76 | address target = _assets.at(entropy); |
| 77 | __asset = target; |
| 78 | } |
| 79 | |
| 80 | /// === Approve & Mint Asset === /// |
| 81 | |
| 82 | /// @notice Mint initial balance and approve allowances for the active asset |
| 83 | /// @param actorsArray The array of actors to mint the asset to |
| 84 | /// @param approvalArray The array of addresses to approve the asset to |
| 85 | /// @param amount The amount of the asset to mint |
| 86 | function _finalizeAssetDeployment(address[] memory actorsArray, address[] memory approvalArray, uint256 amount) |
| 87 | internal |
| 88 | { |
| 89 | _mintAssetToAllActors(actorsArray, amount); |
| 90 | for (uint256 i; i < approvalArray.length; i++) { |
| 91 | _approveAssetToAddressForAllActors(actorsArray, approvalArray[i]); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | /// @notice Mint the asset to all actors |
| 96 | /// @param actorsArray The array of actors to mint the asset to |
| 97 | /// @param amount The amount of the asset to mint |
| 98 | function _mintAssetToAllActors(address[] memory actorsArray, uint256 amount) private { |
| 99 | // mint all actors |
| 100 | address[] memory assets = _getAssets(); |
| 101 | for (uint256 i; i < assets.length; i++) { |
| 102 | for (uint256 j; j < actorsArray.length; j++) { |
| 103 | vm.prank(actorsArray[j]); |
| 104 | MockERC20(assets[i]).mint(actorsArray[j], amount); |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /// @notice Approve the asset to all actors |
| 110 | /// @param actorsArray The array of actors to approve the asset from |
| 111 | /// @param addressToApprove The address to approve the asset to |
| 112 | function _approveAssetToAddressForAllActors(address[] memory actorsArray, address addressToApprove) private { |
| 113 | // approve to all actors |
| 114 | address[] memory assets = _getAssets(); |
| 115 | for (uint256 i; i < assets.length; i++) { |
| 116 | for (uint256 j; j < actorsArray.length; j++) { |
| 117 | vm.prank(actorsArray[j]); |
| 118 | MockERC20(assets[i]).approve(addressToApprove, type(uint256).max); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 |
92.0%
lib/setup-helpers/src/EnumerableSet.sol
Lines covered: 23 / 25 (92.0%)
| 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 | 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 | 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 | set._indexes[value] = set._values.length; |
| 71 | return true; |
| 72 | } else { |
| 73 | 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 | 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 | function _at(Set storage set, uint256 index) private view returns (bytes32) { |
| 142 | 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 | function _values(Set storage set) private view returns (bytes32[] memory) { |
| 154 | return set._values; |
| 155 | } |
| 156 | |
| 157 | // Bytes32Set |
| 158 | |
| 159 | struct Bytes32Set { |
| 160 | Set _inner; |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * @dev Add a value to a set. O(1). |
| 165 | * |
| 166 | * Returns true if the value was added to the set, that is if it was not |
| 167 | * already present. |
| 168 | */ |
| 169 | function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
| 170 | return _add(set._inner, value); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * @dev Removes a value from a set. O(1). |
| 175 | * |
| 176 | * Returns true if the value was removed from the set, that is if it was |
| 177 | * present. |
| 178 | */ |
| 179 | function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
| 180 | return _remove(set._inner, value); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * @dev Returns true if the value is in the set. O(1). |
| 185 | */ |
| 186 | function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { |
| 187 | return _contains(set._inner, value); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * @dev Returns the number of values in the set. O(1). |
| 192 | */ |
| 193 | function length(Bytes32Set storage set) internal view returns (uint256) { |
| 194 | return _length(set._inner); |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * @dev Returns the value stored at position `index` in the set. O(1). |
| 199 | * |
| 200 | * Note that there are no guarantees on the ordering of values inside the |
| 201 | * array, and it may change when more values are added or removed. |
| 202 | * |
| 203 | * Requirements: |
| 204 | * |
| 205 | * - `index` must be strictly less than {length}. |
| 206 | */ |
| 207 | function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { |
| 208 | return _at(set._inner, index); |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * @dev Return the entire set in an array |
| 213 | * |
| 214 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 215 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 216 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 217 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 218 | */ |
| 219 | function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { |
| 220 | bytes32[] memory store = _values(set._inner); |
| 221 | bytes32[] memory result; |
| 222 | |
| 223 | /// @solidity memory-safe-assembly |
| 224 | assembly { |
| 225 | result := store |
| 226 | } |
| 227 | |
| 228 | return result; |
| 229 | } |
| 230 | |
| 231 | // AddressSet |
| 232 | |
| 233 | struct AddressSet { |
| 234 | Set _inner; |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * @dev Add a value to a set. O(1). |
| 239 | * |
| 240 | * Returns true if the value was added to the set, that is if it was not |
| 241 | * already present. |
| 242 | */ |
| 243 | function add(AddressSet storage set, address value) internal returns (bool) { |
| 244 | return _add(set._inner, bytes32(uint256(uint160(value)))); |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * @dev Removes a value from a set. O(1). |
| 249 | * |
| 250 | * Returns true if the value was removed from the set, that is if it was |
| 251 | * present. |
| 252 | */ |
| 253 | function remove(AddressSet storage set, address value) internal returns (bool) { |
| 254 | return _remove(set._inner, bytes32(uint256(uint160(value)))); |
| 255 | } |
| 256 | |
| 257 | /** |
| 258 | * @dev Returns true if the value is in the set. O(1). |
| 259 | */ |
| 260 | function contains(AddressSet storage set, address value) internal view returns (bool) { |
| 261 | return _contains(set._inner, bytes32(uint256(uint160(value)))); |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * @dev Returns the number of values in the set. O(1). |
| 266 | */ |
| 267 | function length(AddressSet storage set) internal view returns (uint256) { |
| 268 | return _length(set._inner); |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * @dev Returns the value stored at position `index` in the set. O(1). |
| 273 | * |
| 274 | * Note that there are no guarantees on the ordering of values inside the |
| 275 | * array, and it may change when more values are added or removed. |
| 276 | * |
| 277 | * Requirements: |
| 278 | * |
| 279 | * - `index` must be strictly less than {length}. |
| 280 | */ |
| 281 | function at(AddressSet storage set, uint256 index) internal view returns (address) { |
| 282 | return address(uint160(uint256(_at(set._inner, index)))); |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * @dev Return the entire set in an array |
| 287 | * |
| 288 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 289 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 290 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 291 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 292 | */ |
| 293 | function values(AddressSet storage set) internal view returns (address[] memory) { |
| 294 | 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 | } |
| 379 |
73.0%
lib/setup-helpers/src/MockERC20.sol
Lines covered: 55 / 75 (73.0%)
| 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 | string public name; |
| 35 | |
| 36 | string public symbol; |
| 37 | |
| 38 | uint8 public immutable decimals; |
| 39 | |
| 40 | /*////////////////////////////////////////////////////////////// |
| 41 | ERC20 STORAGE |
| 42 | //////////////////////////////////////////////////////////////*/ |
| 43 | |
| 44 | uint256 public totalSupply; |
| 45 | |
| 46 | mapping(address => uint256) public balanceOf; |
| 47 | |
| 48 | 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 | mapping(address => uint256) public nonces; |
| 59 | |
| 60 | /*////////////////////////////////////////////////////////////// |
| 61 | CONSTRUCTOR |
| 62 | //////////////////////////////////////////////////////////////*/ |
| 63 | |
| 64 | constructor(string memory _name, string memory _symbol, uint8 _decimals) { |
| 65 | name = _name; |
| 66 | symbol = _symbol; |
| 67 | decimals = _decimals; |
| 68 | |
| 69 | INITIAL_CHAIN_ID = block.chainid; |
| 70 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); |
| 71 | } |
| 72 | |
| 73 | /*////////////////////////////////////////////////////////////// |
| 74 | ERC20 LOGIC |
| 75 | //////////////////////////////////////////////////////////////*/ |
| 76 | |
| 77 | function approve(address spender, uint256 amount) public virtual returns (bool) { |
| 78 | allowance[msg.sender][spender] = amount; |
| 79 | |
| 80 | emit Approval(msg.sender, spender, amount); |
| 81 | |
| 82 | return true; |
| 83 | } |
| 84 | |
| 85 | function transfer(address to, uint256 amount) public virtual returns (bool) { |
| 86 | uint256 fromBalance = balanceOf[msg.sender]; |
| 87 | if (fromBalance < amount) revert InsufficientBalance(msg.sender, fromBalance, amount); |
| 88 | |
| 89 | 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 | balanceOf[to] += amount; |
| 95 | } |
| 96 | |
| 97 | emit Transfer(msg.sender, to, amount); |
| 98 | |
| 99 | return true; |
| 100 | } |
| 101 | |
| 102 | function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { |
| 103 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. |
| 104 | uint256 fromBalance = balanceOf[from]; |
| 105 | |
| 106 | if (allowed != type(uint256).max) { |
| 107 | if (allowed < amount) revert InsufficientAllowance(from, msg.sender, allowed, amount); |
| 108 | allowance[from][msg.sender] = allowed - amount; |
| 109 | } |
| 110 | |
| 111 | if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
| 112 | 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 | balanceOf[to] += amount; |
| 118 | } |
| 119 | |
| 120 | emit Transfer(from, to, amount); |
| 121 | |
| 122 | return true; |
| 123 | } |
| 124 | |
| 125 | /*////////////////////////////////////////////////////////////// |
| 126 | EIP-2612 LOGIC |
| 127 | //////////////////////////////////////////////////////////////*/ |
| 128 | |
| 129 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) |
| 130 | public |
| 131 | virtual |
| 132 | { |
| 133 | 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 | address recoveredAddress = ecrecover( |
| 139 | keccak256( |
| 140 | abi.encodePacked( |
| 141 | "\x19\x01", |
| 142 | DOMAIN_SEPARATOR(), |
| 143 | keccak256( |
| 144 | abi.encode( |
| 145 | keccak256( |
| 146 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" |
| 147 | ), |
| 148 | owner, |
| 149 | spender, |
| 150 | value, |
| 151 | nonces[owner]++, |
| 152 | deadline |
| 153 | ) |
| 154 | ) |
| 155 | ) |
| 156 | ), |
| 157 | v, |
| 158 | r, |
| 159 | s |
| 160 | ); |
| 161 | |
| 162 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); |
| 163 | |
| 164 | allowance[recoveredAddress][spender] = value; |
| 165 | } |
| 166 | |
| 167 | emit Approval(owner, spender, value); |
| 168 | } |
| 169 | |
| 170 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { |
| 171 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); |
| 172 | } |
| 173 | |
| 174 | function computeDomainSeparator() internal view virtual returns (bytes32) { |
| 175 | return keccak256( |
| 176 | abi.encode( |
| 177 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), |
| 178 | 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 | function _mint(address to, uint256 amount) internal virtual { |
| 191 | uint256 newTotalSupply = totalSupply + amount; |
| 192 | if (newTotalSupply < totalSupply) revert MintOverflow(totalSupply, amount); |
| 193 | totalSupply = newTotalSupply; |
| 194 | |
| 195 | // Cannot overflow because the sum of all user |
| 196 | // balances can't exceed the max uint256 value. |
| 197 | unchecked { |
| 198 | balanceOf[to] += amount; |
| 199 | } |
| 200 | |
| 201 | emit Transfer(address(0), to, amount); |
| 202 | } |
| 203 | |
| 204 | function _burn(address from, uint256 amount) internal virtual { |
| 205 | uint256 fromBalance = balanceOf[from]; |
| 206 | if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
| 207 | |
| 208 | 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 | totalSupply -= amount; |
| 214 | } |
| 215 | |
| 216 | emit Transfer(from, address(0), amount); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | contract MockERC20 is ERC20 { |
| 221 | constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {} |
| 222 | |
| 223 | function mint(address to, uint256 value) public virtual { |
| 224 | _mint(to, value); |
| 225 | } |
| 226 | |
| 227 | function burn(address from, uint256 value) public virtual { |
| 228 | _burn(from, value); |
| 229 | } |
| 230 | } |
| 231 |
0.0%
lib/setup-helpers/src/Panic.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | library Panic { |
| 5 | // compiler panics |
| 6 | string constant assertionPanic = "Panic(1)"; |
| 7 | string constant arithmeticPanic = "Panic(17)"; |
| 8 | string constant divisionPanic = "Panic(18)"; |
| 9 | string constant enumPanic = "Panic(33)"; |
| 10 | string constant arrayPanic = "Panic(34)"; |
| 11 | string constant emptyArrayPanic = "Panic(49)"; |
| 12 | string constant outOfBoundsPanic = "Panic(50)"; |
| 13 | string constant memoryPanic = "Panic(65)"; |
| 14 | string constant functionPanic = "Panic(81)"; |
| 15 | } |
| 16 |
62.0%
lib/setup-helpers/src/Utils.sol
Lines covered: 34 / 54 (62.0%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | import {Panic} from "./Panic.sol"; |
| 5 | |
| 6 | 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 | (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 | errorBytes = bytes32(abi.encodePacked(revertMsg, bytes28(0))); |
| 20 | expectedBytes = bytes4(keccak256(abi.encodePacked(expected))); |
| 21 | } else { |
| 22 | errorBytes = keccak256(abi.encodePacked(revertMsg)); |
| 23 | 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 | bool panic = _checkIfPanic(returnData); |
| 43 | |
| 44 | if (panic) { |
| 45 | 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 | returnData := add(returnData, 0x04) |
| 59 | } |
| 60 | return (abi.decode(returnData, (string)), false); |
| 61 | } |
| 62 | |
| 63 | // 3. Custom error - Return the custom error selector as a string |
| 64 | return (string(abi.encodePacked(errorSelector)), true); |
| 65 | } |
| 66 | |
| 67 | function _checkIfPanic(bytes memory returnData) internal pure returns (bool) { |
| 68 | bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)"))); |
| 69 | |
| 70 | for (uint256 i = 0; i < 4; i++) { |
| 71 | if (returnData[i] != panicSignature[i]) { |
| 72 | return false; |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | return true; |
| 77 | } |
| 78 | |
| 79 | function _getPanicCode(bytes memory returnData) internal pure returns (string memory, bool) { |
| 80 | uint256 panicCode; |
| 81 | for (uint256 i = 4; i < 36; i++) { |
| 82 | panicCode = panicCode << 8; |
| 83 | panicCode |= uint8(returnData[i]); |
| 84 | } |
| 85 | |
| 86 | // Convert the panic code into its string representation |
| 87 | if (panicCode == 1) { |
| 88 | // call assert with an argument that evaluates to false |
| 89 | return (Panic.assertionPanic, false); |
| 90 | } else if (panicCode == 17) { |
| 91 | // arithmetic operation results in underflow or overflow |
| 92 | return (Panic.arithmeticPanic, false); |
| 93 | } else if (panicCode == 18) { |
| 94 | // division or modulo by zero |
| 95 | return (Panic.divisionPanic, false); |
| 96 | } else if (panicCode == 33) { |
| 97 | // converting a value that's too big or negative into an enum type |
| 98 | return (Panic.enumPanic, false); |
| 99 | } else if (panicCode == 34) { |
| 100 | // access a storage byte array that is incorrectly encoded |
| 101 | return (Panic.arrayPanic, false); |
| 102 | } else if (panicCode == 49) { |
| 103 | // call .pop() on an empty array |
| 104 | return (Panic.emptyArrayPanic, false); |
| 105 | } else if (panicCode == 50) { |
| 106 | // array access out of bounds |
| 107 | return (Panic.outOfBoundsPanic, false); |
| 108 | } else if (panicCode == 65) { |
| 109 | // allocate too much memory or create an array that is too large |
| 110 | return (Panic.memoryPanic, false); |
| 111 | } else if (panicCode == 81) { |
| 112 | // call a zero-initialized variable of internal function type |
| 113 | return (Panic.functionPanic, false); |
| 114 | } |
| 115 | |
| 116 | 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 | } |
| 126 |
0.0%
lib/v2-core/lib/modulekit/src/accounts/common/interfaces/IERC7579Account.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity >=0.8.23 <0.9.0; |
| 3 | |
| 4 | /* solhint-disable no-unused-import */ |
| 5 | |
| 6 | // Types |
| 7 | import { CallType, ExecType, ModeCode } from "../lib/ModeLib.sol"; |
| 8 | |
| 9 | // Structs |
| 10 | struct Execution { |
| 11 | address target; |
| 12 | uint256 value; |
| 13 | bytes callData; |
| 14 | } |
| 15 | |
| 16 | interface IERC7579Account { |
| 17 | event ModuleInstalled(uint256 moduleTypeId, address module); |
| 18 | event ModuleUninstalled(uint256 moduleTypeId, address module); |
| 19 | |
| 20 | /** |
| 21 | * @dev Executes a transaction on behalf of the account. |
| 22 | * This function is intended to be called by ERC-4337 EntryPoint.sol |
| 23 | * @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf |
| 24 | * |
| 25 | * @dev MSA MUST implement this function signature. |
| 26 | * If a mode is requested that is not supported by the Account, it MUST revert |
| 27 | * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details |
| 28 | * @param executionCalldata The encoded execution call data |
| 29 | */ |
| 30 | function execute(ModeCode mode, bytes calldata executionCalldata) external payable; |
| 31 | |
| 32 | /** |
| 33 | * @dev Executes a transaction on behalf of the account. |
| 34 | * This function is intended to be called by Executor Modules |
| 35 | * @dev Ensure adequate authorization control: i.e. onlyExecutorModule |
| 36 | * |
| 37 | * @dev MSA MUST implement this function signature. |
| 38 | * If a mode is requested that is not supported by the Account, it MUST revert |
| 39 | * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details |
| 40 | * @param executionCalldata The encoded execution call data |
| 41 | */ |
| 42 | function executeFromExecutor( |
| 43 | ModeCode mode, |
| 44 | bytes calldata executionCalldata |
| 45 | ) |
| 46 | external |
| 47 | payable |
| 48 | returns (bytes[] memory returnData); |
| 49 | |
| 50 | /** |
| 51 | * @dev ERC-1271 isValidSignature |
| 52 | * This function is intended to be used to validate a smart account signature |
| 53 | * and may forward the call to a validator module |
| 54 | * |
| 55 | * @param hash The hash of the data that is signed |
| 56 | * @param data The data that is signed |
| 57 | */ |
| 58 | function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); |
| 59 | |
| 60 | /** |
| 61 | * @dev installs a Module of a certain type on the smart account |
| 62 | * @dev Implement Authorization control of your chosing |
| 63 | * @param moduleTypeId the module type ID according the ERC-7579 spec |
| 64 | * @param module the module address |
| 65 | * @param initData arbitrary data that may be required on the module during `onInstall` |
| 66 | * initialization. |
| 67 | */ |
| 68 | function installModule( |
| 69 | uint256 moduleTypeId, |
| 70 | address module, |
| 71 | bytes calldata initData |
| 72 | ) |
| 73 | external |
| 74 | payable; |
| 75 | |
| 76 | /** |
| 77 | * @dev uninstalls a Module of a certain type on the smart account |
| 78 | * @dev Implement Authorization control of your chosing |
| 79 | * @param moduleTypeId the module type ID according the ERC-7579 spec |
| 80 | * @param module the module address |
| 81 | * @param deInitData arbitrary data that may be required on the module during `onUninstall` |
| 82 | * de-initialization. |
| 83 | */ |
| 84 | function uninstallModule( |
| 85 | uint256 moduleTypeId, |
| 86 | address module, |
| 87 | bytes calldata deInitData |
| 88 | ) |
| 89 | external |
| 90 | payable; |
| 91 | |
| 92 | /** |
| 93 | * Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol) |
| 94 | * @param encodedMode the encoded mode |
| 95 | */ |
| 96 | function supportsExecutionMode(ModeCode encodedMode) external view returns (bool); |
| 97 | |
| 98 | /** |
| 99 | * Function to check if the account supports installation of a certain module type Id |
| 100 | * @param moduleTypeId the module type ID according the ERC-7579 spec |
| 101 | */ |
| 102 | function supportsModule(uint256 moduleTypeId) external view returns (bool); |
| 103 | |
| 104 | /** |
| 105 | * Function to check if the account has a certain module installed |
| 106 | * @param moduleTypeId the module type ID according the ERC-7579 spec |
| 107 | * Note: keep in mind that some contracts can be multiple module types at the same time. It |
| 108 | * thus may be necessary to query multiple module types |
| 109 | * @param module the module address |
| 110 | * @param additionalContext additional context data that the smart account may interpret to |
| 111 | * identifiy conditions under which the module is installed. |
| 112 | * usually this is not necessary, but for some special hooks that |
| 113 | * are stored in mappings, this param might be needed |
| 114 | */ |
| 115 | function isModuleInstalled( |
| 116 | uint256 moduleTypeId, |
| 117 | address module, |
| 118 | bytes calldata additionalContext |
| 119 | ) |
| 120 | external |
| 121 | view |
| 122 | returns (bool); |
| 123 | |
| 124 | /** |
| 125 | * @dev Returns the account id of the smart account |
| 126 | * @return accountImplementationId the account id of the smart account |
| 127 | * the accountId should be structured like so: |
| 128 | * "vendorname.accountname.semver" |
| 129 | */ |
| 130 | function accountId() external view returns (string memory accountImplementationId); |
| 131 | } |
| 132 |
0.0%
lib/v2-core/lib/modulekit/src/accounts/common/lib/ModeLib.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: GPL-3.0 |
| 2 | pragma solidity >=0.8.0 <0.9.0; |
| 3 | |
| 4 | /** |
| 5 | * @title ModeLib |
| 6 | * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) |
| 7 | * To allow smart accounts to be very simple, but allow for more complex execution, A custom mode |
| 8 | * encoding is used. |
| 9 | * Function Signature of execute function: |
| 10 | * function execute(ModeCode mode, bytes calldata executionCalldata) external payable; |
| 11 | * This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and |
| 12 | * context. |
| 13 | * NOTE: Simple Account implementations only have to scope for the most significant byte. Account that |
| 14 | * implement |
| 15 | * more complex execution modes may use the entire bytes32. |
| 16 | * |
| 17 | * |--------------------------------------------------------------------| |
| 18 | * | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | |
| 19 | * |--------------------------------------------------------------------| |
| 20 | * | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | |
| 21 | * |--------------------------------------------------------------------| |
| 22 | * |
| 23 | * CALLTYPE: 1 byte |
| 24 | * CallType is used to determine how the executeCalldata paramter of the execute function has to be |
| 25 | * decoded. |
| 26 | * It can be either single, batch or delegatecall. In the future different calls could be added. |
| 27 | * CALLTYPE can be used by a validation module to determine how to decode <userOp.callData[36:]>. |
| 28 | * |
| 29 | * EXECTYPE: 1 byte |
| 30 | * ExecType is used to determine how the account should handle the execution. |
| 31 | * It can indicate if the execution should revert on failure or continue execution. |
| 32 | * In the future more execution modes may be added. |
| 33 | * Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in |
| 34 | * a batch fails, the entire batch is reverted |
| 35 | * |
| 36 | * UNUSED: 4 bytes |
| 37 | * Unused bytes are reserved for future use. |
| 38 | * |
| 39 | * ModeSelector: bytes4 |
| 40 | * The "optional" mode selector can be used by account vendors, to implement custom behavior in |
| 41 | * their accounts. |
| 42 | * the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename")) |
| 43 | * this is to prevent collisions between different vendors, while allowing innovation and the |
| 44 | * development of new features without coordination between ERC-7579 implementing accounts |
| 45 | * |
| 46 | * ModePayload: 22 bytes |
| 47 | * Mode payload is used to pass additional data to the smart account execution, this may be |
| 48 | * interpreted depending on the ModeSelector |
| 49 | * |
| 50 | * ExecutionCallData: n bytes |
| 51 | * single, delegatecall or batch exec abi.encoded as bytes |
| 52 | */ |
| 53 | |
| 54 | // Custom type for improved developer experience |
| 55 | type ModeCode is bytes32; |
| 56 | |
| 57 | type CallType is bytes1; |
| 58 | |
| 59 | type ExecType is bytes1; |
| 60 | |
| 61 | type ModeSelector is bytes4; |
| 62 | |
| 63 | type ModePayload is bytes22; |
| 64 | |
| 65 | // Default CallType |
| 66 | CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); |
| 67 | // Batched CallType |
| 68 | CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); |
| 69 | CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); |
| 70 | // @dev Implementing delegatecall is OPTIONAL! |
| 71 | // implement delegatecall with extreme care. |
| 72 | CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); |
| 73 | |
| 74 | // @dev default behavior is to revert on failure |
| 75 | // To allow very simple accounts to use mode encoding, the default behavior is to revert on failure |
| 76 | // Since this is value 0x00, no additional encoding is required for simple accounts |
| 77 | ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); |
| 78 | // @dev account may elect to change execution behavior. For example "try exec" / "allow fail" |
| 79 | ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); |
| 80 | |
| 81 | ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); |
| 82 | // Example declaration of a custom mode selector |
| 83 | ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset"))); |
| 84 | |
| 85 | /** |
| 86 | * @dev ModeLib is a helper library to encode/decode ModeCodes |
| 87 | */ |
| 88 | library ModeLib { |
| 89 | function decode(ModeCode mode) |
| 90 | internal |
| 91 | pure |
| 92 | returns ( |
| 93 | CallType _calltype, |
| 94 | ExecType _execType, |
| 95 | ModeSelector _modeSelector, |
| 96 | ModePayload _modePayload |
| 97 | ) |
| 98 | { |
| 99 | // solhint-disable-next-line no-inline-assembly |
| 100 | assembly { |
| 101 | _calltype := mode |
| 102 | _execType := shl(8, mode) |
| 103 | _modeSelector := shl(48, mode) |
| 104 | _modePayload := shl(80, mode) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | function encode( |
| 109 | CallType callType, |
| 110 | ExecType execType, |
| 111 | ModeSelector mode, |
| 112 | ModePayload payload |
| 113 | ) |
| 114 | internal |
| 115 | pure |
| 116 | returns (ModeCode) |
| 117 | { |
| 118 | return ModeCode.wrap( |
| 119 | bytes32( |
| 120 | abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload) |
| 121 | ) |
| 122 | ); |
| 123 | } |
| 124 | |
| 125 | function encodeSimpleBatch() internal pure returns (ModeCode mode) { |
| 126 | mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); |
| 127 | } |
| 128 | |
| 129 | function encodeSimpleSingle() internal pure returns (ModeCode mode) { |
| 130 | mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); |
| 131 | } |
| 132 | |
| 133 | function getCallType(ModeCode mode) internal pure returns (CallType calltype) { |
| 134 | // solhint-disable-next-line no-inline-assembly |
| 135 | assembly { |
| 136 | calltype := mode |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | using { eqModeSelector as == } for ModeSelector global; |
| 142 | using { eqCallType as == } for CallType global; |
| 143 | using { neqCallType as != } for CallType global; |
| 144 | using { eqExecType as == } for ExecType global; |
| 145 | |
| 146 | function eqCallType(CallType a, CallType b) pure returns (bool) { |
| 147 | return CallType.unwrap(a) == CallType.unwrap(b); |
| 148 | } |
| 149 | |
| 150 | function neqCallType(CallType a, CallType b) pure returns (bool) { |
| 151 | return CallType.unwrap(a) == CallType.unwrap(b); |
| 152 | } |
| 153 | |
| 154 | function eqExecType(ExecType a, ExecType b) pure returns (bool) { |
| 155 | return ExecType.unwrap(a) == ExecType.unwrap(b); |
| 156 | } |
| 157 | |
| 158 | function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { |
| 159 | return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); |
| 160 | } |
| 161 |
0.0%
lib/v2-core/lib/modulekit/src/accounts/erc7579/lib/ExecutionLib.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity >=0.8.23 <0.9.0; |
| 3 | |
| 4 | // Types |
| 5 | import { Execution } from "../../common/interfaces/IERC7579Account.sol"; |
| 6 | |
| 7 | /** |
| 8 | * Helper Library for decoding Execution calldata |
| 9 | * malloc for memory allocation is bad for gas. use this assembly instead |
| 10 | */ |
| 11 | library ExecutionLib { |
| 12 | error ERC7579DecodingError(); |
| 13 | |
| 14 | /** |
| 15 | * @notice Decode a batch of `Execution` executionBatch from a `bytes` calldata. |
| 16 | * @dev code is copied from solady's LibERC7579.sol |
| 17 | * https://github.com/Vectorized/solady/blob/740812cedc9a1fc11e17cb3d4569744367dedf19/src/accounts/LibERC7579.sol#L146 |
| 18 | * Credits to Vectorized and the Solady Team |
| 19 | */ |
| 20 | function decodeBatch(bytes calldata executionCalldata) |
| 21 | internal |
| 22 | pure |
| 23 | returns (Execution[] calldata executionBatch) |
| 24 | { |
| 25 | /// @solidity memory-safe-assembly |
| 26 | assembly { |
| 27 | let u := calldataload(executionCalldata.offset) |
| 28 | let s := add(executionCalldata.offset, u) |
| 29 | let e := sub(add(executionCalldata.offset, executionCalldata.length), 0x20) |
| 30 | executionBatch.offset := add(s, 0x20) |
| 31 | executionBatch.length := calldataload(s) |
| 32 | if or(shr(64, u), gt(add(s, shl(5, executionBatch.length)), e)) { |
| 33 | mstore(0x00, 0xba597e7e) // `DecodingError()`. |
| 34 | revert(0x1c, 0x04) |
| 35 | } |
| 36 | if executionBatch.length { |
| 37 | // Perform bounds checks on the decoded `executionBatch`. |
| 38 | // Loop runs out-of-gas if `executionBatch.length` is big enough to cause overflows. |
| 39 | for { let i := executionBatch.length } 1 { } { |
| 40 | i := sub(i, 1) |
| 41 | let p := calldataload(add(executionBatch.offset, shl(5, i))) |
| 42 | let c := add(executionBatch.offset, p) |
| 43 | let q := calldataload(add(c, 0x40)) |
| 44 | let o := add(c, q) |
| 45 | // forgefmt: disable-next-item |
| 46 | if or(shr(64, or(calldataload(o), or(p, q))), |
| 47 | or(gt(add(c, 0x40), e), gt(add(o, calldataload(o)), e))) { |
| 48 | mstore(0x00, 0xba597e7e) // `DecodingError()`. |
| 49 | revert(0x1c, 0x04) |
| 50 | } |
| 51 | if iszero(i) { break } |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | function encodeBatch(Execution[] memory executions) |
| 58 | internal |
| 59 | pure |
| 60 | returns (bytes memory callData) |
| 61 | { |
| 62 | callData = abi.encode(executions); |
| 63 | } |
| 64 | |
| 65 | function decodeSingle(bytes calldata executionCalldata) |
| 66 | internal |
| 67 | pure |
| 68 | returns (address target, uint256 value, bytes calldata callData) |
| 69 | { |
| 70 | target = address(bytes20(executionCalldata[0:20])); |
| 71 | value = uint256(bytes32(executionCalldata[20:52])); |
| 72 | callData = executionCalldata[52:]; |
| 73 | } |
| 74 | |
| 75 | function encodeSingle( |
| 76 | address target, |
| 77 | uint256 value, |
| 78 | bytes memory callData |
| 79 | ) |
| 80 | internal |
| 81 | pure |
| 82 | returns (bytes memory userOpCalldata) |
| 83 | { |
| 84 | userOpCalldata = abi.encodePacked(target, value, callData); |
| 85 | } |
| 86 | } |
| 87 |
58.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol
Lines covered: 21 / 36 (58.0%)
| 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 | 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 | _checkRole(role); |
| 65 | _; |
| 66 | } |
| 67 | |
| 68 | /// @inheritdoc IERC165 |
| 69 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { |
| 70 | 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 | 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 | 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 | function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { |
| 105 | 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 | function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { |
| 121 | _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 | function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { |
| 136 | _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 | function renounceRole(bytes32 role, address callerConfirmation) public virtual { |
| 156 | if (callerConfirmation != _msgSender()) { |
| 157 | revert AccessControlBadConfirmation(); |
| 158 | } |
| 159 | |
| 160 | _revokeRole(role, callerConfirmation); |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * @dev Sets `adminRole` as ``role``'s admin role. |
| 165 | * |
| 166 | * Emits a {RoleAdminChanged} event. |
| 167 | */ |
| 168 | function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { |
| 169 | bytes32 previousAdminRole = getRoleAdmin(role); |
| 170 | _roles[role].adminRole = adminRole; |
| 171 | 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 | function _grantRole(bytes32 role, address account) internal virtual returns (bool) { |
| 182 | if (!hasRole(role, account)) { |
| 183 | _roles[role].hasRole[account] = true; |
| 184 | emit RoleGranted(role, account, _msgSender()); |
| 185 | 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 | function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { |
| 199 | if (hasRole(role, account)) { |
| 200 | _roles[role].hasRole[account] = false; |
| 201 | emit RoleRevoked(role, account, _msgSender()); |
| 202 | return true; |
| 203 | } else { |
| 204 | return false; |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol) |
| 3 | |
| 4 | pragma solidity >=0.8.4; |
| 5 | |
| 6 | /** |
| 7 | * @dev External interface of AccessControl declared to support ERC-165 detection. |
| 8 | */ |
| 9 | interface IAccessControl { |
| 10 | /** |
| 11 | * @dev The `account` is missing a role. |
| 12 | */ |
| 13 | error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); |
| 14 | |
| 15 | /** |
| 16 | * @dev The caller of a function is not the expected one. |
| 17 | * |
| 18 | * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. |
| 19 | */ |
| 20 | error AccessControlBadConfirmation(); |
| 21 | |
| 22 | /** |
| 23 | * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` |
| 24 | * |
| 25 | * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite |
| 26 | * {RoleAdminChanged} not being emitted to signal this. |
| 27 | */ |
| 28 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); |
| 29 | |
| 30 | /** |
| 31 | * @dev Emitted when `account` is granted `role`. |
| 32 | * |
| 33 | * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role). |
| 34 | * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}. |
| 35 | */ |
| 36 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); |
| 37 | |
| 38 | /** |
| 39 | * @dev Emitted when `account` is revoked `role`. |
| 40 | * |
| 41 | * `sender` is the account that originated the contract call: |
| 42 | * - if using `revokeRole`, it is the admin role bearer |
| 43 | * - if using `renounceRole`, it is the role bearer (i.e. `account`) |
| 44 | */ |
| 45 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); |
| 46 | |
| 47 | /** |
| 48 | * @dev Returns `true` if `account` has been granted `role`. |
| 49 | */ |
| 50 | function hasRole(bytes32 role, address account) external view returns (bool); |
| 51 | |
| 52 | /** |
| 53 | * @dev Returns the admin role that controls `role`. See {grantRole} and |
| 54 | * {revokeRole}. |
| 55 | * |
| 56 | * To change a role's admin, use {AccessControl-_setRoleAdmin}. |
| 57 | */ |
| 58 | function getRoleAdmin(bytes32 role) external view returns (bytes32); |
| 59 | |
| 60 | /** |
| 61 | * @dev Grants `role` to `account`. |
| 62 | * |
| 63 | * If `account` had not been already granted `role`, emits a {RoleGranted} |
| 64 | * event. |
| 65 | * |
| 66 | * Requirements: |
| 67 | * |
| 68 | * - the caller must have ``role``'s admin role. |
| 69 | */ |
| 70 | function grantRole(bytes32 role, address account) external; |
| 71 | |
| 72 | /** |
| 73 | * @dev Revokes `role` from `account`. |
| 74 | * |
| 75 | * If `account` had been granted `role`, emits a {RoleRevoked} event. |
| 76 | * |
| 77 | * Requirements: |
| 78 | * |
| 79 | * - the caller must have ``role``'s admin role. |
| 80 | */ |
| 81 | function revokeRole(bytes32 role, address account) external; |
| 82 | |
| 83 | /** |
| 84 | * @dev Revokes `role` from the calling account. |
| 85 | * |
| 86 | * Roles are often managed via {grantRole} and {revokeRole}: this function's |
| 87 | * purpose is to provide a mechanism for accounts to lose their privileges |
| 88 | * if they are compromised (such as when a trusted device is misplaced). |
| 89 | * |
| 90 | * If the calling account had been granted `role`, emits a {RoleRevoked} |
| 91 | * event. |
| 92 | * |
| 93 | * Requirements: |
| 94 | * |
| 95 | * - the caller must be `callerConfirmation`. |
| 96 | */ |
| 97 | function renounceRole(bytes32 role, address callerConfirmation) external; |
| 98 | } |
| 99 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol) |
| 3 | |
| 4 | pragma solidity >=0.6.2; |
| 5 | |
| 6 | import {IERC20} from "./IERC20.sol"; |
| 7 | import {IERC165} from "./IERC165.sol"; |
| 8 | |
| 9 | /** |
| 10 | * @title IERC1363 |
| 11 | * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363]. |
| 12 | * |
| 13 | * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract |
| 14 | * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction. |
| 15 | */ |
| 16 | interface IERC1363 is IERC20, IERC165 { |
| 17 | /* |
| 18 | * Note: the ERC-165 identifier for this interface is 0xb0202a11. |
| 19 | * 0xb0202a11 === |
| 20 | * bytes4(keccak256('transferAndCall(address,uint256)')) ^ |
| 21 | * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^ |
| 22 | * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^ |
| 23 | * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^ |
| 24 | * bytes4(keccak256('approveAndCall(address,uint256)')) ^ |
| 25 | * bytes4(keccak256('approveAndCall(address,uint256,bytes)')) |
| 26 | */ |
| 27 | |
| 28 | /** |
| 29 | * @dev Moves a `value` amount of tokens from the caller's account to `to` |
| 30 | * and then calls {IERC1363Receiver-onTransferReceived} on `to`. |
| 31 | * @param to The address which you want to transfer to. |
| 32 | * @param value The amount of tokens to be transferred. |
| 33 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 34 | */ |
| 35 | function transferAndCall(address to, uint256 value) external returns (bool); |
| 36 | |
| 37 | /** |
| 38 | * @dev Moves a `value` amount of tokens from the caller's account to `to` |
| 39 | * and then calls {IERC1363Receiver-onTransferReceived} on `to`. |
| 40 | * @param to The address which you want to transfer to. |
| 41 | * @param value The amount of tokens to be transferred. |
| 42 | * @param data Additional data with no specified format, sent in call to `to`. |
| 43 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 44 | */ |
| 45 | function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool); |
| 46 | |
| 47 | /** |
| 48 | * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism |
| 49 | * and then calls {IERC1363Receiver-onTransferReceived} on `to`. |
| 50 | * @param from The address which you want to send tokens from. |
| 51 | * @param to The address which you want to transfer to. |
| 52 | * @param value The amount of tokens to be transferred. |
| 53 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 54 | */ |
| 55 | function transferFromAndCall(address from, address to, uint256 value) external returns (bool); |
| 56 | |
| 57 | /** |
| 58 | * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism |
| 59 | * and then calls {IERC1363Receiver-onTransferReceived} on `to`. |
| 60 | * @param from The address which you want to send tokens from. |
| 61 | * @param to The address which you want to transfer to. |
| 62 | * @param value The amount of tokens to be transferred. |
| 63 | * @param data Additional data with no specified format, sent in call to `to`. |
| 64 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 65 | */ |
| 66 | function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool); |
| 67 | |
| 68 | /** |
| 69 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
| 70 | * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. |
| 71 | * @param spender The address which will spend the funds. |
| 72 | * @param value The amount of tokens to be spent. |
| 73 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 74 | */ |
| 75 | function approveAndCall(address spender, uint256 value) external returns (bool); |
| 76 | |
| 77 | /** |
| 78 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
| 79 | * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. |
| 80 | * @param spender The address which will spend the funds. |
| 81 | * @param value The amount of tokens to be spent. |
| 82 | * @param data Additional data with no specified format, sent in call to `spender`. |
| 83 | * @return A boolean value indicating whether the operation succeeded unless throwing. |
| 84 | */ |
| 85 | function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool); |
| 86 | } |
| 87 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol) |
| 3 | |
| 4 | pragma solidity >=0.4.16; |
| 5 | |
| 6 | import {IERC165} from "../utils/introspection/IERC165.sol"; |
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) |
| 3 | |
| 4 | pragma solidity >=0.4.16; |
| 5 | |
| 6 | import {IERC20} from "../token/ERC20/IERC20.sol"; |
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20Metadata.sol) |
| 3 | |
| 4 | pragma solidity >=0.6.2; |
| 5 | |
| 6 | import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; |
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol) |
| 3 | |
| 4 | pragma solidity >=0.6.2; |
| 5 | |
| 6 | import {IERC20} from "../token/ERC20/IERC20.sol"; |
| 7 | import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; |
| 8 | |
| 9 | /** |
| 10 | * @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in |
| 11 | * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. |
| 12 | */ |
| 13 | interface IERC4626 is IERC20, IERC20Metadata { |
| 14 | event Deposit(address indexed sender, address indexed owner, uint256 assets, 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 | /** |
| 25 | * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
| 26 | * |
| 27 | * - MUST be an ERC-20 token contract. |
| 28 | * - MUST NOT revert. |
| 29 | */ |
| 30 | function asset() external view returns (address assetTokenAddress); |
| 31 | |
| 32 | /** |
| 33 | * @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
| 34 | * |
| 35 | * - SHOULD include any compounding that occurs from yield. |
| 36 | * - MUST be inclusive of any fees that are charged against assets in the Vault. |
| 37 | * - MUST NOT revert. |
| 38 | */ |
| 39 | function totalAssets() external view returns (uint256 totalManagedAssets); |
| 40 | |
| 41 | /** |
| 42 | * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
| 43 | * scenario where all the conditions are met. |
| 44 | * |
| 45 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 46 | * - MUST NOT show any variations depending on the caller. |
| 47 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 48 | * - MUST NOT revert. |
| 49 | * |
| 50 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 51 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 52 | * from. |
| 53 | */ |
| 54 | function convertToShares(uint256 assets) external view returns (uint256 shares); |
| 55 | |
| 56 | /** |
| 57 | * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
| 58 | * scenario where all the conditions are met. |
| 59 | * |
| 60 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 61 | * - MUST NOT show any variations depending on the caller. |
| 62 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 63 | * - MUST NOT revert. |
| 64 | * |
| 65 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 66 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 67 | * from. |
| 68 | */ |
| 69 | function convertToAssets(uint256 shares) external view returns (uint256 assets); |
| 70 | |
| 71 | /** |
| 72 | * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
| 73 | * through a deposit call. |
| 74 | * |
| 75 | * - MUST return a limited value if receiver is subject to some deposit limit. |
| 76 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
| 77 | * - MUST NOT revert. |
| 78 | */ |
| 79 | function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
| 80 | |
| 81 | /** |
| 82 | * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
| 83 | * current on-chain conditions. |
| 84 | * |
| 85 | * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
| 86 | * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
| 87 | * in the same transaction. |
| 88 | * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
| 89 | * deposit would be accepted, regardless if the user has enough tokens approved, etc. |
| 90 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 91 | * - MUST NOT revert. |
| 92 | * |
| 93 | * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
| 94 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 95 | */ |
| 96 | function previewDeposit(uint256 assets) external view returns (uint256 shares); |
| 97 | |
| 98 | /** |
| 99 | * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
| 100 | * |
| 101 | * - MUST emit the Deposit event. |
| 102 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 103 | * deposit execution, and are accounted for during deposit. |
| 104 | * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
| 105 | * approving enough underlying tokens to the Vault contract, etc). |
| 106 | * |
| 107 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 108 | */ |
| 109 | function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
| 110 | |
| 111 | /** |
| 112 | * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
| 113 | * - MUST return a limited value if receiver is subject to some mint limit. |
| 114 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
| 115 | * - MUST NOT revert. |
| 116 | */ |
| 117 | function maxMint(address receiver) external view returns (uint256 maxShares); |
| 118 | |
| 119 | /** |
| 120 | * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
| 121 | * current on-chain conditions. |
| 122 | * |
| 123 | * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
| 124 | * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
| 125 | * same transaction. |
| 126 | * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
| 127 | * would be accepted, regardless if the user has enough tokens approved, etc. |
| 128 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 129 | * - MUST NOT revert. |
| 130 | * |
| 131 | * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
| 132 | * share price or some other type of condition, meaning the depositor will lose assets by minting. |
| 133 | */ |
| 134 | function previewMint(uint256 shares) external view returns (uint256 assets); |
| 135 | |
| 136 | /** |
| 137 | * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
| 138 | * |
| 139 | * - MUST emit the Deposit event. |
| 140 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
| 141 | * execution, and are accounted for during mint. |
| 142 | * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
| 143 | * approving enough underlying tokens to the Vault contract, etc). |
| 144 | * |
| 145 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 146 | */ |
| 147 | function mint(uint256 shares, address receiver) external returns (uint256 assets); |
| 148 | |
| 149 | /** |
| 150 | * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
| 151 | * Vault, through a withdraw call. |
| 152 | * |
| 153 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 154 | * - MUST NOT revert. |
| 155 | */ |
| 156 | function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
| 157 | |
| 158 | /** |
| 159 | * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
| 160 | * given current on-chain conditions. |
| 161 | * |
| 162 | * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
| 163 | * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
| 164 | * called |
| 165 | * in the same transaction. |
| 166 | * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
| 167 | * the withdrawal would be accepted, regardless if the user has enough shares, etc. |
| 168 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 169 | * - MUST NOT revert. |
| 170 | * |
| 171 | * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
| 172 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 173 | */ |
| 174 | function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
| 175 | |
| 176 | /** |
| 177 | * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
| 178 | * |
| 179 | * - MUST emit the Withdraw event. |
| 180 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 181 | * withdraw execution, and are accounted for during withdraw. |
| 182 | * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
| 183 | * not having enough shares, etc). |
| 184 | * |
| 185 | * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 186 | * Those methods should be performed separately. |
| 187 | */ |
| 188 | function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
| 189 | |
| 190 | /** |
| 191 | * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
| 192 | * through a redeem call. |
| 193 | * |
| 194 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 195 | * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
| 196 | * - MUST NOT revert. |
| 197 | */ |
| 198 | function maxRedeem(address owner) external view returns (uint256 maxShares); |
| 199 | |
| 200 | /** |
| 201 | * @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, |
| 202 | * given current on-chain conditions. |
| 203 | * |
| 204 | * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
| 205 | * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
| 206 | * same transaction. |
| 207 | * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
| 208 | * redemption would be accepted, regardless if the user has enough shares, etc. |
| 209 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 210 | * - MUST NOT revert. |
| 211 | * |
| 212 | * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
| 213 | * share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
| 214 | */ |
| 215 | function previewRedeem(uint256 shares) external view returns (uint256 assets); |
| 216 | |
| 217 | /** |
| 218 | * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
| 219 | * |
| 220 | * - MUST emit the Withdraw event. |
| 221 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 222 | * redeem execution, and are accounted for during redeem. |
| 223 | * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
| 224 | * not having enough shares, etc). |
| 225 | * |
| 226 | * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 227 | * Those methods should be performed separately. |
| 228 | */ |
| 229 | function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
| 230 | } |
| 231 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol) |
| 3 | |
| 4 | pragma solidity >=0.4.16; |
| 5 | |
| 6 | interface IERC5267 { |
| 7 | /** |
| 8 | * @dev MAY be emitted to signal that the domain could have changed. |
| 9 | */ |
| 10 | event EIP712DomainChanged(); |
| 11 | |
| 12 | /** |
| 13 | * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 |
| 14 | * signature. |
| 15 | */ |
| 16 | function eip712Domain() |
| 17 | external |
| 18 | view |
| 19 | returns ( |
| 20 | bytes1 fields, |
| 21 | string memory name, |
| 22 | string memory version, |
| 23 | uint256 chainId, |
| 24 | address verifyingContract, |
| 25 | bytes32 salt, |
| 26 | uint256[] memory extensions |
| 27 | ); |
| 28 | } |
| 29 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol) |
| 3 | pragma solidity >=0.8.4; |
| 4 | |
| 5 | /** |
| 6 | * @dev Standard ERC-20 Errors |
| 7 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens. |
| 8 | */ |
| 9 | interface IERC20Errors { |
| 10 | /** |
| 11 | * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. |
| 12 | * @param sender Address whose tokens are being transferred. |
| 13 | * @param balance Current balance for the interacting account. |
| 14 | * @param needed Minimum amount required to perform a transfer. |
| 15 | */ |
| 16 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); |
| 17 | |
| 18 | /** |
| 19 | * @dev Indicates a failure with the token `sender`. Used in transfers. |
| 20 | * @param sender Address whose tokens are being transferred. |
| 21 | */ |
| 22 | error ERC20InvalidSender(address sender); |
| 23 | |
| 24 | /** |
| 25 | * @dev Indicates a failure with the token `receiver`. Used in transfers. |
| 26 | * @param receiver Address to which tokens are being transferred. |
| 27 | */ |
| 28 | error ERC20InvalidReceiver(address receiver); |
| 29 | |
| 30 | /** |
| 31 | * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. |
| 32 | * @param spender Address that may be allowed to operate on tokens without being their owner. |
| 33 | * @param allowance Amount of tokens a `spender` is allowed to operate with. |
| 34 | * @param needed Minimum amount required to perform a transfer. |
| 35 | */ |
| 36 | error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); |
| 37 | |
| 38 | /** |
| 39 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
| 40 | * @param approver Address initiating an approval operation. |
| 41 | */ |
| 42 | error ERC20InvalidApprover(address approver); |
| 43 | |
| 44 | /** |
| 45 | * @dev Indicates a failure with the `spender` to be approved. Used in approvals. |
| 46 | * @param spender Address that may be allowed to operate on tokens without being their owner. |
| 47 | */ |
| 48 | error ERC20InvalidSpender(address spender); |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * @dev Standard ERC-721 Errors |
| 53 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens. |
| 54 | */ |
| 55 | interface IERC721Errors { |
| 56 | /** |
| 57 | * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20. |
| 58 | * Used in balance queries. |
| 59 | * @param owner Address of the current owner of a token. |
| 60 | */ |
| 61 | error ERC721InvalidOwner(address owner); |
| 62 | |
| 63 | /** |
| 64 | * @dev Indicates a `tokenId` whose `owner` is the zero address. |
| 65 | * @param tokenId Identifier number of a token. |
| 66 | */ |
| 67 | error ERC721NonexistentToken(uint256 tokenId); |
| 68 | |
| 69 | /** |
| 70 | * @dev Indicates an error related to the ownership over a particular token. Used in transfers. |
| 71 | * @param sender Address whose tokens are being transferred. |
| 72 | * @param tokenId Identifier number of a token. |
| 73 | * @param owner Address of the current owner of a token. |
| 74 | */ |
| 75 | error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); |
| 76 | |
| 77 | /** |
| 78 | * @dev Indicates a failure with the token `sender`. Used in transfers. |
| 79 | * @param sender Address whose tokens are being transferred. |
| 80 | */ |
| 81 | error ERC721InvalidSender(address sender); |
| 82 | |
| 83 | /** |
| 84 | * @dev Indicates a failure with the token `receiver`. Used in transfers. |
| 85 | * @param receiver Address to which tokens are being transferred. |
| 86 | */ |
| 87 | error ERC721InvalidReceiver(address receiver); |
| 88 | |
| 89 | /** |
| 90 | * @dev Indicates a failure with the `operator`’s approval. Used in transfers. |
| 91 | * @param operator Address that may be allowed to operate on tokens without being their owner. |
| 92 | * @param tokenId Identifier number of a token. |
| 93 | */ |
| 94 | error ERC721InsufficientApproval(address operator, uint256 tokenId); |
| 95 | |
| 96 | /** |
| 97 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
| 98 | * @param approver Address initiating an approval operation. |
| 99 | */ |
| 100 | error ERC721InvalidApprover(address approver); |
| 101 | |
| 102 | /** |
| 103 | * @dev Indicates a failure with the `operator` to be approved. Used in approvals. |
| 104 | * @param operator Address that may be allowed to operate on tokens without being their owner. |
| 105 | */ |
| 106 | error ERC721InvalidOperator(address operator); |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * @dev Standard ERC-1155 Errors |
| 111 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens. |
| 112 | */ |
| 113 | interface IERC1155Errors { |
| 114 | /** |
| 115 | * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. |
| 116 | * @param sender Address whose tokens are being transferred. |
| 117 | * @param balance Current balance for the interacting account. |
| 118 | * @param needed Minimum amount required to perform a transfer. |
| 119 | * @param tokenId Identifier number of a token. |
| 120 | */ |
| 121 | error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); |
| 122 | |
| 123 | /** |
| 124 | * @dev Indicates a failure with the token `sender`. Used in transfers. |
| 125 | * @param sender Address whose tokens are being transferred. |
| 126 | */ |
| 127 | error ERC1155InvalidSender(address sender); |
| 128 | |
| 129 | /** |
| 130 | * @dev Indicates a failure with the token `receiver`. Used in transfers. |
| 131 | * @param receiver Address to which tokens are being transferred. |
| 132 | */ |
| 133 | error ERC1155InvalidReceiver(address receiver); |
| 134 | |
| 135 | /** |
| 136 | * @dev Indicates a failure with the `operator`’s approval. Used in transfers. |
| 137 | * @param operator Address that may be allowed to operate on tokens without being their owner. |
| 138 | * @param owner Address of the current owner of a token. |
| 139 | */ |
| 140 | error ERC1155MissingApprovalForAll(address operator, address owner); |
| 141 | |
| 142 | /** |
| 143 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
| 144 | * @param approver Address initiating an approval operation. |
| 145 | */ |
| 146 | error ERC1155InvalidApprover(address approver); |
| 147 | |
| 148 | /** |
| 149 | * @dev Indicates a failure with the `operator` to be approved. Used in approvals. |
| 150 | * @param operator Address that may be allowed to operate on tokens without being their owner. |
| 151 | */ |
| 152 | error ERC1155InvalidOperator(address operator); |
| 153 | |
| 154 | /** |
| 155 | * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. |
| 156 | * Used in batch transfers. |
| 157 | * @param idsLength Length of the array of token identifiers |
| 158 | * @param valuesLength Length of the array of token amounts |
| 159 | */ |
| 160 | error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); |
| 161 | } |
| 162 |
75.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol
Lines covered: 9 / 12 (75.0%)
| 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 | 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 | function clone(address implementation) internal returns (address instance) { |
| 29 | 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 | function clone(address implementation, uint256 value) internal returns (address instance) { |
| 40 | if (address(this).balance < value) { |
| 41 | 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 | mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) |
| 47 | // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. |
| 48 | mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) |
| 49 | instance := create(value, 0x09, 0x37) |
| 50 | } |
| 51 | if (instance == address(0)) { |
| 52 | 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 | 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 | revert Errors.FailedDeployment(); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. |
| 97 | */ |
| 98 | function predictDeterministicAddress( |
| 99 | address implementation, |
| 100 | bytes32 salt, |
| 101 | address deployer |
| 102 | ) internal pure returns (address predicted) { |
| 103 | assembly ("memory-safe") { |
| 104 | let ptr := mload(0x40) |
| 105 | mstore(add(ptr, 0x38), deployer) |
| 106 | mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) |
| 107 | mstore(add(ptr, 0x14), implementation) |
| 108 | mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) |
| 109 | mstore(add(ptr, 0x58), salt) |
| 110 | mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) |
| 111 | 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 | } |
| 263 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) |
| 3 | |
| 4 | pragma solidity >=0.4.16; |
| 5 | |
| 6 | /** |
| 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. |
| 8 | */ |
| 9 | interface IERC20 { |
| 10 | /** |
| 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to |
| 12 | * another (`to`). |
| 13 | * |
| 14 | * Note that `value` may be zero. |
| 15 | */ |
| 16 | event Transfer(address indexed from, address indexed to, uint256 value); |
| 17 | |
| 18 | /** |
| 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by |
| 20 | * a call to {approve}. `value` is the new allowance. |
| 21 | */ |
| 22 | event Approval(address indexed owner, address indexed spender, uint256 value); |
| 23 | |
| 24 | /** |
| 25 | * @dev Returns the value of tokens in existence. |
| 26 | */ |
| 27 | function totalSupply() external view returns (uint256); |
| 28 | |
| 29 | /** |
| 30 | * @dev Returns the value of tokens owned by `account`. |
| 31 | */ |
| 32 | function balanceOf(address account) external view returns (uint256); |
| 33 | |
| 34 | /** |
| 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. |
| 36 | * |
| 37 | * Returns a boolean value indicating whether the operation succeeded. |
| 38 | * |
| 39 | * Emits a {Transfer} event. |
| 40 | */ |
| 41 | function transfer(address to, uint256 value) external returns (bool); |
| 42 | |
| 43 | /** |
| 44 | * @dev Returns the remaining number of tokens that `spender` will be |
| 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is |
| 46 | * zero by default. |
| 47 | * |
| 48 | * This value changes when {approve} or {transferFrom} are called. |
| 49 | */ |
| 50 | function allowance(address owner, address spender) external view returns (uint256); |
| 51 | |
| 52 | /** |
| 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
| 54 | * caller's tokens. |
| 55 | * |
| 56 | * Returns a boolean value indicating whether the operation succeeded. |
| 57 | * |
| 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk |
| 59 | * that someone may use both the old and the new allowance by unfortunate |
| 60 | * transaction ordering. One possible solution to mitigate this race |
| 61 | * condition is to first reduce the spender's allowance to 0 and set the |
| 62 | * desired value afterwards: |
| 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 |
| 64 | * |
| 65 | * Emits an {Approval} event. |
| 66 | */ |
| 67 | function approve(address spender, uint256 value) external returns (bool); |
| 68 | |
| 69 | /** |
| 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the |
| 71 | * allowance mechanism. `value` is then deducted from the caller's |
| 72 | * allowance. |
| 73 | * |
| 74 | * Returns a boolean value indicating whether the operation succeeded. |
| 75 | * |
| 76 | * Emits a {Transfer} event. |
| 77 | */ |
| 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); |
| 79 | } |
| 80 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol) |
| 3 | |
| 4 | pragma solidity >=0.6.2; |
| 5 | |
| 6 | import {IERC20} from "../IERC20.sol"; |
| 7 | |
| 8 | /** |
| 9 | * @dev Interface for the optional metadata functions from the ERC-20 standard. |
| 10 | */ |
| 11 | interface IERC20Metadata is IERC20 { |
| 12 | /** |
| 13 | * @dev Returns the name of the token. |
| 14 | */ |
| 15 | function name() external view returns (string memory); |
| 16 | |
| 17 | /** |
| 18 | * @dev Returns the symbol of the token. |
| 19 | */ |
| 20 | function symbol() external view returns (string memory); |
| 21 | |
| 22 | /** |
| 23 | * @dev Returns the decimals places of the token. |
| 24 | */ |
| 25 | function decimals() external view returns (uint8); |
| 26 | } |
| 27 |
94.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
Lines covered: 16 / 17 (94.0%)
| 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 | 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 | _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 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { |
| 42 | _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 | let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) |
| 178 | // bubble errors |
| 179 | if iszero(success) { |
| 180 | let ptr := mload(0x40) |
| 181 | returndatacopy(ptr, 0, returndatasize()) |
| 182 | revert(ptr, returndatasize()) |
| 183 | } |
| 184 | returnSize := returndatasize() |
| 185 | returnValue := mload(0) |
| 186 | } |
| 187 | |
| 188 | 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 | } |
| 213 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/Arrays.sol) |
| 3 | // This file was procedurally generated from scripts/generate/templates/Arrays.js. |
| 4 | |
| 5 | pragma solidity ^0.8.20; |
| 6 | |
| 7 | import {Comparators} from "./Comparators.sol"; |
| 8 | import {SlotDerivation} from "./SlotDerivation.sol"; |
| 9 | import {StorageSlot} from "./StorageSlot.sol"; |
| 10 | import {Math} from "./math/Math.sol"; |
| 11 | |
| 12 | /** |
| 13 | * @dev Collection of functions related to array types. |
| 14 | */ |
| 15 | library Arrays { |
| 16 | using SlotDerivation for bytes32; |
| 17 | using StorageSlot for bytes32; |
| 18 | |
| 19 | /** |
| 20 | * @dev Sort an array of uint256 (in memory) following the provided comparator function. |
| 21 | * |
| 22 | * This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
| 23 | * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
| 24 | * |
| 25 | * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
| 26 | * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
| 27 | * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
| 28 | * consume more gas than is available in a block, leading to potential DoS. |
| 29 | * |
| 30 | * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
| 31 | */ |
| 32 | function sort( |
| 33 | uint256[] memory array, |
| 34 | function(uint256, uint256) pure returns (bool) comp |
| 35 | ) internal pure returns (uint256[] memory) { |
| 36 | _quickSort(_begin(array), _end(array), comp); |
| 37 | return array; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * @dev Variant of {sort} that sorts an array of uint256 in increasing order. |
| 42 | */ |
| 43 | function sort(uint256[] memory array) internal pure returns (uint256[] memory) { |
| 44 | sort(array, Comparators.lt); |
| 45 | return array; |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * @dev Sort an array of address (in memory) following the provided comparator function. |
| 50 | * |
| 51 | * This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
| 52 | * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
| 53 | * |
| 54 | * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
| 55 | * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
| 56 | * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
| 57 | * consume more gas than is available in a block, leading to potential DoS. |
| 58 | * |
| 59 | * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
| 60 | */ |
| 61 | function sort( |
| 62 | address[] memory array, |
| 63 | function(address, address) pure returns (bool) comp |
| 64 | ) internal pure returns (address[] memory) { |
| 65 | sort(_castToUint256Array(array), _castToUint256Comp(comp)); |
| 66 | return array; |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * @dev Variant of {sort} that sorts an array of address in increasing order. |
| 71 | */ |
| 72 | function sort(address[] memory array) internal pure returns (address[] memory) { |
| 73 | sort(_castToUint256Array(array), Comparators.lt); |
| 74 | return array; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * @dev Sort an array of bytes32 (in memory) following the provided comparator function. |
| 79 | * |
| 80 | * This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
| 81 | * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
| 82 | * |
| 83 | * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
| 84 | * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
| 85 | * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
| 86 | * consume more gas than is available in a block, leading to potential DoS. |
| 87 | * |
| 88 | * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
| 89 | */ |
| 90 | function sort( |
| 91 | bytes32[] memory array, |
| 92 | function(bytes32, bytes32) pure returns (bool) comp |
| 93 | ) internal pure returns (bytes32[] memory) { |
| 94 | sort(_castToUint256Array(array), _castToUint256Comp(comp)); |
| 95 | return array; |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * @dev Variant of {sort} that sorts an array of bytes32 in increasing order. |
| 100 | */ |
| 101 | function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) { |
| 102 | sort(_castToUint256Array(array), Comparators.lt); |
| 103 | return array; |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops |
| 108 | * at end (exclusive). Sorting follows the `comp` comparator. |
| 109 | * |
| 110 | * Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls. |
| 111 | * |
| 112 | * IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should |
| 113 | * be used only if the limits are within a memory array. |
| 114 | */ |
| 115 | function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure { |
| 116 | unchecked { |
| 117 | if (end - begin < 0x40) return; |
| 118 | |
| 119 | // Use first element as pivot |
| 120 | uint256 pivot = _mload(begin); |
| 121 | // Position where the pivot should be at the end of the loop |
| 122 | uint256 pos = begin; |
| 123 | |
| 124 | for (uint256 it = begin + 0x20; it < end; it += 0x20) { |
| 125 | if (comp(_mload(it), pivot)) { |
| 126 | // If the value stored at the iterator's position comes before the pivot, we increment the |
| 127 | // position of the pivot and move the value there. |
| 128 | pos += 0x20; |
| 129 | _swap(pos, it); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | _swap(begin, pos); // Swap pivot into place |
| 134 | _quickSort(begin, pos, comp); // Sort the left side of the pivot |
| 135 | _quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * @dev Pointer to the memory location of the first element of `array`. |
| 141 | */ |
| 142 | function _begin(uint256[] memory array) private pure returns (uint256 ptr) { |
| 143 | assembly ("memory-safe") { |
| 144 | ptr := add(array, 0x20) |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word |
| 150 | * that comes just after the last element of the array. |
| 151 | */ |
| 152 | function _end(uint256[] memory array) private pure returns (uint256 ptr) { |
| 153 | unchecked { |
| 154 | return _begin(array) + array.length * 0x20; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * @dev Load memory word (as a uint256) at location `ptr`. |
| 160 | */ |
| 161 | function _mload(uint256 ptr) private pure returns (uint256 value) { |
| 162 | assembly { |
| 163 | value := mload(ptr) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * @dev Swaps the elements memory location `ptr1` and `ptr2`. |
| 169 | */ |
| 170 | function _swap(uint256 ptr1, uint256 ptr2) private pure { |
| 171 | assembly { |
| 172 | let value1 := mload(ptr1) |
| 173 | let value2 := mload(ptr2) |
| 174 | mstore(ptr1, value2) |
| 175 | mstore(ptr2, value1) |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /// @dev Helper: low level cast address memory array to uint256 memory array |
| 180 | function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) { |
| 181 | assembly { |
| 182 | output := input |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | /// @dev Helper: low level cast bytes32 memory array to uint256 memory array |
| 187 | function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) { |
| 188 | assembly { |
| 189 | output := input |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | /// @dev Helper: low level cast address comp function to uint256 comp function |
| 194 | function _castToUint256Comp( |
| 195 | function(address, address) pure returns (bool) input |
| 196 | ) private pure returns (function(uint256, uint256) pure returns (bool) output) { |
| 197 | assembly { |
| 198 | output := input |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /// @dev Helper: low level cast bytes32 comp function to uint256 comp function |
| 203 | function _castToUint256Comp( |
| 204 | function(bytes32, bytes32) pure returns (bool) input |
| 205 | ) private pure returns (function(uint256, uint256) pure returns (bool) output) { |
| 206 | assembly { |
| 207 | output := input |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * @dev Searches a sorted `array` and returns the first index that contains |
| 213 | * a value greater or equal to `element`. If no such index exists (i.e. all |
| 214 | * values in the array are strictly less than `element`), the array length is |
| 215 | * returned. Time complexity O(log n). |
| 216 | * |
| 217 | * NOTE: The `array` is expected to be sorted in ascending order, and to |
| 218 | * contain no repeated elements. |
| 219 | * |
| 220 | * IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks |
| 221 | * support for repeated elements in the array. The {lowerBound} function should |
| 222 | * be used instead. |
| 223 | */ |
| 224 | function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { |
| 225 | uint256 low = 0; |
| 226 | uint256 high = array.length; |
| 227 | |
| 228 | if (high == 0) { |
| 229 | return 0; |
| 230 | } |
| 231 | |
| 232 | while (low < high) { |
| 233 | uint256 mid = Math.average(low, high); |
| 234 | |
| 235 | // Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
| 236 | // because Math.average rounds towards zero (it does integer division with truncation). |
| 237 | if (unsafeAccess(array, mid).value > element) { |
| 238 | high = mid; |
| 239 | } else { |
| 240 | low = mid + 1; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. |
| 245 | if (low > 0 && unsafeAccess(array, low - 1).value == element) { |
| 246 | return low - 1; |
| 247 | } else { |
| 248 | return low; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * @dev Searches an `array` sorted in ascending order and returns the first |
| 254 | * index that contains a value greater or equal than `element`. If no such index |
| 255 | * exists (i.e. all values in the array are strictly less than `element`), the array |
| 256 | * length is returned. Time complexity O(log n). |
| 257 | * |
| 258 | * See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound]. |
| 259 | */ |
| 260 | function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) { |
| 261 | uint256 low = 0; |
| 262 | uint256 high = array.length; |
| 263 | |
| 264 | if (high == 0) { |
| 265 | return 0; |
| 266 | } |
| 267 | |
| 268 | while (low < high) { |
| 269 | uint256 mid = Math.average(low, high); |
| 270 | |
| 271 | // Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
| 272 | // because Math.average rounds towards zero (it does integer division with truncation). |
| 273 | if (unsafeAccess(array, mid).value < element) { |
| 274 | // this cannot overflow because mid < high |
| 275 | unchecked { |
| 276 | low = mid + 1; |
| 277 | } |
| 278 | } else { |
| 279 | high = mid; |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | return low; |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * @dev Searches an `array` sorted in ascending order and returns the first |
| 288 | * index that contains a value strictly greater than `element`. If no such index |
| 289 | * exists (i.e. all values in the array are strictly less than `element`), the array |
| 290 | * length is returned. Time complexity O(log n). |
| 291 | * |
| 292 | * See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound]. |
| 293 | */ |
| 294 | function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { |
| 295 | uint256 low = 0; |
| 296 | uint256 high = array.length; |
| 297 | |
| 298 | if (high == 0) { |
| 299 | return 0; |
| 300 | } |
| 301 | |
| 302 | while (low < high) { |
| 303 | uint256 mid = Math.average(low, high); |
| 304 | |
| 305 | // Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
| 306 | // because Math.average rounds towards zero (it does integer division with truncation). |
| 307 | if (unsafeAccess(array, mid).value > element) { |
| 308 | high = mid; |
| 309 | } else { |
| 310 | // this cannot overflow because mid < high |
| 311 | unchecked { |
| 312 | low = mid + 1; |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | return low; |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * @dev Same as {lowerBound}, but with an array in memory. |
| 322 | */ |
| 323 | function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) { |
| 324 | uint256 low = 0; |
| 325 | uint256 high = array.length; |
| 326 | |
| 327 | if (high == 0) { |
| 328 | return 0; |
| 329 | } |
| 330 | |
| 331 | while (low < high) { |
| 332 | uint256 mid = Math.average(low, high); |
| 333 | |
| 334 | // Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
| 335 | // because Math.average rounds towards zero (it does integer division with truncation). |
| 336 | if (unsafeMemoryAccess(array, mid) < element) { |
| 337 | // this cannot overflow because mid < high |
| 338 | unchecked { |
| 339 | low = mid + 1; |
| 340 | } |
| 341 | } else { |
| 342 | high = mid; |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | return low; |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * @dev Same as {upperBound}, but with an array in memory. |
| 351 | */ |
| 352 | function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) { |
| 353 | uint256 low = 0; |
| 354 | uint256 high = array.length; |
| 355 | |
| 356 | if (high == 0) { |
| 357 | return 0; |
| 358 | } |
| 359 | |
| 360 | while (low < high) { |
| 361 | uint256 mid = Math.average(low, high); |
| 362 | |
| 363 | // Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
| 364 | // because Math.average rounds towards zero (it does integer division with truncation). |
| 365 | if (unsafeMemoryAccess(array, mid) > element) { |
| 366 | high = mid; |
| 367 | } else { |
| 368 | // this cannot overflow because mid < high |
| 369 | unchecked { |
| 370 | low = mid + 1; |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | return low; |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 380 | * |
| 381 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 382 | */ |
| 383 | function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) { |
| 384 | bytes32 slot; |
| 385 | assembly ("memory-safe") { |
| 386 | slot := arr.slot |
| 387 | } |
| 388 | return slot.deriveArray().offset(pos).getAddressSlot(); |
| 389 | } |
| 390 | |
| 391 | /** |
| 392 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 393 | * |
| 394 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 395 | */ |
| 396 | function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) { |
| 397 | bytes32 slot; |
| 398 | assembly ("memory-safe") { |
| 399 | slot := arr.slot |
| 400 | } |
| 401 | return slot.deriveArray().offset(pos).getBytes32Slot(); |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 406 | * |
| 407 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 408 | */ |
| 409 | function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) { |
| 410 | bytes32 slot; |
| 411 | assembly ("memory-safe") { |
| 412 | slot := arr.slot |
| 413 | } |
| 414 | return slot.deriveArray().offset(pos).getUint256Slot(); |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 419 | * |
| 420 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 421 | */ |
| 422 | function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) { |
| 423 | bytes32 slot; |
| 424 | assembly ("memory-safe") { |
| 425 | slot := arr.slot |
| 426 | } |
| 427 | return slot.deriveArray().offset(pos).getBytesSlot(); |
| 428 | } |
| 429 | |
| 430 | /** |
| 431 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 432 | * |
| 433 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 434 | */ |
| 435 | function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) { |
| 436 | bytes32 slot; |
| 437 | assembly ("memory-safe") { |
| 438 | slot := arr.slot |
| 439 | } |
| 440 | return slot.deriveArray().offset(pos).getStringSlot(); |
| 441 | } |
| 442 | |
| 443 | /** |
| 444 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 445 | * |
| 446 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 447 | */ |
| 448 | function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { |
| 449 | assembly { |
| 450 | res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | /** |
| 455 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 456 | * |
| 457 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 458 | */ |
| 459 | function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) { |
| 460 | assembly { |
| 461 | res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | /** |
| 466 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 467 | * |
| 468 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 469 | */ |
| 470 | function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { |
| 471 | assembly { |
| 472 | res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | /** |
| 477 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 478 | * |
| 479 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 480 | */ |
| 481 | function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) { |
| 482 | assembly { |
| 483 | res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | /** |
| 488 | * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
| 489 | * |
| 490 | * WARNING: Only use if you are certain `pos` is lower than the array length. |
| 491 | */ |
| 492 | function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) { |
| 493 | assembly { |
| 494 | res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | /** |
| 499 | * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
| 500 | * |
| 501 | * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
| 502 | */ |
| 503 | function unsafeSetLength(address[] storage array, uint256 len) internal { |
| 504 | assembly ("memory-safe") { |
| 505 | sstore(array.slot, len) |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | /** |
| 510 | * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
| 511 | * |
| 512 | * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
| 513 | */ |
| 514 | function unsafeSetLength(bytes32[] storage array, uint256 len) internal { |
| 515 | assembly ("memory-safe") { |
| 516 | sstore(array.slot, len) |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
| 522 | * |
| 523 | * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
| 524 | */ |
| 525 | function unsafeSetLength(uint256[] storage array, uint256 len) internal { |
| 526 | assembly ("memory-safe") { |
| 527 | sstore(array.slot, len) |
| 528 | } |
| 529 | } |
| 530 | |
| 531 | /** |
| 532 | * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
| 533 | * |
| 534 | * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
| 535 | */ |
| 536 | function unsafeSetLength(bytes[] storage array, uint256 len) internal { |
| 537 | assembly ("memory-safe") { |
| 538 | sstore(array.slot, len) |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | /** |
| 543 | * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
| 544 | * |
| 545 | * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
| 546 | */ |
| 547 | function unsafeSetLength(string[] storage array, uint256 len) internal { |
| 548 | assembly ("memory-safe") { |
| 549 | sstore(array.slot, len) |
| 550 | } |
| 551 | } |
| 552 | } |
| 553 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Comparators.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Provides a set of functions to compare values. |
| 8 | * |
| 9 | * _Available since v5.1._ |
| 10 | */ |
| 11 | library Comparators { |
| 12 | function lt(uint256 a, uint256 b) internal pure returns (bool) { |
| 13 | return a < b; |
| 14 | } |
| 15 | |
| 16 | function gt(uint256 a, uint256 b) internal pure returns (bool) { |
| 17 | return a > b; |
| 18 | } |
| 19 | } |
| 20 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Context.sol
Lines covered: 2 / 2 (100.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Provides information about the current execution context, including the |
| 8 | * sender of the transaction and its data. While these are generally available |
| 9 | * via msg.sender and msg.data, they should not be accessed in such a direct |
| 10 | * manner, since when dealing with meta-transactions the account sending and |
| 11 | * paying for execution may not be the actual sender (as far as an application |
| 12 | * is concerned). |
| 13 | * |
| 14 | * This contract is only required for intermediate, library-like contracts. |
| 15 | */ |
| 16 | abstract contract Context { |
| 17 | function _msgSender() internal view virtual returns (address) { |
| 18 | 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 | } |
| 29 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {Errors} from "./Errors.sol"; |
| 7 | |
| 8 | /** |
| 9 | * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. |
| 10 | * `CREATE2` can be used to compute in advance the address where a smart |
| 11 | * contract will be deployed, which allows for interesting new mechanisms known |
| 12 | * as 'counterfactual interactions'. |
| 13 | * |
| 14 | * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more |
| 15 | * information. |
| 16 | */ |
| 17 | library Create2 { |
| 18 | /** |
| 19 | * @dev There's no code to deploy. |
| 20 | */ |
| 21 | error Create2EmptyBytecode(); |
| 22 | |
| 23 | /** |
| 24 | * @dev Deploys a contract using `CREATE2`. The address where the contract |
| 25 | * will be deployed can be known in advance via {computeAddress}. |
| 26 | * |
| 27 | * The bytecode for a contract can be obtained from Solidity with |
| 28 | * `type(contractName).creationCode`. |
| 29 | * |
| 30 | * Requirements: |
| 31 | * |
| 32 | * - `bytecode` must not be empty. |
| 33 | * - `salt` must have not been used for `bytecode` already. |
| 34 | * - the factory must have a balance of at least `amount`. |
| 35 | * - if `amount` is non-zero, `bytecode` must have a `payable` constructor. |
| 36 | */ |
| 37 | function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) { |
| 38 | if (address(this).balance < amount) { |
| 39 | revert Errors.InsufficientBalance(address(this).balance, amount); |
| 40 | } |
| 41 | if (bytecode.length == 0) { |
| 42 | revert Create2EmptyBytecode(); |
| 43 | } |
| 44 | assembly ("memory-safe") { |
| 45 | addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) |
| 46 | // if no address was created, and returndata is not empty, bubble revert |
| 47 | if and(iszero(addr), not(iszero(returndatasize()))) { |
| 48 | let p := mload(0x40) |
| 49 | returndatacopy(p, 0, returndatasize()) |
| 50 | revert(p, returndatasize()) |
| 51 | } |
| 52 | } |
| 53 | if (addr == address(0)) { |
| 54 | revert Errors.FailedDeployment(); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the |
| 60 | * `bytecodeHash` or `salt` will result in a new destination address. |
| 61 | */ |
| 62 | function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) { |
| 63 | return computeAddress(salt, bytecodeHash, address(this)); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at |
| 68 | * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. |
| 69 | */ |
| 70 | function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) { |
| 71 | assembly ("memory-safe") { |
| 72 | let ptr := mload(0x40) // Get free memory pointer |
| 73 | |
| 74 | // | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | |
| 75 | // |-------------------|---------------------------------------------------------------------------| |
| 76 | // | bytecodeHash | CCCCCCCCCCCCC...CC | |
| 77 | // | salt | BBBBBBBBBBBBB...BB | |
| 78 | // | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA | |
| 79 | // | 0xFF | FF | |
| 80 | // |-------------------|---------------------------------------------------------------------------| |
| 81 | // | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC | |
| 82 | // | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ | |
| 83 | |
| 84 | mstore(add(ptr, 0x40), bytecodeHash) |
| 85 | mstore(add(ptr, 0x20), salt) |
| 86 | mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes |
| 87 | let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff |
| 88 | mstore8(start, 0xff) |
| 89 | addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff) |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Errors.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Collection of common custom errors used in multiple contracts |
| 8 | * |
| 9 | * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. |
| 10 | * It is recommended to avoid relying on the error API for critical functionality. |
| 11 | * |
| 12 | * _Available since v5.1._ |
| 13 | */ |
| 14 | library Errors { |
| 15 | /** |
| 16 | * @dev The ETH balance of the account is not enough to perform the operation. |
| 17 | */ |
| 18 | error InsufficientBalance(uint256 balance, uint256 needed); |
| 19 | |
| 20 | /** |
| 21 | * @dev A call to an address target failed. The target may have reverted. |
| 22 | */ |
| 23 | error FailedCall(); |
| 24 | |
| 25 | /** |
| 26 | * @dev The deployment failed. |
| 27 | */ |
| 28 | error FailedDeployment(); |
| 29 | |
| 30 | /** |
| 31 | * @dev A necessary precompile is missing. |
| 32 | */ |
| 33 | error MissingPrecompile(address); |
| 34 | } |
| 35 |
83.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Panic.sol
Lines covered: 5 / 6 (83.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Helper library for emitting standardized panic codes. |
| 8 | * |
| 9 | * ```solidity |
| 10 | * contract Example { |
| 11 | * using Panic for uint256; |
| 12 | * |
| 13 | * // Use any of the declared internal constants |
| 14 | * function foo() { Panic.GENERIC.panic(); } |
| 15 | * |
| 16 | * // Alternatively |
| 17 | * function foo() { Panic.panic(Panic.GENERIC); } |
| 18 | * } |
| 19 | * ``` |
| 20 | * |
| 21 | * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. |
| 22 | * |
| 23 | * _Available since v5.1._ |
| 24 | */ |
| 25 | // slither-disable-next-line unused-state |
| 26 | 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 | mstore(0x00, 0x4e487b71) |
| 53 | mstore(0x20, code) |
| 54 | revert(0x1c, 0x24) |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/SlotDerivation.sol) |
| 3 | // This file was procedurally generated from scripts/generate/templates/SlotDerivation.js. |
| 4 | |
| 5 | pragma solidity ^0.8.20; |
| 6 | |
| 7 | /** |
| 8 | * @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots |
| 9 | * corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by |
| 10 | * the solidity language / compiler. |
| 11 | * |
| 12 | * See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.]. |
| 13 | * |
| 14 | * Example usage: |
| 15 | * ```solidity |
| 16 | * contract Example { |
| 17 | * // Add the library methods |
| 18 | * using StorageSlot for bytes32; |
| 19 | * using SlotDerivation for bytes32; |
| 20 | * |
| 21 | * // Declare a namespace |
| 22 | * string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot |
| 23 | * |
| 24 | * function setValueInNamespace(uint256 key, address newValue) internal { |
| 25 | * _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue; |
| 26 | * } |
| 27 | * |
| 28 | * function getValueInNamespace(uint256 key) internal view returns (address) { |
| 29 | * return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value; |
| 30 | * } |
| 31 | * } |
| 32 | * ``` |
| 33 | * |
| 34 | * TIP: Consider using this library along with {StorageSlot}. |
| 35 | * |
| 36 | * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking |
| 37 | * upgrade safety will ignore the slots accessed through this library. |
| 38 | * |
| 39 | * _Available since v5.1._ |
| 40 | */ |
| 41 | library SlotDerivation { |
| 42 | /** |
| 43 | * @dev Derive an ERC-7201 slot from a string (namespace). |
| 44 | */ |
| 45 | function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) { |
| 46 | assembly ("memory-safe") { |
| 47 | mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)) |
| 48 | slot := and(keccak256(0x00, 0x20), not(0xff)) |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * @dev Add an offset to a slot to get the n-th element of a structure or an array. |
| 54 | */ |
| 55 | function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) { |
| 56 | unchecked { |
| 57 | return bytes32(uint256(slot) + pos); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * @dev Derive the location of the first element in an array from the slot where the length is stored. |
| 63 | */ |
| 64 | function deriveArray(bytes32 slot) internal pure returns (bytes32 result) { |
| 65 | assembly ("memory-safe") { |
| 66 | mstore(0x00, slot) |
| 67 | result := keccak256(0x00, 0x20) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * @dev Derive the location of a mapping element from the key. |
| 73 | */ |
| 74 | function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) { |
| 75 | assembly ("memory-safe") { |
| 76 | mstore(0x00, and(key, shr(96, not(0)))) |
| 77 | mstore(0x20, slot) |
| 78 | result := keccak256(0x00, 0x40) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * @dev Derive the location of a mapping element from the key. |
| 84 | */ |
| 85 | function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) { |
| 86 | assembly ("memory-safe") { |
| 87 | mstore(0x00, iszero(iszero(key))) |
| 88 | mstore(0x20, slot) |
| 89 | result := keccak256(0x00, 0x40) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * @dev Derive the location of a mapping element from the key. |
| 95 | */ |
| 96 | function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) { |
| 97 | assembly ("memory-safe") { |
| 98 | mstore(0x00, key) |
| 99 | mstore(0x20, slot) |
| 100 | result := keccak256(0x00, 0x40) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * @dev Derive the location of a mapping element from the key. |
| 106 | */ |
| 107 | function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) { |
| 108 | assembly ("memory-safe") { |
| 109 | mstore(0x00, key) |
| 110 | mstore(0x20, slot) |
| 111 | result := keccak256(0x00, 0x40) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * @dev Derive the location of a mapping element from the key. |
| 117 | */ |
| 118 | function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) { |
| 119 | assembly ("memory-safe") { |
| 120 | mstore(0x00, key) |
| 121 | mstore(0x20, slot) |
| 122 | result := keccak256(0x00, 0x40) |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * @dev Derive the location of a mapping element from the key. |
| 128 | */ |
| 129 | function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) { |
| 130 | assembly ("memory-safe") { |
| 131 | let length := mload(key) |
| 132 | let begin := add(key, 0x20) |
| 133 | let end := add(begin, length) |
| 134 | let cache := mload(end) |
| 135 | mstore(end, slot) |
| 136 | result := keccak256(begin, add(length, 0x20)) |
| 137 | mstore(end, cache) |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * @dev Derive the location of a mapping element from the key. |
| 143 | */ |
| 144 | function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) { |
| 145 | assembly ("memory-safe") { |
| 146 | let length := mload(key) |
| 147 | let begin := add(key, 0x20) |
| 148 | let end := add(begin, length) |
| 149 | let cache := mload(end) |
| 150 | mstore(end, slot) |
| 151 | result := keccak256(begin, add(length, 0x20)) |
| 152 | mstore(end, cache) |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol) |
| 3 | // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. |
| 4 | |
| 5 | pragma solidity ^0.8.20; |
| 6 | |
| 7 | /** |
| 8 | * @dev Library for reading and writing primitive types to specific storage slots. |
| 9 | * |
| 10 | * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. |
| 11 | * This library helps with reading and writing to such slots without the need for inline assembly. |
| 12 | * |
| 13 | * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. |
| 14 | * |
| 15 | * Example usage to set ERC-1967 implementation slot: |
| 16 | * ```solidity |
| 17 | * contract ERC1967 { |
| 18 | * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. |
| 19 | * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; |
| 20 | * |
| 21 | * function _getImplementation() internal view returns (address) { |
| 22 | * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; |
| 23 | * } |
| 24 | * |
| 25 | * function _setImplementation(address newImplementation) internal { |
| 26 | * require(newImplementation.code.length > 0); |
| 27 | * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; |
| 28 | * } |
| 29 | * } |
| 30 | * ``` |
| 31 | * |
| 32 | * TIP: Consider using this library along with {SlotDerivation}. |
| 33 | */ |
| 34 | library StorageSlot { |
| 35 | struct AddressSlot { |
| 36 | address value; |
| 37 | } |
| 38 | |
| 39 | struct BooleanSlot { |
| 40 | bool value; |
| 41 | } |
| 42 | |
| 43 | struct Bytes32Slot { |
| 44 | bytes32 value; |
| 45 | } |
| 46 | |
| 47 | struct Uint256Slot { |
| 48 | uint256 value; |
| 49 | } |
| 50 | |
| 51 | struct Int256Slot { |
| 52 | int256 value; |
| 53 | } |
| 54 | |
| 55 | struct StringSlot { |
| 56 | string value; |
| 57 | } |
| 58 | |
| 59 | struct BytesSlot { |
| 60 | bytes value; |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * @dev Returns an `AddressSlot` with member `value` located at `slot`. |
| 65 | */ |
| 66 | function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { |
| 67 | assembly ("memory-safe") { |
| 68 | r.slot := slot |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * @dev Returns a `BooleanSlot` with member `value` located at `slot`. |
| 74 | */ |
| 75 | function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { |
| 76 | assembly ("memory-safe") { |
| 77 | r.slot := slot |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. |
| 83 | */ |
| 84 | function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { |
| 85 | assembly ("memory-safe") { |
| 86 | r.slot := slot |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * @dev Returns a `Uint256Slot` with member `value` located at `slot`. |
| 92 | */ |
| 93 | function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { |
| 94 | assembly ("memory-safe") { |
| 95 | r.slot := slot |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * @dev Returns a `Int256Slot` with member `value` located at `slot`. |
| 101 | */ |
| 102 | function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) { |
| 103 | assembly ("memory-safe") { |
| 104 | r.slot := slot |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * @dev Returns a `StringSlot` with member `value` located at `slot`. |
| 110 | */ |
| 111 | function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { |
| 112 | assembly ("memory-safe") { |
| 113 | r.slot := slot |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * @dev Returns an `StringSlot` representation of the string storage pointer `store`. |
| 119 | */ |
| 120 | function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { |
| 121 | assembly ("memory-safe") { |
| 122 | r.slot := store.slot |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * @dev Returns a `BytesSlot` with member `value` located at `slot`. |
| 128 | */ |
| 129 | function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { |
| 130 | assembly ("memory-safe") { |
| 131 | r.slot := slot |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. |
| 137 | */ |
| 138 | function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { |
| 139 | assembly ("memory-safe") { |
| 140 | r.slot := store.slot |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {Math} from "./math/Math.sol"; |
| 7 | import {SafeCast} from "./math/SafeCast.sol"; |
| 8 | import {SignedMath} from "./math/SignedMath.sol"; |
| 9 | |
| 10 | /** |
| 11 | * @dev String operations. |
| 12 | */ |
| 13 | library Strings { |
| 14 | using SafeCast for *; |
| 15 | |
| 16 | bytes16 private constant HEX_DIGITS = "0123456789abcdef"; |
| 17 | uint8 private constant ADDRESS_LENGTH = 20; |
| 18 | uint256 private constant SPECIAL_CHARS_LOOKUP = |
| 19 | (1 << 0x08) | // backspace |
| 20 | (1 << 0x09) | // tab |
| 21 | (1 << 0x0a) | // newline |
| 22 | (1 << 0x0c) | // form feed |
| 23 | (1 << 0x0d) | // carriage return |
| 24 | (1 << 0x22) | // double quote |
| 25 | (1 << 0x5c); // backslash |
| 26 | |
| 27 | /** |
| 28 | * @dev The `value` string doesn't fit in the specified `length`. |
| 29 | */ |
| 30 | error StringsInsufficientHexLength(uint256 value, uint256 length); |
| 31 | |
| 32 | /** |
| 33 | * @dev The string being parsed contains characters that are not in scope of the given base. |
| 34 | */ |
| 35 | error StringsInvalidChar(); |
| 36 | |
| 37 | /** |
| 38 | * @dev The string being parsed is not a properly formatted address. |
| 39 | */ |
| 40 | error StringsInvalidAddressFormat(); |
| 41 | |
| 42 | /** |
| 43 | * @dev Converts a `uint256` to its ASCII `string` decimal representation. |
| 44 | */ |
| 45 | function toString(uint256 value) internal pure returns (string memory) { |
| 46 | unchecked { |
| 47 | uint256 length = Math.log10(value) + 1; |
| 48 | string memory buffer = new string(length); |
| 49 | uint256 ptr; |
| 50 | assembly ("memory-safe") { |
| 51 | ptr := add(add(buffer, 0x20), length) |
| 52 | } |
| 53 | while (true) { |
| 54 | ptr--; |
| 55 | assembly ("memory-safe") { |
| 56 | mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) |
| 57 | } |
| 58 | value /= 10; |
| 59 | if (value == 0) break; |
| 60 | } |
| 61 | return buffer; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * @dev Converts a `int256` to its ASCII `string` decimal representation. |
| 67 | */ |
| 68 | function toStringSigned(int256 value) internal pure returns (string memory) { |
| 69 | return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. |
| 74 | */ |
| 75 | function toHexString(uint256 value) internal pure returns (string memory) { |
| 76 | unchecked { |
| 77 | return toHexString(value, Math.log256(value) + 1); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. |
| 83 | */ |
| 84 | function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { |
| 85 | uint256 localValue = value; |
| 86 | bytes memory buffer = new bytes(2 * length + 2); |
| 87 | buffer[0] = "0"; |
| 88 | buffer[1] = "x"; |
| 89 | for (uint256 i = 2 * length + 1; i > 1; --i) { |
| 90 | buffer[i] = HEX_DIGITS[localValue & 0xf]; |
| 91 | localValue >>= 4; |
| 92 | } |
| 93 | if (localValue != 0) { |
| 94 | revert StringsInsufficientHexLength(value, length); |
| 95 | } |
| 96 | return string(buffer); |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal |
| 101 | * representation. |
| 102 | */ |
| 103 | function toHexString(address addr) internal pure returns (string memory) { |
| 104 | return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal |
| 109 | * representation, according to EIP-55. |
| 110 | */ |
| 111 | function toChecksumHexString(address addr) internal pure returns (string memory) { |
| 112 | bytes memory buffer = bytes(toHexString(addr)); |
| 113 | |
| 114 | // hash the hex part of buffer (skip length + 2 bytes, length 40) |
| 115 | uint256 hashValue; |
| 116 | assembly ("memory-safe") { |
| 117 | hashValue := shr(96, keccak256(add(buffer, 0x22), 40)) |
| 118 | } |
| 119 | |
| 120 | for (uint256 i = 41; i > 1; --i) { |
| 121 | // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f) |
| 122 | if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) { |
| 123 | // case shift by xoring with 0x20 |
| 124 | buffer[i] ^= 0x20; |
| 125 | } |
| 126 | hashValue >>= 4; |
| 127 | } |
| 128 | return string(buffer); |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * @dev Converts a `bytes` buffer to its ASCII `string` hexadecimal representation. |
| 133 | */ |
| 134 | function toHexString(bytes memory input) internal pure returns (string memory) { |
| 135 | unchecked { |
| 136 | bytes memory buffer = new bytes(2 * input.length + 2); |
| 137 | buffer[0] = "0"; |
| 138 | buffer[1] = "x"; |
| 139 | for (uint256 i = 0; i < input.length; ++i) { |
| 140 | uint8 v = uint8(input[i]); |
| 141 | buffer[2 * i + 2] = HEX_DIGITS[v >> 4]; |
| 142 | buffer[2 * i + 3] = HEX_DIGITS[v & 0xf]; |
| 143 | } |
| 144 | return string(buffer); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * @dev Returns true if the two strings are equal. |
| 150 | */ |
| 151 | function equal(string memory a, string memory b) internal pure returns (bool) { |
| 152 | return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * @dev Parse a decimal string and returns the value as a `uint256`. |
| 157 | * |
| 158 | * Requirements: |
| 159 | * - The string must be formatted as `[0-9]*` |
| 160 | * - The result must fit into an `uint256` type |
| 161 | */ |
| 162 | function parseUint(string memory input) internal pure returns (uint256) { |
| 163 | return parseUint(input, 0, bytes(input).length); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and |
| 168 | * `end` (excluded). |
| 169 | * |
| 170 | * Requirements: |
| 171 | * - The substring must be formatted as `[0-9]*` |
| 172 | * - The result must fit into an `uint256` type |
| 173 | */ |
| 174 | function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { |
| 175 | (bool success, uint256 value) = tryParseUint(input, begin, end); |
| 176 | if (!success) revert StringsInvalidChar(); |
| 177 | return value; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. |
| 182 | * |
| 183 | * NOTE: This function will revert if the result does not fit in a `uint256`. |
| 184 | */ |
| 185 | function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { |
| 186 | return _tryParseUintUncheckedBounds(input, 0, bytes(input).length); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid |
| 191 | * character. |
| 192 | * |
| 193 | * NOTE: This function will revert if the result does not fit in a `uint256`. |
| 194 | */ |
| 195 | function tryParseUint( |
| 196 | string memory input, |
| 197 | uint256 begin, |
| 198 | uint256 end |
| 199 | ) internal pure returns (bool success, uint256 value) { |
| 200 | if (end > bytes(input).length || begin > end) return (false, 0); |
| 201 | return _tryParseUintUncheckedBounds(input, begin, end); |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that |
| 206 | * `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
| 207 | */ |
| 208 | function _tryParseUintUncheckedBounds( |
| 209 | string memory input, |
| 210 | uint256 begin, |
| 211 | uint256 end |
| 212 | ) private pure returns (bool success, uint256 value) { |
| 213 | bytes memory buffer = bytes(input); |
| 214 | |
| 215 | uint256 result = 0; |
| 216 | for (uint256 i = begin; i < end; ++i) { |
| 217 | uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); |
| 218 | if (chr > 9) return (false, 0); |
| 219 | result *= 10; |
| 220 | result += chr; |
| 221 | } |
| 222 | return (true, result); |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * @dev Parse a decimal string and returns the value as a `int256`. |
| 227 | * |
| 228 | * Requirements: |
| 229 | * - The string must be formatted as `[-+]?[0-9]*` |
| 230 | * - The result must fit in an `int256` type. |
| 231 | */ |
| 232 | function parseInt(string memory input) internal pure returns (int256) { |
| 233 | return parseInt(input, 0, bytes(input).length); |
| 234 | } |
| 235 | |
| 236 | /** |
| 237 | * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and |
| 238 | * `end` (excluded). |
| 239 | * |
| 240 | * Requirements: |
| 241 | * - The substring must be formatted as `[-+]?[0-9]*` |
| 242 | * - The result must fit in an `int256` type. |
| 243 | */ |
| 244 | function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { |
| 245 | (bool success, int256 value) = tryParseInt(input, begin, end); |
| 246 | if (!success) revert StringsInvalidChar(); |
| 247 | return value; |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if |
| 252 | * the result does not fit in a `int256`. |
| 253 | * |
| 254 | * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. |
| 255 | */ |
| 256 | function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { |
| 257 | return _tryParseIntUncheckedBounds(input, 0, bytes(input).length); |
| 258 | } |
| 259 | |
| 260 | uint256 private constant ABS_MIN_INT256 = 2 ** 255; |
| 261 | |
| 262 | /** |
| 263 | * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid |
| 264 | * character or if the result does not fit in a `int256`. |
| 265 | * |
| 266 | * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. |
| 267 | */ |
| 268 | function tryParseInt( |
| 269 | string memory input, |
| 270 | uint256 begin, |
| 271 | uint256 end |
| 272 | ) internal pure returns (bool success, int256 value) { |
| 273 | if (end > bytes(input).length || begin > end) return (false, 0); |
| 274 | return _tryParseIntUncheckedBounds(input, begin, end); |
| 275 | } |
| 276 | |
| 277 | /** |
| 278 | * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that |
| 279 | * `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
| 280 | */ |
| 281 | function _tryParseIntUncheckedBounds( |
| 282 | string memory input, |
| 283 | uint256 begin, |
| 284 | uint256 end |
| 285 | ) private pure returns (bool success, int256 value) { |
| 286 | bytes memory buffer = bytes(input); |
| 287 | |
| 288 | // Check presence of a negative sign. |
| 289 | bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty |
| 290 | bool positiveSign = sign == bytes1("+"); |
| 291 | bool negativeSign = sign == bytes1("-"); |
| 292 | uint256 offset = (positiveSign || negativeSign).toUint(); |
| 293 | |
| 294 | (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end); |
| 295 | |
| 296 | if (absSuccess && absValue < ABS_MIN_INT256) { |
| 297 | return (true, negativeSign ? -int256(absValue) : int256(absValue)); |
| 298 | } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) { |
| 299 | return (true, type(int256).min); |
| 300 | } else return (false, 0); |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. |
| 305 | * |
| 306 | * Requirements: |
| 307 | * - The string must be formatted as `(0x)?[0-9a-fA-F]*` |
| 308 | * - The result must fit in an `uint256` type. |
| 309 | */ |
| 310 | function parseHexUint(string memory input) internal pure returns (uint256) { |
| 311 | return parseHexUint(input, 0, bytes(input).length); |
| 312 | } |
| 313 | |
| 314 | /** |
| 315 | * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and |
| 316 | * `end` (excluded). |
| 317 | * |
| 318 | * Requirements: |
| 319 | * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` |
| 320 | * - The result must fit in an `uint256` type. |
| 321 | */ |
| 322 | function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { |
| 323 | (bool success, uint256 value) = tryParseHexUint(input, begin, end); |
| 324 | if (!success) revert StringsInvalidChar(); |
| 325 | return value; |
| 326 | } |
| 327 | |
| 328 | /** |
| 329 | * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character. |
| 330 | * |
| 331 | * NOTE: This function will revert if the result does not fit in a `uint256`. |
| 332 | */ |
| 333 | function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) { |
| 334 | return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length); |
| 335 | } |
| 336 | |
| 337 | /** |
| 338 | * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an |
| 339 | * invalid character. |
| 340 | * |
| 341 | * NOTE: This function will revert if the result does not fit in a `uint256`. |
| 342 | */ |
| 343 | function tryParseHexUint( |
| 344 | string memory input, |
| 345 | uint256 begin, |
| 346 | uint256 end |
| 347 | ) internal pure returns (bool success, uint256 value) { |
| 348 | if (end > bytes(input).length || begin > end) return (false, 0); |
| 349 | return _tryParseHexUintUncheckedBounds(input, begin, end); |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that |
| 354 | * `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
| 355 | */ |
| 356 | function _tryParseHexUintUncheckedBounds( |
| 357 | string memory input, |
| 358 | uint256 begin, |
| 359 | uint256 end |
| 360 | ) private pure returns (bool success, uint256 value) { |
| 361 | bytes memory buffer = bytes(input); |
| 362 | |
| 363 | // skip 0x prefix if present |
| 364 | bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty |
| 365 | uint256 offset = hasPrefix.toUint() * 2; |
| 366 | |
| 367 | uint256 result = 0; |
| 368 | for (uint256 i = begin + offset; i < end; ++i) { |
| 369 | uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); |
| 370 | if (chr > 15) return (false, 0); |
| 371 | result *= 16; |
| 372 | unchecked { |
| 373 | // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check). |
| 374 | // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked. |
| 375 | result += chr; |
| 376 | } |
| 377 | } |
| 378 | return (true, result); |
| 379 | } |
| 380 | |
| 381 | /** |
| 382 | * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. |
| 383 | * |
| 384 | * Requirements: |
| 385 | * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` |
| 386 | */ |
| 387 | function parseAddress(string memory input) internal pure returns (address) { |
| 388 | return parseAddress(input, 0, bytes(input).length); |
| 389 | } |
| 390 | |
| 391 | /** |
| 392 | * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and |
| 393 | * `end` (excluded). |
| 394 | * |
| 395 | * Requirements: |
| 396 | * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` |
| 397 | */ |
| 398 | function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { |
| 399 | (bool success, address value) = tryParseAddress(input, begin, end); |
| 400 | if (!success) revert StringsInvalidAddressFormat(); |
| 401 | return value; |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly |
| 406 | * formatted address. See {parseAddress-string} requirements. |
| 407 | */ |
| 408 | function tryParseAddress(string memory input) internal pure returns (bool success, address value) { |
| 409 | return tryParseAddress(input, 0, bytes(input).length); |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly |
| 414 | * formatted address. See {parseAddress-string-uint256-uint256} requirements. |
| 415 | */ |
| 416 | function tryParseAddress( |
| 417 | string memory input, |
| 418 | uint256 begin, |
| 419 | uint256 end |
| 420 | ) internal pure returns (bool success, address value) { |
| 421 | if (end > bytes(input).length || begin > end) return (false, address(0)); |
| 422 | |
| 423 | bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty |
| 424 | uint256 expectedLength = 40 + hasPrefix.toUint() * 2; |
| 425 | |
| 426 | // check that input is the correct length |
| 427 | if (end - begin == expectedLength) { |
| 428 | // length guarantees that this does not overflow, and value is at most type(uint160).max |
| 429 | (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end); |
| 430 | return (s, address(uint160(v))); |
| 431 | } else { |
| 432 | return (false, address(0)); |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | function _tryParseChr(bytes1 chr) private pure returns (uint8) { |
| 437 | uint8 value = uint8(chr); |
| 438 | |
| 439 | // Try to parse `chr`: |
| 440 | // - Case 1: [0-9] |
| 441 | // - Case 2: [a-f] |
| 442 | // - Case 3: [A-F] |
| 443 | // - otherwise not supported |
| 444 | unchecked { |
| 445 | if (value > 47 && value < 58) value -= 48; |
| 446 | else if (value > 96 && value < 103) value -= 87; |
| 447 | else if (value > 64 && value < 71) value -= 55; |
| 448 | else return type(uint8).max; |
| 449 | } |
| 450 | |
| 451 | return value; |
| 452 | } |
| 453 | |
| 454 | /** |
| 455 | * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata. |
| 456 | * |
| 457 | * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped. |
| 458 | * |
| 459 | * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of |
| 460 | * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode |
| 461 | * characters that are not in this range, but other tooling may provide different results. |
| 462 | */ |
| 463 | function escapeJSON(string memory input) internal pure returns (string memory) { |
| 464 | bytes memory buffer = bytes(input); |
| 465 | bytes memory output = new bytes(2 * buffer.length); // worst case scenario |
| 466 | uint256 outputLength = 0; |
| 467 | |
| 468 | for (uint256 i; i < buffer.length; ++i) { |
| 469 | bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i)); |
| 470 | if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) { |
| 471 | output[outputLength++] = "\\"; |
| 472 | if (char == 0x08) output[outputLength++] = "b"; |
| 473 | else if (char == 0x09) output[outputLength++] = "t"; |
| 474 | else if (char == 0x0a) output[outputLength++] = "n"; |
| 475 | else if (char == 0x0c) output[outputLength++] = "f"; |
| 476 | else if (char == 0x0d) output[outputLength++] = "r"; |
| 477 | else if (char == 0x5c) output[outputLength++] = "\\"; |
| 478 | else if (char == 0x22) { |
| 479 | // solhint-disable-next-line quotes |
| 480 | output[outputLength++] = '"'; |
| 481 | } |
| 482 | } else { |
| 483 | output[outputLength++] = char; |
| 484 | } |
| 485 | } |
| 486 | // write the actual length and deallocate unused memory |
| 487 | assembly ("memory-safe") { |
| 488 | mstore(output, outputLength) |
| 489 | mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63))))) |
| 490 | } |
| 491 | |
| 492 | return string(output); |
| 493 | } |
| 494 | |
| 495 | /** |
| 496 | * @dev Reads a bytes32 from a bytes array without bounds checking. |
| 497 | * |
| 498 | * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the |
| 499 | * assembly block as such would prevent some optimizations. |
| 500 | */ |
| 501 | function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { |
| 502 | // This is not memory safe in the general case, but all calls to this private function are within bounds. |
| 503 | assembly ("memory-safe") { |
| 504 | value := mload(add(add(buffer, 0x20), offset)) |
| 505 | } |
| 506 | } |
| 507 | } |
| 508 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol
Lines covered: 0 / 30 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. |
| 8 | * |
| 9 | * These functions can be used to verify that a message was signed by the holder |
| 10 | * of the private keys of a given address. |
| 11 | */ |
| 12 | library ECDSA { |
| 13 | enum RecoverError { |
| 14 | NoError, |
| 15 | InvalidSignature, |
| 16 | InvalidSignatureLength, |
| 17 | InvalidSignatureS |
| 18 | } |
| 19 | |
| 20 | /** |
| 21 | * @dev The signature derives the `address(0)`. |
| 22 | */ |
| 23 | error ECDSAInvalidSignature(); |
| 24 | |
| 25 | /** |
| 26 | * @dev The signature has an invalid length. |
| 27 | */ |
| 28 | error ECDSAInvalidSignatureLength(uint256 length); |
| 29 | |
| 30 | /** |
| 31 | * @dev The signature has an S value that is in the upper half order. |
| 32 | */ |
| 33 | error ECDSAInvalidSignatureS(bytes32 s); |
| 34 | |
| 35 | /** |
| 36 | * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not |
| 37 | * return address(0) without also returning an error description. Errors are documented using an enum (error type) |
| 38 | * and a bytes32 providing additional information about the error. |
| 39 | * |
| 40 | * If no error is returned, then the address can be used for verification purposes. |
| 41 | * |
| 42 | * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: |
| 43 | * this function rejects them by requiring the `s` value to be in the lower |
| 44 | * half order, and the `v` value to be either 27 or 28. |
| 45 | * |
| 46 | * IMPORTANT: `hash` _must_ be the result of a hash operation for the |
| 47 | * verification to be secure: it is possible to craft signatures that |
| 48 | * recover to arbitrary addresses for non-hashed data. A safe way to ensure |
| 49 | * this is by receiving a hash of the original message (which may otherwise |
| 50 | * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. |
| 51 | * |
| 52 | * Documentation for signature generation: |
| 53 | * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] |
| 54 | * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] |
| 55 | */ |
| 56 | function tryRecover( |
| 57 | bytes32 hash, |
| 58 | bytes memory signature |
| 59 | ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { |
| 60 | if (signature.length == 65) { |
| 61 | bytes32 r; |
| 62 | bytes32 s; |
| 63 | uint8 v; |
| 64 | // ecrecover takes the signature parameters, and the only way to get them |
| 65 | // currently is to use assembly. |
| 66 | assembly ("memory-safe") { |
| 67 | r := mload(add(signature, 0x20)) |
| 68 | s := mload(add(signature, 0x40)) |
| 69 | v := byte(0, mload(add(signature, 0x60))) |
| 70 | } |
| 71 | return tryRecover(hash, v, r, s); |
| 72 | } else { |
| 73 | return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * @dev Returns the address that signed a hashed message (`hash`) with |
| 79 | * `signature`. This address can then be used for verification purposes. |
| 80 | * |
| 81 | * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: |
| 82 | * this function rejects them by requiring the `s` value to be in the lower |
| 83 | * half order, and the `v` value to be either 27 or 28. |
| 84 | * |
| 85 | * IMPORTANT: `hash` _must_ be the result of a hash operation for the |
| 86 | * verification to be secure: it is possible to craft signatures that |
| 87 | * recover to arbitrary addresses for non-hashed data. A safe way to ensure |
| 88 | * this is by receiving a hash of the original message (which may otherwise |
| 89 | * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. |
| 90 | */ |
| 91 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { |
| 92 | (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); |
| 93 | _throwError(error, errorArg); |
| 94 | return recovered; |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. |
| 99 | * |
| 100 | * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] |
| 101 | */ |
| 102 | function tryRecover( |
| 103 | bytes32 hash, |
| 104 | bytes32 r, |
| 105 | bytes32 vs |
| 106 | ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { |
| 107 | unchecked { |
| 108 | bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); |
| 109 | // We do not check for an overflow here since the shift operation results in 0 or 1. |
| 110 | uint8 v = uint8((uint256(vs) >> 255) + 27); |
| 111 | return tryRecover(hash, v, r, s); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. |
| 117 | */ |
| 118 | function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { |
| 119 | (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); |
| 120 | _throwError(error, errorArg); |
| 121 | return recovered; |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * @dev Overload of {ECDSA-tryRecover} that receives the `v`, |
| 126 | * `r` and `s` signature fields separately. |
| 127 | */ |
| 128 | function tryRecover( |
| 129 | bytes32 hash, |
| 130 | uint8 v, |
| 131 | bytes32 r, |
| 132 | bytes32 s |
| 133 | ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { |
| 134 | // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature |
| 135 | // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines |
| 136 | // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most |
| 137 | // signatures from current libraries generate a unique signature with an s-value in the lower half order. |
| 138 | // |
| 139 | // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value |
| 140 | // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or |
| 141 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept |
| 142 | // these malleable signatures as well. |
| 143 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { |
| 144 | return (address(0), RecoverError.InvalidSignatureS, s); |
| 145 | } |
| 146 | |
| 147 | // If the signature is valid (and not malleable), return the signer address |
| 148 | address signer = ecrecover(hash, v, r, s); |
| 149 | if (signer == address(0)) { |
| 150 | return (address(0), RecoverError.InvalidSignature, bytes32(0)); |
| 151 | } |
| 152 | |
| 153 | return (signer, RecoverError.NoError, bytes32(0)); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * @dev Overload of {ECDSA-recover} that receives the `v`, |
| 158 | * `r` and `s` signature fields separately. |
| 159 | */ |
| 160 | function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { |
| 161 | (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); |
| 162 | _throwError(error, errorArg); |
| 163 | return recovered; |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. |
| 168 | */ |
| 169 | function _throwError(RecoverError error, bytes32 errorArg) private pure { |
| 170 | if (error == RecoverError.NoError) { |
| 171 | return; // no error: do nothing |
| 172 | } else if (error == RecoverError.InvalidSignature) { |
| 173 | revert ECDSAInvalidSignature(); |
| 174 | } else if (error == RecoverError.InvalidSignatureLength) { |
| 175 | revert ECDSAInvalidSignatureLength(uint256(errorArg)); |
| 176 | } else if (error == RecoverError.InvalidSignatureS) { |
| 177 | revert ECDSAInvalidSignatureS(errorArg); |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/Hashes.sol
Lines covered: 0 / 7 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | /** |
| 7 | * @dev Library of standard hash functions. |
| 8 | * |
| 9 | * _Available since v5.1._ |
| 10 | */ |
| 11 | library Hashes { |
| 12 | /** |
| 13 | * @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs. |
| 14 | * |
| 15 | * NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. |
| 16 | */ |
| 17 | function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) { |
| 18 | return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a); |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. |
| 23 | */ |
| 24 | function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) { |
| 25 | assembly ("memory-safe") { |
| 26 | mstore(0x00, a) |
| 27 | mstore(0x20, b) |
| 28 | value := keccak256(0x00, 0x40) |
| 29 | } |
| 30 | } |
| 31 | } |
| 32 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol
Lines covered: 0 / 8 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol) |
| 3 | // This file was procedurally generated from scripts/generate/templates/MerkleProof.js. |
| 4 | |
| 5 | pragma solidity ^0.8.20; |
| 6 | |
| 7 | import {Hashes} from "./Hashes.sol"; |
| 8 | |
| 9 | /** |
| 10 | * @dev These functions deal with verification of Merkle Tree proofs. |
| 11 | * |
| 12 | * The tree and the proofs can be generated using our |
| 13 | * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. |
| 14 | * You will find a quickstart guide in the readme. |
| 15 | * |
| 16 | * WARNING: You should avoid using leaf values that are 64 bytes long prior to |
| 17 | * hashing, or use a hash function other than keccak256 for hashing leaves. |
| 18 | * This is because the concatenation of a sorted pair of internal nodes in |
| 19 | * the Merkle tree could be reinterpreted as a leaf value. |
| 20 | * OpenZeppelin's JavaScript library generates Merkle trees that are safe |
| 21 | * against this attack out of the box. |
| 22 | * |
| 23 | * IMPORTANT: Consider memory side-effects when using custom hashing functions |
| 24 | * that access memory in an unsafe way. |
| 25 | * |
| 26 | * NOTE: This library supports proof verification for merkle trees built using |
| 27 | * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving |
| 28 | * leaf inclusion in trees built using non-commutative hashing functions requires |
| 29 | * additional logic that is not supported by this library. |
| 30 | */ |
| 31 | library MerkleProof { |
| 32 | /** |
| 33 | *@dev The multiproof provided is not valid. |
| 34 | */ |
| 35 | error MerkleProofInvalidMultiproof(); |
| 36 | |
| 37 | /** |
| 38 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
| 39 | * defined by `root`. For this, a `proof` must be provided, containing |
| 40 | * sibling hashes on the branch from the leaf to the root of the tree. Each |
| 41 | * pair of leaves and each pair of pre-images are assumed to be sorted. |
| 42 | * |
| 43 | * This version handles proofs in memory with the default hashing function. |
| 44 | */ |
| 45 | function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { |
| 46 | return processProof(proof, leaf) == root; |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
| 51 | * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
| 52 | * hash matches the root of the tree. When processing the proof, the pairs |
| 53 | * of leaves & pre-images are assumed to be sorted. |
| 54 | * |
| 55 | * This version handles proofs in memory with the default hashing function. |
| 56 | */ |
| 57 | function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { |
| 58 | bytes32 computedHash = leaf; |
| 59 | for (uint256 i = 0; i < proof.length; i++) { |
| 60 | computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); |
| 61 | } |
| 62 | return computedHash; |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
| 67 | * defined by `root`. For this, a `proof` must be provided, containing |
| 68 | * sibling hashes on the branch from the leaf to the root of the tree. Each |
| 69 | * pair of leaves and each pair of pre-images are assumed to be sorted. |
| 70 | * |
| 71 | * This version handles proofs in memory with a custom hashing function. |
| 72 | */ |
| 73 | function verify( |
| 74 | bytes32[] memory proof, |
| 75 | bytes32 root, |
| 76 | bytes32 leaf, |
| 77 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 78 | ) internal view returns (bool) { |
| 79 | return processProof(proof, leaf, hasher) == root; |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
| 84 | * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
| 85 | * hash matches the root of the tree. When processing the proof, the pairs |
| 86 | * of leaves & pre-images are assumed to be sorted. |
| 87 | * |
| 88 | * This version handles proofs in memory with a custom hashing function. |
| 89 | */ |
| 90 | function processProof( |
| 91 | bytes32[] memory proof, |
| 92 | bytes32 leaf, |
| 93 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 94 | ) internal view returns (bytes32) { |
| 95 | bytes32 computedHash = leaf; |
| 96 | for (uint256 i = 0; i < proof.length; i++) { |
| 97 | computedHash = hasher(computedHash, proof[i]); |
| 98 | } |
| 99 | return computedHash; |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
| 104 | * defined by `root`. For this, a `proof` must be provided, containing |
| 105 | * sibling hashes on the branch from the leaf to the root of the tree. Each |
| 106 | * pair of leaves and each pair of pre-images are assumed to be sorted. |
| 107 | * |
| 108 | * This version handles proofs in calldata with the default hashing function. |
| 109 | */ |
| 110 | function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { |
| 111 | return processProofCalldata(proof, leaf) == root; |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
| 116 | * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
| 117 | * hash matches the root of the tree. When processing the proof, the pairs |
| 118 | * of leaves & pre-images are assumed to be sorted. |
| 119 | * |
| 120 | * This version handles proofs in calldata with the default hashing function. |
| 121 | */ |
| 122 | function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { |
| 123 | bytes32 computedHash = leaf; |
| 124 | for (uint256 i = 0; i < proof.length; i++) { |
| 125 | computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); |
| 126 | } |
| 127 | return computedHash; |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
| 132 | * defined by `root`. For this, a `proof` must be provided, containing |
| 133 | * sibling hashes on the branch from the leaf to the root of the tree. Each |
| 134 | * pair of leaves and each pair of pre-images are assumed to be sorted. |
| 135 | * |
| 136 | * This version handles proofs in calldata with a custom hashing function. |
| 137 | */ |
| 138 | function verifyCalldata( |
| 139 | bytes32[] calldata proof, |
| 140 | bytes32 root, |
| 141 | bytes32 leaf, |
| 142 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 143 | ) internal view returns (bool) { |
| 144 | return processProofCalldata(proof, leaf, hasher) == root; |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
| 149 | * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
| 150 | * hash matches the root of the tree. When processing the proof, the pairs |
| 151 | * of leaves & pre-images are assumed to be sorted. |
| 152 | * |
| 153 | * This version handles proofs in calldata with a custom hashing function. |
| 154 | */ |
| 155 | function processProofCalldata( |
| 156 | bytes32[] calldata proof, |
| 157 | bytes32 leaf, |
| 158 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 159 | ) internal view returns (bytes32) { |
| 160 | bytes32 computedHash = leaf; |
| 161 | for (uint256 i = 0; i < proof.length; i++) { |
| 162 | computedHash = hasher(computedHash, proof[i]); |
| 163 | } |
| 164 | return computedHash; |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
| 169 | * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. |
| 170 | * |
| 171 | * This version handles multiproofs in memory with the default hashing function. |
| 172 | * |
| 173 | * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. |
| 174 | * |
| 175 | * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
| 176 | * The `leaves` must be validated independently. See {processMultiProof}. |
| 177 | */ |
| 178 | function multiProofVerify( |
| 179 | bytes32[] memory proof, |
| 180 | bool[] memory proofFlags, |
| 181 | bytes32 root, |
| 182 | bytes32[] memory leaves |
| 183 | ) internal pure returns (bool) { |
| 184 | return processMultiProof(proof, proofFlags, leaves) == root; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
| 189 | * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
| 190 | * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
| 191 | * respectively. |
| 192 | * |
| 193 | * This version handles multiproofs in memory with the default hashing function. |
| 194 | * |
| 195 | * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
| 196 | * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
| 197 | * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
| 198 | * |
| 199 | * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
| 200 | * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
| 201 | * validating the leaves elsewhere. |
| 202 | */ |
| 203 | function processMultiProof( |
| 204 | bytes32[] memory proof, |
| 205 | bool[] memory proofFlags, |
| 206 | bytes32[] memory leaves |
| 207 | ) internal pure returns (bytes32 merkleRoot) { |
| 208 | // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
| 209 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
| 210 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
| 211 | // the Merkle tree. |
| 212 | uint256 leavesLen = leaves.length; |
| 213 | uint256 proofFlagsLen = proofFlags.length; |
| 214 | |
| 215 | // Check proof validity. |
| 216 | if (leavesLen + proof.length != proofFlagsLen + 1) { |
| 217 | revert MerkleProofInvalidMultiproof(); |
| 218 | } |
| 219 | |
| 220 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
| 221 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
| 222 | bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
| 223 | uint256 leafPos = 0; |
| 224 | uint256 hashPos = 0; |
| 225 | uint256 proofPos = 0; |
| 226 | // At each step, we compute the next hash using two values: |
| 227 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
| 228 | // get the next hash. |
| 229 | // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
| 230 | // `proof` array. |
| 231 | for (uint256 i = 0; i < proofFlagsLen; i++) { |
| 232 | bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
| 233 | bytes32 b = proofFlags[i] |
| 234 | ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
| 235 | : proof[proofPos++]; |
| 236 | hashes[i] = Hashes.commutativeKeccak256(a, b); |
| 237 | } |
| 238 | |
| 239 | if (proofFlagsLen > 0) { |
| 240 | if (proofPos != proof.length) { |
| 241 | revert MerkleProofInvalidMultiproof(); |
| 242 | } |
| 243 | unchecked { |
| 244 | return hashes[proofFlagsLen - 1]; |
| 245 | } |
| 246 | } else if (leavesLen > 0) { |
| 247 | return leaves[0]; |
| 248 | } else { |
| 249 | return proof[0]; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
| 255 | * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. |
| 256 | * |
| 257 | * This version handles multiproofs in memory with a custom hashing function. |
| 258 | * |
| 259 | * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. |
| 260 | * |
| 261 | * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
| 262 | * The `leaves` must be validated independently. See {processMultiProof}. |
| 263 | */ |
| 264 | function multiProofVerify( |
| 265 | bytes32[] memory proof, |
| 266 | bool[] memory proofFlags, |
| 267 | bytes32 root, |
| 268 | bytes32[] memory leaves, |
| 269 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 270 | ) internal view returns (bool) { |
| 271 | return processMultiProof(proof, proofFlags, leaves, hasher) == root; |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
| 276 | * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
| 277 | * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
| 278 | * respectively. |
| 279 | * |
| 280 | * This version handles multiproofs in memory with a custom hashing function. |
| 281 | * |
| 282 | * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
| 283 | * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
| 284 | * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
| 285 | * |
| 286 | * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
| 287 | * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
| 288 | * validating the leaves elsewhere. |
| 289 | */ |
| 290 | function processMultiProof( |
| 291 | bytes32[] memory proof, |
| 292 | bool[] memory proofFlags, |
| 293 | bytes32[] memory leaves, |
| 294 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 295 | ) internal view returns (bytes32 merkleRoot) { |
| 296 | // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
| 297 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
| 298 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
| 299 | // the Merkle tree. |
| 300 | uint256 leavesLen = leaves.length; |
| 301 | uint256 proofFlagsLen = proofFlags.length; |
| 302 | |
| 303 | // Check proof validity. |
| 304 | if (leavesLen + proof.length != proofFlagsLen + 1) { |
| 305 | revert MerkleProofInvalidMultiproof(); |
| 306 | } |
| 307 | |
| 308 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
| 309 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
| 310 | bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
| 311 | uint256 leafPos = 0; |
| 312 | uint256 hashPos = 0; |
| 313 | uint256 proofPos = 0; |
| 314 | // At each step, we compute the next hash using two values: |
| 315 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
| 316 | // get the next hash. |
| 317 | // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
| 318 | // `proof` array. |
| 319 | for (uint256 i = 0; i < proofFlagsLen; i++) { |
| 320 | bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
| 321 | bytes32 b = proofFlags[i] |
| 322 | ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
| 323 | : proof[proofPos++]; |
| 324 | hashes[i] = hasher(a, b); |
| 325 | } |
| 326 | |
| 327 | if (proofFlagsLen > 0) { |
| 328 | if (proofPos != proof.length) { |
| 329 | revert MerkleProofInvalidMultiproof(); |
| 330 | } |
| 331 | unchecked { |
| 332 | return hashes[proofFlagsLen - 1]; |
| 333 | } |
| 334 | } else if (leavesLen > 0) { |
| 335 | return leaves[0]; |
| 336 | } else { |
| 337 | return proof[0]; |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | /** |
| 342 | * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
| 343 | * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. |
| 344 | * |
| 345 | * This version handles multiproofs in calldata with the default hashing function. |
| 346 | * |
| 347 | * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. |
| 348 | * |
| 349 | * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
| 350 | * The `leaves` must be validated independently. See {processMultiProofCalldata}. |
| 351 | */ |
| 352 | function multiProofVerifyCalldata( |
| 353 | bytes32[] calldata proof, |
| 354 | bool[] calldata proofFlags, |
| 355 | bytes32 root, |
| 356 | bytes32[] memory leaves |
| 357 | ) internal pure returns (bool) { |
| 358 | return processMultiProofCalldata(proof, proofFlags, leaves) == root; |
| 359 | } |
| 360 | |
| 361 | /** |
| 362 | * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
| 363 | * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
| 364 | * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
| 365 | * respectively. |
| 366 | * |
| 367 | * This version handles multiproofs in calldata with the default hashing function. |
| 368 | * |
| 369 | * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
| 370 | * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
| 371 | * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
| 372 | * |
| 373 | * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
| 374 | * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
| 375 | * validating the leaves elsewhere. |
| 376 | */ |
| 377 | function processMultiProofCalldata( |
| 378 | bytes32[] calldata proof, |
| 379 | bool[] calldata proofFlags, |
| 380 | bytes32[] memory leaves |
| 381 | ) internal pure returns (bytes32 merkleRoot) { |
| 382 | // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
| 383 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
| 384 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
| 385 | // the Merkle tree. |
| 386 | uint256 leavesLen = leaves.length; |
| 387 | uint256 proofFlagsLen = proofFlags.length; |
| 388 | |
| 389 | // Check proof validity. |
| 390 | if (leavesLen + proof.length != proofFlagsLen + 1) { |
| 391 | revert MerkleProofInvalidMultiproof(); |
| 392 | } |
| 393 | |
| 394 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
| 395 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
| 396 | bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
| 397 | uint256 leafPos = 0; |
| 398 | uint256 hashPos = 0; |
| 399 | uint256 proofPos = 0; |
| 400 | // At each step, we compute the next hash using two values: |
| 401 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
| 402 | // get the next hash. |
| 403 | // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
| 404 | // `proof` array. |
| 405 | for (uint256 i = 0; i < proofFlagsLen; i++) { |
| 406 | bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
| 407 | bytes32 b = proofFlags[i] |
| 408 | ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
| 409 | : proof[proofPos++]; |
| 410 | hashes[i] = Hashes.commutativeKeccak256(a, b); |
| 411 | } |
| 412 | |
| 413 | if (proofFlagsLen > 0) { |
| 414 | if (proofPos != proof.length) { |
| 415 | revert MerkleProofInvalidMultiproof(); |
| 416 | } |
| 417 | unchecked { |
| 418 | return hashes[proofFlagsLen - 1]; |
| 419 | } |
| 420 | } else if (leavesLen > 0) { |
| 421 | return leaves[0]; |
| 422 | } else { |
| 423 | return proof[0]; |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | /** |
| 428 | * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
| 429 | * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. |
| 430 | * |
| 431 | * This version handles multiproofs in calldata with a custom hashing function. |
| 432 | * |
| 433 | * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. |
| 434 | * |
| 435 | * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
| 436 | * The `leaves` must be validated independently. See {processMultiProofCalldata}. |
| 437 | */ |
| 438 | function multiProofVerifyCalldata( |
| 439 | bytes32[] calldata proof, |
| 440 | bool[] calldata proofFlags, |
| 441 | bytes32 root, |
| 442 | bytes32[] memory leaves, |
| 443 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 444 | ) internal view returns (bool) { |
| 445 | return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root; |
| 446 | } |
| 447 | |
| 448 | /** |
| 449 | * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
| 450 | * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
| 451 | * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
| 452 | * respectively. |
| 453 | * |
| 454 | * This version handles multiproofs in calldata with a custom hashing function. |
| 455 | * |
| 456 | * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
| 457 | * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
| 458 | * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
| 459 | * |
| 460 | * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
| 461 | * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
| 462 | * validating the leaves elsewhere. |
| 463 | */ |
| 464 | function processMultiProofCalldata( |
| 465 | bytes32[] calldata proof, |
| 466 | bool[] calldata proofFlags, |
| 467 | bytes32[] memory leaves, |
| 468 | function(bytes32, bytes32) view returns (bytes32) hasher |
| 469 | ) internal view returns (bytes32 merkleRoot) { |
| 470 | // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
| 471 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
| 472 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
| 473 | // the Merkle tree. |
| 474 | uint256 leavesLen = leaves.length; |
| 475 | uint256 proofFlagsLen = proofFlags.length; |
| 476 | |
| 477 | // Check proof validity. |
| 478 | if (leavesLen + proof.length != proofFlagsLen + 1) { |
| 479 | revert MerkleProofInvalidMultiproof(); |
| 480 | } |
| 481 | |
| 482 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
| 483 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
| 484 | bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
| 485 | uint256 leafPos = 0; |
| 486 | uint256 hashPos = 0; |
| 487 | uint256 proofPos = 0; |
| 488 | // At each step, we compute the next hash using two values: |
| 489 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
| 490 | // get the next hash. |
| 491 | // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
| 492 | // `proof` array. |
| 493 | for (uint256 i = 0; i < proofFlagsLen; i++) { |
| 494 | bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
| 495 | bytes32 b = proofFlags[i] |
| 496 | ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
| 497 | : proof[proofPos++]; |
| 498 | hashes[i] = hasher(a, b); |
| 499 | } |
| 500 | |
| 501 | if (proofFlagsLen > 0) { |
| 502 | if (proofPos != proof.length) { |
| 503 | revert MerkleProofInvalidMultiproof(); |
| 504 | } |
| 505 | unchecked { |
| 506 | return hashes[proofFlagsLen - 1]; |
| 507 | } |
| 508 | } else if (leavesLen > 0) { |
| 509 | return leaves[0]; |
| 510 | } else { |
| 511 | return proof[0]; |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol
Lines covered: 0 / 7 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {Strings} from "../Strings.sol"; |
| 7 | |
| 8 | /** |
| 9 | * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. |
| 10 | * |
| 11 | * The library provides methods for generating a hash of a message that conforms to the |
| 12 | * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] |
| 13 | * specifications. |
| 14 | */ |
| 15 | library MessageHashUtils { |
| 16 | /** |
| 17 | * @dev Returns the keccak256 digest of an ERC-191 signed data with version |
| 18 | * `0x45` (`personal_sign` messages). |
| 19 | * |
| 20 | * The digest is calculated by prefixing a bytes32 `messageHash` with |
| 21 | * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the |
| 22 | * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method. |
| 23 | * |
| 24 | * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with |
| 25 | * keccak256, although any bytes32 value can be safely used because the final digest will |
| 26 | * be re-hashed. |
| 27 | * |
| 28 | * See {ECDSA-recover}. |
| 29 | */ |
| 30 | function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { |
| 31 | assembly ("memory-safe") { |
| 32 | mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash |
| 33 | mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix |
| 34 | digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | /** |
| 39 | * @dev Returns the keccak256 digest of an ERC-191 signed data with version |
| 40 | * `0x45` (`personal_sign` messages). |
| 41 | * |
| 42 | * The digest is calculated by prefixing an arbitrary `message` with |
| 43 | * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the |
| 44 | * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method. |
| 45 | * |
| 46 | * See {ECDSA-recover}. |
| 47 | */ |
| 48 | function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { |
| 49 | return |
| 50 | keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * @dev Returns the keccak256 digest of an ERC-191 signed data with version |
| 55 | * `0x00` (data with intended validator). |
| 56 | * |
| 57 | * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended |
| 58 | * `validator` address. Then hashing the result. |
| 59 | * |
| 60 | * See {ECDSA-recover}. |
| 61 | */ |
| 62 | function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { |
| 63 | return keccak256(abi.encodePacked(hex"19_00", validator, data)); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32. |
| 68 | */ |
| 69 | function toDataWithIntendedValidatorHash( |
| 70 | address validator, |
| 71 | bytes32 messageHash |
| 72 | ) internal pure returns (bytes32 digest) { |
| 73 | assembly ("memory-safe") { |
| 74 | mstore(0x00, hex"19_00") |
| 75 | mstore(0x02, shl(96, validator)) |
| 76 | mstore(0x16, messageHash) |
| 77 | digest := keccak256(0x00, 0x36) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). |
| 83 | * |
| 84 | * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with |
| 85 | * `\x19\x01` and hashing the result. It corresponds to the hash signed by the |
| 86 | * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. |
| 87 | * |
| 88 | * See {ECDSA-recover}. |
| 89 | */ |
| 90 | function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { |
| 91 | assembly ("memory-safe") { |
| 92 | let ptr := mload(0x40) |
| 93 | mstore(ptr, hex"19_01") |
| 94 | mstore(add(ptr, 0x02), domainSeparator) |
| 95 | mstore(add(ptr, 0x22), structHash) |
| 96 | digest := keccak256(ptr, 0x42) |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {IERC165} from "./IERC165.sol"; |
| 7 | |
| 8 | /** |
| 9 | * @dev Implementation of the {IERC165} interface. |
| 10 | * |
| 11 | * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check |
| 12 | * for the additional interface id that will be supported. For example: |
| 13 | * |
| 14 | * ```solidity |
| 15 | * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { |
| 16 | * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); |
| 17 | * } |
| 18 | * ``` |
| 19 | */ |
| 20 | abstract contract ERC165 is IERC165 { |
| 21 | /// @inheritdoc IERC165 |
| 22 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { |
| 23 | return interfaceId == type(IERC165).interfaceId; |
| 24 | } |
| 25 | } |
| 26 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) |
| 3 | |
| 4 | pragma solidity >=0.4.16; |
| 5 | |
| 6 | /** |
| 7 | * @dev Interface of the ERC-165 standard, as defined in the |
| 8 | * https://eips.ethereum.org/EIPS/eip-165[ERC]. |
| 9 | * |
| 10 | * Implementers can declare support of contract interfaces, which can then be |
| 11 | * queried by others ({ERC165Checker}). |
| 12 | * |
| 13 | * For an implementation, see {ERC165}. |
| 14 | */ |
| 15 | interface IERC165 { |
| 16 | /** |
| 17 | * @dev Returns true if this contract implements the interface defined by |
| 18 | * `interfaceId`. See the corresponding |
| 19 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] |
| 20 | * to learn more about how these ids are created. |
| 21 | * |
| 22 | * This function call must use less than 30 000 gas. |
| 23 | */ |
| 24 | function supportsInterface(bytes4 interfaceId) external view returns (bool); |
| 25 | } |
| 26 |
96.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
Lines covered: 32 / 33 (96.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {Panic} from "../Panic.sol"; |
| 7 | import {SafeCast} from "./SafeCast.sol"; |
| 8 | |
| 9 | /** |
| 10 | * @dev Standard math utilities missing in the Solidity language. |
| 11 | */ |
| 12 | 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 | 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 | let mm := mulmod(a, b, not(0)) |
| 43 | low := mul(a, b) |
| 44 | 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 | 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 | 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 | function min(uint256 a, uint256 b) internal pure returns (uint256) { |
| 163 | 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 | function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { |
| 205 | unchecked { |
| 206 | (uint256 high, uint256 low) = mul512(x, y); |
| 207 | |
| 208 | // Handle non-overflow cases, 256 by 256 division. |
| 209 | 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 | return low / denominator; |
| 214 | } |
| 215 | |
| 216 | // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. |
| 217 | if (denominator <= high) { |
| 218 | 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 | remainder := mulmod(x, y, denominator) |
| 230 | |
| 231 | // Subtract 256 bit number from 512 bit number. |
| 232 | high := sub(high, gt(remainder, low)) |
| 233 | 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 | uint256 twos = denominator & (0 - denominator); |
| 240 | assembly ("memory-safe") { |
| 241 | // Divide denominator by twos. |
| 242 | denominator := div(denominator, twos) |
| 243 | |
| 244 | // Divide [high low] by twos. |
| 245 | low := div(low, twos) |
| 246 | |
| 247 | // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. |
| 248 | twos := add(div(sub(0, twos), twos), 1) |
| 249 | } |
| 250 | |
| 251 | // Shift in bits from high into low. |
| 252 | 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 | 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 | inverse *= 2 - denominator * inverse; // inverse mod 2⁸ |
| 262 | inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ |
| 263 | inverse *= 2 - denominator * inverse; // inverse mod 2³² |
| 264 | inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ |
| 265 | inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ |
| 266 | 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 | function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { |
| 281 | 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 | function log10(uint256 value) internal pure returns (uint256) { |
| 669 | uint256 result = 0; |
| 670 | unchecked { |
| 671 | if (value >= 10 ** 64) { |
| 672 | value /= 10 ** 64; |
| 673 | result += 64; |
| 674 | } |
| 675 | if (value >= 10 ** 32) { |
| 676 | value /= 10 ** 32; |
| 677 | result += 32; |
| 678 | } |
| 679 | if (value >= 10 ** 16) { |
| 680 | value /= 10 ** 16; |
| 681 | result += 16; |
| 682 | } |
| 683 | if (value >= 10 ** 8) { |
| 684 | value /= 10 ** 8; |
| 685 | result += 8; |
| 686 | } |
| 687 | if (value >= 10 ** 4) { |
| 688 | value /= 10 ** 4; |
| 689 | result += 4; |
| 690 | } |
| 691 | if (value >= 10 ** 2) { |
| 692 | value /= 10 ** 2; |
| 693 | result += 2; |
| 694 | } |
| 695 | if (value >= 10 ** 1) { |
| 696 | result += 1; |
| 697 | } |
| 698 | } |
| 699 | 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 | function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { |
| 747 | return uint8(rounding) % 2 == 1; |
| 748 | } |
| 749 | } |
| 750 |
66.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
Lines covered: 2 / 3 (66.0%)
| 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 | 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 | function toUint160(uint256 value) internal pure returns (uint160) { |
| 238 | if (value > type(uint160).max) { |
| 239 | revert SafeCastOverflowedUintDowncast(160, value); |
| 240 | } |
| 241 | return uint160(value); |
| 242 | } |
| 243 | |
| 244 | /** |
| 245 | * @dev Returns the downcasted uint152 from uint256, reverting on |
| 246 | * overflow (when the input is greater than largest uint152). |
| 247 | * |
| 248 | * Counterpart to Solidity's `uint152` operator. |
| 249 | * |
| 250 | * Requirements: |
| 251 | * |
| 252 | * - input must fit into 152 bits |
| 253 | */ |
| 254 | function toUint152(uint256 value) internal pure returns (uint152) { |
| 255 | if (value > type(uint152).max) { |
| 256 | revert SafeCastOverflowedUintDowncast(152, value); |
| 257 | } |
| 258 | return uint152(value); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * @dev Returns the downcasted uint144 from uint256, reverting on |
| 263 | * overflow (when the input is greater than largest uint144). |
| 264 | * |
| 265 | * Counterpart to Solidity's `uint144` operator. |
| 266 | * |
| 267 | * Requirements: |
| 268 | * |
| 269 | * - input must fit into 144 bits |
| 270 | */ |
| 271 | function toUint144(uint256 value) internal pure returns (uint144) { |
| 272 | if (value > type(uint144).max) { |
| 273 | revert SafeCastOverflowedUintDowncast(144, value); |
| 274 | } |
| 275 | return uint144(value); |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * @dev Returns the downcasted uint136 from uint256, reverting on |
| 280 | * overflow (when the input is greater than largest uint136). |
| 281 | * |
| 282 | * Counterpart to Solidity's `uint136` operator. |
| 283 | * |
| 284 | * Requirements: |
| 285 | * |
| 286 | * - input must fit into 136 bits |
| 287 | */ |
| 288 | function toUint136(uint256 value) internal pure returns (uint136) { |
| 289 | if (value > type(uint136).max) { |
| 290 | revert SafeCastOverflowedUintDowncast(136, value); |
| 291 | } |
| 292 | return uint136(value); |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * @dev Returns the downcasted uint128 from uint256, reverting on |
| 297 | * overflow (when the input is greater than largest uint128). |
| 298 | * |
| 299 | * Counterpart to Solidity's `uint128` operator. |
| 300 | * |
| 301 | * Requirements: |
| 302 | * |
| 303 | * - input must fit into 128 bits |
| 304 | */ |
| 305 | function toUint128(uint256 value) internal pure returns (uint128) { |
| 306 | if (value > type(uint128).max) { |
| 307 | revert SafeCastOverflowedUintDowncast(128, value); |
| 308 | } |
| 309 | 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 | function toUint64(uint256 value) internal pure returns (uint64) { |
| 442 | if (value > type(uint64).max) { |
| 443 | revert SafeCastOverflowedUintDowncast(64, value); |
| 444 | } |
| 445 | return uint64(value); |
| 446 | } |
| 447 | |
| 448 | /** |
| 449 | * @dev Returns the downcasted uint56 from uint256, reverting on |
| 450 | * overflow (when the input is greater than largest uint56). |
| 451 | * |
| 452 | * Counterpart to Solidity's `uint56` operator. |
| 453 | * |
| 454 | * Requirements: |
| 455 | * |
| 456 | * - input must fit into 56 bits |
| 457 | */ |
| 458 | function toUint56(uint256 value) internal pure returns (uint56) { |
| 459 | if (value > type(uint56).max) { |
| 460 | revert SafeCastOverflowedUintDowncast(56, value); |
| 461 | } |
| 462 | return uint56(value); |
| 463 | } |
| 464 | |
| 465 | /** |
| 466 | * @dev Returns the downcasted uint48 from uint256, reverting on |
| 467 | * overflow (when the input is greater than largest uint48). |
| 468 | * |
| 469 | * Counterpart to Solidity's `uint48` operator. |
| 470 | * |
| 471 | * Requirements: |
| 472 | * |
| 473 | * - input must fit into 48 bits |
| 474 | */ |
| 475 | function toUint48(uint256 value) internal pure returns (uint48) { |
| 476 | if (value > type(uint48).max) { |
| 477 | 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 | function toInt128(int256 value) internal pure returns (int128 downcasted) { |
| 863 | downcasted = int128(value); |
| 864 | if (downcasted != value) { |
| 865 | revert SafeCastOverflowedIntDowncast(128, value); |
| 866 | } |
| 867 | } |
| 868 | |
| 869 | /** |
| 870 | * @dev Returns the downcasted int120 from int256, reverting on |
| 871 | * overflow (when the input is less than smallest int120 or |
| 872 | * greater than largest int120). |
| 873 | * |
| 874 | * Counterpart to Solidity's `int120` operator. |
| 875 | * |
| 876 | * Requirements: |
| 877 | * |
| 878 | * - input must fit into 120 bits |
| 879 | */ |
| 880 | function toInt120(int256 value) internal pure returns (int120 downcasted) { |
| 881 | downcasted = int120(value); |
| 882 | if (downcasted != value) { |
| 883 | revert SafeCastOverflowedIntDowncast(120, value); |
| 884 | } |
| 885 | } |
| 886 | |
| 887 | /** |
| 888 | * @dev Returns the downcasted int112 from int256, reverting on |
| 889 | * overflow (when the input is less than smallest int112 or |
| 890 | * greater than largest int112). |
| 891 | * |
| 892 | * Counterpart to Solidity's `int112` operator. |
| 893 | * |
| 894 | * Requirements: |
| 895 | * |
| 896 | * - input must fit into 112 bits |
| 897 | */ |
| 898 | function toInt112(int256 value) internal pure returns (int112 downcasted) { |
| 899 | downcasted = int112(value); |
| 900 | if (downcasted != value) { |
| 901 | revert SafeCastOverflowedIntDowncast(112, value); |
| 902 | } |
| 903 | } |
| 904 | |
| 905 | /** |
| 906 | * @dev Returns the downcasted int104 from int256, reverting on |
| 907 | * overflow (when the input is less than smallest int104 or |
| 908 | * greater than largest int104). |
| 909 | * |
| 910 | * Counterpart to Solidity's `int104` operator. |
| 911 | * |
| 912 | * Requirements: |
| 913 | * |
| 914 | * - input must fit into 104 bits |
| 915 | */ |
| 916 | function toInt104(int256 value) internal pure returns (int104 downcasted) { |
| 917 | downcasted = int104(value); |
| 918 | if (downcasted != value) { |
| 919 | revert SafeCastOverflowedIntDowncast(104, value); |
| 920 | } |
| 921 | } |
| 922 | |
| 923 | /** |
| 924 | * @dev Returns the downcasted int96 from int256, reverting on |
| 925 | * overflow (when the input is less than smallest int96 or |
| 926 | * greater than largest int96). |
| 927 | * |
| 928 | * Counterpart to Solidity's `int96` operator. |
| 929 | * |
| 930 | * Requirements: |
| 931 | * |
| 932 | * - input must fit into 96 bits |
| 933 | */ |
| 934 | function toInt96(int256 value) internal pure returns (int96 downcasted) { |
| 935 | downcasted = int96(value); |
| 936 | if (downcasted != value) { |
| 937 | revert SafeCastOverflowedIntDowncast(96, value); |
| 938 | } |
| 939 | } |
| 940 | |
| 941 | /** |
| 942 | * @dev Returns the downcasted int88 from int256, reverting on |
| 943 | * overflow (when the input is less than smallest int88 or |
| 944 | * greater than largest int88). |
| 945 | * |
| 946 | * Counterpart to Solidity's `int88` operator. |
| 947 | * |
| 948 | * Requirements: |
| 949 | * |
| 950 | * - input must fit into 88 bits |
| 951 | */ |
| 952 | function toInt88(int256 value) internal pure returns (int88 downcasted) { |
| 953 | downcasted = int88(value); |
| 954 | if (downcasted != value) { |
| 955 | revert SafeCastOverflowedIntDowncast(88, value); |
| 956 | } |
| 957 | } |
| 958 | |
| 959 | /** |
| 960 | * @dev Returns the downcasted int80 from int256, reverting on |
| 961 | * overflow (when the input is less than smallest int80 or |
| 962 | * greater than largest int80). |
| 963 | * |
| 964 | * Counterpart to Solidity's `int80` operator. |
| 965 | * |
| 966 | * Requirements: |
| 967 | * |
| 968 | * - input must fit into 80 bits |
| 969 | */ |
| 970 | function toInt80(int256 value) internal pure returns (int80 downcasted) { |
| 971 | downcasted = int80(value); |
| 972 | if (downcasted != value) { |
| 973 | revert SafeCastOverflowedIntDowncast(80, value); |
| 974 | } |
| 975 | } |
| 976 | |
| 977 | /** |
| 978 | * @dev Returns the downcasted int72 from int256, reverting on |
| 979 | * overflow (when the input is less than smallest int72 or |
| 980 | * greater than largest int72). |
| 981 | * |
| 982 | * Counterpart to Solidity's `int72` operator. |
| 983 | * |
| 984 | * Requirements: |
| 985 | * |
| 986 | * - input must fit into 72 bits |
| 987 | */ |
| 988 | function toInt72(int256 value) internal pure returns (int72 downcasted) { |
| 989 | downcasted = int72(value); |
| 990 | if (downcasted != value) { |
| 991 | revert SafeCastOverflowedIntDowncast(72, value); |
| 992 | } |
| 993 | } |
| 994 | |
| 995 | /** |
| 996 | * @dev Returns the downcasted int64 from int256, reverting on |
| 997 | * overflow (when the input is less than smallest int64 or |
| 998 | * greater than largest int64). |
| 999 | * |
| 1000 | * Counterpart to Solidity's `int64` operator. |
| 1001 | * |
| 1002 | * Requirements: |
| 1003 | * |
| 1004 | * - input must fit into 64 bits |
| 1005 | */ |
| 1006 | function toInt64(int256 value) internal pure returns (int64 downcasted) { |
| 1007 | downcasted = int64(value); |
| 1008 | if (downcasted != value) { |
| 1009 | revert SafeCastOverflowedIntDowncast(64, value); |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | /** |
| 1014 | * @dev Returns the downcasted int56 from int256, reverting on |
| 1015 | * overflow (when the input is less than smallest int56 or |
| 1016 | * greater than largest int56). |
| 1017 | * |
| 1018 | * Counterpart to Solidity's `int56` operator. |
| 1019 | * |
| 1020 | * Requirements: |
| 1021 | * |
| 1022 | * - input must fit into 56 bits |
| 1023 | */ |
| 1024 | function toInt56(int256 value) internal pure returns (int56 downcasted) { |
| 1025 | downcasted = int56(value); |
| 1026 | if (downcasted != value) { |
| 1027 | revert SafeCastOverflowedIntDowncast(56, value); |
| 1028 | } |
| 1029 | } |
| 1030 | |
| 1031 | /** |
| 1032 | * @dev Returns the downcasted int48 from int256, reverting on |
| 1033 | * overflow (when the input is less than smallest int48 or |
| 1034 | * greater than largest int48). |
| 1035 | * |
| 1036 | * Counterpart to Solidity's `int48` operator. |
| 1037 | * |
| 1038 | * Requirements: |
| 1039 | * |
| 1040 | * - input must fit into 48 bits |
| 1041 | */ |
| 1042 | function toInt48(int256 value) internal pure returns (int48 downcasted) { |
| 1043 | downcasted = int48(value); |
| 1044 | if (downcasted != value) { |
| 1045 | revert SafeCastOverflowedIntDowncast(48, value); |
| 1046 | } |
| 1047 | } |
| 1048 | |
| 1049 | /** |
| 1050 | * @dev Returns the downcasted int40 from int256, reverting on |
| 1051 | * overflow (when the input is less than smallest int40 or |
| 1052 | * greater than largest int40). |
| 1053 | * |
| 1054 | * Counterpart to Solidity's `int40` operator. |
| 1055 | * |
| 1056 | * Requirements: |
| 1057 | * |
| 1058 | * - input must fit into 40 bits |
| 1059 | */ |
| 1060 | function toInt40(int256 value) internal pure returns (int40 downcasted) { |
| 1061 | downcasted = int40(value); |
| 1062 | if (downcasted != value) { |
| 1063 | revert SafeCastOverflowedIntDowncast(40, value); |
| 1064 | } |
| 1065 | } |
| 1066 | |
| 1067 | /** |
| 1068 | * @dev Returns the downcasted int32 from int256, reverting on |
| 1069 | * overflow (when the input is less than smallest int32 or |
| 1070 | * greater than largest int32). |
| 1071 | * |
| 1072 | * Counterpart to Solidity's `int32` operator. |
| 1073 | * |
| 1074 | * Requirements: |
| 1075 | * |
| 1076 | * - input must fit into 32 bits |
| 1077 | */ |
| 1078 | function toInt32(int256 value) internal pure returns (int32 downcasted) { |
| 1079 | downcasted = int32(value); |
| 1080 | if (downcasted != value) { |
| 1081 | revert SafeCastOverflowedIntDowncast(32, value); |
| 1082 | } |
| 1083 | } |
| 1084 | |
| 1085 | /** |
| 1086 | * @dev Returns the downcasted int24 from int256, reverting on |
| 1087 | * overflow (when the input is less than smallest int24 or |
| 1088 | * greater than largest int24). |
| 1089 | * |
| 1090 | * Counterpart to Solidity's `int24` operator. |
| 1091 | * |
| 1092 | * Requirements: |
| 1093 | * |
| 1094 | * - input must fit into 24 bits |
| 1095 | */ |
| 1096 | function toInt24(int256 value) internal pure returns (int24 downcasted) { |
| 1097 | downcasted = int24(value); |
| 1098 | if (downcasted != value) { |
| 1099 | revert SafeCastOverflowedIntDowncast(24, value); |
| 1100 | } |
| 1101 | } |
| 1102 | |
| 1103 | /** |
| 1104 | * @dev Returns the downcasted int16 from int256, reverting on |
| 1105 | * overflow (when the input is less than smallest int16 or |
| 1106 | * greater than largest int16). |
| 1107 | * |
| 1108 | * Counterpart to Solidity's `int16` operator. |
| 1109 | * |
| 1110 | * Requirements: |
| 1111 | * |
| 1112 | * - input must fit into 16 bits |
| 1113 | */ |
| 1114 | function toInt16(int256 value) internal pure returns (int16 downcasted) { |
| 1115 | downcasted = int16(value); |
| 1116 | if (downcasted != value) { |
| 1117 | revert SafeCastOverflowedIntDowncast(16, value); |
| 1118 | } |
| 1119 | } |
| 1120 | |
| 1121 | /** |
| 1122 | * @dev Returns the downcasted int8 from int256, reverting on |
| 1123 | * overflow (when the input is less than smallest int8 or |
| 1124 | * greater than largest int8). |
| 1125 | * |
| 1126 | * Counterpart to Solidity's `int8` operator. |
| 1127 | * |
| 1128 | * Requirements: |
| 1129 | * |
| 1130 | * - input must fit into 8 bits |
| 1131 | */ |
| 1132 | function toInt8(int256 value) internal pure returns (int8 downcasted) { |
| 1133 | downcasted = int8(value); |
| 1134 | if (downcasted != value) { |
| 1135 | revert SafeCastOverflowedIntDowncast(8, value); |
| 1136 | } |
| 1137 | } |
| 1138 | |
| 1139 | /** |
| 1140 | * @dev Converts an unsigned uint256 into a signed int256. |
| 1141 | * |
| 1142 | * Requirements: |
| 1143 | * |
| 1144 | * - input must be less than or equal to maxInt256. |
| 1145 | */ |
| 1146 | 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 | u := iszero(iszero(b)) |
| 1160 | } |
| 1161 | } |
| 1162 | } |
| 1163 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol
Lines covered: 0 / 1 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol) |
| 3 | |
| 4 | pragma solidity ^0.8.20; |
| 5 | |
| 6 | import {SafeCast} from "./SafeCast.sol"; |
| 7 | |
| 8 | /** |
| 9 | * @dev Standard signed math utilities missing in the Solidity language. |
| 10 | */ |
| 11 | library SignedMath { |
| 12 | /** |
| 13 | * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. |
| 14 | * |
| 15 | * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. |
| 16 | * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute |
| 17 | * one branch when needed, making this function more expensive. |
| 18 | */ |
| 19 | function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) { |
| 20 | unchecked { |
| 21 | // branchless ternary works because: |
| 22 | // b ^ (a ^ b) == a |
| 23 | // b ^ 0 == b |
| 24 | return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | /** |
| 29 | * @dev Returns the largest of two signed numbers. |
| 30 | */ |
| 31 | function max(int256 a, int256 b) internal pure returns (int256) { |
| 32 | return ternary(a > b, a, b); |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * @dev Returns the smallest of two signed numbers. |
| 37 | */ |
| 38 | function min(int256 a, int256 b) internal pure returns (int256) { |
| 39 | return ternary(a < b, a, b); |
| 40 | } |
| 41 | |
| 42 | /** |
| 43 | * @dev Returns the average of two signed numbers without overflow. |
| 44 | * The result is rounded towards zero. |
| 45 | */ |
| 46 | function average(int256 a, int256 b) internal pure returns (int256) { |
| 47 | // Formula from the book "Hacker's Delight" |
| 48 | int256 x = (a & b) + ((a ^ b) >> 1); |
| 49 | return x + (int256(uint256(x) >> 255) & (a ^ b)); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * @dev Returns the absolute unsigned value of a signed value. |
| 54 | */ |
| 55 | function abs(int256 n) internal pure returns (uint256) { |
| 56 | unchecked { |
| 57 | // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson. |
| 58 | // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, |
| 59 | // taking advantage of the most significant (or "sign" bit) in two's complement representation. |
| 60 | // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, |
| 61 | // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative). |
| 62 | int256 mask = n >> 255; |
| 63 | |
| 64 | // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. |
| 65 | return uint256((n + mask) ^ mask); |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 |
87.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
Lines covered: 35 / 40 (87.0%)
| 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 | 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 | 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 | 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 | uint256 position = set._positions[value]; |
| 95 | |
| 96 | 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 | uint256 valueIndex = position - 1; |
| 103 | uint256 lastIndex = set._values.length - 1; |
| 104 | |
| 105 | if (valueIndex != lastIndex) { |
| 106 | bytes32 lastValue = set._values[lastIndex]; |
| 107 | |
| 108 | // Move the lastValue to the index where the value to delete is |
| 109 | set._values[valueIndex] = lastValue; |
| 110 | // Update the tracked position of the lastValue (that was just moved) |
| 111 | set._positions[lastValue] = position; |
| 112 | } |
| 113 | |
| 114 | // Delete the slot where the moved value was stored |
| 115 | set._values.pop(); |
| 116 | |
| 117 | // Delete the tracked position for the deleted slot |
| 118 | delete set._positions[value]; |
| 119 | |
| 120 | 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 | 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 | function _at(Set storage set, uint256 index) private view returns (bytes32) { |
| 166 | return set._values[index]; |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * @dev Return the entire set in an array |
| 171 | * |
| 172 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 173 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 174 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 175 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 176 | */ |
| 177 | function _values(Set storage set) private view returns (bytes32[] memory) { |
| 178 | return set._values; |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * @dev Return a slice of the set in an array |
| 183 | * |
| 184 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 185 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 186 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 187 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 188 | */ |
| 189 | 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 | 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 | 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 | function contains(AddressSet storage set, address value) internal view returns (bool) { |
| 345 | 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 | function at(AddressSet storage set, uint256 index) internal view returns (address) { |
| 366 | 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 | function values(AddressSet storage set) internal view returns (address[] memory) { |
| 378 | 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 | } |
| 793 |
89.0%
lib/v2-core/src/hooks/BaseHook.sol
Lines covered: 89 / 100 (89.0%)
| 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 | 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 | 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 | address public transient asset; |
| 36 | |
| 37 | /// @notice Execution nonce for creating unique contexts |
| 38 | uint256 public transient executionNonce; |
| 39 | |
| 40 | /// @notice Last execution context caller |
| 41 | address public transient lastCaller; |
| 42 | |
| 43 | // Storage offsets for different state variables |
| 44 | uint256 private constant OUT_AMOUNT_OFFSET = 1; |
| 45 | uint256 private constant PRE_EXECUTE_MUTEX_OFFSET = 2; |
| 46 | uint256 private constant POST_EXECUTE_MUTEX_OFFSET = 3; |
| 47 | |
| 48 | /// @notice Base storage key for hook execution state |
| 49 | bytes32 private constant HOOK_EXECUTION_STORAGE = keccak256("hook.execution.state"); |
| 50 | |
| 51 | /// @notice Storage key for account context mapping |
| 52 | 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 | 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 | 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 | hookType = hookType_; |
| 103 | SUB_TYPE = subType_; |
| 104 | } |
| 105 | |
| 106 | modifier onlyLastCaller() { |
| 107 | if (msg.sender != lastCaller) revert UNAUTHORIZED_CALLER(); |
| 108 | _; |
| 109 | } |
| 110 | |
| 111 | /*////////////////////////////////////////////////////////////// |
| 112 | EXECUTION SECURITY |
| 113 | //////////////////////////////////////////////////////////////*/ |
| 114 | /// @inheritdoc ISuperHook |
| 115 | function setExecutionContext(address caller) external { |
| 116 | _createExecutionContext(caller); |
| 117 | lastCaller = msg.sender; |
| 118 | } |
| 119 | |
| 120 | /// @dev Standard build pattern - MUST include preExecute first, postExecute last |
| 121 | /// @inheritdoc ISuperHook |
| 122 | function build( |
| 123 | address prevHook, |
| 124 | address account, |
| 125 | bytes calldata hookData |
| 126 | ) |
| 127 | external |
| 128 | view |
| 129 | virtual |
| 130 | returns (Execution[] memory executions) |
| 131 | { |
| 132 | // Get hook-specific executions |
| 133 | Execution[] memory hookExecutions = _buildHookExecutions(prevHook, account, hookData); |
| 134 | |
| 135 | // Always include pre + hook + post |
| 136 | executions = new Execution[](hookExecutions.length + 2); |
| 137 | |
| 138 | // FIRST: preExecute |
| 139 | executions[0] = Execution({ |
| 140 | target: address(this), |
| 141 | value: 0, |
| 142 | callData: abi.encodeCall(this.preExecute, (prevHook, account, hookData)) |
| 143 | }); |
| 144 | |
| 145 | // MIDDLE: hook-specific operations |
| 146 | for (uint256 i = 0; i < hookExecutions.length; i++) { |
| 147 | executions[i + 1] = hookExecutions[i]; |
| 148 | } |
| 149 | |
| 150 | // LAST: postExecute |
| 151 | executions[executions.length - 1] = Execution({ |
| 152 | target: address(this), |
| 153 | value: 0, |
| 154 | callData: abi.encodeCall(this.postExecute, (prevHook, account, hookData)) |
| 155 | }); |
| 156 | } |
| 157 | |
| 158 | /// @inheritdoc ISuperHook |
| 159 | function preExecute(address prevHook, address account, bytes calldata data) external { |
| 160 | if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
| 161 | uint256 context = _getCurrentExecutionContext(account); |
| 162 | if (_getPreExecuteMutex(context)) revert PRE_EXECUTE_ALREADY_CALLED(); |
| 163 | _setPreExecuteMutex(context, true); |
| 164 | _preExecute(prevHook, account, data); |
| 165 | } |
| 166 | |
| 167 | /// @inheritdoc ISuperHook |
| 168 | function postExecute(address prevHook, address account, bytes calldata data) external { |
| 169 | if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
| 170 | uint256 context = _getCurrentExecutionContext(account); |
| 171 | if (_getPostExecuteMutex(context)) revert POST_EXECUTE_ALREADY_CALLED(); |
| 172 | _setPostExecuteMutex(context, true); |
| 173 | _postExecute(prevHook, account, data); |
| 174 | } |
| 175 | |
| 176 | /// @inheritdoc ISuperHookSetter |
| 177 | function setOutAmount(uint256 _outAmount, address caller) external { |
| 178 | uint256 context = _getCurrentExecutionContext(caller); |
| 179 | if (_getPreExecuteMutex(context) || _getPostExecuteMutex(context)) { |
| 180 | revert CANNOT_SET_OUT_AMOUNT(); |
| 181 | } |
| 182 | |
| 183 | bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
| 184 | assembly { |
| 185 | tstore(key, _outAmount) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | function getOutAmount(address caller) public view returns (uint256) { |
| 190 | return _getOutAmount(_getCurrentExecutionContext(caller)); |
| 191 | } |
| 192 | |
| 193 | /// @inheritdoc ISuperHook |
| 194 | function resetExecutionState(address caller) external onlyLastCaller { |
| 195 | uint256 context = _getCurrentExecutionContext(caller); |
| 196 | if (!_getPreExecuteMutex(context) || !_getPostExecuteMutex(context)) { |
| 197 | revert INCOMPLETE_HOOK_EXECUTION(); |
| 198 | } |
| 199 | |
| 200 | _clearExecutionState(context); |
| 201 | } |
| 202 | |
| 203 | /*////////////////////////////////////////////////////////////// |
| 204 | VIEW METHODS |
| 205 | //////////////////////////////////////////////////////////////*/ |
| 206 | /// @inheritdoc ISuperHook |
| 207 | function subtype() external view returns (bytes32) { |
| 208 | return SUB_TYPE; |
| 209 | } |
| 210 | |
| 211 | /// @inheritdoc ISuperHookInspector |
| 212 | 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 | 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 | function _decodeBool(bytes memory data, uint256 offset) internal pure returns (bool) { |
| 261 | 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 | function _replaceCalldataAmount( |
| 273 | bytes memory data, |
| 274 | uint256 amount, |
| 275 | uint256 offset |
| 276 | ) |
| 277 | internal |
| 278 | pure |
| 279 | returns (bytes memory) |
| 280 | { |
| 281 | bytes memory newAmountEncoded = abi.encodePacked(amount); |
| 282 | for (uint256 i; i < 32; ++i) { |
| 283 | data[offset + i] = newAmountEncoded[i]; |
| 284 | } |
| 285 | return data; |
| 286 | } |
| 287 | |
| 288 | function _makeAccountContextKey(address account) private pure returns (bytes32) { |
| 289 | return keccak256(abi.encodePacked(ACCOUNT_CONTEXT_STORAGE, account)); |
| 290 | } |
| 291 | |
| 292 | function _createExecutionContext(address caller) private returns (uint256) { |
| 293 | // Always increment nonce for new execution context |
| 294 | executionNonce++; |
| 295 | bytes32 key = _makeAccountContextKey(caller); |
| 296 | |
| 297 | // Store this context for the current caller |
| 298 | uint256 currentNonce = executionNonce; // Load into local variable for assembly |
| 299 | assembly { |
| 300 | tstore(key, currentNonce) |
| 301 | } |
| 302 | |
| 303 | return executionNonce; |
| 304 | } |
| 305 | |
| 306 | function _getCurrentExecutionContext(address caller) private view returns (uint256 context) { |
| 307 | bytes32 key = _makeAccountContextKey(caller); |
| 308 | assembly { |
| 309 | context := tload(key) |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | function _makeKey(uint256 context, uint256 offset) private pure returns (bytes32) { |
| 314 | return keccak256(abi.encodePacked(HOOK_EXECUTION_STORAGE, context, offset)); |
| 315 | } |
| 316 | |
| 317 | function _getOutAmount(uint256 context) private view returns (uint256 value) { |
| 318 | 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 | uint256 context = _getCurrentExecutionContext(caller); |
| 326 | bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
| 327 | assembly { |
| 328 | tstore(key, value) |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | function _getPreExecuteMutex(uint256 context) private view returns (bool value) { |
| 333 | bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
| 334 | assembly { |
| 335 | value := tload(key) |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | function _setPreExecuteMutex(uint256 context, bool value) private { |
| 340 | bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
| 341 | assembly { |
| 342 | tstore(key, value) |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | function _getPostExecuteMutex(uint256 context) private view returns (bool value) { |
| 347 | bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
| 348 | assembly { |
| 349 | value := tload(key) |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | function _setPostExecuteMutex(uint256 context, bool value) private { |
| 354 | bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
| 355 | assembly { |
| 356 | tstore(key, value) |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | function _clearExecutionState(uint256 context) private { |
| 361 | _setPreExecuteMutex(context, false); |
| 362 | _setPostExecuteMutex(context, false); |
| 363 | } |
| 364 | } |
| 365 |
0.0%
lib/v2-core/src/hooks/VaultBankLockableHook.sol
Lines covered: 0 / 2 (0.0%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | import { HookDataDecoder } from "../libraries/HookDataDecoder.sol"; |
| 5 | |
| 6 | /// @title VaultBankLockableHook |
| 7 | /// @author Superform Labs |
| 8 | /// @notice Base implementation for all vault bank lockable hooks in the Superform system |
| 9 | abstract contract VaultBankLockableHook { |
| 10 | using HookDataDecoder for bytes; |
| 11 | |
| 12 | /// @notice The vault bank address (if applicable) for cross-chain operations |
| 13 | /// @dev Used primarily in bridge hooks to track source/destination vault banks |
| 14 | address public transient vaultBank; |
| 15 | |
| 16 | /// @notice The destination chain ID for cross-chain operations |
| 17 | /// @dev Used primarily in bridge hooks to track target chain |
| 18 | uint256 public transient dstChainId; |
| 19 | } |
| 20 |
95.0%
lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol
Lines covered: 39 / 41 (95.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 59 | address token = BytesLib.toAddress(data, 52); |
| 60 | uint256 amount = _decodeAmount(data); |
| 61 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 62 | |
| 63 | if (usePrevHookAmount) { |
| 64 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 65 | } |
| 66 | |
| 67 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 68 | if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
| 69 | |
| 70 | executions = new Execution[](4); |
| 71 | executions[0] = |
| 72 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 73 | executions[1] = |
| 74 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 75 | executions[2] = |
| 76 | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) }); |
| 77 | executions[3] = |
| 78 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 79 | } |
| 80 | |
| 81 | /*////////////////////////////////////////////////////////////// |
| 82 | EXTERNAL METHODS |
| 83 | //////////////////////////////////////////////////////////////*/ |
| 84 | |
| 85 | /// @inheritdoc ISuperHookInflowOutflow |
| 86 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 87 | return _decodeAmount(data); |
| 88 | } |
| 89 | |
| 90 | /// @inheritdoc ISuperHookContextAware |
| 91 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 92 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 93 | } |
| 94 | |
| 95 | /// @inheritdoc ISuperHookInspector |
| 96 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 97 | return abi.encodePacked( |
| 98 | data.extractYieldSource(), |
| 99 | 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 | _setOutAmount(_getBalance(account, data), account); |
| 109 | spToken = data.extractYieldSource(); |
| 110 | } |
| 111 | |
| 112 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 113 | _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 | return IERC4626(data.extractYieldSource()).balanceOf(account); |
| 125 | } |
| 126 | } |
| 127 |
62.0%
lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol
Lines covered: 20 / 32 (62.0%)
| 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 | 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 | { |
| 50 | address yieldSource = data.extractYieldSource(); |
| 51 | uint256 amount = _decodeAmount(data); |
| 52 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 53 | |
| 54 | if (usePrevHookAmount) { |
| 55 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 56 | } |
| 57 | |
| 58 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 59 | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 60 | |
| 61 | executions = new Execution[](1); |
| 62 | executions[0] = |
| 63 | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) }); |
| 64 | } |
| 65 | |
| 66 | /*////////////////////////////////////////////////////////////// |
| 67 | EXTERNAL METHODS |
| 68 | //////////////////////////////////////////////////////////////*/ |
| 69 | |
| 70 | /// @inheritdoc ISuperHookInflowOutflow |
| 71 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 72 | return _decodeAmount(data); |
| 73 | } |
| 74 | |
| 75 | /// @inheritdoc ISuperHookContextAware |
| 76 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 77 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 78 | } |
| 79 | |
| 80 | /// @inheritdoc ISuperHookInspector |
| 81 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 82 | return abi.encodePacked(data.extractYieldSource()); |
| 83 | } |
| 84 | |
| 85 | /*////////////////////////////////////////////////////////////// |
| 86 | INTERNAL METHODS |
| 87 | //////////////////////////////////////////////////////////////*/ |
| 88 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 89 | // store current balance |
| 90 | _setOutAmount(_getBalance(account, data), account); |
| 91 | spToken = data.extractYieldSource(); |
| 92 | } |
| 93 | |
| 94 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 95 | _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 | } |
| 109 |
97.0%
lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol
Lines covered: 46 / 47 (97.0%)
| 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 | 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 53 | address owner = BytesLib.toAddress(data, 52); |
| 54 | uint256 shares = _decodeAmount(data); |
| 55 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 56 | |
| 57 | if (usePrevHookAmount) { |
| 58 | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 59 | } |
| 60 | |
| 61 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 62 | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 63 | |
| 64 | executions = new Execution[](1); |
| 65 | executions[0] = Execution({ |
| 66 | target: yieldSource, |
| 67 | value: 0, |
| 68 | callData: abi.encodeCall(IERC4626.redeem, (shares, account, owner)) |
| 69 | }); |
| 70 | } |
| 71 | |
| 72 | /*////////////////////////////////////////////////////////////// |
| 73 | EXTERNAL METHODS |
| 74 | //////////////////////////////////////////////////////////////*/ |
| 75 | |
| 76 | /// @inheritdoc ISuperHookInflowOutflow |
| 77 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 78 | return _decodeAmount(data); |
| 79 | } |
| 80 | |
| 81 | /// @inheritdoc ISuperHookContextAware |
| 82 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 83 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 84 | } |
| 85 | |
| 86 | /// @inheritdoc ISuperHookOutflow |
| 87 | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 88 | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 89 | } |
| 90 | |
| 91 | /// @inheritdoc ISuperHookInspector |
| 92 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 93 | return abi.encodePacked( |
| 94 | data.extractYieldSource(), |
| 95 | BytesLib.toAddress(data, 52) // owner |
| 96 | ); |
| 97 | } |
| 98 | |
| 99 | /*////////////////////////////////////////////////////////////// |
| 100 | INTERNAL METHODS |
| 101 | //////////////////////////////////////////////////////////////*/ |
| 102 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 103 | address yieldSource = data.extractYieldSource(); |
| 104 | asset = IERC4626(yieldSource).asset(); |
| 105 | _setOutAmount(_getBalance(account, data), account); |
| 106 | usedShares = _getSharesBalance(account, data); |
| 107 | spToken = yieldSource; |
| 108 | } |
| 109 | |
| 110 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 111 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 112 | 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 | return IERC20(asset).balanceOf(account); |
| 124 | } |
| 125 | |
| 126 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 127 | address yieldSource = data.extractYieldSource(); |
| 128 | address owner = BytesLib.toAddress(data, 52); |
| 129 | if (owner == address(0)) { |
| 130 | owner = account; |
| 131 | } |
| 132 | return IERC4626(yieldSource).balanceOf(owner); |
| 133 | } |
| 134 | } |
| 135 |
88.0%
lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol
Lines covered: 40 / 45 (88.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 60 | address tokenIn = BytesLib.toAddress(data, 52); |
| 61 | uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
| 62 | uint256 minSharesOut = BytesLib.toUint256(data, 104); |
| 63 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 64 | |
| 65 | if (usePrevHookAmount) { |
| 66 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 67 | } |
| 68 | |
| 69 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 70 | if (yieldSource == address(0) || account == address(0) || tokenIn == address(0)) revert ADDRESS_NOT_VALID(); |
| 71 | |
| 72 | executions = new Execution[](4); |
| 73 | executions[0] = |
| 74 | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 75 | executions[1] = |
| 76 | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 77 | executions[2] = Execution({ |
| 78 | target: yieldSource, |
| 79 | value: 0, |
| 80 | callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
| 81 | }); |
| 82 | executions[3] = |
| 83 | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 84 | } |
| 85 | |
| 86 | /*////////////////////////////////////////////////////////////// |
| 87 | EXTERNAL METHODS |
| 88 | //////////////////////////////////////////////////////////////*/ |
| 89 | |
| 90 | /// @inheritdoc ISuperHookInflowOutflow |
| 91 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 92 | return _decodeAmount(data); |
| 93 | } |
| 94 | |
| 95 | /// @inheritdoc ISuperHookContextAware |
| 96 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 97 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 98 | } |
| 99 | |
| 100 | /// @inheritdoc ISuperHookInspector |
| 101 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 102 | return abi.encodePacked( |
| 103 | data.extractYieldSource(), |
| 104 | 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 | _setOutAmount(_getBalance(account, data), account); |
| 113 | spToken = data.extractYieldSource(); |
| 114 | asset = BytesLib.toAddress(data, 52); |
| 115 | } |
| 116 | |
| 117 | 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 | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 125 | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 126 | } |
| 127 | |
| 128 | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 129 | return IStandardizedYield(data.extractYieldSource()).balanceOf(account); |
| 130 | } |
| 131 | } |
| 132 |
46.0%
lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol
Lines covered: 18 / 39 (46.0%)
| 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 | 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 | { |
| 52 | address yieldSource = data.extractYieldSource(); |
| 53 | address tokenIn = BytesLib.toAddress(data, 52); |
| 54 | uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
| 55 | uint256 minSharesOut = BytesLib.toUint256(data, 104); |
| 56 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 57 | |
| 58 | if (usePrevHookAmount) { |
| 59 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 60 | } |
| 61 | |
| 62 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 63 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 64 | |
| 65 | executions = new Execution[](1); |
| 66 | executions[0] = Execution({ |
| 67 | target: yieldSource, |
| 68 | value: tokenIn == address(0) ? amount : 0, |
| 69 | callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
| 70 | }); |
| 71 | } |
| 72 | |
| 73 | /*////////////////////////////////////////////////////////////// |
| 74 | EXTERNAL METHODS |
| 75 | //////////////////////////////////////////////////////////////*/ |
| 76 | |
| 77 | /// @inheritdoc ISuperHookInflowOutflow |
| 78 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 79 | return _decodeAmount(data); |
| 80 | } |
| 81 | |
| 82 | /// @inheritdoc ISuperHookContextAware |
| 83 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 84 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 85 | } |
| 86 | |
| 87 | /// @inheritdoc ISuperHookInspector |
| 88 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 89 | return abi.encodePacked( |
| 90 | data.extractYieldSource(), |
| 91 | BytesLib.toAddress(data, 52) // tokenIn |
| 92 | ); |
| 93 | } |
| 94 | |
| 95 | /*////////////////////////////////////////////////////////////// |
| 96 | INTERNAL METHODS |
| 97 | //////////////////////////////////////////////////////////////*/ |
| 98 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | _setOutAmount(_getBalance(account, data), account); |
| 100 | spToken = data.extractYieldSource(); |
| 101 | asset = BytesLib.toAddress(data, 52); |
| 102 | } |
| 103 | |
| 104 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 105 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 106 | } |
| 107 | |
| 108 | /*////////////////////////////////////////////////////////////// |
| 109 | PRIVATE METHODS |
| 110 | //////////////////////////////////////////////////////////////*/ |
| 111 | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 112 | 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 | } |
| 119 |
61.0%
lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol
Lines covered: 27 / 44 (61.0%)
| 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 | 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 | { |
| 53 | address yieldSource = data.extractYieldSource(); |
| 54 | address tokenOut = BytesLib.toAddress(data, 52); |
| 55 | uint256 shares = _decodeAmount(data); |
| 56 | uint256 minTokenOut = BytesLib.toUint256(data, 104); |
| 57 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 58 | |
| 59 | if (usePrevHookAmount) { |
| 60 | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 61 | } |
| 62 | |
| 63 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 64 | if (yieldSource == address(0) || tokenOut == address(0)) revert ADDRESS_NOT_VALID(); |
| 65 | |
| 66 | executions = new Execution[](1); |
| 67 | executions[0] = Execution({ |
| 68 | target: yieldSource, |
| 69 | value: 0, |
| 70 | callData: abi.encodeCall(IStandardizedYield.redeem, (account, shares, tokenOut, minTokenOut, false)) |
| 71 | }); |
| 72 | } |
| 73 | |
| 74 | /*////////////////////////////////////////////////////////////// |
| 75 | EXTERNAL METHODS |
| 76 | //////////////////////////////////////////////////////////////*/ |
| 77 | |
| 78 | /// @inheritdoc ISuperHookInflowOutflow |
| 79 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 80 | return _decodeAmount(data); |
| 81 | } |
| 82 | |
| 83 | /// @inheritdoc ISuperHookContextAware |
| 84 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 85 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 86 | } |
| 87 | |
| 88 | /// @inheritdoc ISuperHookOutflow |
| 89 | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 90 | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 91 | } |
| 92 | |
| 93 | /// @inheritdoc ISuperHookInspector |
| 94 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 95 | return abi.encodePacked( |
| 96 | data.extractYieldSource(), |
| 97 | BytesLib.toAddress(data, 52) // tokenOut |
| 98 | ); |
| 99 | } |
| 100 | |
| 101 | /*////////////////////////////////////////////////////////////// |
| 102 | INTERNAL METHODS |
| 103 | //////////////////////////////////////////////////////////////*/ |
| 104 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 105 | asset = BytesLib.toAddress(data, 52); // tokenOut from data |
| 106 | _setOutAmount(_getBalance(account, data), account); |
| 107 | usedShares = _getSharesBalance(account, data); |
| 108 | spToken = data.extractYieldSource(); |
| 109 | } |
| 110 | |
| 111 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 112 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 113 | 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 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 124 | return IERC20(asset).balanceOf(account); |
| 125 | } |
| 126 | |
| 127 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 128 | address yieldSource = data.extractYieldSource(); |
| 129 | return IStandardizedYield(yieldSource).balanceOf(account); |
| 130 | } |
| 131 | } |
| 132 |
52.0%
lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol
Lines covered: 22 / 42 (52.0%)
| 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 | 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 | { |
| 52 | address yieldSource = data.extractYieldSource(); |
| 53 | address token = BytesLib.toAddress(data, 52); |
| 54 | uint256 amount = _decodeAmount(data); |
| 55 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 56 | |
| 57 | if (usePrevHookAmount) { |
| 58 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 59 | } |
| 60 | |
| 61 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 62 | if (yieldSource == address(0) || account == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
| 63 | |
| 64 | executions = new Execution[](4); |
| 65 | executions[0] = |
| 66 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 67 | executions[1] = |
| 68 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 69 | executions[2] = Execution({ |
| 70 | target: yieldSource, |
| 71 | value: 0, |
| 72 | callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
| 73 | }); |
| 74 | executions[3] = |
| 75 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 76 | } |
| 77 | |
| 78 | /*////////////////////////////////////////////////////////////// |
| 79 | EXTERNAL METHODS |
| 80 | //////////////////////////////////////////////////////////////*/ |
| 81 | |
| 82 | /// @inheritdoc ISuperHookInflowOutflow |
| 83 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 84 | return _decodeAmount(data); |
| 85 | } |
| 86 | |
| 87 | /// @inheritdoc ISuperHookContextAware |
| 88 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 89 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 90 | } |
| 91 | |
| 92 | /// @inheritdoc ISuperHookInspector |
| 93 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 94 | return abi.encodePacked( |
| 95 | data.extractYieldSource(), |
| 96 | BytesLib.toAddress(data, 52) //token |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /*////////////////////////////////////////////////////////////// |
| 101 | INTERNAL METHODS |
| 102 | //////////////////////////////////////////////////////////////*/ |
| 103 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 104 | _setOutAmount(_getBalance(account, data), account); |
| 105 | } |
| 106 | |
| 107 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 108 | _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 | } |
| 122 |
100.0%
lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol
Lines covered: 13 / 13 (100.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 39 | |
| 40 | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 41 | |
| 42 | executions = new Execution[](1); |
| 43 | executions[0] = Execution({ |
| 44 | target: yieldSource, |
| 45 | value: 0, |
| 46 | callData: abi.encodeCall(IERC7540CancelDeposit.cancelDepositRequest, (0, account)) |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | /// @inheritdoc ISuperHookInspector |
| 51 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 52 | return abi.encodePacked(data.extractYieldSource()); |
| 53 | } |
| 54 | } |
| 55 |
100.0%
lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol
Lines covered: 13 / 13 (100.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 39 | |
| 40 | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 41 | |
| 42 | executions = new Execution[](1); |
| 43 | executions[0] = Execution({ |
| 44 | target: yieldSource, |
| 45 | value: 0, |
| 46 | callData: abi.encodeCall(IERC7540CancelRedeem.cancelRedeemRequest, (0, account)) |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | /// @inheritdoc ISuperHookInspector |
| 51 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 52 | return abi.encodePacked(data.extractYieldSource()); |
| 53 | } |
| 54 | } |
| 55 |
93.0%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol
Lines covered: 27 / 29 (93.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 43 | address receiver = BytesLib.toAddress(data, 52); |
| 44 | |
| 45 | if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
| 46 | |
| 47 | executions = new Execution[](1); |
| 48 | executions[0] = Execution({ |
| 49 | target: yieldSource, |
| 50 | value: 0, |
| 51 | callData: abi.encodeCall(IERC7540CancelDeposit.claimCancelDepositRequest, (0, receiver, account)) |
| 52 | }); |
| 53 | } |
| 54 | |
| 55 | /*////////////////////////////////////////////////////////////// |
| 56 | EXTERNAL METHODS |
| 57 | //////////////////////////////////////////////////////////////*/ |
| 58 | |
| 59 | /// @inheritdoc ISuperHookAsyncCancelations |
| 60 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 61 | return CancelationType.INFLOW; |
| 62 | } |
| 63 | |
| 64 | /// @inheritdoc ISuperHookInspector |
| 65 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 66 | return abi.encodePacked( |
| 67 | data.extractYieldSource(), |
| 68 | 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 | address yieldSource = data.extractYieldSource(); |
| 77 | address receiver = BytesLib.toAddress(data, 52); |
| 78 | asset = IERC7540(yieldSource).asset(); |
| 79 | // store current balance |
| 80 | _setOutAmount(_getBalance(receiver, data), account); |
| 81 | } |
| 82 | |
| 83 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 84 | address receiver = BytesLib.toAddress(data, 52); |
| 85 | |
| 86 | _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 | return IERC20(asset).balanceOf(account); |
| 95 | } |
| 96 | } |
| 97 |
92.0%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol
Lines covered: 25 / 27 (92.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 44 | address receiver = BytesLib.toAddress(data, 52); |
| 45 | |
| 46 | if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
| 47 | |
| 48 | executions = new Execution[](1); |
| 49 | executions[0] = Execution({ |
| 50 | target: yieldSource, |
| 51 | value: 0, |
| 52 | callData: abi.encodeCall(IERC7540CancelRedeem.claimCancelRedeemRequest, (0, receiver, account)) |
| 53 | }); |
| 54 | } |
| 55 | |
| 56 | /*////////////////////////////////////////////////////////////// |
| 57 | EXTERNAL METHODS |
| 58 | //////////////////////////////////////////////////////////////*/ |
| 59 | |
| 60 | /// @inheritdoc ISuperHookAsyncCancelations |
| 61 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 62 | return CancelationType.OUTFLOW; |
| 63 | } |
| 64 | |
| 65 | /// @inheritdoc ISuperHookInspector |
| 66 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 67 | return abi.encodePacked( |
| 68 | data.extractYieldSource(), |
| 69 | 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 | _setOutAmount(_getBalance(account, data), account); |
| 78 | spToken = IERC7540(data.extractYieldSource()).share(); |
| 79 | } |
| 80 | |
| 81 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 82 | address receiver = BytesLib.toAddress(data, 52); |
| 83 | _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 | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 92 | } |
| 93 | } |
| 94 |
59.0%
lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol
Lines covered: 19 / 32 (59.0%)
| 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 | 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 | { |
| 51 | address yieldSource = data.extractYieldSource(); |
| 52 | uint256 amount = _decodeAmount(data); |
| 53 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 54 | |
| 55 | if (usePrevHookAmount) { |
| 56 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 57 | } |
| 58 | |
| 59 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 60 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 61 | |
| 62 | executions = new Execution[](1); |
| 63 | executions[0] = Execution({ |
| 64 | target: yieldSource, |
| 65 | value: 0, |
| 66 | callData: abi.encodeCall(IERC7540.deposit, (amount, account, account)) |
| 67 | }); |
| 68 | } |
| 69 | |
| 70 | /*////////////////////////////////////////////////////////////// |
| 71 | EXTERNAL METHODS |
| 72 | //////////////////////////////////////////////////////////////*/ |
| 73 | |
| 74 | /// @inheritdoc ISuperHookInflowOutflow |
| 75 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 76 | return _decodeAmount(data); |
| 77 | } |
| 78 | |
| 79 | /// @inheritdoc ISuperHookContextAware |
| 80 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 81 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 82 | } |
| 83 | |
| 84 | /// @inheritdoc ISuperHookInspector |
| 85 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 86 | return abi.encodePacked(data.extractYieldSource()); |
| 87 | } |
| 88 | |
| 89 | /*////////////////////////////////////////////////////////////// |
| 90 | INTERNAL METHODS |
| 91 | //////////////////////////////////////////////////////////////*/ |
| 92 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 93 | // store current balance |
| 94 | _setOutAmount(_getBalance(account, data), account); |
| 95 | spToken = IERC7540(data.extractYieldSource()).share(); |
| 96 | } |
| 97 | |
| 98 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 99 | _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 | } |
| 113 |
100.0%
lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol
Lines covered: 41 / 41 (100.0%)
| 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 | 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 53 | uint256 shares = _decodeAmount(data); |
| 54 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | |
| 56 | if (usePrevHookAmount) { |
| 57 | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
| 59 | |
| 60 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 61 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | |
| 63 | executions = new Execution[](1); |
| 64 | executions[0] = Execution({ |
| 65 | target: yieldSource, |
| 66 | value: 0, |
| 67 | callData: abi.encodeCall(IERC7540.redeem, (shares, account, account)) |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | /*////////////////////////////////////////////////////////////// |
| 72 | EXTERNAL METHODS |
| 73 | //////////////////////////////////////////////////////////////*/ |
| 74 | /// @inheritdoc ISuperHookInflowOutflow |
| 75 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 76 | return _decodeAmount(data); |
| 77 | } |
| 78 | |
| 79 | /// @inheritdoc ISuperHookContextAware |
| 80 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 81 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 82 | } |
| 83 | |
| 84 | /// @inheritdoc ISuperHookOutflow |
| 85 | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 86 | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 87 | } |
| 88 | |
| 89 | /// @inheritdoc ISuperHookInspector |
| 90 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 91 | return abi.encodePacked(data.extractYieldSource()); |
| 92 | } |
| 93 | |
| 94 | /*////////////////////////////////////////////////////////////// |
| 95 | INTERNAL METHODS |
| 96 | //////////////////////////////////////////////////////////////*/ |
| 97 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 98 | address yieldSource = data.extractYieldSource(); |
| 99 | asset = IERC7540(yieldSource).asset(); |
| 100 | _setOutAmount(_getBalance(account, data), account); |
| 101 | usedShares = _getSharesBalance(account, data); |
| 102 | spToken = IERC7540(yieldSource).share(); |
| 103 | } |
| 104 | |
| 105 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 106 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 107 | 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 | return IERC20(asset).balanceOf(account); |
| 119 | } |
| 120 | |
| 121 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 122 | address yieldSource = data.extractYieldSource(); |
| 123 | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 124 | } |
| 125 | } |
| 126 |
59.0%
lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol
Lines covered: 19 / 32 (59.0%)
| 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 | 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 | { |
| 56 | address yieldSource = data.extractYieldSource(); |
| 57 | uint256 amount = _decodeAmount(data); |
| 58 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 59 | |
| 60 | if (usePrevHookAmount) { |
| 61 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 62 | } |
| 63 | |
| 64 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 65 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 66 | |
| 67 | executions = new Execution[](1); |
| 68 | executions[0] = Execution({ |
| 69 | target: yieldSource, |
| 70 | value: 0, |
| 71 | callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | /// @inheritdoc ISuperHookAsyncCancelations |
| 76 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 77 | return CancelationType.NONE; |
| 78 | } |
| 79 | |
| 80 | /*////////////////////////////////////////////////////////////// |
| 81 | EXTERNAL METHODS |
| 82 | //////////////////////////////////////////////////////////////*/ |
| 83 | |
| 84 | /// @inheritdoc ISuperHookInflowOutflow |
| 85 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 86 | return _decodeAmount(data); |
| 87 | } |
| 88 | |
| 89 | /// @inheritdoc ISuperHookContextAware |
| 90 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 91 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 92 | } |
| 93 | |
| 94 | /// @inheritdoc ISuperHookInspector |
| 95 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 96 | return abi.encodePacked(data.extractYieldSource()); |
| 97 | } |
| 98 | |
| 99 | /*////////////////////////////////////////////////////////////// |
| 100 | INTERNAL METHODS |
| 101 | //////////////////////////////////////////////////////////////*/ |
| 102 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 103 | _setOutAmount(_getBalance(account, data), account); |
| 104 | } |
| 105 | |
| 106 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | _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 | } |
| 121 |
59.0%
lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol
Lines covered: 19 / 32 (59.0%)
| 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 | 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 | { |
| 57 | address yieldSource = data.extractYieldSource(); |
| 58 | uint256 shares = _decodeAmount(data); |
| 59 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 60 | |
| 61 | if (usePrevHookAmount) { |
| 62 | shares = ISuperHookResult(prevHook).getOutAmount(account); |
| 63 | } |
| 64 | |
| 65 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 66 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 67 | |
| 68 | executions = new Execution[](1); |
| 69 | executions[0] = Execution({ |
| 70 | target: yieldSource, |
| 71 | value: 0, |
| 72 | callData: abi.encodeCall(IERC7540.requestRedeem, (shares, account, account)) |
| 73 | }); |
| 74 | } |
| 75 | |
| 76 | /// @inheritdoc ISuperHookAsyncCancelations |
| 77 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 78 | return CancelationType.NONE; |
| 79 | } |
| 80 | |
| 81 | /*////////////////////////////////////////////////////////////// |
| 82 | EXTERNAL METHODS |
| 83 | //////////////////////////////////////////////////////////////*/ |
| 84 | |
| 85 | /// @inheritdoc ISuperHookInflowOutflow |
| 86 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 87 | return _decodeAmount(data); |
| 88 | } |
| 89 | |
| 90 | /// @inheritdoc ISuperHookContextAware |
| 91 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 92 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 93 | } |
| 94 | |
| 95 | /// @inheritdoc ISuperHookInspector |
| 96 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 97 | return abi.encodePacked(data.extractYieldSource()); |
| 98 | } |
| 99 | |
| 100 | /*////////////////////////////////////////////////////////////// |
| 101 | INTERNAL METHODS |
| 102 | //////////////////////////////////////////////////////////////*/ |
| 103 | |
| 104 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 105 | _setOutAmount(_getBalance(account, data), account); |
| 106 | } |
| 107 | |
| 108 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 109 | _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 | } |
| 124 |
4.0%
lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol
Lines covered: 2 / 41 (4.0%)
| 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 | 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 | { |
| 52 | address yieldSource = data.extractYieldSource(); |
| 53 | uint256 amount = _decodeAmount(data); |
| 54 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | |
| 56 | if (usePrevHookAmount) { |
| 57 | amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
| 59 | |
| 60 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 61 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | |
| 63 | executions = new Execution[](1); |
| 64 | executions[0] = Execution({ |
| 65 | target: yieldSource, |
| 66 | value: 0, |
| 67 | callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | /*////////////////////////////////////////////////////////////// |
| 72 | EXTERNAL METHODS |
| 73 | //////////////////////////////////////////////////////////////*/ |
| 74 | |
| 75 | /// @inheritdoc ISuperHookInflowOutflow |
| 76 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 77 | return _decodeAmount(data); |
| 78 | } |
| 79 | |
| 80 | /// @inheritdoc ISuperHookContextAware |
| 81 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 82 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 83 | } |
| 84 | |
| 85 | /// @inheritdoc ISuperHookOutflow |
| 86 | 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 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 92 | return abi.encodePacked(data.extractYieldSource()); |
| 93 | } |
| 94 | |
| 95 | /*////////////////////////////////////////////////////////////// |
| 96 | INTERNAL METHODS |
| 97 | //////////////////////////////////////////////////////////////*/ |
| 98 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | address yieldSource = data.extractYieldSource(); |
| 100 | asset = IERC7540(yieldSource).asset(); |
| 101 | _setOutAmount(_getBalance(account, data), account); |
| 102 | usedShares = _getSharesBalance(account, data); |
| 103 | spToken = IERC7540(yieldSource).share(); |
| 104 | } |
| 105 | |
| 106 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 108 | 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 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 119 | return IERC20(asset).balanceOf(account); |
| 120 | } |
| 121 | |
| 122 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 123 | address yieldSource = data.extractYieldSource(); |
| 124 | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 125 | } |
| 126 | } |
| 127 |
85.0%
lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol
Lines covered: 17 / 20 (85.0%)
| 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 | 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 | address yieldSource = data.extractYieldSource(); |
| 41 | |
| 42 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 43 | |
| 44 | executions = new Execution[](1); |
| 45 | executions[0] = |
| 46 | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(ISuperVault.cancelRedeem, (account)) }); |
| 47 | } |
| 48 | |
| 49 | /*////////////////////////////////////////////////////////////// |
| 50 | EX`TERNAL METHODS |
| 51 | //////////////////////////////////////////////////////////////*/ |
| 52 | |
| 53 | /// @inheritdoc ISuperHookAsyncCancelations |
| 54 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 55 | return CancelationType.OUTFLOW; |
| 56 | } |
| 57 | |
| 58 | /// @inheritdoc ISuperHookInspector |
| 59 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 60 | return abi.encodePacked(data.extractYieldSource()); |
| 61 | } |
| 62 | |
| 63 | /*////////////////////////////////////////////////////////////// |
| 64 | INTERNAL METHODS |
| 65 | //////////////////////////////////////////////////////////////*/ |
| 66 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 67 | _setOutAmount(_getBalance(account, data), account); |
| 68 | spToken = data.extractYieldSource(); |
| 69 | } |
| 70 | |
| 71 | 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 | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 80 | } |
| 81 | } |
| 82 |
4.0%
lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol
Lines covered: 2 / 41 (4.0%)
| 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 | 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 | { |
| 52 | address yieldSource = data.extractYieldSource(); |
| 53 | uint256 amount = _decodeAmount(data); |
| 54 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | |
| 56 | if (usePrevHookAmount) { |
| 57 | amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
| 59 | |
| 60 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 61 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | |
| 63 | executions = new Execution[](1); |
| 64 | executions[0] = Execution({ |
| 65 | target: yieldSource, |
| 66 | value: 0, |
| 67 | callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | /*////////////////////////////////////////////////////////////// |
| 72 | EXTERNAL METHODS |
| 73 | //////////////////////////////////////////////////////////////*/ |
| 74 | |
| 75 | /// @inheritdoc ISuperHookInflowOutflow |
| 76 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 77 | return _decodeAmount(data); |
| 78 | } |
| 79 | |
| 80 | /// @inheritdoc ISuperHookContextAware |
| 81 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 82 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 83 | } |
| 84 | |
| 85 | /// @inheritdoc ISuperHookOutflow |
| 86 | 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 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 92 | return abi.encodePacked(data.extractYieldSource()); |
| 93 | } |
| 94 | |
| 95 | /*////////////////////////////////////////////////////////////// |
| 96 | INTERNAL METHODS |
| 97 | //////////////////////////////////////////////////////////////*/ |
| 98 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | address yieldSource = data.extractYieldSource(); |
| 100 | asset = IERC7540(yieldSource).asset(); |
| 101 | _setOutAmount(_getBalance(account, data), account); |
| 102 | usedShares = _getSharesBalance(account, data); |
| 103 | spToken = IERC7540(yieldSource).share(); |
| 104 | } |
| 105 | |
| 106 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 108 | 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 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 119 | return IERC20(asset).balanceOf(account); |
| 120 | } |
| 121 | |
| 122 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 123 | address yieldSource = data.extractYieldSource(); |
| 124 | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 125 | } |
| 126 | } |
| 127 |
0.0%
lib/v2-core/src/interfaces/ISuperHook.sol
Lines covered: 0 / 0 (0.0%)
| 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 | /** |
| 8 | * @title SuperHook System |
| 9 | * @author Superform Labs |
| 10 | * @notice The hook system provides a modular and composable way to execute operations on assets |
| 11 | * @dev The hook system architecture consists of several interfaces that work together: |
| 12 | * - ISuperHook: The base interface all hooks implement, with lifecycle methods |
| 13 | * - ISuperHookResult: Provides execution results and output information |
| 14 | * - Specialized interfaces (ISuperHookOutflow, ISuperHookLoans, etc.) for specific behaviors |
| 15 | * |
| 16 | * Hooks are executed in sequence, where each hook can access the results from previous hooks. |
| 17 | * The three main types of hooks are: |
| 18 | * - NONACCOUNTING: Utility hooks that don't update the accounting system |
| 19 | * - INFLOW: Hooks that process deposits or additions to positions |
| 20 | * - OUTFLOW: Hooks that process withdrawals or reductions to positions |
| 21 | */ |
| 22 | interface ISuperLockableHook { |
| 23 | /// @notice The vault bank address used to lock SuperPositions |
| 24 | /// @dev Only relevant for cross-chain operations where positions are locked |
| 25 | /// @return The vault bank address, or address(0) if not applicable |
| 26 | function vaultBank() external view returns (address); |
| 27 | |
| 28 | /// @notice The destination chain ID for cross-chain operations |
| 29 | /// @dev Used to identify the target chain for cross-chain position transfers |
| 30 | /// @return The destination chain ID, or 0 if not a cross-chain operation |
| 31 | function dstChainId() external view returns (uint256); |
| 32 | } |
| 33 | |
| 34 | interface ISuperHookSetter { |
| 35 | /// @notice Sets the output amount for the hook |
| 36 | /// @dev Used for updating `outAmount` when fees were deducted |
| 37 | /// @param outAmount The amount of tokens processed by the hook |
| 38 | /// @param caller The caller address for context identification |
| 39 | function setOutAmount(uint256 outAmount, address caller) external; |
| 40 | } |
| 41 | /// @title ISuperHookInspector |
| 42 | /// @author Superform Labs |
| 43 | /// @notice Interface for the SuperHookInspector contract that manages hook inspection |
| 44 | |
| 45 | interface ISuperHookInspector { |
| 46 | /// @notice Inspect the hook |
| 47 | /// @param data The hook data to inspect |
| 48 | /// @return argsEncoded The arguments of the hook encoded |
| 49 | function inspect(bytes calldata data) external view returns (bytes memory argsEncoded); |
| 50 | } |
| 51 | |
| 52 | /// @title ISuperHookResult |
| 53 | /// @author Superform Labs |
| 54 | /// @notice Interface that exposes the result of a hook execution |
| 55 | /// @dev All hooks must implement this interface to provide standardized access to execution results. |
| 56 | /// These results are used by subsequent hooks in the execution chain and by the executor. |
| 57 | interface ISuperHookResult { |
| 58 | /*////////////////////////////////////////////////////////////// |
| 59 | VIEW METHODS |
| 60 | //////////////////////////////////////////////////////////////*/ |
| 61 | |
| 62 | /// @notice The type of hook |
| 63 | /// @dev Used to determine how accounting should process this hook's results |
| 64 | /// @return The hook type (NONACCOUNTING, INFLOW, or OUTFLOW) |
| 65 | function hookType() external view returns (ISuperHook.HookType); |
| 66 | |
| 67 | /// @notice The SuperPosition (SP) token associated with this hook |
| 68 | /// @dev For vault hooks, this would be the tokenized position representing shares |
| 69 | /// @return The address of the SP token, or address(0) if not applicable |
| 70 | function spToken() external view returns (address); |
| 71 | |
| 72 | /// @notice The underlying asset token being processed |
| 73 | /// @dev For most hooks, this is the actual token being deposited or withdrawn |
| 74 | /// @return The address of the asset token, or address(0) for native assets |
| 75 | function asset() external view returns (address); |
| 76 | |
| 77 | /// @notice The amount of tokens processed by the hook in a given caller context, subject to fees after update |
| 78 | /// @dev This is the primary output value used by subsequent hooks |
| 79 | /// @param caller The caller address for context identification |
| 80 | /// @return The amount of tokens (assets or shares) processed |
| 81 | function getOutAmount(address caller) external view returns (uint256); |
| 82 | } |
| 83 | |
| 84 | /// @title ISuperHookContextAware |
| 85 | /// @author Superform Labs |
| 86 | /// @notice Interface for hooks that can use previous hook results in their execution |
| 87 | /// @dev Enables contextual awareness and data flow between hooks in a chain |
| 88 | interface ISuperHookContextAware { |
| 89 | /// @notice Determines if this hook should use the amount from the previous hook |
| 90 | /// @dev Used to create hook chains where output from one hook becomes input to the next |
| 91 | /// @param data The hook-specific data containing configuration |
| 92 | /// @return True if the hook should use the previous hook's output amount |
| 93 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool); |
| 94 | } |
| 95 | |
| 96 | /// @title ISuperHookInflowOutflow |
| 97 | /// @author Superform Labs |
| 98 | /// @notice Interface for hooks that handle both inflows and outflows |
| 99 | /// @dev Provides standardized amount extraction for both deposit and withdrawal operations |
| 100 | interface ISuperHookInflowOutflow { |
| 101 | /// @notice Extracts the amount from the hook's calldata |
| 102 | /// @dev Used to determine the quantity of assets or shares being processed |
| 103 | /// @param data The hook-specific calldata containing the amount |
| 104 | /// @return The amount of tokens to process |
| 105 | function decodeAmount(bytes memory data) external pure returns (uint256); |
| 106 | } |
| 107 | |
| 108 | /// @title ISuperHookOutflow |
| 109 | /// @author Superform Labs |
| 110 | /// @notice Interface for hooks that specifically handle outflows (withdrawals) |
| 111 | /// @dev Provides additional functionality needed only for outflow operations |
| 112 | interface ISuperHookOutflow { |
| 113 | /// @notice Replace the amount in the calldata |
| 114 | /// @param data The data to replace the amount in |
| 115 | /// @param amount The amount to replace |
| 116 | /// @return data The data with the replaced amount |
| 117 | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory); |
| 118 | } |
| 119 | |
| 120 | /// @title ISuperHookResultOutflow |
| 121 | /// @author Superform Labs |
| 122 | /// @notice Extended result interface for outflow hook operations |
| 123 | /// @dev Extends the base result interface with outflow-specific information |
| 124 | interface ISuperHookResultOutflow is ISuperHookResult { |
| 125 | /// @notice The amount of shares consumed during outflow processing |
| 126 | /// @dev Used for cost basis calculation in the accounting system |
| 127 | /// @return The amount of shares consumed from the user's position |
| 128 | function usedShares() external view returns (uint256); |
| 129 | } |
| 130 | |
| 131 | /// @title ISuperHookLoans |
| 132 | /// @author Superform Labs |
| 133 | /// @notice Interface for hooks that interact with lending protocols |
| 134 | /// @dev Extends context awareness to enable loan operations within hook chains |
| 135 | interface ISuperHookLoans is ISuperHookContextAware { |
| 136 | /// @notice Gets the address of the token being borrowed |
| 137 | /// @dev Used to identify which asset is being borrowed from the lending protocol |
| 138 | /// @param data The hook-specific data containing loan information |
| 139 | /// @return The address of the borrowed token |
| 140 | function getLoanTokenAddress(bytes memory data) external pure returns (address); |
| 141 | |
| 142 | /// @notice Gets the address of the token used as collateral |
| 143 | /// @dev Used to identify which asset is being used to secure the loan |
| 144 | /// @param data The hook-specific data containing collateral information |
| 145 | /// @return The address of the collateral token |
| 146 | function getCollateralTokenAddress(bytes memory data) external view returns (address); |
| 147 | |
| 148 | /// @notice Gets the current loan token balance for an account |
| 149 | /// @dev Used to track outstanding loan amounts |
| 150 | /// @param account The account to check the loan balance for |
| 151 | /// @param data The hook-specific data containing loan parameters |
| 152 | /// @return The amount of tokens currently borrowed |
| 153 | function getLoanTokenBalance(address account, bytes memory data) external view returns (uint256); |
| 154 | |
| 155 | /// @notice Gets the current collateral token balance for an account |
| 156 | /// @dev Used to track collateral positions |
| 157 | /// @param account The account to check the collateral balance for |
| 158 | /// @param data The hook-specific data containing collateral parameters |
| 159 | /// @return The amount of tokens currently used as collateral |
| 160 | function getCollateralTokenBalance(address account, bytes memory data) external view returns (uint256); |
| 161 | } |
| 162 | |
| 163 | /// @title ISuperHookAsyncCancelations |
| 164 | /// @author Superform Labs |
| 165 | /// @notice Interface for hooks that can cancel asynchronous operations |
| 166 | /// @dev Used to handle cancellation of pending operations that haven't completed |
| 167 | interface ISuperHookAsyncCancelations { |
| 168 | /// @notice Types of cancellations that can be performed |
| 169 | /// @dev Distinguishes between different operation types that can be canceled |
| 170 | enum CancelationType { |
| 171 | NONE, // Not a cancelation hook |
| 172 | INFLOW, // Cancels a pending deposit operation |
| 173 | OUTFLOW // Cancels a pending withdrawal operation |
| 174 | |
| 175 | } |
| 176 | |
| 177 | /// @notice Identifies the type of async operation this hook can cancel |
| 178 | /// @dev Used to verify the hook is appropriate for the operation being canceled |
| 179 | /// @return asyncType The type of cancellation this hook performs |
| 180 | function isAsyncCancelHook() external pure returns (CancelationType asyncType); |
| 181 | } |
| 182 | |
| 183 | /// @title ISuperHook |
| 184 | /// @author Superform Labs |
| 185 | /// @notice The core hook interface that all hooks must implement |
| 186 | /// @dev Defines the lifecycle methods and execution flow for the hook system |
| 187 | /// Hooks are executed in sequence with results passed between them |
| 188 | interface ISuperHook { |
| 189 | /*////////////////////////////////////////////////////////////// |
| 190 | |
| 191 | ENUMS |
| 192 | //////////////////////////////////////////////////////////////*/ |
| 193 | /// @notice Defines the possible types of hooks in the system |
| 194 | /// @dev Used to determine how the hook affects accounting and what operations it performs |
| 195 | enum HookType { |
| 196 | NONACCOUNTING, // Hook doesn't affect accounting (e.g., a swap or bridge) |
| 197 | INFLOW, // Hook processes deposits or positions being added |
| 198 | OUTFLOW // Hook processes withdrawals or positions being removed |
| 199 | |
| 200 | } |
| 201 | |
| 202 | /*////////////////////////////////////////////////////////////// |
| 203 | VIEW METHODS |
| 204 | //////////////////////////////////////////////////////////////*/ |
| 205 | /// @notice Builds the execution array for the hook operation |
| 206 | /// @dev This is the core method where hooks define their on-chain interactions |
| 207 | /// The returned executions are a sequence of contract calls to perform |
| 208 | /// No state changes should occur in this method |
| 209 | /// @param prevHook The address of the previous hook in the chain, or address(0) if first |
| 210 | /// @param account The account to perform executions for (usually an ERC7579 account) |
| 211 | /// @param data The hook-specific parameters and configuration data |
| 212 | /// @return executions Array of Execution structs defining calls to make |
| 213 | function build( |
| 214 | address prevHook, |
| 215 | address account, |
| 216 | bytes calldata data |
| 217 | ) |
| 218 | external |
| 219 | view |
| 220 | returns (Execution[] memory executions); |
| 221 | |
| 222 | /*////////////////////////////////////////////////////////////// |
| 223 | PUBLIC METHODS |
| 224 | //////////////////////////////////////////////////////////////*/ |
| 225 | /// @notice Prepares the hook for execution |
| 226 | /// @dev Called before the main execution, used to validate inputs and set execution context |
| 227 | /// This method may perform state changes to set up the hook's execution state |
| 228 | /// @param prevHook The address of the previous hook in the chain, or address(0) if first |
| 229 | /// @param account The account to perform operations for |
| 230 | /// @param data The hook-specific parameters and configuration data |
| 231 | function preExecute(address prevHook, address account, bytes memory data) external; |
| 232 | |
| 233 | /// @notice Finalizes the hook after execution |
| 234 | /// @dev Called after the main execution, used to update hook state and calculate results |
| 235 | /// Sets output values (outAmount, usedShares, etc.) for subsequent hooks |
| 236 | /// @param prevHook The address of the previous hook in the chain, or address(0) if first |
| 237 | /// @param account The account operations were performed for |
| 238 | /// @param data The hook-specific parameters and configuration data |
| 239 | function postExecute(address prevHook, address account, bytes memory data) external; |
| 240 | |
| 241 | /// @notice Returns the specific subtype identification for this hook |
| 242 | /// @dev Used to categorize hooks beyond the basic HookType |
| 243 | /// For example, a hook might be of type INFLOW but subtype VAULT_DEPOSIT |
| 244 | /// @return A bytes32 identifier for the specific hook functionality |
| 245 | function subtype() external view returns (bytes32); |
| 246 | |
| 247 | /// @notice Resets hook mutexes |
| 248 | /// @param caller The caller address for context identification |
| 249 | function resetExecutionState(address caller) external; |
| 250 | |
| 251 | /// @notice Sets the caller address that initiated the execution |
| 252 | /// @dev Used for security validation between preExecute and postExecute calls |
| 253 | /// @param caller The caller address for context identification |
| 254 | function setExecutionContext(address caller) external; |
| 255 | |
| 256 | /// @notice Returns the execution nonce for the current execution context |
| 257 | /// @dev Used to ensure unique execution contexts and prevent replay attacks |
| 258 | /// @return The execution nonce |
| 259 | function executionNonce() external view returns (uint256); |
| 260 | |
| 261 | /// @notice Returns the last caller registered by `setExecutionContext` |
| 262 | /// @return The last caller address |
| 263 | function lastCaller() external view returns (address); |
| 264 | } |
| 265 |
0.0%
lib/v2-core/src/interfaces/accounting/IYieldSourceOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | // Superform |
| 5 | import { IOracle } from "../../vendor/awesome-oracles/IOracle.sol"; |
| 6 | |
| 7 | /// @title IYieldSourceOracle |
| 8 | /// @author Superform Labs |
| 9 | /// @notice Interface for oracles that provide price and TVL data for yield-bearing assets |
| 10 | interface IYieldSourceOracle { |
| 11 | /*////////////////////////////////////////////////////////////// |
| 12 | ERRORS |
| 13 | //////////////////////////////////////////////////////////////*/ |
| 14 | |
| 15 | /// @notice Error when array lengths do not match in batch operations |
| 16 | /// @dev Thrown when the lengths of input arrays in multi-asset operations don't match |
| 17 | error ARRAY_LENGTH_MISMATCH(); |
| 18 | |
| 19 | /// @notice Error when base asset is not valid for the yield source |
| 20 | /// @dev Thrown when attempting to use an asset that isn't supported by the yield source |
| 21 | error INVALID_BASE_ASSET(); |
| 22 | |
| 23 | /*////////////////////////////////////////////////////////////// |
| 24 | STRUCTS |
| 25 | //////////////////////////////////////////////////////////////*/ |
| 26 | |
| 27 | /// @notice Struct to hold local variables for getTVLMultipleUSD |
| 28 | /// @dev Used to manage complex computation state without stack-too-deep errors |
| 29 | /// These variables support the calculation of USD-denominated TVL values |
| 30 | /// across multiple yield sources and owners |
| 31 | struct TVLMultipleUSDVars { |
| 32 | /// @notice Number of yield sources being processed |
| 33 | uint256 length; |
| 34 | /// @notice Number of share owners being processed |
| 35 | uint256 ownersLength; |
| 36 | /// @notice Base amount in the underlying asset's native units |
| 37 | uint256 baseAmount; |
| 38 | /// @notice Accumulated TVL in USD for a specific user |
| 39 | uint256 userTvlUSD; |
| 40 | /// @notice Accumulated total TVL in USD across all sources |
| 41 | uint256 totalTvlUSD; |
| 42 | /// @notice Current yield source being processed |
| 43 | address yieldSource; |
| 44 | /// @notice Array of addresses that own shares in the yield source |
| 45 | address[] owners; |
| 46 | /// @notice Oracle registry used for price conversions |
| 47 | IOracle registry; |
| 48 | } |
| 49 | |
| 50 | /*////////////////////////////////////////////////////////////// |
| 51 | VIEW METHODS |
| 52 | //////////////////////////////////////////////////////////////*/ |
| 53 | |
| 54 | /// @notice Returns the number of decimals of the yield source shares |
| 55 | /// @dev Critical for accurately interpreting share amounts and calculating prices |
| 56 | /// Different yield sources may have different decimal precision |
| 57 | /// @param yieldSourceAddress The address of the yield-bearing token contract |
| 58 | /// @return decimals The number of decimals used by the yield source's share token |
| 59 | function decimals(address yieldSourceAddress) external view returns (uint8); |
| 60 | |
| 61 | /// @notice Calculates the number of shares that would be received for a given amount of assets |
| 62 | /// @dev Used for deposit simulations and to calculate current exchange rates |
| 63 | /// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI) |
| 64 | /// @param assetIn The underlying asset being deposited (e.g., USDC, DAI) |
| 65 | /// @param assetsIn The amount of underlying assets to deposit, in the asset's native units |
| 66 | /// @return shares The number of yield-bearing shares that would be received |
| 67 | function getShareOutput( |
| 68 | address yieldSourceAddress, |
| 69 | address assetIn, |
| 70 | uint256 assetsIn |
| 71 | ) |
| 72 | external |
| 73 | view |
| 74 | returns (uint256); |
| 75 | |
| 76 | /// @notice Calculates the number of underlying assets that would be received for a given amount of shares |
| 77 | /// @dev Used for withdrawal simulations and to calculate current yield |
| 78 | /// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI) |
| 79 | /// @param assetIn The underlying asset to receive (e.g., USDC, DAI) |
| 80 | /// @param sharesIn The amount of yield-bearing shares to redeem |
| 81 | /// @return assets The number of underlying assets that would be received |
| 82 | function getAssetOutput( |
| 83 | address yieldSourceAddress, |
| 84 | address assetIn, |
| 85 | uint256 sharesIn |
| 86 | ) |
| 87 | external |
| 88 | view |
| 89 | returns (uint256); |
| 90 | |
| 91 | /// @notice Retrieves the current price per share in terms of the underlying asset |
| 92 | /// @dev Core function for calculating yields and determining returns |
| 93 | /// @param yieldSourceAddress The yield-bearing token address to get the price for |
| 94 | /// @return pricePerShare The current price per share in underlying asset terms, scaled by decimals |
| 95 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256); |
| 96 | |
| 97 | /// @notice Calculates the total value locked in a yield source by a specific owner |
| 98 | /// @dev Used to track individual position sizes within the system |
| 99 | /// @param yieldSourceAddress The yield-bearing token address to check |
| 100 | /// @param ownerOfShares The address owning the yield-bearing tokens |
| 101 | /// @return tvl The total value locked by the owner, in underlying asset terms |
| 102 | function getTVLByOwnerOfShares(address yieldSourceAddress, address ownerOfShares) external view returns (uint256); |
| 103 | |
| 104 | /// @notice Gets the share balance of a specific owner in a yield source |
| 105 | /// @dev Returns raw share balance without converting to underlying assets |
| 106 | /// Used to track participation in the system and for accounting |
| 107 | /// @param yieldSourceAddress The yield-bearing token address |
| 108 | /// @param ownerOfShares The address to check the balance for |
| 109 | /// @return balance The number of yield-bearing tokens owned by the address |
| 110 | function getBalanceOfOwner(address yieldSourceAddress, address ownerOfShares) external view returns (uint256); |
| 111 | |
| 112 | /// @notice Calculates the total value locked across all users in a yield source |
| 113 | /// @dev Critical for monitoring the size of each yield source in the system |
| 114 | /// @param yieldSourceAddress The yield-bearing token address to check |
| 115 | /// @return tvl The total value locked in the yield source, in underlying asset terms |
| 116 | function getTVL(address yieldSourceAddress) external view returns (uint256); |
| 117 | |
| 118 | /// @notice Batch version of getPricePerShare for multiple yield sources |
| 119 | /// @dev Efficiently retrieves current prices for multiple yield sources |
| 120 | /// @param yieldSourceAddresses Array of yield-bearing token addresses |
| 121 | /// @return pricesPerShare Array of current prices for each yield source |
| 122 | function getPricePerShareMultiple(address[] memory yieldSourceAddresses) |
| 123 | external |
| 124 | view |
| 125 | returns (uint256[] memory pricesPerShare); |
| 126 | |
| 127 | /// @notice Batch version of getTVLByOwnerOfShares for multiple yield sources and owners |
| 128 | /// @dev Efficiently calculates TVL for multiple owners across multiple yield sources |
| 129 | /// @param yieldSourceAddresses Array of yield-bearing token addresses |
| 130 | /// @param ownersOfShares 2D array where each sub-array contains owner addresses for a yield source |
| 131 | /// @return userTvls 2D array of TVL values for each owner in each yield source |
| 132 | function getTVLByOwnerOfSharesMultiple( |
| 133 | address[] memory yieldSourceAddresses, |
| 134 | address[][] memory ownersOfShares |
| 135 | ) |
| 136 | external |
| 137 | view |
| 138 | returns (uint256[][] memory userTvls); |
| 139 | |
| 140 | /// @notice Batch version of getTVL for multiple yield sources |
| 141 | /// @dev Efficiently calculates total TVL across multiple yield sources |
| 142 | /// @param yieldSourceAddresses Array of yield-bearing token addresses |
| 143 | /// @return tvls Array containing the total TVL for each yield source |
| 144 | function getTVLMultiple(address[] memory yieldSourceAddresses) external view returns (uint256[] memory tvls); |
| 145 | |
| 146 | /// @notice Calculates the asset output with fees added for an outflow operation |
| 147 | /// @dev Gets the asset output from the oracle and adds any applicable fees |
| 148 | /// Uses try/catch to handle cases where oracle configuration doesn't exist |
| 149 | /// @param yieldSourceOracleId Identifier for the yield source oracle configuration |
| 150 | /// @param yieldSourceAddress Address of the yield-bearing asset |
| 151 | /// @param assetOut Address of the output asset |
| 152 | /// @param user Address of the user performing the outflow |
| 153 | /// @param usedShares Amount of shares being withdrawn |
| 154 | /// @return Total asset amount including fees |
| 155 | function getAssetOutputWithFees( |
| 156 | bytes32 yieldSourceOracleId, |
| 157 | address yieldSourceAddress, |
| 158 | address assetOut, |
| 159 | address user, |
| 160 | uint256 usedShares |
| 161 | ) |
| 162 | external |
| 163 | view |
| 164 | returns (uint256); |
| 165 | } |
| 166 |
66.0%
lib/v2-core/src/libraries/HookDataDecoder.sol
Lines covered: 2 / 3 (66.0%)
| 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 | library HookDataDecoder { |
| 10 | function extractYieldSourceOracleId(bytes memory data) internal pure returns (bytes32) { |
| 11 | return bytes32(BytesLib.slice(data, 0, 32)); |
| 12 | } |
| 13 | |
| 14 | function extractYieldSource(bytes memory data) internal pure returns (address) { |
| 15 | return BytesLib.toAddress(data, 32); |
| 16 | } |
| 17 | } |
| 18 |
34.0%
lib/v2-core/src/libraries/HookSubTypes.sol
Lines covered: 8 / 23 (34.0%)
| 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 | library HookSubTypes { |
| 8 | bytes32 public constant BRIDGE = keccak256(bytes("Bridge")); |
| 9 | bytes32 public constant CANCEL_DEPOSIT = keccak256(bytes("CancelDeposit")); |
| 10 | bytes32 public constant CANCEL_DEPOSIT_REQUEST = keccak256(bytes("CancelDepositRequest")); |
| 11 | bytes32 public constant CANCEL_REDEEM = keccak256(bytes("CancelRedeem")); |
| 12 | bytes32 public constant CANCEL_REDEEM_REQUEST = keccak256(bytes("CancelRedeemRequest")); |
| 13 | bytes32 public constant CLAIM = keccak256(bytes("Claim")); |
| 14 | bytes32 public constant CLAIM_CANCEL_DEPOSIT_REQUEST = keccak256(bytes("ClaimCancelDepositRequest")); |
| 15 | bytes32 public constant CLAIM_CANCEL_REDEEM_REQUEST = keccak256(bytes("ClaimCancelRedeemRequest")); |
| 16 | bytes32 public constant COOLDOWN = keccak256(bytes("Cooldown")); |
| 17 | bytes32 public constant ETHENA = keccak256(bytes("Ethena")); |
| 18 | bytes32 public constant ERC4626 = keccak256(bytes("ERC4626")); |
| 19 | bytes32 public constant ERC5115 = keccak256(bytes("ERC5115")); |
| 20 | bytes32 public constant ERC7540 = keccak256(bytes("ERC7540")); |
| 21 | bytes32 public constant LOAN = keccak256(bytes("Loan")); |
| 22 | bytes32 public constant LOAN_REPAY = keccak256(bytes("LoanRepay")); |
| 23 | bytes32 public constant MISC = keccak256(bytes("Misc")); |
| 24 | bytes32 public constant STAKE = keccak256(bytes("Stake")); |
| 25 | bytes32 public constant SWAP = keccak256(bytes("Swap")); |
| 26 | bytes32 public constant TOKEN = keccak256(bytes("Token")); |
| 27 | bytes32 public constant UNSTAKE = keccak256(bytes("Unstake")); |
| 28 | bytes32 public constant PTYT = keccak256(bytes("PTYT")); |
| 29 | 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 | } |
| 35 |
85.0%
lib/v2-core/src/vendor/BytesLib.sol
Lines covered: 6 / 7 (85.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 | 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 | 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 | require(_length + 31 >= _length, "slice_overflow"); |
| 217 | } |
| 218 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); |
| 219 | |
| 220 | bytes memory tempBytes; |
| 221 | |
| 222 | assembly { |
| 223 | switch iszero(_length) |
| 224 | case 0 { |
| 225 | // Get a location of some free memory and store it in tempBytes as |
| 226 | // Solidity does for memory variables. |
| 227 | 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 | 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 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) |
| 244 | let end := add(mc, _length) |
| 245 | |
| 246 | for { |
| 247 | // The multiplication in the next line has the same exact purpose |
| 248 | // as the one above. |
| 249 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) |
| 250 | } lt(mc, end) { |
| 251 | mc := add(mc, 0x20) |
| 252 | cc := add(cc, 0x20) |
| 253 | } { mstore(mc, mload(cc)) } |
| 254 | |
| 255 | mstore(tempBytes, _length) |
| 256 | |
| 257 | //update free-memory pointer |
| 258 | //allocating the array padded to 32 bytes like the compiler does now |
| 259 | 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 | 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 | mstore(tempBytes, 0) |
| 267 | |
| 268 | mstore(0x40, add(tempBytes, 0x20)) |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | return tempBytes; |
| 273 | } |
| 274 | |
| 275 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { |
| 276 | require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); |
| 277 | address tempAddress; |
| 278 | |
| 279 | assembly { |
| 280 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) |
| 281 | } |
| 282 | |
| 283 | return tempAddress; |
| 284 | } |
| 285 | |
| 286 | function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { |
| 287 | require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); |
| 288 | uint8 tempUint; |
| 289 | |
| 290 | assembly { |
| 291 | 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 | function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { |
| 309 | require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); |
| 310 | uint32 tempUint; |
| 311 | |
| 312 | assembly { |
| 313 | tempUint := mload(add(add(_bytes, 0x4), _start)) |
| 314 | } |
| 315 | |
| 316 | return tempUint; |
| 317 | } |
| 318 | |
| 319 | function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { |
| 320 | require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); |
| 321 | uint64 tempUint; |
| 322 | |
| 323 | assembly { |
| 324 | 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 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { |
| 353 | require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); |
| 354 | uint256 tempUint; |
| 355 | |
| 356 | assembly { |
| 357 | tempUint := mload(add(add(_bytes, 0x20), _start)) |
| 358 | } |
| 359 | |
| 360 | return tempUint; |
| 361 | } |
| 362 | |
| 363 | function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { |
| 364 | 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 | } |
| 481 |
0.0%
lib/v2-core/src/vendor/awesome-oracles/IOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity ^0.8.20; |
| 3 | |
| 4 | /// @title Common interface for price oracles. |
| 5 | /// @dev Implements the spec at https://eips.ethereum.org/EIPS/eip-7726 |
| 6 | interface IOracle { |
| 7 | /// @notice The oracle does not support the given base/quote pair. |
| 8 | /// @param base The asset that the user needs to know the value or price for. |
| 9 | /// @param quote The asset in which the user needs to value or price the base. |
| 10 | error OracleUnsupportedPair(address base, address quote); |
| 11 | |
| 12 | /// @notice The oracle is not capable to provide data within a degree of confidence. |
| 13 | /// @param base The asset that the user needs to know the value or price for. |
| 14 | /// @param quote The asset in which the user needs to value or price the base. |
| 15 | error OracleUntrustedData(address base, address quote); |
| 16 | |
| 17 | /// @notice Returns the value of `baseAmount` of `base` in `quote` terms. |
| 18 | /// @dev MUST round down towards 0. |
| 19 | /// MUST revert with `OracleUnsupportedPair` if not capable to provide data for the specified `base` and `quote` |
| 20 | /// pair. |
| 21 | /// MUST revert with `OracleUntrustedData` if not capable to provide data within a degree of confidence publicly |
| 22 | /// specified. |
| 23 | /// @param baseAmount The amount of `base` to convert. |
| 24 | /// @param base The asset that the user needs to know the value for. |
| 25 | /// @param quote The asset in which the user needs to value the base. |
| 26 | /// @return quoteAmount The value of `baseAmount` of `base` in `quote` terms |
| 27 | function getQuote(uint256 baseAmount, address base, address quote) external view returns (uint256 quoteAmount); |
| 28 | } |
| 29 |
0.0%
lib/v2-core/src/vendor/pendle/IStandardizedYield.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | /* |
| 3 | * MIT License |
| 4 | * =========== |
| 5 | * |
| 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | * of this software and associated documentation files (the "Software"), to deal |
| 8 | * in the Software without restriction, including without limitation the rights |
| 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | * copies of the Software, and to permit persons to whom the Software is |
| 11 | * furnished to do so, subject to the following conditions: |
| 12 | * |
| 13 | * The above copyright notice and this permission notice shall be included in all |
| 14 | * copies or substantial portions of the Software. |
| 15 | * |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | */ |
| 23 | |
| 24 | pragma solidity ^0.8.0; |
| 25 | |
| 26 | import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
| 27 | |
| 28 | interface IStandardizedYield is IERC20Metadata { |
| 29 | /// @dev Emitted when any base tokens is deposited to mint shares |
| 30 | event Deposit( |
| 31 | address indexed caller, |
| 32 | address indexed receiver, |
| 33 | address indexed tokenIn, |
| 34 | uint256 amountDeposited, |
| 35 | uint256 amountSyOut |
| 36 | ); |
| 37 | |
| 38 | /// @dev Emitted when any shares are redeemed for base tokens |
| 39 | event Redeem( |
| 40 | address indexed caller, |
| 41 | address indexed receiver, |
| 42 | address indexed tokenOut, |
| 43 | uint256 amountSyToRedeem, |
| 44 | uint256 amountTokenOut |
| 45 | ); |
| 46 | |
| 47 | /// @dev check `assetInfo()` for more information |
| 48 | enum AssetType { |
| 49 | TOKEN, |
| 50 | LIQUIDITY |
| 51 | } |
| 52 | |
| 53 | /// @dev Emitted when (`user`) claims their rewards |
| 54 | event ClaimRewards(address indexed user, address[] rewardTokens, uint256[] rewardAmounts); |
| 55 | |
| 56 | /** |
| 57 | * @notice mints an amount of shares by depositing a base token. |
| 58 | * @param receiver shares recipient address |
| 59 | * @param tokenIn address of the base tokens to mint shares |
| 60 | * @param amountTokenToDeposit amount of base tokens to be transferred from (`msg.sender`) |
| 61 | * @param minSharesOut reverts if amount of shares minted is lower than this |
| 62 | * @return amountSharesOut amount of shares minted |
| 63 | * @dev Emits a {Deposit} event |
| 64 | * |
| 65 | * Requirements: |
| 66 | * - (`tokenIn`) must be a valid base token. |
| 67 | */ |
| 68 | function deposit( |
| 69 | address receiver, |
| 70 | address tokenIn, |
| 71 | uint256 amountTokenToDeposit, |
| 72 | uint256 minSharesOut |
| 73 | ) |
| 74 | external |
| 75 | payable |
| 76 | returns (uint256 amountSharesOut); |
| 77 | |
| 78 | /** |
| 79 | * @notice redeems an amount of base tokens by burning some shares |
| 80 | * @param receiver recipient address |
| 81 | * @param amountSharesToRedeem amount of shares to be burned |
| 82 | * @param tokenOut address of the base token to be redeemed |
| 83 | * @param minTokenOut reverts if amount of base token redeemed is lower than this |
| 84 | * @param burnFromInternalBalance if true, burns from balance of `address(this)`, otherwise burns from `msg.sender` |
| 85 | * @return amountTokenOut amount of base tokens redeemed |
| 86 | * @dev Emits a {Redeem} event |
| 87 | * |
| 88 | * Requirements: |
| 89 | * - (`tokenOut`) must be a valid base token. |
| 90 | */ |
| 91 | function redeem( |
| 92 | address receiver, |
| 93 | uint256 amountSharesToRedeem, |
| 94 | address tokenOut, |
| 95 | uint256 minTokenOut, |
| 96 | bool burnFromInternalBalance |
| 97 | ) |
| 98 | external |
| 99 | returns (uint256 amountTokenOut); |
| 100 | |
| 101 | /** |
| 102 | * @notice exchangeRate * syBalance / 1e18 must return the asset balance of the account |
| 103 | * @notice vice-versa, if a user uses some amount of tokens equivalent to X asset, the amount of sy |
| 104 | * he can mint must be X * exchangeRate / 1e18 |
| 105 | * @dev SYUtils's assetToSy & syToAsset should be used instead of raw multiplication |
| 106 | * & division |
| 107 | */ |
| 108 | function exchangeRate() external view returns (uint256 res); |
| 109 | |
| 110 | /** |
| 111 | * @notice claims reward for (`user`) |
| 112 | * @param user the user receiving their rewards |
| 113 | * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens` |
| 114 | * @dev |
| 115 | * Emits a `ClaimRewards` event |
| 116 | * See {getRewardTokens} for list of reward tokens |
| 117 | */ |
| 118 | function claimRewards(address user) external returns (uint256[] memory rewardAmounts); |
| 119 | |
| 120 | /** |
| 121 | * @notice get the amount of unclaimed rewards for (`user`) |
| 122 | * @param user the user to check for |
| 123 | * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens` |
| 124 | */ |
| 125 | function accruedRewards(address user) external view returns (uint256[] memory rewardAmounts); |
| 126 | |
| 127 | function rewardIndexesCurrent() external returns (uint256[] memory indexes); |
| 128 | |
| 129 | function rewardIndexesStored() external view returns (uint256[] memory indexes); |
| 130 | |
| 131 | /** |
| 132 | * @notice returns the list of reward token addresses |
| 133 | */ |
| 134 | function getRewardTokens() external view returns (address[] memory); |
| 135 | |
| 136 | /** |
| 137 | * @notice returns the address of the underlying yield token |
| 138 | */ |
| 139 | function yieldToken() external view returns (address); |
| 140 | |
| 141 | /** |
| 142 | * @notice returns all tokens that can mint this SY |
| 143 | */ |
| 144 | function getTokensIn() external view returns (address[] memory res); |
| 145 | |
| 146 | /** |
| 147 | * @notice returns all tokens that can be redeemed by this SY |
| 148 | */ |
| 149 | function getTokensOut() external view returns (address[] memory res); |
| 150 | |
| 151 | function isValidTokenIn(address token) external view returns (bool); |
| 152 | |
| 153 | function isValidTokenOut(address token) external view returns (bool); |
| 154 | |
| 155 | function previewDeposit( |
| 156 | address tokenIn, |
| 157 | uint256 amountTokenToDeposit |
| 158 | ) |
| 159 | external |
| 160 | view |
| 161 | returns (uint256 amountSharesOut); |
| 162 | |
| 163 | function previewRedeem( |
| 164 | address tokenOut, |
| 165 | uint256 amountSharesToRedeem |
| 166 | ) |
| 167 | external |
| 168 | view |
| 169 | returns (uint256 amountTokenOut); |
| 170 | |
| 171 | /** |
| 172 | * @notice This function contains information to interpret what the asset is |
| 173 | * @return assetType the type of the asset (0 for ERC20 tokens, 1 for AMM liquidity tokens, |
| 174 | * 2 for bridged yield bearing tokens like wstETH, rETH on Arbi whose the underlying asset doesn't exist on the |
| 175 | * chain) |
| 176 | * @return assetAddress the address of the asset |
| 177 | * @return assetDecimals the decimals of the asset |
| 178 | */ |
| 179 | function assetInfo() external view returns (AssetType assetType, address assetAddress, uint8 assetDecimals); |
| 180 | } |
| 181 |
0.0%
lib/v2-core/src/vendor/standards/ERC7540/IERC7540Vault.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
| 2 | pragma solidity >=0.8.0; |
| 3 | |
| 4 | import { IERC7741 } from "../ERC7741/IERC7741.sol"; |
| 5 | |
| 6 | interface IERC7540Operator { |
| 7 | /** |
| 8 | * @dev The event emitted when an operator is set. |
| 9 | * |
| 10 | * @param controller The address of the controller. |
| 11 | * @param operator The address of the operator. |
| 12 | * @param approved The approval status. |
| 13 | */ |
| 14 | event OperatorSet(address indexed controller, address indexed operator, bool approved); |
| 15 | |
| 16 | /** |
| 17 | * @dev Sets or removes an operator for the caller. |
| 18 | * |
| 19 | * @param operator The address of the operator. |
| 20 | * @param approved The approval status. |
| 21 | * @return Whether the call was executed successfully or not |
| 22 | */ |
| 23 | function setOperator(address operator, bool approved) external returns (bool); |
| 24 | |
| 25 | /** |
| 26 | * @dev Returns `true` if the `operator` is approved as an operator for an `controller`. |
| 27 | * |
| 28 | * @param controller The address of the controller. |
| 29 | * @param operator The address of the operator. |
| 30 | * @return status The approval status |
| 31 | */ |
| 32 | function isOperator(address controller, address operator) external view returns (bool status); |
| 33 | } |
| 34 | |
| 35 | interface IERC7540Deposit is IERC7540Operator { |
| 36 | event DepositRequest( |
| 37 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 38 | ); |
| 39 | /** |
| 40 | * @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit. |
| 41 | * |
| 42 | * - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow. |
| 43 | * - MUST revert if all of assets cannot be requested for deposit. |
| 44 | * - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 45 | * approval of ERC-20 tokens from owner to sender is NOT enough. |
| 46 | * |
| 47 | * @param assets the amount of deposit assets to transfer from owner |
| 48 | * @param controller the controller of the request who will be able to operate the request |
| 49 | * @param owner the source of the deposit assets |
| 50 | * |
| 51 | * NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token. |
| 52 | */ |
| 53 | |
| 54 | function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
| 55 | |
| 56 | /** |
| 57 | * @dev Returns the amount of requested assets in Pending state. |
| 58 | * |
| 59 | * - MUST NOT include any assets in Claimable state for deposit or mint. |
| 60 | * - MUST NOT show any variations depending on the caller. |
| 61 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 62 | */ |
| 63 | function pendingDepositRequest( |
| 64 | uint256 requestId, |
| 65 | address controller |
| 66 | ) |
| 67 | external |
| 68 | view |
| 69 | returns (uint256 pendingAssets); |
| 70 | |
| 71 | /** |
| 72 | * @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint. |
| 73 | * |
| 74 | * - MUST NOT include any assets in Pending state. |
| 75 | * - MUST NOT show any variations depending on the caller. |
| 76 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 77 | */ |
| 78 | function claimableDepositRequest( |
| 79 | uint256 requestId, |
| 80 | address controller |
| 81 | ) |
| 82 | external |
| 83 | view |
| 84 | returns (uint256 claimableAssets); |
| 85 | |
| 86 | /** |
| 87 | * @dev Mints shares Vault shares to receiver by claiming the Request of the controller. |
| 88 | * |
| 89 | * - MUST emit the Deposit event. |
| 90 | * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
| 91 | */ |
| 92 | function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
| 93 | |
| 94 | /** |
| 95 | * @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller. |
| 96 | * |
| 97 | * - MUST emit the Deposit event. |
| 98 | * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
| 99 | */ |
| 100 | function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
| 101 | } |
| 102 | |
| 103 | interface IERC7540Redeem is IERC7540Operator { |
| 104 | event RedeemRequest( |
| 105 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 106 | ); |
| 107 | |
| 108 | /** |
| 109 | * @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem. |
| 110 | * |
| 111 | * - MUST support a redeem Request flow where the control of shares is taken from sender directly |
| 112 | * where msg.sender has ERC-20 approval over the shares of owner. |
| 113 | * - MUST revert if all of shares cannot be requested for redeem. |
| 114 | * |
| 115 | * @param shares the amount of shares to be redeemed to transfer from owner |
| 116 | * @param controller the controller of the request who will be able to operate the request |
| 117 | * @param owner the source of the shares to be redeemed |
| 118 | * |
| 119 | * NOTE: most implementations will require pre-approval of the Vault with the Vault's share token. |
| 120 | */ |
| 121 | function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
| 122 | |
| 123 | /** |
| 124 | * @dev Returns the amount of requested shares in Pending state. |
| 125 | * |
| 126 | * - MUST NOT include any shares in Claimable state for redeem or withdraw. |
| 127 | * - MUST NOT show any variations depending on the caller. |
| 128 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 129 | */ |
| 130 | function pendingRedeemRequest( |
| 131 | uint256 requestId, |
| 132 | address controller |
| 133 | ) |
| 134 | external |
| 135 | view |
| 136 | returns (uint256 pendingShares); |
| 137 | |
| 138 | /** |
| 139 | * @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw. |
| 140 | * |
| 141 | * - MUST NOT include any shares in Pending state for redeem or withdraw. |
| 142 | * - MUST NOT show any variations depending on the caller. |
| 143 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 144 | */ |
| 145 | function claimableRedeemRequest( |
| 146 | uint256 requestId, |
| 147 | address controller |
| 148 | ) |
| 149 | external |
| 150 | view |
| 151 | returns (uint256 claimableShares); |
| 152 | } |
| 153 | |
| 154 | interface IERC7540CancelDeposit { |
| 155 | event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender); |
| 156 | event CancelDepositClaim( |
| 157 | address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets |
| 158 | ); |
| 159 | |
| 160 | /** |
| 161 | * @dev Submits a Request for cancelling the pending deposit Request |
| 162 | * |
| 163 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 164 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 165 | * - MUST set pendingCancelDepositRequest to `true` for the returned requestId after request |
| 166 | * - MUST increase claimableCancelDepositRequest for the returned requestId after fulfillment |
| 167 | * - SHOULD be claimable using `claimCancelDepositRequest` |
| 168 | * Note: while `pendingCancelDepositRequest` is `true`, `requestDeposit` cannot be called |
| 169 | */ |
| 170 | function cancelDepositRequest(uint256 requestId, address controller) external; |
| 171 | |
| 172 | /** |
| 173 | * @dev Returns whether the deposit Request is pending cancelation |
| 174 | * |
| 175 | * - MUST NOT show any variations depending on the caller. |
| 176 | */ |
| 177 | function pendingCancelDepositRequest( |
| 178 | uint256 requestId, |
| 179 | address controller |
| 180 | ) |
| 181 | external |
| 182 | view |
| 183 | returns (bool isPending); |
| 184 | |
| 185 | /** |
| 186 | * @dev Returns the amount of assets that were canceled from a deposit Request, and can now be claimed. |
| 187 | * |
| 188 | * - MUST NOT show any variations depending on the caller. |
| 189 | */ |
| 190 | function claimableCancelDepositRequest( |
| 191 | uint256 requestId, |
| 192 | address controller |
| 193 | ) |
| 194 | external |
| 195 | view |
| 196 | returns (uint256 claimableAssets); |
| 197 | |
| 198 | /** |
| 199 | * @dev Claims the canceled deposit assets, and removes the pending cancelation Request |
| 200 | * |
| 201 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 202 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 203 | * - MUST set pendingCancelDepositRequest to `false` for the returned requestId after request |
| 204 | * - MUST set claimableCancelDepositRequest to 0 for the returned requestId after fulfillment |
| 205 | */ |
| 206 | function claimCancelDepositRequest( |
| 207 | uint256 requestId, |
| 208 | address receiver, |
| 209 | address controller |
| 210 | ) |
| 211 | external |
| 212 | returns (uint256 assets); |
| 213 | } |
| 214 | |
| 215 | interface IERC7540CancelRedeem { |
| 216 | event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender); |
| 217 | event CancelRedeemClaim( |
| 218 | address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares |
| 219 | ); |
| 220 | |
| 221 | /** |
| 222 | * @dev Submits a Request for cancelling the pending redeem Request |
| 223 | * |
| 224 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 225 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 226 | * - MUST set pendingCancelRedeemRequest to `true` for the returned requestId after request |
| 227 | * - MUST increase claimableCancelRedeemRequest for the returned requestId after fulfillment |
| 228 | * - SHOULD be claimable using `claimCancelRedeemRequest` |
| 229 | * Note: while `pendingCancelRedeemRequest` is `true`, `requestRedeem` cannot be called |
| 230 | */ |
| 231 | function cancelRedeemRequest(uint256 requestId, address controller) external; |
| 232 | |
| 233 | /** |
| 234 | * @dev Returns whether the redeem Request is pending cancelation |
| 235 | * |
| 236 | * - MUST NOT show any variations depending on the caller. |
| 237 | */ |
| 238 | function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool isPending); |
| 239 | |
| 240 | /** |
| 241 | * @dev Returns the amount of shares that were canceled from a redeem Request, and can now be claimed. |
| 242 | * |
| 243 | * - MUST NOT show any variations depending on the caller. |
| 244 | */ |
| 245 | function claimableCancelRedeemRequest( |
| 246 | uint256 requestId, |
| 247 | address controller |
| 248 | ) |
| 249 | external |
| 250 | view |
| 251 | returns (uint256 claimableShares); |
| 252 | |
| 253 | /** |
| 254 | * @dev Claims the canceled redeem shares, and removes the pending cancelation Request |
| 255 | * |
| 256 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 257 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 258 | * - MUST set pendingCancelRedeemRequest to `false` for the returned requestId after request |
| 259 | * - MUST set claimableCancelRedeemRequest to 0 for the returned requestId after fulfillment |
| 260 | */ |
| 261 | function claimCancelRedeemRequest( |
| 262 | uint256 requestId, |
| 263 | address receiver, |
| 264 | address controller |
| 265 | ) |
| 266 | external |
| 267 | returns (uint256 shares); |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * @title IERC7540 |
| 272 | * @dev Fully async ERC7540 implementation according to the standard |
| 273 | * @dev Adapted from Centrifuge's IERC7540 implementation |
| 274 | */ |
| 275 | interface IERC7540 is IERC7540Deposit, IERC7540Redeem { } |
| 276 | |
| 277 | /** |
| 278 | * @title IERC7540Vault |
| 279 | * @dev This is the specific set of interfaces used by the SuperVaults |
| 280 | */ |
| 281 | interface IERC7540Vault is IERC7540, IERC7741 { |
| 282 | event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
| 283 | event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
| 284 | } |
| 285 |
0.0%
lib/v2-core/src/vendor/standards/ERC7741/IERC7741.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
| 2 | pragma solidity >=0.8.0; |
| 3 | |
| 4 | interface IERC7741 { |
| 5 | /** |
| 6 | * @dev Grants or revokes permissions for `operator` to manage Requests on behalf of the |
| 7 | * `msg.sender`, using an [EIP-712](./eip-712.md) signature. |
| 8 | */ |
| 9 | function authorizeOperator( |
| 10 | address controller, |
| 11 | address operator, |
| 12 | bool approved, |
| 13 | bytes32 nonce, |
| 14 | uint256 deadline, |
| 15 | bytes memory signature |
| 16 | ) |
| 17 | external |
| 18 | returns (bool); |
| 19 | |
| 20 | /** |
| 21 | * @dev Revokes the given `nonce` for `msg.sender` as the `owner`. |
| 22 | */ |
| 23 | function invalidateNonce(bytes32 nonce) external; |
| 24 | |
| 25 | /** |
| 26 | * @dev Returns whether the given `nonce` has been used for the `controller`. |
| 27 | */ |
| 28 | function authorizations(address controller, bytes32 nonce) external view returns (bool used); |
| 29 | |
| 30 | /** |
| 31 | * @dev Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR |
| 32 | * should be unique to the contract and chain to prevent replay attacks from other domains, |
| 33 | * and satisfy the requirements of EIP-712, but is otherwise unconstrained. |
| 34 | */ |
| 35 | function DOMAIN_SEPARATOR() external view returns (bytes32); |
| 36 | } |
| 37 |
0.0%
lib/v2-core/src/vendor/superform/ISuperVault.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ISuperVault |
| 5 | /// @notice Interface for SuperVault core contract that manages share minting |
| 6 | /// @author Superform Labs |
| 7 | interface ISuperVault { |
| 8 | /*////////////////////////////////////////////////////////////// |
| 9 | ERRORS |
| 10 | //////////////////////////////////////////////////////////////*/ |
| 11 | error ALREADY_INITIALIZED(); |
| 12 | error INVALID_ASSET(); |
| 13 | error INVALID_STRATEGY(); |
| 14 | error INVALID_ESCROW(); |
| 15 | error ZERO_ADDRESS(); |
| 16 | error ZERO_AMOUNT(); |
| 17 | error INVALID_OWNER_OR_OPERATOR(); |
| 18 | error INVALID_AMOUNT(); |
| 19 | error REQUEST_NOT_FOUND(); |
| 20 | error UNAUTHORIZED(); |
| 21 | error TIMELOCK_NOT_EXPIRED(); |
| 22 | error INVALID_SIGNATURE(); |
| 23 | error NOT_IMPLEMENTED(); |
| 24 | error INVALID_NONCE(); |
| 25 | error INVALID_WITHDRAW_PRICE(); |
| 26 | error TRANSFER_FAILED(); |
| 27 | error CAP_EXCEEDED(); |
| 28 | error INVALID_PPS(); |
| 29 | error INVALID_CONTROLLER(); |
| 30 | |
| 31 | /*////////////////////////////////////////////////////////////// |
| 32 | EVENTS |
| 33 | //////////////////////////////////////////////////////////////*/ |
| 34 | |
| 35 | event RedeemClaimable( |
| 36 | address indexed user, |
| 37 | uint256 indexed requestId, |
| 38 | uint256 assets, |
| 39 | uint256 shares, |
| 40 | uint256 averageWithdrawPrice, |
| 41 | uint256 accumulatorShares, |
| 42 | uint256 accumulatorCostBasis |
| 43 | ); |
| 44 | |
| 45 | event NonceInvalidated(address indexed sender, bytes32 indexed nonce); |
| 46 | |
| 47 | event RedeemRequestCancelled(address indexed controller, address indexed sender); |
| 48 | |
| 49 | /*////////////////////////////////////////////////////////////// |
| 50 | EXTERNAL METHODS |
| 51 | //////////////////////////////////////////////////////////////*/ |
| 52 | |
| 53 | function cancelRedeem(address controller) external; |
| 54 | |
| 55 | /// @notice Mint new shares, only callable by strategy |
| 56 | /// @param amount The amount of shares to mint |
| 57 | function mintShares(uint256 amount) external; |
| 58 | |
| 59 | /// @notice Burn shares, only callable by strategy |
| 60 | /// @param amount The amount of shares to burn |
| 61 | function burnShares(uint256 amount) external; |
| 62 | |
| 63 | /// @notice Callback function for when a redeem becomes claimable |
| 64 | /// @param user The user whose redeem is claimable |
| 65 | /// @param assets The amount of assets to be received |
| 66 | /// @param shares The amount of shares redeemed |
| 67 | /// @param averageWithdrawPrice The average price of the redeem |
| 68 | /// @param accumulatorShares The amount of shares in the accumulator |
| 69 | /// @param accumulatorCostBasis The cost basis of the accumulator |
| 70 | function onRedeemClaimable( |
| 71 | address user, |
| 72 | uint256 assets, |
| 73 | uint256 shares, |
| 74 | uint256 averageWithdrawPrice, |
| 75 | uint256 accumulatorShares, |
| 76 | uint256 accumulatorCostBasis |
| 77 | ) |
| 78 | external; |
| 79 | } |
| 80 |
0.0%
lib/v2-core/src/vendor/vaults/7540/IERC7540.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | interface IERC7575 { |
| 5 | event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); |
| 6 | event Withdraw( |
| 7 | address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares |
| 8 | ); |
| 9 | |
| 10 | /** |
| 11 | * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
| 12 | * |
| 13 | * - MUST be an ERC-20 token contract. |
| 14 | * - MUST NOT revert. |
| 15 | */ |
| 16 | function asset() external view returns (address assetTokenAddress); |
| 17 | |
| 18 | /** |
| 19 | * @dev Returns the address of the share token |
| 20 | * |
| 21 | * - MUST be an ERC-20 token contract. |
| 22 | * - MUST NOT revert. |
| 23 | */ |
| 24 | function share() external view returns (address shareTokenAddress); |
| 25 | |
| 26 | /** |
| 27 | * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
| 28 | * scenario where all the conditions are met. |
| 29 | * |
| 30 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 31 | * - MUST NOT show any variations depending on the caller. |
| 32 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 33 | * - MUST NOT revert. |
| 34 | * |
| 35 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 36 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 37 | * from. |
| 38 | */ |
| 39 | function convertToShares(uint256 assets) external view returns (uint256 shares); |
| 40 | |
| 41 | /** |
| 42 | * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
| 43 | * scenario where all the conditions are met. |
| 44 | * |
| 45 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 46 | * - MUST NOT show any variations depending on the caller. |
| 47 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 48 | * - MUST NOT revert. |
| 49 | * |
| 50 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 51 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 52 | * from. |
| 53 | */ |
| 54 | function convertToAssets(uint256 shares) external view returns (uint256 assets); |
| 55 | |
| 56 | /** |
| 57 | * @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
| 58 | * |
| 59 | * - SHOULD include any compounding that occurs from yield. |
| 60 | * - MUST be inclusive of any fees that are charged against assets in the Vault. |
| 61 | * - MUST NOT revert. |
| 62 | */ |
| 63 | function totalAssets() external view returns (uint256 totalManagedAssets); |
| 64 | |
| 65 | /** |
| 66 | * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
| 67 | * through a deposit call. |
| 68 | * |
| 69 | * - MUST return a limited value if receiver is subject to some deposit limit. |
| 70 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
| 71 | * - MUST NOT revert. |
| 72 | */ |
| 73 | function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
| 74 | |
| 75 | /** |
| 76 | * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
| 77 | * current on-chain conditions. |
| 78 | * |
| 79 | * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
| 80 | * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
| 81 | * in the same transaction. |
| 82 | * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
| 83 | * deposit would be accepted, regardless if the user has enough tokens approved, etc. |
| 84 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 85 | * - MUST NOT revert. |
| 86 | * |
| 87 | * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
| 88 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 89 | */ |
| 90 | function previewDeposit(uint256 assets) external view returns (uint256 shares); |
| 91 | |
| 92 | /** |
| 93 | * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
| 94 | * |
| 95 | * - MUST emit the Deposit event. |
| 96 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 97 | * deposit execution, and are accounted for during deposit. |
| 98 | * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
| 99 | * approving enough underlying tokens to the Vault contract, etc). |
| 100 | * |
| 101 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 102 | */ |
| 103 | function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
| 104 | |
| 105 | /** |
| 106 | * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
| 107 | * - MUST return a limited value if receiver is subject to some mint limit. |
| 108 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
| 109 | * - MUST NOT revert. |
| 110 | */ |
| 111 | function maxMint(address receiver) external view returns (uint256 maxShares); |
| 112 | |
| 113 | /** |
| 114 | * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
| 115 | * current on-chain conditions. |
| 116 | * |
| 117 | * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
| 118 | * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
| 119 | * same transaction. |
| 120 | * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
| 121 | * would be accepted, regardless if the user has enough tokens approved, etc. |
| 122 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 123 | * - MUST NOT revert. |
| 124 | * |
| 125 | * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
| 126 | * share price or some other type of condition, meaning the depositor will lose assets by minting. |
| 127 | */ |
| 128 | function previewMint(uint256 shares) external view returns (uint256 assets); |
| 129 | |
| 130 | /** |
| 131 | * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
| 132 | * |
| 133 | * - MUST emit the Deposit event. |
| 134 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
| 135 | * execution, and are accounted for during mint. |
| 136 | * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
| 137 | * approving enough underlying tokens to the Vault contract, etc). |
| 138 | * |
| 139 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 140 | */ |
| 141 | function mint(uint256 shares, address receiver) external returns (uint256 assets); |
| 142 | |
| 143 | /** |
| 144 | * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
| 145 | * Vault, through a withdraw call. |
| 146 | * |
| 147 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 148 | * - MUST NOT revert. |
| 149 | */ |
| 150 | function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
| 151 | |
| 152 | /** |
| 153 | * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
| 154 | * given current on-chain conditions. |
| 155 | * |
| 156 | * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
| 157 | * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
| 158 | * called |
| 159 | * in the same transaction. |
| 160 | * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
| 161 | * the withdrawal would be accepted, regardless if the user has enough shares, etc. |
| 162 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 163 | * - MUST NOT revert. |
| 164 | * |
| 165 | * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
| 166 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 167 | */ |
| 168 | function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
| 169 | |
| 170 | /** |
| 171 | * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
| 172 | * |
| 173 | * - MUST emit the Withdraw event. |
| 174 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 175 | * withdraw execution, and are accounted for during withdraw. |
| 176 | * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
| 177 | * not having enough shares, etc). |
| 178 | * |
| 179 | * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 180 | * Those methods should be performed separately. |
| 181 | */ |
| 182 | function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
| 183 | |
| 184 | /** |
| 185 | * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
| 186 | * through a redeem call. |
| 187 | * |
| 188 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 189 | * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
| 190 | * - MUST NOT revert. |
| 191 | */ |
| 192 | function maxRedeem(address owner) external view returns (uint256 maxShares); |
| 193 | |
| 194 | /** |
| 195 | * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, |
| 196 | * given current on-chain conditions. |
| 197 | * |
| 198 | * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
| 199 | * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
| 200 | * same transaction. |
| 201 | * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
| 202 | * redemption would be accepted, regardless if the user has enough shares, etc. |
| 203 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 204 | * - MUST NOT revert. |
| 205 | * |
| 206 | * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
| 207 | * share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
| 208 | */ |
| 209 | function previewRedeem(uint256 shares) external view returns (uint256 assets); |
| 210 | |
| 211 | /** |
| 212 | * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
| 213 | * |
| 214 | * - MUST emit the Withdraw event. |
| 215 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 216 | * redeem execution, and are accounted for during redeem. |
| 217 | * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
| 218 | * not having enough shares, etc). |
| 219 | * |
| 220 | * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 221 | * Those methods should be performed separately. |
| 222 | */ |
| 223 | function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
| 224 | } |
| 225 | // As defined by https://eips.ethereum.org/EIPS/eip-7540#request-flows |
| 226 | |
| 227 | interface IERC7540 is IERC7575 { |
| 228 | event DepositRequest( |
| 229 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 230 | ); |
| 231 | |
| 232 | event RedeemRequest( |
| 233 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 234 | ); |
| 235 | |
| 236 | /*////////////////////////////////////////////////////////////// |
| 237 | VIEW METHODS |
| 238 | //////////////////////////////////////////////////////////////*/ |
| 239 | /// @notice Check if the contract supports an interface |
| 240 | /// @param interfaceId The selector of the interface to check |
| 241 | function supportsInterface(bytes4 interfaceId) external view returns (bool); |
| 242 | |
| 243 | /// @notice Preview the amount of assets that would be received for a given amount of shares |
| 244 | /// @param shares The amount of shares to redeem |
| 245 | function previewRedeem(uint256 shares) external view returns (uint256 assets); |
| 246 | |
| 247 | /// @notice Check if a deposit request is pending |
| 248 | /// @param requestId The id of the request to check |
| 249 | /// @param controller The address of the controller |
| 250 | function pendingDepositRequest(uint256 requestId, address controller) external view returns (uint256); |
| 251 | |
| 252 | /// @notice Check if a deposit request is pending cancellation |
| 253 | /// @param requestId The id of the request to check |
| 254 | /// @param controller The address of the controller |
| 255 | function pendingCancelDepositRequest(uint256 requestId, address controller) external view returns (bool); |
| 256 | |
| 257 | /// @notice Check if a redeem request is pending |
| 258 | /// @param requestId The id of the request to check |
| 259 | /// @param controller The address of the controller |
| 260 | function pendingRedeemRequest(uint256 requestId, address controller) external view returns (uint256); |
| 261 | |
| 262 | /// @notice Get the amount of assets that can be claimed from a deposit request |
| 263 | /// @param requestId The id of the request to check |
| 264 | /// @param controller The address of the controller |
| 265 | function claimableDepositRequest(uint256 requestId, address controller) external view returns (uint256); |
| 266 | |
| 267 | /// @notice Get the amount of shares that can be claimed from a redeem request |
| 268 | /// @param requestId The id of the request to check |
| 269 | /// @param controller The address of the controller |
| 270 | function claimableRedeemRequest(uint256 requestId, address controller) external view returns (uint256); |
| 271 | |
| 272 | /// @notice Check if a redeem request is pending cancellation |
| 273 | /// @param requestId The id of the request to check |
| 274 | /// @param controller The address of the controller |
| 275 | function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool); |
| 276 | |
| 277 | /*////////////////////////////////////////////////////////////// |
| 278 | EXTERNAL METHODS |
| 279 | //////////////////////////////////////////////////////////////*/ |
| 280 | /// @notice Mints amount of shares by claiming the controller's request |
| 281 | /// @dev sender must be the controller or an operator approved by the controller |
| 282 | /// @param assets The amount of assets to deposit |
| 283 | /// @param receiver The address of the receiver |
| 284 | /// @param controller The address of the controller |
| 285 | function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
| 286 | |
| 287 | /// @notice Mint exact amount of shares into the vault by claiming the controller's request |
| 288 | /// @param shares The amount of shares to mint |
| 289 | /// @param receiver The address of the receiver |
| 290 | /// @param controller The address of the controller |
| 291 | function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
| 292 | |
| 293 | /// @notice Burn shares from the vault and sends exact amount of assets to the receiver |
| 294 | /// @param assets The amount of assets to withdraw |
| 295 | /// @param receiver The address of the receiver |
| 296 | /// @param owner The address of the owner |
| 297 | function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
| 298 | |
| 299 | /// @notice Burns exact shares from the vault and sends assets to the receiver |
| 300 | /// @param shares The amount of shares to redeem |
| 301 | /// @param receiver The address of the receiver |
| 302 | /// @param owner The address of the owner |
| 303 | function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
| 304 | |
| 305 | /// @notice Request a deposit of assets |
| 306 | /// @param assets The amount of assets to deposit |
| 307 | /// @param controller The address of the controller |
| 308 | /// @param owner The address of the owner |
| 309 | function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
| 310 | |
| 311 | /// @notice Request a redeem of shares |
| 312 | /// @param shares The amount of shares to redeem |
| 313 | /// @param controller The address of the controller |
| 314 | /// @param owner The address of the owner |
| 315 | function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
| 316 | |
| 317 | /// @notice Cancel a deposit request |
| 318 | /// @param requestId The id of the request to cancel |
| 319 | /// @param controller The address of the controller |
| 320 | function cancelDepositRequest(uint256 requestId, address controller) external; |
| 321 | |
| 322 | /// @notice Claims the canceled deposit assets, and removes the pending cancelation Request |
| 323 | /// @dev sender must be the controller |
| 324 | /// @param requestId The id of the request to claim |
| 325 | /// @param receiver The address of the receiver |
| 326 | /// @param controller The address of the controller |
| 327 | function claimCancelDepositRequest( |
| 328 | uint256 requestId, |
| 329 | address receiver, |
| 330 | address controller |
| 331 | ) |
| 332 | external |
| 333 | returns (uint256 assets); |
| 334 | |
| 335 | /// @notice Cancel a redeem request |
| 336 | /// @param requestId The id of the request to cancel |
| 337 | /// @param controller The address of the controller |
| 338 | function cancelRedeemRequest(uint256 requestId, address controller) external; |
| 339 | |
| 340 | /// @notice Claims the canceled redeem shares, and removes the pending cancelation Request |
| 341 | /// @dev sender must be the controller |
| 342 | /// @param requestId The id of the request to claim |
| 343 | /// @param receiver The address of the receiver |
| 344 | /// @param controller The address of the controller |
| 345 | function claimCancelRedeemRequest( |
| 346 | uint256 requestId, |
| 347 | address receiver, |
| 348 | address controller |
| 349 | ) |
| 350 | external |
| 351 | returns (uint256 shares); |
| 352 | |
| 353 | /// @notice Get the pool id |
| 354 | function poolId() external view returns (uint64); |
| 355 | |
| 356 | /// @notice Get the tranche id |
| 357 | function trancheId() external view returns (bytes16); |
| 358 | |
| 359 | /*////////////////////////////////////////////////////////////// |
| 360 | OWNER METHODS |
| 361 | //////////////////////////////////////////////////////////////*/ |
| 362 | function setOperator(address operator, bool approved) external returns (bool); |
| 363 | function isOperator(address controller, address operator) external view returns (bool status); |
| 364 | } |
| 365 |
33.0%
src/SuperGovernor.sol
Lines covered: 155 / 457 (33.0%)
| 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 | 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 | address private constant NATIVE_TOKEN = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); |
| 102 | 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 | 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 | bytes32 private constant _BANK_MANAGER_ROLE = keccak256("BANK_MANAGER_ROLE"); |
| 117 | bytes32 private constant _GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); |
| 118 | bytes32 private constant _SUPER_ASSET_FACTORY = keccak256("SUPER_ASSET_FACTORY"); |
| 119 | bytes32 private constant _GAS_MANAGER_ROLE = keccak256("GAS_MANAGER_ROLE"); |
| 120 | |
| 121 | // Common contract keys |
| 122 | bytes32 public constant UP = keccak256("UP"); |
| 123 | bytes32 public constant SUP = keccak256("SUP"); |
| 124 | bytes32 public constant TREASURY = keccak256("TREASURY"); |
| 125 | bytes32 public constant VAULT_BANK = keccak256("VAULT_BANK"); |
| 126 | bytes32 public constant SUPER_BANK = keccak256("SUPER_BANK"); |
| 127 | bytes32 public constant SUPER_ORACLE = keccak256("SUPER_ORACLE"); |
| 128 | bytes32 public constant BANK_MANAGER = keccak256("BANK_MANAGER"); |
| 129 | bytes32 public constant ECDSAPPSORACLE = keccak256("ECDSAPPSORACLE"); |
| 130 | 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 | constructor(address superGovernor, address governor, address bankManager, address gasManager, address treasury_, address prover_) { |
| 142 | if ( |
| 143 | superGovernor == address(0) || treasury_ == address(0) || governor == address(0) |
| 144 | || bankManager == address(0) || prover_ == address(0) || gasManager == address(0) |
| 145 | ) revert INVALID_ADDRESS(); |
| 146 | |
| 147 | // Set up roles |
| 148 | _grantRole(DEFAULT_ADMIN_ROLE, superGovernor); |
| 149 | _grantRole(_SUPER_GOVERNOR_ROLE, superGovernor); |
| 150 | _grantRole(_GOVERNOR_ROLE, governor); |
| 151 | _grantRole(_BANK_MANAGER_ROLE, bankManager); |
| 152 | _grantRole(_GAS_MANAGER_ROLE, gasManager); |
| 153 | // Setup GUARDIAN_ROLE without assigning any address |
| 154 | _setRoleAdmin(_GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE); |
| 155 | |
| 156 | // Set role admins |
| 157 | _setRoleAdmin(_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
| 158 | _setRoleAdmin(_SUPER_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
| 159 | _setRoleAdmin(_BANK_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
| 160 | _setRoleAdmin(_GAS_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
| 161 | |
| 162 | // Initialize with default fees |
| 163 | _feeValues[FeeType.REVENUE_SHARE] = 2000; // 20% revenue share |
| 164 | _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE] = 2000; // 20% performance fee |
| 165 | _feeValues[FeeType.SUPER_ASSET_SWAP_FEE] = 4000; // 40% swap fee |
| 166 | emit FeeUpdated(FeeType.REVENUE_SHARE, _feeValues[FeeType.REVENUE_SHARE]); |
| 167 | emit FeeUpdated(FeeType.SUPER_VAULT_PERFORMANCE_FEE, _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE]); |
| 168 | emit FeeUpdated(FeeType.SUPER_ASSET_SWAP_FEE, _feeValues[FeeType.SUPER_ASSET_SWAP_FEE]); |
| 169 | |
| 170 | // Set treasury in address registry |
| 171 | _addressRegistry[TREASURY] = treasury_; |
| 172 | emit AddressSet(TREASURY, treasury_); |
| 173 | |
| 174 | // Initialize minimum staleness (5 minutes to prevent extremely low staleness values) |
| 175 | _minStaleness = 300; // 5 minutes in seconds |
| 176 | |
| 177 | // Initialize prover |
| 178 | _prover = prover_; |
| 179 | emit ProverSet(prover_); |
| 180 | } |
| 181 | |
| 182 | /*////////////////////////////////////////////////////////////// |
| 183 | CONTRACT REGISTRY FUNCTIONS |
| 184 | //////////////////////////////////////////////////////////////*/ |
| 185 | /// @inheritdoc ISuperGovernor |
| 186 | function setAddress(bytes32 key, address value) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 187 | if (value == address(0)) revert INVALID_ADDRESS(); |
| 188 | |
| 189 | _addressRegistry[key] = value; |
| 190 | emit AddressSet(key, value); |
| 191 | } |
| 192 | |
| 193 | /*////////////////////////////////////////////////////////////// |
| 194 | PERIPHERY CONFIGURATIONS |
| 195 | //////////////////////////////////////////////////////////////*/ |
| 196 | /// @inheritdoc ISuperGovernor |
| 197 | function setProver(address prover) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 198 | if (prover == address(0)) revert INVALID_ADDRESS(); |
| 199 | |
| 200 | _prover = prover; |
| 201 | emit ProverSet(prover); |
| 202 | } |
| 203 | |
| 204 | /// @inheritdoc ISuperGovernor |
| 205 | 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 | ISuperVaultAggregator(aggregator).changePrimaryManager(strategy, newManager); |
| 215 | } |
| 216 | |
| 217 | /// @inheritdoc ISuperGovernor |
| 218 | function freezeManagerTakeover() external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 219 | if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN(); |
| 220 | |
| 221 | // Set frozen status to true (permanent, cannot be undone) |
| 222 | _managerTakeoversFrozen = true; |
| 223 | |
| 224 | // Emit event for the frozen status |
| 225 | emit ManagerTakeoversFrozen(); |
| 226 | } |
| 227 | |
| 228 | /// @inheritdoc ISuperGovernor |
| 229 | function changeHooksRootUpdateTimelock(uint256 newTimelock) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 230 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 231 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 232 | |
| 233 | // Call the SuperVaultAggregator to change the hooks root update timelock |
| 234 | ISuperVaultAggregator(aggregator).setHooksRootUpdateTimelock(newTimelock); |
| 235 | } |
| 236 | |
| 237 | /// @inheritdoc ISuperGovernor |
| 238 | 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 | ISuperVaultAggregator(aggregator).proposeGlobalHooksRoot(newRoot); |
| 243 | } |
| 244 | |
| 245 | /// @inheritdoc ISuperGovernor |
| 246 | function setGlobalHooksRootVetoStatus(bool vetoed) external onlyRole(_GUARDIAN_ROLE) { |
| 247 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 248 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 249 | |
| 250 | ISuperVaultAggregator(aggregator).setGlobalHooksRootVetoStatus(vetoed); |
| 251 | } |
| 252 | |
| 253 | /// @inheritdoc ISuperGovernor |
| 254 | function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external onlyRole(_GUARDIAN_ROLE) { |
| 255 | if (strategy == address(0)) revert INVALID_ADDRESS(); |
| 256 | |
| 257 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 258 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 259 | |
| 260 | ISuperVaultAggregator(aggregator).setStrategyHooksRootVetoStatus(strategy, vetoed); |
| 261 | } |
| 262 | |
| 263 | /// @inheritdoc ISuperGovernor |
| 264 | function setSuperAssetManager(address superAsset, address superAssetManager) external onlyRole(_GOVERNOR_ROLE) { |
| 265 | if (superAsset == address(0) || superAssetManager == address(0)) revert INVALID_ADDRESS(); |
| 266 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 267 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 268 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 269 | factory.setSuperAssetManager(superAsset, superAssetManager); |
| 270 | } |
| 271 | |
| 272 | /// @inheritdoc ISuperGovernor |
| 273 | function addICCToWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 274 | if (icc == address(0)) revert INVALID_ADDRESS(); |
| 275 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 276 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 277 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 278 | factory.addICCToWhitelist(icc); |
| 279 | } |
| 280 | |
| 281 | /// @inheritdoc ISuperGovernor |
| 282 | function removeICCFromWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 283 | if (icc == address(0)) revert INVALID_ADDRESS(); |
| 284 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 285 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 286 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 287 | factory.removeICCFromWhitelist(icc); |
| 288 | } |
| 289 | |
| 290 | /// @inheritdoc ISuperGovernor |
| 291 | function setOracleMaxStaleness(uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) { |
| 292 | if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 293 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 294 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 295 | |
| 296 | ISuperOracle(oracle).setMaxStaleness(newMaxStaleness); |
| 297 | } |
| 298 | |
| 299 | /// @inheritdoc ISuperGovernor |
| 300 | function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) { |
| 301 | if (feed == address(0)) revert INVALID_ADDRESS(); |
| 302 | if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 303 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 304 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 305 | |
| 306 | ISuperOracle(oracle).setFeedMaxStaleness(feed, newMaxStaleness); |
| 307 | } |
| 308 | |
| 309 | /// @inheritdoc ISuperGovernor |
| 310 | 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 | for (uint256 i; i < newMaxStalenessList_.length; i++) { |
| 319 | if (newMaxStalenessList_[i] < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 320 | } |
| 321 | |
| 322 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 323 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 324 | |
| 325 | ISuperOracle(oracle).setFeedMaxStalenessBatch(feeds_, newMaxStalenessList_); |
| 326 | } |
| 327 | |
| 328 | /// @inheritdoc ISuperGovernor |
| 329 | 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 | { |
| 338 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 339 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 340 | |
| 341 | ISuperOracle(oracle).queueOracleUpdate(bases_, quotes_, providers_, feeds_); |
| 342 | } |
| 343 | |
| 344 | /// @inheritdoc ISuperGovernor |
| 345 | function queueOracleProviderRemoval(bytes32[] calldata providers) external onlyRole(_GOVERNOR_ROLE) { |
| 346 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 347 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 348 | |
| 349 | ISuperOracle(oracle).queueProviderRemoval(providers); |
| 350 | } |
| 351 | |
| 352 | /// @inheritdoc ISuperGovernor |
| 353 | function batchSetOracleUptimeFeed( |
| 354 | address[] calldata dataOracles_, |
| 355 | address[] calldata uptimeOracles_, |
| 356 | uint256[] calldata gracePeriods_ |
| 357 | ) |
| 358 | external |
| 359 | onlyRole(_GOVERNOR_ROLE) |
| 360 | { |
| 361 | address oracleL2 = _addressRegistry[SUPER_ORACLE]; |
| 362 | if (oracleL2 == address(0)) revert CONTRACT_NOT_FOUND(); |
| 363 | |
| 364 | ISuperOracleL2(oracleL2).batchSetUptimeFeed(dataOracles_, uptimeOracles_, gracePeriods_); |
| 365 | } |
| 366 | |
| 367 | /// @inheritdoc ISuperGovernor |
| 368 | function setEmergencyPrice(address token, uint256 price) external onlyRole(_GOVERNOR_ROLE) { |
| 369 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 370 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 371 | |
| 372 | 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 | { |
| 383 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 384 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 385 | |
| 386 | ISuperOracle(oracle).batchSetEmergencyPrice(tokens_, prices_); |
| 387 | } |
| 388 | |
| 389 | /*////////////////////////////////////////////////////////////// |
| 390 | HOOK MANAGEMENT |
| 391 | //////////////////////////////////////////////////////////////*/ |
| 392 | /// @inheritdoc ISuperGovernor |
| 393 | function registerHook(address hook, bool isFulfillRequestsHook) external onlyRole(_GOVERNOR_ROLE) { |
| 394 | if (hook == address(0)) revert INVALID_ADDRESS(); |
| 395 | |
| 396 | if (isFulfillRequestsHook && _registeredFulfillRequestsHooks.add(hook)) { |
| 397 | emit FulfillRequestsHookRegistered(hook); |
| 398 | } |
| 399 | if (_registeredHooks.add(hook)) { |
| 400 | emit HookApproved(hook); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | /// @inheritdoc ISuperGovernor |
| 405 | function unregisterHook(address hook) external onlyRole(_GOVERNOR_ROLE) { |
| 406 | if (_registeredFulfillRequestsHooks.remove(hook)) { |
| 407 | emit FulfillRequestsHookUnregistered(hook); |
| 408 | } |
| 409 | if (_registeredHooks.remove(hook)) { |
| 410 | // Clear merkle root data for the unregistered hook to prevent stale data |
| 411 | delete superBankHooksMerkleRoots[hook]; |
| 412 | delete vaultBankHooksMerkleRoots[hook]; |
| 413 | |
| 414 | emit HookRemoved(hook); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /*////////////////////////////////////////////////////////////// |
| 419 | EXECUTORS MANAGEMENT |
| 420 | //////////////////////////////////////////////////////////////*/ |
| 421 | /// @inheritdoc ISuperGovernor |
| 422 | function addExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) { |
| 423 | if (executor == address(0)) revert INVALID_ADDRESS(); |
| 424 | if (!_executors.add(executor)) revert EXECUTOR_ALREADY_REGISTERED(); |
| 425 | |
| 426 | emit ExecutorAdded(executor); |
| 427 | } |
| 428 | |
| 429 | /// @inheritdoc ISuperGovernor |
| 430 | function removeExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) { |
| 431 | if (!_executors.remove(executor)) revert EXECUTOR_NOT_REGISTERED(); |
| 432 | |
| 433 | emit ExecutorRemoved(executor); |
| 434 | } |
| 435 | |
| 436 | /*////////////////////////////////////////////////////////////// |
| 437 | RELAYER MANAGEMENT |
| 438 | //////////////////////////////////////////////////////////////*/ |
| 439 | /// @inheritdoc ISuperGovernor |
| 440 | function addRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) { |
| 441 | if (relayer == address(0)) revert INVALID_ADDRESS(); |
| 442 | if (!_relayers.add(relayer)) revert RELAYER_ALREADY_REGISTERED(); |
| 443 | |
| 444 | emit RelayerAdded(relayer); |
| 445 | } |
| 446 | |
| 447 | /// @inheritdoc ISuperGovernor |
| 448 | function removeRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) { |
| 449 | if (!_relayers.remove(relayer)) revert RELAYER_NOT_REGISTERED(); |
| 450 | |
| 451 | emit RelayerRemoved(relayer); |
| 452 | } |
| 453 | |
| 454 | /*////////////////////////////////////////////////////////////// |
| 455 | VALIDATOR MANAGEMENT |
| 456 | //////////////////////////////////////////////////////////////*/ |
| 457 | /// @inheritdoc ISuperGovernor |
| 458 | function addValidator(address validator) external onlyRole(_GOVERNOR_ROLE) { |
| 459 | if (validator == address(0)) revert INVALID_ADDRESS(); |
| 460 | if (!_validators.add(validator)) revert VALIDATOR_ALREADY_REGISTERED(); |
| 461 | |
| 462 | emit ValidatorAdded(validator); |
| 463 | } |
| 464 | |
| 465 | /// @inheritdoc ISuperGovernor |
| 466 | function removeValidator(address validator) external onlyRole(_GOVERNOR_ROLE) { |
| 467 | if (!_validators.remove(validator)) revert VALIDATOR_NOT_REGISTERED(); |
| 468 | |
| 469 | emit ValidatorRemoved(validator); |
| 470 | } |
| 471 | |
| 472 | /*////////////////////////////////////////////////////////////// |
| 473 | PPS ORACLE MANAGEMENT |
| 474 | //////////////////////////////////////////////////////////////*/ |
| 475 | /// @inheritdoc ISuperGovernor |
| 476 | function setActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 477 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 478 | |
| 479 | // If this is the first oracle or replacing a zero oracle, set it immediately |
| 480 | if (_activePPSOracle == address(0)) { |
| 481 | _activePPSOracle = oracle; |
| 482 | emit ActivePPSOracleSet(oracle); |
| 483 | } else { |
| 484 | // Otherwise require the timelock process |
| 485 | revert MUST_USE_TIMELOCK_FOR_CHANGE(); |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | /// @inheritdoc ISuperGovernor |
| 490 | function proposeActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 491 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 492 | |
| 493 | _proposedActivePPSOracle = oracle; |
| 494 | _activePPSOracleEffectiveTime = block.timestamp + TIMELOCK; |
| 495 | |
| 496 | emit ActivePPSOracleProposed(oracle, _activePPSOracleEffectiveTime); |
| 497 | } |
| 498 | |
| 499 | /// @inheritdoc ISuperGovernor |
| 500 | function executeActivePPSOracleChange() external { |
| 501 | if (_proposedActivePPSOracle == address(0)) revert NO_PROPOSED_PPS_ORACLE(); |
| 502 | |
| 503 | if (block.timestamp < _activePPSOracleEffectiveTime) { |
| 504 | revert TIMELOCK_NOT_EXPIRED(); |
| 505 | } |
| 506 | |
| 507 | address oldOracle = _activePPSOracle; |
| 508 | _activePPSOracle = _proposedActivePPSOracle; |
| 509 | |
| 510 | // Reset proposal data |
| 511 | _proposedActivePPSOracle = address(0); |
| 512 | _activePPSOracleEffectiveTime = 0; |
| 513 | |
| 514 | emit ActivePPSOracleChanged(oldOracle, _activePPSOracle); |
| 515 | } |
| 516 | |
| 517 | /// @inheritdoc ISuperGovernor |
| 518 | function setPPSOracleQuorum(uint256 quorum) external onlyRole(_GOVERNOR_ROLE) { |
| 519 | if (quorum == 0) revert INVALID_QUORUM(); |
| 520 | |
| 521 | _activePPSOracleQuorum = quorum; |
| 522 | emit PPSOracleQuorumUpdated(quorum); |
| 523 | } |
| 524 | |
| 525 | /*////////////////////////////////////////////////////////////// |
| 526 | REVENUE SHARE MANAGEMENT |
| 527 | //////////////////////////////////////////////////////////////*/ |
| 528 | /// @inheritdoc ISuperGovernor |
| 529 | function proposeFee(FeeType feeType, uint256 value) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 530 | if (value > BPS_MAX) revert INVALID_FEE_VALUE(); |
| 531 | |
| 532 | _proposedFeeValues[feeType] = value; |
| 533 | _feeEffectiveTimes[feeType] = block.timestamp + TIMELOCK; |
| 534 | |
| 535 | emit FeeProposed(feeType, value, _feeEffectiveTimes[feeType]); |
| 536 | } |
| 537 | |
| 538 | /// @inheritdoc ISuperGovernor |
| 539 | function executeFeeUpdate(FeeType feeType) external { |
| 540 | uint256 effectiveTime = _feeEffectiveTimes[feeType]; |
| 541 | if (effectiveTime == 0) revert NO_PROPOSED_FEE(feeType); |
| 542 | if (block.timestamp < effectiveTime) { |
| 543 | revert TIMELOCK_NOT_EXPIRED(); |
| 544 | } |
| 545 | |
| 546 | // Update the fee value |
| 547 | _feeValues[feeType] = _proposedFeeValues[feeType]; |
| 548 | |
| 549 | // Reset proposal data |
| 550 | delete _proposedFeeValues[feeType]; |
| 551 | delete _feeEffectiveTimes[feeType]; |
| 552 | |
| 553 | emit FeeUpdated(feeType, _feeValues[feeType]); |
| 554 | } |
| 555 | |
| 556 | /// @inheritdoc ISuperGovernor |
| 557 | 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 | ISuperVaultAggregator(aggregator).claimUpkeep(amount); |
| 562 | } |
| 563 | |
| 564 | /*////////////////////////////////////////////////////////////// |
| 565 | UPKEEP COST MANAGEMENT |
| 566 | //////////////////////////////////////////////////////////////*/ |
| 567 | /// @inheritdoc ISuperGovernor |
| 568 | function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external onlyRole(_GAS_MANAGER_ROLE) { |
| 569 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 570 | if (baseGasBatch == 0 || gasIncreasePerEntryBatch == 0) revert INVALID_GAS_INFO(); |
| 571 | |
| 572 | _oracleGasInfo[oracle] = GasInfo({baseGasBatch: baseGasBatch, gasIncreasePerEntryBatch: gasIncreasePerEntryBatch}); |
| 573 | 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 | function proposeUpkeepPaymentsChange(bool enabled) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 579 | _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 | if (_upkeepPaymentsChangeEffectiveTime == 0) revert NO_PENDING_CHANGE(); |
| 588 | if (block.timestamp < _upkeepPaymentsChangeEffectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 589 | |
| 590 | _upkeepPaymentsEnabled = _proposedUpkeepPaymentsEnabled; |
| 591 | _upkeepPaymentsChangeEffectiveTime = 0; |
| 592 | _proposedUpkeepPaymentsEnabled = false; |
| 593 | |
| 594 | emit UpkeepPaymentsChanged(_upkeepPaymentsEnabled); |
| 595 | } |
| 596 | |
| 597 | /*////////////////////////////////////////////////////////////// |
| 598 | MIN STALENESS MANAGEMENT |
| 599 | //////////////////////////////////////////////////////////////*/ |
| 600 | /// @inheritdoc ISuperGovernor |
| 601 | 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 | if (minStalenesEffectiveTime == 0) revert NO_PROPOSED_MIN_STALENESS(); |
| 612 | 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 | emit MinStalenesChanged(_minStaleness); |
| 621 | } |
| 622 | |
| 623 | /*////////////////////////////////////////////////////////////// |
| 624 | SUPERFORM MANAGER MANAGEMENT |
| 625 | //////////////////////////////////////////////////////////////*/ |
| 626 | /// @inheritdoc ISuperGovernor |
| 627 | function addSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) { |
| 628 | if (manager == address(0)) revert INVALID_ADDRESS(); |
| 629 | if (!_superformManagers.add(manager)) revert MANAGER_ALREADY_REGISTERED(); |
| 630 | |
| 631 | emit SuperformManagerAdded(manager); |
| 632 | } |
| 633 | |
| 634 | /// @inheritdoc ISuperGovernor |
| 635 | function removeSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) { |
| 636 | if (!_superformManagers.remove(manager)) revert MANAGER_NOT_REGISTERED(); |
| 637 | |
| 638 | emit SuperformManagerRemoved(manager); |
| 639 | } |
| 640 | |
| 641 | /*////////////////////////////////////////////////////////////// |
| 642 | VAULT HOOKS MGMT |
| 643 | //////////////////////////////////////////////////////////////*/ |
| 644 | |
| 645 | /// @inheritdoc ISuperGovernor |
| 646 | function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) { |
| 647 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 648 | if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
| 649 | |
| 650 | uint256 effectiveTime = block.timestamp + TIMELOCK; |
| 651 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 652 | data.proposedRoot = proposedRoot; |
| 653 | data.effectiveTime = effectiveTime; |
| 654 | |
| 655 | emit VaultBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
| 656 | } |
| 657 | |
| 658 | /// @inheritdoc ISuperGovernor |
| 659 | function executeVaultBankHookMerkleRootUpdate(address hook) external { |
| 660 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 661 | |
| 662 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 663 | |
| 664 | // Check if there's a proposed update |
| 665 | bytes32 proposedRoot = data.proposedRoot; |
| 666 | if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
| 667 | |
| 668 | // Check if the effective time has passed |
| 669 | if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 670 | |
| 671 | // Update the Merkle root |
| 672 | data.currentRoot = proposedRoot; |
| 673 | |
| 674 | // Reset the proposal |
| 675 | data.proposedRoot = bytes32(0); |
| 676 | data.effectiveTime = 0; |
| 677 | |
| 678 | emit VaultBankHookMerkleRootUpdated(hook, proposedRoot); |
| 679 | } |
| 680 | |
| 681 | /*////////////////////////////////////////////////////////////// |
| 682 | SUPERBANK HOOKS MGMT |
| 683 | //////////////////////////////////////////////////////////////*/ |
| 684 | /// @inheritdoc ISuperGovernor |
| 685 | function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) { |
| 686 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 687 | if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
| 688 | |
| 689 | uint256 effectiveTime = block.timestamp + TIMELOCK; |
| 690 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 691 | data.proposedRoot = proposedRoot; |
| 692 | data.effectiveTime = effectiveTime; |
| 693 | |
| 694 | emit SuperBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
| 695 | } |
| 696 | |
| 697 | /// @inheritdoc ISuperGovernor |
| 698 | function executeSuperBankHookMerkleRootUpdate(address hook) external { |
| 699 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 700 | |
| 701 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 702 | |
| 703 | // Check if there's a proposed update |
| 704 | bytes32 proposedRoot = data.proposedRoot; |
| 705 | if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
| 706 | |
| 707 | // Check if the effective time has passed |
| 708 | if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 709 | |
| 710 | // Update the Merkle root |
| 711 | data.currentRoot = proposedRoot; |
| 712 | |
| 713 | // Reset the proposal |
| 714 | data.proposedRoot = bytes32(0); |
| 715 | data.effectiveTime = 0; |
| 716 | |
| 717 | emit SuperBankHookMerkleRootUpdated(hook, proposedRoot); |
| 718 | } |
| 719 | |
| 720 | /*////////////////////////////////////////////////////////////// |
| 721 | VAULT BANK MANAGEMENT |
| 722 | //////////////////////////////////////////////////////////////*/ |
| 723 | /// @inheritdoc ISuperGovernor |
| 724 | function addVaultBank(uint64 chainId, address vaultBank) external onlyRole(_GOVERNOR_ROLE) { |
| 725 | if (chainId == 0) revert INVALID_CHAIN_ID(); |
| 726 | if (vaultBank == address(0)) revert INVALID_ADDRESS(); |
| 727 | |
| 728 | if (_vaultBanksByChainId[chainId] != address(0)) { |
| 729 | _vaultBanks.remove(_vaultBanksByChainId[chainId]); |
| 730 | } |
| 731 | |
| 732 | _vaultBanks.add(vaultBank); |
| 733 | _vaultBanksByChainId[chainId] = vaultBank; |
| 734 | |
| 735 | emit VaultBankAddressAdded(chainId, vaultBank); |
| 736 | } |
| 737 | |
| 738 | /// @inheritdoc ISuperGovernor |
| 739 | function getVaultBank(uint64 chainId) external view returns (address) { |
| 740 | return _vaultBanksByChainId[chainId]; |
| 741 | } |
| 742 | |
| 743 | /*////////////////////////////////////////////////////////////// |
| 744 | INCENTIVE TOKEN MANAGEMENT |
| 745 | //////////////////////////////////////////////////////////////*/ |
| 746 | /// @inheritdoc ISuperGovernor |
| 747 | function proposeAddIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) { |
| 748 | for (uint256 i; i < tokens.length; i++) { |
| 749 | if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
| 750 | _proposedWhitelistedIncentiveTokens.add(tokens[i]); |
| 751 | } |
| 752 | |
| 753 | _proposedAddWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
| 754 | |
| 755 | 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 | ) 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 | for (uint256 i; i < len; i++) { |
| 772 | token = tokensToAdd[i]; |
| 773 | _isWhitelistedIncentiveToken[token] = true; |
| 774 | // Remove from proposed whitelisted tokens |
| 775 | _proposedWhitelistedIncentiveTokens.remove(token); |
| 776 | } |
| 777 | |
| 778 | // Emit event once with all tokens |
| 779 | emit WhitelistedIncentiveTokensAdded(tokensToAdd); |
| 780 | |
| 781 | // Reset proposal timestamp |
| 782 | _proposedAddWhitelistedIncentiveTokensEffectiveTime = 0; |
| 783 | } |
| 784 | |
| 785 | /// @inheritdoc ISuperGovernor |
| 786 | function proposeRemoveIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) { |
| 787 | for (uint256 i; i < tokens.length; i++) { |
| 788 | if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
| 789 | if (!_isWhitelistedIncentiveToken[tokens[i]]) revert NOT_WHITELISTED_INCENTIVE_TOKEN(); |
| 790 | |
| 791 | _proposedRemoveWhitelistedIncentiveTokens.add(tokens[i]); |
| 792 | } |
| 793 | |
| 794 | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
| 795 | |
| 796 | emit WhitelistedIncentiveTokensProposed( |
| 797 | _proposedRemoveWhitelistedIncentiveTokens.values(), _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
| 798 | ); |
| 799 | } |
| 800 | |
| 801 | /// @inheritdoc ISuperGovernor |
| 802 | function executeRemoveIncentiveTokens() external { |
| 803 | if ( |
| 804 | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime == 0 |
| 805 | || block.timestamp < _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
| 806 | ) revert TIMELOCK_NOT_EXPIRED(); |
| 807 | |
| 808 | // Get all proposed tokens before modifying the set |
| 809 | address[] memory tokensToRemove = _proposedRemoveWhitelistedIncentiveTokens.values(); |
| 810 | uint256 len = tokensToRemove.length; |
| 811 | address token; |
| 812 | for (uint256 i; i < len; i++) { |
| 813 | token = tokensToRemove[i]; |
| 814 | if (_isWhitelistedIncentiveToken[token]) { |
| 815 | _isWhitelistedIncentiveToken[token] = false; |
| 816 | } |
| 817 | // Remove from proposed whitelisted tokens to be removed |
| 818 | _proposedRemoveWhitelistedIncentiveTokens.remove(token); |
| 819 | } |
| 820 | |
| 821 | // Emit event once with all tokens |
| 822 | emit WhitelistedIncentiveTokensRemoved(tokensToRemove); |
| 823 | |
| 824 | // Reset proposal timestamp |
| 825 | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = 0; |
| 826 | } |
| 827 | |
| 828 | /*////////////////////////////////////////////////////////////// |
| 829 | EXTERNAL VIEW FUNCTIONS |
| 830 | //////////////////////////////////////////////////////////////*/ |
| 831 | /// @inheritdoc ISuperGovernor |
| 832 | function SUPER_GOVERNOR_ROLE() external pure returns (bytes32) { |
| 833 | return _SUPER_GOVERNOR_ROLE; |
| 834 | } |
| 835 | |
| 836 | /// @inheritdoc ISuperGovernor |
| 837 | function GOVERNOR_ROLE() external pure returns (bytes32) { |
| 838 | return _GOVERNOR_ROLE; |
| 839 | } |
| 840 | |
| 841 | /// @inheritdoc ISuperGovernor |
| 842 | function BANK_MANAGER_ROLE() external pure returns (bytes32) { |
| 843 | return _BANK_MANAGER_ROLE; |
| 844 | } |
| 845 | |
| 846 | /// @inheritdoc ISuperGovernor |
| 847 | function GAS_MANAGER_ROLE() external pure returns (bytes32) { |
| 848 | return _GAS_MANAGER_ROLE; |
| 849 | } |
| 850 | |
| 851 | /// @inheritdoc ISuperGovernor |
| 852 | function GUARDIAN_ROLE() external pure returns (bytes32) { |
| 853 | return _GUARDIAN_ROLE; |
| 854 | } |
| 855 | |
| 856 | /// @inheritdoc ISuperGovernor |
| 857 | function SUPER_ASSET_FACTORY() external pure returns (bytes32) { |
| 858 | return _SUPER_ASSET_FACTORY; |
| 859 | } |
| 860 | |
| 861 | /// @inheritdoc ISuperGovernor |
| 862 | function getAddress(bytes32 key) external view returns (address) { |
| 863 | address value = _addressRegistry[key]; |
| 864 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 865 | return value; |
| 866 | } |
| 867 | |
| 868 | /// @inheritdoc ISuperGovernor |
| 869 | function isManagerTakeoverFrozen() external view returns (bool) { |
| 870 | return _managerTakeoversFrozen; |
| 871 | } |
| 872 | |
| 873 | /// @inheritdoc ISuperGovernor |
| 874 | function isHookRegistered(address hook) external view returns (bool) { |
| 875 | return _registeredHooks.contains(hook); |
| 876 | } |
| 877 | |
| 878 | /// @inheritdoc ISuperGovernor |
| 879 | function isFulfillRequestsHookRegistered(address hook) external view returns (bool) { |
| 880 | return _registeredFulfillRequestsHooks.contains(hook); |
| 881 | } |
| 882 | |
| 883 | /// @inheritdoc ISuperGovernor |
| 884 | function getRegisteredHooks() external view returns (address[] memory) { |
| 885 | return _registeredHooks.values(); |
| 886 | } |
| 887 | |
| 888 | /// @inheritdoc ISuperGovernor |
| 889 | function getRegisteredFulfillRequestsHooks() external view returns (address[] memory) { |
| 890 | return _registeredFulfillRequestsHooks.values(); |
| 891 | } |
| 892 | |
| 893 | /// @inheritdoc ISuperGovernor |
| 894 | function isValidator(address validator) external view returns (bool) { |
| 895 | return _validators.contains(validator); |
| 896 | } |
| 897 | |
| 898 | /// @inheritdoc ISuperGovernor |
| 899 | function isGuardian(address guardian) external view returns (bool) { |
| 900 | return hasRole(_GUARDIAN_ROLE, guardian); |
| 901 | } |
| 902 | |
| 903 | /// @inheritdoc ISuperGovernor |
| 904 | function isRelayer(address relayer) external view returns (bool) { |
| 905 | return _relayers.contains(relayer); |
| 906 | } |
| 907 | |
| 908 | /// @inheritdoc ISuperGovernor |
| 909 | function isExecutor(address executor) external view returns (bool) { |
| 910 | return _executors.contains(executor); |
| 911 | } |
| 912 | |
| 913 | /// @inheritdoc ISuperGovernor |
| 914 | function getValidators() external view returns (address[] memory) { |
| 915 | return _validators.values(); |
| 916 | } |
| 917 | |
| 918 | /// @inheritdoc ISuperGovernor |
| 919 | function getRelayers() external view returns (address[] memory) { |
| 920 | return _relayers.values(); |
| 921 | } |
| 922 | |
| 923 | /// @inheritdoc ISuperGovernor |
| 924 | function getExecutors() external view returns (address[] memory) { |
| 925 | return _executors.values(); |
| 926 | } |
| 927 | |
| 928 | /// @inheritdoc ISuperGovernor |
| 929 | function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime) { |
| 930 | return (_proposedActivePPSOracle, _activePPSOracleEffectiveTime); |
| 931 | } |
| 932 | |
| 933 | /// @inheritdoc ISuperGovernor |
| 934 | function getPPSOracleQuorum() external view returns (uint256) { |
| 935 | return _activePPSOracleQuorum; |
| 936 | } |
| 937 | |
| 938 | /// @inheritdoc ISuperGovernor |
| 939 | function getActivePPSOracle() external view returns (address) { |
| 940 | if (_activePPSOracle == address(0)) revert NO_ACTIVE_PPS_ORACLE(); |
| 941 | 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 | function getFee(FeeType feeType) external view returns (uint256) { |
| 951 | return _feeValues[feeType]; |
| 952 | } |
| 953 | |
| 954 | /// @inheritdoc ISuperGovernor |
| 955 | function getGasInfo(address oracle_) external view returns (GasInfo memory) { |
| 956 | return _oracleGasInfo[oracle_]; |
| 957 | } |
| 958 | |
| 959 | /// @inheritdoc ISuperGovernor |
| 960 | function getUpkeepCostPerBatchUpdate(address oracle_, uint256 chargeableEntries_) external view returns (uint256) { |
| 961 | // Calculate total gas cost |
| 962 | uint256 totalGas = _oracleGasInfo[oracle_].baseGasBatch + |
| 963 | (_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 | function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime) { |
| 975 | return (_proposedMinStaleness, _minStalenesEffectiveTime); |
| 976 | } |
| 977 | |
| 978 | /// @inheritdoc ISuperGovernor |
| 979 | function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32) { |
| 980 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 981 | return superBankHooksMerkleRoots[hook].currentRoot; |
| 982 | } |
| 983 | |
| 984 | /// @inheritdoc ISuperGovernor |
| 985 | function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32) { |
| 986 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 987 | return vaultBankHooksMerkleRoots[hook].currentRoot; |
| 988 | } |
| 989 | |
| 990 | /// @inheritdoc ISuperGovernor |
| 991 | function getProposedSuperBankHookMerkleRoot(address hook) |
| 992 | external |
| 993 | view |
| 994 | returns (bytes32 proposedRoot, uint256 effectiveTime) |
| 995 | { |
| 996 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 997 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 998 | return (data.proposedRoot, data.effectiveTime); |
| 999 | } |
| 1000 | |
| 1001 | /// @inheritdoc ISuperGovernor |
| 1002 | function getProposedVaultBankHookMerkleRoot(address hook) |
| 1003 | external |
| 1004 | view |
| 1005 | returns (bytes32 proposedRoot, uint256 effectiveTime) |
| 1006 | { |
| 1007 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 1008 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 1009 | return (data.proposedRoot, data.effectiveTime); |
| 1010 | } |
| 1011 | |
| 1012 | /// @inheritdoc ISuperGovernor |
| 1013 | function getProver() external view returns (address) { |
| 1014 | return _prover; |
| 1015 | } |
| 1016 | |
| 1017 | /// @inheritdoc ISuperGovernor |
| 1018 | function isUpkeepPaymentsEnabled() external view returns (bool enabled) { |
| 1019 | return _upkeepPaymentsEnabled; |
| 1020 | } |
| 1021 | |
| 1022 | /// @inheritdoc ISuperGovernor |
| 1023 | function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime) { |
| 1024 | return (_proposedUpkeepPaymentsEnabled, _upkeepPaymentsChangeEffectiveTime); |
| 1025 | } |
| 1026 | |
| 1027 | /// @inheritdoc ISuperGovernor |
| 1028 | function isSuperformManager(address manager) external view returns (bool isSuperform) { |
| 1029 | return _superformManagers.contains(manager); |
| 1030 | } |
| 1031 | |
| 1032 | /// @inheritdoc ISuperGovernor |
| 1033 | function getAllSuperformManagers() external view returns (address[] memory managers) { |
| 1034 | return _superformManagers.values(); |
| 1035 | } |
| 1036 | |
| 1037 | /// @inheritdoc ISuperGovernor |
| 1038 | function getManagersPaginated( |
| 1039 | uint256 cursor, |
| 1040 | uint256 limit |
| 1041 | ) |
| 1042 | external |
| 1043 | view |
| 1044 | returns (address[] memory chunkOfManagers, uint256 next) |
| 1045 | { |
| 1046 | uint256 len = _superformManagers.length(); |
| 1047 | |
| 1048 | // clamp limit so we don’t read past end |
| 1049 | uint256 realLimit = limit; |
| 1050 | // If cursor is beyond the end, return empty array |
| 1051 | if (cursor >= len) { |
| 1052 | return (new address[](0), 0); |
| 1053 | } |
| 1054 | |
| 1055 | uint256 remaining = len - cursor; |
| 1056 | if (realLimit > remaining) realLimit = remaining; |
| 1057 | |
| 1058 | chunkOfManagers = new address[](realLimit); |
| 1059 | for (uint256 i; i < realLimit; ++i) { |
| 1060 | chunkOfManagers[i] = _superformManagers.at(cursor + i); |
| 1061 | } |
| 1062 | |
| 1063 | next = (cursor + realLimit < len) ? cursor + realLimit : 0; |
| 1064 | } |
| 1065 | |
| 1066 | /// @inheritdoc ISuperGovernor |
| 1067 | function getSuperformManagersCount() external view returns (uint256) { |
| 1068 | return _superformManagers.length(); |
| 1069 | } |
| 1070 | |
| 1071 | /// @inheritdoc ISuperGovernor |
| 1072 | function isWhitelistedIncentiveToken(address token) external view returns (bool) { |
| 1073 | return _isWhitelistedIncentiveToken[token]; |
| 1074 | } |
| 1075 | |
| 1076 | /// @inheritdoc ISuperGovernor |
| 1077 | function registerProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) { |
| 1078 | if (keeper == address(0)) revert INVALID_ADDRESS(); |
| 1079 | if (_protectedKeepers.contains(keeper)) revert KEEPER_ALREADY_REGISTERED(); |
| 1080 | |
| 1081 | _protectedKeepers.add(keeper); |
| 1082 | emit ProtectedKeeperRegistered(keeper); |
| 1083 | } |
| 1084 | |
| 1085 | /// @inheritdoc ISuperGovernor |
| 1086 | function unregisterProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) { |
| 1087 | if (!_protectedKeepers.contains(keeper)) revert KEEPER_NOT_REGISTERED(); |
| 1088 | |
| 1089 | _protectedKeepers.remove(keeper); |
| 1090 | emit ProtectedKeeperUnregistered(keeper); |
| 1091 | } |
| 1092 | |
| 1093 | /// @inheritdoc ISuperGovernor |
| 1094 | function isProtectedKeeper(address keeper) external view returns (bool) { |
| 1095 | return _protectedKeepers.contains(keeper); |
| 1096 | } |
| 1097 | |
| 1098 | /// @inheritdoc ISuperGovernor |
| 1099 | function getProtectedKeepers() external view returns (address[] memory) { |
| 1100 | return _protectedKeepers.values(); |
| 1101 | } |
| 1102 | |
| 1103 | /// @inheritdoc ISuperGovernor |
| 1104 | function getProtectedKeepersCount() external view returns (uint256) { |
| 1105 | 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 | if (oracle == address(0)) revert SUPER_ORACLE_NOT_FOUND(); |
| 1114 | address upToken = _addressRegistry[UP]; |
| 1115 | if (upToken == address(0)) revert UP_NOT_FOUND(); |
| 1116 | |
| 1117 | // Step 1: convert gas to ETH |
| 1118 | (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 | (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 | (uint256 upPerUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
| 1135 | 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 | uint256 requiredUpTokens = Math.mulDiv(ethToUsd, 1e18, upPerUsd, Math.Rounding.Ceil); |
| 1144 | return requiredUpTokens; |
| 1145 | } |
| 1146 | } |
| 1147 |
86.0%
src/SuperVault/SuperVault.sol
Lines covered: 167 / 192 (86.0%)
| 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 | 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 | bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH = |
| 57 | keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)"); |
| 58 | |
| 59 | /*////////////////////////////////////////////////////////////// |
| 60 | STATE |
| 61 | //////////////////////////////////////////////////////////////*/ |
| 62 | address public share; |
| 63 | IERC20 private _asset; |
| 64 | uint8 private _underlyingDecimals; |
| 65 | ISuperVaultStrategy public strategy; |
| 66 | address public escrow; |
| 67 | uint256 public PRECISION; |
| 68 | |
| 69 | // Core contracts |
| 70 | ISuperGovernor public immutable superGovernor; |
| 71 | |
| 72 | /// @inheritdoc IERC7540Operator |
| 73 | 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 | 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 | 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 | __EIP712_init(name_, "1"); |
| 118 | |
| 119 | // Set asset and precision |
| 120 | _asset = IERC20(asset_); |
| 121 | (bool success, uint8 assetDecimals) = asset_.tryGetAssetDecimals(); |
| 122 | if (!success) revert INVALID_ASSET(); |
| 123 | _underlyingDecimals = assetDecimals; |
| 124 | PRECISION = 10 ** _underlyingDecimals; |
| 125 | share = address(this); |
| 126 | strategy = ISuperVaultStrategy(strategy_); |
| 127 | escrow = escrow_; |
| 128 | } |
| 129 | |
| 130 | /*////////////////////////////////////////////////////////////// |
| 131 | ERC20 OVERRIDES |
| 132 | //////////////////////////////////////////////////////////////*/ |
| 133 | |
| 134 | /*////////////////////////////////////////////////////////////// |
| 135 | USER EXTERNAL FUNCTIONS |
| 136 | //////////////////////////////////////////////////////////////*/ |
| 137 | |
| 138 | /// @inheritdoc IERC4626 |
| 139 | function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) { |
| 140 | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 141 | if (assets == 0) revert ZERO_AMOUNT(); |
| 142 | |
| 143 | // Forward assets from msg-sender to strategy |
| 144 | _asset.safeTransferFrom(msg.sender, address(strategy), assets); |
| 145 | |
| 146 | // Single executor call: strategy skims entry fee, accounts on NET, returns net shares |
| 147 | shares = strategy.handleOperations4626Deposit(receiver, assets); |
| 148 | if (shares == 0) revert ZERO_AMOUNT(); |
| 149 | |
| 150 | // Mint the net shares |
| 151 | _mint(receiver, shares); |
| 152 | |
| 153 | emit Deposit(msg.sender, receiver, assets, shares); |
| 154 | } |
| 155 | |
| 156 | /// @inheritdoc IERC4626 |
| 157 | function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) { |
| 158 | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 159 | if (shares == 0) revert ZERO_AMOUNT(); |
| 160 | |
| 161 | uint256 assetsNet; |
| 162 | (assets, assetsNet) = strategy.quoteMintAssetsGross(shares); |
| 163 | |
| 164 | // Forward quoted gross assets from msg-sender to strategy |
| 165 | _asset.safeTransferFrom(msg.sender, address(strategy), assets); |
| 166 | |
| 167 | // Single executor call: strategy handles fees and accounts on NET |
| 168 | strategy.handleOperations4626Mint(receiver, shares, assets, assetsNet); |
| 169 | |
| 170 | // Mint the exact shares asked |
| 171 | _mint(receiver, shares); |
| 172 | |
| 173 | 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 | function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256) { |
| 179 | if (shares == 0) revert ZERO_AMOUNT(); |
| 180 | if (owner == address(0) || controller == address(0)) revert ZERO_ADDRESS(); |
| 181 | if (owner != msg.sender && !isOperator[owner][msg.sender]) revert INVALID_OWNER_OR_OPERATOR(); |
| 182 | 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 | _approve(owner, escrow, shares); |
| 189 | ISuperVaultEscrow(escrow).escrowShares(owner, shares); |
| 190 | |
| 191 | // Forward to strategy (7540 path) |
| 192 | strategy.handleOperations7540(ISuperVaultStrategy.Operation.RedeemRequest, controller, address(0), shares); |
| 193 | |
| 194 | emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares); |
| 195 | return REQUEST_ID; |
| 196 | } |
| 197 | |
| 198 | /// @inheritdoc ISuperVault |
| 199 | function cancelRedeem(address controller) external { |
| 200 | _validateController(controller); |
| 201 | |
| 202 | uint256 shares = strategy.pendingRedeemRequest(controller); |
| 203 | |
| 204 | // Forward to strategy (7540 path) |
| 205 | strategy.handleOperations7540(ISuperVaultStrategy.Operation.CancelRedeem, controller, address(0), 0); |
| 206 | |
| 207 | // Return shares to controller |
| 208 | ISuperVaultEscrow(escrow).returnShares(controller, shares); |
| 209 | |
| 210 | emit RedeemRequestCancelled(controller, msg.sender); |
| 211 | } |
| 212 | |
| 213 | /// @inheritdoc IERC7540Operator |
| 214 | function setOperator(address operator, bool approved) external returns (bool success) { |
| 215 | if (msg.sender == operator) revert UNAUTHORIZED(); |
| 216 | isOperator[msg.sender][operator] = approved; |
| 217 | emit OperatorSet(msg.sender, operator, approved); |
| 218 | return true; |
| 219 | } |
| 220 | |
| 221 | /// @inheritdoc IERC7741 |
| 222 | 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 | returns (bool) |
| 232 | { |
| 233 | if (controller == operator) revert UNAUTHORIZED(); |
| 234 | if (block.timestamp > deadline) revert DEADLINE_PASSED(); |
| 235 | if (_authorizations[controller][nonce]) revert UNAUTHORIZED(); |
| 236 | |
| 237 | _authorizations[controller][nonce] = true; |
| 238 | |
| 239 | bytes32 structHash = |
| 240 | keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline)); |
| 241 | bytes32 digest = _hashTypedDataV4(structHash); |
| 242 | |
| 243 | if (!_isValidSignature(controller, digest, signature)) revert INVALID_SIGNATURE(); |
| 244 | |
| 245 | isOperator[controller][operator] = approved; |
| 246 | emit OperatorSet(controller, operator, approved); |
| 247 | |
| 248 | return true; |
| 249 | } |
| 250 | |
| 251 | /*////////////////////////////////////////////////////////////// |
| 252 | USER EXTERNAL VIEW FUNCTIONS |
| 253 | //////////////////////////////////////////////////////////////*/ |
| 254 | |
| 255 | //--ERC7540-- |
| 256 | |
| 257 | /// @inheritdoc IERC7540Redeem |
| 258 | function pendingRedeemRequest( |
| 259 | uint256, /*requestId*/ |
| 260 | address controller |
| 261 | ) |
| 262 | external |
| 263 | view |
| 264 | returns (uint256 pendingShares) |
| 265 | { |
| 266 | return strategy.pendingRedeemRequest(controller); |
| 267 | } |
| 268 | |
| 269 | /// @inheritdoc IERC7540Redeem |
| 270 | 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 | function authorizations(address controller, bytes32 nonce) external view returns (bool used) { |
| 285 | 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 | function invalidateNonce(bytes32 nonce) external { |
| 295 | if (_authorizations[msg.sender][nonce]) revert INVALID_NONCE(); |
| 296 | _authorizations[msg.sender][nonce] = true; |
| 297 | |
| 298 | emit NonceInvalidated(msg.sender, nonce); |
| 299 | } |
| 300 | |
| 301 | /*////////////////////////////////////////////////////////////// |
| 302 | ERC4626 IMPLEMENTATION |
| 303 | //////////////////////////////////////////////////////////////*/ |
| 304 | function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) { |
| 305 | return _underlyingDecimals; |
| 306 | } |
| 307 | |
| 308 | function asset() public view virtual override returns (address) { |
| 309 | return address(_asset); |
| 310 | } |
| 311 | |
| 312 | /// @inheritdoc IERC4626 |
| 313 | function totalAssets() external view override returns (uint256) { |
| 314 | uint256 supply = totalSupply(); |
| 315 | if (supply == 0) return 0; |
| 316 | uint256 currentPPS = _getStoredPPS(); |
| 317 | return Math.mulDiv(supply, currentPPS, PRECISION, Math.Rounding.Floor); |
| 318 | } |
| 319 | |
| 320 | /// @inheritdoc IERC4626 |
| 321 | 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 | function convertToAssets(uint256 shares) public view override returns (uint256) { |
| 329 | uint256 currentPPS = _getStoredPPS(); |
| 330 | if (currentPPS == 0) return 0; |
| 331 | return Math.mulDiv(shares, currentPPS, PRECISION, Math.Rounding.Floor); |
| 332 | } |
| 333 | |
| 334 | /// @inheritdoc IERC4626 |
| 335 | function maxDeposit(address) public view override returns (uint256) { |
| 336 | 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 | function maxWithdraw(address owner) public view override returns (uint256) { |
| 348 | return strategy.claimableWithdraw(owner); |
| 349 | } |
| 350 | |
| 351 | /// @inheritdoc IERC4626 |
| 352 | function maxRedeem(address owner) public view override returns (uint256) { |
| 353 | uint256 withdrawPrice = strategy.getAverageWithdrawPrice(owner); |
| 354 | if (withdrawPrice == 0) return 0; |
| 355 | return maxWithdraw(owner).mulDiv(PRECISION, withdrawPrice, Math.Rounding.Floor); |
| 356 | } |
| 357 | |
| 358 | /// @inheritdoc IERC4626 |
| 359 | function previewDeposit(uint256 assets) public view override returns (uint256) { |
| 360 | uint256 pps = _getStoredPPS(); |
| 361 | if (pps == 0) return 0; |
| 362 | |
| 363 | (uint256 feeBps,) = _getManagementFeeConfig(); |
| 364 | |
| 365 | if (feeBps == 0) return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor); |
| 366 | // fee-on-gross: fee = ceil(gross * feeBps / BPS) |
| 367 | uint256 fee = Math.mulDiv(assets, feeBps, BPS_PRECISION, Math.Rounding.Ceil); |
| 368 | |
| 369 | uint256 assetsNet = assets - fee; |
| 370 | return Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
| 371 | } |
| 372 | |
| 373 | /// @inheritdoc IERC4626 |
| 374 | function previewMint(uint256 shares) public view override returns (uint256) { |
| 375 | uint256 pps = _getStoredPPS(); |
| 376 | if (pps == 0) return 0; |
| 377 | |
| 378 | uint256 assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
| 379 | |
| 380 | (uint256 feeBps,) = _getManagementFeeConfig(); |
| 381 | if (feeBps == 0) return assetsNet; |
| 382 | if (feeBps >= BPS_PRECISION) return 0; // impossible to mint (would require infinite gross) |
| 383 | |
| 384 | return Math.mulDiv(assetsNet, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil); |
| 385 | } |
| 386 | |
| 387 | /// @inheritdoc IERC4626 |
| 388 | function previewWithdraw(uint256 /* assets*/ ) public pure override returns (uint256) { |
| 389 | 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 | function withdraw( |
| 399 | uint256 assets, |
| 400 | address receiver, |
| 401 | address controller |
| 402 | ) |
| 403 | public |
| 404 | override |
| 405 | nonReentrant |
| 406 | returns (uint256 shares) |
| 407 | { |
| 408 | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 409 | _validateController(controller); |
| 410 | |
| 411 | uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
| 412 | 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 | shares = assets.mulDiv(PRECISION, averageWithdrawPrice, Math.Rounding.Floor); |
| 419 | |
| 420 | // Take assets from strategy (7540 path) |
| 421 | strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
| 422 | |
| 423 | emit Withdraw(msg.sender, receiver, controller, assets, shares); |
| 424 | } |
| 425 | |
| 426 | /// @inheritdoc IERC4626 |
| 427 | function redeem( |
| 428 | uint256 shares, |
| 429 | address receiver, |
| 430 | address controller |
| 431 | ) |
| 432 | public |
| 433 | override |
| 434 | nonReentrant |
| 435 | returns (uint256 assets) |
| 436 | { |
| 437 | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 438 | _validateController(controller); |
| 439 | |
| 440 | uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
| 441 | if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE(); |
| 442 | |
| 443 | // Calculate assets based on shares and average withdraw price |
| 444 | 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 | strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
| 451 | |
| 452 | emit Withdraw(msg.sender, receiver, controller, assets, shares); |
| 453 | } |
| 454 | |
| 455 | // @inheritdoc ISuperVault |
| 456 | function burnShares(uint256 amount) external { |
| 457 | if (msg.sender != address(strategy)) revert UNAUTHORIZED(); |
| 458 | _burn(escrow, amount); |
| 459 | } |
| 460 | |
| 461 | // @inheritdoc ISuperVault |
| 462 | 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 | emit RedeemClaimable( |
| 474 | user, REQUEST_ID, assets, shares, averageWithdrawPrice, accumulatorShares, accumulatorCostBasis |
| 475 | ); |
| 476 | } |
| 477 | |
| 478 | /*////////////////////////////////////////////////////////////// |
| 479 | ERC165 INTERFACE |
| 480 | //////////////////////////////////////////////////////////////*/ |
| 481 | function supportsInterface(bytes4 interfaceId) public pure returns (bool) { |
| 482 | return interfaceId == type(IERC7540Redeem).interfaceId || interfaceId == type(IERC165).interfaceId |
| 483 | || interfaceId == type(IERC7741).interfaceId || interfaceId == type(IERC4626).interfaceId |
| 484 | || 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 | 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 | function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal pure returns (bool) { |
| 501 | address recoveredSigner = ECDSA.recover(digest, signature); |
| 502 | 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 | 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 | if (shares > 0) { |
| 515 | strategy.moveAccumulatorOnTransfer(from, to, shares); |
| 516 | } |
| 517 | } |
| 518 | super._update(from, to, value); |
| 519 | } |
| 520 | |
| 521 | function _getStoredPPS() internal view returns (uint256) { |
| 522 | 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 | address aggregatorAddress = superGovernor.getAddress(superGovernor.SUPER_VAULT_AGGREGATOR()); |
| 530 | return ISuperVaultAggregator(aggregatorAddress).isStrategyPaused(address(strategy)); |
| 531 | } |
| 532 | |
| 533 | /// @dev Read management fee config (view-only for previews) |
| 534 | function _getManagementFeeConfig() internal view returns (uint256 feeBps, address recipient) { |
| 535 | ISuperVaultStrategy.FeeConfig memory cfg = strategy.getConfigInfo(); |
| 536 | return (cfg.managementFeeBps, cfg.recipient); |
| 537 | } |
| 538 | } |
| 539 |
50.0%
src/SuperVault/SuperVaultAggregator.sol
Lines covered: 315 / 621 (50.0%)
| 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 | 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 | address public immutable VAULT_IMPLEMENTATION; |
| 37 | address public immutable STRATEGY_IMPLEMENTATION; |
| 38 | address public immutable ESCROW_IMPLEMENTATION; |
| 39 | |
| 40 | // Governance |
| 41 | ISuperGovernor public immutable SUPER_GOVERNOR; |
| 42 | |
| 43 | // Claimable upkeep |
| 44 | 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 | 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 | if (!SUPER_GOVERNOR.isActivePPSOracle(msg.sender)) { |
| 88 | 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 | if (!_superVaultStrategies.contains(strategy)) |
| 96 | 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 | 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 | function createVault( |
| 130 | VaultCreationParams calldata params |
| 131 | ) external returns (address superVault, address strategy, address escrow) { |
| 132 | // Input validation |
| 133 | if ( |
| 134 | params.asset == address(0) || |
| 135 | params.mainManager == address(0) || |
| 136 | params.feeConfig.recipient == address(0) |
| 137 | ) { |
| 138 | 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 | vars.currentNonce = _vaultCreationNonce++; |
| 146 | vars.salt = keccak256( |
| 147 | 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 | superVault = VAULT_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 158 | escrow = ESCROW_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 159 | strategy = STRATEGY_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 160 | |
| 161 | // Initialize superVault |
| 162 | SuperVault(superVault).initialize( |
| 163 | params.asset, |
| 164 | params.name, |
| 165 | params.symbol, |
| 166 | strategy, |
| 167 | escrow |
| 168 | ); |
| 169 | |
| 170 | // Initialize escrow |
| 171 | SuperVaultEscrow(escrow).initialize(superVault, strategy); |
| 172 | |
| 173 | // Initialize strategy |
| 174 | SuperVaultStrategy(payable(strategy)).initialize( |
| 175 | superVault, |
| 176 | params.feeConfig |
| 177 | ); |
| 178 | |
| 179 | // Store vault trio in registry |
| 180 | _superVaults.add(superVault); |
| 181 | _superVaultStrategies.add(strategy); |
| 182 | _superVaultEscrows.add(escrow); |
| 183 | |
| 184 | // Get asset decimals |
| 185 | (bool success, uint8 assetDecimals) = params |
| 186 | .asset |
| 187 | .tryGetAssetDecimals(); |
| 188 | if (!success) revert INVALID_ASSET(); |
| 189 | vars.initialPPS = 10 ** assetDecimals; // 1.0 as initial PPS |
| 190 | |
| 191 | // Validate maxStaleness against minimum required staleness |
| 192 | if (params.maxStaleness < SUPER_GOVERNOR.getMinStaleness()) { |
| 193 | revert MAX_STALENESS_TOO_LOW(); |
| 194 | } |
| 195 | |
| 196 | // Initialize StrategyData individually to avoid mapping assignment issues |
| 197 | _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 | _strategyData[strategy].mainManager = params.mainManager; |
| 204 | |
| 205 | uint256 secondaryLen = params.secondaryManagers.length; |
| 206 | for (uint256 i; i < secondaryLen; ++i) { |
| 207 | _strategyData[strategy].secondaryManagers.add( |
| 208 | params.secondaryManagers[i] |
| 209 | ); |
| 210 | } |
| 211 | if ( |
| 212 | _strategyData[strategy].secondaryManagers.length() >= |
| 213 | MAX_SECONDARY_MANAGERS |
| 214 | ) { |
| 215 | revert TOO_MANY_SECONDARY_MANAGERS(); |
| 216 | } |
| 217 | |
| 218 | // Set default threshold values |
| 219 | _strategyData[strategy].dispersionThreshold = type(uint256).max; // Default: max (disabled) |
| 220 | _strategyData[strategy].deviationThreshold = type(uint256).max; // Default: max (disabled) |
| 221 | |
| 222 | emit VaultDeployed( |
| 223 | superVault, |
| 224 | strategy, |
| 225 | escrow, |
| 226 | params.asset, |
| 227 | params.name, |
| 228 | params.symbol, |
| 229 | vars.currentNonce |
| 230 | ); |
| 231 | emit PPSUpdated( |
| 232 | strategy, |
| 233 | vars.initialPPS, |
| 234 | 0, |
| 235 | 0, |
| 236 | 0, |
| 237 | _strategyData[strategy].lastUpdateTimestamp |
| 238 | ); |
| 239 | |
| 240 | return (superVault, strategy, escrow); |
| 241 | } |
| 242 | |
| 243 | /*////////////////////////////////////////////////////////////// |
| 244 | PPS UPDATE FUNCTIONS |
| 245 | //////////////////////////////////////////////////////////////*/ |
| 246 | /// @inheritdoc ISuperVaultAggregator |
| 247 | 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 | strategiesLength != args.ppss.length || |
| 255 | strategiesLength != args.ppsStdevs.length || |
| 256 | strategiesLength != args.validatorSets.length || |
| 257 | strategiesLength != args.timestamps.length || |
| 258 | strategiesLength != args.totalValidators.length |
| 259 | ) revert ARRAY_LENGTH_MISMATCH(); |
| 260 | |
| 261 | bool paymentsEnabled = SUPER_GOVERNOR.isUpkeepPaymentsEnabled(); |
| 262 | uint256 chargeableCount; |
| 263 | if (paymentsEnabled) { |
| 264 | for (uint256 i; i < strategiesLength; ++i) { |
| 265 | // Skip invalid strategies without reverting |
| 266 | if (!_superVaultStrategies.contains(args.strategies[i])) { |
| 267 | emit UnknownStrategy(args.strategies[i]); |
| 268 | continue; |
| 269 | } |
| 270 | |
| 271 | // Skip when invalid timestamp is provided |
| 272 | if (args.timestamps[i] > block.timestamp) { |
| 273 | emit ProvidedTimestampExceedsBlockTimestamp( |
| 274 | args.strategies[i], |
| 275 | args.timestamps[i], |
| 276 | block.timestamp |
| 277 | ); |
| 278 | continue; |
| 279 | } |
| 280 | |
| 281 | // Skip Superform manager |
| 282 | address manager = _strategyData[args.strategies[i]].mainManager; |
| 283 | if (SUPER_GOVERNOR.isSuperformManager(manager)) { |
| 284 | emit SuperformManager(args.strategies[i], manager); |
| 285 | 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 | _strategyData[args.strategies[i]] |
| 294 | .authorizedCallers |
| 295 | .contains(args.updateAuthority) |
| 296 | ) { |
| 297 | emit AuthorizedCaller( |
| 298 | args.strategies[i], |
| 299 | args.updateAuthority |
| 300 | ); |
| 301 | continue; |
| 302 | } |
| 303 | |
| 304 | // Count only non-stale entries as chargeable |
| 305 | if ( |
| 306 | block.timestamp - args.timestamps[i] <= |
| 307 | _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 | ? SUPER_GOVERNOR.getUpkeepCostPerBatchUpdate( |
| 317 | msg.sender, |
| 318 | chargeableCount |
| 319 | ) |
| 320 | : 0; |
| 321 | |
| 322 | // Compute per-entry charge |
| 323 | uint256 perEntry = 0; |
| 324 | if (paymentsEnabled && chargeableCount > 0) { |
| 325 | perEntry = totalCost / chargeableCount; |
| 326 | } |
| 327 | |
| 328 | // Process all valid strategies |
| 329 | for (uint256 i; i < strategiesLength; ++i) { |
| 330 | // Skip invalid strategies without reverting |
| 331 | if (!_superVaultStrategies.contains(args.strategies[i])) continue; |
| 332 | |
| 333 | // Skip when invalid timestamp is provided (future timestamp) |
| 334 | if (args.timestamps[i] > block.timestamp) { |
| 335 | emit ProvidedTimestampExceedsBlockTimestamp( |
| 336 | args.strategies[i], |
| 337 | 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 | block.timestamp - args.timestamps[i] > |
| 349 | _strategyData[args.strategies[i]].maxStaleness |
| 350 | ) { |
| 351 | upkeepCost = 0; |
| 352 | emit StaleUpdate( |
| 353 | args.strategies[i], |
| 354 | args.updateAuthority, |
| 355 | args.timestamps[i] |
| 356 | ); |
| 357 | } else { |
| 358 | address manager = _strategyData[args.strategies[i]] |
| 359 | .mainManager; |
| 360 | if ( |
| 361 | SUPER_GOVERNOR.isSuperformManager(manager) || |
| 362 | _strategyData[args.strategies[i]] |
| 363 | .authorizedCallers |
| 364 | .contains(args.updateAuthority) |
| 365 | ) { |
| 366 | upkeepCost = 0; |
| 367 | } else { |
| 368 | // Split the total batch cost fairly across chargeable entries |
| 369 | upkeepCost = perEntry; |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | // Forward update |
| 375 | _forwardPPS( |
| 376 | PPSUpdateData({ |
| 377 | strategy: args.strategies[i], |
| 378 | isExempt: (!paymentsEnabled) || (upkeepCost == 0), // If payments are disabled or the update is exempt from UP payments |
| 379 | pps: args.ppss[i], |
| 380 | ppsStdev: args.ppsStdevs[i], |
| 381 | validatorSet: args.validatorSets[i], |
| 382 | totalValidators: args.totalValidators[i], |
| 383 | timestamp: args.timestamps[i], |
| 384 | upkeepCost: upkeepCost |
| 385 | }) |
| 386 | ); |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | /*////////////////////////////////////////////////////////////// |
| 391 | UPKEEP MANAGEMENT |
| 392 | //////////////////////////////////////////////////////////////*/ |
| 393 | /// @inheritdoc ISuperVaultAggregator |
| 394 | function depositUpkeep(address manager, uint256 amount) external { |
| 395 | if (amount == 0) revert ZERO_AMOUNT(); |
| 396 | |
| 397 | // Get the UP token address from SUPER_GOVERNOR |
| 398 | 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 | _managerUpkeepBalance[manager] += amount; |
| 405 | |
| 406 | emit UpkeepDeposited(manager, amount); |
| 407 | } |
| 408 | |
| 409 | /// @inheritdoc ISuperVaultAggregator |
| 410 | function claimUpkeep(uint256 amount) external { |
| 411 | // Only SUPER_GOVERNOR can claim upkeep |
| 412 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 413 | revert CALLER_NOT_AUTHORIZED(); |
| 414 | } |
| 415 | |
| 416 | if (claimableUpkeep < amount) revert INSUFFICIENT_UPKEEP(); |
| 417 | claimableUpkeep -= amount; |
| 418 | |
| 419 | // Get the UP token address from SUPER_GOVERNOR |
| 420 | 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 | function withdrawUpkeep(uint256 amount) external { |
| 430 | if (amount == 0) revert ZERO_AMOUNT(); |
| 431 | |
| 432 | // Check sufficient balance |
| 433 | if (_managerUpkeepBalance[msg.sender] < amount) { |
| 434 | revert INSUFFICIENT_UPKEEP_BALANCE(); |
| 435 | } |
| 436 | |
| 437 | // Get the UP token address from SUPER_GOVERNOR |
| 438 | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 439 | |
| 440 | // Update upkeep balance |
| 441 | unchecked { |
| 442 | _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 | function depositStake(address manager, uint256 amount) external { |
| 458 | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 459 | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 460 | |
| 461 | // Get the UP token address from SUPER_GOVERNOR |
| 462 | 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 | _managerStakeBalance[manager] += amount; |
| 469 | |
| 470 | 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 | function withdrawStake(uint256 amount) external { |
| 476 | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 477 | |
| 478 | // Check sufficient balance |
| 479 | if (_managerStakeBalance[msg.sender] < amount) { |
| 480 | revert INSUFFICIENT_STAKE_BALANCE(); |
| 481 | } |
| 482 | |
| 483 | // Get the UP token address from SUPER_GOVERNOR |
| 484 | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 485 | |
| 486 | // Update stake balance |
| 487 | unchecked { |
| 488 | _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 | function slashStake(address manager, uint256 amount) external { |
| 501 | // Only SUPER_GOVERNOR can slash stake |
| 502 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 503 | revert CALLER_NOT_AUTHORIZED(); |
| 504 | } |
| 505 | |
| 506 | // Validate inputs |
| 507 | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 508 | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 509 | |
| 510 | // Check if manager has sufficient stake balance to slash |
| 511 | if (_managerStakeBalance[manager] < amount) { |
| 512 | revert INSUFFICIENT_STAKE_BALANCE(); |
| 513 | } |
| 514 | |
| 515 | // Reduce manager's stake balance |
| 516 | _managerStakeBalance[manager] -= amount; |
| 517 | |
| 518 | // Get the UP token address and SuperBank address |
| 519 | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 520 | address superBank = _getSuperBank(); |
| 521 | |
| 522 | // Transfer slashed amount directly to SuperBank |
| 523 | IERC20(upToken).safeTransfer(superBank, amount); |
| 524 | |
| 525 | // Emit event for transparency |
| 526 | emit StakeSlashed(manager, amount); |
| 527 | } |
| 528 | |
| 529 | /*////////////////////////////////////////////////////////////// |
| 530 | AUTHORIZED CALLER MANAGEMENT |
| 531 | //////////////////////////////////////////////////////////////*/ |
| 532 | /// @inheritdoc ISuperVaultAggregator |
| 533 | 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 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 540 | |
| 541 | if (caller == address(0)) revert ZERO_ADDRESS(); |
| 542 | |
| 543 | // Prevent managers from adding protected keepers to circumvent fees |
| 544 | if (SUPER_GOVERNOR.isProtectedKeeper(caller)) { |
| 545 | revert CANNOT_ADD_PROTECTED_KEEPER(); |
| 546 | } |
| 547 | |
| 548 | // Check if caller is already authorized and add if not |
| 549 | if (!_strategyData[strategy].authorizedCallers.add(caller)) { |
| 550 | revert CALLER_ALREADY_AUTHORIZED(); |
| 551 | } |
| 552 | emit AuthorizedCallerAdded(strategy, caller); |
| 553 | } |
| 554 | |
| 555 | /// @inheritdoc ISuperVaultAggregator |
| 556 | 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 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 563 | |
| 564 | // Remove the caller |
| 565 | if (!_strategyData[strategy].authorizedCallers.remove(caller)) { |
| 566 | revert CALLER_NOT_AUTHORIZED(); |
| 567 | } |
| 568 | emit AuthorizedCallerRemoved(strategy, caller); |
| 569 | } |
| 570 | |
| 571 | /*////////////////////////////////////////////////////////////// |
| 572 | MANAGER MANAGEMENT FUNCTIONS |
| 573 | //////////////////////////////////////////////////////////////*/ |
| 574 | /// @inheritdoc ISuperVaultAggregator |
| 575 | function addSecondaryManager( |
| 576 | address strategy, |
| 577 | address manager |
| 578 | ) external validStrategy(strategy) { |
| 579 | // Only the primary manager can add secondary managers |
| 580 | if (msg.sender != _strategyData[strategy].mainManager) |
| 581 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 582 | |
| 583 | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 584 | |
| 585 | // Check if manager is already the primary manager |
| 586 | if (_strategyData[strategy].mainManager == manager) |
| 587 | revert MANAGER_ALREADY_EXISTS(); |
| 588 | |
| 589 | // Enforce a cap on secondary managers to prevent governance DoS on changePrimaryManager |
| 590 | if ( |
| 591 | _strategyData[strategy].secondaryManagers.length() >= |
| 592 | MAX_SECONDARY_MANAGERS |
| 593 | ) { |
| 594 | revert TOO_MANY_SECONDARY_MANAGERS(); |
| 595 | } |
| 596 | |
| 597 | // Add as secondary manager using EnumerableSet |
| 598 | if (!_strategyData[strategy].secondaryManagers.add(manager)) |
| 599 | revert MANAGER_ALREADY_EXISTS(); |
| 600 | |
| 601 | emit SecondaryManagerAdded(strategy, manager); |
| 602 | } |
| 603 | |
| 604 | /// @inheritdoc ISuperVaultAggregator |
| 605 | function removeSecondaryManager( |
| 606 | address strategy, |
| 607 | address manager |
| 608 | ) external validStrategy(strategy) { |
| 609 | // Only the primary manager can remove secondary managers |
| 610 | if (msg.sender != _strategyData[strategy].mainManager) |
| 611 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 612 | |
| 613 | // Remove the manager using EnumerableSet |
| 614 | if (!_strategyData[strategy].secondaryManagers.remove(manager)) |
| 615 | revert MANAGER_NOT_FOUND(); |
| 616 | |
| 617 | emit SecondaryManagerRemoved(strategy, manager); |
| 618 | } |
| 619 | |
| 620 | /// @inheritdoc ISuperVaultAggregator |
| 621 | 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 | if (msg.sender != _strategyData[strategy].mainManager) { |
| 629 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 630 | } |
| 631 | |
| 632 | // Update the thresholds |
| 633 | _strategyData[strategy].dispersionThreshold = dispersionThreshold_; |
| 634 | _strategyData[strategy].deviationThreshold = deviationThreshold_; |
| 635 | _strategyData[strategy].mnThreshold = mnThreshold_; |
| 636 | |
| 637 | // Emit the event |
| 638 | emit PPSVerificationThresholdsUpdated( |
| 639 | strategy, |
| 640 | dispersionThreshold_, |
| 641 | deviationThreshold_, |
| 642 | mnThreshold_ |
| 643 | ); |
| 644 | } |
| 645 | |
| 646 | /// @inheritdoc ISuperVaultAggregator |
| 647 | function changeGlobalLeavesStatus( |
| 648 | bytes32[] memory leaves, |
| 649 | bool[] memory statuses, |
| 650 | address strategy |
| 651 | ) external validStrategy(strategy) { |
| 652 | // Only the primary manager can change global leaves status |
| 653 | if (msg.sender != _strategyData[strategy].mainManager) { |
| 654 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 655 | } |
| 656 | uint256 leavesLen = leaves.length; |
| 657 | // Check array lengths match |
| 658 | if (leavesLen != statuses.length) { |
| 659 | revert MISMATCHED_ARRAY_LENGTHS(); |
| 660 | } |
| 661 | |
| 662 | // Update banned status for each leaf |
| 663 | for (uint256 i; i < leavesLen; i++) { |
| 664 | _strategyData[strategy].bannedLeaves[leaves[i]] = statuses[i]; |
| 665 | } |
| 666 | |
| 667 | // Emit event |
| 668 | emit GlobalLeavesStatusChanged(strategy, leaves, statuses); |
| 669 | } |
| 670 | |
| 671 | /// @inheritdoc ISuperVaultAggregator |
| 672 | function changePrimaryManager( |
| 673 | address strategy, |
| 674 | address newManager |
| 675 | ) external validStrategy(strategy) { |
| 676 | // Only SuperGovernor can call this |
| 677 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 678 | 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 | 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 | for (uint256 i = 0; i < clearedSecondaryManagers.length; i++) { |
| 703 | _strategyData[strategy].secondaryManagers.remove( |
| 704 | clearedSecondaryManagers[i] |
| 705 | ); |
| 706 | emit SecondaryManagerRemoved(strategy, clearedSecondaryManagers[i]); |
| 707 | } |
| 708 | |
| 709 | // Set the new primary manager |
| 710 | _strategyData[strategy].mainManager = newManager; |
| 711 | |
| 712 | emit PrimaryManagerChanged(strategy, oldManager, newManager); |
| 713 | } |
| 714 | |
| 715 | /// @inheritdoc ISuperVaultAggregator |
| 716 | function proposeChangePrimaryManager( |
| 717 | address strategy, |
| 718 | address newManager |
| 719 | ) external validStrategy(strategy) { |
| 720 | // Only secondary managers can propose changes to the primary manager |
| 721 | if (!_strategyData[strategy].secondaryManagers.contains(msg.sender)) { |
| 722 | 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 | _strategyData[strategy].proposedManager = newManager; |
| 732 | _strategyData[strategy].managerChangeEffectiveTime = effectiveTime; |
| 733 | |
| 734 | emit PrimaryManagerChangeProposed( |
| 735 | strategy, |
| 736 | msg.sender, |
| 737 | newManager, |
| 738 | effectiveTime |
| 739 | ); |
| 740 | } |
| 741 | |
| 742 | /// @inheritdoc ISuperVaultAggregator |
| 743 | function executeChangePrimaryManager( |
| 744 | address strategy |
| 745 | ) external validStrategy(strategy) { |
| 746 | // Check if there is a pending proposal |
| 747 | if (_strategyData[strategy].proposedManager == address(0)) |
| 748 | revert NO_PENDING_MANAGER_CHANGE(); |
| 749 | |
| 750 | // Check if the timelock period has passed |
| 751 | if ( |
| 752 | block.timestamp < _strategyData[strategy].managerChangeEffectiveTime |
| 753 | ) revert TIMELOCK_NOT_EXPIRED(); |
| 754 | |
| 755 | 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 | _strategyData[strategy].secondaryManagers.length() < |
| 764 | MAX_SECONDARY_MANAGERS |
| 765 | ) { |
| 766 | _strategyData[strategy].secondaryManagers.add(oldManager); |
| 767 | } |
| 768 | |
| 769 | // Set the new primary manager |
| 770 | _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 | function setHooksRootUpdateTimelock(uint256 newTimelock) external { |
| 783 | // Only SUPER_GOVERNOR can update the timelock |
| 784 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 785 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 786 | } |
| 787 | |
| 788 | // Update the timelock |
| 789 | _hooksRootUpdateTimelock = newTimelock; |
| 790 | |
| 791 | emit HooksRootUpdateTimelockChanged(newTimelock); |
| 792 | } |
| 793 | |
| 794 | /// @inheritdoc ISuperVaultAggregator |
| 795 | function proposeGlobalHooksRoot(bytes32 newRoot) external { |
| 796 | // Only SUPER_GOVERNOR can update the global hooks root |
| 797 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 798 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 799 | } |
| 800 | |
| 801 | // Set new root with timelock |
| 802 | _proposedGlobalHooksRoot = newRoot; |
| 803 | uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
| 804 | _globalHooksRootEffectiveTime = effectiveTime; |
| 805 | |
| 806 | emit GlobalHooksRootUpdateProposed(newRoot, effectiveTime); |
| 807 | } |
| 808 | |
| 809 | /// @inheritdoc ISuperVaultAggregator |
| 810 | function executeGlobalHooksRootUpdate() external { |
| 811 | bytes32 proposedRoot = _proposedGlobalHooksRoot; |
| 812 | // Ensure there is a pending proposal |
| 813 | if (proposedRoot == bytes32(0)) { |
| 814 | revert NO_PENDING_GLOBAL_ROOT_CHANGE(); |
| 815 | } |
| 816 | |
| 817 | // Check if timelock period has elapsed |
| 818 | if (block.timestamp < _globalHooksRootEffectiveTime) { |
| 819 | revert ROOT_UPDATE_NOT_READY(); |
| 820 | } |
| 821 | |
| 822 | // Update the global hooks root |
| 823 | bytes32 oldRoot = _globalHooksRoot; |
| 824 | _globalHooksRoot = _proposedGlobalHooksRoot; |
| 825 | _globalHooksRootEffectiveTime = 0; |
| 826 | _proposedGlobalHooksRoot = bytes32(0); |
| 827 | |
| 828 | emit GlobalHooksRootUpdated(oldRoot, proposedRoot); |
| 829 | } |
| 830 | |
| 831 | /// @inheritdoc ISuperVaultAggregator |
| 832 | function setGlobalHooksRootVetoStatus(bool vetoed) external { |
| 833 | // Only SuperGovernor can call this |
| 834 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 835 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 836 | } |
| 837 | |
| 838 | // Don't emit event if status doesn't change |
| 839 | if (_globalHooksRootVetoed == vetoed) { |
| 840 | return; |
| 841 | } |
| 842 | |
| 843 | // Update veto status |
| 844 | _globalHooksRootVetoed = vetoed; |
| 845 | |
| 846 | emit GlobalHooksRootVetoStatusChanged(vetoed, _globalHooksRoot); |
| 847 | } |
| 848 | |
| 849 | /// @inheritdoc ISuperVaultAggregator |
| 850 | function proposeStrategyHooksRoot( |
| 851 | address strategy, |
| 852 | bytes32 newRoot |
| 853 | ) external validStrategy(strategy) { |
| 854 | // Only the main manager can propose strategy-specific hooks root |
| 855 | if (_strategyData[strategy].mainManager != msg.sender) { |
| 856 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 857 | } |
| 858 | |
| 859 | // Set proposed root with timelock |
| 860 | _strategyData[strategy].proposedHooksRoot = newRoot; |
| 861 | uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
| 862 | _strategyData[strategy].hooksRootEffectiveTime = effectiveTime; |
| 863 | |
| 864 | emit StrategyHooksRootUpdateProposed( |
| 865 | strategy, |
| 866 | msg.sender, |
| 867 | newRoot, |
| 868 | effectiveTime |
| 869 | ); |
| 870 | } |
| 871 | |
| 872 | /// @inheritdoc ISuperVaultAggregator |
| 873 | function executeStrategyHooksRootUpdate( |
| 874 | address strategy |
| 875 | ) external validStrategy(strategy) { |
| 876 | bytes32 proposedRoot = _strategyData[strategy].proposedHooksRoot; |
| 877 | // Ensure there is a pending proposal |
| 878 | if (proposedRoot == bytes32(0)) { |
| 879 | revert NO_PENDING_MANAGER_CHANGE(); // Reusing error for simplicity |
| 880 | } |
| 881 | |
| 882 | // Check if timelock period has elapsed |
| 883 | if (block.timestamp < _strategyData[strategy].hooksRootEffectiveTime) { |
| 884 | revert ROOT_UPDATE_NOT_READY(); |
| 885 | } |
| 886 | |
| 887 | // Update the strategy's hooks root |
| 888 | bytes32 oldRoot = _strategyData[strategy].managerHooksRoot; |
| 889 | _strategyData[strategy].managerHooksRoot = proposedRoot; |
| 890 | |
| 891 | // Reset proposal state |
| 892 | _strategyData[strategy].proposedHooksRoot = bytes32(0); |
| 893 | _strategyData[strategy].hooksRootEffectiveTime = 0; |
| 894 | |
| 895 | emit StrategyHooksRootUpdated(strategy, oldRoot, proposedRoot); |
| 896 | } |
| 897 | |
| 898 | /// @inheritdoc ISuperVaultAggregator |
| 899 | function setStrategyHooksRootVetoStatus( |
| 900 | address strategy, |
| 901 | bool vetoed |
| 902 | ) external validStrategy(strategy) { |
| 903 | // Only SuperGovernor can call this |
| 904 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 905 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 906 | } |
| 907 | |
| 908 | // Don't emit event if status doesn't change |
| 909 | if (_strategyData[strategy].hooksRootVetoed == vetoed) { |
| 910 | return; |
| 911 | } |
| 912 | |
| 913 | // Update veto status |
| 914 | _strategyData[strategy].hooksRootVetoed = vetoed; |
| 915 | |
| 916 | emit StrategyHooksRootVetoStatusChanged( |
| 917 | strategy, |
| 918 | vetoed, |
| 919 | _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 | function isStrategyHooksRootVetoed( |
| 930 | address strategy |
| 931 | ) external view returns (bool vetoed) { |
| 932 | return _strategyData[strategy].hooksRootVetoed; |
| 933 | } |
| 934 | |
| 935 | /*////////////////////////////////////////////////////////////// |
| 936 | VIEW FUNCTIONS |
| 937 | //////////////////////////////////////////////////////////////*/ |
| 938 | |
| 939 | /// @inheritdoc ISuperVaultAggregator |
| 940 | function getCurrentNonce() external view returns (uint256) { |
| 941 | return _vaultCreationNonce; |
| 942 | } |
| 943 | |
| 944 | /// @inheritdoc ISuperVaultAggregator |
| 945 | function getHooksRootUpdateTimelock() external view returns (uint256) { |
| 946 | return _hooksRootUpdateTimelock; |
| 947 | } |
| 948 | |
| 949 | /// @inheritdoc ISuperVaultAggregator |
| 950 | function getPPS( |
| 951 | address strategy |
| 952 | ) external view validStrategy(strategy) returns (uint256 pps) { |
| 953 | return _strategyData[strategy].pps; |
| 954 | } |
| 955 | |
| 956 | /// @inheritdoc ISuperVaultAggregator |
| 957 | function getPPSWithStdDev( |
| 958 | address strategy |
| 959 | ) |
| 960 | external |
| 961 | view |
| 962 | validStrategy(strategy) |
| 963 | returns (uint256 pps, uint256 ppsStdev) |
| 964 | { |
| 965 | return (_strategyData[strategy].pps, _strategyData[strategy].ppsStdev); |
| 966 | } |
| 967 | |
| 968 | /// @inheritdoc ISuperVaultAggregator |
| 969 | function getLastUpdateTimestamp( |
| 970 | address strategy |
| 971 | ) external view returns (uint256 timestamp) { |
| 972 | return _strategyData[strategy].lastUpdateTimestamp; |
| 973 | } |
| 974 | |
| 975 | /// @inheritdoc ISuperVaultAggregator |
| 976 | function getMinUpdateInterval( |
| 977 | address strategy |
| 978 | ) external view returns (uint256 interval) { |
| 979 | return _strategyData[strategy].minUpdateInterval; |
| 980 | } |
| 981 | |
| 982 | /// @inheritdoc ISuperVaultAggregator |
| 983 | function getMaxStaleness( |
| 984 | address strategy |
| 985 | ) external view returns (uint256 staleness) { |
| 986 | return _strategyData[strategy].maxStaleness; |
| 987 | } |
| 988 | |
| 989 | /// @inheritdoc ISuperVaultAggregator |
| 990 | function getPPSVerificationThresholds( |
| 991 | address strategy |
| 992 | ) |
| 993 | external |
| 994 | view |
| 995 | validStrategy(strategy) |
| 996 | returns ( |
| 997 | uint256 dispersionThreshold, |
| 998 | uint256 deviationThreshold, |
| 999 | uint256 mnThreshold |
| 1000 | ) |
| 1001 | { |
| 1002 | return ( |
| 1003 | _strategyData[strategy].dispersionThreshold, |
| 1004 | _strategyData[strategy].deviationThreshold, |
| 1005 | _strategyData[strategy].mnThreshold |
| 1006 | ); |
| 1007 | } |
| 1008 | |
| 1009 | /// @inheritdoc ISuperVaultAggregator |
| 1010 | function isStrategyPaused( |
| 1011 | address strategy |
| 1012 | ) external view returns (bool isPaused) { |
| 1013 | return _strategyData[strategy].isPaused; |
| 1014 | } |
| 1015 | |
| 1016 | /// @inheritdoc ISuperVaultAggregator |
| 1017 | function getUpkeepBalance( |
| 1018 | address manager |
| 1019 | ) external view returns (uint256 balance) { |
| 1020 | 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 | function getStakeBalance( |
| 1027 | address manager |
| 1028 | ) external view returns (uint256 balance) { |
| 1029 | return _managerStakeBalance[manager]; |
| 1030 | } |
| 1031 | |
| 1032 | /// @inheritdoc ISuperVaultAggregator |
| 1033 | function getAuthorizedCallers( |
| 1034 | address strategy |
| 1035 | ) external view returns (address[] memory callers) { |
| 1036 | return _strategyData[strategy].authorizedCallers.values(); |
| 1037 | } |
| 1038 | |
| 1039 | /// @inheritdoc ISuperVaultAggregator |
| 1040 | function getMainManager( |
| 1041 | address strategy |
| 1042 | ) external view returns (address manager) { |
| 1043 | manager = _strategyData[strategy].mainManager; |
| 1044 | 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 | return _strategyData[strategy].mainManager == manager; |
| 1055 | } |
| 1056 | |
| 1057 | /// @inheritdoc ISuperVaultAggregator |
| 1058 | function getSecondaryManagers( |
| 1059 | address strategy |
| 1060 | ) external view returns (address[] memory) { |
| 1061 | return _strategyData[strategy].secondaryManagers.values(); |
| 1062 | } |
| 1063 | |
| 1064 | /// @inheritdoc ISuperVaultAggregator |
| 1065 | function isSecondaryManager( |
| 1066 | address manager, |
| 1067 | address strategy |
| 1068 | ) external view returns (bool) { |
| 1069 | return _strategyData[strategy].secondaryManagers.contains(manager); |
| 1070 | } |
| 1071 | |
| 1072 | /// @inheritdoc ISuperVaultAggregator |
| 1073 | function isAnyManager( |
| 1074 | address manager, |
| 1075 | address strategy |
| 1076 | ) public view returns (bool) { |
| 1077 | // Check if primary manager |
| 1078 | if (_strategyData[strategy].mainManager == manager) { |
| 1079 | return true; |
| 1080 | } |
| 1081 | |
| 1082 | // Check if secondary manager using EnumerableSet |
| 1083 | return _strategyData[strategy].secondaryManagers.contains(manager); |
| 1084 | } |
| 1085 | |
| 1086 | /// @inheritdoc ISuperVaultAggregator |
| 1087 | function getAllSuperVaults() external view returns (address[] memory) { |
| 1088 | return _superVaults.values(); |
| 1089 | } |
| 1090 | |
| 1091 | /// @inheritdoc ISuperVaultAggregator |
| 1092 | function superVaults(uint256 index) external view returns (address) { |
| 1093 | if (index >= _superVaults.length()) revert INDEX_OUT_OF_BOUNDS(); |
| 1094 | return _superVaults.at(index); |
| 1095 | } |
| 1096 | |
| 1097 | /// @inheritdoc ISuperVaultAggregator |
| 1098 | function getAllSuperVaultStrategies() |
| 1099 | external |
| 1100 | view |
| 1101 | returns (address[] memory) |
| 1102 | { |
| 1103 | return _superVaultStrategies.values(); |
| 1104 | } |
| 1105 | |
| 1106 | /// @inheritdoc ISuperVaultAggregator |
| 1107 | function superVaultStrategies( |
| 1108 | uint256 index |
| 1109 | ) external view returns (address) { |
| 1110 | if (index >= _superVaultStrategies.length()) |
| 1111 | revert INDEX_OUT_OF_BOUNDS(); |
| 1112 | return _superVaultStrategies.at(index); |
| 1113 | } |
| 1114 | |
| 1115 | /// @inheritdoc ISuperVaultAggregator |
| 1116 | function getAllSuperVaultEscrows() |
| 1117 | external |
| 1118 | view |
| 1119 | returns (address[] memory) |
| 1120 | { |
| 1121 | return _superVaultEscrows.values(); |
| 1122 | } |
| 1123 | |
| 1124 | /// @inheritdoc ISuperVaultAggregator |
| 1125 | function superVaultEscrows(uint256 index) external view returns (address) { |
| 1126 | if (index >= _superVaultEscrows.length()) revert INDEX_OUT_OF_BOUNDS(); |
| 1127 | return _superVaultEscrows.at(index); |
| 1128 | } |
| 1129 | |
| 1130 | /// @inheritdoc ISuperVaultAggregator |
| 1131 | function validateHook( |
| 1132 | address strategy, |
| 1133 | ValidateHookArgs calldata args |
| 1134 | ) external view virtual returns (bool isValid) { |
| 1135 | // Cache all state variables in struct |
| 1136 | HookValidationCache memory cache = HookValidationCache({ |
| 1137 | globalHooksRootVetoed: _globalHooksRootVetoed, |
| 1138 | globalHooksRoot: _globalHooksRoot, |
| 1139 | strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
| 1140 | strategyRoot: _strategyData[strategy].managerHooksRoot |
| 1141 | }); |
| 1142 | |
| 1143 | // Early return false if either global or strategy hooks root is vetoed |
| 1144 | if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) { |
| 1145 | return false; |
| 1146 | } |
| 1147 | |
| 1148 | // Try to validate against global root first |
| 1149 | if ( |
| 1150 | _validateSingleHook( |
| 1151 | args.hookAddress, |
| 1152 | args.hookArgs, |
| 1153 | args.globalProof, |
| 1154 | true, |
| 1155 | cache, |
| 1156 | strategy |
| 1157 | ) |
| 1158 | ) { |
| 1159 | return true; |
| 1160 | } |
| 1161 | |
| 1162 | // If global validation fails, try strategy root |
| 1163 | return |
| 1164 | _validateSingleHook( |
| 1165 | args.hookAddress, |
| 1166 | args.hookArgs, |
| 1167 | args.strategyProof, |
| 1168 | false, |
| 1169 | cache, |
| 1170 | strategy |
| 1171 | ); |
| 1172 | } |
| 1173 | |
| 1174 | /// @inheritdoc ISuperVaultAggregator |
| 1175 | function validateHooks( |
| 1176 | address strategy, |
| 1177 | ValidateHookArgs[] calldata argsArray |
| 1178 | ) external view returns (bool[] memory validHooks) { |
| 1179 | uint256 length = argsArray.length; |
| 1180 | |
| 1181 | // Cache all state variables in struct |
| 1182 | HookValidationCache memory cache = HookValidationCache({ |
| 1183 | globalHooksRootVetoed: _globalHooksRootVetoed, |
| 1184 | globalHooksRoot: _globalHooksRoot, |
| 1185 | strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
| 1186 | strategyRoot: _strategyData[strategy].managerHooksRoot |
| 1187 | }); |
| 1188 | |
| 1189 | // Early return all false if either global or strategy hooks root is vetoed |
| 1190 | if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) { |
| 1191 | return new bool[](length); // Array initialized with all false values |
| 1192 | } |
| 1193 | |
| 1194 | // Validate each hook |
| 1195 | validHooks = new bool[](length); |
| 1196 | for (uint256 i; i < length; i++) { |
| 1197 | // Try global root first |
| 1198 | if ( |
| 1199 | _validateSingleHook( |
| 1200 | argsArray[i].hookAddress, |
| 1201 | argsArray[i].hookArgs, |
| 1202 | argsArray[i].globalProof, |
| 1203 | true, |
| 1204 | cache, |
| 1205 | strategy |
| 1206 | ) |
| 1207 | ) { |
| 1208 | validHooks[i] = true; |
| 1209 | } else { |
| 1210 | // Try strategy root |
| 1211 | validHooks[i] = _validateSingleHook( |
| 1212 | argsArray[i].hookAddress, |
| 1213 | argsArray[i].hookArgs, |
| 1214 | argsArray[i].strategyProof, |
| 1215 | false, |
| 1216 | cache, |
| 1217 | strategy |
| 1218 | ); |
| 1219 | } |
| 1220 | // If both conditions fail, validHooks[i] remains false (default value) |
| 1221 | } |
| 1222 | |
| 1223 | return validHooks; |
| 1224 | } |
| 1225 | |
| 1226 | /// @inheritdoc ISuperVaultAggregator |
| 1227 | function getGlobalHooksRoot() external view returns (bytes32 root) { |
| 1228 | return _globalHooksRoot; |
| 1229 | } |
| 1230 | |
| 1231 | /// @inheritdoc ISuperVaultAggregator |
| 1232 | function getProposedGlobalHooksRoot() |
| 1233 | external |
| 1234 | view |
| 1235 | returns (bytes32 root, uint256 effectiveTime) |
| 1236 | { |
| 1237 | 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 | function isGlobalHooksRootActive() external view returns (bool) { |
| 1243 | return |
| 1244 | block.timestamp >= _globalHooksRootEffectiveTime && |
| 1245 | _globalHooksRoot != bytes32(0); |
| 1246 | } |
| 1247 | |
| 1248 | /// @inheritdoc ISuperVaultAggregator |
| 1249 | function getStrategyHooksRoot( |
| 1250 | address strategy |
| 1251 | ) external view returns (bytes32 root) { |
| 1252 | return _strategyData[strategy].managerHooksRoot; |
| 1253 | } |
| 1254 | |
| 1255 | /// @inheritdoc ISuperVaultAggregator |
| 1256 | function getProposedStrategyHooksRoot( |
| 1257 | address strategy |
| 1258 | ) external view returns (bytes32 root, uint256 effectiveTime) { |
| 1259 | return ( |
| 1260 | _strategyData[strategy].proposedHooksRoot, |
| 1261 | _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 | function _forwardPPS(PPSUpdateData memory args) internal { |
| 1271 | // Check rate limiting |
| 1272 | uint256 minInterval = _strategyData[args.strategy].minUpdateInterval; |
| 1273 | uint256 lastUpdate = _strategyData[args.strategy].lastUpdateTimestamp; |
| 1274 | 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 | 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 | _strategyData[args.strategy].dispersionThreshold != |
| 1294 | type(uint256).max && |
| 1295 | args.pps > 0 |
| 1296 | ) { |
| 1297 | // Calculate dispersion as stddev/mean |
| 1298 | uint256 dispersion = (args.ppsStdev * 1e18) / args.pps; // Scaled by 1e18 for precision |
| 1299 | if (dispersion > _strategyData[args.strategy].dispersionThreshold) { |
| 1300 | checksFailed = true; |
| 1301 | 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 | uint256 currentPPS = _strategyData[args.strategy].pps; |
| 1307 | if ( |
| 1308 | _strategyData[args.strategy].deviationThreshold != |
| 1309 | type(uint256).max && |
| 1310 | currentPPS > 0 |
| 1311 | ) { |
| 1312 | // Calculate absolute deviation, scaled by 1e18 |
| 1313 | uint256 absDiff = args.pps > currentPPS |
| 1314 | ? (args.pps - currentPPS) |
| 1315 | : (currentPPS - args.pps); |
| 1316 | uint256 relativeDeviation = (absDiff * 1e18) / currentPPS; |
| 1317 | if ( |
| 1318 | relativeDeviation > |
| 1319 | _strategyData[args.strategy].deviationThreshold |
| 1320 | ) { |
| 1321 | checksFailed = true; |
| 1322 | emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DEVIATION"); |
| 1323 | } |
| 1324 | } |
| 1325 | |
| 1326 | // C2.3) M/N Check: Check if enough validators participated |
| 1327 | if ( |
| 1328 | args.totalValidators > 0 && |
| 1329 | _strategyData[args.strategy].mnThreshold > 0 |
| 1330 | ) { |
| 1331 | // Calculate participation rate, scaled by 1e18 |
| 1332 | uint256 participationRate = (args.validatorSet * 1e18) / |
| 1333 | args.totalValidators; |
| 1334 | if (participationRate < _strategyData[args.strategy].mnThreshold) { |
| 1335 | checksFailed = true; |
| 1336 | emit StrategyCheckFailed( |
| 1337 | args.strategy, |
| 1338 | "INSUFFICIENT_VALIDATOR_PARTICIPATION" |
| 1339 | ); |
| 1340 | } |
| 1341 | } |
| 1342 | |
| 1343 | // Pause strategy if any check failed |
| 1344 | if (checksFailed && !_strategyData[args.strategy].isPaused) { |
| 1345 | _strategyData[args.strategy].isPaused = true; |
| 1346 | emit StrategyPaused(args.strategy); |
| 1347 | } |
| 1348 | // Unpause strategy if all checks passed and strategy was previously paused |
| 1349 | else if (!checksFailed && _strategyData[args.strategy].isPaused) { |
| 1350 | _strategyData[args.strategy].isPaused = false; |
| 1351 | 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 | if (_managerUpkeepBalance[manager] < args.upkeepCost) { |
| 1358 | emit InsufficientUpkeep( |
| 1359 | args.strategy, |
| 1360 | manager, |
| 1361 | _managerUpkeepBalance[manager], |
| 1362 | args.upkeepCost |
| 1363 | ); |
| 1364 | return; |
| 1365 | } |
| 1366 | |
| 1367 | // Deduct the upkeep cost and emit event |
| 1368 | _managerUpkeepBalance[manager] -= args.upkeepCost; |
| 1369 | |
| 1370 | // Add claimable upkeep for the `feeRecipient` |
| 1371 | claimableUpkeep += args.upkeepCost; |
| 1372 | |
| 1373 | emit UpkeepSpent(manager, args.upkeepCost); |
| 1374 | } |
| 1375 | |
| 1376 | // Update PPS, ppsStdev and timestamp in StrategyData |
| 1377 | _strategyData[args.strategy].pps = args.pps; |
| 1378 | _strategyData[args.strategy].ppsStdev = args.ppsStdev; |
| 1379 | _strategyData[args.strategy].lastUpdateTimestamp = args.timestamp; |
| 1380 | |
| 1381 | 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 | function _createLeaf( |
| 1437 | address hookAddress, |
| 1438 | bytes calldata hookArgs |
| 1439 | ) 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 | return |
| 1446 | keccak256( |
| 1447 | 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 | function _validateSingleHook( |
| 1462 | address hookAddress, |
| 1463 | bytes calldata hookArgs, |
| 1464 | bytes32[] calldata proof, |
| 1465 | bool isGlobalProof, |
| 1466 | HookValidationCache memory cache, |
| 1467 | address strategy |
| 1468 | ) internal view returns (bool) { |
| 1469 | // Create leaf node from the hook address and arguments |
| 1470 | bytes32 leaf = _createLeaf(hookAddress, hookArgs); |
| 1471 | |
| 1472 | if (isGlobalProof) { |
| 1473 | // Validate against global root |
| 1474 | if ( |
| 1475 | cache.globalHooksRootVetoed || |
| 1476 | cache.globalHooksRoot == bytes32(0) |
| 1477 | ) { |
| 1478 | return false; |
| 1479 | } |
| 1480 | |
| 1481 | // Check if this leaf is banned by the manager |
| 1482 | if (_strategyData[strategy].bannedLeaves[leaf]) { |
| 1483 | return false; |
| 1484 | } |
| 1485 | |
| 1486 | // For single-leaf trees, empty proof is valid when root equals leaf |
| 1487 | if (proof.length == 0) { |
| 1488 | return cache.globalHooksRoot == leaf; |
| 1489 | } |
| 1490 | return MerkleProof.verify(proof, cache.globalHooksRoot, leaf); |
| 1491 | } else { |
| 1492 | // Validate against strategy root |
| 1493 | if ( |
| 1494 | cache.strategyHooksRootVetoed || |
| 1495 | cache.strategyRoot == bytes32(0) |
| 1496 | ) { |
| 1497 | return false; |
| 1498 | } |
| 1499 | // For single-leaf trees, empty proof is valid when root equals leaf |
| 1500 | if (proof.length == 0) { |
| 1501 | return cache.strategyRoot == leaf; |
| 1502 | } |
| 1503 | 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 | return SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK()); |
| 1513 | } |
| 1514 | } |
| 1515 |
80.0%
src/SuperVault/SuperVaultEscrow.sol
Lines covered: 12 / 15 (80.0%)
| 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 | 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 | bool public initialized; |
| 24 | address public vault; |
| 25 | address public strategy; |
| 26 | |
| 27 | /*////////////////////////////////////////////////////////////// |
| 28 | MODIFIERS |
| 29 | //////////////////////////////////////////////////////////////*/ |
| 30 | modifier onlyVault() { |
| 31 | 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 | function initialize(address vaultAddress, address strategyAddress) external { |
| 43 | if (initialized) revert ALREADY_INITIALIZED(); |
| 44 | if (vaultAddress == address(0) || strategyAddress == address(0)) revert ZERO_ADDRESS(); |
| 45 | |
| 46 | initialized = true; |
| 47 | vault = vaultAddress; |
| 48 | 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 | function escrowShares(address from, uint256 amount) external onlyVault { |
| 59 | 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 | function returnShares(address to, uint256 amount) external onlyVault { |
| 66 | IERC20(vault).safeTransfer(to, amount); |
| 67 | } |
| 68 | } |
| 69 |
88.0%
src/SuperVault/SuperVaultStrategy.sol
Lines covered: 583 / 661 (88.0%)
| 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 | 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 | 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 | 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 | ISuperGovernor public immutable superGovernor; |
| 68 | |
| 69 | // Emergency withdrawable configuration |
| 70 | bool public emergencyWithdrawable; |
| 71 | bool public proposedEmergencyWithdrawable; |
| 72 | 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 | 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 | 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 | (feeConfigData.performanceFeeBps > 0 || |
| 106 | feeConfigData.managementFeeBps > 0) && |
| 107 | feeConfigData.recipient == address(0) |
| 108 | ) revert ZERO_ADDRESS(); |
| 109 | if (feeConfigData.performanceFeeBps > BPS_PRECISION) |
| 110 | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 111 | if (feeConfigData.managementFeeBps > BPS_PRECISION) |
| 112 | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 113 | |
| 114 | __ReentrancyGuard_init(); |
| 115 | |
| 116 | _vault = vaultAddress; |
| 117 | _asset = IERC20(IERC4626(vaultAddress).asset()); |
| 118 | _vaultDecimals = IERC20Metadata(vaultAddress).decimals(); |
| 119 | PRECISION = 10 ** _vaultDecimals; |
| 120 | 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 | function handleOperations4626Deposit( |
| 132 | address controller, |
| 133 | uint256 assetsGross |
| 134 | ) external returns (uint256 sharesNet) { |
| 135 | _requireVault(); |
| 136 | |
| 137 | if (assetsGross == 0) revert INVALID_AMOUNT(); |
| 138 | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 139 | |
| 140 | // Check if strategy is paused or if global hooks root is vetoed |
| 141 | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 142 | if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) { |
| 143 | revert OPERATIONS_BLOCKED_BY_VETO(); |
| 144 | } |
| 145 | |
| 146 | // Fee skim in ASSETS (asset-side entry fee) |
| 147 | uint256 feeBps = feeConfig.managementFeeBps; |
| 148 | uint256 feeAssets = feeBps == 0 |
| 149 | ? 0 |
| 150 | : Math.mulDiv( |
| 151 | assetsGross, |
| 152 | feeBps, |
| 153 | BPS_PRECISION, |
| 154 | Math.Rounding.Ceil |
| 155 | ); |
| 156 | |
| 157 | uint256 assetsNet = assetsGross - feeAssets; |
| 158 | if (assetsNet == 0) revert INVALID_AMOUNT(); |
| 159 | |
| 160 | if (feeAssets != 0) { |
| 161 | address recipient = feeConfig.recipient; |
| 162 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 163 | _safeTokenTransfer(address(_asset), recipient, feeAssets); |
| 164 | emit ManagementFeePaid(controller, recipient, feeAssets, feeBps); |
| 165 | } |
| 166 | |
| 167 | // Compute shares on NET using current PPS |
| 168 | uint256 pps = getStoredPPS(); |
| 169 | if (pps == 0) revert INVALID_PPS(); |
| 170 | sharesNet = Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
| 171 | if (sharesNet == 0) revert INVALID_AMOUNT(); |
| 172 | |
| 173 | // Account on NET |
| 174 | SuperVaultState storage state = superVaultState[controller]; |
| 175 | state.accumulatorShares += sharesNet; |
| 176 | state.accumulatorCostBasis += assetsNet; |
| 177 | emit DepositHandled(controller, assetsNet, sharesNet); |
| 178 | return sharesNet; |
| 179 | } |
| 180 | |
| 181 | /// @inheritdoc ISuperVaultStrategy |
| 182 | function handleOperations4626Mint( |
| 183 | address controller, |
| 184 | uint256 sharesNet, |
| 185 | uint256 assetsGross, |
| 186 | uint256 assetsNet |
| 187 | ) external { |
| 188 | _requireVault(); |
| 189 | |
| 190 | if (sharesNet == 0) revert INVALID_AMOUNT(); |
| 191 | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 192 | |
| 193 | // Check if strategy is paused or if global hooks root is vetoed |
| 194 | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 195 | if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) { |
| 196 | revert OPERATIONS_BLOCKED_BY_VETO(); |
| 197 | } |
| 198 | |
| 199 | uint256 feeBps = feeConfig.managementFeeBps; |
| 200 | // Transfer fee if needed |
| 201 | if (feeBps != 0) { |
| 202 | uint256 feeAssets = assetsGross - assetsNet; |
| 203 | if (feeAssets != 0) { |
| 204 | address recipient = feeConfig.recipient; |
| 205 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 206 | _safeTokenTransfer(address(_asset), recipient, feeAssets); |
| 207 | emit ManagementFeePaid( |
| 208 | controller, |
| 209 | recipient, |
| 210 | feeAssets, |
| 211 | feeBps |
| 212 | ); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | // Account on NET |
| 217 | SuperVaultState storage state = superVaultState[controller]; |
| 218 | state.accumulatorShares += sharesNet; |
| 219 | state.accumulatorCostBasis += assetsNet; |
| 220 | emit DepositHandled(controller, assetsNet, sharesNet); |
| 221 | } |
| 222 | |
| 223 | /// @inheritdoc ISuperVaultStrategy |
| 224 | function quoteMintAssetsGross( |
| 225 | uint256 shares |
| 226 | ) external view returns (uint256 assetsGross, uint256 assetsNet) { |
| 227 | uint256 pps = getStoredPPS(); |
| 228 | if (pps == 0) revert INVALID_PPS(); |
| 229 | assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
| 230 | if (assetsNet == 0) revert INVALID_AMOUNT(); |
| 231 | |
| 232 | uint256 feeBps = feeConfig.managementFeeBps; |
| 233 | if (feeBps == 0) return (assetsNet, assetsNet); |
| 234 | if (feeBps >= BPS_PRECISION) revert INVALID_AMOUNT(); // prevents div-by-zero (100% fee) |
| 235 | assetsGross = Math.mulDiv( |
| 236 | assetsNet, |
| 237 | BPS_PRECISION, |
| 238 | (BPS_PRECISION - feeBps), |
| 239 | Math.Rounding.Ceil |
| 240 | ); |
| 241 | return (assetsGross, assetsNet); |
| 242 | } |
| 243 | |
| 244 | /// @inheritdoc ISuperVaultStrategy |
| 245 | function handleOperations7540( |
| 246 | Operation operation, |
| 247 | address controller, |
| 248 | address receiver, |
| 249 | uint256 amount |
| 250 | ) external { |
| 251 | _requireVault(); |
| 252 | if (operation == Operation.RedeemRequest) { |
| 253 | _handleRequestRedeem(controller, amount); // amount = shares |
| 254 | } else if (operation == Operation.CancelRedeem) { |
| 255 | _handleCancelRedeem(controller); |
| 256 | } else if (operation == Operation.ClaimRedeem) { |
| 257 | _handleClaimRedeem(controller, receiver, amount); // amount = assets |
| 258 | } else { |
| 259 | revert ACTION_TYPE_DISALLOWED(); |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | /*////////////////////////////////////////////////////////////// |
| 264 | MANAGER EXTERNAL ACCESS FUNCTIONS |
| 265 | //////////////////////////////////////////////////////////////*/ |
| 266 | |
| 267 | /// @inheritdoc ISuperVaultStrategy |
| 268 | function executeHooks( |
| 269 | ExecuteArgs calldata args |
| 270 | ) external payable nonReentrant { |
| 271 | _isManager(msg.sender); |
| 272 | |
| 273 | uint256 hooksLength = args.hooks.length; |
| 274 | if (hooksLength == 0) revert ZERO_LENGTH(); |
| 275 | if (args.hookCalldata.length != hooksLength) |
| 276 | revert INVALID_ARRAY_LENGTH(); |
| 277 | if (args.expectedAssetsOrSharesOut.length != hooksLength) |
| 278 | revert INVALID_ARRAY_LENGTH(); |
| 279 | if (args.globalProofs.length != hooksLength) |
| 280 | revert INVALID_ARRAY_LENGTH(); |
| 281 | if (args.strategyProofs.length != hooksLength) |
| 282 | revert INVALID_ARRAY_LENGTH(); |
| 283 | |
| 284 | address prevHook; |
| 285 | for (uint256 i; i < hooksLength; ++i) { |
| 286 | address hook = args.hooks[i]; |
| 287 | if (!_isRegisteredHook(hook)) revert INVALID_HOOK(); |
| 288 | |
| 289 | // Check if the hook was validated |
| 290 | if ( |
| 291 | !_validateHook( |
| 292 | hook, |
| 293 | args.hookCalldata[i], |
| 294 | args.globalProofs[i], |
| 295 | args.strategyProofs[i] |
| 296 | ) |
| 297 | ) { |
| 298 | revert HOOK_VALIDATION_FAILED(); |
| 299 | } |
| 300 | |
| 301 | prevHook = _processSingleHookExecution( |
| 302 | hook, |
| 303 | prevHook, |
| 304 | args.hookCalldata[i], |
| 305 | args.expectedAssetsOrSharesOut[i] |
| 306 | ); |
| 307 | } |
| 308 | emit HooksExecuted(args.hooks); |
| 309 | } |
| 310 | |
| 311 | /// @inheritdoc ISuperVaultStrategy |
| 312 | function fulfillRedeemRequests( |
| 313 | FulfillArgs calldata args |
| 314 | ) external nonReentrant { |
| 315 | _isManager(msg.sender); |
| 316 | |
| 317 | // Check if strategy is paused |
| 318 | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 319 | |
| 320 | uint256 hooksLength = args.hooks.length; |
| 321 | if (hooksLength == 0) revert ZERO_LENGTH(); |
| 322 | uint256 controllersLength = args.controllers.length; |
| 323 | if (controllersLength == 0) revert ZERO_LENGTH(); |
| 324 | if (args.hookCalldata.length != hooksLength) |
| 325 | revert INVALID_ARRAY_LENGTH(); |
| 326 | if (args.expectedAssetsOrSharesOut.length != hooksLength) |
| 327 | revert INVALID_ARRAY_LENGTH(); |
| 328 | if (args.globalProofs.length != hooksLength) |
| 329 | revert INVALID_ARRAY_LENGTH(); |
| 330 | if (args.strategyProofs.length != hooksLength) |
| 331 | revert INVALID_ARRAY_LENGTH(); |
| 332 | |
| 333 | uint256 currentPPS = getStoredPPS(); |
| 334 | if (currentPPS == 0) revert INVALID_PPS(); |
| 335 | |
| 336 | // Pre-calculate totals to ensure no overburn of escrowed shares |
| 337 | uint256 totalRequestedShares; |
| 338 | for (uint256 i; i < controllersLength; ++i) { |
| 339 | totalRequestedShares += superVaultState[args.controllers[i]] |
| 340 | .pendingRedeemRequest; |
| 341 | } |
| 342 | uint256 intendedShares; |
| 343 | for (uint256 i; i < hooksLength; ++i) { |
| 344 | address hook = args.hooks[i]; |
| 345 | if (!_isFulfillRequestsHook(hook)) revert INVALID_HOOK(); |
| 346 | // Check if the hook was validated |
| 347 | if ( |
| 348 | !_validateHook( |
| 349 | hook, |
| 350 | args.hookCalldata[i], |
| 351 | args.globalProofs[i], |
| 352 | args.strategyProofs[i] |
| 353 | ) |
| 354 | ) { |
| 355 | revert HOOK_VALIDATION_FAILED(); |
| 356 | } |
| 357 | if (args.expectedAssetsOrSharesOut[i] == 0) |
| 358 | revert ZERO_EXPECTED_VALUE(); |
| 359 | |
| 360 | intendedShares += ISuperHookInflowOutflow(hook).decodeAmount( |
| 361 | args.hookCalldata[i] |
| 362 | ); |
| 363 | } |
| 364 | |
| 365 | // Enforce both lower- and upper-bounds on intended shares to prevent escrow overburn |
| 366 | if (intendedShares + TOLERANCE_CONSTANT < totalRequestedShares) { |
| 367 | revert INVALID_REDEEM_FILL(); |
| 368 | } |
| 369 | |
| 370 | if (intendedShares > totalRequestedShares + TOLERANCE_CONSTANT) { |
| 371 | revert INVALID_REDEEM_FILL(); |
| 372 | } |
| 373 | |
| 374 | uint256 processedShares; |
| 375 | for (uint256 i; i < hooksLength; ++i) { |
| 376 | address hook = args.hooks[i]; |
| 377 | uint256 amountSharesSpent = _processSingleFulfillHookExecution( |
| 378 | hook, |
| 379 | args.hookCalldata[i], |
| 380 | 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 | ISuperVault(_vault).burnShares(processedShares); |
| 397 | |
| 398 | 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 | 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 | 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 | if (oracles.length != length) revert INVALID_ARRAY_LENGTH(); |
| 431 | if (actionTypes.length != length) revert INVALID_ARRAY_LENGTH(); |
| 432 | |
| 433 | for (uint256 i; i < length; ++i) { |
| 434 | _manageYieldSource(sources[i], oracles[i], actionTypes[i]); |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | // @inheritdoc ISuperVaultStrategy |
| 439 | function proposeVaultFeeConfigUpdate( |
| 440 | uint256 performanceFeeBps, |
| 441 | uint256 managementFeeBps, |
| 442 | address recipient |
| 443 | ) external { |
| 444 | _isPrimaryManager(msg.sender); |
| 445 | |
| 446 | if (performanceFeeBps > BPS_PRECISION) |
| 447 | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 448 | if (managementFeeBps > BPS_PRECISION) |
| 449 | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 450 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 451 | proposedFeeConfig = FeeConfig({ |
| 452 | performanceFeeBps: performanceFeeBps, |
| 453 | managementFeeBps: managementFeeBps, |
| 454 | recipient: recipient |
| 455 | }); |
| 456 | feeConfigEffectiveTime = block.timestamp + ONE_WEEK; |
| 457 | emit VaultFeeConfigProposed( |
| 458 | performanceFeeBps, |
| 459 | managementFeeBps, |
| 460 | recipient, |
| 461 | feeConfigEffectiveTime |
| 462 | ); |
| 463 | } |
| 464 | |
| 465 | // @inheritdoc ISuperVaultStrategy |
| 466 | function executeVaultFeeConfigUpdate() external { |
| 467 | if (block.timestamp < feeConfigEffectiveTime) |
| 468 | revert INVALID_TIMESTAMP(); |
| 469 | if (proposedFeeConfig.recipient == address(0)) revert ZERO_ADDRESS(); |
| 470 | feeConfig = proposedFeeConfig; |
| 471 | delete proposedFeeConfig; |
| 472 | feeConfigEffectiveTime = 0; |
| 473 | emit VaultFeeConfigUpdated( |
| 474 | feeConfig.performanceFeeBps, |
| 475 | feeConfig.managementFeeBps, |
| 476 | feeConfig.recipient |
| 477 | ); |
| 478 | } |
| 479 | |
| 480 | // @inheritdoc ISuperVaultStrategy |
| 481 | function updateMaxPPSSlippage(uint256 maxSlippageBps) external { |
| 482 | _isPrimaryManager(msg.sender); |
| 483 | if (maxSlippageBps > BPS_PRECISION) revert INVALID_MAX_SLIPPAGE_BPS(); |
| 484 | _maxPPSSlippage = maxSlippageBps; |
| 485 | emit MaxPPSSlippageUpdated(maxSlippageBps); |
| 486 | } |
| 487 | |
| 488 | // @inheritdoc ISuperVaultStrategy |
| 489 | 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 | function moveAccumulatorOnTransfer( |
| 512 | address from, |
| 513 | address to, |
| 514 | uint256 shares |
| 515 | ) external { |
| 516 | _requireVault(); |
| 517 | if (shares == 0) return; |
| 518 | |
| 519 | SuperVaultState storage fromState = superVaultState[from]; |
| 520 | 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 | uint256 availableAccumulatorShares = fromState.accumulatorShares; |
| 526 | if (availableAccumulatorShares == 0) return; // No accumulator to move |
| 527 | |
| 528 | uint256 sharesToMove = shares > availableAccumulatorShares |
| 529 | ? availableAccumulatorShares |
| 530 | : shares; |
| 531 | |
| 532 | // Pro-rata move of cost basis (NO PPS here; preserves fee correctness) |
| 533 | 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 | fromState.accumulatorShares -= sharesToMove; |
| 543 | fromState.accumulatorCostBasis -= movedCostBasis; |
| 544 | |
| 545 | toState.accumulatorShares += sharesToMove; |
| 546 | toState.accumulatorCostBasis += movedCostBasis; |
| 547 | |
| 548 | // Never touch: pendingRedeemRequest, averageRequestPPS, maxWithdraw, averageWithdrawPrice |
| 549 | } |
| 550 | |
| 551 | /*////////////////////////////////////////////////////////////// |
| 552 | VIEW FUNCTIONS |
| 553 | //////////////////////////////////////////////////////////////*/ |
| 554 | |
| 555 | // @inheritdoc ISuperVaultStrategy |
| 556 | function getVaultInfo() |
| 557 | external |
| 558 | view |
| 559 | returns (address vault, address asset, uint8 vaultDecimals) |
| 560 | { |
| 561 | vault = _vault; |
| 562 | asset = address(_asset); |
| 563 | vaultDecimals = _vaultDecimals; |
| 564 | } |
| 565 | |
| 566 | // @inheritdoc ISuperVaultStrategy |
| 567 | function getConfigInfo() |
| 568 | external |
| 569 | view |
| 570 | returns (FeeConfig memory feeConfig_) |
| 571 | { |
| 572 | feeConfig_ = feeConfig; |
| 573 | } |
| 574 | |
| 575 | // @inheritdoc ISuperVaultStrategy |
| 576 | function getStoredPPS() public view returns (uint256) { |
| 577 | return _getSuperVaultAggregator().getPPS(address(this)); |
| 578 | } |
| 579 | |
| 580 | // @inheritdoc ISuperVaultStrategy |
| 581 | function getSuperVaultState( |
| 582 | address controller |
| 583 | ) external view returns (SuperVaultState memory state) { |
| 584 | return superVaultState[controller]; |
| 585 | } |
| 586 | |
| 587 | // @inheritdoc ISuperVaultStrategy |
| 588 | function getYieldSource( |
| 589 | address source |
| 590 | ) external view returns (YieldSource memory) { |
| 591 | return YieldSource({oracle: yieldSources[source]}); |
| 592 | } |
| 593 | |
| 594 | // @inheritdoc ISuperVaultStrategy |
| 595 | function getYieldSourcesList() |
| 596 | external |
| 597 | view |
| 598 | returns (YieldSourceInfo[] memory) |
| 599 | { |
| 600 | uint256 length = yieldSourcesList.length(); |
| 601 | YieldSourceInfo[] memory sourcesInfo = new YieldSourceInfo[](length); |
| 602 | |
| 603 | for (uint256 i; i < length; ++i) { |
| 604 | address sourceAddress = yieldSourcesList.at(i); |
| 605 | address oracle = yieldSources[sourceAddress]; |
| 606 | |
| 607 | sourcesInfo[i] = YieldSourceInfo({ |
| 608 | sourceAddress: sourceAddress, |
| 609 | oracle: oracle |
| 610 | }); |
| 611 | } |
| 612 | |
| 613 | return sourcesInfo; |
| 614 | } |
| 615 | |
| 616 | // @inheritdoc ISuperVaultStrategy |
| 617 | function getYieldSources() external view returns (address[] memory) { |
| 618 | return yieldSourcesList.values(); |
| 619 | } |
| 620 | |
| 621 | // @inheritdoc ISuperVaultStrategy |
| 622 | function getYieldSourcesCount() external view returns (uint256) { |
| 623 | return yieldSourcesList.length(); |
| 624 | } |
| 625 | |
| 626 | // @inheritdoc ISuperVaultStrategy |
| 627 | function containsYieldSource(address source) external view returns (bool) { |
| 628 | return yieldSourcesList.contains(source); |
| 629 | } |
| 630 | |
| 631 | // @inheritdoc ISuperVaultStrategy |
| 632 | function pendingRedeemRequest( |
| 633 | address controller |
| 634 | ) external view returns (uint256 pendingShares) { |
| 635 | return superVaultState[controller].pendingRedeemRequest; |
| 636 | } |
| 637 | |
| 638 | // @inheritdoc ISuperVaultStrategy |
| 639 | function claimableWithdraw( |
| 640 | address controller |
| 641 | ) external view returns (uint256 claimableAssets) { |
| 642 | return superVaultState[controller].maxWithdraw; |
| 643 | } |
| 644 | |
| 645 | // @inheritdoc ISuperVaultStrategy |
| 646 | function getAverageWithdrawPrice( |
| 647 | address controller |
| 648 | ) external view returns (uint256 averageWithdrawPrice) { |
| 649 | return superVaultState[controller].averageWithdrawPrice; |
| 650 | } |
| 651 | |
| 652 | // @inheritdoc ISuperVaultStrategy |
| 653 | function previewPerformanceFee( |
| 654 | address controller, |
| 655 | uint256 sharesToRedeem |
| 656 | ) |
| 657 | external |
| 658 | view |
| 659 | returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee) |
| 660 | { |
| 661 | if (sharesToRedeem == 0) return (0, 0, 0); |
| 662 | |
| 663 | // Get controller's state |
| 664 | SuperVaultState storage state = superVaultState[controller]; |
| 665 | |
| 666 | // Check if controller has enough shares |
| 667 | if (sharesToRedeem > state.accumulatorShares) return (0, 0, 0); |
| 668 | |
| 669 | // Get the current price per share |
| 670 | uint256 currentPPS = getStoredPPS(); |
| 671 | |
| 672 | // Calculate historical assets (cost basis) |
| 673 | uint256 historicalAssets = 0; |
| 674 | if (state.accumulatorShares > 0) { |
| 675 | historicalAssets = sharesToRedeem.mulDiv( |
| 676 | state.accumulatorCostBasis, |
| 677 | state.accumulatorShares, |
| 678 | Math.Rounding.Floor |
| 679 | ); |
| 680 | } |
| 681 | |
| 682 | // Calculate current value of shares in asset terms |
| 683 | uint256 currentAssetsWithFees = sharesToRedeem.mulDiv( |
| 684 | currentPPS, |
| 685 | PRECISION, |
| 686 | Math.Rounding.Floor |
| 687 | ); |
| 688 | |
| 689 | // Calculate fee (if any) using same logic as _calculateAndTransferFee |
| 690 | if (currentAssetsWithFees > historicalAssets) { |
| 691 | uint256 profit = currentAssetsWithFees - historicalAssets; |
| 692 | uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
| 693 | totalFee = profit.mulDiv( |
| 694 | performanceFeeBps, |
| 695 | BPS_PRECISION, |
| 696 | Math.Rounding.Floor |
| 697 | ); |
| 698 | |
| 699 | if (totalFee > 0) { |
| 700 | // Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
| 701 | superformFee = totalFee.mulDiv( |
| 702 | superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
| 703 | BPS_PRECISION, |
| 704 | Math.Rounding.Floor |
| 705 | ); |
| 706 | recipientFee = totalFee - superformFee; |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | 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 | vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
| 733 | hookCalldata |
| 734 | ); |
| 735 | if (yieldSources[vars.targetedYieldSource] == address(0)) |
| 736 | revert YIELD_SOURCE_NOT_FOUND(); |
| 737 | |
| 738 | bool usePrevHookAmount = _decodeHookUsePrevHookAmount( |
| 739 | hook, |
| 740 | hookCalldata |
| 741 | ); |
| 742 | if (usePrevHookAmount && prevHook != address(0)) { |
| 743 | vars.outAmount = _getPreviousHookOutAmount(prevHook); |
| 744 | if (expectedAssetsOrSharesOut == 0) revert ZERO_EXPECTED_VALUE(); |
| 745 | uint256 minExpectedPrevOut = expectedAssetsOrSharesOut * |
| 746 | (BPS_PRECISION - _getSlippageTolerance()); |
| 747 | if (vars.outAmount * BPS_PRECISION < minExpectedPrevOut) { |
| 748 | revert MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET(); |
| 749 | } |
| 750 | } |
| 751 | |
| 752 | ISuperHook(address(vars.hookContract)).setExecutionContext( |
| 753 | address(this) |
| 754 | ); |
| 755 | vars.executions = vars.hookContract.build( |
| 756 | prevHook, |
| 757 | address(this), |
| 758 | hookCalldata |
| 759 | ); |
| 760 | for (uint256 j; j < vars.executions.length; ++j) { |
| 761 | (vars.success, ) = vars.executions[j].target.call{ |
| 762 | value: vars.executions[j].value |
| 763 | }(vars.executions[j].callData); |
| 764 | if (!vars.success) revert OPERATION_FAILED(); |
| 765 | } |
| 766 | ISuperHook(address(vars.hookContract)).resetExecutionState( |
| 767 | address(this) |
| 768 | ); |
| 769 | |
| 770 | uint256 actualOutput = ISuperHookResult(hook).getOutAmount( |
| 771 | address(this) |
| 772 | ); |
| 773 | if (actualOutput == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 774 | |
| 775 | uint256 minExpectedOut = (expectedAssetsOrSharesOut * |
| 776 | (BPS_PRECISION - _getSlippageTolerance())) / BPS_PRECISION; |
| 777 | if (actualOutput < minExpectedOut) { |
| 778 | revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 779 | } |
| 780 | |
| 781 | 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 | vars.hookContract = ISuperHook(hook); |
| 806 | vars.hookType = ISuperHookResult(hook).hookType(); |
| 807 | if (vars.hookType != ISuperHook.HookType.OUTFLOW) |
| 808 | revert INVALID_HOOK_TYPE(); |
| 809 | |
| 810 | vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
| 811 | hookCalldata |
| 812 | ); |
| 813 | if (yieldSources[vars.targetedYieldSource] == address(0)) |
| 814 | revert YIELD_SOURCE_NOT_FOUND(); |
| 815 | |
| 816 | // we must always encode supervault shares when fulfilling redemptions |
| 817 | vars.superVaultShares = ISuperHookInflowOutflow(hook).decodeAmount( |
| 818 | hookCalldata |
| 819 | ); |
| 820 | |
| 821 | // Calculate underlying shares and update hook calldata |
| 822 | vars.amountOfAssets = vars.superVaultShares.mulDiv( |
| 823 | currentPPS, |
| 824 | PRECISION, |
| 825 | Math.Rounding.Floor |
| 826 | ); |
| 827 | vars.svAsset = address(_asset); |
| 828 | vars.amountConvertedToUnderlyingShares = IYieldSourceOracle( |
| 829 | yieldSources[vars.targetedYieldSource] |
| 830 | ).getShareOutput( |
| 831 | vars.targetedYieldSource, |
| 832 | vars.svAsset, |
| 833 | vars.amountOfAssets |
| 834 | ); |
| 835 | hookCalldata = ISuperHookOutflow(hook).replaceCalldataAmount( |
| 836 | hookCalldata, |
| 837 | vars.amountConvertedToUnderlyingShares |
| 838 | ); |
| 839 | |
| 840 | vars.balanceAssetBefore = _getTokenBalance(vars.svAsset, address(this)); |
| 841 | |
| 842 | ISuperHook(address(vars.hookContract)).setExecutionContext( |
| 843 | address(this) |
| 844 | ); |
| 845 | |
| 846 | vars.executions = vars.hookContract.build( |
| 847 | address(0), |
| 848 | address(this), |
| 849 | hookCalldata |
| 850 | ); |
| 851 | for (uint256 j; j < vars.executions.length; ++j) { |
| 852 | (vars.success, ) = vars.executions[j].target.call( |
| 853 | vars.executions[j].callData |
| 854 | ); |
| 855 | if (!vars.success) revert OPERATION_FAILED(); |
| 856 | } |
| 857 | ISuperHook(address(vars.hookContract)).resetExecutionState( |
| 858 | address(this) |
| 859 | ); |
| 860 | |
| 861 | vars.outAmount = |
| 862 | _getTokenBalance(vars.svAsset, address(this)) - |
| 863 | vars.balanceAssetBefore; |
| 864 | |
| 865 | if (vars.outAmount == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 866 | if ( |
| 867 | vars.outAmount * BPS_PRECISION < |
| 868 | expectedAssetOutput * (BPS_PRECISION - _getSlippageTolerance()) |
| 869 | ) { |
| 870 | revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 871 | } |
| 872 | 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 | for (uint256 i; i < controllersLength; ++i) { |
| 889 | 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 | 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 | uint256 decrease = ( |
| 898 | (averageRequestPPS - currentPPS).mulDiv( |
| 899 | BPS_PRECISION, |
| 900 | averageRequestPPS, |
| 901 | Math.Rounding.Floor |
| 902 | ) |
| 903 | ); |
| 904 | // If decrease exceeds maximum allowed slippage, revert |
| 905 | 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 | state.maxWithdraw += currentAssets; |
| 918 | state.averageRequestPPS = 0; // Reset PPS value after fulfillment |
| 919 | |
| 920 | // Call vault callback |
| 921 | _onRedeemClaimable( |
| 922 | 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 | if (requestedShares > state.accumulatorShares) |
| 971 | 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 | state.accumulatorShares -= requestedShares; |
| 982 | 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 | 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 | uint256 profit = currentAssetsWithFees - historicalAssets; |
| 1035 | uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
| 1036 | uint256 totalFee = profit.mulDiv( |
| 1037 | performanceFeeBps, |
| 1038 | BPS_PRECISION, |
| 1039 | Math.Rounding.Floor |
| 1040 | ); |
| 1041 | if (totalFee > 0) { |
| 1042 | // Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
| 1043 | uint256 superformFee = totalFee.mulDiv( |
| 1044 | superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
| 1045 | BPS_PRECISION, |
| 1046 | Math.Rounding.Floor |
| 1047 | ); |
| 1048 | uint256 recipientFee = totalFee - superformFee; |
| 1049 | |
| 1050 | // Transfer fees |
| 1051 | if (superformFee > 0) { |
| 1052 | // Get treasury address from SuperGovernor |
| 1053 | address treasury = superGovernor.getAddress( |
| 1054 | superGovernor.TREASURY() |
| 1055 | ); |
| 1056 | _safeTokenTransfer(address(_asset), treasury, superformFee); |
| 1057 | emit FeePaid(treasury, superformFee, performanceFeeBps); |
| 1058 | } |
| 1059 | |
| 1060 | if (recipientFee > 0) { |
| 1061 | address recipient = feeConfig.recipient; |
| 1062 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 1063 | _safeTokenTransfer( |
| 1064 | address(_asset), |
| 1065 | recipient, |
| 1066 | recipientFee |
| 1067 | ); |
| 1068 | emit FeePaid(recipient, recipientFee, performanceFeeBps); |
| 1069 | } |
| 1070 | |
| 1071 | 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 | if (state.maxWithdraw > 0 && state.averageWithdrawPrice > 0) { |
| 1090 | existingShares = state.maxWithdraw.mulDiv( |
| 1091 | PRECISION, |
| 1092 | state.averageWithdrawPrice, |
| 1093 | Math.Rounding.Floor |
| 1094 | ); |
| 1095 | existingAssets = state.maxWithdraw; |
| 1096 | } |
| 1097 | |
| 1098 | uint256 newTotalShares = existingShares + requestedShares; |
| 1099 | uint256 newTotalAssets = existingAssets + currentAssetsWithFees; |
| 1100 | |
| 1101 | if (newTotalShares > 0) { |
| 1102 | 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 | address aggregatorAddress = superGovernor.getAddress( |
| 1118 | 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 | if (!_getSuperVaultAggregator().isAnyManager(manager_, address(this))) { |
| 1128 | 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 | !_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 | if (source == address(0) || oracle == address(0)) revert ZERO_ADDRESS(); |
| 1167 | if (yieldSources[source] != address(0)) |
| 1168 | revert YIELD_SOURCE_ALREADY_EXISTS(); |
| 1169 | yieldSources[source] = oracle; |
| 1170 | if (!yieldSourcesList.add(source)) revert YIELD_SOURCE_ALREADY_EXISTS(); |
| 1171 | |
| 1172 | 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 | if (oracle == address(0)) revert ZERO_ADDRESS(); |
| 1180 | address oldOracle = yieldSources[source]; |
| 1181 | if (oldOracle == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1182 | 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 | if (yieldSources[source] == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1191 | |
| 1192 | // Remove from mapping |
| 1193 | delete yieldSources[source]; |
| 1194 | |
| 1195 | // Remove from EnumerableSet |
| 1196 | if (!yieldSourcesList.remove(source)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1197 | |
| 1198 | 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 | emit EmergencyWithdrawableProposed( |
| 1208 | true, |
| 1209 | emergencyWithdrawableEffectiveTime |
| 1210 | ); |
| 1211 | } |
| 1212 | |
| 1213 | /// @notice Internal function to execute an emergency withdraw |
| 1214 | function _executeEmergencyWithdrawActivation() internal { |
| 1215 | if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL(); |
| 1216 | if (block.timestamp < emergencyWithdrawableEffectiveTime) |
| 1217 | revert INVALID_TIMESTAMP(); |
| 1218 | emergencyWithdrawable = proposedEmergencyWithdrawable; |
| 1219 | 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 | 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 | if (!emergencyWithdrawable) revert INVALID_EMERGENCY_WITHDRAWAL(); |
| 1244 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 1245 | uint256 freeAssets = _getTokenBalance(address(_asset), address(this)); |
| 1246 | if (amount == 0 || amount > freeAssets) revert INSUFFICIENT_FUNDS(); |
| 1247 | _safeTokenTransfer(address(_asset), recipient, amount); |
| 1248 | 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 | 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 | 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 | 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 | function _getPreviousHookOutAmount( |
| 1286 | address prevHook |
| 1287 | ) private view returns (uint256) { |
| 1288 | 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 | 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 | 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 | 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 | ((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 | 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 | SuperVaultState storage state = superVaultState[controller]; |
| 1359 | uint256 pendingShares = state.pendingRedeemRequest; |
| 1360 | 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 | SuperVaultState storage state = superVaultState[controller]; |
| 1379 | |
| 1380 | // Handle dust collection for rounding errors |
| 1381 | uint256 actualAmountToClaim = assetsToClaim; |
| 1382 | 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 | assetsToClaim - remainingAssets <= TOLERANCE_CONSTANT |
| 1389 | ) { |
| 1390 | actualAmountToClaim = remainingAssets; |
| 1391 | } |
| 1392 | |
| 1393 | if (state.maxWithdraw < actualAmountToClaim) |
| 1394 | revert INVALID_REDEEM_CLAIM(); |
| 1395 | state.maxWithdraw -= actualAmountToClaim; |
| 1396 | _asset.safeTransfer(receiver, actualAmountToClaim); |
| 1397 | 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 | 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 | 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 | 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 | 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 | 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 | _getSuperVaultAggregator().validateHook( |
| 1461 | address(this), |
| 1462 | ISuperVaultAggregator.ValidateHookArgs({ |
| 1463 | hookAddress: hook, |
| 1464 | hookArgs: ISuperHookInspector(hook).inspect(hookCalldata), |
| 1465 | globalProof: globalProof, |
| 1466 | strategyProof: strategyProof |
| 1467 | }) |
| 1468 | ); |
| 1469 | } |
| 1470 | } |
| 1471 |
0.0%
src/interfaces/ISuperGovernor.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | import "@openzeppelin/contracts/access/IAccessControl.sol"; |
| 5 | |
| 6 | /*////////////////////////////////////////////////////////////// |
| 7 | ENUMS |
| 8 | //////////////////////////////////////////////////////////////*/ |
| 9 | /// @notice Enum representing different types of fees that can be managed |
| 10 | enum FeeType { |
| 11 | REVENUE_SHARE, |
| 12 | SUPER_VAULT_PERFORMANCE_FEE, |
| 13 | SUPER_ASSET_SWAP_FEE |
| 14 | } |
| 15 | /// @title ISuperGovernor |
| 16 | /// @author Superform Labs |
| 17 | /// @notice Interface for the SuperGovernor contract |
| 18 | /// @dev Central registry for all deployed contracts in the Superform periphery |
| 19 | |
| 20 | interface ISuperGovernor is IAccessControl { |
| 21 | /*////////////////////////////////////////////////////////////// |
| 22 | STRUCTS |
| 23 | //////////////////////////////////////////////////////////////*/ |
| 24 | |
| 25 | /// @notice Structure containing Merkle root data for a hook |
| 26 | struct HookMerkleRootData { |
| 27 | bytes32 currentRoot; // Current active Merkle root for the hook |
| 28 | bytes32 proposedRoot; // Proposed new Merkle root (zero if no proposal exists) |
| 29 | uint256 effectiveTime; // Timestamp when the proposed root becomes effective |
| 30 | } |
| 31 | |
| 32 | struct GasInfo { |
| 33 | // `batchForwardPPS` base gas |
| 34 | uint256 baseGasBatch; |
| 35 | // `batchForwardPPS` gas increase per entry |
| 36 | uint256 gasIncreasePerEntryBatch; |
| 37 | } |
| 38 | |
| 39 | /*////////////////////////////////////////////////////////////// |
| 40 | ERRORS |
| 41 | //////////////////////////////////////////////////////////////*/ |
| 42 | /// @notice Thrown when a function that should only be called by governor is called by someone else |
| 43 | error ONLY_GOVERNOR(); |
| 44 | /// @notice Thrown when trying to register a contract that is already registered |
| 45 | error CONTRACT_ALREADY_REGISTERED(); |
| 46 | /// @notice Thrown when trying to access a contract that is not registered |
| 47 | error CONTRACT_NOT_FOUND(); |
| 48 | /// @notice Thrown when providing an invalid address (typically zero address) |
| 49 | error INVALID_ADDRESS(); |
| 50 | /// @notice Thrown when providing an invalid chain ID |
| 51 | error INVALID_CHAIN_ID(); |
| 52 | /// @notice Thrown when a hook is already approved |
| 53 | error HOOK_ALREADY_APPROVED(); |
| 54 | /// @notice Thrown when a hook is not approved but expected to be |
| 55 | error HOOK_NOT_APPROVED(); |
| 56 | /// @notice Thrown when a fulfill requests hook is already registered |
| 57 | error FULFILL_REQUESTS_HOOK_ALREADY_REGISTERED(); |
| 58 | /// @notice Thrown when a fulfill requests hook is not registered but expected to be |
| 59 | error FULFILL_REQUESTS_HOOK_NOT_REGISTERED(); |
| 60 | /// @notice Thrown when provided revenue share is invalid (exceeds 100%) |
| 61 | error INVALID_REVENUE_SHARE(); |
| 62 | /// @notice Thrown when an invalid fee value is proposed (must be <= BPS_MAX) |
| 63 | error INVALID_FEE_VALUE(); |
| 64 | /// @notice Thrown when no proposed fee exists but one is expected |
| 65 | error NO_PROPOSED_FEE(FeeType feeType); |
| 66 | /// @notice Thrown when timelock period has not expired |
| 67 | error TIMELOCK_NOT_EXPIRED(); |
| 68 | /// @notice Thrown when a validator is not registered |
| 69 | error VALIDATOR_NOT_REGISTERED(); |
| 70 | /// @notice Thrown when a validator is already registered |
| 71 | error VALIDATOR_ALREADY_REGISTERED(); |
| 72 | /// @notice Thrown when trying to change active PPS oracle directly |
| 73 | error MUST_USE_TIMELOCK_FOR_CHANGE(); |
| 74 | /// @notice Thrown when a SuperBank hook Merkle root is not registered but expected to be |
| 75 | error INVALID_TIMESTAMP(); |
| 76 | /// @notice Thrown when attempting to set an invalid quorum value (typically zero) |
| 77 | error INVALID_QUORUM(); |
| 78 | /// @notice Thrown when no active PPS oracle is set but one is required |
| 79 | error NO_ACTIVE_PPS_ORACLE(); |
| 80 | /// @notice Thrown when no proposed PPS oracle exists but one is expected |
| 81 | error NO_PROPOSED_PPS_ORACLE(); |
| 82 | /// @notice Error thrown when manager takeovers are frozen |
| 83 | error MANAGER_TAKEOVERS_FROZEN(); |
| 84 | /// @notice Thrown when no proposed Merkle root exists but one is expected |
| 85 | error NO_PROPOSED_MERKLE_ROOT(); |
| 86 | /// @notice Thrown when no proposed Merkle root exists but one is expected |
| 87 | error ZERO_PROPOSED_MERKLE_ROOT(); |
| 88 | /// @notice Thrown when no proposed minimum staleness exists but one is expected |
| 89 | error NO_PROPOSED_MIN_STALENESS(); |
| 90 | /// @notice Thrown when the provided maxStaleness is less than the minimum required staleness |
| 91 | error MAX_STALENESS_TOO_LOW(); |
| 92 | /// @notice Thrown when a relayer is not registered |
| 93 | error RELAYER_NOT_REGISTERED(); |
| 94 | /// @notice Thrown when a relayer is already registered |
| 95 | error RELAYER_ALREADY_REGISTERED(); |
| 96 | /// @notice Thrown when an executor is not registered |
| 97 | error EXECUTOR_NOT_REGISTERED(); |
| 98 | /// @notice Thrown when an executor is already registered |
| 99 | error EXECUTOR_ALREADY_REGISTERED(); |
| 100 | /// @notice Thrown when there's no pending change but one is expected |
| 101 | error NO_PENDING_CHANGE(); |
| 102 | /// @notice Thrown when a manager is not registered |
| 103 | error MANAGER_NOT_REGISTERED(); |
| 104 | /// @notice Thrown when a manager is already registered |
| 105 | error MANAGER_ALREADY_REGISTERED(); |
| 106 | /// @notice Thrown when a token is already whitelisted |
| 107 | error TOKEN_ALREADY_WHITELISTED(); |
| 108 | /// @notice Thrown when a token is not proposed for whitelisting but expected to be |
| 109 | error NOT_PROPOSED_INCENTIVE_TOKEN(); |
| 110 | /// @notice Thrown when a token is not whitelisted but expected to be |
| 111 | error NOT_WHITELISTED_INCENTIVE_TOKEN(); |
| 112 | /// @notice Thrown when trying to register a keeper that is already registered |
| 113 | error KEEPER_ALREADY_REGISTERED(); |
| 114 | /// @notice Thrown when trying to unregister a keeper that is not registered |
| 115 | error KEEPER_NOT_REGISTERED(); |
| 116 | /// @notice Thrown when the price is not found |
| 117 | error PRICE_NOT_FOUND(); |
| 118 | /// @notice Thrown when the price is stale |
| 119 | error STALE_ORACLE_PRICE(); |
| 120 | /// @notice Thrown when the super oracle is not found |
| 121 | error SUPER_ORACLE_NOT_FOUND(); |
| 122 | /// @notice Thrown when the up token is not found |
| 123 | error UP_NOT_FOUND(); |
| 124 | /// @notice Thrown when the gas info is invalid |
| 125 | error INVALID_GAS_INFO(); |
| 126 | |
| 127 | /*////////////////////////////////////////////////////////////// |
| 128 | EVENTS |
| 129 | //////////////////////////////////////////////////////////////*/ |
| 130 | /// @notice Emitted when an address is set in the registry |
| 131 | /// @param key The key used to reference the address |
| 132 | /// @param value The address value |
| 133 | event AddressSet(bytes32 indexed key, address indexed value); |
| 134 | |
| 135 | /// @notice Emitted when a hook is approved |
| 136 | /// @param hook The address of the approved hook |
| 137 | event HookApproved(address indexed hook); |
| 138 | |
| 139 | /// @notice Emitted when a hook is removed |
| 140 | /// @param hook The address of the removed hook |
| 141 | event HookRemoved(address indexed hook); |
| 142 | |
| 143 | /// @notice Emitted when a fulfill requests hook is registered |
| 144 | /// @param hook The address of the registered fulfill requests hook |
| 145 | event FulfillRequestsHookRegistered(address indexed hook); |
| 146 | |
| 147 | /// @notice Emitted when a fulfill requests hook is unregistered |
| 148 | /// @param hook The address of the unregistered fulfill requests hook |
| 149 | event FulfillRequestsHookUnregistered(address indexed hook); |
| 150 | |
| 151 | /// @notice Emitted when a validator is registered |
| 152 | /// @param validator The address of the registered validator |
| 153 | event ValidatorAdded(address indexed validator); |
| 154 | |
| 155 | /// @notice Emitted when a validator is removed |
| 156 | /// @param validator The address of the removed validator |
| 157 | event ValidatorRemoved(address indexed validator); |
| 158 | |
| 159 | /// @notice Emitted when revenue share is updated |
| 160 | /// @param share The new revenue share percentage |
| 161 | event RevenueShareUpdated(uint256 share); |
| 162 | |
| 163 | /// @notice Emitted when a new fee is proposed |
| 164 | /// @param feeType The type of fee being proposed |
| 165 | /// @param value The proposed fee value (in basis points) |
| 166 | /// @param effectiveTime The timestamp when the fee will be effective |
| 167 | event FeeProposed(FeeType indexed feeType, uint256 value, uint256 effectiveTime); |
| 168 | |
| 169 | /// @notice Emitted when a fee is updated |
| 170 | /// @param feeType The type of fee being updated |
| 171 | /// @param value The new fee value (in basis points) |
| 172 | event FeeUpdated(FeeType indexed feeType, uint256 value); |
| 173 | |
| 174 | /// @notice Emitted when a new SuperBank hook Merkle root is proposed |
| 175 | /// @param hook The hook address for which the Merkle root is being proposed |
| 176 | /// @param newRoot The new Merkle root |
| 177 | /// @param effectiveTime The timestamp when the new root will be effective |
| 178 | event SuperBankHookMerkleRootProposed(address indexed hook, bytes32 newRoot, uint256 effectiveTime); |
| 179 | |
| 180 | /// @notice Emitted when the SuperBank hook Merkle root is updated. |
| 181 | /// @param hook The address of the hook for which the Merkle root was updated. |
| 182 | /// @param newRoot The new Merkle root. |
| 183 | event SuperBankHookMerkleRootUpdated(address indexed hook, bytes32 newRoot); |
| 184 | |
| 185 | /// @notice Emitted when the VaultBank hook Merkle root is proposed |
| 186 | /// @param hook The hook address for which the Merkle root is being proposed |
| 187 | /// @param newRoot The new Merkle root |
| 188 | /// @param effectiveTime The timestamp when the new root will be effective |
| 189 | event VaultBankHookMerkleRootProposed(address indexed hook, bytes32 newRoot, uint256 effectiveTime); |
| 190 | |
| 191 | /// @notice Emitted when the VaultBank hook Merkle root is updated. |
| 192 | /// @param hook The address of the hook for which the Merkle root was updated. |
| 193 | /// @param newRoot The new Merkle root. |
| 194 | event VaultBankHookMerkleRootUpdated(address indexed hook, bytes32 newRoot); |
| 195 | |
| 196 | /// @notice Emitted when the active PPS Oracle's quorum requirement is updated |
| 197 | /// @param quorum The new quorum value |
| 198 | event PPSOracleQuorumUpdated(uint256 quorum); |
| 199 | |
| 200 | /// @notice Emitted when an active PPS oracle is initially set |
| 201 | /// @param oracle The address of the set oracle |
| 202 | event ActivePPSOracleSet(address indexed oracle); |
| 203 | |
| 204 | /// @notice Emitted when a new PPS oracle is proposed |
| 205 | /// @param oracle The address of the proposed oracle |
| 206 | /// @param effectiveTime The timestamp when the proposal will be effective |
| 207 | event ActivePPSOracleProposed(address indexed oracle, uint256 effectiveTime); |
| 208 | |
| 209 | /// @notice Emitted when the active PPS oracle is changed |
| 210 | /// @param oldOracle The address of the previous oracle |
| 211 | /// @param newOracle The address of the new oracle |
| 212 | event ActivePPSOracleChanged(address indexed oldOracle, address indexed newOracle); |
| 213 | |
| 214 | /// @notice Event emitted when manager takeovers are permanently frozen |
| 215 | event ManagerTakeoversFrozen(); |
| 216 | |
| 217 | /// @notice Emitted when a relayer is added |
| 218 | /// @param relayer The address of the added relayer |
| 219 | event RelayerAdded(address indexed relayer); |
| 220 | |
| 221 | /// @notice Emitted when a relayer is removed |
| 222 | /// @param relayer The address of the removed relayer |
| 223 | event RelayerRemoved(address indexed relayer); |
| 224 | |
| 225 | /// @notice Emitted when a vault bank is added |
| 226 | /// @param chainId The chain ID of the added vault bank |
| 227 | /// @param vaultBank The address of the added vault bank |
| 228 | event VaultBankAddressAdded(uint64 indexed chainId, address indexed vaultBank); |
| 229 | |
| 230 | /// @notice Emitted when an executor is added |
| 231 | /// @param executor The address of the added executor |
| 232 | event ExecutorAdded(address indexed executor); |
| 233 | |
| 234 | /// @notice Emitted when an executor is removed |
| 235 | /// @param executor The address of the removed executor |
| 236 | event ExecutorRemoved(address indexed executor); |
| 237 | |
| 238 | /// @notice Emitted when a prover is set |
| 239 | /// @param prover The address of the prover |
| 240 | event ProverSet(address indexed prover); |
| 241 | |
| 242 | /// @notice Emitted when a change to upkeep payments status is proposed |
| 243 | /// @param enabled The proposed status (enabled/disabled) |
| 244 | /// @param effectiveTime The timestamp when the status change will be effective |
| 245 | event UpkeepPaymentsChangeProposed(bool enabled, uint256 effectiveTime); |
| 246 | |
| 247 | /// @notice Emitted when upkeep payments status is changed |
| 248 | /// @param enabled The new status (enabled/disabled) |
| 249 | event UpkeepPaymentsChanged(bool enabled); |
| 250 | |
| 251 | /// @notice Emitted when a new minimum staleness is proposed |
| 252 | /// @param newMinStaleness The proposed minimum staleness value |
| 253 | /// @param effectiveTime The timestamp when the new value will be effective |
| 254 | event MinStalenesProposed(uint256 newMinStaleness, uint256 effectiveTime); |
| 255 | |
| 256 | /// @notice Emitted when the minimum staleness is changed |
| 257 | /// @param newMinStaleness The new minimum staleness value |
| 258 | event MinStalenesChanged(uint256 newMinStaleness); |
| 259 | |
| 260 | /// @notice Emitted when a superform manager is added |
| 261 | /// @param manager The address of the added manager |
| 262 | event SuperformManagerAdded(address indexed manager); |
| 263 | |
| 264 | /// @notice Emitted when a superform manager is removed |
| 265 | /// @param manager The address of the removed manager |
| 266 | event SuperformManagerRemoved(address indexed manager); |
| 267 | |
| 268 | /// @notice Emitted when incentive tokens are proposed for whitelisting |
| 269 | /// @param tokens The addresses of the proposed tokens |
| 270 | /// @param effectiveTime The timestamp when the proposal will be effective |
| 271 | event WhitelistedIncentiveTokensProposed(address[] tokens, uint256 effectiveTime); |
| 272 | |
| 273 | /// @notice Emitted when whitelisted incentive tokens are added |
| 274 | /// @param tokens The addresses of the added tokens |
| 275 | event WhitelistedIncentiveTokensAdded(address[] tokens); |
| 276 | |
| 277 | /// @notice Emitted when whitelisted incentive tokens are removed |
| 278 | /// @param tokens The addresses of the removed tokens |
| 279 | event WhitelistedIncentiveTokensRemoved(address[] tokens); |
| 280 | |
| 281 | /// @notice Emitted when a protected keeper is registered |
| 282 | /// @param keeper Address of the keeper being registered |
| 283 | event ProtectedKeeperRegistered(address indexed keeper); |
| 284 | |
| 285 | /// @notice Emitted when a protected keeper is unregistered |
| 286 | /// @param keeper Address of the keeper being unregistered |
| 287 | event ProtectedKeeperUnregistered(address indexed keeper); |
| 288 | |
| 289 | /// @notice Emitted when gas info is set |
| 290 | /// @param oracle The address of the oracle |
| 291 | /// @param baseGasBatch The base gas for the oracle |
| 292 | /// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle |
| 293 | event GasInfoSet(address indexed oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch); |
| 294 | |
| 295 | /*////////////////////////////////////////////////////////////// |
| 296 | CONTRACT REGISTRY FUNCTIONS |
| 297 | //////////////////////////////////////////////////////////////*/ |
| 298 | /// @notice Sets an address in the registry |
| 299 | /// @param key The key to associate with the address |
| 300 | /// @param value The address value |
| 301 | function setAddress(bytes32 key, address value) external; |
| 302 | |
| 303 | /*////////////////////////////////////////////////////////////// |
| 304 | PERIPHERY CONFIGURATIONS |
| 305 | //////////////////////////////////////////////////////////////*/ |
| 306 | /// @notice Sets the prover address |
| 307 | /// @param prover The address of the prover |
| 308 | function setProver(address prover) external; |
| 309 | |
| 310 | /// @notice Change the primary manager for a strategy |
| 311 | /// @dev Only SuperGovernor can call this function directly |
| 312 | /// @param strategy The strategy address |
| 313 | /// @param newManager The new primary manager address |
| 314 | function changePrimaryManager(address strategy, address newManager) external; |
| 315 | |
| 316 | /// @notice Permanently freezes all manager takeovers globally |
| 317 | function freezeManagerTakeover() external; |
| 318 | |
| 319 | /// @notice Changes the hooks root update timelock duration |
| 320 | /// @param newTimelock New timelock duration in seconds |
| 321 | function changeHooksRootUpdateTimelock(uint256 newTimelock) external; |
| 322 | |
| 323 | /// @notice Proposes a new global hooks Merkle root |
| 324 | /// @dev Only GOVERNOR_ROLE can call this function |
| 325 | /// @param newRoot New Merkle root for global hooks validation |
| 326 | function proposeGlobalHooksRoot(bytes32 newRoot) external; |
| 327 | |
| 328 | /// @notice Sets veto status for global hooks Merkle root |
| 329 | /// @dev Only GUARDIAN_ROLE can call this function |
| 330 | /// @param vetoed Whether to veto (true) or unveto (false) the global hooks root |
| 331 | function setGlobalHooksRootVetoStatus(bool vetoed) external; |
| 332 | |
| 333 | /// @notice Sets veto status for a strategy-specific hooks Merkle root |
| 334 | /// @dev Only GUARDIAN_ROLE can call this function |
| 335 | /// @param strategy Address of the strategy to affect |
| 336 | /// @param vetoed Whether to veto (true) or unveto (false) the strategy hooks root |
| 337 | function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external; |
| 338 | |
| 339 | /// @notice Sets the superasset manager for a superasset |
| 340 | /// @param superAsset The superasset address |
| 341 | /// @param superAssetManager The new superasset manager address |
| 342 | function setSuperAssetManager(address superAsset, address superAssetManager) external; |
| 343 | |
| 344 | /// @notice Adds an ICC to the whitelist |
| 345 | /// @param icc The ICC address to add |
| 346 | function addICCToWhitelist(address icc) external; |
| 347 | |
| 348 | /// @notice Removes an ICC from the whitelist |
| 349 | /// @param icc The ICC address to remove |
| 350 | function removeICCFromWhitelist(address icc) external; |
| 351 | |
| 352 | /// @notice Sets the maximum staleness period for all oracle feeds |
| 353 | /// @param newMaxStaleness The new maximum staleness period in seconds |
| 354 | function setOracleMaxStaleness(uint256 newMaxStaleness) external; |
| 355 | |
| 356 | /// @notice Sets the maximum staleness period for a specific oracle feed |
| 357 | /// @param feed The address of the feed to set staleness for |
| 358 | /// @param newMaxStaleness The new maximum staleness period in seconds |
| 359 | function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external; |
| 360 | |
| 361 | /// @notice Sets the maximum staleness periods for multiple oracle feeds in batch |
| 362 | /// @param feeds The addresses of the feeds to set staleness for |
| 363 | /// @param newMaxStalenessList The new maximum staleness periods in seconds |
| 364 | function setOracleFeedMaxStalenessBatch( |
| 365 | address[] calldata feeds, |
| 366 | uint256[] calldata newMaxStalenessList |
| 367 | ) |
| 368 | external; |
| 369 | |
| 370 | /// @notice Queues an oracle update for execution after timelock period |
| 371 | /// @param bases Base asset addresses |
| 372 | /// @param quotes Quote asset addresses |
| 373 | /// @param providers Provider identifiers |
| 374 | /// @param feeds Feed addresses |
| 375 | function queueOracleUpdate( |
| 376 | address[] calldata bases, |
| 377 | address[] calldata quotes, |
| 378 | bytes32[] calldata providers, |
| 379 | address[] calldata feeds |
| 380 | ) |
| 381 | external; |
| 382 | |
| 383 | /// @notice Queues a provider removal for execution after timelock period |
| 384 | /// @param providers The providers to remove |
| 385 | function queueOracleProviderRemoval(bytes32[] calldata providers) external; |
| 386 | |
| 387 | /// @notice Sets uptime feeds for multiple data oracles in batch (Layer 2 only) |
| 388 | /// @param dataOracles Array of data oracle addresses to set uptime feeds for |
| 389 | /// @param uptimeOracles Array of uptime feed addresses to set |
| 390 | /// @param gracePeriods Array of grace periods in seconds after sequencer restart |
| 391 | function batchSetOracleUptimeFeed( |
| 392 | address[] calldata dataOracles, |
| 393 | address[] calldata uptimeOracles, |
| 394 | uint256[] calldata gracePeriods |
| 395 | ) |
| 396 | external; |
| 397 | |
| 398 | /// @notice Sets the emergency price for a token |
| 399 | /// @param token The address of the token |
| 400 | /// @param price The emergency price to set |
| 401 | function setEmergencyPrice(address token, uint256 price) external; |
| 402 | |
| 403 | /// @notice Sets the emergency price for multiple tokens o |
| 404 | /// @param tokens Array of token addresses |
| 405 | /// @param prices Array of emergency prices |
| 406 | function batchSetEmergencyPrices(address[] calldata tokens, uint256[] calldata prices) external; |
| 407 | |
| 408 | /*////////////////////////////////////////////////////////////// |
| 409 | HOOK MANAGEMENT |
| 410 | //////////////////////////////////////////////////////////////*/ |
| 411 | /// @notice Registers a hook for use in SuperVaults |
| 412 | /// @param hook The address of the hook to register |
| 413 | /// @param isFulfillRequestsHook Whether the hook is a fulfill requests hook |
| 414 | function registerHook(address hook, bool isFulfillRequestsHook) external; |
| 415 | |
| 416 | /// @notice Unregisters a hook from the approved list |
| 417 | /// @param hook The address of the hook to unregister |
| 418 | function unregisterHook(address hook) external; |
| 419 | |
| 420 | /*////////////////////////////////////////////////////////////// |
| 421 | EXECUTOR MANAGEMENT |
| 422 | //////////////////////////////////////////////////////////////*/ |
| 423 | /// @notice Adds an executor to the approved list |
| 424 | /// @param executor The address of the executor to add |
| 425 | function addExecutor(address executor) external; |
| 426 | |
| 427 | /// @notice Removes an executor from the approved list |
| 428 | /// @param executor The address of the executor to remove |
| 429 | function removeExecutor(address executor) external; |
| 430 | |
| 431 | /*////////////////////////////////////////////////////////////// |
| 432 | RELAYER MANAGEMENT |
| 433 | //////////////////////////////////////////////////////////////*/ |
| 434 | /// @notice Adds a relayer to the approved list |
| 435 | /// @param relayer The address of the relayer to add |
| 436 | function addRelayer(address relayer) external; |
| 437 | |
| 438 | /// @notice Removes a relayer from the approved list |
| 439 | /// @param relayer The address of the relayer to remove |
| 440 | function removeRelayer(address relayer) external; |
| 441 | |
| 442 | /*////////////////////////////////////////////////////////////// |
| 443 | VALIDATOR MANAGEMENT |
| 444 | //////////////////////////////////////////////////////////////*/ |
| 445 | /// @notice Adds a validator to the approved list |
| 446 | /// @param validator The address of the validator to add |
| 447 | function addValidator(address validator) external; |
| 448 | |
| 449 | /// @notice Removes a validator from the approved list |
| 450 | /// @param validator The address of the validator to remove |
| 451 | function removeValidator(address validator) external; |
| 452 | |
| 453 | /*////////////////////////////////////////////////////////////// |
| 454 | PPS ORACLE MANAGEMENT |
| 455 | //////////////////////////////////////////////////////////////*/ |
| 456 | /// @notice Sets the active PPS oracle (only if there is no active oracle yet) |
| 457 | /// @param oracle Address of the PPS oracle to set as active |
| 458 | function setActivePPSOracle(address oracle) external; |
| 459 | |
| 460 | /// @notice Proposes a new active PPS oracle (when there is already an active one) |
| 461 | /// @param oracle Address of the PPS oracle to propose as active |
| 462 | function proposeActivePPSOracle(address oracle) external; |
| 463 | |
| 464 | /// @notice Executes a previously proposed PPS oracle change after timelock has expired |
| 465 | function executeActivePPSOracleChange() external; |
| 466 | |
| 467 | /// @notice Sets the quorum requirement for the active PPS Oracle |
| 468 | /// @param quorum The new quorum value |
| 469 | function setPPSOracleQuorum(uint256 quorum) external; |
| 470 | |
| 471 | /*////////////////////////////////////////////////////////////// |
| 472 | REVENUE SHARE MANAGEMENT |
| 473 | //////////////////////////////////////////////////////////////*/ |
| 474 | /// @notice Proposes a new fee value |
| 475 | /// @param feeType The type of fee to propose |
| 476 | /// @param value The proposed fee value (in basis points) |
| 477 | function proposeFee(FeeType feeType, uint256 value) external; |
| 478 | |
| 479 | /// @notice Executes a previously proposed fee update after timelock has expired |
| 480 | /// @param feeType The type of ffee to execute the update for |
| 481 | function executeFeeUpdate(FeeType feeType) external; |
| 482 | |
| 483 | /// @notice Executes an upkeep claim on `SuperVaultAggregator` |
| 484 | /// @param amount The amount to claim |
| 485 | function executeUpkeepClaim(uint256 amount) external; |
| 486 | |
| 487 | /*////////////////////////////////////////////////////////////// |
| 488 | UPKEEP COST MANAGEMENT |
| 489 | //////////////////////////////////////////////////////////////*/ |
| 490 | /// @notice Sets gas info for an oracle |
| 491 | /// @param oracle The address of the oracle |
| 492 | /// @param baseGasBatch The base gas for the oracle |
| 493 | /// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle |
| 494 | function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external; |
| 495 | |
| 496 | /// @notice Proposes a change to upkeep payments enabled status |
| 497 | /// @param enabled The proposed enabled status |
| 498 | function proposeUpkeepPaymentsChange(bool enabled) external; |
| 499 | |
| 500 | /// @notice Executes a previously proposed upkeep payments status change |
| 501 | function executeUpkeepPaymentsChange() external; |
| 502 | |
| 503 | /*////////////////////////////////////////////////////////////// |
| 504 | MIN STALENESS MANAGEMENT |
| 505 | //////////////////////////////////////////////////////////////*/ |
| 506 | /// @notice Proposes a new minimum staleness value to prevent maxStaleness from being set too low |
| 507 | /// @param newMinStaleness The proposed new minimum staleness value in seconds |
| 508 | function proposeMinStaleness(uint256 newMinStaleness) external; |
| 509 | |
| 510 | /// @notice Executes a previously proposed minimum staleness change after timelock has expired |
| 511 | function executeMinStalenesChange() external; |
| 512 | |
| 513 | /*////////////////////////////////////////////////////////////// |
| 514 | SUPERFORM MANAGER MANAGEMENT |
| 515 | //////////////////////////////////////////////////////////////*/ |
| 516 | |
| 517 | /// @notice Adds a manager to the superform managers list |
| 518 | /// @param manager Address of the manager to add |
| 519 | function addSuperformManager(address manager) external; |
| 520 | |
| 521 | /// @notice Removes a manager from the superform managers list |
| 522 | /// @param manager Address of the manager to remove |
| 523 | function removeSuperformManager(address manager) external; |
| 524 | |
| 525 | /*////////////////////////////////////////////////////////////// |
| 526 | VAULT HOOKS MGMT |
| 527 | //////////////////////////////////////////////////////////////*/ |
| 528 | /// @notice Proposes a new Merkle root for a specific hook's allowed targets. |
| 529 | /// @param hook The address of the hook to update the Merkle root for. |
| 530 | /// @param proposedRoot The proposed new Merkle root. |
| 531 | function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external; |
| 532 | |
| 533 | /// @notice Executes a previously proposed Merkle root update for a specific hook if the effective time has passed. |
| 534 | /// @param hook The address of the hook to execute the update for. |
| 535 | function executeVaultBankHookMerkleRootUpdate(address hook) external; |
| 536 | |
| 537 | /*////////////////////////////////////////////////////////////// |
| 538 | SUPERBANK HOOKS MGMT |
| 539 | //////////////////////////////////////////////////////////////*/ |
| 540 | /// @notice Proposes a new Merkle root for a specific hook's allowed targets. |
| 541 | /// @param hook The address of the hook to update the Merkle root for. |
| 542 | /// @param proposedRoot The proposed new Merkle root. |
| 543 | function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external; |
| 544 | |
| 545 | /// @notice Executes a previously proposed Merkle root update for a specific hook if the effective time has passed. |
| 546 | /// @param hook The address of the hook to execute the update for. |
| 547 | function executeSuperBankHookMerkleRootUpdate(address hook) external; |
| 548 | |
| 549 | /*////////////////////////////////////////////////////////////// |
| 550 | VAULT BANK MANAGEMENT |
| 551 | //////////////////////////////////////////////////////////////*/ |
| 552 | /// @notice Adds a vault bank address for a specific chain ID |
| 553 | /// @param chainId The chain ID to add the vault bank for |
| 554 | /// @param vaultBank The address of the vault bank to add |
| 555 | function addVaultBank(uint64 chainId, address vaultBank) external; |
| 556 | |
| 557 | /*////////////////////////////////////////////////////////////// |
| 558 | INCENTIVE TOKEN MANAGEMENT |
| 559 | //////////////////////////////////////////////////////////////*/ |
| 560 | /// @notice Proposes whitelisted incentive tokens |
| 561 | /// @param tokens The addresses of the tokens to add |
| 562 | function proposeAddIncentiveTokens(address[] memory tokens) external; |
| 563 | |
| 564 | /// @notice Executes a previously proposed whitelisted incentive token update after timelock has expired |
| 565 | function executeAddIncentiveTokens() external; |
| 566 | |
| 567 | /// @notice Proposes a new whitelisted incentive token |
| 568 | /// @param tokens The addresses of the tokens to add |
| 569 | function proposeRemoveIncentiveTokens(address[] memory tokens) external; |
| 570 | |
| 571 | /// @notice Executes a previously proposed whitelisted incentive tokens removal after timelock has expired |
| 572 | function executeRemoveIncentiveTokens() external; |
| 573 | |
| 574 | /*////////////////////////////////////////////////////////////// |
| 575 | EXTERNAL VIEW FUNCTIONS |
| 576 | //////////////////////////////////////////////////////////////*/ |
| 577 | /// @notice The identifier of the role that grants access to critical governance functions |
| 578 | function SUPER_GOVERNOR_ROLE() external view returns (bytes32); |
| 579 | |
| 580 | /// @notice The identifier of the role that grants access to daily operations like hooks and validators |
| 581 | function GOVERNOR_ROLE() external view returns (bytes32); |
| 582 | |
| 583 | /// @notice The identifier of the role that grants access to bank management functions |
| 584 | function BANK_MANAGER_ROLE() external view returns (bytes32); |
| 585 | |
| 586 | /// @notice The identifier of the role that grants access to gas management functions |
| 587 | function GAS_MANAGER_ROLE() external view returns (bytes32); |
| 588 | |
| 589 | /// @notice The identifier of the role that grants access to guardian functions |
| 590 | function GUARDIAN_ROLE() external view returns (bytes32); |
| 591 | |
| 592 | /// @notice The identifier of the role that grants access to superasset factory |
| 593 | function SUPER_ASSET_FACTORY() external view returns (bytes32); |
| 594 | |
| 595 | /// @notice Gets an address from the registry |
| 596 | /// @param key The key of the address to get |
| 597 | /// @return The address value |
| 598 | function getAddress(bytes32 key) external view returns (address); |
| 599 | |
| 600 | /// @notice Checks if manager takeovers are frozen |
| 601 | /// @return True if manager takeovers are frozen, false otherwise |
| 602 | function isManagerTakeoverFrozen() external view returns (bool); |
| 603 | |
| 604 | /// @notice Gets the vault bank address for a specific chain ID |
| 605 | /// @param chainId The chain ID to get the vault bank for |
| 606 | /// @return The vault bank address |
| 607 | function getVaultBank(uint64 chainId) external view returns (address); |
| 608 | |
| 609 | /// @notice Checks if a hook is registered |
| 610 | /// @param hook The address of the hook to check |
| 611 | /// @return True if the hook is registered, false otherwise |
| 612 | function isHookRegistered(address hook) external view returns (bool); |
| 613 | |
| 614 | /// @notice Checks if a hook is registered as a fulfill requests hook |
| 615 | /// @param hook The address of the hook to check |
| 616 | /// @return True if the hook is registered as a fulfill requests hook, false otherwise |
| 617 | function isFulfillRequestsHookRegistered(address hook) external view returns (bool); |
| 618 | |
| 619 | /// @notice Gets all registered hooks |
| 620 | /// @return An array of registered hook addresses |
| 621 | function getRegisteredHooks() external view returns (address[] memory); |
| 622 | |
| 623 | /// @notice Gets all registered fulfill requests hooks |
| 624 | /// @return An array of registered fulfill requests hook addresses |
| 625 | function getRegisteredFulfillRequestsHooks() external view returns (address[] memory); |
| 626 | |
| 627 | /// @notice Checks if an address is an approved validator |
| 628 | /// @param validator The address to check |
| 629 | /// @return True if the address is an approved validator, false otherwise |
| 630 | function isValidator(address validator) external view returns (bool); |
| 631 | |
| 632 | /// @notice Checks if an address has the guardian role |
| 633 | /// @param guardian Address to check |
| 634 | /// @return true if the address has the GUARDIAN_ROLE |
| 635 | function isGuardian(address guardian) external view returns (bool); |
| 636 | |
| 637 | /// @notice Checks if an address is an approved relayer |
| 638 | /// @param relayer The address to check |
| 639 | /// @return True if the address is an approved relayer, false otherwise |
| 640 | function isRelayer(address relayer) external view returns (bool); |
| 641 | |
| 642 | /// @notice Checks if an address is an approved executor |
| 643 | /// @param executor The address to check |
| 644 | /// @return True if the address is an approved executor, false otherwise |
| 645 | function isExecutor(address executor) external view returns (bool); |
| 646 | |
| 647 | /// @notice Returns all registered validators |
| 648 | /// @return List of validator addresses |
| 649 | function getValidators() external view returns (address[] memory); |
| 650 | |
| 651 | /// @notice Returns all registered relayers |
| 652 | /// @return List of relayer addresses |
| 653 | function getRelayers() external view returns (address[] memory); |
| 654 | |
| 655 | /// @notice Returns all registered executors |
| 656 | /// @return List of executor addresses |
| 657 | function getExecutors() external view returns (address[] memory); |
| 658 | |
| 659 | /// @notice Gets the proposed active PPS oracle and its effective time |
| 660 | /// @return proposedOracle The proposed oracle address |
| 661 | /// @return effectiveTime The timestamp when the proposed oracle will become effective |
| 662 | function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime); |
| 663 | |
| 664 | /// @notice Gets the current quorum requirement for the active PPS Oracle |
| 665 | /// @return The current quorum requirement |
| 666 | function getPPSOracleQuorum() external view returns (uint256); |
| 667 | |
| 668 | /// @notice Gets the active PPS oracle |
| 669 | /// @return The active PPS oracle address |
| 670 | function getActivePPSOracle() external view returns (address); |
| 671 | |
| 672 | /// @notice Checks if an address is the current active PPS oracle |
| 673 | /// @param oracle The address to check |
| 674 | /// @return True if the address is the active PPS oracle, false otherwise |
| 675 | function isActivePPSOracle(address oracle) external view returns (bool); |
| 676 | |
| 677 | /// @notice Gets the current fee value for a specific fee type |
| 678 | /// @param feeType The type of fee to get |
| 679 | /// @return The current fee value (in basis points) |
| 680 | function getFee(FeeType feeType) external view returns (uint256); |
| 681 | |
| 682 | /// @notice Gets the current upkeep cost per batch update for PPS updates |
| 683 | /// @param oracle The address of the PPS oracle |
| 684 | /// @param chargeableEntries The number of chargeable entries |
| 685 | /// @return The current upkeep cost per batch update in UP tokens |
| 686 | function getUpkeepCostPerBatchUpdate(address oracle, uint256 chargeableEntries) external view returns (uint256); |
| 687 | |
| 688 | /// @notice Gets the proposed upkeep cost per update and its effective time |
| 689 | /// @notice Gets the current minimum staleness value |
| 690 | /// @return The current minimum staleness value in seconds |
| 691 | function getMinStaleness() external view returns (uint256); |
| 692 | |
| 693 | /// @notice Gets the proposed minimum staleness value and its effective time |
| 694 | /// @return proposedMinStaleness The proposed new minimum staleness value |
| 695 | /// @return effectiveTime The timestamp when the new value will become effective |
| 696 | function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime); |
| 697 | |
| 698 | /// @notice Returns the current Merkle root for a specific hook's allowed targets. |
| 699 | /// @param hook The address of the hook to get the Merkle root for. |
| 700 | /// @return The Merkle root for the hook's allowed targets. |
| 701 | function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32); |
| 702 | |
| 703 | /// @notice Returns the current Merkle root for a specific hook's allowed targets. |
| 704 | /// @param hook The address of the hook to get the Merkle root for. |
| 705 | /// @return The Merkle root for the hook's allowed targets. |
| 706 | function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32); |
| 707 | |
| 708 | /// @notice Gets the proposed Merkle root and its effective time for a specific hook. |
| 709 | /// @param hook The address of the hook to get the proposed Merkle root for. |
| 710 | /// @return proposedRoot The proposed Merkle root. |
| 711 | /// @return effectiveTime The timestamp when the proposed root will become effective. |
| 712 | function getProposedSuperBankHookMerkleRoot(address hook) |
| 713 | external |
| 714 | view |
| 715 | returns (bytes32 proposedRoot, uint256 effectiveTime); |
| 716 | |
| 717 | /// @notice Gets the proposed Merkle root and its effective time for a specific hook. |
| 718 | /// @param hook The address of the hook to get the proposed Merkle root for. |
| 719 | /// @return proposedRoot The proposed Merkle root. |
| 720 | /// @return effectiveTime The timestamp when the proposed root will become effective. |
| 721 | function getProposedVaultBankHookMerkleRoot(address hook) |
| 722 | external |
| 723 | view |
| 724 | returns (bytes32 proposedRoot, uint256 effectiveTime); |
| 725 | |
| 726 | /// @notice Checks if a token is whitelisted as an incentive token |
| 727 | /// @param token The address of the token to check |
| 728 | /// @return True if the token is whitelisted as an incentive token, false otherwise |
| 729 | function isWhitelistedIncentiveToken(address token) external view returns (bool); |
| 730 | |
| 731 | /// @notice Gets the prover address |
| 732 | /// @return The address of the prover |
| 733 | function getProver() external view returns (address); |
| 734 | |
| 735 | /// @notice Checks if upkeep payments are currently enabled |
| 736 | /// @return enabled True if upkeep payments are enabled |
| 737 | function isUpkeepPaymentsEnabled() external view returns (bool); |
| 738 | |
| 739 | /// @notice Gets the proposed upkeep payments status and effective time |
| 740 | /// @return enabled The proposed status |
| 741 | /// @return effectiveTime The timestamp when the change becomes effective |
| 742 | function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime); |
| 743 | |
| 744 | /// @notice Checks if an address is a registered superform manager |
| 745 | /// @param manager The address to check |
| 746 | /// @return isSuperform True if the address is a superform manager |
| 747 | function isSuperformManager(address manager) external view returns (bool); |
| 748 | |
| 749 | /// @notice Gets the list of all superform managers |
| 750 | /// @return managers The list of all superform manager addresses |
| 751 | function getAllSuperformManagers() external view returns (address[] memory); |
| 752 | |
| 753 | /// @notice Returns up to `limit` superform managers starting from `cursor` |
| 754 | /// @param cursor The index to start reading from (0 … len-1) |
| 755 | /// @param limit The maximum number of records to return |
| 756 | /// @return chunkOfManagers The array slice [cursor … cursor+limit-1] |
| 757 | /// @return next The next cursor value the caller should use, or 0 to indicate done |
| 758 | function getManagersPaginated( |
| 759 | uint256 cursor, |
| 760 | uint256 limit |
| 761 | ) |
| 762 | external |
| 763 | view |
| 764 | returns (address[] memory chunkOfManagers, uint256 next); |
| 765 | |
| 766 | /// @notice Gets the number of superform managers |
| 767 | /// @return The number of superform managers |
| 768 | function getSuperformManagersCount() external view returns (uint256); |
| 769 | |
| 770 | /// @notice Gets the SUP ID |
| 771 | /// @return The ID of the SUP token |
| 772 | function SUP() external view returns (bytes32); |
| 773 | |
| 774 | /// @notice Gets the UP ID |
| 775 | /// @return The ID of the UP token |
| 776 | function UP() external view returns (bytes32); |
| 777 | |
| 778 | /// @notice Gets the Treasury ID |
| 779 | /// @return The ID for the Treasury in the registry |
| 780 | function TREASURY() external view returns (bytes32); |
| 781 | |
| 782 | /// @notice Gets the SuperOracle ID |
| 783 | /// @return The ID for the SuperOracle in the registry |
| 784 | function SUPER_ORACLE() external view returns (bytes32); |
| 785 | |
| 786 | /// @notice Gets the ECDSA PPS Oracle ID |
| 787 | /// @return The ID for the ECDSA PPS Oracle in the registry |
| 788 | function ECDSAPPSORACLE() external view returns (bytes32); |
| 789 | |
| 790 | /// @notice Gets the SuperVaultAggregator ID |
| 791 | /// @return The ID for the SuperVaultAggregator in the registry |
| 792 | function SUPER_VAULT_AGGREGATOR() external view returns (bytes32); |
| 793 | |
| 794 | /// @notice Gets the SuperBank ID |
| 795 | /// @return The ID for the SuperBank in the registry |
| 796 | function SUPER_BANK() external view returns (bytes32); |
| 797 | |
| 798 | /// @notice Registers a keeper that cannot be added as authorized caller by managers |
| 799 | /// @dev Only governance can register protected keepers |
| 800 | /// @param keeper Address of the keeper to register |
| 801 | function registerProtectedKeeper(address keeper) external; |
| 802 | |
| 803 | /// @notice Unregisters a protected keeper |
| 804 | /// @dev Only governance can unregister protected keepers |
| 805 | /// @param keeper Address of the keeper to unregister |
| 806 | function unregisterProtectedKeeper(address keeper) external; |
| 807 | |
| 808 | /// @notice Checks if an address is a registered protected keeper |
| 809 | /// @param keeper Address to check |
| 810 | /// @return True if the address is a registered protected keeper |
| 811 | function isProtectedKeeper(address keeper) external view returns (bool); |
| 812 | |
| 813 | /// @notice Gets all registered protected keepers |
| 814 | /// @return Array of all registered protected keeper addresses |
| 815 | function getProtectedKeepers() external view returns (address[] memory); |
| 816 | |
| 817 | /// @notice Gets the number of registered protected keepers |
| 818 | /// @return The number of registered protected keepers |
| 819 | function getProtectedKeepersCount() external view returns (uint256); |
| 820 | |
| 821 | /// @notice Gets the gas info for a specific SuperVault PPS Oracle |
| 822 | /// @param oracle_ The address of the oracle to get gas info for |
| 823 | /// @return The gas info for the specified oracle |
| 824 | function getGasInfo(address oracle_) external view returns (GasInfo memory); |
| 825 | } |
| 826 |
0.0%
src/interfaces/SuperAsset/ISuperAssetFactory.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /** |
| 5 | * @title ISuperAssetFactory |
| 6 | * @notice Interface for the SuperAssetFactory contract which deploys SuperAsset and its dependencies |
| 7 | */ |
| 8 | interface ISuperAssetFactory { |
| 9 | /*////////////////////////////////////////////////////////////// |
| 10 | EVENTS |
| 11 | //////////////////////////////////////////////////////////////*/ |
| 12 | |
| 13 | /// @notice Emitted when a new SuperAsset and its dependencies are created |
| 14 | /// @param superAsset Address of the deployed SuperAsset contract |
| 15 | /// @param incentiveFund Address of the deployed IncentiveFundContract |
| 16 | /// @param incentiveCalc Address of the deployed IncentiveCalculationContract |
| 17 | /// @param name Name of the SuperAsset token |
| 18 | /// @param symbol Symbol of the SuperAsset token |
| 19 | event SuperAssetCreated( |
| 20 | address indexed superAsset, address indexed incentiveFund, address incentiveCalc, string name, string symbol |
| 21 | ); |
| 22 | |
| 23 | /*////////////////////////////////////////////////////////////// |
| 24 | ERRORS |
| 25 | //////////////////////////////////////////////////////////////*/ |
| 26 | |
| 27 | /// @notice Thrown when an address parameter is zero |
| 28 | error ZERO_ADDRESS(); |
| 29 | |
| 30 | /// @notice Thrown when the caller is not authorized |
| 31 | error UNAUTHORIZED(); |
| 32 | |
| 33 | /// @notice Thrown when ICC is not whitelisted |
| 34 | error ICC_NOT_WHITELISTED(); |
| 35 | |
| 36 | /*////////////////////////////////////////////////////////////// |
| 37 | STRUCTS |
| 38 | //////////////////////////////////////////////////////////////*/ |
| 39 | |
| 40 | /// @notice Parameters required for creating a new SuperAsset |
| 41 | /// @param name Name of the SuperAsset token |
| 42 | /// @param symbol Symbol of the SuperAsset token |
| 43 | /// @param swapFeeInPercentage Initial swap fee percentage for deposits |
| 44 | /// @param swapFeeOutPercentage Initial swap fee percentage for redemptions |
| 45 | /// @param asset Address of the primary asset |
| 46 | /// @param superAssetManager Address of the manager |
| 47 | /// @param superAssetStrategist Address of the strategist |
| 48 | /// @param incentiveFundManager Address of the incentive fund manager |
| 49 | /// @param incentiveCalculationContract Address of the incentive calculation contract |
| 50 | /// @param tokenInIncentive Address of the token for incoming incentives |
| 51 | /// @param tokenOutIncentive Address of the token for outgoing incentives |
| 52 | struct AssetCreationParams { |
| 53 | string name; |
| 54 | string symbol; |
| 55 | uint256 swapFeeInPercentage; |
| 56 | uint256 swapFeeOutPercentage; |
| 57 | address asset; |
| 58 | address superAssetManager; |
| 59 | address superAssetStrategist; |
| 60 | address incentiveFundManager; |
| 61 | address incentiveCalculationContract; |
| 62 | address tokenInIncentive; |
| 63 | address tokenOutIncentive; |
| 64 | } |
| 65 | |
| 66 | /// @notice Data for a SuperAsset |
| 67 | /// @param superAssetStrategist Address of the strategist |
| 68 | /// @param superAssetManager Address of the manager |
| 69 | /// @param incentiveFundManager Address of the incentive fund manager |
| 70 | /// @param incentiveCalculationContract Address of the incentive calculation contract |
| 71 | /// @param incentiveFundContract Address of the incentive fund contract |
| 72 | struct SuperAssetData { |
| 73 | address superAssetStrategist; |
| 74 | address superAssetManager; |
| 75 | address incentiveFundManager; |
| 76 | address incentiveCalculationContract; |
| 77 | address incentiveFundContract; |
| 78 | } |
| 79 | |
| 80 | /*////////////////////////////////////////////////////////////// |
| 81 | EXTERNAL METHODS |
| 82 | //////////////////////////////////////////////////////////////*/ |
| 83 | |
| 84 | /// @notice Sets the manager for a SuperAsset |
| 85 | /// @param superAsset Address of the SuperAsset contract |
| 86 | /// @param _superAssetManager Address of the manager |
| 87 | function setSuperAssetManager(address superAsset, address _superAssetManager) external; |
| 88 | |
| 89 | /// @notice Sets the strategist for a SuperAsset |
| 90 | /// @param superAsset Address of the SuperAsset contract |
| 91 | /// @param _superAssetStrategist Address of the strategist |
| 92 | function setSuperAssetStrategist(address superAsset, address _superAssetStrategist) external; |
| 93 | |
| 94 | /// @notice Sets the incentive fund manager for a SuperAsset |
| 95 | /// @param superAsset Address of the SuperAsset contract |
| 96 | /// @param _incentiveFundManager Address of the incentive fund manager |
| 97 | function setIncentiveFundManager(address superAsset, address _incentiveFundManager) external; |
| 98 | |
| 99 | /// @notice Sets the incentive calculation contract for a SuperAsset |
| 100 | /// @param superAsset Address of the SuperAsset contract |
| 101 | /// @param incentiveCalculationContract Address of the incentive calculation contract |
| 102 | function setIncentiveCalculationContract(address superAsset, address incentiveCalculationContract) external; |
| 103 | |
| 104 | /// @notice Gets the manager for a SuperAsset |
| 105 | /// @param superAsset Address of the SuperAsset contract |
| 106 | /// @return superAssetManager Address of the manager |
| 107 | function getSuperAssetManager(address superAsset) external view returns (address); |
| 108 | |
| 109 | /// @notice Gets the strategist for a SuperAsset |
| 110 | /// @param superAsset Address of the SuperAsset contract |
| 111 | /// @return superAssetStrategist Address of the strategist |
| 112 | function getSuperAssetStrategist(address superAsset) external view returns (address); |
| 113 | |
| 114 | /// @notice Gets the incentive fund manager for a SuperAsset |
| 115 | /// @param superAsset Address of the SuperAsset contract |
| 116 | /// @return incentiveFundManager Address of the incentive fund manager |
| 117 | function getIncentiveFundManager(address superAsset) external view returns (address); |
| 118 | |
| 119 | /// @notice Gets the incentive calculation contract for a SuperAsset |
| 120 | /// @param superAsset Address of the SuperAsset contract |
| 121 | /// @return incentiveCalculationContract Address of the incentive calculation contract |
| 122 | function getIncentiveCalculationContract(address superAsset) external view returns (address); |
| 123 | |
| 124 | /// @notice Gets the incentive fund contract for a SuperAsset |
| 125 | /// @param superAsset Address of the SuperAsset contract |
| 126 | /// @return incentiveFundContract Address of the incentive fund contract |
| 127 | function getIncentiveFundContract(address superAsset) external view returns (address); |
| 128 | |
| 129 | /// @notice Adds an Incentive Calculation Contract to the whitelist |
| 130 | /// @param icc Address of the Incentive Calculation Contract |
| 131 | function addICCToWhitelist(address icc) external; |
| 132 | |
| 133 | /// @notice Removes an Incentive Calculation Contract from the whitelist |
| 134 | /// @param icc Address of the Incentive Calculation Contract |
| 135 | function removeICCFromWhitelist(address icc) external; |
| 136 | |
| 137 | /// @notice Checks if an Incentive Calculation Contract is whitelisted |
| 138 | /// @param icc Address of the Incentive Calculation Contract |
| 139 | /// @return isValid Whether the Incentive Calculation Contract is whitelisted |
| 140 | function isICCWhitelisted(address icc) external view returns (bool); |
| 141 | |
| 142 | /// @notice Creates a new SuperAsset instance with its dependencies |
| 143 | /// @param params Parameters for creating the SuperAsset |
| 144 | /// @return superAsset Address of the deployed SuperAsset contract |
| 145 | /// @return incentiveFund Address of the deployed IncentiveFundContract |
| 146 | function createSuperAsset(AssetCreationParams calldata params) |
| 147 | external |
| 148 | returns (address superAsset, address incentiveFund); |
| 149 | } |
| 150 |
0.0%
src/interfaces/SuperVault/ISuperVault.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ISuperVault |
| 5 | /// @notice Interface for SuperVault core contract that manages share minting |
| 6 | /// @author Superform Labs |
| 7 | interface ISuperVault { |
| 8 | /*////////////////////////////////////////////////////////////// |
| 9 | ERRORS |
| 10 | //////////////////////////////////////////////////////////////*/ |
| 11 | error INVALID_ASSET(); |
| 12 | error INVALID_STRATEGY(); |
| 13 | error INVALID_ESCROW(); |
| 14 | error ZERO_ADDRESS(); |
| 15 | error ZERO_AMOUNT(); |
| 16 | error INVALID_OWNER_OR_OPERATOR(); |
| 17 | error INVALID_AMOUNT(); |
| 18 | error REQUEST_NOT_FOUND(); |
| 19 | error UNAUTHORIZED(); |
| 20 | error DEADLINE_PASSED(); |
| 21 | error INVALID_SIGNATURE(); |
| 22 | error NOT_IMPLEMENTED(); |
| 23 | error INVALID_NONCE(); |
| 24 | error INVALID_WITHDRAW_PRICE(); |
| 25 | error TRANSFER_FAILED(); |
| 26 | error CAP_EXCEEDED(); |
| 27 | error INVALID_PPS(); |
| 28 | error INVALID_CONTROLLER(); |
| 29 | error CONTROLLER_MUST_EQUAL_OWNER(); |
| 30 | |
| 31 | /*////////////////////////////////////////////////////////////// |
| 32 | EVENTS |
| 33 | //////////////////////////////////////////////////////////////*/ |
| 34 | |
| 35 | event RedeemClaimable( |
| 36 | address indexed user, |
| 37 | uint256 indexed requestId, |
| 38 | uint256 assets, |
| 39 | uint256 shares, |
| 40 | uint256 averageWithdrawPrice, |
| 41 | uint256 accumulatorShares, |
| 42 | uint256 accumulatorCostBasis |
| 43 | ); |
| 44 | |
| 45 | event NonceInvalidated(address indexed sender, bytes32 indexed nonce); |
| 46 | |
| 47 | event RedeemRequestCancelled(address indexed controller, address indexed sender); |
| 48 | |
| 49 | event SuperGovernorSet(address indexed superGovernor); |
| 50 | |
| 51 | /*////////////////////////////////////////////////////////////// |
| 52 | EXTERNAL METHODS |
| 53 | //////////////////////////////////////////////////////////////*/ |
| 54 | |
| 55 | function cancelRedeem(address controller) external; |
| 56 | |
| 57 | /// @notice Burn shares, only callable by strategy |
| 58 | /// @param amount The amount of shares to burn |
| 59 | function burnShares(uint256 amount) external; |
| 60 | |
| 61 | /// @notice Callback function for when a redeem becomes claimable |
| 62 | /// @param user The user whose redeem is claimable |
| 63 | /// @param assets The amount of assets to be received |
| 64 | /// @param shares The amount of shares redeemed |
| 65 | /// @param averageWithdrawPrice The average price of the redeem |
| 66 | /// @param accumulatorShares The amount of shares in the accumulator |
| 67 | /// @param accumulatorCostBasis The cost basis of the accumulator |
| 68 | function onRedeemClaimable( |
| 69 | address user, |
| 70 | uint256 assets, |
| 71 | uint256 shares, |
| 72 | uint256 averageWithdrawPrice, |
| 73 | uint256 accumulatorShares, |
| 74 | uint256 accumulatorCostBasis |
| 75 | ) |
| 76 | external; |
| 77 | } |
| 78 |
0.0%
src/interfaces/SuperVault/ISuperVaultAggregator.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
| 5 | import { ISuperVaultStrategy } from "../SuperVault/ISuperVaultStrategy.sol"; |
| 6 | |
| 7 | /// @title ISuperVaultAggregator |
| 8 | /// @author Superform Labs |
| 9 | /// @notice Interface for the SuperVaultAggregator contract |
| 10 | /// @dev Registry and PPS oracle for all SuperVaults |
| 11 | interface ISuperVaultAggregator { |
| 12 | /*////////////////////////////////////////////////////////////// |
| 13 | STRUCTS |
| 14 | //////////////////////////////////////////////////////////////*/ |
| 15 | /// @notice Arguments for forwarding PPS updates to avoid stack too deep errors |
| 16 | /// @param strategy Address of the strategy being updated |
| 17 | /// @param isExempt Whether the update is exempt from paying upkeep |
| 18 | /// @param pps New price-per-share value |
| 19 | /// @param ppsStdev Standard deviation of the price-per-share |
| 20 | /// @param validatorSet Number of validators who calculated this PPS |
| 21 | /// @param totalValidators Total number of validators in the network |
| 22 | /// @param timestamp Timestamp when the value was generated |
| 23 | /// @param upkeepCost Amount of upkeep tokens to charge if not exempt |
| 24 | struct PPSUpdateData { |
| 25 | address strategy; |
| 26 | bool isExempt; |
| 27 | uint256 pps; |
| 28 | uint256 ppsStdev; |
| 29 | uint256 validatorSet; |
| 30 | uint256 totalValidators; |
| 31 | uint256 timestamp; |
| 32 | uint256 upkeepCost; |
| 33 | } |
| 34 | |
| 35 | /// @notice Local variables for vault creation to avoid stack too deep |
| 36 | /// @param currentNonce Current vault creation nonce |
| 37 | /// @param salt Salt for deterministic proxy creation |
| 38 | /// @param success Whether asset decimals retrieval was successful |
| 39 | /// @param assetDecimals Decimals of the underlying asset |
| 40 | /// @param underlyingDecimals Final decimals to use (18 if retrieval failed) |
| 41 | /// @param initialPPS Initial price-per-share value |
| 42 | struct VaultCreationLocalVars { |
| 43 | uint256 currentNonce; |
| 44 | bytes32 salt; |
| 45 | bool success; |
| 46 | uint8 assetDecimals; |
| 47 | uint8 underlyingDecimals; |
| 48 | uint256 initialPPS; |
| 49 | } |
| 50 | |
| 51 | /// @notice Strategy configuration and state data |
| 52 | /// @param pps Current price-per-share value |
| 53 | /// @param lastUpdateTimestamp Last time PPS was updated |
| 54 | /// @param minUpdateInterval Minimum time interval between PPS updates |
| 55 | /// @param maxStaleness Maximum time allowed between PPS updates before staleness |
| 56 | /// @param isPaused Whether the strategy is paused |
| 57 | /// @param mainManager Address of the primary manager controlling the strategy |
| 58 | /// @param secondaryManagers Set of secondary managers that can manage the strategy |
| 59 | /// @param authorizedCallers List of callers authorized to update PPS without paying upkeep |
| 60 | struct StrategyData { |
| 61 | uint256 pps; |
| 62 | uint256 ppsStdev; |
| 63 | uint256 lastUpdateTimestamp; |
| 64 | uint256 minUpdateInterval; |
| 65 | uint256 maxStaleness; |
| 66 | bool isPaused; |
| 67 | address mainManager; |
| 68 | EnumerableSet.AddressSet secondaryManagers; |
| 69 | EnumerableSet.AddressSet authorizedCallers; |
| 70 | // Manager change proposal data |
| 71 | address proposedManager; |
| 72 | uint256 managerChangeEffectiveTime; |
| 73 | // Hook validation data |
| 74 | bytes32 managerHooksRoot; |
| 75 | // Hook root update proposal data |
| 76 | bytes32 proposedHooksRoot; |
| 77 | uint256 hooksRootEffectiveTime; |
| 78 | // Veto status |
| 79 | bool hooksRootVetoed; |
| 80 | // PPS Verification thresholds |
| 81 | uint256 dispersionThreshold; // Threshold for standard deviation / mean |
| 82 | uint256 deviationThreshold; // Threshold for abs(new - current) / current |
| 83 | uint256 mnThreshold; // Threshold for validatorSet / totalValidators ratio, scaled by 1e18 |
| 84 | // Banned global leaves mapping |
| 85 | mapping(bytes32 => bool) bannedLeaves; // Mapping of leaf hash to banned status |
| 86 | } |
| 87 | |
| 88 | /// @notice Parameters for creating a new SuperVault trio |
| 89 | /// @param asset Address of the underlying asset |
| 90 | /// @param name Name of the vault token |
| 91 | /// @param symbol Symbol of the vault token |
| 92 | /// @param mainManager Address of the vault mainManager |
| 93 | /// @param minUpdateInterval Minimum time interval between PPS updates |
| 94 | /// @param maxStaleness Maximum time allowed between PPS updates before staleness |
| 95 | /// @param feeConfig Fee configuration for the vault |
| 96 | struct VaultCreationParams { |
| 97 | address asset; |
| 98 | string name; |
| 99 | string symbol; |
| 100 | address mainManager; |
| 101 | address[] secondaryManagers; |
| 102 | uint256 minUpdateInterval; |
| 103 | uint256 maxStaleness; |
| 104 | ISuperVaultStrategy.FeeConfig feeConfig; |
| 105 | } |
| 106 | |
| 107 | /// @notice Struct to hold cached hook validation state variables to avoid stack too deep |
| 108 | /// @param globalHooksRootVetoed Cached global hooks root veto status |
| 109 | /// @param globalHooksRoot Cached global hooks root |
| 110 | /// @param strategyHooksRootVetoed Cached strategy hooks root veto status |
| 111 | /// @param strategyRoot Cached strategy hooks root |
| 112 | struct HookValidationCache { |
| 113 | bool globalHooksRootVetoed; |
| 114 | bytes32 globalHooksRoot; |
| 115 | bool strategyHooksRootVetoed; |
| 116 | bytes32 strategyRoot; |
| 117 | } |
| 118 | |
| 119 | /// @notice Arguments for validating a hook to avoid stack too deep |
| 120 | /// @param hookAddress Address of the hook contract |
| 121 | /// @param hookArgs Encoded arguments for the hook operation |
| 122 | /// @param globalProof Merkle proof for the global root |
| 123 | /// @param strategyProof Merkle proof for the strategy-specific root |
| 124 | struct ValidateHookArgs { |
| 125 | address hookAddress; |
| 126 | bytes hookArgs; |
| 127 | bytes32[] globalProof; |
| 128 | bytes32[] strategyProof; |
| 129 | } |
| 130 | |
| 131 | /*////////////////////////////////////////////////////////////// |
| 132 | EVENTS |
| 133 | //////////////////////////////////////////////////////////////*/ |
| 134 | /// @notice Emitted when a new vault trio is created |
| 135 | /// @param vault Address of the created SuperVault |
| 136 | /// @param strategy Address of the created SuperVaultStrategy |
| 137 | /// @param escrow Address of the created SuperVaultEscrow |
| 138 | /// @param asset Address of the underlying asset |
| 139 | /// @param name Name of the vault token |
| 140 | /// @param symbol Symbol of the vault token |
| 141 | /// @param nonce The nonce used for vault creation |
| 142 | event VaultDeployed( |
| 143 | address indexed vault, |
| 144 | address indexed strategy, |
| 145 | address escrow, |
| 146 | address asset, |
| 147 | string name, |
| 148 | string symbol, |
| 149 | uint256 indexed nonce |
| 150 | ); |
| 151 | |
| 152 | /// @notice Emitted when a PPS value is updated |
| 153 | /// @param strategy Address of the strategy |
| 154 | /// @param pps New price-per-share value |
| 155 | /// @param ppsStdev Standard deviation of price-per-share value |
| 156 | /// @param validatorSet Number of validators who calculated this PPS |
| 157 | /// @param totalValidators Total number of validators in the network |
| 158 | /// @param timestamp Timestamp of the update |
| 159 | event PPSUpdated( |
| 160 | address indexed strategy, |
| 161 | uint256 pps, |
| 162 | uint256 ppsStdev, |
| 163 | uint256 validatorSet, |
| 164 | uint256 totalValidators, |
| 165 | uint256 timestamp |
| 166 | ); |
| 167 | |
| 168 | /// @notice Emitted when a strategy is paused due to missed updates |
| 169 | /// @param strategy Address of the paused strategy |
| 170 | event StrategyPaused(address indexed strategy); |
| 171 | |
| 172 | /// @notice Emitted when a strategy is unpaused |
| 173 | /// @param strategy Address of the unpaused strategy |
| 174 | event StrategyUnpaused(address indexed strategy); |
| 175 | |
| 176 | /// @notice Emitted when a strategy validation check fails but execution continues |
| 177 | /// @param strategy Address of the strategy that failed the check |
| 178 | /// @param reason String description of which check failed |
| 179 | event StrategyCheckFailed(address indexed strategy, string reason); |
| 180 | |
| 181 | /// @notice Emitted when upkeep tokens are deposited |
| 182 | /// @param manager Address of the manager |
| 183 | /// @param amount Amount of UP tokens deposited |
| 184 | event UpkeepDeposited(address indexed manager, uint256 amount); |
| 185 | |
| 186 | /// @notice Emitted when upkeep tokens are withdrawn |
| 187 | /// @param manager Address of the manager |
| 188 | /// @param amount Amount of UP tokens withdrawn |
| 189 | event UpkeepWithdrawn(address indexed manager, uint256 amount); |
| 190 | |
| 191 | /// @notice Emitted when upkeep tokens are spent for validation |
| 192 | /// @param manager Address of the manager |
| 193 | /// @param amount Amount of UP tokens spent |
| 194 | event UpkeepSpent(address indexed manager, uint256 amount); |
| 195 | |
| 196 | /// @notice Emitted when stake tokens are deposited |
| 197 | /// @param manager Address of the manager |
| 198 | /// @param amount Amount of UP tokens deposited as stake |
| 199 | event StakeDeposited(address indexed manager, uint256 amount); |
| 200 | |
| 201 | /// @notice Emitted when stake tokens are withdrawn |
| 202 | /// @param manager Address of the manager |
| 203 | /// @param amount Amount of UP tokens withdrawn from stake |
| 204 | event StakeWithdrawn(address indexed manager, uint256 amount); |
| 205 | |
| 206 | /// @notice Emitted when a manager's stake is slashed |
| 207 | /// @param manager The manager whose stake was slashed |
| 208 | /// @param amount The amount of UP tokens slashed |
| 209 | event StakeSlashed(address indexed manager, uint256 amount); |
| 210 | |
| 211 | /// @notice Emitted when an authorized caller is added for a strategy |
| 212 | /// @param strategy Address of the strategy |
| 213 | /// @param caller Address of the authorized caller |
| 214 | event AuthorizedCallerAdded(address indexed strategy, address indexed caller); |
| 215 | |
| 216 | /// @notice Emitted when an authorized caller is removed for a strategy |
| 217 | /// @param strategy Address of the strategy |
| 218 | /// @param caller Address of the removed caller |
| 219 | event AuthorizedCallerRemoved(address indexed strategy, address indexed caller); |
| 220 | |
| 221 | /// @notice Emitted when a secondary manager is added to a strategy |
| 222 | /// @param strategy Address of the strategy |
| 223 | /// @param manager Address of the manager added |
| 224 | event SecondaryManagerAdded(address indexed strategy, address indexed manager); |
| 225 | |
| 226 | /// @notice Emitted when a secondary manager is removed from a strategy |
| 227 | /// @param strategy Address of the strategy |
| 228 | /// @param manager Address of the manager removed |
| 229 | event SecondaryManagerRemoved(address indexed strategy, address indexed manager); |
| 230 | |
| 231 | /// @notice Emitted when a primary manager is changed |
| 232 | /// @param strategy Address of the strategy |
| 233 | /// @param oldManager Address of the old primary manager |
| 234 | /// @param newManager Address of the new primary manager |
| 235 | event PrimaryManagerChanged( |
| 236 | address indexed strategy, address indexed oldManager, address indexed newManager |
| 237 | ); |
| 238 | |
| 239 | /// @notice Emitted when a primary manager is changed to a superform manager |
| 240 | /// @param strategy Address of the strategy |
| 241 | /// @param oldManager Address of the old primary manager |
| 242 | /// @param newManager Address of the new primary manager (superform manager) |
| 243 | event PrimaryManagerChangedToSuperform( |
| 244 | address indexed strategy, address indexed oldManager, address indexed newManager |
| 245 | ); |
| 246 | |
| 247 | /// @notice Emitted when a change to primary manager is proposed by a secondary manager |
| 248 | /// @param strategy Address of the strategy |
| 249 | /// @param proposer Address of the secondary manager who made the proposal |
| 250 | /// @param newManager Address of the proposed new primary manager |
| 251 | /// @param effectiveTime Timestamp when the proposal can be executed |
| 252 | event PrimaryManagerChangeProposed( |
| 253 | address indexed strategy, address indexed proposer, address indexed newManager, uint256 effectiveTime |
| 254 | ); |
| 255 | |
| 256 | /// @notice Emitted when a PPS update is stale (Validators could get slashed for innactivity) |
| 257 | /// @param strategy Address of the strategy |
| 258 | /// @param updateAuthority Address of the update authority |
| 259 | /// @param timestamp Timestamp of the stale update |
| 260 | event StaleUpdate(address indexed strategy, address indexed updateAuthority, uint256 timestamp); |
| 261 | |
| 262 | /// @notice Emitted when the upkeep cost per update is changed |
| 263 | /// @param oldCost Previous upkeep cost per update |
| 264 | /// @param newCost New upkeep cost per update |
| 265 | event UpkeepCostUpdated(uint256 oldCost, uint256 newCost); |
| 266 | |
| 267 | /// @notice Emitted when the global hooks Merkle root is being updated |
| 268 | /// @param root New root value |
| 269 | /// @param effectiveTime Timestamp when the root becomes effective |
| 270 | event GlobalHooksRootUpdateProposed(bytes32 indexed root, uint256 effectiveTime); |
| 271 | |
| 272 | /// @notice Emitted when the global hooks Merkle root is updated |
| 273 | /// @param oldRoot Previous root value |
| 274 | /// @param newRoot New root value |
| 275 | event GlobalHooksRootUpdated(bytes32 indexed oldRoot, bytes32 newRoot); |
| 276 | |
| 277 | /// @notice Emitted when a strategy-specific hooks Merkle root is updated |
| 278 | /// @param strategy Address of the strategy |
| 279 | /// @param oldRoot Previous root value (may be zero) |
| 280 | /// @param newRoot New root value |
| 281 | event StrategyHooksRootUpdated(address indexed strategy, bytes32 oldRoot, bytes32 newRoot); |
| 282 | |
| 283 | /// @notice Emitted when a strategy-specific hooks Merkle root is proposed |
| 284 | /// @param strategy Address of the strategy |
| 285 | /// @param proposer Address of the account proposing the new root |
| 286 | /// @param root New root value |
| 287 | /// @param effectiveTime Timestamp when the root becomes effective |
| 288 | event StrategyHooksRootUpdateProposed( |
| 289 | address indexed strategy, address indexed proposer, bytes32 root, uint256 effectiveTime |
| 290 | ); |
| 291 | |
| 292 | /// @notice Emitted when a proposed global hooks root update is vetoed by SuperGovernor |
| 293 | /// @param vetoed Whether the root is being vetoed (true) or unvetoed (false) |
| 294 | /// @param root The root value affected |
| 295 | event GlobalHooksRootVetoStatusChanged(bool vetoed, bytes32 indexed root); |
| 296 | |
| 297 | /// @notice Emitted when a strategy's hooks Merkle root veto status changes |
| 298 | /// @param strategy Address of the strategy |
| 299 | /// @param vetoed Whether the root is being vetoed (true) or unvetoed (false) |
| 300 | /// @param root The root value affected |
| 301 | event StrategyHooksRootVetoStatusChanged(address indexed strategy, bool vetoed, bytes32 indexed root); |
| 302 | |
| 303 | /// @notice Emitted when a strategy's PPS verification thresholds are updated |
| 304 | /// @param strategy Address of the strategy |
| 305 | /// @param dispersionThreshold New dispersion threshold (stddev/mean) |
| 306 | /// @param deviationThreshold New deviation threshold (abs diff/current) |
| 307 | /// @param mnThreshold New M/N threshold (validatorSet/totalValidators) |
| 308 | event PPSVerificationThresholdsUpdated( |
| 309 | address indexed strategy, uint256 dispersionThreshold, uint256 deviationThreshold, uint256 mnThreshold |
| 310 | ); |
| 311 | |
| 312 | /// @notice Emitted when the hooks root update timelock is changed |
| 313 | /// @param newTimelock New timelock duration in seconds |
| 314 | event HooksRootUpdateTimelockChanged(uint256 newTimelock); |
| 315 | |
| 316 | /// @notice Emitted when global leaves status is changed for a strategy |
| 317 | /// @param strategy Address of the strategy |
| 318 | /// @param leaves Array of leaf hashes that had their status changed |
| 319 | /// @param statuses Array of new banned statuses (true = banned, false = allowed) |
| 320 | event GlobalLeavesStatusChanged(address indexed strategy, bytes32[] leaves, bool[] statuses); |
| 321 | |
| 322 | /// @notice Emitted when a proposed global hooks root update is vetoed by a guardian |
| 323 | /// @param guardian Address of the guardian who vetoed the update |
| 324 | /// @param root The vetoed root value |
| 325 | event GlobalHooksRootVetoed(address indexed guardian, bytes32 indexed root); |
| 326 | |
| 327 | /// @notice Emitted when a proposed strategy hooks root update is vetoed by a guardian |
| 328 | /// @param guardian Address of the guardian who vetoed the update |
| 329 | /// @param strategy Address of the strategy whose root update was vetoed |
| 330 | /// @param root The vetoed root value |
| 331 | event StrategyHooksRootVetoed(address indexed guardian, address indexed strategy, bytes32 indexed root); |
| 332 | |
| 333 | /// @notice Emitted when upkeep is claimed |
| 334 | /// @param superBank Address of the superBank |
| 335 | /// @param amount Amount of upkeep claimed |
| 336 | event UpkeepClaimed(address indexed superBank, uint256 amount); |
| 337 | |
| 338 | /// @notice Emitted when PPS update is too frequent (before minUpdateInterval) |
| 339 | event UpdateTooFrequent(); |
| 340 | |
| 341 | /// @notice Emitted when PPS update timestamp is not monotonically increasing |
| 342 | event TimestampNotMonotonic(); |
| 343 | |
| 344 | /// @notice Emitted when a manager does not have enough upkeep balance |
| 345 | event InsufficientUpkeep(address indexed strategy, address indexed manager, uint256 balance, uint256 cost); |
| 346 | |
| 347 | /// @notice Emitted when the provided timestamp is too large |
| 348 | event ProvidedTimestampExceedsBlockTimestamp(address indexed strategy, uint256 argsTimestamp, uint256 blockTimestamp); |
| 349 | |
| 350 | /// @notice Emitted when a strategy is unknown |
| 351 | event UnknownStrategy(address indexed strategy); |
| 352 | |
| 353 | /// @notice Emitted when a strategy is managed by Superform |
| 354 | event SuperformManager(address indexed strategy, address indexed manager); |
| 355 | |
| 356 | /// @notice Emitted when the caller is authorized |
| 357 | event AuthorizedCaller(address indexed strategy, address indexed caller); |
| 358 | |
| 359 | /*/////////////////////////////////////////////////////////////// |
| 360 | ERRORS |
| 361 | //////////////////////////////////////////////////////////////*/ |
| 362 | /// @notice Thrown when address provided is zero |
| 363 | error ZERO_ADDRESS(); |
| 364 | /// @notice Thrown when amount provided is zero |
| 365 | error ZERO_AMOUNT(); |
| 366 | /// @notice Thrown when array length is zero |
| 367 | error ZERO_ARRAY_LENGTH(); |
| 368 | /// @notice Thrown when array length is zero |
| 369 | error ARRAY_LENGTH_MISMATCH(); |
| 370 | /// @notice Thrown when asset is invalid |
| 371 | error INVALID_ASSET(); |
| 372 | /// @notice Thrown when insufficient upkeep balance for operation |
| 373 | error INSUFFICIENT_UPKEEP(); |
| 374 | /// @notice Thrown when vault is paused but operation requires active state |
| 375 | error VAULT_PAUSED(); |
| 376 | /// @notice Thrown when caller is not an approved PPS oracle |
| 377 | error UNAUTHORIZED_PPS_ORACLE(); |
| 378 | /// @notice Thrown when PPS update is too stale (after maxStaleness) |
| 379 | error UPDATE_TOO_STALE(); |
| 380 | /// @notice Thrown when caller is not authorized for update |
| 381 | error UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 382 | /// @notice Thrown when strategy address is not a known SuperVault strategy |
| 383 | error UNKNOWN_STRATEGY(); |
| 384 | /// @notice Thrown when withdrawing more upkeep than available |
| 385 | error INSUFFICIENT_UPKEEP_BALANCE(); |
| 386 | /// @notice Thrown when withdrawing more stake than available |
| 387 | error INSUFFICIENT_STAKE_BALANCE(); |
| 388 | /// @notice Thrown when caller is already authorized |
| 389 | error CALLER_ALREADY_AUTHORIZED(); |
| 390 | /// @notice Thrown when caller is not authorized |
| 391 | error CALLER_NOT_AUTHORIZED(); |
| 392 | /// @notice Thrown when array index is out of bounds |
| 393 | error INDEX_OUT_OF_BOUNDS(); |
| 394 | /// @notice Thrown when attempting to remove the last manager |
| 395 | error CANNOT_REMOVE_LAST_MANAGER(); |
| 396 | /// @notice Thrown when attempting to add a manager that already exists |
| 397 | error MANAGER_ALREADY_EXISTS(); |
| 398 | /// @notice Thrown when there is no pending global hooks root change |
| 399 | error NO_PENDING_GLOBAL_ROOT_CHANGE(); |
| 400 | /// @notice Thrown when attempting to execute an in-progress manager change before timelock elapsed |
| 401 | error MANAGER_CHANGE_NOT_READY(); |
| 402 | /// @notice Thrown when attempting to execute a hooks root change before timelock has elapsed |
| 403 | error ROOT_UPDATE_NOT_READY(); |
| 404 | /// @notice Thrown when a provided hook fails Merkle proof validation |
| 405 | error HOOK_VALIDATION_FAILED(); |
| 406 | /// @notice Thrown when a non-guardian tries to veto a root update |
| 407 | error NOT_A_GUARDIAN(); |
| 408 | /// @notice Thrown when trying to veto a root update that doesn't exist |
| 409 | error NO_PENDING_ROOT_UPDATE(); |
| 410 | /// @notice Thrown when manager is not found |
| 411 | error MANAGER_NOT_FOUND(); |
| 412 | /// @notice Thrown when there is no pending manager change proposal |
| 413 | error NO_PENDING_MANAGER_CHANGE(); |
| 414 | /// @notice Thrown when caller is not authorized to update settings |
| 415 | error UNAUTHORIZED_CALLER(); |
| 416 | /// @notice Thrown when the timelock for a proposed change has not expired |
| 417 | error TIMELOCK_NOT_EXPIRED(); |
| 418 | /// @notice Thrown when an array length is invalid |
| 419 | error INVALID_ARRAY_LENGTH(); |
| 420 | /// @notice Thrown when trying to add a protected keeper as an authorized caller |
| 421 | error CANNOT_ADD_PROTECTED_KEEPER(); |
| 422 | /// @notice Thrown when the provided maxStaleness is less than the minimum required staleness |
| 423 | error MAX_STALENESS_TOO_LOW(); |
| 424 | /// @notice Thrown when arrays have mismatched lengths |
| 425 | error MISMATCHED_ARRAY_LENGTHS(); |
| 426 | /// @notice Thrown when timestamp is invalid |
| 427 | error INVALID_TIMESTAMP(uint256 index); |
| 428 | /// @notice Thrown when too many secondary managers are added |
| 429 | error TOO_MANY_SECONDARY_MANAGERS(); |
| 430 | /// @notice Thrown when the number of strategies exceeds the maximum allowed |
| 431 | error MAX_STRATEGIES_EXCEEDED(); |
| 432 | /// @notice Thrown when provided timestamp is too large |
| 433 | error TIMESTAMP_EXCEEDS_BLOCK(); |
| 434 | |
| 435 | /*////////////////////////////////////////////////////////////// |
| 436 | VAULT CREATION |
| 437 | //////////////////////////////////////////////////////////////*/ |
| 438 | /// @notice Creates a new SuperVault trio (SuperVault, SuperVaultStrategy, SuperVaultEscrow) |
| 439 | /// @param params Parameters for the new vault creation |
| 440 | /// @return superVault Address of the created SuperVault |
| 441 | /// @return strategy Address of the created SuperVaultStrategy |
| 442 | /// @return escrow Address of the created SuperVaultEscrow |
| 443 | function createVault(VaultCreationParams calldata params) |
| 444 | external |
| 445 | returns (address superVault, address strategy, address escrow); |
| 446 | |
| 447 | /*////////////////////////////////////////////////////////////// |
| 448 | PPS UPDATE FUNCTIONS |
| 449 | //////////////////////////////////////////////////////////////*/ |
| 450 | /// @notice Arguments for batch forwarding PPS updates |
| 451 | /// @param strategies Array of strategy addresses |
| 452 | /// @param ppss Array of price-per-share values |
| 453 | /// @param ppsStdevs Array of standard deviations of price-per-share values |
| 454 | /// @param validatorSets Array of numbers of validators who calculated each PPS |
| 455 | /// @param totalValidators Total number of validators in the network |
| 456 | /// @param timestamps Array of timestamps when values were generated |
| 457 | struct ForwardPPSArgs { |
| 458 | address[] strategies; |
| 459 | uint256[] ppss; |
| 460 | uint256[] ppsStdevs; |
| 461 | uint256[] validatorSets; |
| 462 | uint256[] totalValidators; |
| 463 | uint256[] timestamps; |
| 464 | address updateAuthority; |
| 465 | } |
| 466 | |
| 467 | /// @notice Batch forwards validated PPS updates to multiple strategies |
| 468 | /// @param args Struct containing all batch PPS update parameters |
| 469 | function forwardPPS(ForwardPPSArgs calldata args) external; |
| 470 | |
| 471 | /*////////////////////////////////////////////////////////////// |
| 472 | UPKEEP MANAGEMENT |
| 473 | //////////////////////////////////////////////////////////////*/ |
| 474 | |
| 475 | /// @notice Deposits UP tokens for manager upkeep |
| 476 | /// @param manager Address of the manager to deposit for |
| 477 | /// @param amount Amount of UP tokens to deposit |
| 478 | function depositUpkeep(address manager, uint256 amount) external; |
| 479 | |
| 480 | /// @notice Withdraws UP tokens from manager upkeep balance |
| 481 | /// @param amount Amount of UP tokens to withdraw |
| 482 | function withdrawUpkeep(uint256 amount) external; |
| 483 | |
| 484 | /// @notice Claims upkeep tokens from the contract |
| 485 | /// @param amount Amount of UP tokens to claim |
| 486 | function claimUpkeep(uint256 amount) external; |
| 487 | |
| 488 | /*////////////////////////////////////////////////////////////// |
| 489 | STAKE MANAGEMENT |
| 490 | //////////////////////////////////////////////////////////////*/ |
| 491 | |
| 492 | /// @notice Deposits UP tokens as stake for manager economic security |
| 493 | /// @param manager Address of the manager to deposit stake for |
| 494 | /// @param amount Amount of UP tokens to deposit as stake |
| 495 | function depositStake(address manager, uint256 amount) external; |
| 496 | |
| 497 | /// @notice Withdraws UP tokens from manager stake balance |
| 498 | /// @param amount Amount of UP tokens to withdraw from stake |
| 499 | function withdrawStake(uint256 amount) external; |
| 500 | |
| 501 | /// @notice Slashes a manager's stake balance by a specified amount |
| 502 | /// @param manager The manager whose stake will be slashed |
| 503 | /// @param amount The amount of UP tokens to slash from the manager's stake balance |
| 504 | function slashStake(address manager, uint256 amount) external; |
| 505 | |
| 506 | /*////////////////////////////////////////////////////////////// |
| 507 | AUTHORIZED CALLER MANAGEMENT |
| 508 | //////////////////////////////////////////////////////////////*/ |
| 509 | |
| 510 | /// @notice Adds an authorized caller for a strategy |
| 511 | /// @param strategy Address of the strategy |
| 512 | /// @param caller Address of the caller to authorize |
| 513 | function addAuthorizedCaller(address strategy, address caller) external; |
| 514 | |
| 515 | /// @notice Removes an authorized caller for a strategy |
| 516 | /// @param strategy Address of the strategy |
| 517 | /// @param caller Address of the caller to remove |
| 518 | function removeAuthorizedCaller(address strategy, address caller) external; |
| 519 | |
| 520 | /*////////////////////////////////////////////////////////////// |
| 521 | MANAGER MANAGEMENT FUNCTIONS |
| 522 | //////////////////////////////////////////////////////////////*/ |
| 523 | |
| 524 | /// @notice Adds a secondary manager to a strategy |
| 525 | /// @notice A manager can either be secondary or primary |
| 526 | /// @param strategy Address of the strategy |
| 527 | /// @param manager Address of the manager to add |
| 528 | function addSecondaryManager(address strategy, address manager) external; |
| 529 | |
| 530 | /// @notice Removes a secondary manager from a strategy |
| 531 | /// @param strategy Address of the strategy |
| 532 | /// @param manager Address of the manager to remove |
| 533 | function removeSecondaryManager(address strategy, address manager) external; |
| 534 | |
| 535 | /// @notice Changes the primary manager of a strategy immediately (only callable by SuperGovernor) |
| 536 | /// @notice A manager can either be secondary or primary |
| 537 | /// @param strategy Address of the strategy |
| 538 | /// @param newManager Address of the new primary manager |
| 539 | function changePrimaryManager(address strategy, address newManager) external; |
| 540 | |
| 541 | /// @notice Proposes a change to the primary manager (callable by secondary managers) |
| 542 | /// @notice A manager can either be secondary or primary |
| 543 | /// @param strategy Address of the strategy |
| 544 | /// @param newManager Address of the proposed new primary manager |
| 545 | function proposeChangePrimaryManager(address strategy, address newManager) external; |
| 546 | |
| 547 | /// @notice Executes a previously proposed change to the primary manager after timelock |
| 548 | /// @param strategy Address of the strategy |
| 549 | function executeChangePrimaryManager(address strategy) external; |
| 550 | |
| 551 | /*////////////////////////////////////////////////////////////// |
| 552 | HOOK VALIDATION FUNCTIONS |
| 553 | //////////////////////////////////////////////////////////////*/ |
| 554 | /// @notice Sets a new hooks root update timelock duration |
| 555 | /// @param newTimelock The new timelock duration in seconds |
| 556 | function setHooksRootUpdateTimelock(uint256 newTimelock) external; |
| 557 | |
| 558 | /// @notice Proposes an update to the global hooks Merkle root |
| 559 | /// @dev Only callable by SUPER_GOVERNOR |
| 560 | /// @param newRoot New Merkle root for global hooks validation |
| 561 | function proposeGlobalHooksRoot(bytes32 newRoot) external; |
| 562 | |
| 563 | /// @notice Executes a previously proposed global hooks root update after timelock period |
| 564 | /// @dev Can be called by anyone after the timelock period has elapsed |
| 565 | function executeGlobalHooksRootUpdate() external; |
| 566 | |
| 567 | /// @notice Proposes an update to a strategy-specific hooks Merkle root |
| 568 | /// @dev Only callable by the main manager for the strategy |
| 569 | /// @param strategy Address of the strategy |
| 570 | /// @param newRoot New Merkle root for strategy-specific hooks |
| 571 | function proposeStrategyHooksRoot(address strategy, bytes32 newRoot) external; |
| 572 | |
| 573 | /// @notice Executes a previously proposed strategy hooks root update after timelock period |
| 574 | /// @dev Can be called by anyone after the timelock period has elapsed |
| 575 | /// @param strategy Address of the strategy whose root update to execute |
| 576 | function executeStrategyHooksRootUpdate(address strategy) external; |
| 577 | |
| 578 | /// @notice Set veto status for the global hooks root |
| 579 | /// @dev Only callable by SuperGovernor |
| 580 | /// @param vetoed Whether to veto (true) or unveto (false) the global hooks root |
| 581 | function setGlobalHooksRootVetoStatus(bool vetoed) external; |
| 582 | |
| 583 | /// @notice Set veto status for a strategy-specific hooks root |
| 584 | /// @notice Sets the veto status of a strategy's hooks Merkle root |
| 585 | /// @param strategy Address of the strategy |
| 586 | /// @param vetoed Whether to veto (true) or unveto (false) |
| 587 | function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external; |
| 588 | |
| 589 | /// @notice Updates the PPS verification thresholds for a strategy |
| 590 | /// @param strategy Address of the strategy |
| 591 | /// @param dispersionThreshold_ New dispersion threshold (stddev/mean ratio, scaled by 1e18) |
| 592 | /// @param deviationThreshold_ New deviation threshold (abs diff/current ratio, scaled by 1e18) |
| 593 | /// @param mnThreshold_ New M/N threshold (validatorSet/totalValidators ratio, scaled by 1e18) |
| 594 | function updatePPSVerificationThresholds( |
| 595 | address strategy, |
| 596 | uint256 dispersionThreshold_, |
| 597 | uint256 deviationThreshold_, |
| 598 | uint256 mnThreshold_ |
| 599 | ) |
| 600 | external; |
| 601 | |
| 602 | /// @notice Changes the banned status of global leaves for a specific strategy |
| 603 | /// @dev Only callable by the primary manager of the strategy |
| 604 | /// @param leaves Array of leaf hashes to change status for |
| 605 | /// @param statuses Array of banned statuses (true = banned, false = allowed) |
| 606 | /// @param strategy Address of the strategy to change banned leaves for |
| 607 | function changeGlobalLeavesStatus( |
| 608 | bytes32[] memory leaves, |
| 609 | bool[] memory statuses, |
| 610 | address strategy |
| 611 | ) |
| 612 | external; |
| 613 | |
| 614 | /*////////////////////////////////////////////////////////////// |
| 615 | VIEW FUNCTIONS |
| 616 | //////////////////////////////////////////////////////////////*/ |
| 617 | |
| 618 | /// @notice Returns the current vault creation nonce |
| 619 | /// @dev This nonce is incremented every time a new vault is created |
| 620 | /// @return Current vault creation nonce |
| 621 | function getCurrentNonce() external view returns (uint256); |
| 622 | |
| 623 | /// @notice Check if the global hooks root is currently vetoed |
| 624 | /// @return vetoed True if the global hooks root is vetoed |
| 625 | function isGlobalHooksRootVetoed() external view returns (bool vetoed); |
| 626 | |
| 627 | /// @notice Check if a strategy hooks root is currently vetoed |
| 628 | /// @param strategy Address of the strategy to check |
| 629 | /// @return vetoed True if the strategy hooks root is vetoed |
| 630 | function isStrategyHooksRootVetoed(address strategy) external view returns (bool vetoed); |
| 631 | |
| 632 | /// @notice Gets the current hooks root update timelock duration |
| 633 | /// @return The current timelock duration in seconds |
| 634 | function getHooksRootUpdateTimelock() external view returns (uint256); |
| 635 | |
| 636 | /// @notice Gets the current PPS (price-per-share) for a strategy |
| 637 | /// @param strategy Address of the strategy |
| 638 | /// @return pps Current price-per-share value |
| 639 | function getPPS(address strategy) external view returns (uint256 pps); |
| 640 | |
| 641 | /// @notice Gets the current PPS and its standard deviation for a strategy |
| 642 | /// @param strategy Address of the strategy |
| 643 | /// @return pps Current price-per-share value |
| 644 | /// @return ppsStdev Standard deviation of price-per-share value |
| 645 | function getPPSWithStdDev(address strategy) external view returns (uint256 pps, uint256 ppsStdev); |
| 646 | |
| 647 | /// @notice Gets the last update timestamp for a strategy's PPS |
| 648 | /// @param strategy Address of the strategy |
| 649 | /// @return timestamp Last update timestamp |
| 650 | function getLastUpdateTimestamp(address strategy) external view returns (uint256 timestamp); |
| 651 | |
| 652 | /// @notice Gets the minimum update interval for a strategy |
| 653 | /// @param strategy Address of the strategy |
| 654 | /// @return interval Minimum time between updates |
| 655 | function getMinUpdateInterval(address strategy) external view returns (uint256 interval); |
| 656 | |
| 657 | /// @notice Gets the maximum staleness period for a strategy |
| 658 | /// @param strategy Address of the strategy |
| 659 | /// @return staleness Maximum time allowed between updates |
| 660 | function getMaxStaleness(address strategy) external view returns (uint256 staleness); |
| 661 | |
| 662 | /// @notice Gets the PPS verification thresholds for a strategy |
| 663 | /// @param strategy Address of the strategy |
| 664 | /// @return dispersionThreshold The current dispersion threshold (stddev/mean ratio, scaled by 1e18) |
| 665 | /// @return deviationThreshold The current deviation threshold (abs diff/current ratio, scaled by 1e18) |
| 666 | /// @return mnThreshold The current M/N threshold (validatorSet/totalValidators ratio, scaled by 1e18) |
| 667 | function getPPSVerificationThresholds(address strategy) |
| 668 | external |
| 669 | view |
| 670 | returns (uint256 dispersionThreshold, uint256 deviationThreshold, uint256 mnThreshold); |
| 671 | |
| 672 | /// @notice Checks if a strategy is currently paused |
| 673 | /// @param strategy Address of the strategy |
| 674 | /// @return isPaused True if paused, false otherwise |
| 675 | function isStrategyPaused(address strategy) external view returns (bool isPaused); |
| 676 | |
| 677 | /// @notice Gets the current upkeep balance for a manager |
| 678 | /// @param manager Address of the manager |
| 679 | /// @return balance Current upkeep balance in UP tokens |
| 680 | function getUpkeepBalance(address manager) external view returns (uint256 balance); |
| 681 | |
| 682 | /// @notice Gets the current stake balance for a manager |
| 683 | /// @param manager Address of the manager |
| 684 | /// @return balance Current stake balance in UP tokens |
| 685 | function getStakeBalance(address manager) external view returns (uint256 balance); |
| 686 | |
| 687 | /// @notice Gets all authorized callers for a strategy |
| 688 | /// @param strategy Address of the strategy |
| 689 | /// @return callers Array of authorized callers |
| 690 | function getAuthorizedCallers(address strategy) external view returns (address[] memory callers); |
| 691 | |
| 692 | /// @notice Gets the main manager for a strategy |
| 693 | /// @param strategy Address of the strategy |
| 694 | /// @return manager Address of the main manager |
| 695 | function getMainManager(address strategy) external view returns (address manager); |
| 696 | |
| 697 | /// @notice Checks if an address is the main manager for a strategy |
| 698 | /// @param manager Address of the manager |
| 699 | /// @param strategy Address of the strategy |
| 700 | /// @return isMainManager True if the address is the main manager, false otherwise |
| 701 | function isMainManager(address manager, address strategy) external view returns (bool isMainManager); |
| 702 | |
| 703 | /// @notice Gets all secondary managers for a strategy |
| 704 | /// @param strategy Address of the strategy |
| 705 | /// @return secondaryManagers Array of secondary manager addresses |
| 706 | function getSecondaryManagers(address strategy) external view returns (address[] memory secondaryManagers); |
| 707 | |
| 708 | /// @notice Checks if an address is a secondary manager for a strategy |
| 709 | /// @param manager Address of the manager |
| 710 | /// @param strategy Address of the strategy |
| 711 | /// @return isSecondaryManager True if the address is a secondary manager, false otherwise |
| 712 | function isSecondaryManager( |
| 713 | address manager, |
| 714 | address strategy |
| 715 | ) |
| 716 | external |
| 717 | view |
| 718 | returns (bool isSecondaryManager); |
| 719 | |
| 720 | /// @dev Internal helper function to check if an address is any kind of manager (primary or secondary) |
| 721 | /// @param manager Address to check |
| 722 | /// @param strategy The strategy to check against |
| 723 | /// @return True if the address is either the primary manager or a secondary manager |
| 724 | function isAnyManager(address manager, address strategy) external view returns (bool); |
| 725 | |
| 726 | /// @notice Gets all created SuperVaults |
| 727 | /// @return Array of SuperVault addresses |
| 728 | function getAllSuperVaults() external view returns (address[] memory); |
| 729 | |
| 730 | /// @notice Gets a SuperVault by index |
| 731 | /// @param index The index of the SuperVault |
| 732 | /// @return The SuperVault address at the given index |
| 733 | function superVaults(uint256 index) external view returns (address); |
| 734 | |
| 735 | /// @notice Gets all created SuperVaultStrategies |
| 736 | /// @return Array of SuperVaultStrategy addresses |
| 737 | function getAllSuperVaultStrategies() external view returns (address[] memory); |
| 738 | |
| 739 | /// @notice Gets a SuperVaultStrategy by index |
| 740 | /// @param index The index of the SuperVaultStrategy |
| 741 | /// @return The SuperVaultStrategy address at the given index |
| 742 | function superVaultStrategies(uint256 index) external view returns (address); |
| 743 | |
| 744 | /// @notice Gets all created SuperVaultEscrows |
| 745 | /// @return Array of SuperVaultEscrow addresses |
| 746 | function getAllSuperVaultEscrows() external view returns (address[] memory); |
| 747 | |
| 748 | /// @notice Gets a SuperVaultEscrow by index |
| 749 | /// @param index The index of the SuperVaultEscrow |
| 750 | /// @return The SuperVaultEscrow address at the given index |
| 751 | function superVaultEscrows(uint256 index) external view returns (address); |
| 752 | |
| 753 | /// @notice Validates a hook against both global and strategy-specific Merkle roots |
| 754 | /// @param strategy Address of the strategy |
| 755 | /// @param args Arguments for hook validation |
| 756 | /// @return isValid True if the hook is valid against either root |
| 757 | function validateHook( |
| 758 | address strategy, |
| 759 | ValidateHookArgs calldata args |
| 760 | ) |
| 761 | external |
| 762 | view |
| 763 | returns (bool isValid); |
| 764 | |
| 765 | /// @notice Batch validates multiple hooks against Merkle roots |
| 766 | /// @param strategy Address of the strategy |
| 767 | /// @param argsArray Array of hook validation arguments |
| 768 | /// @return validHooks Array of booleans indicating which hooks are valid |
| 769 | function validateHooks( |
| 770 | address strategy, |
| 771 | ValidateHookArgs[] calldata argsArray |
| 772 | ) |
| 773 | external |
| 774 | view |
| 775 | returns (bool[] memory validHooks); |
| 776 | |
| 777 | /// @notice Gets the current global hooks Merkle root |
| 778 | /// @return root The current global hooks Merkle root |
| 779 | function getGlobalHooksRoot() external view returns (bytes32 root); |
| 780 | |
| 781 | /// @notice Gets the proposed global hooks root and effective time |
| 782 | /// @return root The proposed global hooks Merkle root |
| 783 | /// @return effectiveTime The timestamp when the proposed root becomes effective |
| 784 | function getProposedGlobalHooksRoot() external view returns (bytes32 root, uint256 effectiveTime); |
| 785 | |
| 786 | /// @notice Checks if the global hooks root is active (timelock period has passed) |
| 787 | /// @return isActive True if the global hooks root is active |
| 788 | function isGlobalHooksRootActive() external view returns (bool); |
| 789 | |
| 790 | /// @notice Gets the hooks Merkle root for a specific strategy |
| 791 | /// @param strategy Address of the strategy |
| 792 | /// @return root The strategy-specific hooks Merkle root |
| 793 | function getStrategyHooksRoot(address strategy) external view returns (bytes32 root); |
| 794 | |
| 795 | /// @notice Gets the proposed strategy hooks root and effective time |
| 796 | /// @param strategy Address of the strategy |
| 797 | /// @return root The proposed strategy hooks Merkle root |
| 798 | /// @return effectiveTime The timestamp when the proposed root becomes effective |
| 799 | function getProposedStrategyHooksRoot(address strategy) |
| 800 | external |
| 801 | view |
| 802 | returns (bytes32 root, uint256 effectiveTime); |
| 803 | } |
| 804 |
0.0%
src/interfaces/SuperVault/ISuperVaultEscrow.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ISuperVaultEscrow |
| 5 | /// @notice Interface for SuperVault escrow contract that holds shares during request/claim process |
| 6 | /// @author Superform Labs |
| 7 | interface ISuperVaultEscrow { |
| 8 | /*////////////////////////////////////////////////////////////// |
| 9 | ERRORS |
| 10 | //////////////////////////////////////////////////////////////*/ |
| 11 | error ALREADY_INITIALIZED(); |
| 12 | error UNAUTHORIZED(); |
| 13 | error ZERO_ADDRESS(); |
| 14 | |
| 15 | /*////////////////////////////////////////////////////////////// |
| 16 | INITIALIZATION |
| 17 | //////////////////////////////////////////////////////////////*/ |
| 18 | |
| 19 | /// @notice Initialize the escrow with required parameters |
| 20 | /// @param vaultAddress The vault contract address |
| 21 | /// @param strategyAddress The strategy contract address |
| 22 | function initialize(address vaultAddress, address strategyAddress) external; |
| 23 | |
| 24 | /*////////////////////////////////////////////////////////////// |
| 25 | VAULT FUNCTIONS |
| 26 | //////////////////////////////////////////////////////////////*/ |
| 27 | |
| 28 | /// @notice Transfer shares from user to escrow during redeem request |
| 29 | /// @param from The address to transfer shares from |
| 30 | /// @param amount The amount of shares to transfer |
| 31 | function escrowShares(address from, uint256 amount) external; |
| 32 | |
| 33 | /// @notice Return shares from escrow to user during redeem cancellation |
| 34 | /// @param to The address to return shares to |
| 35 | /// @param amount The amount of shares to return |
| 36 | function returnShares(address to, uint256 amount) external; |
| 37 | } |
| 38 |
0.0%
src/interfaces/SuperVault/ISuperVaultStrategy.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
| 5 | import { ISuperHook, Execution } from "@superform-v2-core/src/interfaces/ISuperHook.sol"; |
| 6 | |
| 7 | /// @title ISuperVaultStrategy |
| 8 | /// @author Superform Labs |
| 9 | /// @notice Interface for SuperVault strategy implementation that manages yield sources and executes strategies |
| 10 | interface ISuperVaultStrategy { |
| 11 | /*////////////////////////////////////////////////////////////// |
| 12 | ERRORS |
| 13 | //////////////////////////////////////////////////////////////*/ |
| 14 | |
| 15 | error ZERO_LENGTH(); |
| 16 | error INVALID_HOOK(); |
| 17 | error ZERO_ADDRESS(); |
| 18 | error ACCESS_DENIED(); |
| 19 | error INVALID_AMOUNT(); |
| 20 | error INVALID_MANAGER(); |
| 21 | error OPERATION_FAILED(); |
| 22 | error INVALID_TIMESTAMP(); |
| 23 | error REQUEST_NOT_FOUND(); |
| 24 | error INVALID_HOOK_ROOT(); |
| 25 | error INVALID_HOOK_TYPE(); |
| 26 | error INSUFFICIENT_FUNDS(); |
| 27 | error ZERO_OUTPUT_AMOUNT(); |
| 28 | error INSUFFICIENT_SHARES(); |
| 29 | error ZERO_EXPECTED_VALUE(); |
| 30 | error INVALID_ARRAY_LENGTH(); |
| 31 | error ACTION_TYPE_DISALLOWED(); |
| 32 | error YIELD_SOURCE_NOT_FOUND(); |
| 33 | error INVALID_EMERGENCY_ADMIN(); |
| 34 | error INVALID_PERIPHERY_REGISTRY(); |
| 35 | error YIELD_SOURCE_ALREADY_EXISTS(); |
| 36 | error INVALID_PERFORMANCE_FEE_BPS(); |
| 37 | error INVALID_EMERGENCY_WITHDRAWAL(); |
| 38 | error ASYNC_REQUEST_BLOCKING(); |
| 39 | error MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET(); |
| 40 | error MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 41 | error INVALID_REDEEM_CLAIM(); |
| 42 | error MANAGER_NOT_AUTHORIZED(); |
| 43 | error PPS_UPDATE_RATE_LIMITED(); |
| 44 | error PPS_OUT_OF_BOUNDS(); |
| 45 | error CALCULATION_BLOCK_TOO_OLD(); |
| 46 | error INVALID_PPS(); |
| 47 | error INVALID_REDEEM_FILL(); |
| 48 | error SLIPPAGE_EXCEEDED(); |
| 49 | error INVALID_VAULT(); |
| 50 | error STAKE_TOO_LOW(); |
| 51 | error OPERATIONS_BLOCKED_BY_VETO(); |
| 52 | error HOOK_VALIDATION_FAILED(); |
| 53 | error STRATEGY_PAUSED(); |
| 54 | error INVALID_MAX_SLIPPAGE_BPS(); |
| 55 | error NO_PROPOSAL(); |
| 56 | |
| 57 | /*////////////////////////////////////////////////////////////// |
| 58 | EVENTS |
| 59 | //////////////////////////////////////////////////////////////*/ |
| 60 | |
| 61 | event SuperGovernorSet(address indexed superGovernor); |
| 62 | event Initialized(address indexed vault); |
| 63 | event YieldSourceAdded(address indexed source, address indexed oracle); |
| 64 | event YieldSourceOracleUpdated(address indexed source, address indexed oldOracle, address indexed newOracle); |
| 65 | event YieldSourceRemoved(address indexed source); |
| 66 | |
| 67 | event HookRootUpdated(bytes32 newRoot); |
| 68 | event HookRootProposed(bytes32 proposedRoot, uint256 effectiveTime); |
| 69 | event EmergencyWithdrawableProposed(bool newWithdrawable, uint256 effectiveTime); |
| 70 | event EmergencyWithdrawableUpdated(bool withdrawable); |
| 71 | event EmergencyWithdrawableProposalCanceled(); |
| 72 | event EmergencyWithdrawal(address indexed recipient, uint256 assets); |
| 73 | event VaultFeeConfigUpdated(uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient); |
| 74 | event VaultFeeConfigProposed( |
| 75 | uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient, uint256 effectiveTime |
| 76 | ); |
| 77 | event MaxPPSSlippageUpdated(uint256 maxSlippageBps); |
| 78 | event HooksExecuted(address[] hooks); |
| 79 | event RedeemRequestPlaced(address indexed controller, address indexed owner, uint256 shares); |
| 80 | event RedeemRequestFulfilled(address indexed controller, address indexed receiver, uint256 assets, uint256 shares); |
| 81 | event RedeemRequestCanceled(address indexed controller, uint256 shares); |
| 82 | event HookExecuted( |
| 83 | address indexed hook, |
| 84 | address indexed prevHook, |
| 85 | address indexed targetedYieldSource, |
| 86 | bool usePrevHookAmount, |
| 87 | bytes hookCalldata |
| 88 | ); |
| 89 | event FulfillHookExecuted(address indexed hook, address indexed targetedYieldSource, bytes hookCalldata); |
| 90 | |
| 91 | event PPSUpdated(uint256 newPPS, uint256 calculationBlock); |
| 92 | |
| 93 | event RedeemRequestsFulfilled(address[] hooks, address[] controllers, uint256 processedShares, uint256 currentPPS); |
| 94 | |
| 95 | event FeePaid(address indexed recipient, uint256 amount, uint256 performanceFeeBps); |
| 96 | event ManagementFeePaid(address indexed controller, address indexed recipient, uint256 feeAssets, uint256 feeBps); |
| 97 | event DepositHandled(address indexed controller, uint256 assets, uint256 shares); |
| 98 | |
| 99 | /*////////////////////////////////////////////////////////////// |
| 100 | STRUCTS |
| 101 | //////////////////////////////////////////////////////////////*/ |
| 102 | |
| 103 | struct FeeConfig { |
| 104 | uint256 performanceFeeBps; // On profit at fulfill time |
| 105 | uint256 managementFeeBps; // Entry fee on deposit/mint (asset-side) |
| 106 | address recipient; // Fee sink (entry + performance) |
| 107 | } |
| 108 | |
| 109 | /// @notice Structure for hook execution arguments |
| 110 | struct ExecuteArgs { |
| 111 | /// @notice Array of hooks to execute |
| 112 | address[] hooks; |
| 113 | /// @notice Calldata for each hook (must match hooks array length) |
| 114 | bytes[] hookCalldata; |
| 115 | /// @notice Expected output amounts or output shares |
| 116 | uint256[] expectedAssetsOrSharesOut; |
| 117 | /// @notice Global Merkle proofs for hook validation (must match hooks array length) |
| 118 | bytes32[][] globalProofs; |
| 119 | /// @notice Strategy-specific Merkle proofs for hook validation (must match hooks array length) |
| 120 | bytes32[][] strategyProofs; |
| 121 | } |
| 122 | |
| 123 | struct FulfillArgs { |
| 124 | address[] controllers; |
| 125 | address[] hooks; |
| 126 | bytes[] hookCalldata; |
| 127 | uint256[] expectedAssetsOrSharesOut; |
| 128 | bytes32[][] globalProofs; |
| 129 | bytes32[][] strategyProofs; |
| 130 | } |
| 131 | |
| 132 | struct YieldSource { |
| 133 | address oracle; // Associated yield source oracle address |
| 134 | } |
| 135 | |
| 136 | /// @notice Comprehensive information about a yield source including its address and configuration |
| 137 | struct YieldSourceInfo { |
| 138 | address sourceAddress; // Address of the yield source |
| 139 | address oracle; // Associated yield source oracle address |
| 140 | } |
| 141 | |
| 142 | /// @notice State specific to asynchronous redeem requests |
| 143 | struct SuperVaultState { |
| 144 | uint256 pendingRedeemRequest; // Shares requested |
| 145 | uint256 maxWithdraw; // Assets claimable after fulfillment |
| 146 | uint256 averageRequestPPS; // Average PPS at the time of redeem request |
| 147 | // Accumulators needed for fee calculation on redeem |
| 148 | uint256 accumulatorShares; |
| 149 | uint256 accumulatorCostBasis; |
| 150 | uint256 averageWithdrawPrice; // Average price for claimable assets |
| 151 | } |
| 152 | |
| 153 | struct ExecutionVars { |
| 154 | bool success; |
| 155 | address targetedYieldSource; |
| 156 | uint256 outAmount; |
| 157 | ISuperHook hookContract; |
| 158 | Execution[] executions; |
| 159 | } |
| 160 | |
| 161 | struct OutflowExecutionVars { |
| 162 | bool success; |
| 163 | address targetedYieldSource; |
| 164 | address svAsset; |
| 165 | uint256 outAmount; |
| 166 | uint256 superVaultShares; |
| 167 | uint256 amountOfAssets; |
| 168 | uint256 amountConvertedToUnderlyingShares; |
| 169 | uint256 balanceAssetBefore; |
| 170 | Execution[] executions; |
| 171 | ISuperHook hookContract; |
| 172 | ISuperHook.HookType hookType; |
| 173 | } |
| 174 | |
| 175 | /*////////////////////////////////////////////////////////////// |
| 176 | ENUMS |
| 177 | //////////////////////////////////////////////////////////////*/ |
| 178 | enum Operation { |
| 179 | Deposit, |
| 180 | RedeemRequest, |
| 181 | CancelRedeem, |
| 182 | ClaimRedeem, |
| 183 | Claim, |
| 184 | UpdateDepositAccumulators |
| 185 | } |
| 186 | |
| 187 | /*////////////////////////////////////////////////////////////// |
| 188 | CORE STRATEGY OPERATIONS |
| 189 | //////////////////////////////////////////////////////////////*/ |
| 190 | |
| 191 | /// @notice Initializes the strategy with required parameters |
| 192 | /// @param vaultAddress Address of the associated SuperVault |
| 193 | /// @param feeConfigData Fee configuration |
| 194 | function initialize(address vaultAddress, FeeConfig memory feeConfigData) external; |
| 195 | |
| 196 | /// @notice Execute a 4626 deposit by processing assets. |
| 197 | /// @param controller The controller address |
| 198 | /// @param assetsGross The amount of gross assets user has to deposit |
| 199 | /// @return sharesNet The amount of net shares to mint |
| 200 | function handleOperations4626Deposit( |
| 201 | address controller, |
| 202 | uint256 assetsGross |
| 203 | ) |
| 204 | external |
| 205 | returns (uint256 sharesNet); |
| 206 | |
| 207 | /// @notice Execute a 4626 mint by processing shares. |
| 208 | /// @param controller The controller address |
| 209 | /// @param sharesNet The amount of shares to mint |
| 210 | /// @param assetsGross The amount of gross assets user has to deposit |
| 211 | /// @param assetsNet The amount of net assets that strategy will receive |
| 212 | function handleOperations4626Mint( |
| 213 | address controller, |
| 214 | uint256 sharesNet, |
| 215 | uint256 assetsGross, |
| 216 | uint256 assetsNet |
| 217 | ) |
| 218 | external; |
| 219 | |
| 220 | /// @notice Quotes the amount of assets that will be received for a given amount of shares. |
| 221 | /// @param shares The amount of shares to mint |
| 222 | /// @return assetsGross The amount of gross assets that will be received |
| 223 | /// @return assetsNet The amount of net assets that will be received |
| 224 | function quoteMintAssetsGross(uint256 shares) external view returns (uint256 assetsGross, uint256 assetsNet); |
| 225 | |
| 226 | /// @notice Execute async redeem requests (redeem, cancel, claim). |
| 227 | /// @param op The operation type (RedeemRequest, CancelRedeem, ClaimRedeem) |
| 228 | /// @param controller The controller address |
| 229 | /// @param receiver The receiver address |
| 230 | /// @param amount The amount of assets or shares |
| 231 | function handleOperations7540(Operation op, address controller, address receiver, uint256 amount) external; |
| 232 | |
| 233 | /*////////////////////////////////////////////////////////////// |
| 234 | MANAGER EXTERNAL ACCESS FUNCTIONS |
| 235 | //////////////////////////////////////////////////////////////*/ |
| 236 | |
| 237 | /// @notice Execute hooks for general strategy management (rebalancing, etc.). |
| 238 | /// @param args Execution arguments containing hooks, calldata, proofs, expectations. |
| 239 | function executeHooks(ExecuteArgs calldata args) external payable; |
| 240 | |
| 241 | /// @notice Fulfills pending redeem requests by executing specific fulfill hooks. |
| 242 | /// @param args Execution arguments containing fulfill hooks, calldata, and expected outputs (proofs ignored). |
| 243 | function fulfillRedeemRequests(FulfillArgs calldata args) external; |
| 244 | |
| 245 | /*////////////////////////////////////////////////////////////// |
| 246 | YIELD SOURCE MANAGEMENT |
| 247 | //////////////////////////////////////////////////////////////*/ |
| 248 | |
| 249 | /// @notice Manage a single yield source: add, update oracle, or remove |
| 250 | /// @param source Address of the yield source |
| 251 | /// @param oracle Address of the oracle (used for adding/updating, ignored for removal) |
| 252 | /// @param actionType Type of action: 0=Add, 1=UpdateOracle, 2=Remove |
| 253 | function manageYieldSource(address source, address oracle, uint8 actionType) external; |
| 254 | |
| 255 | /// @notice Batch manage multiple yield sources in a single transaction |
| 256 | /// @param sources Array of yield source addresses |
| 257 | /// @param oracles Array of oracle addresses (used for adding/updating, ignored for removal) |
| 258 | /// @param actionTypes Array of action types: 0=Add, 1=UpdateOracle, 2=Remove |
| 259 | function manageYieldSources( |
| 260 | address[] calldata sources, |
| 261 | address[] calldata oracles, |
| 262 | uint8[] calldata actionTypes |
| 263 | ) |
| 264 | external; |
| 265 | |
| 266 | /// @notice Propose or execute a hook root update |
| 267 | /// @notice Propose changes to vault-specific fee configuration |
| 268 | /// @param performanceFeeBps New performance fee in basis points |
| 269 | /// @param managementFeeBps New management fee in basis points |
| 270 | /// @param recipient New fee recipient |
| 271 | function proposeVaultFeeConfigUpdate( |
| 272 | uint256 performanceFeeBps, |
| 273 | uint256 managementFeeBps, |
| 274 | address recipient |
| 275 | ) |
| 276 | external; |
| 277 | |
| 278 | /// @notice Execute the proposed vault fee configuration update after timelock |
| 279 | function executeVaultFeeConfigUpdate() external; |
| 280 | |
| 281 | /// @notice Update the maximum allowed PPS slippage for redemptions |
| 282 | /// @param maxSlippageBps Maximum slippage in basis points (e.g., 100 = 1%) |
| 283 | function updateMaxPPSSlippage(uint256 maxSlippageBps) external; |
| 284 | |
| 285 | /// @notice Manage emergency withdrawals |
| 286 | /// @param action Type of action: 1=Propose, 2=ExecuteActivation, 3=Withdraw, 4=CancelProposal |
| 287 | /// @param recipient The recipient of the withdrawn assets (for action 3) |
| 288 | /// @param amount The amount of assets to withdraw (for action 3) |
| 289 | function manageEmergencyWithdraw(uint8 action, address recipient, uint256 amount) external; |
| 290 | |
| 291 | /*////////////////////////////////////////////////////////////// |
| 292 | ACCOUNTING MANAGEMENT |
| 293 | //////////////////////////////////////////////////////////////*/ |
| 294 | /// @notice Move accumulator shares and cost basis pro-rata during share transfers |
| 295 | /// @param from The address transferring shares |
| 296 | /// @param to The address receiving shares |
| 297 | /// @param shares The amount of shares being transferred |
| 298 | function moveAccumulatorOnTransfer(address from, address to, uint256 shares) external; |
| 299 | |
| 300 | /*////////////////////////////////////////////////////////////// |
| 301 | VIEW FUNCTIONS |
| 302 | //////////////////////////////////////////////////////////////*/ |
| 303 | |
| 304 | /// @notice Get the vault info |
| 305 | function getVaultInfo() external view returns (address vault, address asset, uint8 vaultDecimals); |
| 306 | |
| 307 | /// @notice Get the fee configurations |
| 308 | function getConfigInfo() external view returns (FeeConfig memory feeConfig); |
| 309 | |
| 310 | /// @notice Returns the currently stored PPS value. |
| 311 | function getStoredPPS() external view returns (uint256); |
| 312 | |
| 313 | /// @notice Get a yield source's configuration |
| 314 | function getYieldSource(address source) external view returns (YieldSource memory); |
| 315 | |
| 316 | /// @notice Get all yield sources with their information |
| 317 | /// @return Array of YieldSourceInfo structs |
| 318 | function getYieldSourcesList() external view returns (YieldSourceInfo[] memory); |
| 319 | |
| 320 | /// @notice Get all yield source addresses |
| 321 | /// @return Array of yield source addresses |
| 322 | function getYieldSources() external view returns (address[] memory); |
| 323 | |
| 324 | /// @notice Get the count of yield sources |
| 325 | /// @return Number of yield sources |
| 326 | function getYieldSourcesCount() external view returns (uint256); |
| 327 | |
| 328 | /// @notice Check if a yield source exists |
| 329 | /// @param source Address of the yield source |
| 330 | /// @return True if the yield source exists |
| 331 | function containsYieldSource(address source) external view returns (bool); |
| 332 | |
| 333 | /// @notice Get the average withdraw price for a controller |
| 334 | /// @param controller The controller address |
| 335 | /// @return averageWithdrawPrice The average withdraw price |
| 336 | function getAverageWithdrawPrice(address controller) external view returns (uint256 averageWithdrawPrice); |
| 337 | |
| 338 | /// @notice Get the super vault state for a controller |
| 339 | /// @param controller The controller address |
| 340 | /// @return state The super vault state |
| 341 | function getSuperVaultState(address controller) external view returns (SuperVaultState memory state); |
| 342 | |
| 343 | /// @notice Previews the fee that would be taken for redeeming a specific amount of shares |
| 344 | /// @param controller The address of the controller requesting the redemption |
| 345 | /// @param sharesToRedeem The number of shares to redeem |
| 346 | /// @return totalFee The estimated fee that would be taken in asset terms |
| 347 | /// @return superformFee The portion of the fee that would go to Superform treasury |
| 348 | /// @return recipientFee The portion of the fee that would go to the fee recipient |
| 349 | function previewPerformanceFee( |
| 350 | address controller, |
| 351 | uint256 sharesToRedeem |
| 352 | ) |
| 353 | external |
| 354 | view |
| 355 | returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee); |
| 356 | |
| 357 | /// @notice Get the pending redeem request amount (shares) for a controller |
| 358 | /// @param controller The controller address |
| 359 | /// @return pendingShares The amount of shares pending redemption |
| 360 | function pendingRedeemRequest(address controller) external view returns (uint256 pendingShares); |
| 361 | |
| 362 | /// @notice Get the claimable withdraw amount (assets) for a controller |
| 363 | /// @param controller The controller address |
| 364 | /// @return claimableAssets The amount of assets claimable |
| 365 | function claimableWithdraw(address controller) external view returns (uint256 claimableAssets); |
| 366 | } |
| 367 |
0.0%
src/interfaces/oracles/IECDSAPPSOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ECDSAPPSOracle |
| 5 | /// @author Superform Labs |
| 6 | /// @notice Interface for PPS oracles that provide price-per-share updates |
| 7 | /// @dev All PPS oracle implementations must conform to this interface |
| 8 | interface IECDSAPPSOracle { |
| 9 | /*////////////////////////////////////////////////////////////// |
| 10 | ERRORS |
| 11 | //////////////////////////////////////////////////////////////*/ |
| 12 | /// @notice Thrown when the proof is invalid or cannot be verified |
| 13 | error INVALID_PROOF(); |
| 14 | /// @notice Thrown when a validator is not registered or authorized |
| 15 | error INVALID_VALIDATOR(); |
| 16 | /// @notice Thrown when the quorum of validators is not met |
| 17 | error QUORUM_NOT_MET(); |
| 18 | /// @notice Thrown when the input arrays have different lengths |
| 19 | error ARRAY_LENGTH_MISMATCH(); |
| 20 | /// @notice Thrown when the input array is empty |
| 21 | error ZERO_LENGTH_ARRAY(); |
| 22 | /// @notice Thrown when the timestamp in the proof is invalid |
| 23 | error INVALID_TIMESTAMP(); |
| 24 | /// @notice Thrown when the strategy address in the proof does not match |
| 25 | error STRATEGY_MISMATCH(); |
| 26 | /// @notice Thrown when the pps value in the proof does not match |
| 27 | error PPS_MISMATCH(); |
| 28 | /// @notice Thrown when the oracle is not set as the active PPS Oracle in SuperGovernor |
| 29 | error NOT_ACTIVE_PPS_ORACLE(); |
| 30 | /// @notice Thrown when the dispersion (standard deviation / mean) is too high |
| 31 | error HIGH_PPS_DISPERSION(); |
| 32 | /// @notice Thrown when the deviation from previous PPS is too high |
| 33 | error HIGH_PPS_DEVIATION(); |
| 34 | /// @notice Thrown when too few validators participated in the round |
| 35 | error INSUFFICIENT_VALIDATOR_PARTICIPATION(); |
| 36 | /// @notice Thrown when the reported validator count doesn't match the actual number of valid signatures |
| 37 | error VALIDATOR_COUNT_MISMATCH(); |
| 38 | /// @notice Thrown when the validatorSet doesn't match the actual number of valid signatures |
| 39 | error INVALID_VALIDATOR_SET(); |
| 40 | /// @notice Thrown when the totalValidators doesn't match the actual total number of validators |
| 41 | error INVALID_TOTAL_VALIDATORS(); |
| 42 | |
| 43 | /*////////////////////////////////////////////////////////////// |
| 44 | EVENTS |
| 45 | //////////////////////////////////////////////////////////////*/ |
| 46 | /// @notice Emitted when a PPS update is validated and forwarded |
| 47 | /// @param strategy Address of the strategy |
| 48 | /// @param pps The validated price-per-share value |
| 49 | /// @param ppsStdev The standard deviation of the price-per-share |
| 50 | /// @param validatorSet Number of validators who calculated the PPS |
| 51 | /// @param totalValidators Total number of validators in the network |
| 52 | /// @param timestamp Timestamp when the value was generated |
| 53 | /// @param sender Address that submitted the update |
| 54 | event PPSValidated( |
| 55 | address indexed strategy, |
| 56 | uint256 pps, |
| 57 | uint256 ppsStdev, |
| 58 | uint256 validatorSet, |
| 59 | uint256 totalValidators, |
| 60 | uint256 timestamp, |
| 61 | address indexed sender |
| 62 | ); |
| 63 | |
| 64 | /// @notice Emitted when proof validation failed |
| 65 | /// @param strategy Address of the strategy |
| 66 | /// @param reason Revert reason |
| 67 | event ProofValidationFailed(address indexed strategy, string reason); |
| 68 | |
| 69 | /// @notice Emitted when proof validation failed |
| 70 | /// @param strategy Address of the strategy |
| 71 | /// @param data Revert encoded data |
| 72 | event ProofValidationFailedLowLevel(address indexed strategy, bytes data); |
| 73 | |
| 74 | /// @notice Emitted when batch forward PPS failed |
| 75 | /// @param reason Revert reason |
| 76 | event BatchForwardPPSFailed(string reason); |
| 77 | |
| 78 | /// @notice Emitted when batch forward PPS failed |
| 79 | /// @param lowLevelData Revert encoded data |
| 80 | event BatchForwardPPSFailedLowLevel(bytes lowLevelData); |
| 81 | |
| 82 | /*////////////////////////////////////////////////////////////// |
| 83 | STRUCTS |
| 84 | //////////////////////////////////////////////////////////////*/ |
| 85 | /// @notice Parameters for validating PPS proofs |
| 86 | /// @param strategy Address of the strategy |
| 87 | /// @param proofs Array of cryptographic proofs |
| 88 | /// @param pps Price-per-share value (mean) |
| 89 | /// @param ppsStdev Standard deviation of the price-per-share |
| 90 | /// @param validatorSet Number of validators who calculated this PPS |
| 91 | /// @param totalValidators Total number of validators in the network |
| 92 | /// @param timestamp Timestamp when the value was generated |
| 93 | struct ValidationParams { |
| 94 | address strategy; |
| 95 | bytes[] proofs; |
| 96 | uint256 pps; |
| 97 | uint256 ppsStdev; |
| 98 | uint256 validatorSet; |
| 99 | uint256 totalValidators; |
| 100 | uint256 timestamp; |
| 101 | } |
| 102 | |
| 103 | /// @notice Arguments for batch updating PPS for multiple strategies |
| 104 | /// @param strategies Array of strategy addresses |
| 105 | /// @param proofsArray Array of arrays of cryptographic proofs (one array of proofs per strategy) |
| 106 | /// @param ppss Array of price-per-share values (means) |
| 107 | /// @param ppsStdevs Array of standard deviations of price-per-share values |
| 108 | /// @param validatorSets Array of numbers of validators who calculated each PPS |
| 109 | /// @param totalValidators Array of total number of validators in the network for each update |
| 110 | /// @param timestamps The time and therefore the blockchain(s) state(s) (plural important) this PPS refers to |
| 111 | struct UpdatePPSArgs { |
| 112 | address[] strategies; |
| 113 | bytes[][] proofsArray; |
| 114 | uint256[] ppss; |
| 115 | uint256[] ppsStdevs; |
| 116 | uint256[] validatorSets; |
| 117 | uint256[] totalValidators; |
| 118 | uint256[] timestamps; |
| 119 | } |
| 120 | |
| 121 | /*////////////////////////////////////////////////////////////// |
| 122 | VIEW FUNCTIONS |
| 123 | //////////////////////////////////////////////////////////////*/ |
| 124 | /// @notice Returns the current nonce |
| 125 | /// @param strategy_ Address of the strategy |
| 126 | /// @return The current nonce |
| 127 | function noncePerStrategy(address strategy_) external view returns (uint256); |
| 128 | |
| 129 | /// @notice Returns the domain separator for the contract |
| 130 | /// @return The domain separator |
| 131 | function domainSeparator() external view returns (bytes32); |
| 132 | |
| 133 | /// @notice Returns the signature typehash |
| 134 | /// @return The typehash |
| 135 | function UPDATE_PPS_TYPEHASH() external view returns (bytes32); |
| 136 | |
| 137 | /// @notice Validates an array of proofs for a strategy's PPS update |
| 138 | /// @param params Validation parameters |
| 139 | /// @dev Reverts immediately if duplicate signers are found or quorum is not met |
| 140 | function validateProofs(IECDSAPPSOracle.ValidationParams memory params) external view; |
| 141 | |
| 142 | /*////////////////////////////////////////////////////////////// |
| 143 | EXTERNAL FUNCTIONS |
| 144 | //////////////////////////////////////////////////////////////*/ |
| 145 | /// @notice Updates the PPS for multiple strategies in a batch |
| 146 | /// @param args Struct containing all parameters for batch PPS update |
| 147 | function updatePPS(UpdatePPSArgs calldata args) external; |
| 148 | } |
| 149 |
0.0%
src/interfaces/oracles/ISuperOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ISuperOracle |
| 5 | /// @author Superform Labs |
| 6 | /// @notice Interface for SuperOracle |
| 7 | interface ISuperOracle { |
| 8 | /*////////////////////////////////////////////////////////////// |
| 9 | ERRORS |
| 10 | //////////////////////////////////////////////////////////////*/ |
| 11 | /// @notice Error when address is zero |
| 12 | error ZERO_ADDRESS(); |
| 13 | |
| 14 | /// @notice Error when array length is zero |
| 15 | error ZERO_ARRAY_LENGTH(); |
| 16 | |
| 17 | /// @notice Error when oracle provider index is invalid |
| 18 | error INVALID_ORACLE_PROVIDER(); |
| 19 | |
| 20 | /// @notice Error when no oracles are configured for base asset |
| 21 | error NO_ORACLES_CONFIGURED(); |
| 22 | |
| 23 | /// @notice Error when no valid reported prices are found |
| 24 | error NO_VALID_REPORTED_PRICES(); |
| 25 | |
| 26 | /// @notice Error when caller is not admin |
| 27 | error NOT_ADMIN(); |
| 28 | |
| 29 | /// @notice Error when arrays have mismatched lengths |
| 30 | error ARRAY_LENGTH_MISMATCH(); |
| 31 | |
| 32 | /// @notice Error when timelock period has not elapsed |
| 33 | error TIMELOCK_NOT_ELAPSED(); |
| 34 | |
| 35 | /// @notice Error when there is already a pending update |
| 36 | error PENDING_UPDATE_EXISTS(); |
| 37 | |
| 38 | /// @notice Error when oracle data is untrusted |
| 39 | error ORACLE_UNTRUSTED_DATA(); |
| 40 | |
| 41 | /// @notice Error when provider max staleness period is not set |
| 42 | error NO_PENDING_UPDATE(); |
| 43 | |
| 44 | /// @notice Error when quote is not supported (only USD is supported) |
| 45 | error UNSUPPORTED_QUOTE(); |
| 46 | |
| 47 | /// @notice Error when provider max staleness period is exceeded |
| 48 | error MAX_STALENESS_EXCEEDED(); |
| 49 | |
| 50 | /// @notice Error when no prices are reported |
| 51 | error NO_PRICES(); |
| 52 | |
| 53 | /// @notice Error when average provider is not allowed |
| 54 | error AVERAGE_PROVIDER_NOT_ALLOWED(); |
| 55 | |
| 56 | /// @notice Error when provider is zero |
| 57 | error ZERO_PROVIDER(); |
| 58 | |
| 59 | /// @notice Error when caller is not authorized to update |
| 60 | error UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 61 | |
| 62 | /// @notice Error when oracle decimals call fails |
| 63 | error ORACLE_DECIMALS_CALL_FAIL(address oracle); |
| 64 | |
| 65 | /// @notice Error when oracle round data call fails |
| 66 | error ORACLE_ROUND_DATA_CALL_FAIL(address oracle); |
| 67 | |
| 68 | /*////////////////////////////////////////////////////////////// |
| 69 | EVENTS |
| 70 | //////////////////////////////////////////////////////////////*/ |
| 71 | /// @notice Emitted when oracles are configured |
| 72 | /// @param bases Array of base assets |
| 73 | /// @param providers Array of provider indexes |
| 74 | /// @param feeds Array of oracle addresses |
| 75 | event OraclesConfigured(address[] bases, address[] quotes, bytes32[] providers, address[] feeds); |
| 76 | |
| 77 | /// @notice Emitted when oracle update is queued |
| 78 | /// @param bases Array of base assets |
| 79 | /// @param providers Array of provider indexes |
| 80 | /// @param feeds Array of oracle addresses |
| 81 | /// @param timestamp Timestamp when update was queued |
| 82 | event OracleUpdateQueued( |
| 83 | address[] bases, address[] quotes, bytes32[] providers, address[] feeds, uint256 timestamp |
| 84 | ); |
| 85 | |
| 86 | /// @notice Emitted when oracle update is executed |
| 87 | /// @param bases Array of base assets |
| 88 | /// @param providers Array of provider indexes |
| 89 | /// @param feeds Array of oracle addresses |
| 90 | event OracleUpdateExecuted(address[] bases, address[] quotes, bytes32[] providers, address[] feeds); |
| 91 | |
| 92 | /// @notice Emitted when provider max staleness period is updated |
| 93 | /// @param feed Feed address |
| 94 | /// @param newMaxStaleness New maximum staleness period in seconds |
| 95 | event FeedMaxStalenessUpdated(address feed, uint256 newMaxStaleness); |
| 96 | |
| 97 | /// @notice Emitted when max staleness period is updated |
| 98 | /// @param newMaxStaleness New maximum staleness period in seconds |
| 99 | event MaxStalenessUpdated(uint256 newMaxStaleness); |
| 100 | |
| 101 | /// @notice Emitted when provider removal is queued |
| 102 | /// @param providers Array of provider ids to remove |
| 103 | /// @param timestamp Timestamp when removal was queued |
| 104 | event ProviderRemovalQueued(bytes32[] providers, uint256 timestamp); |
| 105 | |
| 106 | /// @notice Emitted when provider removal is executed |
| 107 | /// @param providers Array of provider ids that were removed |
| 108 | event ProviderRemovalExecuted(bytes32[] providers); |
| 109 | |
| 110 | /// @notice Emitted when emergency price is updated |
| 111 | /// @param token Token address |
| 112 | /// @param price Emergency price |
| 113 | event EmergencyPriceUpdated(address token, uint256 price); |
| 114 | |
| 115 | /*////////////////////////////////////////////////////////////// |
| 116 | STRUCTS |
| 117 | //////////////////////////////////////////////////////////////*/ |
| 118 | /// @notice Struct for pending oracle update |
| 119 | struct PendingUpdate { |
| 120 | address[] bases; |
| 121 | address[] quotes; |
| 122 | bytes32[] providers; |
| 123 | address[] feeds; |
| 124 | uint256 timestamp; |
| 125 | } |
| 126 | |
| 127 | /// @notice Struct for pending provider removal |
| 128 | struct PendingRemoval { |
| 129 | bytes32[] providers; |
| 130 | uint256 timestamp; |
| 131 | } |
| 132 | |
| 133 | /*////////////////////////////////////////////////////////////// |
| 134 | EXTERNAL FUNCTIONS |
| 135 | //////////////////////////////////////////////////////////////*/ |
| 136 | |
| 137 | /// @notice Get oracle address for a base asset and provider |
| 138 | /// @param base Base asset address |
| 139 | /// @param quote Quote asset address |
| 140 | /// @param provider Provider id |
| 141 | /// @return oracle Oracle address |
| 142 | function getOracleAddress(address base, address quote, bytes32 provider) external view returns (address oracle); |
| 143 | |
| 144 | /// @notice Get quote from specified oracle provider |
| 145 | /// @param baseAmount Amount of base asset |
| 146 | /// @param base Base asset address |
| 147 | /// @param quote Quote asset address |
| 148 | /// @param oracleProvider Id of oracle provider to use |
| 149 | /// @return quoteAmount The quote amount |
| 150 | function getQuoteFromProvider( |
| 151 | uint256 baseAmount, |
| 152 | address base, |
| 153 | address quote, |
| 154 | bytes32 oracleProvider |
| 155 | ) |
| 156 | external |
| 157 | view |
| 158 | returns (uint256 quoteAmount, uint256 deviation, uint256 totalProviders, uint256 availableProviders); |
| 159 | |
| 160 | /// @notice Queue oracle update for timelock |
| 161 | /// @param bases Array of base assets |
| 162 | /// @param providers Array of provider ids |
| 163 | /// @param quotes Array of quote assets |
| 164 | /// @param feeds Array of oracle addresses |
| 165 | function queueOracleUpdate( |
| 166 | address[] calldata bases, |
| 167 | address[] calldata quotes, |
| 168 | bytes32[] calldata providers, |
| 169 | address[] calldata feeds |
| 170 | ) |
| 171 | external; |
| 172 | |
| 173 | /// @notice Execute queued oracle update after timelock period |
| 174 | function executeOracleUpdate() external; |
| 175 | |
| 176 | /// @notice Queue provider removal for timelock |
| 177 | /// @param providers Array of provider ids to remove |
| 178 | function queueProviderRemoval(bytes32[] calldata providers) external; |
| 179 | |
| 180 | /// @notice Execute queued provider removal after timelock period |
| 181 | function executeProviderRemoval() external; |
| 182 | |
| 183 | /// @notice Set the maximum staleness period for a specific provider |
| 184 | /// @param feed Feed address |
| 185 | /// @param newMaxStaleness New maximum staleness period in seconds |
| 186 | function setFeedMaxStaleness(address feed, uint256 newMaxStaleness) external; |
| 187 | |
| 188 | /// @notice Set the maximum staleness period for all providers |
| 189 | /// @param newMaxStaleness New maximum staleness period in seconds |
| 190 | function setMaxStaleness(uint256 newMaxStaleness) external; |
| 191 | |
| 192 | /// @notice Set the maximum staleness period for multiple providers |
| 193 | /// @param feeds Array of feed addresses |
| 194 | /// @param newMaxStalenessList Array of new maximum staleness periods in seconds |
| 195 | function setFeedMaxStalenessBatch(address[] calldata feeds, uint256[] calldata newMaxStalenessList) external; |
| 196 | |
| 197 | /// @notice Get all active provider ids |
| 198 | /// @return Array of active provider ids |
| 199 | function getActiveProviders() external view returns (bytes32[] memory); |
| 200 | |
| 201 | /// @notice Get the emergency price for a token |
| 202 | /// @param token Token address |
| 203 | /// @return Emergency price |
| 204 | function getEmergencyPrice(address token) external view returns (uint256); |
| 205 | |
| 206 | /// @notice Set the emergency price for a token |
| 207 | /// @param token Token address |
| 208 | /// @param price Emergency price |
| 209 | function setEmergencyPrice(address token, uint256 price) external; |
| 210 | |
| 211 | /// @notice Set the emergency price for multiple tokens in a batch |
| 212 | /// @param tokens Array of token addresses |
| 213 | /// @param prices Array of emergency prices |
| 214 | function batchSetEmergencyPrice(address[] calldata tokens, uint256[] calldata prices) external; |
| 215 | } |
| 216 |
0.0%
src/interfaces/oracles/ISuperOracleL2.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity 0.8.30; |
| 3 | |
| 4 | /// @title ISuperOracleL2 |
| 5 | /// @author Superform Labs |
| 6 | /// @notice Interface for Layer 2 Oracle for Superform |
| 7 | interface ISuperOracleL2 { |
| 8 | /*////////////////////////////////////////////////////////////// |
| 9 | ERRORS |
| 10 | //////////////////////////////////////////////////////////////*/ |
| 11 | /// @notice Error when no uptime feed is configured for the data oracle |
| 12 | error NO_UPTIME_FEED(); |
| 13 | |
| 14 | /// @notice Error when the L2 sequencer is down |
| 15 | error SEQUENCER_DOWN(); |
| 16 | |
| 17 | /// @notice Error when the grace period after sequencer restart is not over |
| 18 | error GRACE_PERIOD_NOT_OVER(); |
| 19 | |
| 20 | /*////////////////////////////////////////////////////////////// |
| 21 | EVENTS |
| 22 | //////////////////////////////////////////////////////////////*/ |
| 23 | /// @notice Emitted when an uptime feed is set for a data oracle |
| 24 | /// @param dataOracle The data oracle address |
| 25 | /// @param uptimeOracle The uptime feed address |
| 26 | event UptimeFeedSet(address dataOracle, address uptimeOracle); |
| 27 | |
| 28 | /// @notice Emitted when a grace period is set for an uptime oracle |
| 29 | /// @param uptimeOracle The uptime oracle address |
| 30 | /// @param gracePeriod The grace period in seconds |
| 31 | event GracePeriodSet(address uptimeOracle, uint256 gracePeriod); |
| 32 | |
| 33 | /*////////////////////////////////////////////////////////////// |
| 34 | EXTERNAL FUNCTIONS |
| 35 | //////////////////////////////////////////////////////////////*/ |
| 36 | /// @notice Set uptime feeds for multiple data oracles in batch |
| 37 | /// @param dataOracles Array of data oracle addresses to set uptime feeds for |
| 38 | /// @param uptimeOracles Array of uptime feed addresses to set |
| 39 | /// @param gracePeriods Array of grace periods in seconds after sequencer restart |
| 40 | function batchSetUptimeFeed( |
| 41 | address[] calldata dataOracles, |
| 42 | address[] calldata uptimeOracles, |
| 43 | uint256[] calldata gracePeriods |
| 44 | ) external; |
| 45 | } |
| 46 |
90.0%
src/libraries/AssetMetadataLib.sol
Lines covered: 9 / 10 (90.0%)
| 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 | 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 | function tryGetAssetDecimals(address asset_) internal view returns (bool ok, uint8 assetDecimals) { |
| 20 | if(asset_.code.length == 0) revert INVALID_ASSET(); |
| 21 | |
| 22 | (bool success, bytes memory encodedDecimals) = |
| 23 | address(asset_).staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); |
| 24 | if (success && encodedDecimals.length >= 32) { |
| 25 | uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); |
| 26 | if (returnedDecimals <= type(uint8).max) { |
| 27 | return (true, uint8(returnedDecimals)); |
| 28 | } |
| 29 | } |
| 30 | return (false, 0); |
| 31 | } |
| 32 | } |
| 33 |
0.0%
src/vendor/standards/ERC7540/IERC7540Vault.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
| 2 | pragma solidity >=0.8.0; |
| 3 | |
| 4 | import { IERC7741 } from "../ERC7741/IERC7741.sol"; |
| 5 | |
| 6 | interface IERC7540Operator { |
| 7 | /** |
| 8 | * @dev The event emitted when an operator is set. |
| 9 | * |
| 10 | * @param controller The address of the controller. |
| 11 | * @param operator The address of the operator. |
| 12 | * @param approved The approval status. |
| 13 | */ |
| 14 | event OperatorSet(address indexed controller, address indexed operator, bool approved); |
| 15 | |
| 16 | /** |
| 17 | * @dev Sets or removes an operator for the caller. |
| 18 | * |
| 19 | * @param operator The address of the operator. |
| 20 | * @param approved The approval status. |
| 21 | * @return Whether the call was executed successfully or not |
| 22 | */ |
| 23 | function setOperator(address operator, bool approved) external returns (bool); |
| 24 | |
| 25 | /** |
| 26 | * @dev Returns `true` if the `operator` is approved as an operator for an `controller`. |
| 27 | * |
| 28 | * @param controller The address of the controller. |
| 29 | * @param operator The address of the operator. |
| 30 | * @return status The approval status |
| 31 | */ |
| 32 | function isOperator(address controller, address operator) external view returns (bool status); |
| 33 | } |
| 34 | |
| 35 | interface IERC7540Deposit is IERC7540Operator { |
| 36 | event DepositRequest( |
| 37 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 38 | ); |
| 39 | /** |
| 40 | * @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit. |
| 41 | * |
| 42 | * - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow. |
| 43 | * - MUST revert if all of assets cannot be requested for deposit. |
| 44 | * - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 45 | * approval of ERC-20 tokens from owner to sender is NOT enough. |
| 46 | * |
| 47 | * @param assets the amount of deposit assets to transfer from owner |
| 48 | * @param controller the controller of the request who will be able to operate the request |
| 49 | * @param owner the source of the deposit assets |
| 50 | * |
| 51 | * NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token. |
| 52 | */ |
| 53 | |
| 54 | function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
| 55 | |
| 56 | /** |
| 57 | * @dev Returns the amount of requested assets in Pending state. |
| 58 | * |
| 59 | * - MUST NOT include any assets in Claimable state for deposit or mint. |
| 60 | * - MUST NOT show any variations depending on the caller. |
| 61 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 62 | */ |
| 63 | function pendingDepositRequest( |
| 64 | uint256 requestId, |
| 65 | address controller |
| 66 | ) |
| 67 | external |
| 68 | view |
| 69 | returns (uint256 pendingAssets); |
| 70 | |
| 71 | /** |
| 72 | * @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint. |
| 73 | * |
| 74 | * - MUST NOT include any assets in Pending state. |
| 75 | * - MUST NOT show any variations depending on the caller. |
| 76 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 77 | */ |
| 78 | function claimableDepositRequest( |
| 79 | uint256 requestId, |
| 80 | address controller |
| 81 | ) |
| 82 | external |
| 83 | view |
| 84 | returns (uint256 claimableAssets); |
| 85 | |
| 86 | /** |
| 87 | * @dev Mints shares Vault shares to receiver by claiming the Request of the controller. |
| 88 | * |
| 89 | * - MUST emit the Deposit event. |
| 90 | * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
| 91 | */ |
| 92 | function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
| 93 | |
| 94 | /** |
| 95 | * @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller. |
| 96 | * |
| 97 | * - MUST emit the Deposit event. |
| 98 | * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
| 99 | */ |
| 100 | function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
| 101 | } |
| 102 | |
| 103 | interface IERC7540Redeem is IERC7540Operator { |
| 104 | event RedeemRequest( |
| 105 | address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
| 106 | ); |
| 107 | |
| 108 | /** |
| 109 | * @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem. |
| 110 | * |
| 111 | * - MUST support a redeem Request flow where the control of shares is taken from sender directly |
| 112 | * where msg.sender has ERC-20 approval over the shares of owner. |
| 113 | * - MUST revert if all of shares cannot be requested for redeem. |
| 114 | * |
| 115 | * @param shares the amount of shares to be redeemed to transfer from owner |
| 116 | * @param controller the controller of the request who will be able to operate the request |
| 117 | * @param owner the source of the shares to be redeemed |
| 118 | * |
| 119 | * NOTE: most implementations will require pre-approval of the Vault with the Vault's share token. |
| 120 | */ |
| 121 | function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
| 122 | |
| 123 | /** |
| 124 | * @dev Returns the amount of requested shares in Pending state. |
| 125 | * |
| 126 | * - MUST NOT include any shares in Claimable state for redeem or withdraw. |
| 127 | * - MUST NOT show any variations depending on the caller. |
| 128 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 129 | */ |
| 130 | function pendingRedeemRequest( |
| 131 | uint256 requestId, |
| 132 | address controller |
| 133 | ) |
| 134 | external |
| 135 | view |
| 136 | returns (uint256 pendingShares); |
| 137 | |
| 138 | /** |
| 139 | * @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw. |
| 140 | * |
| 141 | * - MUST NOT include any shares in Pending state for redeem or withdraw. |
| 142 | * - MUST NOT show any variations depending on the caller. |
| 143 | * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
| 144 | */ |
| 145 | function claimableRedeemRequest( |
| 146 | uint256 requestId, |
| 147 | address controller |
| 148 | ) |
| 149 | external |
| 150 | view |
| 151 | returns (uint256 claimableShares); |
| 152 | } |
| 153 | |
| 154 | interface IERC7540CancelDeposit { |
| 155 | event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender); |
| 156 | event CancelDepositClaim( |
| 157 | address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets |
| 158 | ); |
| 159 | |
| 160 | /** |
| 161 | * @dev Submits a Request for cancelling the pending deposit Request |
| 162 | * |
| 163 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 164 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 165 | * - MUST set pendingCancelDepositRequest to `true` for the returned requestId after request |
| 166 | * - MUST increase claimableCancelDepositRequest for the returned requestId after fulfillment |
| 167 | * - SHOULD be claimable using `claimCancelDepositRequest` |
| 168 | * Note: while `pendingCancelDepositRequest` is `true`, `requestDeposit` cannot be called |
| 169 | */ |
| 170 | function cancelDepositRequest(uint256 requestId, address controller) external; |
| 171 | |
| 172 | /** |
| 173 | * @dev Returns whether the deposit Request is pending cancelation |
| 174 | * |
| 175 | * - MUST NOT show any variations depending on the caller. |
| 176 | */ |
| 177 | function pendingCancelDepositRequest( |
| 178 | uint256 requestId, |
| 179 | address controller |
| 180 | ) |
| 181 | external |
| 182 | view |
| 183 | returns (bool isPending); |
| 184 | |
| 185 | /** |
| 186 | * @dev Returns the amount of assets that were canceled from a deposit Request, and can now be claimed. |
| 187 | * |
| 188 | * - MUST NOT show any variations depending on the caller. |
| 189 | */ |
| 190 | function claimableCancelDepositRequest( |
| 191 | uint256 requestId, |
| 192 | address controller |
| 193 | ) |
| 194 | external |
| 195 | view |
| 196 | returns (uint256 claimableAssets); |
| 197 | |
| 198 | /** |
| 199 | * @dev Claims the canceled deposit assets, and removes the pending cancelation Request |
| 200 | * |
| 201 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 202 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 203 | * - MUST set pendingCancelDepositRequest to `false` for the returned requestId after request |
| 204 | * - MUST set claimableCancelDepositRequest to 0 for the returned requestId after fulfillment |
| 205 | */ |
| 206 | function claimCancelDepositRequest( |
| 207 | uint256 requestId, |
| 208 | address receiver, |
| 209 | address controller |
| 210 | ) |
| 211 | external |
| 212 | returns (uint256 assets); |
| 213 | } |
| 214 | |
| 215 | interface IERC7540CancelRedeem { |
| 216 | event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender); |
| 217 | event CancelRedeemClaim( |
| 218 | address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares |
| 219 | ); |
| 220 | |
| 221 | /** |
| 222 | * @dev Submits a Request for cancelling the pending redeem Request |
| 223 | * |
| 224 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 225 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 226 | * - MUST set pendingCancelRedeemRequest to `true` for the returned requestId after request |
| 227 | * - MUST increase claimableCancelRedeemRequest for the returned requestId after fulfillment |
| 228 | * - SHOULD be claimable using `claimCancelRedeemRequest` |
| 229 | * Note: while `pendingCancelRedeemRequest` is `true`, `requestRedeem` cannot be called |
| 230 | */ |
| 231 | function cancelRedeemRequest(uint256 requestId, address controller) external; |
| 232 | |
| 233 | /** |
| 234 | * @dev Returns whether the redeem Request is pending cancelation |
| 235 | * |
| 236 | * - MUST NOT show any variations depending on the caller. |
| 237 | */ |
| 238 | function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool isPending); |
| 239 | |
| 240 | /** |
| 241 | * @dev Returns the amount of shares that were canceled from a redeem Request, and can now be claimed. |
| 242 | * |
| 243 | * - MUST NOT show any variations depending on the caller. |
| 244 | */ |
| 245 | function claimableCancelRedeemRequest( |
| 246 | uint256 requestId, |
| 247 | address controller |
| 248 | ) |
| 249 | external |
| 250 | view |
| 251 | returns (uint256 claimableShares); |
| 252 | |
| 253 | /** |
| 254 | * @dev Claims the canceled redeem shares, and removes the pending cancelation Request |
| 255 | * |
| 256 | * - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
| 257 | * approval of ERC-20 tokens from controller to sender is NOT enough. |
| 258 | * - MUST set pendingCancelRedeemRequest to `false` for the returned requestId after request |
| 259 | * - MUST set claimableCancelRedeemRequest to 0 for the returned requestId after fulfillment |
| 260 | */ |
| 261 | function claimCancelRedeemRequest( |
| 262 | uint256 requestId, |
| 263 | address receiver, |
| 264 | address controller |
| 265 | ) |
| 266 | external |
| 267 | returns (uint256 shares); |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * @title IERC7540 |
| 272 | * @dev Fully async ERC7540 implementation according to the standard |
| 273 | * @dev Adapted from Centrifuge's IERC7540 implementation |
| 274 | */ |
| 275 | interface IERC7540 is IERC7540Deposit, IERC7540Redeem { } |
| 276 | |
| 277 | /** |
| 278 | * @title IERC7540Vault |
| 279 | * @dev This is the specific set of interfaces used by the SuperVaults |
| 280 | */ |
| 281 | interface IERC7540Vault is IERC7540, IERC7741 { |
| 282 | event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
| 283 | event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
| 284 | } |
| 285 |
0.0%
src/vendor/standards/ERC7575/IERC7575.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
| 2 | pragma solidity >=0.8.0; |
| 3 | |
| 4 | /** |
| 5 | * @dev Interface of the ERC165 standard, as defined in the |
| 6 | * https://eips.ethereum.org/EIPS/eip-165[EIP]. |
| 7 | * |
| 8 | * Implementers can declare support of contract interfaces, which can then be |
| 9 | * queried by others. |
| 10 | */ |
| 11 | interface IERC165 { |
| 12 | /** |
| 13 | * @dev Returns true if this contract implements the interface defined by |
| 14 | * `interfaceId`. See the corresponding |
| 15 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] |
| 16 | * to learn more about how these ids are created. |
| 17 | * |
| 18 | * This function call must use less than 30 000 gas. |
| 19 | */ |
| 20 | function supportsInterface(bytes4 interfaceId) external view returns (bool); |
| 21 | } |
| 22 | |
| 23 | interface IERC7575 is IERC165 { |
| 24 | event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); |
| 25 | event Withdraw( |
| 26 | address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares |
| 27 | ); |
| 28 | |
| 29 | /** |
| 30 | * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
| 31 | * |
| 32 | * - MUST be an ERC-20 token contract. |
| 33 | * - MUST NOT revert. |
| 34 | */ |
| 35 | function asset() external view returns (address assetTokenAddress); |
| 36 | |
| 37 | /** |
| 38 | * @dev Returns the address of the share token |
| 39 | * |
| 40 | * - MUST be an ERC-20 token contract. |
| 41 | * - MUST NOT revert. |
| 42 | */ |
| 43 | function share() external view returns (address shareTokenAddress); |
| 44 | |
| 45 | /** |
| 46 | * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
| 47 | * scenario where all the conditions are met. |
| 48 | * |
| 49 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 50 | * - MUST NOT show any variations depending on the caller. |
| 51 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 52 | * - MUST NOT revert. |
| 53 | * |
| 54 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 55 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 56 | * from. |
| 57 | */ |
| 58 | function convertToShares(uint256 assets) external view returns (uint256 shares); |
| 59 | |
| 60 | /** |
| 61 | * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
| 62 | * scenario where all the conditions are met. |
| 63 | * |
| 64 | * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
| 65 | * - MUST NOT show any variations depending on the caller. |
| 66 | * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
| 67 | * - MUST NOT revert. |
| 68 | * |
| 69 | * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
| 70 | * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
| 71 | * from. |
| 72 | */ |
| 73 | function convertToAssets(uint256 shares) external view returns (uint256 assets); |
| 74 | |
| 75 | /** |
| 76 | * @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
| 77 | * |
| 78 | * - SHOULD include any compounding that occurs from yield. |
| 79 | * - MUST be inclusive of any fees that are charged against assets in the Vault. |
| 80 | * - MUST NOT revert. |
| 81 | */ |
| 82 | function totalAssets() external view returns (uint256 totalManagedAssets); |
| 83 | |
| 84 | /** |
| 85 | * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
| 86 | * through a deposit call. |
| 87 | * |
| 88 | * - MUST return a limited value if receiver is subject to some deposit limit. |
| 89 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
| 90 | * - MUST NOT revert. |
| 91 | */ |
| 92 | function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
| 93 | |
| 94 | /** |
| 95 | * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
| 96 | * current on-chain conditions. |
| 97 | * |
| 98 | * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
| 99 | * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
| 100 | * in the same transaction. |
| 101 | * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
| 102 | * deposit would be accepted, regardless if the user has enough tokens approved, etc. |
| 103 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 104 | * - MUST NOT revert. |
| 105 | * |
| 106 | * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
| 107 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 108 | */ |
| 109 | function previewDeposit(uint256 assets) external view returns (uint256 shares); |
| 110 | |
| 111 | /** |
| 112 | * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
| 113 | * |
| 114 | * - MUST emit the Deposit event. |
| 115 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 116 | * deposit execution, and are accounted for during deposit. |
| 117 | * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
| 118 | * approving enough underlying tokens to the Vault contract, etc). |
| 119 | * |
| 120 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 121 | */ |
| 122 | function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
| 123 | |
| 124 | /** |
| 125 | * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
| 126 | * - MUST return a limited value if receiver is subject to some mint limit. |
| 127 | * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
| 128 | * - MUST NOT revert. |
| 129 | */ |
| 130 | function maxMint(address receiver) external view returns (uint256 maxShares); |
| 131 | |
| 132 | /** |
| 133 | * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
| 134 | * current on-chain conditions. |
| 135 | * |
| 136 | * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
| 137 | * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
| 138 | * same transaction. |
| 139 | * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
| 140 | * would be accepted, regardless if the user has enough tokens approved, etc. |
| 141 | * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
| 142 | * - MUST NOT revert. |
| 143 | * |
| 144 | * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
| 145 | * share price or some other type of condition, meaning the depositor will lose assets by minting. |
| 146 | */ |
| 147 | function previewMint(uint256 shares) external view returns (uint256 assets); |
| 148 | |
| 149 | /** |
| 150 | * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
| 151 | * |
| 152 | * - MUST emit the Deposit event. |
| 153 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
| 154 | * execution, and are accounted for during mint. |
| 155 | * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
| 156 | * approving enough underlying tokens to the Vault contract, etc). |
| 157 | * |
| 158 | * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
| 159 | */ |
| 160 | function mint(uint256 shares, address receiver) external returns (uint256 assets); |
| 161 | |
| 162 | /** |
| 163 | * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
| 164 | * Vault, through a withdraw call. |
| 165 | * |
| 166 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 167 | * - MUST NOT revert. |
| 168 | */ |
| 169 | function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
| 170 | |
| 171 | /** |
| 172 | * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
| 173 | * given current on-chain conditions. |
| 174 | * |
| 175 | * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
| 176 | * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
| 177 | * called |
| 178 | * in the same transaction. |
| 179 | * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
| 180 | * the withdrawal would be accepted, regardless if the user has enough shares, etc. |
| 181 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 182 | * - MUST NOT revert. |
| 183 | * |
| 184 | * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
| 185 | * share price or some other type of condition, meaning the depositor will lose assets by depositing. |
| 186 | */ |
| 187 | function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
| 188 | |
| 189 | /** |
| 190 | * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
| 191 | * |
| 192 | * - MUST emit the Withdraw event. |
| 193 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 194 | * withdraw execution, and are accounted for during withdraw. |
| 195 | * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
| 196 | * not having enough shares, etc). |
| 197 | * |
| 198 | * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 199 | * Those methods should be performed separately. |
| 200 | */ |
| 201 | function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
| 202 | |
| 203 | /** |
| 204 | * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
| 205 | * through a redeem call. |
| 206 | * |
| 207 | * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
| 208 | * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
| 209 | * - MUST NOT revert. |
| 210 | */ |
| 211 | function maxRedeem(address owner) external view returns (uint256 maxShares); |
| 212 | |
| 213 | /** |
| 214 | * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, |
| 215 | * given current on-chain conditions. |
| 216 | * |
| 217 | * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
| 218 | * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
| 219 | * same transaction. |
| 220 | * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
| 221 | * redemption would be accepted, regardless if the user has enough shares, etc. |
| 222 | * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
| 223 | * - MUST NOT revert. |
| 224 | * |
| 225 | * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
| 226 | * share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
| 227 | */ |
| 228 | function previewRedeem(uint256 shares) external view returns (uint256 assets); |
| 229 | |
| 230 | /** |
| 231 | * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
| 232 | * |
| 233 | * - MUST emit the Withdraw event. |
| 234 | * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
| 235 | * redeem execution, and are accounted for during redeem. |
| 236 | * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
| 237 | * not having enough shares, etc). |
| 238 | * |
| 239 | * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
| 240 | * Those methods should be performed separately. |
| 241 | */ |
| 242 | function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
| 243 | } |
| 244 |
0.0%
src/vendor/standards/ERC7741/IERC7741.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
| 2 | pragma solidity >=0.8.0; |
| 3 | |
| 4 | interface IERC7741 { |
| 5 | /** |
| 6 | * @dev Grants or revokes permissions for `operator` to manage Requests on behalf of the |
| 7 | * `msg.sender`, using an [EIP-712](./eip-712.md) signature. |
| 8 | */ |
| 9 | function authorizeOperator( |
| 10 | address controller, |
| 11 | address operator, |
| 12 | bool approved, |
| 13 | bytes32 nonce, |
| 14 | uint256 deadline, |
| 15 | bytes memory signature |
| 16 | ) |
| 17 | external |
| 18 | returns (bool); |
| 19 | |
| 20 | /** |
| 21 | * @dev Revokes the given `nonce` for `msg.sender` as the `owner`. |
| 22 | */ |
| 23 | function invalidateNonce(bytes32 nonce) external; |
| 24 | |
| 25 | /** |
| 26 | * @dev Returns whether the given `nonce` has been used for the `controller`. |
| 27 | */ |
| 28 | function authorizations(address controller, bytes32 nonce) external view returns (bool used); |
| 29 | |
| 30 | /** |
| 31 | * @dev Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR |
| 32 | * should be unique to the contract and chain to prevent replay attacks from other domains, |
| 33 | * and satisfy the requirements of EIP-712, but is otherwise unconstrained. |
| 34 | */ |
| 35 | function DOMAIN_SEPARATOR() external view returns (bytes32); |
| 36 | } |
| 37 |
0.0%
test/mocks/MockYieldSourceOracle.sol
Lines covered: 0 / 53 (0.0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
| 2 | pragma solidity ^0.8.30; |
| 3 | |
| 4 | import { IYieldSourceOracle } from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
| 5 | |
| 6 | // Mock YieldSourceOracle implementation for testing |
| 7 | contract MockYieldSourceOracle is IYieldSourceOracle { |
| 8 | uint256 public pricePerShare; |
| 9 | uint256 public tvl; |
| 10 | uint256 public tvlByOwner; |
| 11 | bool public validity; |
| 12 | mapping(address => bool) public validAssetMap; |
| 13 | |
| 14 | constructor(uint256 _pricePerShare, uint256 _tvl, uint256 _tvlByOwner, bool _validity) { |
| 15 | pricePerShare = _pricePerShare; |
| 16 | tvl = _tvl; |
| 17 | tvlByOwner = _tvlByOwner; |
| 18 | validity = _validity; |
| 19 | } |
| 20 | |
| 21 | function setPricePerShare(uint256 _pricePerShare) external { |
| 22 | pricePerShare = _pricePerShare; |
| 23 | } |
| 24 | |
| 25 | function setTVL(uint256 _tvl) external { |
| 26 | tvl = _tvl; |
| 27 | } |
| 28 | |
| 29 | function setTVLByOwner(uint256 _tvlByOwner) external { |
| 30 | tvlByOwner = _tvlByOwner; |
| 31 | } |
| 32 | |
| 33 | function setValidity(bool _validity) external { |
| 34 | validity = _validity; |
| 35 | } |
| 36 | |
| 37 | function setValidAsset(address asset, bool isValid) external { |
| 38 | validAssetMap[asset] = isValid; |
| 39 | } |
| 40 | |
| 41 | function decimals(address) external pure returns (uint8) { |
| 42 | return 18; |
| 43 | } |
| 44 | |
| 45 | function getShareOutput(address, address, uint256 assetsIn) external pure returns (uint256) { |
| 46 | return assetsIn; |
| 47 | } |
| 48 | |
| 49 | function getAssetOutput(address, address, uint256 sharesIn) public pure returns (uint256) { |
| 50 | return sharesIn; |
| 51 | } |
| 52 | |
| 53 | function getAssetOutputWithFees( |
| 54 | bytes32, |
| 55 | address, |
| 56 | address, |
| 57 | address, |
| 58 | uint256 sharesIn |
| 59 | ) |
| 60 | public |
| 61 | pure |
| 62 | returns (uint256) |
| 63 | { |
| 64 | return sharesIn; |
| 65 | } |
| 66 | |
| 67 | function getBalanceOfOwner(address, address) external view returns (uint256) { |
| 68 | return tvlByOwner; |
| 69 | } |
| 70 | |
| 71 | function getPricePerShare(address) external view returns (uint256) { |
| 72 | return pricePerShare; |
| 73 | } |
| 74 | |
| 75 | function getTVLByOwnerOfShares(address, address) external view returns (uint256) { |
| 76 | return tvlByOwner; |
| 77 | } |
| 78 | |
| 79 | function getTVL(address) external view returns (uint256) { |
| 80 | return tvl; |
| 81 | } |
| 82 | |
| 83 | function getPricePerShareMultiple(address[] memory) external view returns (uint256[] memory) { |
| 84 | uint256[] memory prices = new uint256[](1); |
| 85 | prices[0] = pricePerShare; |
| 86 | return prices; |
| 87 | } |
| 88 | |
| 89 | function getTVLByOwnerOfSharesMultiple( |
| 90 | address[] memory yieldSources, |
| 91 | address[][] memory |
| 92 | ) |
| 93 | external |
| 94 | view |
| 95 | returns (uint256[][] memory) |
| 96 | { |
| 97 | uint256[][] memory result = new uint256[][](yieldSources.length); |
| 98 | for (uint256 i = 0; i < yieldSources.length; i++) { |
| 99 | result[i] = new uint256[](1); |
| 100 | result[i][0] = tvlByOwner; |
| 101 | } |
| 102 | return result; |
| 103 | } |
| 104 | |
| 105 | function getTVLMultiple(address[] memory) external view returns (uint256[] memory) { |
| 106 | uint256[] memory tvls = new uint256[](1); |
| 107 | tvls[0] = tvl; |
| 108 | return tvls; |
| 109 | } |
| 110 | |
| 111 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 112 | return validAssetMap[asset]; |
| 113 | } |
| 114 | |
| 115 | function isValidUnderlyingAssets(address[] memory, address[] memory) external view returns (bool[] memory) { |
| 116 | bool[] memory validities = new bool[](1); |
| 117 | validities[0] = validity; |
| 118 | return validities; |
| 119 | } |
| 120 | } |
| 121 |
100.0%
test/recon/BeforeAfter.sol
Lines covered: 86 / 86 (100.0%)
| 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 | _currentOp = OpType.DEFAULT; |
| 45 | __before(); |
| 46 | _; |
| 47 | __after(); |
| 48 | } |
| 49 | |
| 50 | modifier updateGhostsWithOpType(OpType op) { |
| 51 | _currentOp = op; |
| 52 | __before(); |
| 53 | _; |
| 54 | __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 | _before.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
| 68 | _before.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
| 69 | _before.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
| 70 | _getActor() |
| 71 | ); |
| 72 | _before.superVaultShares[_getActor()] = superVault.balanceOf( |
| 73 | _getActor() |
| 74 | ); |
| 75 | |
| 76 | _before.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
| 77 | address(superVaultStrategy) |
| 78 | ); |
| 79 | _before.oraclePPS = superVaultAggregator.getPPS( |
| 80 | address(superVaultStrategy) |
| 81 | ); |
| 82 | } |
| 83 | |
| 84 | function __after() internal { |
| 85 | ( |
| 86 | _after.summedAccumulatorShares, |
| 87 | _after.summedAccumulatorCostBasis |
| 88 | ) = _sumSuperVaultValues(); |
| 89 | _after.naivePPS = _calculateNaivePPS(); |
| 90 | _after.summedTotalShares = _sumTotalShares(); |
| 91 | _after.summedTotalAssets = _sumStrategyAssets(); |
| 92 | _after.summedPendingRedeem = _sumRequestedRedemptions(); |
| 93 | |
| 94 | _after.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
| 95 | _after.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
| 96 | _after.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
| 97 | _getActor() |
| 98 | ); |
| 99 | _after.superVaultShares[_getActor()] = superVault.balanceOf( |
| 100 | _getActor() |
| 101 | ); |
| 102 | |
| 103 | _after.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
| 104 | address(superVaultStrategy) |
| 105 | ); |
| 106 | _after.oraclePPS = superVaultAggregator.getPPS( |
| 107 | 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 | address[] memory actors = _getActors(); |
| 116 | uint256 totalShares; |
| 117 | |
| 118 | totalShares += superVault.balanceOf(address(superVaultEscrow)); |
| 119 | for (uint256 i; i < actors.length; i++) { |
| 120 | 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 | uint256 totalSupply = superVault.totalSupply(); |
| 132 | |
| 133 | // If no shares exist, PPS is 0 |
| 134 | if (totalSupply == 0) { |
| 135 | return 0; |
| 136 | } |
| 137 | |
| 138 | // Calculate total assets across all locations |
| 139 | uint256 totalAssets = _sumStrategyAssets(); |
| 140 | |
| 141 | // Calculate naive PPS: (totalAssets * PRECISION) / totalSupply |
| 142 | // Using 1e18 as precision to match the system's PPS_DECIMALS |
| 143 | naivePPS = (totalAssets * superVault.PRECISION()) / totalSupply; |
| 144 | |
| 145 | return naivePPS; |
| 146 | } |
| 147 | |
| 148 | function _sumStrategyAssets() public view returns (uint256) { |
| 149 | // Get the underlying asset |
| 150 | address asset = superVault.asset(); |
| 151 | |
| 152 | uint256 totalAssets; |
| 153 | // 1. Assets held directly in SuperVaultStrategy |
| 154 | totalAssets += IERC20(asset).balanceOf(address(superVaultStrategy)); |
| 155 | |
| 156 | // 2. Loop through all yield sources and sum underlying asset balances |
| 157 | address[] memory yieldSources = _getYieldSources(); |
| 158 | for (uint256 i = 0; i < yieldSources.length; i++) { |
| 159 | if (yieldSources[i] != address(0)) { |
| 160 | // Get the underlying asset balance held in each yield source |
| 161 | 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 | address[] memory actors = _getActors(); |
| 174 | |
| 175 | for (uint256 i; i < actors.length; i++) { |
| 176 | sumAccumulatorShares += superVaultStrategy |
| 177 | .getSuperVaultState(actors[i]) |
| 178 | .accumulatorShares; |
| 179 | sumAccumulatorCostBasis += superVaultStrategy |
| 180 | .getSuperVaultState(actors[i]) |
| 181 | .accumulatorCostBasis; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | function _sumRequestedRedemptions() internal returns (uint256) { |
| 186 | address[] memory actors = _getActors(); |
| 187 | uint256 totalRequested; |
| 188 | for (uint256 i; i < actors.length; i++) { |
| 189 | totalRequested += superVault.pendingRedeemRequest(0, actors[i]); |
| 190 | } |
| 191 | |
| 192 | return totalRequested; |
| 193 | } |
| 194 | |
| 195 | function _getPendingAsAssets() internal returns (uint256) { |
| 196 | uint256 pendingRedemptions = superVault.pendingRedeemRequest( |
| 197 | 0, |
| 198 | _getActor() |
| 199 | ); |
| 200 | uint256 pendingRedemptionsAsAssets = superVault.convertToAssets( |
| 201 | pendingRedemptions |
| 202 | ); |
| 203 | |
| 204 | return pendingRedemptionsAsAssets; |
| 205 | } |
| 206 | |
| 207 | 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 | } |
| 219 |
100.0%
test/recon/CryticTester.sol
Lines covered: 2 / 2 (100.0%)
| 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 | contract CryticTester is TargetFunctions, CryticAsserts { |
| 11 | constructor() payable { |
| 12 | setup(); |
| 13 | } |
| 14 | } |
| 15 |
95.0%
test/recon/Properties.sol
Lines covered: 330 / 346 (95.0%)
| 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 | function invariant_oraclePPSDoesntChangeOnAddOrRemove() public returns (bool) { |
| 96 | if (_currentOp == OpType.ADD || _currentOp == OpType.REMOVE) { |
| 97 | 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 | function invariant_maxRedeemMaxWithdrawSymmetry() public returns (bool) { |
| 148 | uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
| 149 | // convertToShares uses current price instead of avg from fulfillment |
| 150 | uint256 withdrawPrice = superVaultStrategy.getAverageWithdrawPrice( |
| 151 | _getActor() |
| 152 | ); |
| 153 | uint256 maxWithdrawAsShares = maxWithdraw.mulDiv( |
| 154 | superVault.PRECISION(), |
| 155 | withdrawPrice, |
| 156 | Math.Rounding.Floor |
| 157 | ); |
| 158 | |
| 159 | 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 | function invariant_totalSharesDontDecreaseOnRedemptionRequest() public returns (bool) { |
| 167 | if (_currentOp == OpType.REQUEST) { |
| 168 | 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 | function invariant_shareSolvency() public returns (bool) { |
| 179 | uint256 sumOfShares = _sumTotalShares(); |
| 180 | eq(superVault.totalSupply(), sumOfShares, "vault shares are insolvent"); |
| 181 | return true; |
| 182 | } |
| 183 | |
| 184 | /// @dev Property: balanceOf(escrow) >= SUM(controllers.pendingRedeemRequest) |
| 185 | function invariant_escrowBalance() public returns (bool) { |
| 186 | address[] memory actors = _getActors(); |
| 187 | |
| 188 | uint256 escrowBalance = superVault.balanceOf(address(superVaultEscrow)); |
| 189 | uint256 pendingActorShares; |
| 190 | for (uint256 i; i < actors.length; i++) { |
| 191 | pendingActorShares += superVault.pendingRedeemRequest(0, actors[i]); |
| 192 | } |
| 193 | |
| 194 | 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 | function invariant_maxMintZeroWhenPaused() public returns (bool) { |
| 204 | bool paused = superVaultAggregator.isStrategyPaused( |
| 205 | address(superVaultStrategy) |
| 206 | ); |
| 207 | uint256 maxMint = superVault.maxMint(_getActor()); |
| 208 | |
| 209 | if (paused) { |
| 210 | 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 | function invariant_maxDepositZeroWhenPaused() public returns (bool) { |
| 217 | bool paused = superVaultAggregator.isStrategyPaused( |
| 218 | address(superVaultStrategy) |
| 219 | ); |
| 220 | uint256 maxDeposit = superVault.maxDeposit(_getActor()); |
| 221 | |
| 222 | if (paused) { |
| 223 | 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 | function invariant_accumulatorSharesSolvency() public returns (bool) { |
| 234 | if (_currentOp == OpType.TRANSFER) { |
| 235 | 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 | function invariant_accumulatorCostBasisSolvency() public returns (bool) { |
| 246 | if (_currentOp == OpType.TRANSFER) { |
| 247 | 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 | function invariant_cancelDoesntChangeTotalSupply() public returns (bool) { |
| 258 | if (_currentOp == OpType.CANCEL) { |
| 259 | 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 | function invariant_assetBacking() public returns (bool) { |
| 270 | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 271 | |
| 272 | if (superVault.totalSupply() > 0) { |
| 273 | 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 | function invariant_totalAssets() public returns (bool) { |
| 284 | uint256 totalShares = _sumTotalShares(); |
| 285 | uint256 pps = superVaultAggregator.getPPS(address(superVaultStrategy)); |
| 286 | uint256 implicitTotalAssets = (totalShares * pps) / |
| 287 | superVault.PRECISION(); |
| 288 | |
| 289 | uint256 vaultTotalAssets = superVault.totalAssets(); |
| 290 | 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 | function invariant_avgPPSDoesntDecrease() public returns (bool) { |
| 300 | uint256 currentPrice = _before.oraclePPS; |
| 301 | uint256 beforeAvgPPS = _before.state[_getActor()].averageRequestPPS; |
| 302 | uint256 afterAvgPPS = _after.state[_getActor()].averageRequestPPS; |
| 303 | |
| 304 | if (_currentOp == OpType.REQUEST && currentPrice >= beforeAvgPPS) { |
| 305 | 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 | function global_previewEquivalenceFromShares_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES(uint256 shares) public { |
| 316 | uint256 previewMintAssets = superVault.previewMint(shares); |
| 317 | uint256 previewDepositShares = superVault.previewDeposit( |
| 318 | previewMintAssets |
| 319 | ); |
| 320 | uint256 price = superVaultStrategy.getStoredPPS(); |
| 321 | |
| 322 | if (price > 0) { |
| 323 | eq( |
| 324 | shares, |
| 325 | previewDepositShares, |
| 326 | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES |
| 327 | ); |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | /// @dev Property: previewMint and previewDeposit equivalence (from assets) |
| 332 | function global_previewEquivalenceFromAssets_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS(uint256 assets) public { |
| 333 | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 334 | uint256 previewMintAssets_under = superVault.previewMint( |
| 335 | previewDepositShares |
| 336 | ); |
| 337 | uint256 previewMintAssets_over = superVault.previewMint( |
| 338 | previewDepositShares + 1 |
| 339 | ); |
| 340 | uint256 price = superVaultStrategy.getStoredPPS(); |
| 341 | |
| 342 | if (price > 0) { |
| 343 | gte( |
| 344 | assets, |
| 345 | previewMintAssets_under, |
| 346 | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
| 347 | ); |
| 348 | |
| 349 | lte( |
| 350 | assets, |
| 351 | previewMintAssets_over, |
| 352 | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
| 353 | ); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | /// @dev Property: previewMint is >= convertToAssets |
| 358 | function global_comparePreviewMintAndConvertToAssets_ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS( |
| 359 | uint256 shares |
| 360 | ) public { |
| 361 | uint256 previewMintAssets = superVault.previewMint(shares); |
| 362 | uint256 convertToAssets = superVault.convertToAssets(shares); |
| 363 | gte( |
| 364 | previewMintAssets, |
| 365 | convertToAssets, |
| 366 | ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS |
| 367 | ); |
| 368 | } |
| 369 | |
| 370 | /// @dev Property: convertToShares is >= previewDepositShares (equivalent without fees) |
| 371 | function global_comparePreviewDepositAndConvertToShares_ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT( |
| 372 | uint256 assets |
| 373 | ) public { |
| 374 | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 375 | uint256 convertToShares = superVault.convertToShares(assets); |
| 376 | gte( |
| 377 | convertToShares, |
| 378 | previewDepositShares, |
| 379 | 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 | function invariant_sumOfClaimable() public returns (bool) { |
| 387 | address[] memory actors = _getActors(); |
| 388 | |
| 389 | uint256 sumPending; |
| 390 | uint256 sumClaimable; |
| 391 | for (uint256 i; i < actors.length; i++) { |
| 392 | sumPending += superVault.pendingRedeemRequest(0, actors[i]); |
| 393 | sumClaimable += superVault.maxWithdraw(actors[i]); |
| 394 | } |
| 395 | |
| 396 | uint256 strategyBalance = MockERC20(superVault.asset()).balanceOf( |
| 397 | address(superVaultStrategy) |
| 398 | ); |
| 399 | |
| 400 | // precondition: all pending has been fulfilled |
| 401 | if (sumPending == 0) { |
| 402 | 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 | function invariant_sumOfAssetsMaxWithdrawable() public returns (bool) { |
| 413 | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 414 | |
| 415 | if (summedTotalAssets == 0) { |
| 416 | uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
| 417 | 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 | function invariant_avgPPSMonotonicity() public returns (bool) { |
| 428 | if ( |
| 429 | _currentOp == OpType.FULFILL && |
| 430 | _before.oraclePPS > _before.state[_getActor()].averageRequestPPS && // fulfilled at a higher price |
| 431 | _after.state[_getActor()].pendingRedeemRequest != 0 // redemptions have all been fulfilled/cancelled; avg gets reset to 0 in this case |
| 432 | ) { |
| 433 | gte( |
| 434 | _after.state[_getActor()].averageRequestPPS, |
| 435 | _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 | function invariant_accumulatorSharesGtPendingRequests() public returns (bool) { |
| 444 | address[] memory actors = _getActors(); |
| 445 | |
| 446 | for (uint256 i; i < actors.length; i++) { |
| 447 | ISuperVaultStrategy.SuperVaultState |
| 448 | memory state = superVaultStrategy.getSuperVaultState(actors[i]); |
| 449 | 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 | function invariant_accumulatorSharesIncrease() public returns (bool) { |
| 460 | if (_currentOp == OpType.ADD) { |
| 461 | uint256 accumulatorSharesBefore = _before |
| 462 | .state[_getActor()] |
| 463 | .accumulatorShares; |
| 464 | uint256 accumulatorSharesAfter = _after |
| 465 | .state[_getActor()] |
| 466 | .accumulatorShares; |
| 467 | uint256 actorSharesBefore = _before.superVaultShares[_getActor()]; |
| 468 | uint256 actorSharesAfter = _after.superVaultShares[_getActor()]; |
| 469 | 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 | function invariant_accumulatorCostBasisIncrease() public returns (bool) { |
| 480 | if (_currentOp == OpType.ADD) { |
| 481 | uint256 accumulatorCostBasisBefore = _before |
| 482 | .state[_getActor()] |
| 483 | .accumulatorCostBasis; |
| 484 | 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 | function invariant_fulfillOnlyBurnsRequestedAmount() public returns (bool) { |
| 498 | uint256 TOLERANCE_CONSTANT = 10 wei; // taken from SuperVaultStrategy |
| 499 | |
| 500 | if (_currentOp == OpType.FULFILL) { |
| 501 | uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
| 502 | _after.summedPendingRedeem; |
| 503 | uint256 totalSupplyDelta = _before.summedTotalShares - |
| 504 | _after.summedTotalShares; |
| 505 | |
| 506 | // Check that burned amount is within tolerance of requested amount |
| 507 | if (totalSupplyDelta < pendingRedeemDelta) { |
| 508 | // Burned less than requested - check within tolerance |
| 509 | gte( |
| 510 | totalSupplyDelta, |
| 511 | pendingRedeemDelta - TOLERANCE_CONSTANT, |
| 512 | "burned less than requested beyond tolerance" |
| 513 | ); |
| 514 | } else { |
| 515 | // Burned more than requested - check within tolerance |
| 516 | lte( |
| 517 | totalSupplyDelta, |
| 518 | pendingRedeemDelta + TOLERANCE_CONSTANT, |
| 519 | "burned more than requested beyond tolerance" |
| 520 | ); |
| 521 | } |
| 522 | } |
| 523 | return true; |
| 524 | } |
| 525 | |
| 526 | /// @dev Property: accumulatorShares decreases by the exact amounts requested when fulfilling redemptions |
| 527 | function invariant_accumulatorSharesDecreaseOnFulfill_exact() public returns (bool) { |
| 528 | if (_currentOp == OpType.FULFILL) { |
| 529 | uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
| 530 | _after.summedAccumulatorShares; |
| 531 | uint256 totalSharesDelta = _before.summedTotalShares - |
| 532 | _after.summedTotalShares; |
| 533 | eq( |
| 534 | accumulatorSharesDelta, |
| 535 | totalSharesDelta, |
| 536 | "accumulatorShares decreases by the exact amounts requested when fulfilling redemptions" |
| 537 | ); |
| 538 | } |
| 539 | return true; |
| 540 | } |
| 541 | |
| 542 | function invariant_accumulatorSharesDecreaseOnFulfill_with_tolerance() |
| 543 | public returns (bool) |
| 544 | { |
| 545 | if (_currentOp == OpType.FULFILL) { |
| 546 | uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
| 547 | _after.summedAccumulatorShares; |
| 548 | uint256 totalSharesDelta = _before.summedTotalShares - |
| 549 | _after.summedTotalShares; |
| 550 | if (accumulatorSharesDelta >= totalSharesDelta) { |
| 551 | lte( |
| 552 | accumulatorSharesDelta - totalSharesDelta, |
| 553 | TOLERANCE, |
| 554 | "accumulatorShares decreases by more than TOLERANCE when fulfilling redemptions" |
| 555 | ); |
| 556 | } else { |
| 557 | lte( |
| 558 | totalSharesDelta - accumulatorSharesDelta, |
| 559 | TOLERANCE, |
| 560 | "accumulatorShares decreases by less than TOLERANCE when fulfilling redemptions" |
| 561 | ); |
| 562 | } |
| 563 | } |
| 564 | return true; |
| 565 | } |
| 566 | |
| 567 | /// Optimization Setters |
| 568 | |
| 569 | function setpreviewAssetsGreater(uint256 shares) public { |
| 570 | uint256 previewMintAssets = superVault.previewMint(shares); |
| 571 | 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 | function setPreviewSharesGreater(uint256 assets) public { |
| 583 | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 584 | 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 | function setFulfilledDifference() public { |
| 600 | if (_currentOp == OpType.FULFILL) { |
| 601 | uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
| 602 | _after.summedPendingRedeem; |
| 603 | uint256 totalSupplyDelta = _before.summedTotalShares - |
| 604 | _after.summedTotalShares; |
| 605 | |
| 606 | // Check that burned amount is within tolerance of requested amount |
| 607 | if (totalSupplyDelta < pendingRedeemDelta) { |
| 608 | // Burned less than requested |
| 609 | int256 burnedLessThanRequested = int256(pendingRedeemDelta) - |
| 610 | int256(totalSupplyDelta); |
| 611 | } else { |
| 612 | // Burned more than requested |
| 613 | int256 burnedMoreThanRequested = int256(totalSupplyDelta) - |
| 614 | 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 | function optimize_maxDustAccumulation() public view returns (int256) { |
| 623 | address[] memory actors = _getActors(); |
| 624 | |
| 625 | uint256 summedClaimableRedemptionsAsAssets; |
| 626 | for (uint256 i; i < actors.length; i++) { |
| 627 | uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
| 628 | 0, |
| 629 | actors[i] |
| 630 | ); |
| 631 | 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 | function optimize_moreClaimableThanHeldDifference() |
| 643 | public |
| 644 | view |
| 645 | returns (int256) |
| 646 | { |
| 647 | address[] memory actors = _getActors(); |
| 648 | |
| 649 | uint256 summedClaimableRedemptionsAsAssets; |
| 650 | for (uint256 i; i < actors.length; i++) { |
| 651 | summedClaimableRedemptionsAsAssets += superVault.maxWithdraw( |
| 652 | actors[i] |
| 653 | ); |
| 654 | } |
| 655 | |
| 656 | uint256 totalAssets = _sumStrategyAssets(); |
| 657 | |
| 658 | if (summedClaimableRedemptionsAsAssets > totalAssets) { |
| 659 | return |
| 660 | int256(summedClaimableRedemptionsAsAssets) - |
| 661 | int256(totalAssets); |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | function optimize_burnMoreThanRequestedInRedemption() |
| 666 | public |
| 667 | view |
| 668 | returns (int256) |
| 669 | { |
| 670 | return burnedMoreThanRequested; |
| 671 | } |
| 672 | |
| 673 | 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 | function optimize_assetBackingDifference() public view returns (int256) { |
| 706 | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 707 | uint256 totalSupply = superVault.totalSupply(); |
| 708 | |
| 709 | if (summedTotalAssets == 0 && totalSupply > 0) { |
| 710 | 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 | function assert_canary_ASSERTION_CANARY(uint256 entropy) public { |
| 718 | t(entropy > 0, ASSERTION_CANARY); |
| 719 | } |
| 720 | |
| 721 | /// @dev Canary global invariant expected to fail immediately. |
| 722 | function invariant_canary() public returns (bool) { |
| 723 | 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 | function invariant_erc7540_1() public returns (bool) { |
| 731 | actor = _getActor(); |
| 732 | 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 | function invariant_erc7540_2() public returns (bool) { |
| 741 | actor = _getActor(); |
| 742 | 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 | function invariant_erc7540_3() public returns (bool) { |
| 751 | actor = _getActor(); |
| 752 | 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 | function global_erc7540_4_deposit_ASSERTION_ERC7540_4_DEPOSIT(uint256 amt) public stateless { |
| 761 | actor = _getActor(); |
| 762 | t( |
| 763 | erc7540_4_deposit(address(superVault), amt), |
| 764 | ASSERTION_ERC7540_4_DEPOSIT |
| 765 | ); |
| 766 | } |
| 767 | |
| 768 | function global_erc7540_4_mint_ASSERTION_ERC7540_4_MINT(uint256 amt) public stateless { |
| 769 | actor = _getActor(); |
| 770 | t( |
| 771 | erc7540_4_mint(address(superVault), amt), |
| 772 | ASSERTION_ERC7540_4_MINT |
| 773 | ); |
| 774 | } |
| 775 | |
| 776 | function global_erc7540_4_withdraw_ASSERTION_ERC7540_4_WITHDRAW(uint256 amt) public stateless { |
| 777 | actor = _getActor(); |
| 778 | t( |
| 779 | erc7540_4_withdraw(address(superVault), amt), |
| 780 | ASSERTION_ERC7540_4_WITHDRAW |
| 781 | ); |
| 782 | } |
| 783 | |
| 784 | function global_erc7540_4_redeem_ASSERTION_ERC7540_4_REDEEM(uint256 amt) public stateless { |
| 785 | actor = _getActor(); |
| 786 | t( |
| 787 | erc7540_4_redeem(address(superVault), amt), |
| 788 | ASSERTION_ERC7540_4_REDEEM |
| 789 | ); |
| 790 | } |
| 791 | |
| 792 | /// @dev Property 7540-5: requestRedeem reverts if the share balance is less than amount |
| 793 | function global_erc7540_5_ASSERTION_ERC7540_5(uint256 shares) public stateless { |
| 794 | actor = _getActor(); |
| 795 | t( |
| 796 | erc7540_5(address(superVault), address(superVault), shares), |
| 797 | 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 | function global_erc7540_7_withdraw_ASSERTION_ERC7540_7_WITHDRAW(uint256 amt) public stateless { |
| 831 | actor = _getActor(); |
| 832 | t( |
| 833 | erc7540_7_withdraw(address(superVault), amt), |
| 834 | 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 | function global_erc7540_7_redeem_ASSERTION_ERC7540_7_REDEEM(uint256 amt) public stateless { |
| 840 | actor = _getActor(); |
| 841 | |
| 842 | uint256 maxRedeem = superVault.maxRedeem(actor); |
| 843 | amt = between(amt, 0, maxRedeem); |
| 844 | |
| 845 | if (amt == 0) { |
| 846 | return; // Skip |
| 847 | } |
| 848 | |
| 849 | uint256 averageWithdrawPrice = superVaultStrategy |
| 850 | .getAverageWithdrawPrice(actor); |
| 851 | |
| 852 | // calculates assets in the same way as redeem to confirm that a nonzero amount is being requested |
| 853 | uint256 assets = amt.mulDiv( |
| 854 | averageWithdrawPrice, |
| 855 | superVault.PRECISION(), |
| 856 | Math.Rounding.Floor |
| 857 | ); |
| 858 | |
| 859 | try superVault.redeem(amt, actor, actor) {} catch { |
| 860 | if (amt > 0 && assets > 0) { |
| 861 | t( |
| 862 | false, |
| 863 | ASSERTION_ERC7540_7_REDEEM |
| 864 | ); |
| 865 | } |
| 866 | } |
| 867 | } |
| 868 | } |
| 869 |
99.0%
test/recon/Setup.sol
Lines covered: 158 / 159 (99.0%)
| 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 | vm.prank(address(this)); |
| 139 | _; |
| 140 | } |
| 141 | |
| 142 | modifier asActor() { |
| 143 | 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 | bool preFailed = _foundryFailSlotSet(); |
| 159 | _; |
| 160 | if (!preFailed && _foundryFailSlotSet()) assert(false); |
| 161 | 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 | (bool ok, bytes memory ret) = address(vm).staticcall( |
| 173 | abi.encodeWithSignature("load(address,bytes32)", address(vm), _FOUNDRY_FAIL_SLOT) |
| 174 | ); |
| 175 | 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 | erc4626YieldSource = _newYieldSource( |
| 194 | _getAsset(), |
| 195 | YieldSourceType.ERC4626 |
| 196 | ); |
| 197 | |
| 198 | // Deploy ERC5115 yield source |
| 199 | erc5115YieldSource = _newYieldSource( |
| 200 | _getAsset(), |
| 201 | YieldSourceType.ERC5115 |
| 202 | ); |
| 203 | |
| 204 | // Deploy ERC7540 yield source |
| 205 | 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 | superGovernor = new SuperGovernor( |
| 215 | address(this), // superGovernor role |
| 216 | address(this), // governor role |
| 217 | address(this), // bankManager role |
| 218 | address(this), // gasManager role |
| 219 | feeRecipient, // treasury |
| 220 | address(this) // prover |
| 221 | ); |
| 222 | |
| 223 | // 5. Deploy implementation contracts for the aggregator |
| 224 | vaultImpl = new SuperVault(address(superGovernor)); |
| 225 | strategyImpl = new SuperVaultStrategy(address(superGovernor)); |
| 226 | escrowImpl = new SuperVaultEscrow(); |
| 227 | |
| 228 | // 6. Deploy SuperVaultAggregator with implementation contracts |
| 229 | 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 | superGovernor.setAddress( |
| 238 | superGovernor.SUPER_VAULT_AGGREGATOR(), |
| 239 | address(superVaultAggregator) |
| 240 | ); |
| 241 | |
| 242 | address[] memory assets = _getAssets(); |
| 243 | superGovernor.setAddress(superGovernor.UP(), assets[1]); // the second deployed token in the AssetManager is the UPToken |
| 244 | 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 | erc4626YieldSourceOracle = new MockERC4626YieldSourceOracle(); |
| 250 | erc5115YieldSourceOracle = new MockERC5115YieldSourceOracle(); |
| 251 | erc7540YieldSourceOracle = new MockERC7540YieldSourceOracle(); |
| 252 | ECDSAPPSOracle = new MockECDSAPPSOracle(); |
| 253 | |
| 254 | // ECDSAPPSOracle setup |
| 255 | superGovernor.setActivePPSOracle(address(ECDSAPPSOracle)); |
| 256 | ECDSAPPSOracle.setSUPER_GOVERNORReturn(address(superVaultAggregator)); |
| 257 | |
| 258 | // Set valid assets for all oracles |
| 259 | asset = _getAsset(); |
| 260 | erc4626YieldSourceOracle.setValidAsset(asset, true); |
| 261 | erc5115YieldSourceOracle.setValidAsset(asset, true); |
| 262 | erc7540YieldSourceOracle.setValidAsset(asset, true); |
| 263 | |
| 264 | // 9. Create a vault trio using the aggregator |
| 265 | ISuperVaultAggregator.VaultCreationParams |
| 266 | 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 | 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 | 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 | ) = superVaultAggregator.createVault(params); |
| 286 | |
| 287 | // 10. Store the deployed contracts |
| 288 | superVault = SuperVault(vaultAddr); |
| 289 | superVaultStrategy = SuperVaultStrategy(payable(strategyAddr)); |
| 290 | superVaultEscrow = SuperVaultEscrow(escrowAddr); |
| 291 | |
| 292 | /// 11. Deploy all hook contracts and helper |
| 293 | merkleHelper = new MerkleTestHelper(); |
| 294 | |
| 295 | // Deploy ERC4626 Hooks |
| 296 | approveAndDeposit4626Hook = new ApproveAndDeposit4626VaultHook(); |
| 297 | deposit4626Hook = new Deposit4626VaultHook(); |
| 298 | redeem4626Hook = new Redeem4626VaultHook(); |
| 299 | |
| 300 | // Deploy ERC5115 Hooks |
| 301 | approveAndDeposit5115Hook = new ApproveAndDeposit5115VaultHook(); |
| 302 | deposit5115Hook = new Deposit5115VaultHook(); |
| 303 | redeem5115Hook = new Redeem5115VaultHook(); |
| 304 | |
| 305 | // Deploy ERC7540 Hooks |
| 306 | deposit7540Hook = new Deposit7540VaultHook(); |
| 307 | redeem7540Hook = new Redeem7540VaultHook(); |
| 308 | requestDeposit7540Hook = new RequestDeposit7540VaultHook(); |
| 309 | requestRedeem7540Hook = new RequestRedeem7540VaultHook(); |
| 310 | approveAndRequestDeposit7540Hook = new ApproveAndRequestDeposit7540VaultHook(); |
| 311 | cancelDepositRequest7540Hook = new CancelDepositRequest7540Hook(); |
| 312 | cancelRedeemRequest7540Hook = new CancelRedeemRequest7540Hook(); |
| 313 | claimCancelDepositRequest7540Hook = new ClaimCancelDepositRequest7540Hook(); |
| 314 | claimCancelRedeemRequest7540Hook = new ClaimCancelRedeemRequest7540Hook(); |
| 315 | withdraw7540Hook = new Withdraw7540VaultHook(); |
| 316 | |
| 317 | // Deploy Super Vault Hooks |
| 318 | cancelRedeemHook = new CancelRedeemHook(); |
| 319 | 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 | superGovernor.registerHook(address(approveAndDeposit4626Hook), false); |
| 324 | superGovernor.registerHook(address(deposit4626Hook), false); |
| 325 | superGovernor.registerHook(address(redeem4626Hook), true); // fulfill request hook |
| 326 | |
| 327 | // ERC5115 Hooks |
| 328 | superGovernor.registerHook(address(approveAndDeposit5115Hook), false); |
| 329 | superGovernor.registerHook(address(deposit5115Hook), false); |
| 330 | 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 | superGovernor.registerHook(address(deposit7540Hook), false); |
| 334 | superGovernor.registerHook(address(redeem7540Hook), true); // fulfill request hook |
| 335 | superGovernor.registerHook(address(requestDeposit7540Hook), false); |
| 336 | superGovernor.registerHook(address(requestRedeem7540Hook), false); |
| 337 | superGovernor.registerHook( |
| 338 | address(approveAndRequestDeposit7540Hook), |
| 339 | false |
| 340 | ); |
| 341 | superGovernor.registerHook( |
| 342 | address(cancelDepositRequest7540Hook), |
| 343 | false |
| 344 | ); |
| 345 | superGovernor.registerHook(address(cancelRedeemRequest7540Hook), false); |
| 346 | superGovernor.registerHook( |
| 347 | address(claimCancelDepositRequest7540Hook), |
| 348 | false |
| 349 | ); |
| 350 | superGovernor.registerHook( |
| 351 | address(claimCancelRedeemRequest7540Hook), |
| 352 | false |
| 353 | ); |
| 354 | superGovernor.registerHook(address(withdraw7540Hook), true); // fulfill request hook |
| 355 | |
| 356 | // Super Vault Hooks |
| 357 | superGovernor.registerHook(address(cancelRedeemHook), false); |
| 358 | superGovernor.registerHook(address(superVaultWithdraw7540Hook), true); // fulfill request hook |
| 359 | |
| 360 | // 12. Set up approval array for contracts that need token access |
| 361 | address[] memory approvalArray = new address[](6); |
| 362 | approvalArray[0] = address(superVault); |
| 363 | approvalArray[1] = address(superVaultStrategy); |
| 364 | approvalArray[2] = address(superVaultAggregator); |
| 365 | approvalArray[3] = erc4626YieldSource; |
| 366 | approvalArray[4] = erc5115YieldSource; |
| 367 | 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 | if (sourceType == YieldSourceType.ERC4626) { |
| 379 | return address(approveAndDeposit4626Hook); |
| 380 | } else if (sourceType == YieldSourceType.ERC5115) { |
| 381 | return address(approveAndDeposit5115Hook); |
| 382 | } 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 | if (sourceType == YieldSourceType.ERC4626) { |
| 392 | return address(redeem4626Hook); |
| 393 | } else if (sourceType == YieldSourceType.ERC5115) { |
| 394 | return address(redeem5115Hook); |
| 395 | } else if (sourceType == YieldSourceType.ERC7540) { |
| 396 | return address(redeem7540Hook); |
| 397 | } |
| 398 | 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 | if (sourceType == YieldSourceType.ERC4626) { |
| 407 | return address(erc4626YieldSourceOracle); |
| 408 | } else if (sourceType == YieldSourceType.ERC5115) { |
| 409 | return address(erc5115YieldSourceOracle); |
| 410 | } else if (sourceType == YieldSourceType.ERC7540) { |
| 411 | return address(erc7540YieldSourceOracle); |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | /// @dev Helper function to determine yield source type from address |
| 416 | function _getYieldSourceTypeFromAddress( |
| 417 | address yieldSource |
| 418 | ) internal view returns (YieldSourceType) { |
| 419 | // Get all available yield sources from YieldManager |
| 420 | address[] memory yieldSources = _getYieldSources(); |
| 421 | |
| 422 | // Check which index matches the current yield source |
| 423 | for (uint256 i = 0; i < yieldSources.length; i++) { |
| 424 | if (yieldSources[i] == yieldSource) { |
| 425 | // Return the corresponding type based on creation order: |
| 426 | // Index 0: ERC4626, Index 1: ERC5115, Index 2: ERC7540 |
| 427 | if (i == 0) return YieldSourceType.ERC4626; |
| 428 | if (i == 1) return YieldSourceType.ERC5115; |
| 429 | 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 | function _getRandomActor(uint256 entropy) public view returns (address) { |
| 451 | address[] memory actors = _getActors(); |
| 452 | |
| 453 | return actors[entropy % actors.length]; |
| 454 | } |
| 455 | } |
| 456 |
0.0%
test/recon/TargetFunctions.sol
Lines covered: 0 / 0 (0.0%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | pragma solidity ^0.8.0; |
| 3 | |
| 4 | // Chimera deps |
| 5 | import {vm} from "@chimera/Hevm.sol"; |
| 6 | |
| 7 | // Helpers |
| 8 | import {Panic} from "@recon/Panic.sol"; |
| 9 | |
| 10 | // Targets |
| 11 | // NOTE: Always import and apply them in alphabetical order, so much easier to debug! |
| 12 | import {AdminTargets} from "./targets/AdminTargets.sol"; |
| 13 | import {DoomsdayTargets} from "./targets/DoomsdayTargets.sol"; |
| 14 | import {ManagersTargets} from "./targets/ManagersTargets.sol"; |
| 15 | import {OracleTargets} from "./targets/OracleTargets.sol"; |
| 16 | import {SuperVaultTargets} from "./targets/SuperVaultTargets.sol"; |
| 17 | import {SuperVaultAggregatorTargets} from "./targets/SuperVaultAggregatorTargets.sol"; |
| 18 | import {SuperVaultEscrowTargets} from "./targets/SuperVaultEscrowTargets.sol"; |
| 19 | import {SuperVaultStrategyTargets} from "./targets/SuperVaultStrategyTargets.sol"; |
| 20 | import {SuperGovernorTargets} from "./targets/SuperGovernorTargets.sol"; |
| 21 | import {YieldSourceTargets} from "./targets/YieldSourceTargets.sol"; |
| 22 | |
| 23 | abstract contract TargetFunctions is |
| 24 | AdminTargets, |
| 25 | DoomsdayTargets, |
| 26 | ManagersTargets, |
| 27 | OracleTargets, |
| 28 | SuperVaultTargets, |
| 29 | SuperVaultAggregatorTargets, |
| 30 | SuperVaultEscrowTargets, |
| 31 | SuperVaultStrategyTargets, |
| 32 | SuperGovernorTargets, |
| 33 | YieldSourceTargets |
| 34 | { |
| 35 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
| 36 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
| 37 | } |
| 38 |
73.0%
test/recon/helpers/MerkleTestHelper.sol
Lines covered: 25 / 34 (73.0%)
| 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 | 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 | 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 | 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 | bytes memory depositArgs = abi.encodePacked(mockVault); // Only yield source address |
| 28 | 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 | bytes memory redeemArgs = abi.encodePacked(mockVault); // Only yield source address |
| 33 | leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
| 34 | |
| 35 | // Sort leaves to match standard Merkle tree ordering |
| 36 | if (leaves[0] > leaves[1]) { |
| 37 | (leaves[0], leaves[1]) = (leaves[1], leaves[0]); |
| 38 | } |
| 39 | |
| 40 | // For a 2-leaf tree, root is hash of the sorted leaves |
| 41 | root = keccak256(abi.encodePacked(leaves[0], leaves[1])); |
| 42 | |
| 43 | // Store original leaf hashes for proof assignment |
| 44 | bytes32 depositLeaf = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs)))); |
| 45 | bytes32 redeemLeaf = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
| 46 | |
| 47 | // Generate proofs for each leaf |
| 48 | proofs = new bytes32[][](2); |
| 49 | proofs[0] = new bytes32[](1); // Proof for deposit hook |
| 50 | 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 | if (leaves[0] == depositLeaf) { |
| 55 | // depositHook is first leaf after sorting |
| 56 | proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
| 57 | proofs[1][0] = leaves[0]; // Sibling of redeem leaf |
| 58 | } else { |
| 59 | // redeemHook is first leaf after sorting |
| 60 | proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
| 61 | 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 | function encodeDepositHookArgs( |
| 73 | address yieldSource, |
| 74 | uint256 amount, |
| 75 | bool usePrevHookAmount |
| 76 | ) public pure returns (bytes memory) { |
| 77 | return abi.encodePacked( |
| 78 | 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 | ) public pure returns (bytes memory) { |
| 97 | return abi.encodePacked( |
| 98 | 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 | function createLeaf(address hookAddress, bytes memory hookArgs) public pure returns (bytes32 leaf) { |
| 132 | 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.0%
test/recon/helpers/UnsafeSuperVaultAggregator.sol
Lines covered: 8 / 8 (100.0%)
| 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 | contract UnsafeSuperVaultAggregator is SuperVaultAggregator { |
| 8 | 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 | function validateHook( |
| 23 | address strategy, |
| 24 | ValidateHookArgs calldata args |
| 25 | ) external view override returns (bool isValid) { |
| 26 | return true; |
| 27 | } |
| 28 | } |
| 29 |
89.0%
test/recon/managers/YieldManager.sol
Lines covered: 26 / 29 (89.0%)
| 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 | if (__yieldSource == address(0)) { |
| 47 | 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 | function _getCurrentYieldSourceType() internal view returns (YieldSourceType) { |
| 60 | 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 | function _newYieldSource(address asset, YieldSourceType yieldSourceType) internal returns (address) { |
| 68 | address yieldSource_; |
| 69 | |
| 70 | if (yieldSourceType == YieldSourceType.ERC4626) { |
| 71 | yieldSource_ = address(new MockERC4626Tester(asset)); |
| 72 | } else if (yieldSourceType == YieldSourceType.ERC5115) { |
| 73 | yieldSource_ = address(new MockERC5115Tester(asset)); |
| 74 | } else if (yieldSourceType == YieldSourceType.ERC7540) { |
| 75 | yieldSource_ = address(new MockERC7540Tester(asset)); |
| 76 | } else { |
| 77 | revert InvalidYieldSourceType(); |
| 78 | } |
| 79 | |
| 80 | _addYieldSource(yieldSource_); |
| 81 | __yieldSource = yieldSource_; // sets the yield source as the current yield source |
| 82 | __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 | if (_yieldSources.contains(target)) { |
| 104 | revert YieldSourceExists(); |
| 105 | } |
| 106 | |
| 107 | _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 | address target = _yieldSources.at(entropy % _yieldSources.length()); |
| 136 | __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 | } |
| 153 |
35.0%
test/recon/mocks/MockECDSAPPSOracle.sol
Lines covered: 15 / 42 (35.0%)
| 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 | contract MockECDSAPPSOracle { |
| 8 | //<>=============================================================<> |
| 9 | //|| || |
| 10 | //|| NON-VIEW FUNCTIONS || |
| 11 | //|| || |
| 12 | //<>=============================================================<> |
| 13 | // Mock implementation of updatePPS |
| 14 | function updatePPS(IECDSAPPSOracle.UpdatePPSArgs memory args) public { |
| 15 | ISuperVaultAggregator.ForwardPPSArgs |
| 16 | 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 | 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 | function setSUPER_GOVERNORReturn(address _value0) public { |
| 36 | _SUPER_GOVERNORReturn_0 = _value0; |
| 37 | } |
| 38 | |
| 39 | // Function to set return values for UPDATE_PPS_TYPEHASH |
| 40 | function setUPDATE_PPS_TYPEHASHReturn(bytes32 _value0) public { |
| 41 | _UPDATE_PPS_TYPEHASHReturn_0 = _value0; |
| 42 | } |
| 43 | |
| 44 | // Function to set return values for domainSeparator |
| 45 | function setDomainSeparatorReturn(bytes32 _value0) public { |
| 46 | _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 | function SUPER_GOVERNOR() public view returns (address) { |
| 103 | return _SUPER_GOVERNORReturn_0; |
| 104 | } |
| 105 | |
| 106 | // Mock implementation of UPDATE_PPS_TYPEHASH |
| 107 | function UPDATE_PPS_TYPEHASH() public view returns (bytes32) { |
| 108 | return _UPDATE_PPS_TYPEHASHReturn_0; |
| 109 | } |
| 110 | |
| 111 | // Mock implementation of domainSeparator |
| 112 | function domainSeparator() public view returns (bytes32) { |
| 113 | return _domainSeparatorReturn_0; |
| 114 | } |
| 115 | |
| 116 | // Mock implementation of eip712Domain |
| 117 | function eip712Domain() |
| 118 | public |
| 119 | view |
| 120 | returns ( |
| 121 | bytes1, |
| 122 | string memory, |
| 123 | string memory, |
| 124 | uint256, |
| 125 | address, |
| 126 | bytes32, |
| 127 | uint256[] memory |
| 128 | ) |
| 129 | { |
| 130 | return ( |
| 131 | _eip712DomainReturn_0, |
| 132 | _eip712DomainReturn_1, |
| 133 | _eip712DomainReturn_2, |
| 134 | _eip712DomainReturn_3, |
| 135 | _eip712DomainReturn_4, |
| 136 | _eip712DomainReturn_5, |
| 137 | _eip712DomainReturn_6 |
| 138 | ); |
| 139 | } |
| 140 | |
| 141 | // Mock implementation of nonce |
| 142 | function nonce() public view returns (uint256) { |
| 143 | return _nonceReturn_0; |
| 144 | } |
| 145 | } |
| 146 |
89.0%
test/recon/mocks/MockERC4626Tester.sol
Lines covered: 105 / 117 (89.0%)
| 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 | 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 | uint256 shares = previewDeposit(assets); |
| 33 | _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 | uint256 assets = previewMint(shares); |
| 42 | _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 | function totalAssets() public view virtual returns (uint256) { |
| 67 | return asset.balanceOf(address(this)); |
| 68 | } |
| 69 | |
| 70 | function convertToShares( |
| 71 | uint256 assets |
| 72 | ) public view virtual returns (uint256) { |
| 73 | uint256 supply = totalSupply; |
| 74 | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
| 75 | } |
| 76 | |
| 77 | function convertToAssets( |
| 78 | uint256 shares |
| 79 | ) public view virtual returns (uint256) { |
| 80 | uint256 supply = totalSupply; |
| 81 | return supply == 0 ? shares : (shares * totalAssets()) / supply; |
| 82 | } |
| 83 | |
| 84 | function previewDeposit( |
| 85 | uint256 assets |
| 86 | ) public view virtual returns (uint256) { |
| 87 | 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 | 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 | function maxWithdraw(address owner) public view virtual returns (uint256) { |
| 117 | return convertToAssets(balanceOf[owner]); |
| 118 | } |
| 119 | |
| 120 | function maxRedeem(address owner) public view virtual returns (uint256) { |
| 121 | return balanceOf[owner]; |
| 122 | } |
| 123 | |
| 124 | function _deposit( |
| 125 | address caller, |
| 126 | address receiver, |
| 127 | uint256 assets, |
| 128 | uint256 shares |
| 129 | ) internal virtual { |
| 130 | asset.transferFrom(caller, address(this), assets); |
| 131 | _mint(receiver, shares); |
| 132 | emit Deposit(caller, receiver, assets, shares); |
| 133 | } |
| 134 | |
| 135 | function _withdraw( |
| 136 | address caller, |
| 137 | address receiver, |
| 138 | address owner, |
| 139 | uint256 assets, |
| 140 | uint256 shares |
| 141 | ) internal virtual { |
| 142 | if (caller != owner) { |
| 143 | allowance[owner][caller] -= shares; |
| 144 | } |
| 145 | _burn(owner, shares); |
| 146 | asset.transfer(receiver, assets); |
| 147 | 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 | contract MockERC4626Tester is ERC4626 { |
| 170 | mapping(FunctionType => RevertType) public revertBehaviours; |
| 171 | |
| 172 | uint8 public decimalsOffset; |
| 173 | /// @dev Track total losses |
| 174 | uint256 public totalLosses; |
| 175 | uint256 public totalGains; |
| 176 | uint256 public lossOnWithdraw; |
| 177 | uint256 public MAX_BPS = 10_000; |
| 178 | |
| 179 | constructor(address _asset) ERC4626(MockERC20(_asset)) {} |
| 180 | |
| 181 | /// Standard ERC4626 functions /// |
| 182 | |
| 183 | /// @dev Deposit assets, reverts as specified |
| 184 | function deposit( |
| 185 | uint256 assets, |
| 186 | address receiver |
| 187 | ) public override returns (uint256) { |
| 188 | _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
| 189 | return super.deposit(assets, receiver); |
| 190 | } |
| 191 | |
| 192 | /// @dev Mint shares, reverts as specified |
| 193 | function mint( |
| 194 | uint256 shares, |
| 195 | address receiver |
| 196 | ) public override returns (uint256) { |
| 197 | _performRevertBehaviour(revertBehaviours[FunctionType.MINT]); |
| 198 | return super.mint(shares, receiver); |
| 199 | } |
| 200 | |
| 201 | /// @dev Withdraw assets, reverts as specified |
| 202 | function withdraw( |
| 203 | uint256 assets, |
| 204 | address receiver, |
| 205 | address owner |
| 206 | ) public override returns (uint256) { |
| 207 | _performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]); |
| 208 | |
| 209 | uint256 shares = previewWithdraw(assets); |
| 210 | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 211 | _withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 212 | |
| 213 | return shares; |
| 214 | } |
| 215 | |
| 216 | /// @dev Redeem shares, reverts as specified |
| 217 | function redeem( |
| 218 | uint256 shares, |
| 219 | address receiver, |
| 220 | address owner |
| 221 | ) public override returns (uint256) { |
| 222 | _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
| 223 | |
| 224 | uint256 assets = previewRedeem(shares); |
| 225 | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 226 | _withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 227 | |
| 228 | return lossyAssets; |
| 229 | } |
| 230 | |
| 231 | /// @dev Preview deposit, reverts as specified |
| 232 | function previewDeposit( |
| 233 | uint256 assets |
| 234 | ) public view override returns (uint256) { |
| 235 | _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
| 236 | return super.previewDeposit(assets); |
| 237 | } |
| 238 | |
| 239 | /// @dev Preview mint, reverts as specified |
| 240 | function previewMint( |
| 241 | uint256 shares |
| 242 | ) public view override returns (uint256) { |
| 243 | _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 | _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 | _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
| 260 | return super.previewRedeem(shares); |
| 261 | } |
| 262 | |
| 263 | /// @dev Revert in different ways to test the revert behaviour |
| 264 | function _performRevertBehaviour(RevertType action) internal pure { |
| 265 | if (action == RevertType.THROW) { |
| 266 | revert("A normal Revert"); |
| 267 | } |
| 268 | |
| 269 | // 3 gas per iteration, consider changing to storage changes if traces are cluttered |
| 270 | if (action == RevertType.OOG) { |
| 271 | uint256 i; |
| 272 | while (true) { |
| 273 | ++i; |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | if (action == RevertType.RETURN_BOMB) { |
| 278 | uint256 _bytes = 2_000_000; |
| 279 | assembly { |
| 280 | return(0, _bytes) |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | 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 | function setRevertBehavior(FunctionType ft, RevertType rt) public { |
| 298 | revertBehaviours[ft] = rt; |
| 299 | } |
| 300 | |
| 301 | /// @dev Simulate a loss on the vault's assets |
| 302 | function simulateLoss(uint256 lossAmount) external { |
| 303 | MockERC20(asset).transfer(address(0xbeef), lossAmount); |
| 304 | totalLosses += lossAmount; |
| 305 | } |
| 306 | |
| 307 | /// @dev Simulate a gain on the vault's assets (similar to Yearn's profit taking) |
| 308 | function simulateGain(uint256 gainAmount) external { |
| 309 | MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
| 310 | totalGains += gainAmount; |
| 311 | } |
| 312 | |
| 313 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
| 314 | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 315 | _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 | function setDecimalsOffset(uint8 targetDecimalsOffset) external { |
| 321 | if (totalSupply != 0) { |
| 322 | revert("Supply is not zero"); |
| 323 | } |
| 324 | decimalsOffset = targetDecimalsOffset; |
| 325 | } |
| 326 | } |
| 327 |
10.0%
test/recon/mocks/MockERC4626YieldSourceOracle.sol
Lines covered: 6 / 57 (10.0%)
| 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 | contract MockERC4626YieldSourceOracle is IYieldSourceOracle { |
| 10 | mapping(address => bool) public validAssetMap; |
| 11 | |
| 12 | function setValidAsset(address asset, bool isValid) external { |
| 13 | validAssetMap[asset] = isValid; |
| 14 | } |
| 15 | |
| 16 | function decimals(address yieldSourceAddress) external view returns (uint8) { |
| 17 | return IERC4626(yieldSourceAddress).decimals(); |
| 18 | } |
| 19 | |
| 20 | function getShareOutput( |
| 21 | address yieldSourceAddress, |
| 22 | address, |
| 23 | uint256 assetsIn |
| 24 | ) external view returns (uint256) { |
| 25 | return IERC4626(yieldSourceAddress).previewDeposit(assetsIn); |
| 26 | } |
| 27 | |
| 28 | function getAssetOutput( |
| 29 | address yieldSourceAddress, |
| 30 | address, |
| 31 | uint256 sharesIn |
| 32 | ) public view returns (uint256) { |
| 33 | return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
| 34 | } |
| 35 | |
| 36 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 37 | IERC4626 yieldSource = IERC4626(yieldSourceAddress); |
| 38 | uint256 _decimals = yieldSource.decimals(); |
| 39 | return yieldSource.convertToAssets(10 ** _decimals); |
| 40 | } |
| 41 | |
| 42 | function getBalanceOfOwner( |
| 43 | address yieldSourceAddress, |
| 44 | address ownerOfShares |
| 45 | ) external view returns (uint256) { |
| 46 | return IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
| 47 | } |
| 48 | |
| 49 | function getTVLByOwnerOfShares( |
| 50 | address yieldSourceAddress, |
| 51 | address ownerOfShares |
| 52 | ) external view returns (uint256) { |
| 53 | uint256 shares = IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
| 54 | return IERC4626(yieldSourceAddress).convertToAssets(shares); |
| 55 | } |
| 56 | |
| 57 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 58 | return IERC4626(yieldSourceAddress).totalAssets(); |
| 59 | } |
| 60 | |
| 61 | function getPricePerShareMultiple( |
| 62 | address[] memory yieldSourceAddresses |
| 63 | ) external view returns (uint256[] memory) { |
| 64 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 65 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 66 | IERC4626 yieldSource = IERC4626(yieldSourceAddresses[i]); |
| 67 | uint256 _decimals = yieldSource.decimals(); |
| 68 | prices[i] = yieldSource.convertToAssets(10 ** _decimals); |
| 69 | } |
| 70 | return prices; |
| 71 | } |
| 72 | |
| 73 | function getTVLByOwnerOfSharesMultiple( |
| 74 | address[] memory yieldSourceAddresses, |
| 75 | address[][] memory ownersOfShares |
| 76 | ) external view returns (uint256[][] memory) { |
| 77 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 78 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 79 | result[i] = new uint256[](ownersOfShares[i].length); |
| 80 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 81 | uint256 shares = IERC4626(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
| 82 | result[i][j] = IERC4626(yieldSourceAddresses[i]).convertToAssets(shares); |
| 83 | } |
| 84 | } |
| 85 | return result; |
| 86 | } |
| 87 | |
| 88 | function getTVLMultiple( |
| 89 | address[] memory yieldSourceAddresses |
| 90 | ) external view returns (uint256[] memory) { |
| 91 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 92 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 93 | tvls[i] = IERC4626(yieldSourceAddresses[i]).totalAssets(); |
| 94 | } |
| 95 | return tvls; |
| 96 | } |
| 97 | |
| 98 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 99 | return validAssetMap[asset]; |
| 100 | } |
| 101 | |
| 102 | function isValidUnderlyingAssets( |
| 103 | address[] memory, |
| 104 | address[] memory assets |
| 105 | ) external view returns (bool[] memory) { |
| 106 | bool[] memory validities = new bool[](assets.length); |
| 107 | for (uint256 i = 0; i < assets.length; i++) { |
| 108 | validities[i] = validAssetMap[assets[i]]; |
| 109 | } |
| 110 | return validities; |
| 111 | } |
| 112 | |
| 113 | function getAssetOutputWithFees( |
| 114 | bytes32, |
| 115 | address yieldSourceAddress, |
| 116 | address, |
| 117 | address, |
| 118 | uint256 sharesIn |
| 119 | ) external view returns (uint256) { |
| 120 | return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
| 121 | } |
| 122 | } |
25.0%
test/recon/mocks/MockERC5115Tester.sol
Lines covered: 26 / 104 (25.0%)
| 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 | MockERC20 public immutable yieldToken; |
| 8 | address[] public tokensIn; |
| 9 | 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 | tokensIn.push(address(_yieldToken)); |
| 32 | tokensOut.push(address(_yieldToken)); |
| 33 | } |
| 34 | |
| 35 | function deposit( |
| 36 | address receiver, |
| 37 | address tokenIn, |
| 38 | uint256 amountTokenToDeposit, |
| 39 | uint256 minSharesOut, |
| 40 | bool depositFromInternalBalance |
| 41 | ) public virtual returns (uint256 amountSharesOut) { |
| 42 | amountSharesOut = previewDeposit(tokenIn, amountTokenToDeposit); |
| 43 | |
| 44 | MockERC20(tokenIn).transferFrom( |
| 45 | msg.sender, |
| 46 | address(this), |
| 47 | amountTokenToDeposit |
| 48 | ); |
| 49 | _mint(receiver, amountSharesOut); |
| 50 | |
| 51 | emit Deposit( |
| 52 | msg.sender, |
| 53 | receiver, |
| 54 | tokenIn, |
| 55 | amountTokenToDeposit, |
| 56 | amountSharesOut |
| 57 | ); |
| 58 | } |
| 59 | |
| 60 | function exchangeRate() public view virtual returns (uint256) { |
| 61 | uint256 supply = totalSupply; |
| 62 | if (supply == 0) return 1e18; |
| 63 | return (yieldToken.balanceOf(address(this)) * 1e18) / supply; |
| 64 | } |
| 65 | |
| 66 | function getTokensIn() public view virtual returns (address[] memory) { |
| 67 | return tokensIn; |
| 68 | } |
| 69 | |
| 70 | function getTokensOut() public view virtual returns (address[] memory) { |
| 71 | return tokensOut; |
| 72 | } |
| 73 | |
| 74 | function previewDeposit( |
| 75 | address tokenIn, |
| 76 | uint256 amountTokenToDeposit |
| 77 | ) public view virtual returns (uint256 amountSharesOut) { |
| 78 | require(tokenIn == address(yieldToken), "Invalid token"); |
| 79 | uint256 supply = totalSupply; |
| 80 | if (supply == 0) { |
| 81 | return amountTokenToDeposit; |
| 82 | } |
| 83 | return |
| 84 | (amountTokenToDeposit * supply) / |
| 85 | yieldToken.balanceOf(address(this)); |
| 86 | } |
| 87 | |
| 88 | function previewRedeem( |
| 89 | address tokenOut, |
| 90 | uint256 amountSharesToRedeem |
| 91 | ) public view virtual returns (uint256 amountTokenOut) { |
| 92 | require(tokenOut == address(yieldToken), "Invalid token"); |
| 93 | uint256 supply = totalSupply; |
| 94 | if (supply == 0) { |
| 95 | return amountSharesToRedeem; |
| 96 | } |
| 97 | return |
| 98 | (amountSharesToRedeem * yieldToken.balanceOf(address(this))) / |
| 99 | supply; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | enum RevertType { |
| 104 | NONE, |
| 105 | THROW, |
| 106 | OOG, |
| 107 | RETURN_BOMB, |
| 108 | REVERT_BOMB |
| 109 | } |
| 110 | |
| 111 | contract MockERC5115Tester is ERC5115 { |
| 112 | RevertType public revertBehaviour; |
| 113 | uint256 public totalLosses; |
| 114 | uint256 public totalGains; |
| 115 | uint256 public lossOnWithdraw; |
| 116 | uint256 public MAX_BPS = 10_000; |
| 117 | |
| 118 | constructor(address _yieldToken) ERC5115(MockERC20(_yieldToken)) {} |
| 119 | |
| 120 | function deposit( |
| 121 | address receiver, |
| 122 | address tokenIn, |
| 123 | uint256 amountTokenToDeposit, |
| 124 | uint256 minSharesOut, |
| 125 | bool depositFromInternalBalance |
| 126 | ) public override returns (uint256 amountSharesOut) { |
| 127 | _performRevertBehaviour(revertBehaviour); |
| 128 | return |
| 129 | super.deposit( |
| 130 | receiver, |
| 131 | tokenIn, |
| 132 | amountTokenToDeposit, |
| 133 | minSharesOut, |
| 134 | depositFromInternalBalance |
| 135 | ); |
| 136 | } |
| 137 | |
| 138 | function redeem( |
| 139 | address receiver, |
| 140 | uint256 amountSharesToRedeem, |
| 141 | address tokenOut, |
| 142 | uint256 minTokenOut, |
| 143 | bool burnFromInternalBalance |
| 144 | ) public virtual returns (uint256 amountTokenOut) { |
| 145 | amountTokenOut = previewRedeem(tokenOut, amountSharesToRedeem); |
| 146 | |
| 147 | _burn(msg.sender, amountSharesToRedeem); |
| 148 | uint256 lossyAmountTokenOut = amountTokenOut - |
| 149 | ((amountTokenOut * lossOnWithdraw) / MAX_BPS); |
| 150 | MockERC20(tokenOut).transfer(receiver, lossyAmountTokenOut); |
| 151 | |
| 152 | emit Redeem( |
| 153 | msg.sender, |
| 154 | receiver, |
| 155 | tokenOut, |
| 156 | amountSharesToRedeem, |
| 157 | amountTokenOut |
| 158 | ); |
| 159 | } |
| 160 | |
| 161 | function _performRevertBehaviour(RevertType action) internal pure { |
| 162 | if (action == RevertType.THROW) { |
| 163 | revert("A normal Revert"); |
| 164 | } |
| 165 | |
| 166 | if (action == RevertType.OOG) { |
| 167 | uint256 i; |
| 168 | while (true) { |
| 169 | ++i; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | if (action == RevertType.RETURN_BOMB) { |
| 174 | uint256 _bytes = 2_000_000; |
| 175 | assembly { |
| 176 | return(0, _bytes) |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | if (action == RevertType.REVERT_BOMB) { |
| 181 | uint256 _bytes = 2_000_000; |
| 182 | assembly { |
| 183 | revert(0, _bytes) |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | return; // NONE |
| 188 | } |
| 189 | |
| 190 | function setRevertBehavior(RevertType rt) public { |
| 191 | revertBehaviour = rt; |
| 192 | } |
| 193 | |
| 194 | function simulateLoss(uint256 lossAmount) external { |
| 195 | MockERC20(yieldToken).transfer(address(0xbeef), lossAmount); |
| 196 | totalLosses += lossAmount; |
| 197 | } |
| 198 | |
| 199 | function simulateGain(uint256 gainAmount) external { |
| 200 | MockERC20(yieldToken).transferFrom( |
| 201 | msg.sender, |
| 202 | address(this), |
| 203 | gainAmount |
| 204 | ); |
| 205 | totalGains += gainAmount; |
| 206 | } |
| 207 | |
| 208 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
| 209 | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 210 | _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
| 211 | lossOnWithdraw = _lossOnWithdraw; |
| 212 | } |
| 213 | |
| 214 | function increaseYield(uint256 increasePercentageFP4) public { |
| 215 | require(increasePercentageFP4 <= 10000, "Invalid percentage"); |
| 216 | uint256 amount = (yieldToken.balanceOf(address(this)) * |
| 217 | increasePercentageFP4) / 10000; |
| 218 | MockERC20(yieldToken).transferFrom(msg.sender, address(this), amount); |
| 219 | } |
| 220 | |
| 221 | function decreaseYield(uint256 decreasePercentageFP4) public { |
| 222 | require(decreasePercentageFP4 <= 10000, "Invalid percentage"); |
| 223 | uint256 amount = (yieldToken.balanceOf(address(this)) * |
| 224 | decreasePercentageFP4) / 10000; |
| 225 | MockERC20(yieldToken).transfer(address(0xbeef), amount); |
| 226 | totalLosses += amount; |
| 227 | } |
| 228 | } |
| 229 |
10.0%
test/recon/mocks/MockERC5115YieldSourceOracle.sol
Lines covered: 6 / 59 (10.0%)
| 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 | contract MockERC5115YieldSourceOracle is IYieldSourceOracle { |
| 10 | mapping(address => bool) public validAssetMap; |
| 11 | |
| 12 | function setValidAsset(address asset, bool isValid) external { |
| 13 | validAssetMap[asset] = isValid; |
| 14 | } |
| 15 | |
| 16 | function decimals(address) external pure returns (uint8) { |
| 17 | // ERC5115 always uses 18 decimals |
| 18 | return 18; |
| 19 | } |
| 20 | |
| 21 | function getShareOutput( |
| 22 | address yieldSourceAddress, |
| 23 | address assetIn, |
| 24 | uint256 assetsIn |
| 25 | ) external view returns (uint256) { |
| 26 | return MockERC5115Tester(yieldSourceAddress).previewDeposit(assetIn, assetsIn); |
| 27 | } |
| 28 | |
| 29 | function getAssetOutput( |
| 30 | address yieldSourceAddress, |
| 31 | address assetOut, |
| 32 | uint256 sharesIn |
| 33 | ) public view returns (uint256) { |
| 34 | return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
| 35 | } |
| 36 | |
| 37 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 38 | return MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 39 | } |
| 40 | |
| 41 | function getBalanceOfOwner( |
| 42 | address yieldSourceAddress, |
| 43 | address ownerOfShares |
| 44 | ) external view returns (uint256) { |
| 45 | return MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
| 46 | } |
| 47 | |
| 48 | function getTVLByOwnerOfShares( |
| 49 | address yieldSourceAddress, |
| 50 | address ownerOfShares |
| 51 | ) external view returns (uint256) { |
| 52 | uint256 shares = MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
| 53 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 54 | return (shares * exchangeRate) / 1e18; |
| 55 | } |
| 56 | |
| 57 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 58 | uint256 totalShares = MockERC5115Tester(yieldSourceAddress).totalSupply(); |
| 59 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 60 | return (totalShares * exchangeRate) / 1e18; |
| 61 | } |
| 62 | |
| 63 | function getPricePerShareMultiple( |
| 64 | address[] memory yieldSourceAddresses |
| 65 | ) external view returns (uint256[] memory) { |
| 66 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 67 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 68 | prices[i] = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 69 | } |
| 70 | return prices; |
| 71 | } |
| 72 | |
| 73 | function getTVLByOwnerOfSharesMultiple( |
| 74 | address[] memory yieldSourceAddresses, |
| 75 | address[][] memory ownersOfShares |
| 76 | ) external view returns (uint256[][] memory) { |
| 77 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 78 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 79 | result[i] = new uint256[](ownersOfShares[i].length); |
| 80 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 81 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 82 | uint256 shares = MockERC5115Tester(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
| 83 | result[i][j] = (shares * exchangeRate) / 1e18; |
| 84 | } |
| 85 | } |
| 86 | return result; |
| 87 | } |
| 88 | |
| 89 | function getTVLMultiple( |
| 90 | address[] memory yieldSourceAddresses |
| 91 | ) external view returns (uint256[] memory) { |
| 92 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 93 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 94 | uint256 totalShares = MockERC5115Tester(yieldSourceAddresses[i]).totalSupply(); |
| 95 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 96 | tvls[i] = (totalShares * exchangeRate) / 1e18; |
| 97 | } |
| 98 | return tvls; |
| 99 | } |
| 100 | |
| 101 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 102 | return validAssetMap[asset]; |
| 103 | } |
| 104 | |
| 105 | function isValidUnderlyingAssets( |
| 106 | address[] memory, |
| 107 | address[] memory assets |
| 108 | ) external view returns (bool[] memory) { |
| 109 | bool[] memory validities = new bool[](assets.length); |
| 110 | for (uint256 i = 0; i < assets.length; i++) { |
| 111 | validities[i] = validAssetMap[assets[i]]; |
| 112 | } |
| 113 | return validities; |
| 114 | } |
| 115 | |
| 116 | function getAssetOutputWithFees( |
| 117 | bytes32, |
| 118 | address yieldSourceAddress, |
| 119 | address assetOut, |
| 120 | address, |
| 121 | uint256 sharesIn |
| 122 | ) external view returns (uint256) { |
| 123 | return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
| 124 | } |
| 125 | } |
71.0%
test/recon/mocks/MockERC7540Tester.sol
Lines covered: 127 / 177 (71.0%)
| 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 | 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 | function totalAssets() public view virtual returns (uint256) { |
| 33 | 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 | function convertToAssets( |
| 44 | uint256 shares |
| 45 | ) public view virtual returns (uint256) { |
| 46 | uint256 supply = totalSupply; |
| 47 | 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 | function maxWithdraw(address owner) public view virtual returns (uint256) { |
| 59 | return convertToAssets(balanceOf[owner]); |
| 60 | } |
| 61 | |
| 62 | function maxRedeem(address owner) public view virtual returns (uint256) { |
| 63 | return balanceOf[owner]; |
| 64 | } |
| 65 | |
| 66 | function previewDeposit( |
| 67 | uint256 assets |
| 68 | ) public view virtual returns (uint256) { |
| 69 | 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 | function previewWithdraw( |
| 78 | uint256 assets |
| 79 | ) public view virtual returns (uint256) { |
| 80 | uint256 supply = totalSupply; |
| 81 | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
| 82 | } |
| 83 | |
| 84 | function previewRedeem( |
| 85 | uint256 shares |
| 86 | ) public view virtual returns (uint256) { |
| 87 | return convertToAssets(shares); |
| 88 | } |
| 89 | |
| 90 | function deposit( |
| 91 | uint256 assets, |
| 92 | address receiver |
| 93 | ) public virtual returns (uint256 shares) { |
| 94 | shares = previewDeposit(assets); |
| 95 | asset.transferFrom(msg.sender, address(this), assets); |
| 96 | _mint(receiver, shares); |
| 97 | emit Deposit(msg.sender, receiver, assets, shares); |
| 98 | } |
| 99 | |
| 100 | function mint( |
| 101 | uint256 shares, |
| 102 | address receiver |
| 103 | ) public virtual returns (uint256 assets) { |
| 104 | assets = previewMint(shares); |
| 105 | asset.transferFrom(msg.sender, address(this), assets); |
| 106 | _mint(receiver, shares); |
| 107 | emit Deposit(msg.sender, receiver, assets, shares); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | 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 | mapping(uint256 => DepositRequestStruct) public depositRequests; |
| 151 | mapping(uint256 => RedeemRequestStruct) public redeemRequests; |
| 152 | mapping(address => mapping(address => bool)) public operators; |
| 153 | mapping(uint256 => bool) public pendingCancelDeposit; |
| 154 | mapping(uint256 => bool) public pendingCancelRedeem; |
| 155 | |
| 156 | uint256 public yieldMultiplier = 10000; // 100% in basis points |
| 157 | uint256 private constant MAX_BPS = 10000; |
| 158 | uint256 public totalLosses; |
| 159 | uint256 public totalGains; |
| 160 | uint256 public lossOnWithdraw; |
| 161 | |
| 162 | constructor(address _asset) ERC7575(MockERC20(_asset)) {} |
| 163 | |
| 164 | // Operator Management |
| 165 | function setOperator( |
| 166 | address operator, |
| 167 | bool approved |
| 168 | ) external returns (bool) { |
| 169 | operators[msg.sender][operator] = approved; |
| 170 | emit OperatorSet(msg.sender, operator, approved); |
| 171 | return true; |
| 172 | } |
| 173 | |
| 174 | function isOperator( |
| 175 | address controller, |
| 176 | address operator |
| 177 | ) external view returns (bool) { |
| 178 | return operators[controller][operator]; |
| 179 | } |
| 180 | |
| 181 | // Async Deposit Flow |
| 182 | function requestDeposit( |
| 183 | uint256 assets, |
| 184 | address controller, |
| 185 | address owner |
| 186 | ) external returns (uint256 requestId) { |
| 187 | requestId = _nextRequestId++; |
| 188 | depositRequests[requestId] = DepositRequestStruct({ |
| 189 | assets: assets, |
| 190 | controller: controller, |
| 191 | owner: owner, |
| 192 | fulfilled: false, |
| 193 | canceled: false |
| 194 | }); |
| 195 | |
| 196 | asset.transferFrom(msg.sender, address(this), assets); |
| 197 | emit DepositRequest(controller, owner, requestId, msg.sender, assets); |
| 198 | } |
| 199 | |
| 200 | function pendingDepositRequest( |
| 201 | uint256 requestId, |
| 202 | address controller |
| 203 | ) external view returns (uint256) { |
| 204 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 205 | if ( |
| 206 | request.controller != controller || |
| 207 | request.fulfilled || |
| 208 | request.canceled |
| 209 | ) { |
| 210 | return 0; |
| 211 | } |
| 212 | 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 | function pendingCancelDepositRequest( |
| 231 | uint256 requestId, |
| 232 | address controller |
| 233 | ) external view returns (bool) { |
| 234 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 235 | return |
| 236 | request.controller == controller && pendingCancelDeposit[requestId]; |
| 237 | } |
| 238 | |
| 239 | // Override deposit to handle async requests |
| 240 | function deposit( |
| 241 | uint256 assets, |
| 242 | address receiver, |
| 243 | address controller |
| 244 | ) public returns (uint256 shares) { |
| 245 | require( |
| 246 | msg.sender == controller || operators[controller][msg.sender], |
| 247 | "Not authorized" |
| 248 | ); |
| 249 | |
| 250 | // Find and fulfill a matching deposit request |
| 251 | for (uint256 i = 1; i < _nextRequestId; i++) { |
| 252 | DepositRequestStruct storage request = depositRequests[i]; |
| 253 | if ( |
| 254 | 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 | asset.transfer(controller, request.assets - assets); |
| 266 | } |
| 267 | |
| 268 | 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 | function withdraw( |
| 278 | uint256 assets, |
| 279 | address receiver, |
| 280 | address owner |
| 281 | ) public returns (uint256 shares) { |
| 282 | shares = previewWithdraw(assets); |
| 283 | if (msg.sender != owner) { |
| 284 | allowance[owner][msg.sender] -= shares; |
| 285 | } |
| 286 | _burn(owner, shares); |
| 287 | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 288 | |
| 289 | asset.transfer(receiver, lossyAssets); |
| 290 | emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 291 | } |
| 292 | |
| 293 | function redeem( |
| 294 | uint256 shares, |
| 295 | address receiver, |
| 296 | address owner |
| 297 | ) public returns (uint256 assets) { |
| 298 | assets = previewRedeem(shares); |
| 299 | if (msg.sender != owner) { |
| 300 | allowance[owner][msg.sender] -= shares; |
| 301 | } |
| 302 | _burn(owner, shares); |
| 303 | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 304 | |
| 305 | asset.transfer(receiver, lossyAssets); |
| 306 | emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 307 | } |
| 308 | |
| 309 | // Async Redeem Flow |
| 310 | function requestRedeem( |
| 311 | uint256 shares, |
| 312 | address controller, |
| 313 | address owner |
| 314 | ) external returns (uint256 requestId) { |
| 315 | requestId = _nextRequestId++; |
| 316 | redeemRequests[requestId] = RedeemRequestStruct({ |
| 317 | shares: shares, |
| 318 | controller: controller, |
| 319 | owner: owner, |
| 320 | fulfilled: false, |
| 321 | canceled: false |
| 322 | }); |
| 323 | |
| 324 | 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 | function claimableRedeemRequest( |
| 343 | uint256 requestId, |
| 344 | address controller |
| 345 | ) external view returns (uint256) { |
| 346 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 347 | if ( |
| 348 | request.controller != controller || |
| 349 | request.fulfilled || |
| 350 | request.canceled |
| 351 | ) { |
| 352 | return 0; |
| 353 | } |
| 354 | return request.shares; |
| 355 | } |
| 356 | |
| 357 | function pendingCancelRedeemRequest( |
| 358 | uint256 requestId, |
| 359 | address controller |
| 360 | ) external view returns (bool) { |
| 361 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 362 | return |
| 363 | request.controller == controller && pendingCancelRedeem[requestId]; |
| 364 | } |
| 365 | |
| 366 | // Cancel Operations |
| 367 | function cancelDepositRequest( |
| 368 | uint256 requestId, |
| 369 | address controller |
| 370 | ) external { |
| 371 | require( |
| 372 | msg.sender == controller || operators[controller][msg.sender], |
| 373 | "Not authorized" |
| 374 | ); |
| 375 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 376 | require( |
| 377 | request.controller == controller && !request.fulfilled, |
| 378 | "Invalid request" |
| 379 | ); |
| 380 | |
| 381 | pendingCancelDeposit[requestId] = true; |
| 382 | } |
| 383 | |
| 384 | function claimCancelDepositRequest( |
| 385 | uint256 requestId, |
| 386 | address receiver, |
| 387 | address controller |
| 388 | ) external returns (uint256 assets) { |
| 389 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 390 | |
| 391 | assets = request.assets; |
| 392 | request.canceled = true; |
| 393 | pendingCancelDeposit[requestId] = false; |
| 394 | |
| 395 | asset.transfer(receiver, assets); |
| 396 | } |
| 397 | |
| 398 | function cancelRedeemRequest( |
| 399 | uint256 requestId, |
| 400 | address controller |
| 401 | ) external { |
| 402 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 403 | |
| 404 | pendingCancelRedeem[requestId] = true; |
| 405 | } |
| 406 | |
| 407 | function claimCancelRedeemRequest( |
| 408 | uint256 requestId, |
| 409 | address receiver, |
| 410 | address controller |
| 411 | ) external returns (uint256 shares) { |
| 412 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 413 | |
| 414 | shares = request.shares; |
| 415 | request.canceled = true; |
| 416 | pendingCancelRedeem[requestId] = false; |
| 417 | |
| 418 | _mint(receiver, shares); |
| 419 | } |
| 420 | |
| 421 | // Placeholder functions for Centrifuge compatibility |
| 422 | function poolId() external pure returns (uint64) { |
| 423 | return 1; |
| 424 | } |
| 425 | |
| 426 | 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 | function increaseYield(uint256 increasePercentageFP4) external { |
| 433 | uint256 amount = (totalAssets() * increasePercentageFP4) / MAX_BPS; |
| 434 | MockERC20(asset).transferFrom(msg.sender, address(this), amount); |
| 435 | } |
| 436 | |
| 437 | function decreaseYield(uint256 decreasePercentageFP4) external { |
| 438 | uint256 amount = (totalAssets() * decreasePercentageFP4) / MAX_BPS; |
| 439 | MockERC20(asset).transfer(address(0xbeef), amount); |
| 440 | } |
| 441 | |
| 442 | function simulateGain(uint256 gainAmount) external { |
| 443 | MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
| 444 | totalGains += gainAmount; |
| 445 | } |
| 446 | |
| 447 | function simulateLoss(uint256 lossAmount) external { |
| 448 | MockERC20(asset).transfer(address(0xbeef), lossAmount); |
| 449 | totalLosses += lossAmount; |
| 450 | } |
| 451 | |
| 452 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
| 453 | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 454 | _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 | return interfaceId == type(IERC165).interfaceId; |
| 463 | } |
| 464 | } |
| 465 |
10.0%
test/recon/mocks/MockERC7540YieldSourceOracle.sol
Lines covered: 6 / 60 (10.0%)
| 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 | contract MockERC7540YieldSourceOracle is IYieldSourceOracle { |
| 12 | mapping(address => bool) public validAssetMap; |
| 13 | |
| 14 | function setValidAsset(address asset, bool isValid) external { |
| 15 | validAssetMap[asset] = isValid; |
| 16 | } |
| 17 | |
| 18 | function decimals(address yieldSourceAddress) external view returns (uint8) { |
| 19 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 20 | return IERC20Metadata(share).decimals(); |
| 21 | } |
| 22 | |
| 23 | function getShareOutput( |
| 24 | address yieldSourceAddress, |
| 25 | address, |
| 26 | uint256 assetsIn |
| 27 | ) external view returns (uint256) { |
| 28 | return MockERC7540Tester(yieldSourceAddress).convertToShares(assetsIn); |
| 29 | } |
| 30 | |
| 31 | function getAssetOutput( |
| 32 | address yieldSourceAddress, |
| 33 | address, |
| 34 | uint256 sharesIn |
| 35 | ) public view returns (uint256) { |
| 36 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
| 37 | } |
| 38 | |
| 39 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 40 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 41 | uint256 _decimals = IERC20Metadata(share).decimals(); |
| 42 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(10 ** _decimals); |
| 43 | } |
| 44 | |
| 45 | function getBalanceOfOwner( |
| 46 | address yieldSourceAddress, |
| 47 | address ownerOfShares |
| 48 | ) external view returns (uint256) { |
| 49 | return IERC20(MockERC7540Tester(yieldSourceAddress).share()).balanceOf(ownerOfShares); |
| 50 | } |
| 51 | |
| 52 | function getTVLByOwnerOfShares( |
| 53 | address yieldSourceAddress, |
| 54 | address ownerOfShares |
| 55 | ) external view returns (uint256) { |
| 56 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 57 | uint256 shares = IERC20(share).balanceOf(ownerOfShares); |
| 58 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(shares); |
| 59 | } |
| 60 | |
| 61 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 62 | return MockERC7540Tester(yieldSourceAddress).totalAssets(); |
| 63 | } |
| 64 | |
| 65 | function getPricePerShareMultiple( |
| 66 | address[] memory yieldSourceAddresses |
| 67 | ) external view returns (uint256[] memory) { |
| 68 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 69 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 70 | address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
| 71 | uint256 _decimals = IERC20Metadata(share).decimals(); |
| 72 | prices[i] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(10 ** _decimals); |
| 73 | } |
| 74 | return prices; |
| 75 | } |
| 76 | |
| 77 | function getTVLByOwnerOfSharesMultiple( |
| 78 | address[] memory yieldSourceAddresses, |
| 79 | address[][] memory ownersOfShares |
| 80 | ) external view returns (uint256[][] memory) { |
| 81 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 82 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 83 | result[i] = new uint256[](ownersOfShares[i].length); |
| 84 | address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
| 85 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 86 | uint256 shares = IERC20(share).balanceOf(ownersOfShares[i][j]); |
| 87 | result[i][j] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(shares); |
| 88 | } |
| 89 | } |
| 90 | return result; |
| 91 | } |
| 92 | |
| 93 | function getTVLMultiple( |
| 94 | address[] memory yieldSourceAddresses |
| 95 | ) external view returns (uint256[] memory) { |
| 96 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 97 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 98 | tvls[i] = MockERC7540Tester(yieldSourceAddresses[i]).totalAssets(); |
| 99 | } |
| 100 | return tvls; |
| 101 | } |
| 102 | |
| 103 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 104 | return validAssetMap[asset]; |
| 105 | } |
| 106 | |
| 107 | function isValidUnderlyingAssets( |
| 108 | address[] memory, |
| 109 | address[] memory assets |
| 110 | ) external view returns (bool[] memory) { |
| 111 | bool[] memory validities = new bool[](assets.length); |
| 112 | for (uint256 i = 0; i < assets.length; i++) { |
| 113 | validities[i] = validAssetMap[assets[i]]; |
| 114 | } |
| 115 | return validities; |
| 116 | } |
| 117 | |
| 118 | function getAssetOutputWithFees( |
| 119 | bytes32, |
| 120 | address yieldSourceAddress, |
| 121 | address, |
| 122 | address, |
| 123 | uint256 sharesIn |
| 124 | ) external view returns (uint256) { |
| 125 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
| 126 | } |
| 127 | } |
96.0%
test/recon/targets/AdminTargets.sol
Lines covered: 196 / 203 (96.0%)
| 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 | function superVaultStrategy_executeHooks_clamped( |
| 47 | uint256[] memory hookTypeInts, |
| 48 | uint256[] memory amountsToInvest, |
| 49 | bool[] memory usePrevHookAmounts |
| 50 | ) 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 | return; |
| 68 | } |
| 69 | |
| 70 | // Create ExecuteArgs for the hooks |
| 71 | ISuperVaultStrategy.ExecuteArgs memory executeArgs = ISuperVaultStrategy |
| 72 | .ExecuteArgs({ |
| 73 | hooks: new address[](numHooks), |
| 74 | hookCalldata: new bytes[](numHooks), |
| 75 | expectedAssetsOrSharesOut: new uint256[](numHooks), |
| 76 | globalProofs: new bytes32[][](numHooks), |
| 77 | strategyProofs: new bytes32[][](numHooks) |
| 78 | }); |
| 79 | |
| 80 | // Process each hook |
| 81 | uint256 totalAmountToDeposit; |
| 82 | for (uint256 i = 0; i < numHooks; i++) { |
| 83 | // Convert integer to enum (will wrap around if > max enum value) |
| 84 | 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 | uint256 clampedAmount = amountsToInvest[i] % |
| 88 | (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 | usePrevHookAmounts[i] |
| 100 | ); |
| 101 | |
| 102 | executeArgs.hooks[i] = hookAddress; |
| 103 | executeArgs.hookCalldata[i] = hookCalldata; |
| 104 | executeArgs.expectedAssetsOrSharesOut[i] = clampedAmount; |
| 105 | executeArgs.globalProofs[i] = new bytes32[](1); |
| 106 | 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 | if (_claimableMoreThanInvested(totalAmountToDeposit)) return; |
| 113 | |
| 114 | // Execute all hooks |
| 115 | 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 | if (hookType == HookType.ApproveAndDeposit4626) { |
| 124 | hookAddress = address(approveAndDeposit4626Hook); |
| 125 | hookCalldata = abi.encodePacked( |
| 126 | bytes32(0), // yieldSourceOracleId placeholder |
| 127 | _getYieldSource(), // Address of the yield source |
| 128 | superVault.asset(), // Address of the token to approve and deposit |
| 129 | amountToInvest, // Amount to deposit |
| 130 | usePrevHookAmount |
| 131 | ); |
| 132 | } else if (hookType == HookType.Deposit4626) { |
| 133 | hookAddress = address(deposit4626Hook); |
| 134 | hookCalldata = abi.encodePacked( |
| 135 | bytes32(0), // yieldSourceOracleId placeholder |
| 136 | _getYieldSource(), // Address of the yield source |
| 137 | amountToInvest, // Amount to deposit |
| 138 | usePrevHookAmount |
| 139 | ); |
| 140 | } 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 | } 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 | 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 | } 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 | } 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 | } 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 | } else if (hookType == HookType.Redeem7540) { |
| 188 | hookAddress = address(redeem7540Hook); |
| 189 | hookCalldata = abi.encodePacked( |
| 190 | bytes32(0), // yieldSourceOracleId placeholder |
| 191 | _getYieldSource(), // Address of the yield source |
| 192 | amountToInvest, // Amount to redeem |
| 193 | address(superVaultStrategy), // Receiver |
| 194 | address(superVaultStrategy), // Owner |
| 195 | address(superVaultStrategy) // Controller |
| 196 | ); |
| 197 | } 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 | } 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 | } 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 | superVault.asset(), // Address of the token to approve |
| 221 | amountToInvest, // Amount to request deposit |
| 222 | address(superVaultStrategy), // Owner |
| 223 | address(superVaultStrategy) // Controller |
| 224 | ); |
| 225 | } 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 | } 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 | } 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 | } 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 | } 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 | } 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 | hookAddress = address(superVaultWithdraw7540Hook); |
| 274 | hookCalldata = abi.encodePacked( |
| 275 | bytes32(0), // yieldSourceOracleId placeholder |
| 276 | _getYieldSource(), // Address of the yield source |
| 277 | amountToInvest, // Amount to withdraw |
| 278 | address(superVaultStrategy), // Receiver |
| 279 | address(superVaultStrategy), // Owner |
| 280 | address(superVaultStrategy) // Controller |
| 281 | ); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | function superVaultStrategy_fulfillRedeemRequests_clamped( |
| 286 | uint256 redeemAmount |
| 287 | ) public { |
| 288 | // Find a controller that has pending redeem requests |
| 289 | address selectedController = _getActor(); |
| 290 | uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
| 291 | selectedController |
| 292 | ); |
| 293 | |
| 294 | // Clamp using the actor's pending amount |
| 295 | uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
| 296 | |
| 297 | address[] memory controllers = new address[](1); |
| 298 | 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 | activeYieldSourceType == YieldSourceType.ERC4626 || |
| 311 | activeYieldSourceType == YieldSourceType.ERC5115 |
| 312 | ) { |
| 313 | // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
| 314 | 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 | 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 | address[] memory hooks = new address[](1); |
| 333 | hooks[0] = redeemHook; |
| 334 | |
| 335 | bytes[] memory hookCalldata = new bytes[](1); |
| 336 | hookCalldata[0] = redeemHookCalldata; |
| 337 | |
| 338 | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 339 | expectedAssetsOrSharesOut[0] = 1; // @audit Allow max losse amount matching the actual redeem |
| 340 | |
| 341 | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 342 | globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
| 343 | |
| 344 | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 345 | strategyProofs[0] = new bytes32[](0); // Empty proof |
| 346 | |
| 347 | // Create the FulfillArgs struct |
| 348 | 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 | function superVaultStrategy_executeHooks( |
| 365 | ISuperVaultStrategy.ExecuteArgs memory args |
| 366 | ) public payable asAdmin { |
| 367 | superVaultStrategy.executeHooks{value: msg.value}(args); |
| 368 | |
| 369 | executeHooksSuccess = true; |
| 370 | } |
| 371 | |
| 372 | /// @dev Property: superVaultStrategy does not incur loss on fulfillment |
| 373 | function superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT( |
| 374 | ISuperVaultStrategy.FulfillArgs memory args |
| 375 | ) public updateGhostsWithOpType(OpType.FULFILL) { |
| 376 | uint256 summedExpectedAssets; |
| 377 | for (uint256 i; i < args.expectedAssetsOrSharesOut.length; i++) { |
| 378 | summedExpectedAssets += args.expectedAssetsOrSharesOut[i]; |
| 379 | } |
| 380 | |
| 381 | // no need to prank because called as admin address(this) |
| 382 | superVaultStrategy.fulfillRedeemRequests(args); |
| 383 | |
| 384 | uint256 assetBalanceAfter = MockERC20(superVault.asset()).balanceOf( |
| 385 | address(superVaultStrategy) |
| 386 | ); |
| 387 | |
| 388 | gte( |
| 389 | assetBalanceAfter, |
| 390 | summedExpectedAssets, |
| 391 | 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 | function superVaultAggregator_changePrimaryManager( |
| 434 | address strategy, |
| 435 | address newManager |
| 436 | ) public asAdmin { |
| 437 | 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 | function superVaultAggregator_slashStake( |
| 442 | address manager, |
| 443 | uint256 amount |
| 444 | ) public asAdmin { |
| 445 | 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 | for (uint256 i; i < actors.length; i++) { |
| 487 | uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
| 488 | 0, |
| 489 | actors[i] |
| 490 | ); |
| 491 | uint256 claimableRedemptionsAsAssets = superVault.convertToAssets( |
| 492 | claimableRedemptions |
| 493 | ); |
| 494 | totalClaimable += claimableRedemptionsAsAssets; |
| 495 | } |
| 496 | |
| 497 | 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 | return true; |
| 510 | } |
| 511 | |
| 512 | return false; |
| 513 | } |
| 514 | } |
| 515 |
89.0%
test/recon/targets/DoomsdayTargets.sol
Lines covered: 336 / 375 (89.0%)
| 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 | function doomsday_previewDepositEquivalence_ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE( |
| 21 | uint256 assets |
| 22 | ) public stateless { |
| 23 | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 24 | |
| 25 | vm.prank(_getActor()); |
| 26 | uint256 sharesActualDeposit = superVault.deposit(assets, _getActor()); |
| 27 | |
| 28 | eq( |
| 29 | previewDepositShares, |
| 30 | sharesActualDeposit, |
| 31 | ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE |
| 32 | ); |
| 33 | } |
| 34 | |
| 35 | /// @dev Property: previewMint and mint equivalence |
| 36 | function doomsday_previewMintEquivalence_ASSERTION_PREVIEW_MINT_EQUIVALENCE(uint256 shares) public stateless { |
| 37 | uint256 previewMintAssets = superVault.previewMint(shares); |
| 38 | |
| 39 | vm.prank(_getActor()); |
| 40 | uint256 assetsActualMint = superVault.mint(shares, _getActor()); |
| 41 | |
| 42 | eq( |
| 43 | previewMintAssets, |
| 44 | assetsActualMint, |
| 45 | ASSERTION_PREVIEW_MINT_EQUIVALENCE |
| 46 | ); |
| 47 | } |
| 48 | |
| 49 | /// @dev Property: mint/redeem doesn't cause loss to user |
| 50 | function doomsday_mintRedeemSymmetrical_ASSERTION_MINT_REDEEM_SYMMETRICAL( |
| 51 | uint256 sharesToMint |
| 52 | ) 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 | address asset = superVault.asset(); |
| 59 | uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
| 60 | uint256 feeRecipientBalanceBefore = MockERC20(asset).balanceOf( |
| 61 | feeRecipient |
| 62 | ); |
| 63 | |
| 64 | // 1. Mint |
| 65 | vm.prank(_getActor()); |
| 66 | 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 | 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 | superVaultStrategy.executeHooks(depositArgs); |
| 81 | } |
| 82 | |
| 83 | // 3. Request Redemption |
| 84 | uint256 userShares = superVault.balanceOf(_getActor()); |
| 85 | // get shares of strategy in the deposited yield source |
| 86 | uint256 shares = _getSuperVaultStrategyShares(); |
| 87 | |
| 88 | vm.prank(_getActor()); |
| 89 | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 90 | |
| 91 | 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 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 102 | |
| 103 | // 5. Claim Redemption |
| 104 | uint256 sharesToRedeem = superVault.maxRedeem(_getActor()); |
| 105 | |
| 106 | vm.prank(_getActor()); |
| 107 | superVault.redeem(sharesToRedeem, _getActor(), _getActor()); |
| 108 | |
| 109 | uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor()); |
| 110 | |
| 111 | uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost |
| 112 | |
| 113 | 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 | balanceAfter + TOLERANCE + feeDelta, |
| 120 | balanceBefore, |
| 121 | ASSERTION_MINT_REDEEM_SYMMETRICAL |
| 122 | ); |
| 123 | } |
| 124 | |
| 125 | /// @dev Property: deposit/withdraw doesn't cause loss to user |
| 126 | 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 | address asset = superVault.asset(); |
| 135 | uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
| 136 | |
| 137 | // 1. Deposit |
| 138 | vm.prank(_getActor()); |
| 139 | superVault.deposit(assetsToDeposit, _getActor()); |
| 140 | |
| 141 | // 2. Request Withdrawal (through redemption in ERC7540) |
| 142 | uint256 shares = superVault.balanceOf(_getActor()); |
| 143 | vm.prank(_getActor()); |
| 144 | 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 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 151 | |
| 152 | // 4. Claim Withdrawal |
| 153 | uint256 withdrawableAssets = superVault.maxWithdraw(_getActor()); |
| 154 | vm.prank(_getActor()); |
| 155 | superVault.withdraw(withdrawableAssets, _getActor(), _getActor()); |
| 156 | |
| 157 | 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 | 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 | function doomsday_maxRedeemResetsAfterFullRedemption_ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION( |
| 173 | uint256 sharesToMint |
| 174 | ) public stateless { |
| 175 | // 1. Deposit to get shares |
| 176 | vm.prank(_getActor()); |
| 177 | superVault.mint(sharesToMint, _getActor()); |
| 178 | |
| 179 | // redeem all user shares |
| 180 | uint256 shares = superVault.balanceOf(_getActor()); |
| 181 | |
| 182 | // 2. Request full redemption |
| 183 | vm.prank(_getActor()); |
| 184 | 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 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 191 | |
| 192 | // 4. Check maxRedeem before claiming |
| 193 | uint256 maxRedeemBeforeClaim = superVault.maxRedeem(_getActor()); |
| 194 | |
| 195 | // 5. Claim the full redemption |
| 196 | vm.prank(_getActor()); |
| 197 | try superVault.redeem(maxRedeemBeforeClaim, _getActor(), _getActor()) { |
| 198 | // 6. Check maxRedeem is reset to 0 after full redemption |
| 199 | uint256 maxRedeemAfterClaim = superVault.maxRedeem(_getActor()); |
| 200 | eq( |
| 201 | maxRedeemAfterClaim, |
| 202 | 0, |
| 203 | ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION |
| 204 | ); |
| 205 | } catch { |
| 206 | if (maxRedeemBeforeClaim > 0) { |
| 207 | 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 | function doomsday_maxWithdrawResetsAfterFullWithdrawal_ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL( |
| 214 | uint256 assetsToDeposit |
| 215 | ) public stateless { |
| 216 | // 1. Deposit to get shares |
| 217 | vm.prank(_getActor()); |
| 218 | superVault.deposit(assetsToDeposit, _getActor()); |
| 219 | |
| 220 | uint256 shares = superVault.balanceOf(_getActor()); |
| 221 | |
| 222 | // 2. Request redemption of all shares |
| 223 | vm.prank(_getActor()); |
| 224 | 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 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 231 | |
| 232 | // 4. Check maxWithdraw after fulfillment and use that value |
| 233 | uint256 maxWithdrawBefore = superVault.maxWithdraw(_getActor()); |
| 234 | |
| 235 | // 5. Withdraw the exact amount returned by maxWithdraw |
| 236 | vm.prank(_getActor()); |
| 237 | try superVault.withdraw(maxWithdrawBefore, _getActor(), _getActor()) { |
| 238 | // 6. Check maxWithdraw is reset to 0 after full withdrawal |
| 239 | uint256 maxWithdrawAfter = superVault.maxWithdraw(_getActor()); |
| 240 | eq( |
| 241 | maxWithdrawAfter, |
| 242 | 0, |
| 243 | ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL |
| 244 | ); |
| 245 | } catch { |
| 246 | 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 | function doomsday_fulfillDoesntOverRedeemMultipleActors_ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS( |
| 252 | uint256[3] memory sharesToMint, |
| 253 | uint256[3] memory actorIndexes |
| 254 | ) 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 | address[] memory testActors = new address[](3); |
| 260 | uint256[] memory requestedShares = new uint256[](3); |
| 261 | uint256[] memory sharesBefore = new uint256[](3); |
| 262 | |
| 263 | // 1. Setup: Each actor deposits and requests redemption |
| 264 | uint256 totalRequestedShares; |
| 265 | for (uint256 i = 0; i < 3; i++) { |
| 266 | // Get unique actor |
| 267 | testActors[i] = actors[actorIndexes[i] % actors.length]; |
| 268 | |
| 269 | // Mint shares for this actor |
| 270 | if (sharesToMint[i] > 0) { |
| 271 | vm.prank(testActors[i]); |
| 272 | superVault.mint(sharesToMint[i], testActors[i]); |
| 273 | } |
| 274 | |
| 275 | // Get actual share balance |
| 276 | sharesBefore[i] = superVault.maxRedeem(testActors[i]); |
| 277 | requestedShares[i] = sharesBefore[i]; |
| 278 | |
| 279 | // Request redemption of all shares |
| 280 | if (requestedShares[i] > 0) { |
| 281 | vm.prank(testActors[i]); |
| 282 | superVault.requestRedeem( |
| 283 | requestedShares[i], |
| 284 | testActors[i], |
| 285 | testActors[i] |
| 286 | ); |
| 287 | } |
| 288 | 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 | for (uint256 i = 0; i < 3; i++) { |
| 301 | totalPendingBefore += superVault.pendingRedeemRequest( |
| 302 | 0, |
| 303 | testActors[i] |
| 304 | ); |
| 305 | } |
| 306 | |
| 307 | // 4. Fulfill all redemption requests at once |
| 308 | // fulfill as address(this) |
| 309 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 310 | |
| 311 | // 5. Calculate total pending after |
| 312 | uint256 totalPendingAfter; |
| 313 | for (uint256 i = 0; i < 3; i++) { |
| 314 | totalPendingAfter += superVault.pendingRedeemRequest( |
| 315 | 0, |
| 316 | testActors[i] |
| 317 | ); |
| 318 | } |
| 319 | |
| 320 | lte( |
| 321 | totalPendingBefore - totalPendingAfter, |
| 322 | totalRequestedShares, |
| 323 | ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS |
| 324 | ); |
| 325 | } |
| 326 | |
| 327 | /// @dev Property: primary manager can always be replaced by governance via `changePrimaryManager` |
| 328 | 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 | vm.prank(address(this)); |
| 334 | try superGovernor.changePrimaryManager(strategy, newManager) { |
| 335 | // Call succeeded - this is expected behavior |
| 336 | } catch (bytes memory err) { |
| 337 | bool expectedError; |
| 338 | expectedError = checkError(err, "MANAGER_TAKEOVERS_FROZEN()"); // custom error |
| 339 | t( |
| 340 | !expectedError, |
| 341 | 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 | function doomsday_allUsersCanWithdraw_ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED() public stateless { |
| 350 | address[] memory actors = _getActors(); |
| 351 | bool paused = superVaultAggregator.isStrategyPaused( |
| 352 | address(superVaultStrategy) |
| 353 | ); |
| 354 | |
| 355 | // request redemption for all actors |
| 356 | for (uint256 i; i < actors.length; i++) { |
| 357 | uint256 redeemableShares = superVault.balanceOf(actors[i]); |
| 358 | |
| 359 | vm.prank(actors[i]); |
| 360 | superVault.requestRedeem(redeemableShares, actors[i], actors[i]); |
| 361 | } |
| 362 | |
| 363 | // fulfill redemption for all actors |
| 364 | for (uint256 i; i < actors.length; i++) { |
| 365 | uint256 redeemableShares = superVault.pendingRedeemRequest( |
| 366 | 0, |
| 367 | actors[i] |
| 368 | ); |
| 369 | |
| 370 | // switch the actor |
| 371 | _switchActor(i); |
| 372 | |
| 373 | ISuperVaultStrategy.FulfillArgs |
| 374 | memory fulfillArgs = _fulfillRedeemRequestsArgs( |
| 375 | redeemableShares |
| 376 | ); |
| 377 | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 378 | } |
| 379 | |
| 380 | // try to withdraw max possible for all actors |
| 381 | for (uint256 i; i < actors.length; i++) { |
| 382 | uint256 withdrawable = superVault.maxWithdraw(actors[i]); |
| 383 | |
| 384 | if (withdrawable > 0 && !paused) { |
| 385 | vm.prank(actors[i]); |
| 386 | try |
| 387 | 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 | t( |
| 391 | false, |
| 392 | 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 | function doomsday_redemptionsNeverReverts_ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM( |
| 401 | uint256 shares |
| 402 | ) public asActor stateless { |
| 403 | try superVault.redeem(shares, _getActor(), _getActor()) {} catch ( |
| 404 | bytes memory err |
| 405 | ) { |
| 406 | bool unexpectedError = checkError(err, "INVALID_REDEEM_CLAIM()"); |
| 407 | t( |
| 408 | !unexpectedError, |
| 409 | 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 | if (sourceType == YieldSourceType.ERC4626) { |
| 422 | return |
| 423 | MockERC4626Tester(yieldSource).balanceOf( |
| 424 | address(superVaultStrategy) |
| 425 | ); |
| 426 | } else if (sourceType == YieldSourceType.ERC5115) { |
| 427 | return |
| 428 | MockERC5115Tester(yieldSource).balanceOf( |
| 429 | address(superVaultStrategy) |
| 430 | ); |
| 431 | } else if (sourceType == YieldSourceType.ERC7540) { |
| 432 | return |
| 433 | MockERC7540Tester(yieldSource).balanceOf( |
| 434 | address(superVaultStrategy) |
| 435 | ); |
| 436 | } else { |
| 437 | revert("Invalid yield source type"); |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | // Helpers |
| 442 | |
| 443 | /// @dev Helper function to clamp the values for the function call |
| 444 | 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 | uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
| 450 | selectedController |
| 451 | ); |
| 452 | |
| 453 | // Clamp using the actor's pending amount |
| 454 | uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
| 455 | |
| 456 | address[] memory controllers = new address[](1); |
| 457 | controllers[0] = selectedController; |
| 458 | |
| 459 | // Determine yield source type from currently active yield source |
| 460 | YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress( |
| 461 | _getYieldSource() |
| 462 | ); |
| 463 | address redeemHook = _getRedeemHookForType(activeYieldSourceType); |
| 464 | |
| 465 | // Create realistic hook calldata for redeem operation |
| 466 | bytes memory redeemHookCalldata; |
| 467 | |
| 468 | if ( |
| 469 | activeYieldSourceType == YieldSourceType.ERC4626 || |
| 470 | activeYieldSourceType == YieldSourceType.ERC5115 |
| 471 | ) { |
| 472 | // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
| 473 | redeemHookCalldata = abi.encodePacked( |
| 474 | bytes32(0), // yieldSourceOracleId placeholder |
| 475 | _getYieldSource(), // Current active yield source |
| 476 | 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 | 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 | address[] memory hooks = new address[](1); |
| 492 | hooks[0] = redeemHook; |
| 493 | |
| 494 | bytes[] memory hookCalldata = new bytes[](1); |
| 495 | hookCalldata[0] = redeemHookCalldata; |
| 496 | |
| 497 | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 498 | expectedAssetsOrSharesOut[0] = actualRedeemAmount; // Expect amount matching the actual redeem |
| 499 | |
| 500 | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 501 | globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
| 502 | |
| 503 | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 504 | strategyProofs[0] = new bytes32[](0); // Empty proof |
| 505 | |
| 506 | // Create the FulfillArgs struct |
| 507 | 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 | address[] memory hooks = new address[](numActors); |
| 527 | bytes[] memory hookCalldata = new bytes[](numActors); |
| 528 | uint256[] memory expectedAssetsOrSharesOut = new uint256[](numActors); |
| 529 | bytes32[][] memory globalProofs = new bytes32[][](numActors); |
| 530 | bytes32[][] memory strategyProofs = new bytes32[][](numActors); |
| 531 | |
| 532 | for (uint256 i = 0; i < numActors; i++) { |
| 533 | hooks[i] = _getRedeemHookForType( |
| 534 | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 535 | ); |
| 536 | |
| 537 | if ( |
| 538 | _getYieldSourceTypeFromAddress(_getYieldSource()) == |
| 539 | YieldSourceType.ERC4626 |
| 540 | ) { |
| 541 | hookCalldata[i] = abi.encodePacked( |
| 542 | bytes32(0), |
| 543 | _getYieldSource(), |
| 544 | address(superVaultStrategy), |
| 545 | amounts[i], |
| 546 | false |
| 547 | ); |
| 548 | } else { |
| 549 | hookCalldata[i] = abi.encodePacked( |
| 550 | bytes32(0), |
| 551 | _getYieldSource(), |
| 552 | amounts[i], |
| 553 | false |
| 554 | ); |
| 555 | } |
| 556 | |
| 557 | expectedAssetsOrSharesOut[i] = amounts[i]; |
| 558 | globalProofs[i] = new bytes32[](0); |
| 559 | strategyProofs[i] = new bytes32[](0); |
| 560 | } |
| 561 | |
| 562 | return |
| 563 | 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 | address[] memory controllers = new address[](1); |
| 578 | controllers[0] = _getActor(); |
| 579 | |
| 580 | address[] memory hooks = new address[](1); |
| 581 | hooks[0] = _getRedeemHookForType( |
| 582 | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 583 | ); |
| 584 | |
| 585 | bytes[] memory hookCalldata = new bytes[](1); |
| 586 | if ( |
| 587 | _getYieldSourceTypeFromAddress(_getYieldSource()) == |
| 588 | YieldSourceType.ERC4626 |
| 589 | ) { |
| 590 | hookCalldata[0] = abi.encodePacked( |
| 591 | bytes32(0), |
| 592 | _getYieldSource(), |
| 593 | address(superVaultStrategy), |
| 594 | amount, |
| 595 | false |
| 596 | ); |
| 597 | } else { |
| 598 | hookCalldata[0] = abi.encodePacked( |
| 599 | bytes32(0), |
| 600 | _getYieldSource(), |
| 601 | amount, |
| 602 | false |
| 603 | ); |
| 604 | } |
| 605 | |
| 606 | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 607 | expectedAssetsOrSharesOut[0] = amount; |
| 608 | |
| 609 | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 610 | globalProofs[0] = new bytes32[](0); |
| 611 | |
| 612 | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 613 | strategyProofs[0] = new bytes32[](0); |
| 614 | |
| 615 | return |
| 616 | 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 | address[] memory hooks = new address[](1); |
| 631 | hooks[0] = _getApproveAndDepositHookForType( |
| 632 | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 633 | ); |
| 634 | |
| 635 | bytes[] memory hookCalldata = new bytes[](1); |
| 636 | YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
| 637 | _getYieldSource() |
| 638 | ); |
| 639 | |
| 640 | if (sourceType == YieldSourceType.ERC4626) { |
| 641 | // ERC4626 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, bool usePrevAmount |
| 642 | hookCalldata[0] = abi.encodePacked( |
| 643 | bytes32(0), // oracle ID placeholder |
| 644 | _getYieldSource(), |
| 645 | superVault.asset(), // token address |
| 646 | amount, |
| 647 | false // don't use previous amount |
| 648 | ); |
| 649 | } 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 | superVault.asset(), // token address |
| 655 | amount, |
| 656 | address(superVaultStrategy), |
| 657 | uint256(0), // sy ID for ERC5115 |
| 658 | false |
| 659 | ); |
| 660 | } else if (sourceType == YieldSourceType.ERC7540) { |
| 661 | // ERC7540 requestDeposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address controller, bool usePrevAmount |
| 662 | hookCalldata[0] = abi.encodePacked( |
| 663 | bytes32(0), |
| 664 | _getYieldSource(), |
| 665 | superVault.asset(), // token address |
| 666 | amount, |
| 667 | address(superVaultStrategy), |
| 668 | false |
| 669 | ); |
| 670 | } |
| 671 | |
| 672 | 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 | expectedAssetsOrSharesOut[0] = 1; // Minimum expected shares from deposit |
| 676 | |
| 677 | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 678 | globalProofs[0] = new bytes32[](0); |
| 679 | |
| 680 | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 681 | strategyProofs[0] = new bytes32[](0); |
| 682 | |
| 683 | return |
| 684 | 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 | address[] memory controllers = new address[](1); |
| 698 | controllers[0] = _getActor(); |
| 699 | |
| 700 | // Get the actual share balance that the SuperVaultStrategy holds in the yield source |
| 701 | address yieldSource = _getYieldSource(); |
| 702 | 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 | uint256 pendingShares = superVault.pendingRedeemRequest(0, _getActor()); |
| 708 | |
| 709 | // For debugging: ensure we don't try to redeem more than the strategy has |
| 710 | uint256 actualSharesToRedeem = strategyYieldSourceShares < pendingShares |
| 711 | ? strategyYieldSourceShares |
| 712 | : pendingShares; |
| 713 | |
| 714 | address[] memory hooks = new address[](1); |
| 715 | hooks[0] = _getRedeemHookForType( |
| 716 | _getYieldSourceTypeFromAddress(yieldSource) |
| 717 | ); |
| 718 | |
| 719 | bytes[] memory hookCalldata = new bytes[](1); |
| 720 | YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
| 721 | yieldSource |
| 722 | ); |
| 723 | |
| 724 | if ( |
| 725 | sourceType == YieldSourceType.ERC4626 || |
| 726 | 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 | 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 | hookCalldata[0] = abi.encodePacked( |
| 740 | bytes32(0), |
| 741 | yieldSource, |
| 742 | 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 | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 750 | expectedAssetsOrSharesOut[0] = actualSharesToRedeem > 0 ? 1 : 0; // Minimum 1 asset expected if redeeming anything |
| 751 | |
| 752 | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 753 | globalProofs[0] = new bytes32[](0); |
| 754 | |
| 755 | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 756 | strategyProofs[0] = new bytes32[](0); |
| 757 | |
| 758 | return |
| 759 | ISuperVaultStrategy.FulfillArgs({ |
| 760 | controllers: controllers, |
| 761 | hooks: hooks, |
| 762 | hookCalldata: hookCalldata, |
| 763 | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
| 764 | globalProofs: globalProofs, |
| 765 | strategyProofs: strategyProofs |
| 766 | }); |
| 767 | } |
| 768 | } |
| 769 |
100.0%
test/recon/targets/ManagersTargets.sol
Lines covered: 14 / 14 (100.0%)
| 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 | function switchActor(uint256 entropy) public updateGhosts { |
| 20 | _switchActor(entropy); |
| 21 | } |
| 22 | |
| 23 | /// @dev Starts using a new asset |
| 24 | 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 | 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 | 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 | function add_new_vault() public { |
| 43 | _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 | function asset_approve( |
| 53 | address to, |
| 54 | uint128 amt |
| 55 | ) public updateGhosts asActor { |
| 56 | 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 | function asset_mint(address to, uint128 amt) public updateGhosts asAdmin { |
| 61 | MockERC20(superVault.asset()).mint(to, amt); |
| 62 | } |
| 63 | } |
| 64 |
100.0%
test/recon/targets/OracleTargets.sol
Lines covered: 36 / 36 (100.0%)
| 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 | function yieldSourceOracle_setValidAsset_clamped() public { |
| 23 | mockERC4626YieldSourceOracle_setValidAsset(superVault.asset(), true); |
| 24 | } |
| 25 | |
| 26 | function mockERC4626YieldSourceOracle_setValidAsset( |
| 27 | address asset, |
| 28 | bool isValid |
| 29 | ) public asActor { |
| 30 | MockERC4626YieldSourceOracle(address(erc4626YieldSourceOracle)) |
| 31 | .setValidAsset(asset, isValid); |
| 32 | } |
| 33 | |
| 34 | function mockERC5115YieldSourceOracle_setValidAsset( |
| 35 | address asset, |
| 36 | bool isValid |
| 37 | ) public asActor { |
| 38 | MockERC5115YieldSourceOracle(address(erc5115YieldSourceOracle)) |
| 39 | .setValidAsset(asset, isValid); |
| 40 | } |
| 41 | |
| 42 | function mockERC7540YieldSourceOracle_setValidAsset( |
| 43 | address asset, |
| 44 | bool isValid |
| 45 | ) public asActor { |
| 46 | MockERC7540YieldSourceOracle(address(erc7540YieldSourceOracle)) |
| 47 | .setValidAsset(asset, isValid); |
| 48 | } |
| 49 | |
| 50 | function ECDSAPPSOracle_updatePPS_clamped(uint256 pps) public { |
| 51 | pps %= (100_000_000 * 1e18) + 1; // clamp to a reasonable max price |
| 52 | |
| 53 | address[] memory strategies = new address[](1); |
| 54 | strategies[0] = address(superVaultStrategy); |
| 55 | |
| 56 | bytes[][] memory proofsArray = new bytes[][](1); |
| 57 | proofsArray[0] = new bytes[](0); |
| 58 | |
| 59 | uint256[] memory ppss = new uint256[](1); |
| 60 | ppss[0] = pps; |
| 61 | |
| 62 | uint256[] memory ppsStdevs = new uint256[](1); |
| 63 | ppsStdevs[0] = 0; |
| 64 | |
| 65 | uint256[] memory validatorSets = new uint256[](1); |
| 66 | validatorSets[0] = 0; |
| 67 | |
| 68 | uint256[] memory totalValidators = new uint256[](1); |
| 69 | totalValidators[0] = 0; |
| 70 | |
| 71 | uint256[] memory timestamps = new uint256[](1); |
| 72 | timestamps[0] = block.timestamp; |
| 73 | |
| 74 | 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 | function ECDSAPPSOracle_updatePPS( |
| 90 | IECDSAPPSOracle.UpdatePPSArgs memory args |
| 91 | ) public asActor { |
| 92 | ECDSAPPSOracle.updatePPS(args); |
| 93 | |
| 94 | hasUpdatedPPS = true; |
| 95 | } |
| 96 | } |
| 97 |
100.0%
test/recon/targets/SuperGovernorTargets.sol
Lines covered: 37 / 37 (100.0%)
| 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 | function superGovernor_proposeGlobalHooksRoot_clamped( |
| 18 | bytes32 newRoot |
| 19 | ) public { |
| 20 | (bytes32 testRoot, bytes32[][] memory testProofs) = merkleHelper |
| 21 | .generateTestHooksRoot( |
| 22 | address(approveAndDeposit4626Hook), |
| 23 | address(redeem4626Hook), |
| 24 | _getYieldSource(), |
| 25 | superVault.asset() |
| 26 | ); |
| 27 | |
| 28 | superGovernor_proposeGlobalHooksRoot(testRoot); |
| 29 | } |
| 30 | |
| 31 | function superGovernor_proposeUpkeepPaymentsChange_clamped() public { |
| 32 | superGovernor_proposeUpkeepPaymentsChange(true); |
| 33 | } |
| 34 | |
| 35 | function superGovernor_proposeFee_clamped( |
| 36 | uint256 feeTypeAsUint, |
| 37 | uint256 value |
| 38 | ) public { |
| 39 | feeTypeAsUint %= 3; |
| 40 | superGovernor_proposeFee(FeeType(feeTypeAsUint), value); |
| 41 | } |
| 42 | |
| 43 | function superGovernor_executeFeeUpdate_clamped( |
| 44 | uint256 feeTypeAsUint |
| 45 | ) public { |
| 46 | feeTypeAsUint %= 3; |
| 47 | superGovernor_executeFeeUpdate(FeeType(feeTypeAsUint)); |
| 48 | } |
| 49 | |
| 50 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
| 51 | |
| 52 | function superGovernor_proposeFee( |
| 53 | FeeType feeType, |
| 54 | uint256 value |
| 55 | ) public asAdmin { |
| 56 | superGovernor.proposeFee(feeType, value); |
| 57 | } |
| 58 | |
| 59 | function superGovernor_executeFeeUpdate(FeeType feeType) public asAdmin { |
| 60 | superGovernor.executeFeeUpdate(feeType); |
| 61 | } |
| 62 | |
| 63 | function superGovernor_proposeMinStaleness( |
| 64 | uint256 newMinStaleness |
| 65 | ) public asAdmin { |
| 66 | superGovernor.proposeMinStaleness(newMinStaleness); |
| 67 | } |
| 68 | |
| 69 | function superGovernor_executeMinStalenesChange() public asAdmin { |
| 70 | superGovernor.executeMinStalenesChange(); |
| 71 | } |
| 72 | |
| 73 | function superGovernor_executeRemoveIncentiveTokens() public asAdmin { |
| 74 | superGovernor.executeRemoveIncentiveTokens(); |
| 75 | } |
| 76 | |
| 77 | function superGovernor_executeUpkeepClaim(uint256 amount) public asAdmin { |
| 78 | superGovernor.executeUpkeepClaim(amount); |
| 79 | } |
| 80 | |
| 81 | function superGovernor_proposeUpkeepPaymentsChange( |
| 82 | bool enabled |
| 83 | ) public asAdmin { |
| 84 | superGovernor.proposeUpkeepPaymentsChange(enabled); |
| 85 | } |
| 86 | |
| 87 | function superGovernor_executeUpkeepPaymentsChange() public asAdmin { |
| 88 | superGovernor.executeUpkeepPaymentsChange(); |
| 89 | } |
| 90 | |
| 91 | function superGovernor_proposeAddIncentiveTokens( |
| 92 | address[] memory tokens |
| 93 | ) public asAdmin { |
| 94 | superGovernor.proposeAddIncentiveTokens(tokens); |
| 95 | } |
| 96 | |
| 97 | function superGovernor_executeAddIncentiveTokens() public asAdmin { |
| 98 | superGovernor.executeAddIncentiveTokens(); |
| 99 | } |
| 100 | |
| 101 | function superGovernor_proposeGlobalHooksRoot( |
| 102 | bytes32 newRoot |
| 103 | ) public asAdmin { |
| 104 | superGovernor.proposeGlobalHooksRoot(newRoot); |
| 105 | } |
| 106 | } |
| 107 |
100.0%
test/recon/targets/SuperVaultAggregatorTargets.sol
Lines covered: 56 / 56 (100.0%)
| 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 | function superVaultAggregator_proposeChangePrimaryManager_clamped() public { |
| 20 | superVaultAggregator_proposeChangePrimaryManager( |
| 21 | address(superVaultStrategy), |
| 22 | _getActor() |
| 23 | ); |
| 24 | } |
| 25 | |
| 26 | function superVaultAggregator_executeChangePrimaryManager_clamped() public { |
| 27 | superVaultAggregator_executeChangePrimaryManager( |
| 28 | address(superVaultStrategy) |
| 29 | ); |
| 30 | } |
| 31 | |
| 32 | 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 | 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 | address[] memory secondaryManagers = new address[](1); |
| 46 | |
| 47 | ISuperVaultAggregator.VaultCreationParams |
| 48 | 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 | 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 | function superVaultAggregator_addAuthorizedCaller( |
| 69 | address strategy, |
| 70 | address caller |
| 71 | ) public asAdmin { |
| 72 | superVaultAggregator.addAuthorizedCaller(strategy, caller); |
| 73 | } |
| 74 | |
| 75 | function superVaultAggregator_addSecondaryManager( |
| 76 | address strategy, |
| 77 | address manager |
| 78 | ) public asActor { |
| 79 | 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 | function superVaultAggregator_claimUpkeep(uint256 amount) public asActor { |
| 103 | superVaultAggregator.claimUpkeep(amount); |
| 104 | } |
| 105 | |
| 106 | function superVaultAggregator_createVault( |
| 107 | ISuperVaultAggregator.VaultCreationParams memory params |
| 108 | ) public asActor { |
| 109 | ( |
| 110 | address _superVault, |
| 111 | address _strategy, |
| 112 | address _escrow |
| 113 | ) = superVaultAggregator.createVault(params); |
| 114 | |
| 115 | superVault = SuperVault(_superVault); |
| 116 | superVaultStrategy = SuperVaultStrategy(payable(_strategy)); |
| 117 | superVaultEscrow = SuperVaultEscrow(_escrow); |
| 118 | |
| 119 | hasDeployedNewVault = true; |
| 120 | } |
| 121 | |
| 122 | function superVaultAggregator_depositStake( |
| 123 | address manager, |
| 124 | uint256 amount |
| 125 | ) public asActor { |
| 126 | superVaultAggregator.depositStake(manager, amount); |
| 127 | } |
| 128 | |
| 129 | function superVaultAggregator_depositUpkeep(uint256 amount) public asActor { |
| 130 | superVaultAggregator.depositUpkeep(_getActor(), amount); |
| 131 | } |
| 132 | |
| 133 | function superVaultAggregator_executeChangePrimaryManager( |
| 134 | address strategy |
| 135 | ) public asActor { |
| 136 | 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 | function superVaultAggregator_proposeChangePrimaryManager( |
| 155 | address strategy, |
| 156 | address newManager |
| 157 | ) public asActor { |
| 158 | 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 | function superVaultAggregator_removeAuthorizedCaller( |
| 170 | address strategy, |
| 171 | address caller |
| 172 | ) public asActor { |
| 173 | superVaultAggregator.removeAuthorizedCaller(strategy, caller); |
| 174 | } |
| 175 | |
| 176 | function superVaultAggregator_removeSecondaryManager( |
| 177 | address strategy, |
| 178 | address manager |
| 179 | ) public asActor { |
| 180 | superVaultAggregator.removeSecondaryManager(strategy, manager); |
| 181 | } |
| 182 | |
| 183 | function superVaultAggregator_updatePPSVerificationThresholds( |
| 184 | address strategy, |
| 185 | uint256 dispersionThreshold_, |
| 186 | uint256 deviationThreshold_, |
| 187 | uint256 mnThreshold_ |
| 188 | ) public asActor { |
| 189 | superVaultAggregator.updatePPSVerificationThresholds( |
| 190 | strategy, |
| 191 | dispersionThreshold_, |
| 192 | deviationThreshold_, |
| 193 | mnThreshold_ |
| 194 | ); |
| 195 | } |
| 196 | |
| 197 | function superVaultAggregator_withdrawStake(uint256 amount) public asActor { |
| 198 | superVaultAggregator.withdrawStake(amount); |
| 199 | } |
| 200 | |
| 201 | function superVaultAggregator_withdrawUpkeep( |
| 202 | uint256 amount |
| 203 | ) public asActor { |
| 204 | superVaultAggregator.withdrawUpkeep(amount); |
| 205 | } |
| 206 | } |
| 207 |
100.0%
test/recon/targets/SuperVaultEscrowTargets.sol
Lines covered: 6 / 6 (100.0%)
| 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 | function superVaultEscrow_escrowShares(address from, uint256 amount) public asActor { |
| 25 | superVaultEscrow.escrowShares(from, amount); |
| 26 | } |
| 27 | |
| 28 | function superVaultEscrow_initialize(address vaultAddress, address strategyAddress) public asActor { |
| 29 | superVaultEscrow.initialize(vaultAddress, strategyAddress); |
| 30 | } |
| 31 | |
| 32 | function superVaultEscrow_returnShares(address to, uint256 amount) public asActor { |
| 33 | superVaultEscrow.returnShares(to, amount); |
| 34 | } |
| 35 | } |
100.0%
test/recon/targets/SuperVaultStrategyTargets.sol
Lines covered: 36 / 36 (100.0%)
| 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 | function superVaultStrategy_manageYieldSource_clamped( |
| 21 | uint256 sourceType |
| 22 | ) public { |
| 23 | 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 | 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 | 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 | function superVaultStrategy_executeVaultFeeConfigUpdate() public asActor { |
| 49 | superVaultStrategy.executeVaultFeeConfigUpdate(); |
| 50 | } |
| 51 | |
| 52 | function superVaultStrategy_handleOperations4626Deposit( |
| 53 | address controller, |
| 54 | uint256 assetsGross |
| 55 | ) public asActor { |
| 56 | superVaultStrategy.handleOperations4626Deposit(controller, assetsGross); |
| 57 | } |
| 58 | |
| 59 | function superVaultStrategy_handleOperations4626Mint( |
| 60 | address controller, |
| 61 | uint256 sharesNet, |
| 62 | uint256 assetsGross, |
| 63 | uint256 assetsNet |
| 64 | ) public asActor { |
| 65 | superVaultStrategy.handleOperations4626Mint( |
| 66 | controller, |
| 67 | sharesNet, |
| 68 | assetsGross, |
| 69 | assetsNet |
| 70 | ); |
| 71 | } |
| 72 | |
| 73 | function superVaultStrategy_handleOperations7540( |
| 74 | ISuperVaultStrategy.Operation operation, |
| 75 | address controller, |
| 76 | address receiver, |
| 77 | uint256 amount |
| 78 | ) public asActor { |
| 79 | superVaultStrategy.handleOperations7540( |
| 80 | operation, |
| 81 | controller, |
| 82 | receiver, |
| 83 | amount |
| 84 | ); |
| 85 | } |
| 86 | |
| 87 | function superVaultStrategy_manageEmergencyWithdraw( |
| 88 | uint8 action, |
| 89 | address recipient, |
| 90 | uint256 amount |
| 91 | ) public asActor { |
| 92 | superVaultStrategy.manageEmergencyWithdraw(action, recipient, amount); |
| 93 | } |
| 94 | |
| 95 | function superVaultStrategy_manageYieldSource( |
| 96 | address source, |
| 97 | address oracle, |
| 98 | uint8 actionType |
| 99 | ) public asActor { |
| 100 | superVaultStrategy.manageYieldSource(source, oracle, actionType); |
| 101 | } |
| 102 | |
| 103 | function superVaultStrategy_manageYieldSources( |
| 104 | address[] memory sources, |
| 105 | address[] memory oracles, |
| 106 | uint8[] memory actionTypes |
| 107 | ) public asActor { |
| 108 | superVaultStrategy.manageYieldSources(sources, oracles, actionTypes); |
| 109 | } |
| 110 | |
| 111 | function superVaultStrategy_moveAccumulatorOnTransfer( |
| 112 | address from, |
| 113 | address to, |
| 114 | uint256 shares |
| 115 | ) public asActor { |
| 116 | superVaultStrategy.moveAccumulatorOnTransfer(from, to, shares); |
| 117 | } |
| 118 | |
| 119 | function superVaultStrategy_proposeVaultFeeConfigUpdate( |
| 120 | uint256 performanceFeeBps, |
| 121 | uint256 managementFeeBps, |
| 122 | address recipient |
| 123 | ) public asActor { |
| 124 | superVaultStrategy.proposeVaultFeeConfigUpdate( |
| 125 | performanceFeeBps, |
| 126 | managementFeeBps, |
| 127 | recipient |
| 128 | ); |
| 129 | } |
| 130 | |
| 131 | function superVaultStrategy_updateMaxPPSSlippage( |
| 132 | uint256 maxSlippageBps |
| 133 | ) public asActor { |
| 134 | superVaultStrategy.updateMaxPPSSlippage(maxSlippageBps); |
| 135 | } |
| 136 | } |
| 137 |
100.0%
test/recon/targets/SuperVaultTargets.sol
Lines covered: 123 / 123 (100.0%)
| 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 | function superVault_requestRedeem_clamped(uint256 shares) public { |
| 19 | shares %= superVault.balanceOf(_getActor()) + 1; |
| 20 | |
| 21 | superVault_requestRedeem(shares); |
| 22 | } |
| 23 | |
| 24 | function superVault_redeem_clamped(uint256 shares) public { |
| 25 | uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
| 26 | uint256 claimableShares = superVault.convertToShares(claimableAssets); |
| 27 | |
| 28 | shares %= claimableShares + 1; |
| 29 | |
| 30 | superVault_redeem(shares); |
| 31 | } |
| 32 | |
| 33 | function superVault_withdraw_clamped(uint256 assets) public { |
| 34 | uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
| 35 | 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 | function superVault_approve(address spender, uint256 value) public asActor { |
| 43 | superVault.approve(spender, value); |
| 44 | } |
| 45 | |
| 46 | function superVault_burnShares(uint256 amount) public asActor { |
| 47 | 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 | function superVault_cancelRedeem_ASSERTION_CANCEL_REDEEM_NO_OVERPAY() |
| 54 | public |
| 55 | updateGhostsWithOpType(OpType.CANCEL) |
| 56 | { |
| 57 | uint256 pendingRedeemRequestsBefore = superVault.pendingRedeemRequest( |
| 58 | 0, |
| 59 | _getActor() |
| 60 | ); |
| 61 | uint256 pendingRedeemRequestsAsAssets = superVault.convertToAssets( |
| 62 | pendingRedeemRequestsBefore |
| 63 | ); |
| 64 | uint256 balanceBefore = MockERC20(superVault.asset()).balanceOf( |
| 65 | _getActor() |
| 66 | ); |
| 67 | |
| 68 | vm.prank(_getActor()); |
| 69 | superVault.cancelRedeem(_getActor()); |
| 70 | |
| 71 | uint256 pendingRedeemRequestsAfter = superVault.pendingRedeemRequest( |
| 72 | 0, |
| 73 | _getActor() |
| 74 | ); |
| 75 | uint256 averageRequestPPS = superVaultStrategy |
| 76 | .getSuperVaultState(_getActor()) |
| 77 | .averageRequestPPS; |
| 78 | uint256 balanceAfter = MockERC20(superVault.asset()).balanceOf( |
| 79 | _getActor() |
| 80 | ); |
| 81 | |
| 82 | // Checks |
| 83 | eq( |
| 84 | pendingRedeemRequestsAfter, |
| 85 | 0, |
| 86 | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 87 | ); |
| 88 | eq( |
| 89 | averageRequestPPS, |
| 90 | 0, |
| 91 | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 92 | ); |
| 93 | lte( |
| 94 | balanceAfter - balanceBefore, |
| 95 | pendingRedeemRequestsAsAssets, |
| 96 | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /// @dev Property: previewDeposit returns the correct amounts compared to executing a deposit |
| 101 | function superVault_deposit_ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION( |
| 102 | uint256 assets |
| 103 | ) public updateGhostsWithOpType(OpType.ADD) { |
| 104 | uint256 previewShares = superVault.previewDeposit(assets); |
| 105 | |
| 106 | vm.prank(_getActor()); |
| 107 | uint256 shares = superVault.deposit(assets, _getActor()); |
| 108 | |
| 109 | eq( |
| 110 | previewShares, |
| 111 | shares, |
| 112 | ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION |
| 113 | ); |
| 114 | } |
| 115 | |
| 116 | /// @dev Property: previewMint returns the correct amounts compared to executing a mint |
| 117 | function superVault_mint_ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION( |
| 118 | uint256 shares |
| 119 | ) public updateGhostsWithOpType(OpType.ADD) { |
| 120 | uint256 previewMint = superVault.previewMint(shares); |
| 121 | |
| 122 | vm.prank(_getActor()); |
| 123 | uint256 assets = superVault.mint(shares, _getActor()); |
| 124 | |
| 125 | eq( |
| 126 | assets, |
| 127 | previewMint, |
| 128 | ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION |
| 129 | ); |
| 130 | } |
| 131 | |
| 132 | function superVault_invalidateNonce(bytes32 nonce) public asActor { |
| 133 | superVault.invalidateNonce(nonce); |
| 134 | } |
| 135 | |
| 136 | function superVault_redeem( |
| 137 | uint256 shares |
| 138 | ) public updateGhostsWithOpType(OpType.REMOVE) asActor { |
| 139 | superVault.redeem(shares, _getActor(), _getActor()); |
| 140 | } |
| 141 | |
| 142 | function superVault_withdraw( |
| 143 | uint256 assets |
| 144 | ) public updateGhostsWithOpType(OpType.REMOVE) asActor { |
| 145 | superVault.withdraw(assets, _getActor(), _getActor()); |
| 146 | } |
| 147 | |
| 148 | function superVault_requestRedeem( |
| 149 | uint256 shares |
| 150 | ) public updateGhostsWithOpType(OpType.REQUEST) asActor { |
| 151 | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 152 | } |
| 153 | |
| 154 | function superVault_setOperator( |
| 155 | uint256 entropy, |
| 156 | bool approved |
| 157 | ) public asActor { |
| 158 | address operator = _getRandomActor(entropy); |
| 159 | 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 | 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 | memory stateSenderBefore = superVaultStrategy.getSuperVaultState( |
| 173 | _getActor() |
| 174 | ); |
| 175 | ISuperVaultStrategy.SuperVaultState |
| 176 | memory stateRecipientBefore = superVaultStrategy.getSuperVaultState( |
| 177 | to |
| 178 | ); |
| 179 | |
| 180 | vm.prank(_getActor()); |
| 181 | try superVault.transfer(to, value) { |
| 182 | ISuperVaultStrategy.SuperVaultState |
| 183 | memory stateSenderAfter = superVaultStrategy.getSuperVaultState( |
| 184 | _getActor() |
| 185 | ); |
| 186 | ISuperVaultStrategy.SuperVaultState |
| 187 | memory stateRecipientAfter = superVaultStrategy |
| 188 | .getSuperVaultState(to); |
| 189 | |
| 190 | eq( |
| 191 | stateSenderBefore.accumulatorShares - |
| 192 | stateSenderAfter.accumulatorShares, |
| 193 | stateRecipientAfter.accumulatorShares - |
| 194 | stateRecipientBefore.accumulatorShares, |
| 195 | ASSERTION_TRANSFER_SHARES_CONSERVED |
| 196 | ); |
| 197 | eq( |
| 198 | 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 | expectedError = checkError( |
| 207 | err, |
| 208 | "ERC20InsufficientBalance(address,uint256,uint256)" |
| 209 | ); |
| 210 | 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 | 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 | vm.prank(_getActor()); |
| 225 | try superVault.transferFrom(from, to, value) {} catch ( |
| 226 | bytes memory err |
| 227 | ) { |
| 228 | bool expectedError; |
| 229 | expectedError = |
| 230 | checkError( |
| 231 | err, |
| 232 | "ERC20InsufficientBalance(address,uint256,uint256)" |
| 233 | ) || |
| 234 | checkError( |
| 235 | err, |
| 236 | "ERC20InsufficientAllowance(address,uint256,uint256)" |
| 237 | ); |
| 238 | t( |
| 239 | expectedError, |
| 240 | 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 | } |
| 264 |
94.0%
test/recon/targets/YieldSourceTargets.sol
Lines covered: 166 / 176 (94.0%)
| 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 | function yieldSource_approve( |
| 23 | address spender, |
| 24 | uint256 value |
| 25 | ) public asActor { |
| 26 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 27 | address yieldSource = _getYieldSource(); |
| 28 | |
| 29 | if (currentType == YieldSourceType.ERC4626) { |
| 30 | MockERC4626Tester(yieldSource).approve(spender, value); |
| 31 | } else if (currentType == YieldSourceType.ERC5115) { |
| 32 | MockERC5115Tester(yieldSource).approve(spender, value); |
| 33 | } else if (currentType == YieldSourceType.ERC7540) { |
| 34 | MockERC7540Tester(yieldSource).approve(spender, value); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | function yieldSource_setDecimalsOffset( |
| 39 | uint8 targetDecimalsOffset |
| 40 | ) public asActor { |
| 41 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 42 | address yieldSource = _getYieldSource(); |
| 43 | |
| 44 | if (currentType == YieldSourceType.ERC4626) { |
| 45 | MockERC4626Tester(yieldSource).setDecimalsOffset( |
| 46 | targetDecimalsOffset |
| 47 | ); |
| 48 | } |
| 49 | // Note: ERC5115 and ERC7540 don't have setDecimalsOffset function |
| 50 | } |
| 51 | |
| 52 | function yieldSource_transfer(address to, uint256 value) public asActor { |
| 53 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 54 | address yieldSource = _getYieldSource(); |
| 55 | |
| 56 | if (currentType == YieldSourceType.ERC4626) { |
| 57 | MockERC4626Tester(yieldSource).transfer(to, value); |
| 58 | } else if (currentType == YieldSourceType.ERC5115) { |
| 59 | MockERC5115Tester(yieldSource).transfer(to, value); |
| 60 | } else if (currentType == YieldSourceType.ERC7540) { |
| 61 | MockERC7540Tester(yieldSource).transfer(to, value); |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | 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 | if (currentType == YieldSourceType.ERC4626) { |
| 74 | MockERC4626Tester(yieldSource).transferFrom(from, to, value); |
| 75 | } else if (currentType == YieldSourceType.ERC5115) { |
| 76 | MockERC5115Tester(yieldSource).transferFrom(from, to, value); |
| 77 | } else if (currentType == YieldSourceType.ERC7540) { |
| 78 | MockERC7540Tester(yieldSource).transferFrom(from, to, value); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /// Core Vault Functions (ERC4626/ERC4626-like) /// |
| 83 | function yieldSource_deposit( |
| 84 | uint256 assets, |
| 85 | address receiver |
| 86 | ) public asActor { |
| 87 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 88 | address yieldSource = _getYieldSource(); |
| 89 | |
| 90 | if (currentType == YieldSourceType.ERC4626) { |
| 91 | 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 | function yieldSource_mint(uint256 shares, address receiver) public asActor { |
| 98 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 99 | address yieldSource = _getYieldSource(); |
| 100 | |
| 101 | if (currentType == YieldSourceType.ERC4626) { |
| 102 | MockERC4626Tester(yieldSource).mint(shares, receiver); |
| 103 | } else if (currentType == YieldSourceType.ERC7540) { |
| 104 | MockERC7540Tester(yieldSource).mint(shares, receiver); |
| 105 | } |
| 106 | // Note: ERC5115 doesn't have mint function |
| 107 | } |
| 108 | |
| 109 | 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 | if (currentType == YieldSourceType.ERC4626) { |
| 118 | MockERC4626Tester(yieldSource).withdraw(assets, receiver, owner); |
| 119 | } else if (currentType == YieldSourceType.ERC7540) { |
| 120 | MockERC7540Tester(yieldSource).withdraw(assets, receiver, owner); |
| 121 | } |
| 122 | // Note: ERC5115 doesn't have withdraw function |
| 123 | } |
| 124 | |
| 125 | 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 | if (currentType == YieldSourceType.ERC4626) { |
| 134 | MockERC4626Tester(yieldSource).redeem(shares, receiver, owner); |
| 135 | } else if (currentType == YieldSourceType.ERC7540) { |
| 136 | MockERC7540Tester(yieldSource).redeem(shares, receiver, owner); |
| 137 | } |
| 138 | // Note: ERC5115 has different redeem signature, handled separately |
| 139 | } |
| 140 | |
| 141 | /// ERC5115-specific functions /// |
| 142 | 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 | if (currentType == YieldSourceType.ERC5115) { |
| 153 | MockERC5115Tester(yieldSource).deposit( |
| 154 | receiver, |
| 155 | tokenIn, |
| 156 | amountTokenToDeposit, |
| 157 | minSharesOut, |
| 158 | depositFromInternalBalance |
| 159 | ); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | 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 | if (currentType == YieldSourceType.ERC5115) { |
| 174 | MockERC5115Tester(yieldSource).redeem( |
| 175 | receiver, |
| 176 | amountSharesToRedeem, |
| 177 | tokenOut, |
| 178 | minTokenOut, |
| 179 | burnFromInternalBalance |
| 180 | ); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | /// ERC7540-specific functions /// |
| 185 | function yieldSource_setOperator( |
| 186 | address operator, |
| 187 | bool approved |
| 188 | ) public asActor { |
| 189 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 190 | address yieldSource = _getYieldSource(); |
| 191 | |
| 192 | if (currentType == YieldSourceType.ERC7540) { |
| 193 | MockERC7540Tester(yieldSource).setOperator(operator, approved); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | 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 | if (currentType == YieldSourceType.ERC7540) { |
| 206 | MockERC7540Tester(yieldSource).requestDeposit( |
| 207 | assets, |
| 208 | controller, |
| 209 | owner |
| 210 | ); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | 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 | if (currentType == YieldSourceType.ERC7540) { |
| 223 | MockERC7540Tester(yieldSource).requestRedeem( |
| 224 | shares, |
| 225 | controller, |
| 226 | owner |
| 227 | ); |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | function yieldSource_cancelDepositRequest( |
| 232 | uint256 requestId, |
| 233 | address controller |
| 234 | ) public asActor { |
| 235 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 236 | address yieldSource = _getYieldSource(); |
| 237 | |
| 238 | if (currentType == YieldSourceType.ERC7540) { |
| 239 | MockERC7540Tester(yieldSource).cancelDepositRequest( |
| 240 | requestId, |
| 241 | controller |
| 242 | ); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | function yieldSource_cancelRedeemRequest( |
| 247 | uint256 requestId, |
| 248 | address controller |
| 249 | ) public asActor { |
| 250 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 251 | address yieldSource = _getYieldSource(); |
| 252 | |
| 253 | if (currentType == YieldSourceType.ERC7540) { |
| 254 | MockERC7540Tester(yieldSource).cancelRedeemRequest( |
| 255 | requestId, |
| 256 | controller |
| 257 | ); |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | 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 | if (currentType == YieldSourceType.ERC7540) { |
| 270 | MockERC7540Tester(yieldSource).claimCancelDepositRequest( |
| 271 | requestId, |
| 272 | receiver, |
| 273 | controller |
| 274 | ); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | 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 | if (currentType == YieldSourceType.ERC7540) { |
| 287 | MockERC7540Tester(yieldSource).claimCancelRedeemRequest( |
| 288 | requestId, |
| 289 | receiver, |
| 290 | controller |
| 291 | ); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | 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 | if (currentType == YieldSourceType.ERC7540) { |
| 304 | MockERC7540Tester(yieldSource).deposit( |
| 305 | assets, |
| 306 | receiver, |
| 307 | controller |
| 308 | ); |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | /// Testing-specific functions (ERC4626 only) /// |
| 313 | function yieldSource_setRevertBehavior4626( |
| 314 | uint8 functionType, |
| 315 | uint8 revertType |
| 316 | ) public asActor { |
| 317 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 318 | address yieldSource = _getYieldSource(); |
| 319 | |
| 320 | if (currentType == YieldSourceType.ERC4626) { |
| 321 | MockERC4626Tester(yieldSource).setRevertBehavior( |
| 322 | FunctionType(functionType), |
| 323 | RevertType(revertType) |
| 324 | ); |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | /// Testing-specific functions (ERC5115 only) /// |
| 329 | function yieldSource_setRevertBehavior5115( |
| 330 | uint8 revertType |
| 331 | ) public asActor { |
| 332 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 333 | address yieldSource = _getYieldSource(); |
| 334 | |
| 335 | if (currentType == YieldSourceType.ERC5115) { |
| 336 | MockERC5115Tester(yieldSource).setRevertBehavior( |
| 337 | RevertType5115(revertType) |
| 338 | ); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | /// Common yield manipulation functions /// |
| 343 | |
| 344 | function yieldSource_simulateLoss(uint256 lossAmount) public { |
| 345 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 346 | address yieldSource = _getYieldSource(); |
| 347 | |
| 348 | if (currentType == YieldSourceType.ERC4626) { |
| 349 | MockERC4626Tester(yieldSource).simulateLoss(lossAmount); |
| 350 | } else if (currentType == YieldSourceType.ERC5115) { |
| 351 | MockERC5115Tester(yieldSource).simulateLoss(lossAmount); |
| 352 | } else if (currentType == YieldSourceType.ERC7540) { |
| 353 | MockERC7540Tester(yieldSource).simulateLoss(lossAmount); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | function yieldSource_simulateGain(uint256 gainAmount) public { |
| 358 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 359 | address yieldSource = _getYieldSource(); |
| 360 | |
| 361 | if (currentType == YieldSourceType.ERC4626) { |
| 362 | MockERC4626Tester(yieldSource).simulateGain(gainAmount); |
| 363 | } else if (currentType == YieldSourceType.ERC5115) { |
| 364 | MockERC5115Tester(yieldSource).simulateGain(gainAmount); |
| 365 | } else if (currentType == YieldSourceType.ERC7540) { |
| 366 | MockERC7540Tester(yieldSource).simulateGain(gainAmount); |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | function yieldSource_setLossOnWithdraw( |
| 371 | uint256 lossOnWithdraw |
| 372 | ) public asActor { |
| 373 | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 374 | address yieldSource = _getYieldSource(); |
| 375 | |
| 376 | if (currentType == YieldSourceType.ERC4626) { |
| 377 | MockERC4626Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 378 | } else if (currentType == YieldSourceType.ERC5115) { |
| 379 | MockERC5115Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 380 | } else if (currentType == YieldSourceType.ERC7540) { |
| 381 | MockERC7540Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | /// Yield source management functions /// |
| 386 | function yieldSource_switchToERC4626() public { |
| 387 | _switchYieldSource(0); // Switch to first yield source (ERC4626) |
| 388 | } |
| 389 | |
| 390 | function yieldSource_switchToERC5115() public { |
| 391 | _switchYieldSource(1); // Switch to second yield source (ERC5115) |
| 392 | } |
| 393 | |
| 394 | function yieldSource_switchToERC7540() public { |
| 395 | _switchYieldSource(2); // Switch to third yield source (ERC7540) |
| 396 | } |
| 397 | |
| 398 | function yieldSource_switchRandom(uint256 entropy) public { |
| 399 | // Randomly switch between the three deployed yield sources |
| 400 | _switchYieldSource(entropy); |
| 401 | } |
| 402 | } |
| 403 |