Files
92
Lines
23530
Coverage
57%
4122 / 7230
Actions
92%
lib/erc7540-reusable-properties/src/ERC7540Properties.sol
Lines covered: 114 / 123 (92%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
|
| 2 | pragma solidity ^0.8.21; |
|
| 3 | ||
| 4 | import {Asserts} from "@chimera/Asserts.sol"; |
|
| 5 | ||
| 6 | interface IShareLike { |
|
| 7 | function balanceOf(address) external view returns (uint256); |
|
| 8 | function totalSupply() external view returns (uint256); |
|
| 9 | function decimals() external view returns (uint8); |
|
| 10 | } |
|
| 11 | ||
| 12 | interface IERC7540Like { |
|
| 13 | function share() external view returns (address shareTokenAddress); |
|
| 14 | function convertToShares( |
|
| 15 | uint256 assets |
|
| 16 | ) external view returns (uint256 shares); |
|
| 17 | function convertToAssets( |
|
| 18 | uint256 shares |
|
| 19 | ) external view returns (uint256 assets); |
|
| 20 | function totalAssets() external view returns (uint256 totalManagedAssets); |
|
| 21 | function maxDeposit( |
|
| 22 | address receiver |
|
| 23 | ) external view returns (uint256 maxAssets); |
|
| 24 | function previewDeposit( |
|
| 25 | uint256 assets |
|
| 26 | ) external view returns (uint256 shares); |
|
| 27 | function deposit( |
|
| 28 | uint256 assets, |
|
| 29 | address receiver |
|
| 30 | ) external returns (uint256 shares); |
|
| 31 | function maxMint( |
|
| 32 | address receiver |
|
| 33 | ) external view returns (uint256 maxShares); |
|
| 34 | function previewMint(uint256 shares) external view returns (uint256 assets); |
|
| 35 | function mint( |
|
| 36 | uint256 shares, |
|
| 37 | address receiver |
|
| 38 | ) external returns (uint256 assets); |
|
| 39 | function maxWithdraw( |
|
| 40 | address owner |
|
| 41 | ) external view returns (uint256 maxAssets); |
|
| 42 | function previewWithdraw( |
|
| 43 | uint256 assets |
|
| 44 | ) external view returns (uint256 shares); |
|
| 45 | function withdraw( |
|
| 46 | uint256 assets, |
|
| 47 | address receiver, |
|
| 48 | address owner |
|
| 49 | ) external returns (uint256 shares); |
|
| 50 | function maxRedeem(address owner) external view returns (uint256 maxShares); |
|
| 51 | function previewRedeem( |
|
| 52 | uint256 shares |
|
| 53 | ) external view returns (uint256 assets); |
|
| 54 | function redeem( |
|
| 55 | uint256 shares, |
|
| 56 | address receiver, |
|
| 57 | address owner |
|
| 58 | ) external returns (uint256 assets); |
|
| 59 | ||
| 60 | function requestRedeem( |
|
| 61 | uint256 shares, |
|
| 62 | address receiver, |
|
| 63 | address owner, |
|
| 64 | bytes calldata data |
|
| 65 | ) external returns (uint256 requestId); |
|
| 66 | } |
|
| 67 | ||
| 68 | /// @dev ERC-7540 Properties |
|
| 69 | /// @author The Recon Team |
|
| 70 | /// @notice A set of reuseable tests for your ERC7540 Vaults |
|
| 71 | /// To get started, extend from this contract and make sure to add a way to set the actor (the current user) |
|
| 72 | /// For more info: https://getrecon.xyz/ |
|
| 73 | abstract contract ERC7540Properties is Asserts { |
|
| 74 | 16× | uint256 public constant MAX_ROUNDING_ERROR = 10 ** 18; |
| 75 | ||
| 76 | address actor; |
|
| 77 | ||
| 78 | /// @audit TODO: You must add a way to change this! |
|
| 79 | ||
| 80 | /// @dev 7540-1 convertToAssets(totalSupply) == totalAssets unless price is 0.0 |
|
| 81 | 25× | function erc7540_1(address erc7540Target) public virtual returns (bool) { |
| 82 | // Doesn't hold on zero price |
|
| 83 | 3× | if ( |
| 84 | 62× | IERC7540Like(erc7540Target).convertToAssets( |
| 85 | 128× | 10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
| 86 | 1× | ) == 0 |
| 87 | 2× | ) return true; |
| 88 | ||
| 89 | return |
|
| 90 | 63× | IERC7540Like(erc7540Target).convertToAssets( |
| 91 | 115× | IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
| 92 | 58× | ) == IERC7540Like(erc7540Target).totalAssets(); |
| 93 | } |
|
| 94 | ||
| 95 | /// @dev 7540-2 convertToShares(totalAssets) == totalSupply unless price is 0.0 |
|
| 96 | 25× | function erc7540_2(address erc7540Target) public virtual returns (bool) { |
| 97 | 3× | if ( |
| 98 | 62× | IERC7540Like(erc7540Target).convertToAssets( |
| 99 | 128× | 10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
| 100 | 1× | ) == 0 |
| 101 | 2× | ) return true; |
| 102 | ||
| 103 | // convertToShares(totalAssets) == totalSupply |
|
| 104 | return |
|
| 105 | 7× | _diff( |
| 106 | 61× | IERC7540Like(erc7540Target).convertToShares( |
| 107 | 58× | IERC7540Like(erc7540Target).totalAssets() |
| 108 | ), |
|
| 109 | 115× | IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
| 110 | ) <= MAX_ROUNDING_ERROR; |
|
| 111 | } |
|
| 112 | ||
| 113 | 2× | function _diff(uint256 a, uint256 b) internal pure returns (uint256) { |
| 114 | 19× | return a > b ? a - b : b - a; |
| 115 | } |
|
| 116 | ||
| 117 | /// @dev 7540-3 max* never reverts |
|
| 118 | 26× | function erc7540_3(address erc7540Target) public virtual returns (bool) { |
| 119 | // max* never reverts |
|
| 120 | 65× | try IERC7540Like(erc7540Target).maxDeposit(actor) {} catch { |
| 121 | 2× | return false; |
| 122 | } |
|
| 123 | 66× | try IERC7540Like(erc7540Target).maxMint(actor) {} catch { |
| 124 | 0 | return false; |
| 125 | } |
|
| 126 | 66× | try IERC7540Like(erc7540Target).maxRedeem(actor) {} catch { |
| 127 | 2× | return false; |
| 128 | } |
|
| 129 | 67× | try IERC7540Like(erc7540Target).maxWithdraw(actor) {} catch { |
| 130 | 0 | return false; |
| 131 | } |
|
| 132 | ||
| 133 | 2× | return true; |
| 134 | } |
|
| 135 | ||
| 136 | /// == erc7540_4 == // |
|
| 137 | ||
| 138 | /// @dev 7540-4 claiming more than max always reverts |
|
| 139 | 21× | function erc7540_4_deposit( |
| 140 | address erc7540Target, |
|
| 141 | uint256 amt |
|
| 142 | 2× | ) public virtual returns (bool) { |
| 143 | // Skip 0 |
|
| 144 | 12× | if (amt == 0) { |
| 145 | 6× | return true; // Skip |
| 146 | } |
|
| 147 | ||
| 148 | 139× | uint256 maxDep = IERC7540Like(erc7540Target).maxDeposit(actor); |
| 149 | ||
| 150 | /// @audit No Revert is proven by erc7540_5 |
|
| 151 | ||
| 152 | 16× | uint256 sum = maxDep + amt; |
| 153 | 12× | if (sum == 0) { |
| 154 | 0 | return true; // Needs to be greater than 0, skip |
| 155 | } |
|
| 156 | ||
| 157 | 16× | try IERC7540Like(erc7540Target).deposit(maxDep + amt, actor) { |
| 158 | return false; |
|
| 159 | } catch { |
|
| 160 | // We want this to be hit |
|
| 161 | return true; // So we explicitly return here, as a means to ensure that this is the code path |
|
| 162 | } |
|
| 163 | ||
| 164 | // NOTE: This code path is never hit per the above |
|
| 165 | } |
|
| 166 | ||
| 167 | 21× | function erc7540_4_mint( |
| 168 | address erc7540Target, |
|
| 169 | uint256 amt |
|
| 170 | 2× | ) public virtual returns (bool) { |
| 171 | // Skip 0 |
|
| 172 | 12× | if (amt == 0) { |
| 173 | 6× | return true; |
| 174 | } |
|
| 175 | ||
| 176 | 139× | uint256 maxDep = IERC7540Like(erc7540Target).maxMint(actor); |
| 177 | ||
| 178 | 16× | uint256 sum = maxDep + amt; |
| 179 | 12× | if (sum == 0) { |
| 180 | 0 | return true; // Needs to be greater than 0, skip |
| 181 | } |
|
| 182 | ||
| 183 | 64× | try IERC7540Like(erc7540Target).mint(maxDep + amt, actor) { |
| 184 | return false; |
|
| 185 | } catch { |
|
| 186 | // We want this to be hit |
|
| 187 | return true; // So we explicitly return here, as a means to ensure that this is the code path |
|
| 188 | } |
|
| 189 | ||
| 190 | // NOTE: This code path is never hit per the above |
|
| 191 | } |
|
| 192 | ||
| 193 | 21× | function erc7540_4_withdraw( |
| 194 | address erc7540Target, |
|
| 195 | uint256 amt |
|
| 196 | 2× | ) public virtual returns (bool) { |
| 197 | // Skip 0 |
|
| 198 | 12× | if (amt == 0) { |
| 199 | 6× | return true; |
| 200 | } |
|
| 201 | ||
| 202 | 139× | uint256 maxDep = IERC7540Like(erc7540Target).maxWithdraw(actor); |
| 203 | ||
| 204 | 16× | uint256 sum = maxDep + amt; |
| 205 | 12× | if (sum == 0) { |
| 206 | 0 | return true; // Needs to be greater than 0 |
| 207 | } |
|
| 208 | ||
| 209 | 16× | try IERC7540Like(erc7540Target).withdraw(maxDep + amt, actor, actor) { |
| 210 | return false; |
|
| 211 | } catch { |
|
| 212 | // We want this to be hit |
|
| 213 | return true; // So we explicitly return here, as a means to ensure that this is the code path |
|
| 214 | } |
|
| 215 | ||
| 216 | // NOTE: This code path is never hit per the above |
|
| 217 | } |
|
| 218 | ||
| 219 | 33× | function erc7540_4_redeem( |
| 220 | address erc7540Target, |
|
| 221 | uint256 amt |
|
| 222 | 2× | ) public virtual returns (bool) { |
| 223 | // Skip 0 |
|
| 224 | 12× | if (amt == 0) { |
| 225 | 6× | return true; |
| 226 | } |
|
| 227 | ||
| 228 | 139× | uint256 maxDep = IERC7540Like(erc7540Target).maxRedeem(actor); |
| 229 | ||
| 230 | 16× | uint256 sum = maxDep + amt; |
| 231 | 12× | if (sum == 0) { |
| 232 | 0 | return true; // Needs to be greater than 0 |
| 233 | } |
|
| 234 | ||
| 235 | 139× | try IERC7540Like(erc7540Target).redeem(maxDep + amt, actor, actor) { |
| 236 | 5× | return false; |
| 237 | } catch { |
|
| 238 | // We want this to be hit |
|
| 239 | 14× | return true; // So we explicitly return here, as a means to ensure that this is the code path |
| 240 | } |
|
| 241 | ||
| 242 | // NOTE: This code path is never hit per the above |
|
| 243 | } |
|
| 244 | ||
| 245 | /// == END erc7540_4 == // |
|
| 246 | ||
| 247 | /// @dev 7540-5 requestRedeem reverts if the share balance is less than amount |
|
| 248 | 23× | function erc7540_5( |
| 249 | address erc7540Target, |
|
| 250 | address shareToken, |
|
| 251 | uint256 shares |
|
| 252 | 2× | ) public virtual returns (bool) { |
| 253 | 12× | if (shares == 0) { |
| 254 | 6× | return true; // Skip |
| 255 | } |
|
| 256 | ||
| 257 | 139× | uint256 actualBal = IShareLike(shareToken).balanceOf(actor); |
| 258 | 16× | uint256 balWeWillUse = actualBal + shares; |
| 259 | ||
| 260 | 12× | if (balWeWillUse == 0) { |
| 261 | 0 | return true; // Skip |
| 262 | } |
|
| 263 | ||
| 264 | 5× | try |
| 265 | 87× | IERC7540Like(erc7540Target).requestRedeem( |
| 266 | balWeWillUse, |
|
| 267 | 18× | actor, |
| 268 | actor, |
|
| 269 | "" |
|
| 270 | ) |
|
| 271 | { |
|
| 272 | 2× | return false; |
| 273 | } catch { |
|
| 274 | 14× | return true; |
| 275 | } |
|
| 276 | ||
| 277 | // NOTE: This code path is never hit per the above |
|
| 278 | } |
|
| 279 | ||
| 280 | /// @dev 7540-6 preview* always reverts |
|
| 281 | 28× | function erc7540_6(address erc7540Target) public virtual returns (bool) { |
| 282 | // preview* always reverts |
|
| 283 | 58× | try IERC7540Like(erc7540Target).previewDeposit(0) { |
| 284 | 2× | return false; |
| 285 | } catch {} |
|
| 286 | 59× | try IERC7540Like(erc7540Target).previewMint(0) { |
| 287 | 2× | return false; |
| 288 | } catch {} |
|
| 289 | 36× | try IERC7540Like(erc7540Target).previewRedeem(0) { |
| 290 | 0 | return false; |
| 291 | } catch {} |
|
| 292 | 36× | try IERC7540Like(erc7540Target).previewWithdraw(0) { |
| 293 | 0 | return false; |
| 294 | } catch {} |
|
| 295 | ||
| 296 | 2× | return true; |
| 297 | } |
|
| 298 | ||
| 299 | /// == erc7540_7 == // |
|
| 300 | ||
| 301 | /// @dev 7540-7 if max[method] > 0, then [method] (max) should not revert |
|
| 302 | 20× | function erc7540_7_deposit( |
| 303 | address erc7540Target, |
|
| 304 | uint256 amt |
|
| 305 | 4× | ) public virtual returns (bool) { |
| 306 | // Per erc7540_4 |
|
| 307 | 74× | uint256 maxDeposit = IERC7540Like(erc7540Target).maxDeposit(actor); |
| 308 | 9× | amt = between(amt, 0, maxDeposit); |
| 309 | ||
| 310 | 6× | if (amt == 0) { |
| 311 | 6× | return true; // Skip |
| 312 | } |
|
| 313 | ||
| 314 | 25× | try IERC7540Like(erc7540Target).deposit(amt, actor) { |
| 315 | // Success here |
|
| 316 | return true; |
|
| 317 | } catch { |
|
| 318 | return false; |
|
| 319 | } |
|
| 320 | ||
| 321 | // NOTE: This code path is never hit per the above |
|
| 322 | } |
|
| 323 | ||
| 324 | 20× | function erc7540_7_mint( |
| 325 | address erc7540Target, |
|
| 326 | uint256 amt |
|
| 327 | 4× | ) public virtual returns (bool) { |
| 328 | 74× | uint256 maxMint = IERC7540Like(erc7540Target).maxMint(actor); |
| 329 | 9× | amt = between(amt, 0, maxMint); |
| 330 | ||
| 331 | 6× | if (amt == 0) { |
| 332 | 6× | return true; // Skip |
| 333 | } |
|
| 334 | ||
| 335 | 25× | try IERC7540Like(erc7540Target).mint(amt, actor) { |
| 336 | // Success here |
|
| 337 | return true; |
|
| 338 | } catch { |
|
| 339 | return false; |
|
| 340 | } |
|
| 341 | ||
| 342 | // NOTE: This code path is never hit per the above |
|
| 343 | } |
|
| 344 | ||
| 345 | 31× | function erc7540_7_withdraw( |
| 346 | address erc7540Target, |
|
| 347 | uint256 amt |
|
| 348 | 10× | ) public virtual returns (bool) { |
| 349 | 141× | uint256 maxWithdraw = IERC7540Like(erc7540Target).maxWithdraw(actor); |
| 350 | 18× | amt = between(amt, 0, maxWithdraw); |
| 351 | ||
| 352 | 11× | if (amt == 0) { |
| 353 | 12× | return true; // Skip |
| 354 | } |
|
| 355 | ||
| 356 | 77× | try IERC7540Like(erc7540Target).withdraw(amt, actor, actor) { |
| 357 | // Success here |
|
| 358 | 6× | return true; |
| 359 | } catch { |
|
| 360 | 6× | return false; |
| 361 | } |
|
| 362 | ||
| 363 | // NOTE: This code path is never hit per the above |
|
| 364 | } |
|
| 365 | ||
| 366 | 20× | function erc7540_7_redeem( |
| 367 | address erc7540Target, |
|
| 368 | uint256 amt |
|
| 369 | 4× | ) public virtual returns (bool) { |
| 370 | // Per erc7540_4 |
|
| 371 | 74× | uint256 maxRedeem = IERC7540Like(erc7540Target).maxRedeem(actor); |
| 372 | 9× | amt = between(amt, 0, maxRedeem); |
| 373 | ||
| 374 | 6× | if (amt == 0) { |
| 375 | 6× | return true; // Skip |
| 376 | } |
|
| 377 | ||
| 378 | 27× | try IERC7540Like(erc7540Target).redeem(amt, actor, actor) { |
| 379 | return true; |
|
| 380 | } catch { |
|
| 381 | return false; |
|
| 382 | } |
|
| 383 | ||
| 384 | // NOTE: This code path is never hit per the above |
|
| 385 | } |
|
| 386 | ||
| 387 | /// == END erc7540_7 == // |
|
| 388 | } |
90%
lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
Lines covered: 27 / 30 (90%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | /** |
|
| 7 | * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed |
|
| 8 | * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an |
|
| 9 | * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer |
|
| 10 | * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. |
|
| 11 | * |
|
| 12 | * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be |
|
| 13 | * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in |
|
| 14 | * case an upgrade adds a module that needs to be initialized. |
|
| 15 | * |
|
| 16 | * For example: |
|
| 17 | * |
|
| 18 | * [.hljs-theme-light.nopadding] |
|
| 19 | * ```solidity |
|
| 20 | * contract MyToken is ERC20Upgradeable { |
|
| 21 | * function initialize() initializer public { |
|
| 22 | * __ERC20_init("MyToken", "MTK"); |
|
| 23 | * } |
|
| 24 | * } |
|
| 25 | * |
|
| 26 | * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { |
|
| 27 | * function initializeV2() reinitializer(2) public { |
|
| 28 | * __ERC20Permit_init("MyToken"); |
|
| 29 | * } |
|
| 30 | * } |
|
| 31 | * ``` |
|
| 32 | * |
|
| 33 | * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as |
|
| 34 | * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. |
|
| 35 | * |
|
| 36 | * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure |
|
| 37 | * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. |
|
| 38 | * |
|
| 39 | * [CAUTION] |
|
| 40 | * ==== |
|
| 41 | * Avoid leaving a contract uninitialized. |
|
| 42 | * |
|
| 43 | * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation |
|
| 44 | * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke |
|
| 45 | * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: |
|
| 46 | * |
|
| 47 | * [.hljs-theme-light.nopadding] |
|
| 48 | * ``` |
|
| 49 | * /// @custom:oz-upgrades-unsafe-allow constructor |
|
| 50 | * constructor() { |
|
| 51 | * _disableInitializers(); |
|
| 52 | * } |
|
| 53 | * ``` |
|
| 54 | * ==== |
|
| 55 | */ |
|
| 56 | abstract contract Initializable { |
|
| 57 | /** |
|
| 58 | * @dev Storage of the initializable contract. |
|
| 59 | * |
|
| 60 | * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions |
|
| 61 | * when using with upgradeable contracts. |
|
| 62 | * |
|
| 63 | * @custom:storage-location erc7201:openzeppelin.storage.Initializable |
|
| 64 | */ |
|
| 65 | struct InitializableStorage { |
|
| 66 | /** |
|
| 67 | * @dev Indicates that the contract has been initialized. |
|
| 68 | */ |
|
| 69 | uint64 _initialized; |
|
| 70 | /** |
|
| 71 | * @dev Indicates that the contract is in the process of being initialized. |
|
| 72 | */ |
|
| 73 | bool _initializing; |
|
| 74 | } |
|
| 75 | ||
| 76 | // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) |
|
| 77 | 4× | bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; |
| 78 | ||
| 79 | /** |
|
| 80 | * @dev The contract is already initialized. |
|
| 81 | */ |
|
| 82 | error InvalidInitialization(); |
|
| 83 | ||
| 84 | /** |
|
| 85 | * @dev The contract is not initializing. |
|
| 86 | */ |
|
| 87 | error NotInitializing(); |
|
| 88 | ||
| 89 | /** |
|
| 90 | * @dev Triggered when the contract has been initialized or reinitialized. |
|
| 91 | */ |
|
| 92 | event Initialized(uint64 version); |
|
| 93 | ||
| 94 | /** |
|
| 95 | * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, |
|
| 96 | * `onlyInitializing` functions can be used to initialize parent contracts. |
|
| 97 | * |
|
| 98 | * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any |
|
| 99 | * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in |
|
| 100 | * production. |
|
| 101 | * |
|
| 102 | * Emits an {Initialized} event. |
|
| 103 | */ |
|
| 104 | 10× | modifier initializer() { |
| 105 | // solhint-disable-next-line var-name-mixedcase |
|
| 106 | 14× | InitializableStorage storage $ = _getInitializableStorage(); |
| 107 | ||
| 108 | // Cache values to avoid duplicated sloads |
|
| 109 | 18× | bool isTopLevelCall = !$._initializing; |
| 110 | 3× | uint64 initialized = $._initialized; |
| 111 | ||
| 112 | // Allowed calls: |
|
| 113 | // - initialSetup: the contract is not in the initializing state and no previous version was |
|
| 114 | // initialized |
|
| 115 | // - construction: the contract is initialized at version 1 (no reinitialization) and the |
|
| 116 | // current contract is just being deployed |
|
| 117 | 22× | bool initialSetup = initialized == 0 && isTopLevelCall; |
| 118 | 25× | bool construction = initialized == 1 && address(this).code.length == 0; |
| 119 | ||
| 120 | 22× | if (!initialSetup && !construction) { |
| 121 | 0 | revert InvalidInitialization(); |
| 122 | } |
|
| 123 | 14× | $._initialized = 1; |
| 124 | 10× | if (isTopLevelCall) { |
| 125 | 12× | $._initializing = true; |
| 126 | } |
|
| 127 | _; |
|
| 128 | 10× | if (isTopLevelCall) { |
| 129 | 10× | $._initializing = false; |
| 130 | 22× | emit Initialized(1); |
| 131 | } |
|
| 132 | } |
|
| 133 | ||
| 134 | /** |
|
| 135 | * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the |
|
| 136 | * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be |
|
| 137 | * used to initialize parent contracts. |
|
| 138 | * |
|
| 139 | * A reinitializer may be used after the original initialization step. This is essential to configure modules that |
|
| 140 | * are added through upgrades and that require initialization. |
|
| 141 | * |
|
| 142 | * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` |
|
| 143 | * cannot be nested. If one is invoked in the context of another, execution will revert. |
|
| 144 | * |
|
| 145 | * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in |
|
| 146 | * a contract, executing them in the right order is up to the developer or operator. |
|
| 147 | * |
|
| 148 | * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. |
|
| 149 | * |
|
| 150 | * Emits an {Initialized} event. |
|
| 151 | */ |
|
| 152 | modifier reinitializer(uint64 version) { |
|
| 153 | // solhint-disable-next-line var-name-mixedcase |
|
| 154 | InitializableStorage storage $ = _getInitializableStorage(); |
|
| 155 | ||
| 156 | if ($._initializing || $._initialized >= version) { |
|
| 157 | revert InvalidInitialization(); |
|
| 158 | } |
|
| 159 | $._initialized = version; |
|
| 160 | $._initializing = true; |
|
| 161 | _; |
|
| 162 | $._initializing = false; |
|
| 163 | emit Initialized(version); |
|
| 164 | } |
|
| 165 | ||
| 166 | /** |
|
| 167 | * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the |
|
| 168 | * {initializer} and {reinitializer} modifiers, directly or indirectly. |
|
| 169 | */ |
|
| 170 | modifier onlyInitializing() { |
|
| 171 | 30× | _checkInitializing(); |
| 172 | _; |
|
| 173 | } |
|
| 174 | ||
| 175 | /** |
|
| 176 | * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. |
|
| 177 | */ |
|
| 178 | 2× | function _checkInitializing() internal view virtual { |
| 179 | 12× | if (!_isInitializing()) { |
| 180 | 0 | revert NotInitializing(); |
| 181 | } |
|
| 182 | } |
|
| 183 | ||
| 184 | /** |
|
| 185 | * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. |
|
| 186 | * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized |
|
| 187 | * to any version. It is recommended to use this to lock implementation contracts that are designed to be called |
|
| 188 | * through proxies. |
|
| 189 | * |
|
| 190 | * Emits an {Initialized} event the first time it is successfully executed. |
|
| 191 | */ |
|
| 192 | 6× | function _disableInitializers() internal virtual { |
| 193 | // solhint-disable-next-line var-name-mixedcase |
|
| 194 | InitializableStorage storage $ = _getInitializableStorage(); |
|
| 195 | ||
| 196 | 22× | if ($._initializing) { |
| 197 | 0 | revert InvalidInitialization(); |
| 198 | } |
|
| 199 | 18× | if ($._initialized != type(uint64).max) { |
| 200 | 16× | $._initialized = type(uint64).max; |
| 201 | 22× | emit Initialized(type(uint64).max); |
| 202 | } |
|
| 203 | } |
|
| 204 | ||
| 205 | /** |
|
| 206 | * @dev Returns the highest version that has been initialized. See {reinitializer}. |
|
| 207 | */ |
|
| 208 | function _getInitializedVersion() internal view returns (uint64) { |
|
| 209 | return _getInitializableStorage()._initialized; |
|
| 210 | } |
|
| 211 | ||
| 212 | /** |
|
| 213 | * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. |
|
| 214 | */ |
|
| 215 | 6× | function _isInitializing() internal view returns (bool) { |
| 216 | 22× | return _getInitializableStorage()._initializing; |
| 217 | } |
|
| 218 | ||
| 219 | /** |
|
| 220 | * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location. |
|
| 221 | * |
|
| 222 | * NOTE: Consider following the ERC-7201 formula to derive storage locations. |
|
| 223 | */ |
|
| 224 | 2× | 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 | 6× | function _getInitializableStorage() private pure returns (InitializableStorage storage $) { |
| 233 | 2× | bytes32 slot = _initializableStorageSlot(); |
| 234 | assembly { |
|
| 235 | $.slot := slot |
|
| 236 | } |
|
| 237 | } |
|
| 238 | } |
85%
lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
Lines covered: 57 / 67 (85%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
|
| 8 | import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol"; |
|
| 9 | import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; |
|
| 10 | import {Initializable} from "../../proxy/utils/Initializable.sol"; |
|
| 11 | ||
| 12 | /** |
|
| 13 | * @dev Implementation of the {IERC20} interface. |
|
| 14 | * |
|
| 15 | * This implementation is agnostic to the way tokens are created. This means |
|
| 16 | * that a supply mechanism has to be added in a derived contract using {_mint}. |
|
| 17 | * |
|
| 18 | * TIP: For a detailed writeup see our guide |
|
| 19 | * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How |
|
| 20 | * to implement supply mechanisms]. |
|
| 21 | * |
|
| 22 | * The default value of {decimals} is 18. To change this, you should override |
|
| 23 | * this function so it returns a different value. |
|
| 24 | * |
|
| 25 | * We have followed general OpenZeppelin Contracts guidelines: functions revert |
|
| 26 | * instead returning `false` on failure. This behavior is nonetheless |
|
| 27 | * conventional and does not conflict with the expectations of ERC-20 |
|
| 28 | * applications. |
|
| 29 | */ |
|
| 30 | abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors { |
|
| 31 | /// @custom:storage-location erc7201:openzeppelin.storage.ERC20 |
|
| 32 | struct ERC20Storage { |
|
| 33 | mapping(address account => uint256) _balances; |
|
| 34 | ||
| 35 | mapping(address account => mapping(address spender => uint256)) _allowances; |
|
| 36 | ||
| 37 | uint256 _totalSupply; |
|
| 38 | ||
| 39 | string _name; |
|
| 40 | string _symbol; |
|
| 41 | } |
|
| 42 | ||
| 43 | // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff)) |
|
| 44 | bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00; |
|
| 45 | ||
| 46 | function _getERC20Storage() private pure returns (ERC20Storage storage $) { |
|
| 47 | assembly { |
|
| 48 | 0 | $.slot := ERC20StorageLocation |
| 49 | } |
|
| 50 | } |
|
| 51 | ||
| 52 | /** |
|
| 53 | * @dev Sets the values for {name} and {symbol}. |
|
| 54 | * |
|
| 55 | * Both values are immutable: they can only be set once during construction. |
|
| 56 | */ |
|
| 57 | 10× | function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { |
| 58 | 8× | __ERC20_init_unchained(name_, symbol_); |
| 59 | } |
|
| 60 | ||
| 61 | 1× | function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { |
| 62 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 63 | 7× | $._name = name_; |
| 64 | 8× | $._symbol = symbol_; |
| 65 | } |
|
| 66 | ||
| 67 | /** |
|
| 68 | * @dev Returns the name of the token. |
|
| 69 | */ |
|
| 70 | 0 | function name() public view virtual returns (string memory) { |
| 71 | 0 | ERC20Storage storage $ = _getERC20Storage(); |
| 72 | 0 | return $._name; |
| 73 | } |
|
| 74 | ||
| 75 | /** |
|
| 76 | * @dev Returns the symbol of the token, usually a shorter version of the |
|
| 77 | * name. |
|
| 78 | */ |
|
| 79 | 0 | function symbol() public view virtual returns (string memory) { |
| 80 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 81 | 0 | return $._symbol; |
| 82 | } |
|
| 83 | ||
| 84 | /** |
|
| 85 | * @dev Returns the number of decimals used to get its user representation. |
|
| 86 | * For example, if `decimals` equals `2`, a balance of `505` tokens should |
|
| 87 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). |
|
| 88 | * |
|
| 89 | * Tokens usually opt for a value of 18, imitating the relationship between |
|
| 90 | * Ether and Wei. This is the default value returned by this function, unless |
|
| 91 | * it's overridden. |
|
| 92 | * |
|
| 93 | * NOTE: This information is only used for _display_ purposes: it in |
|
| 94 | * no way affects any of the arithmetic of the contract, including |
|
| 95 | * {IERC20-balanceOf} and {IERC20-transfer}. |
|
| 96 | */ |
|
| 97 | function decimals() public view virtual returns (uint8) { |
|
| 98 | return 18; |
|
| 99 | } |
|
| 100 | ||
| 101 | /// @inheritdoc IERC20 |
|
| 102 | 7× | function totalSupply() public view virtual returns (uint256) { |
| 103 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 104 | 7× | return $._totalSupply; |
| 105 | } |
|
| 106 | ||
| 107 | /// @inheritdoc IERC20 |
|
| 108 | 39× | function balanceOf(address account) public view virtual returns (uint256) { |
| 109 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 110 | 33× | return $._balances[account]; |
| 111 | } |
|
| 112 | ||
| 113 | /** |
|
| 114 | * @dev See {IERC20-transfer}. |
|
| 115 | * |
|
| 116 | * Requirements: |
|
| 117 | * |
|
| 118 | * - `to` cannot be the zero address. |
|
| 119 | * - the caller must have a balance of at least `value`. |
|
| 120 | */ |
|
| 121 | 48× | function transfer(address to, uint256 value) public virtual returns (bool) { |
| 122 | address owner = _msgSender(); |
|
| 123 | 20× | _transfer(owner, to, value); |
| 124 | return true; |
|
| 125 | } |
|
| 126 | ||
| 127 | /// @inheritdoc IERC20 |
|
| 128 | 12× | function allowance(address owner, address spender) public view virtual returns (uint256) { |
| 129 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 130 | 112× | return $._allowances[owner][spender]; |
| 131 | } |
|
| 132 | ||
| 133 | /** |
|
| 134 | * @dev See {IERC20-approve}. |
|
| 135 | * |
|
| 136 | * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on |
|
| 137 | * `transferFrom`. This is semantically equivalent to an infinite approval. |
|
| 138 | * |
|
| 139 | * Requirements: |
|
| 140 | * |
|
| 141 | * - `spender` cannot be the zero address. |
|
| 142 | */ |
|
| 143 | 20× | function approve(address spender, uint256 value) public virtual returns (bool) { |
| 144 | address owner = _msgSender(); |
|
| 145 | 9× | _approve(owner, spender, value); |
| 146 | 8× | return true; |
| 147 | } |
|
| 148 | ||
| 149 | /** |
|
| 150 | * @dev See {IERC20-transferFrom}. |
|
| 151 | * |
|
| 152 | * Skips emitting an {Approval} event indicating an allowance update. This is not |
|
| 153 | * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. |
|
| 154 | * |
|
| 155 | * NOTE: Does not update the allowance if the current allowance |
|
| 156 | * is the maximum `uint256`. |
|
| 157 | * |
|
| 158 | * Requirements: |
|
| 159 | * |
|
| 160 | * - `from` and `to` cannot be the zero address. |
|
| 161 | * - `from` must have a balance of at least `value`. |
|
| 162 | * - the caller must have allowance for ``from``'s tokens of at least |
|
| 163 | * `value`. |
|
| 164 | */ |
|
| 165 | 56× | function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { |
| 166 | address spender = _msgSender(); |
|
| 167 | 24× | _spendAllowance(from, spender, value); |
| 168 | 28× | _transfer(from, to, value); |
| 169 | 8× | 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 | 4× | function _transfer(address from, address to, uint256 value) internal { |
| 183 | 20× | if (from == address(0)) { |
| 184 | 0 | revert ERC20InvalidSender(address(0)); |
| 185 | } |
|
| 186 | 20× | if (to == address(0)) { |
| 187 | 9× | revert ERC20InvalidReceiver(address(0)); |
| 188 | } |
|
| 189 | 24× | _update(from, to, value); |
| 190 | } |
|
| 191 | ||
| 192 | /** |
|
| 193 | * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` |
|
| 194 | * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding |
|
| 195 | * this function. |
|
| 196 | * |
|
| 197 | * Emits a {Transfer} event. |
|
| 198 | */ |
|
| 199 | 24× | function _update(address from, address to, uint256 value) internal virtual { |
| 200 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 201 | 30× | if (from == address(0)) { |
| 202 | // Overflow check required: The rest of the code assumes that totalSupply never overflows |
|
| 203 | 34× | $._totalSupply += value; |
| 204 | } else { |
|
| 205 | 56× | uint256 fromBalance = $._balances[from]; |
| 206 | 28× | if (fromBalance < value) { |
| 207 | 24× | revert ERC20InsufficientBalance(from, fromBalance, value); |
| 208 | } |
|
| 209 | unchecked { |
|
| 210 | // Overflow not possible: value <= fromBalance <= totalSupply. |
|
| 211 | 76× | $._balances[from] = fromBalance - value; |
| 212 | } |
|
| 213 | } |
|
| 214 | ||
| 215 | 26× | if (to == address(0)) { |
| 216 | unchecked { |
|
| 217 | // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. |
|
| 218 | 10× | $._totalSupply -= value; |
| 219 | } |
|
| 220 | } else { |
|
| 221 | unchecked { |
|
| 222 | // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. |
|
| 223 | 76× | $._balances[to] += value; |
| 224 | } |
|
| 225 | } |
|
| 226 | ||
| 227 | 72× | emit Transfer(from, to, value); |
| 228 | } |
|
| 229 | ||
| 230 | /** |
|
| 231 | * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). |
|
| 232 | * Relies on the `_update` mechanism |
|
| 233 | * |
|
| 234 | * Emits a {Transfer} event with `from` set to the zero address. |
|
| 235 | * |
|
| 236 | * NOTE: This function is not virtual, {_update} should be overridden instead. |
|
| 237 | */ |
|
| 238 | 2× | function _mint(address account, uint256 value) internal { |
| 239 | 10× | if (account == address(0)) { |
| 240 | 0 | revert ERC20InvalidReceiver(address(0)); |
| 241 | } |
|
| 242 | 12× | _update(address(0), account, value); |
| 243 | } |
|
| 244 | ||
| 245 | /** |
|
| 246 | * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. |
|
| 247 | * Relies on the `_update` mechanism. |
|
| 248 | * |
|
| 249 | * Emits a {Transfer} event with `to` set to the zero address. |
|
| 250 | * |
|
| 251 | * NOTE: This function is not virtual, {_update} should be overridden instead |
|
| 252 | */ |
|
| 253 | 1× | function _burn(address account, uint256 value) internal { |
| 254 | 5× | if (account == address(0)) { |
| 255 | 0 | revert ERC20InvalidSender(address(0)); |
| 256 | } |
|
| 257 | 6× | _update(account, address(0), value); |
| 258 | } |
|
| 259 | ||
| 260 | /** |
|
| 261 | * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens. |
|
| 262 | * |
|
| 263 | * This internal function is equivalent to `approve`, and can be used to |
|
| 264 | * e.g. set automatic allowances for certain subsystems, etc. |
|
| 265 | * |
|
| 266 | * Emits an {Approval} event. |
|
| 267 | * |
|
| 268 | * Requirements: |
|
| 269 | * |
|
| 270 | * - `owner` cannot be the zero address. |
|
| 271 | * - `spender` cannot be the zero address. |
|
| 272 | * |
|
| 273 | * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. |
|
| 274 | */ |
|
| 275 | 17× | function _approve(address owner, address spender, uint256 value) internal { |
| 276 | 11× | _approve(owner, spender, value, true); |
| 277 | } |
|
| 278 | ||
| 279 | /** |
|
| 280 | * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. |
|
| 281 | * |
|
| 282 | * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by |
|
| 283 | * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any |
|
| 284 | * `Approval` event during `transferFrom` operations. |
|
| 285 | * |
|
| 286 | * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to |
|
| 287 | * true using the following override: |
|
| 288 | * |
|
| 289 | * ```solidity |
|
| 290 | * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { |
|
| 291 | * super._approve(owner, spender, value, true); |
|
| 292 | * } |
|
| 293 | * ``` |
|
| 294 | * |
|
| 295 | * Requirements are the same as {_approve}. |
|
| 296 | */ |
|
| 297 | 28× | function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { |
| 298 | ERC20Storage storage $ = _getERC20Storage(); |
|
| 299 | 20× | if (owner == address(0)) { |
| 300 | 0 | revert ERC20InvalidApprover(address(0)); |
| 301 | } |
|
| 302 | 20× | if (spender == address(0)) { |
| 303 | 9× | revert ERC20InvalidSpender(address(0)); |
| 304 | } |
|
| 305 | 120× | $._allowances[owner][spender] = value; |
| 306 | 20× | if (emitEvent) { |
| 307 | 18× | emit Approval(owner, spender, value); |
| 308 | } |
|
| 309 | } |
|
| 310 | ||
| 311 | /** |
|
| 312 | * @dev Updates `owner`'s allowance for `spender` based on spent `value`. |
|
| 313 | * |
|
| 314 | * Does not update the allowance value in case of infinite allowance. |
|
| 315 | * Revert if not enough allowance is available. |
|
| 316 | * |
|
| 317 | * Does not emit an {Approval} event. |
|
| 318 | */ |
|
| 319 | 24× | function _spendAllowance(address owner, address spender, uint256 value) internal virtual { |
| 320 | 36× | uint256 currentAllowance = allowance(owner, spender); |
| 321 | 20× | if (currentAllowance < type(uint256).max) { |
| 322 | 28× | if (currentAllowance < value) { |
| 323 | 24× | revert ERC20InsufficientAllowance(spender, currentAllowance, value); |
| 324 | } |
|
| 325 | unchecked { |
|
| 326 | 40× | _approve(owner, spender, currentAllowance - value, false); |
| 327 | } |
|
| 328 | } |
|
| 329 | } |
|
| 330 | } |
100%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
Lines covered: 1 / 1 (100%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | import {Initializable} from "../proxy/utils/Initializable.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @dev Provides information about the current execution context, including the |
|
| 9 | * sender of the transaction and its data. While these are generally available |
|
| 10 | * via msg.sender and msg.data, they should not be accessed in such a direct |
|
| 11 | * manner, since when dealing with meta-transactions the account sending and |
|
| 12 | * paying for execution may not be the actual sender (as far as an application |
|
| 13 | * is concerned). |
|
| 14 | * |
|
| 15 | * This contract is only required for intermediate, library-like contracts. |
|
| 16 | */ |
|
| 17 | abstract contract ContextUpgradeable is Initializable { |
|
| 18 | function __Context_init() internal onlyInitializing { |
|
| 19 | } |
|
| 20 | ||
| 21 | function __Context_init_unchained() internal onlyInitializing { |
|
| 22 | } |
|
| 23 | function _msgSender() internal view virtual returns (address) { |
|
| 24 | 18× | return msg.sender; |
| 25 | } |
|
| 26 | ||
| 27 | function _msgData() internal view virtual returns (bytes calldata) { |
|
| 28 | return msg.data; |
|
| 29 | } |
|
| 30 | ||
| 31 | function _contextSuffixLength() internal view virtual returns (uint256) { |
|
| 32 | return 0; |
|
| 33 | } |
|
| 34 | } |
92%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
Lines covered: 12 / 13 (92%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | import {Initializable} from "../proxy/utils/Initializable.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @dev Contract module that helps prevent reentrant calls to a function. |
|
| 9 | * |
|
| 10 | * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier |
|
| 11 | * available, which can be applied to functions to make sure there are no nested |
|
| 12 | * (reentrant) calls to them. |
|
| 13 | * |
|
| 14 | * Note that because there is a single `nonReentrant` guard, functions marked as |
|
| 15 | * `nonReentrant` may not call one another. This can be worked around by making |
|
| 16 | * those functions `private`, and then adding `external` `nonReentrant` entry |
|
| 17 | * points to them. |
|
| 18 | * |
|
| 19 | * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at, |
|
| 20 | * consider using {ReentrancyGuardTransient} instead. |
|
| 21 | * |
|
| 22 | * TIP: If you would like to learn more about reentrancy and alternative ways |
|
| 23 | * to protect against it, check out our blog post |
|
| 24 | * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. |
|
| 25 | */ |
|
| 26 | abstract contract ReentrancyGuardUpgradeable is Initializable { |
|
| 27 | // Booleans are more expensive than uint256 or any type that takes up a full |
|
| 28 | // word because each write operation emits an extra SLOAD to first read the |
|
| 29 | // slot's contents, replace the bits taken up by the boolean, and then write |
|
| 30 | // back. This is the compiler's defense against contract upgrades and |
|
| 31 | // pointer aliasing, and it cannot be disabled. |
|
| 32 | ||
| 33 | // The values being non-zero value makes deployment a bit more expensive, |
|
| 34 | // but in exchange the refund on every call to nonReentrant will be lower in |
|
| 35 | // amount. Since refunds are capped to a percentage of the total |
|
| 36 | // transaction's gas, it is best to keep them low in cases like this one, to |
|
| 37 | // increase the likelihood of the full refund coming into effect. |
|
| 38 | 9× | uint256 private constant NOT_ENTERED = 1; |
| 39 | 4× | 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 | 3× | function __ReentrancyGuard_init() internal onlyInitializing { |
| 61 | 7× | __ReentrancyGuard_init_unchained(); |
| 62 | } |
|
| 63 | ||
| 64 | 2× | function __ReentrancyGuard_init_unchained() internal onlyInitializing { |
| 65 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
|
| 66 | $._status = NOT_ENTERED; |
|
| 67 | } |
|
| 68 | ||
| 69 | /** |
|
| 70 | * @dev Prevents a contract from calling itself, directly or indirectly. |
|
| 71 | * Calling a `nonReentrant` function from another `nonReentrant` |
|
| 72 | * function is not supported. It is possible to prevent this from happening |
|
| 73 | * by making the `nonReentrant` function external, and making it call a |
|
| 74 | * `private` function that does the actual work. |
|
| 75 | */ |
|
| 76 | modifier nonReentrant() { |
|
| 77 | 48× | _nonReentrantBefore(); |
| 78 | _; |
|
| 79 | 12× | _nonReentrantAfter(); |
| 80 | } |
|
| 81 | ||
| 82 | 8× | function _nonReentrantBefore() private { |
| 83 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
|
| 84 | // On the first call to nonReentrant, _status will be NOT_ENTERED |
|
| 85 | 24× | if ($._status == ENTERED) { |
| 86 | 0 | revert ReentrancyGuardReentrantCall(); |
| 87 | } |
|
| 88 | ||
| 89 | // Any calls to nonReentrant after this point will fail |
|
| 90 | 8× | $._status = ENTERED; |
| 91 | } |
|
| 92 | ||
| 93 | 11× | function _nonReentrantAfter() private { |
| 94 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
|
| 95 | // By storing the original value once again, a refund is triggered (see |
|
| 96 | // https://eips.ethereum.org/EIPS/eip-2200) |
|
| 97 | 9× | $._status = NOT_ENTERED; |
| 98 | } |
|
| 99 | ||
| 100 | /** |
|
| 101 | * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a |
|
| 102 | * `nonReentrant` function in the call stack. |
|
| 103 | */ |
|
| 104 | function _reentrancyGuardEntered() internal view returns (bool) { |
|
| 105 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
|
| 106 | return $._status == ENTERED; |
|
| 107 | } |
|
| 108 | } |
17%
lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol
Lines covered: 8 / 47 (17%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; |
|
| 7 | import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol"; |
|
| 8 | import {Initializable} from "../../proxy/utils/Initializable.sol"; |
|
| 9 | ||
| 10 | /** |
|
| 11 | * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data. |
|
| 12 | * |
|
| 13 | * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose |
|
| 14 | * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract |
|
| 15 | * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to |
|
| 16 | * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. |
|
| 17 | * |
|
| 18 | * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding |
|
| 19 | * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA |
|
| 20 | * ({_hashTypedDataV4}). |
|
| 21 | * |
|
| 22 | * The implementation of the domain separator was designed to be as efficient as possible while still properly updating |
|
| 23 | * the chain id to protect against replay attacks on an eventual fork of the chain. |
|
| 24 | * |
|
| 25 | * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method |
|
| 26 | * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. |
|
| 27 | * |
|
| 28 | * NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator |
|
| 29 | * each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage. |
|
| 30 | */ |
|
| 31 | abstract contract EIP712Upgradeable is Initializable, IERC5267 { |
|
| 32 | bytes32 private constant TYPE_HASH = |
|
| 33 | 0 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); |
| 34 | ||
| 35 | /// @custom:storage-location erc7201:openzeppelin.storage.EIP712 |
|
| 36 | struct EIP712Storage { |
|
| 37 | /// @custom:oz-renamed-from _HASHED_NAME |
|
| 38 | bytes32 _hashedName; |
|
| 39 | /// @custom:oz-renamed-from _HASHED_VERSION |
|
| 40 | bytes32 _hashedVersion; |
|
| 41 | ||
| 42 | string _name; |
|
| 43 | string _version; |
|
| 44 | } |
|
| 45 | ||
| 46 | // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff)) |
|
| 47 | bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100; |
|
| 48 | ||
| 49 | 0 | function _getEIP712Storage() private pure returns (EIP712Storage storage $) { |
| 50 | assembly { |
|
| 51 | 0 | $.slot := EIP712StorageLocation |
| 52 | } |
|
| 53 | } |
|
| 54 | ||
| 55 | /** |
|
| 56 | * @dev Initializes the domain separator and parameter caches. |
|
| 57 | * |
|
| 58 | * The meaning of `name` and `version` is specified in |
|
| 59 | * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]: |
|
| 60 | * |
|
| 61 | * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. |
|
| 62 | * - `version`: the current major version of the signing domain. |
|
| 63 | * |
|
| 64 | * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart |
|
| 65 | * contract upgrade]. |
|
| 66 | */ |
|
| 67 | 1× | function __EIP712_init(string memory name, string memory version) internal onlyInitializing { |
| 68 | 5× | __EIP712_init_unchained(name, version); |
| 69 | } |
|
| 70 | ||
| 71 | 2× | function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing { |
| 72 | EIP712Storage storage $ = _getEIP712Storage(); |
|
| 73 | 7× | $._name = name; |
| 74 | 9× | $._version = version; |
| 75 | ||
| 76 | // Reset prior values in storage if upgrading |
|
| 77 | 4× | $._hashedName = 0; |
| 78 | 5× | $._hashedVersion = 0; |
| 79 | } |
|
| 80 | ||
| 81 | /** |
|
| 82 | * @dev Returns the domain separator for the current chain. |
|
| 83 | */ |
|
| 84 | 0 | function _domainSeparatorV4() internal view returns (bytes32) { |
| 85 | 0 | return _buildDomainSeparator(); |
| 86 | } |
|
| 87 | ||
| 88 | 0 | function _buildDomainSeparator() private view returns (bytes32) { |
| 89 | 0 | return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this))); |
| 90 | } |
|
| 91 | ||
| 92 | /** |
|
| 93 | * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this |
|
| 94 | * function returns the hash of the fully encoded EIP712 message for this domain. |
|
| 95 | * |
|
| 96 | * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: |
|
| 97 | * |
|
| 98 | * ```solidity |
|
| 99 | * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( |
|
| 100 | * keccak256("Mail(address to,string contents)"), |
|
| 101 | * mailTo, |
|
| 102 | * keccak256(bytes(mailContents)) |
|
| 103 | * ))); |
|
| 104 | * address signer = ECDSA.recover(digest, signature); |
|
| 105 | * ``` |
|
| 106 | */ |
|
| 107 | 0 | function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { |
| 108 | 0 | return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); |
| 109 | } |
|
| 110 | ||
| 111 | /// @inheritdoc IERC5267 |
|
| 112 | 0 | function eip712Domain() |
| 113 | public |
|
| 114 | view |
|
| 115 | virtual |
|
| 116 | returns ( |
|
| 117 | 0 | bytes1 fields, |
| 118 | 0 | string memory name, |
| 119 | string memory version, |
|
| 120 | uint256 chainId, |
|
| 121 | address verifyingContract, |
|
| 122 | bytes32 salt, |
|
| 123 | uint256[] memory extensions |
|
| 124 | ) |
|
| 125 | { |
|
| 126 | 0 | EIP712Storage storage $ = _getEIP712Storage(); |
| 127 | // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized |
|
| 128 | // and the EIP712 domain is not reliable, as it will be missing name and version. |
|
| 129 | 24× | require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized"); |
| 130 | ||
| 131 | 0 | return ( |
| 132 | hex"0f", // 01111 |
|
| 133 | 0 | _EIP712Name(), |
| 134 | 0 | _EIP712Version(), |
| 135 | 0 | block.chainid, |
| 136 | 0 | address(this), |
| 137 | 0 | bytes32(0), |
| 138 | 0 | new uint256[](0) |
| 139 | ); |
|
| 140 | } |
|
| 141 | ||
| 142 | /** |
|
| 143 | * @dev The name parameter for the EIP712 domain. |
|
| 144 | * |
|
| 145 | * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs |
|
| 146 | * are a concern. |
|
| 147 | */ |
|
| 148 | 0 | function _EIP712Name() internal view virtual returns (string memory) { |
| 149 | EIP712Storage storage $ = _getEIP712Storage(); |
|
| 150 | 0 | return $._name; |
| 151 | } |
|
| 152 | ||
| 153 | /** |
|
| 154 | * @dev The version parameter for the EIP712 domain. |
|
| 155 | * |
|
| 156 | * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs |
|
| 157 | * are a concern. |
|
| 158 | */ |
|
| 159 | 0 | function _EIP712Version() internal view virtual returns (string memory) { |
| 160 | 0 | EIP712Storage storage $ = _getEIP712Storage(); |
| 161 | return $._version; |
|
| 162 | } |
|
| 163 | ||
| 164 | /** |
|
| 165 | * @dev The hash of the name parameter for the EIP712 domain. |
|
| 166 | * |
|
| 167 | * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead. |
|
| 168 | */ |
|
| 169 | 0 | function _EIP712NameHash() internal view returns (bytes32) { |
| 170 | EIP712Storage storage $ = _getEIP712Storage(); |
|
| 171 | 0 | string memory name = _EIP712Name(); |
| 172 | 0 | if (bytes(name).length > 0) { |
| 173 | 0 | return keccak256(bytes(name)); |
| 174 | } else { |
|
| 175 | // If the name is empty, the contract may have been upgraded without initializing the new storage. |
|
| 176 | // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design. |
|
| 177 | 0 | bytes32 hashedName = $._hashedName; |
| 178 | 0 | if (hashedName != 0) { |
| 179 | 0 | return hashedName; |
| 180 | } else { |
|
| 181 | 0 | return keccak256(""); |
| 182 | } |
|
| 183 | } |
|
| 184 | } |
|
| 185 | ||
| 186 | /** |
|
| 187 | * @dev The hash of the version parameter for the EIP712 domain. |
|
| 188 | * |
|
| 189 | * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. |
|
| 190 | */ |
|
| 191 | 0 | function _EIP712VersionHash() internal view returns (bytes32) { |
| 192 | EIP712Storage storage $ = _getEIP712Storage(); |
|
| 193 | 0 | string memory version = _EIP712Version(); |
| 194 | 0 | if (bytes(version).length > 0) { |
| 195 | 0 | return keccak256(bytes(version)); |
| 196 | } else { |
|
| 197 | // If the version is empty, the contract may have been upgraded without initializing the new storage. |
|
| 198 | // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design. |
|
| 199 | 0 | bytes32 hashedVersion = $._hashedVersion; |
| 200 | 0 | if (hashedVersion != 0) { |
| 201 | 0 | return hashedVersion; |
| 202 | } else { |
|
| 203 | return keccak256(""); |
|
| 204 | } |
|
| 205 | } |
|
| 206 | } |
|
| 207 | } |
91%
lib/setup-helpers/lib/chimera/src/CryticAsserts.sol
Lines covered: 21 / 23 (91%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {Asserts} from "./Asserts.sol"; |
|
| 5 | ||
| 6 | 0 | contract CryticAsserts is Asserts { |
| 7 | event Log(string); |
|
| 8 | ||
| 9 | 1× | function gt(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 10 | 5× | if (!(a > b)) { |
| 11 | 0 | emit Log(reason); |
| 12 | assert(false); |
|
| 13 | } |
|
| 14 | } |
|
| 15 | ||
| 16 | 2× | function gte(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 17 | 12× | if (!(a >= b)) { |
| 18 | 16× | emit Log(reason); |
| 19 | assert(false); |
|
| 20 | } |
|
| 21 | } |
|
| 22 | ||
| 23 | function lt(uint256 a, uint256 b, string memory reason) internal virtual override { |
|
| 24 | if (!(a < b)) { |
|
| 25 | emit Log(reason); |
|
| 26 | assert(false); |
|
| 27 | } |
|
| 28 | } |
|
| 29 | ||
| 30 | 2× | function lte(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 31 | 12× | if (!(a <= b)) { |
| 32 | 8× | emit Log(reason); |
| 33 | assert(false); |
|
| 34 | } |
|
| 35 | } |
|
| 36 | ||
| 37 | 2× | function eq(uint256 a, uint256 b, string memory reason) internal virtual override { |
| 38 | 10× | if (!(a == b)) { |
| 39 | 24× | emit Log(reason); |
| 40 | 6× | assert(false); |
| 41 | } |
|
| 42 | } |
|
| 43 | ||
| 44 | 2× | function t(bool b, string memory reason) internal virtual override { |
| 45 | 6× | if (!b) { |
| 46 | 16× | emit Log(reason); |
| 47 | 3× | assert(false); |
| 48 | } |
|
| 49 | } |
|
| 50 | ||
| 51 | 8× | function between(uint256 value, uint256 low, uint256 high) internal virtual override returns (uint256) { |
| 52 | 30× | if (value < low || value > high) { |
| 53 | 52× | uint256 ans = low + (value % (high - low + 1)); |
| 54 | 6× | return ans; |
| 55 | } |
|
| 56 | 4× | return value; |
| 57 | } |
|
| 58 | ||
| 59 | function between(int256 value, int256 low, int256 high) internal virtual override returns (int256) { |
|
| 60 | if (value < low || value > high) { |
|
| 61 | int256 range = high - low + 1; |
|
| 62 | int256 clamped = (value - low) % (range); |
|
| 63 | if (clamped < 0) clamped += range; |
|
| 64 | int256 ans = low + clamped; |
|
| 65 | return ans; |
|
| 66 | } |
|
| 67 | return value; |
|
| 68 | } |
|
| 69 | ||
| 70 | function precondition(bool p) internal virtual override { |
|
| 71 | require(p); |
|
| 72 | } |
|
| 73 | } |
100%
lib/setup-helpers/lib/chimera/src/Hevm.sol
Lines covered: 1 / 1 (100%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // slither-disable-start shadowing-local |
|
| 5 | ||
| 6 | interface IHevm { |
|
| 7 | // Set block.timestamp to newTimestamp |
|
| 8 | function warp(uint256 newTimestamp) external; |
|
| 9 | ||
| 10 | // Set block.number to newNumber |
|
| 11 | function roll(uint256 newNumber) external; |
|
| 12 | ||
| 13 | // Add the condition b to the assumption base for the current branch |
|
| 14 | // This function is almost identical to require |
|
| 15 | function assume(bool b) external; |
|
| 16 | ||
| 17 | // Sets the eth balance of usr to amt |
|
| 18 | function deal(address usr, uint256 amt) external; |
|
| 19 | ||
| 20 | // Loads a storage slot from an address |
|
| 21 | function load(address where, bytes32 slot) external returns (bytes32); |
|
| 22 | ||
| 23 | // Stores a value to an address' storage slot |
|
| 24 | function store(address where, bytes32 slot, bytes32 value) external; |
|
| 25 | ||
| 26 | // Signs data (privateKey, digest) => (v, r, s) |
|
| 27 | function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); |
|
| 28 | ||
| 29 | // Gets address for a given private key |
|
| 30 | function addr(uint256 privateKey) external returns (address addr); |
|
| 31 | ||
| 32 | // Performs a foreign function call via terminal |
|
| 33 | function ffi(string[] calldata inputs) external returns (bytes memory result); |
|
| 34 | ||
| 35 | // Performs the next smart contract call with specified `msg.sender` |
|
| 36 | function prank(address newSender) external; |
|
| 37 | ||
| 38 | // Creates a new fork with the given endpoint and the latest block and returns the identifier of the fork |
|
| 39 | function createFork(string calldata urlOrAlias) external returns (uint256); |
|
| 40 | ||
| 41 | // Takes a fork identifier created by createFork and sets the corresponding forked state as active |
|
| 42 | function selectFork(uint256 forkId) external; |
|
| 43 | ||
| 44 | // Returns the identifier of the current fork |
|
| 45 | function activeFork() external returns (uint256); |
|
| 46 | ||
| 47 | // Labels the address in traces |
|
| 48 | function label(address addr, string calldata label) external; |
|
| 49 | ||
| 50 | /// Sets an address' code. |
|
| 51 | function etch(address target, bytes calldata newRuntimeBytecode) external; |
|
| 52 | } |
|
| 53 | ||
| 54 | 21× | IHevm constant vm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); |
| 55 | ||
| 56 | // slither-disable-end shadowing-local |
86%
lib/setup-helpers/src/ActorManager.sol
Lines covered: 13 / 15 (86%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
|
| 5 | import {vm} from "@chimera/Hevm.sol"; |
|
| 6 | import {EnumerableSet} from "./EnumerableSet.sol"; |
|
| 7 | ||
| 8 | /// @dev This is the source of truth for the actors being used in the test |
|
| 9 | /// @notice No actors should be used in the suite without being added here first |
|
| 10 | abstract contract ActorManager { |
|
| 11 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 12 | ||
| 13 | ///@notice The current actor being used |
|
| 14 | address private _actor; |
|
| 15 | ||
| 16 | ///@notice The list of all actors being used |
|
| 17 | EnumerableSet.AddressSet private _actors; |
|
| 18 | ||
| 19 | // If the current target is address(0) then it has not been setup yet and should revert |
|
| 20 | error ActorNotSetup(); |
|
| 21 | // Do not allow duplicates |
|
| 22 | error ActorExists(); |
|
| 23 | // If the actor does not exist |
|
| 24 | error ActorNotAdded(); |
|
| 25 | // Do not allow the default actor |
|
| 26 | error DefaultActor(); |
|
| 27 | ||
| 28 | /// @notice address(this) is the default actor |
|
| 29 | constructor() { |
|
| 30 | 5× | _actors.add(address(this)); |
| 31 | 8× | _actor = address(this); |
| 32 | } |
|
| 33 | ||
| 34 | /// @notice Returns the current active actor |
|
| 35 | 351× | function _getActor() internal view returns (address) { |
| 36 | 552× | return _actor; |
| 37 | } |
|
| 38 | ||
| 39 | /// @notice Returns all actors being used |
|
| 40 | 10× | function _getActors() internal view returns (address[] memory) { |
| 41 | 18× | return _actors.values(); |
| 42 | } |
|
| 43 | ||
| 44 | /// @notice Adds an actor to the list of actors |
|
| 45 | 3× | function _addActor(address target) internal { |
| 46 | 10× | if (_actors.contains(target)) { |
| 47 | 0 | revert ActorExists(); |
| 48 | } |
|
| 49 | ||
| 50 | 7× | if (target == address(this)) { |
| 51 | 0 | revert DefaultActor(); |
| 52 | } |
|
| 53 | ||
| 54 | 7× | _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 | 6× | function _switchActor(uint256 entropy) internal returns (address target) { |
| 77 | 14× | target = _actors.at(entropy); |
| 78 | 20× | _actor = target; |
| 79 | } |
|
| 80 | } |
93%
lib/setup-helpers/src/AssetManager.sol
Lines covered: 31 / 33 (93%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
|
| 5 | import {vm} from "@chimera/Hevm.sol"; |
|
| 6 | ||
| 7 | import {EnumerableSet} from "./EnumerableSet.sol"; |
|
| 8 | import {MockERC20} from "./MockERC20.sol"; |
|
| 9 | ||
| 10 | /// @dev Source of truth for the assets being used in the test |
|
| 11 | /// @notice No assets should be used in the suite without being added here first |
|
| 12 | abstract contract AssetManager { |
|
| 13 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 14 | ||
| 15 | /// @notice The current target for this set of variables |
|
| 16 | address private __asset; |
|
| 17 | ||
| 18 | /// @notice The list of all assets being used |
|
| 19 | EnumerableSet.AddressSet private _assets; |
|
| 20 | ||
| 21 | // If the current target is address(0) then it has not been setup yet and should revert |
|
| 22 | error NotSetup(); |
|
| 23 | // Do not allow duplicates |
|
| 24 | error Exists(); |
|
| 25 | // Enable only added assets |
|
| 26 | error NotAdded(); |
|
| 27 | ||
| 28 | /// @notice Returns the current active asset |
|
| 29 | 8× | function _getAsset() internal view returns (address) { |
| 30 | 12× | if (__asset == address(0)) { |
| 31 | 0 | revert NotSetup(); |
| 32 | } |
|
| 33 | ||
| 34 | 8× | return __asset; |
| 35 | } |
|
| 36 | ||
| 37 | /// @notice Returns all assets being used |
|
| 38 | 8× | function _getAssets() internal view returns (address[] memory) { |
| 39 | 14× | return _assets.values(); |
| 40 | } |
|
| 41 | ||
| 42 | /// @notice Creates a new asset and adds it to the list of assets |
|
| 43 | /// @param decimals The number of decimals for the asset |
|
| 44 | /// @return The address of the new asset |
|
| 45 | 6× | function _newAsset(uint8 decimals) internal returns (address) { |
| 46 | 53× | address asset_ = address(new MockERC20("Test Token", "TST", decimals)); // If names get confusing, concatenate the decimals to the name |
| 47 | 9× | _addAsset(asset_); |
| 48 | 22× | __asset = asset_; // sets the asset as the current asset |
| 49 | return asset_; |
|
| 50 | } |
|
| 51 | ||
| 52 | /// @notice Adds an asset to the list of assets |
|
| 53 | /// @param target The address of the asset to add |
|
| 54 | 2× | function _addAsset(address target) internal { |
| 55 | 20× | if (_assets.contains(target)) { |
| 56 | 0 | revert Exists(); |
| 57 | } |
|
| 58 | ||
| 59 | 10× | _assets.add(target); |
| 60 | } |
|
| 61 | ||
| 62 | /// @notice Removes an asset from the list of assets |
|
| 63 | /// @param target The address of the asset to remove |
|
| 64 | function _removeAsset(address target) internal { |
|
| 65 | if (!_assets.contains(target)) { |
|
| 66 | revert NotAdded(); |
|
| 67 | } |
|
| 68 | ||
| 69 | _assets.remove(target); |
|
| 70 | } |
|
| 71 | ||
| 72 | /// @notice Switches the current asset based on the entropy |
|
| 73 | /// NOTE: We revert if the entropy is greater than the number of actors, for Halmos compatibility |
|
| 74 | /// @param entropy The entropy to choose a random asset in the array for switching |
|
| 75 | 4× | function _switchAsset(uint256 entropy) internal { |
| 76 | 14× | address target = _assets.at(entropy); |
| 77 | 28× | __asset = target; |
| 78 | } |
|
| 79 | ||
| 80 | /// === Approve & Mint Asset === /// |
|
| 81 | ||
| 82 | /// @notice Mint initial balance and approve allowances for the active asset |
|
| 83 | /// @param actorsArray The array of actors to mint the asset to |
|
| 84 | /// @param approvalArray The array of addresses to approve the asset to |
|
| 85 | /// @param amount The amount of the asset to mint |
|
| 86 | 5× | function _finalizeAssetDeployment(address[] memory actorsArray, address[] memory approvalArray, uint256 amount) |
| 87 | internal |
|
| 88 | { |
|
| 89 | 6× | _mintAssetToAllActors(actorsArray, amount); |
| 90 | 15× | for (uint256 i; i < approvalArray.length; i++) { |
| 91 | 24× | _approveAssetToAddressForAllActors(actorsArray, approvalArray[i]); |
| 92 | } |
|
| 93 | } |
|
| 94 | ||
| 95 | /// @notice Mint the asset to all actors |
|
| 96 | /// @param actorsArray The array of actors to mint the asset to |
|
| 97 | /// @param amount The amount of the asset to mint |
|
| 98 | 1× | function _mintAssetToAllActors(address[] memory actorsArray, uint256 amount) private { |
| 99 | // mint all actors |
|
| 100 | 7× | address[] memory assets = _getAssets(); |
| 101 | 21× | for (uint256 i; i < assets.length; i++) { |
| 102 | 27× | for (uint256 j; j < actorsArray.length; j++) { |
| 103 | 114× | vm.prank(actorsArray[j]); |
| 104 | 75× | MockERC20(assets[i]).mint(actorsArray[j], amount); |
| 105 | } |
|
| 106 | } |
|
| 107 | } |
|
| 108 | ||
| 109 | /// @notice Approve the asset to all actors |
|
| 110 | /// @param actorsArray The array of actors to approve the asset from |
|
| 111 | /// @param addressToApprove The address to approve the asset to |
|
| 112 | 1× | function _approveAssetToAddressForAllActors(address[] memory actorsArray, address addressToApprove) private { |
| 113 | // approve to all actors |
|
| 114 | 7× | address[] memory assets = _getAssets(); |
| 115 | 13× | for (uint256 i; i < assets.length; i++) { |
| 116 | 14× | for (uint256 j; j < actorsArray.length; j++) { |
| 117 | 62× | vm.prank(actorsArray[j]); |
| 118 | 77× | MockERC20(assets[i]).approve(addressToApprove, type(uint256).max); |
| 119 | } |
|
| 120 | } |
|
| 121 | } |
|
| 122 | } |
92%
lib/setup-helpers/src/EnumerableSet.sol
Lines covered: 23 / 25 (92%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) |
|
| 3 | // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. |
|
| 4 | ||
| 5 | pragma solidity ^0.8.0; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @dev Library for managing |
|
| 9 | * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive |
|
| 10 | * types. |
|
| 11 | * |
|
| 12 | * Sets have the following properties: |
|
| 13 | * |
|
| 14 | * - Elements are added, removed, and checked for existence in constant time |
|
| 15 | * (O(1)). |
|
| 16 | * - Elements are enumerated in O(n). No guarantees are made on the ordering. |
|
| 17 | * |
|
| 18 | * ```solidity |
|
| 19 | * contract Example { |
|
| 20 | * // Add the library methods |
|
| 21 | * using EnumerableSet for EnumerableSet.AddressSet; |
|
| 22 | * |
|
| 23 | * // Declare a set state variable |
|
| 24 | * EnumerableSet.AddressSet private mySet; |
|
| 25 | * } |
|
| 26 | * ``` |
|
| 27 | * |
|
| 28 | * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) |
|
| 29 | * and `uint256` (`UintSet`) are supported. |
|
| 30 | * |
|
| 31 | * [WARNING] |
|
| 32 | * ==== |
|
| 33 | * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure |
|
| 34 | * unusable. |
|
| 35 | * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. |
|
| 36 | * |
|
| 37 | * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an |
|
| 38 | * array of EnumerableSet. |
|
| 39 | * ==== |
|
| 40 | */ |
|
| 41 | 0 | library EnumerableSet { |
| 42 | // To implement this library for multiple types with as little code |
|
| 43 | // repetition as possible, we write it in terms of a generic Set type with |
|
| 44 | // bytes32 values. |
|
| 45 | // The Set implementation uses private functions, and user-facing |
|
| 46 | // implementations (such as AddressSet) are just wrappers around the |
|
| 47 | // underlying Set. |
|
| 48 | // This means that we can only create new EnumerableSets for types that fit |
|
| 49 | // in bytes32. |
|
| 50 | ||
| 51 | struct Set { |
|
| 52 | // Storage of set values |
|
| 53 | bytes32[] _values; |
|
| 54 | // Position of the value in the `values` array, plus 1 because index 0 |
|
| 55 | // means a value is not in the set. |
|
| 56 | mapping(bytes32 => uint256) _indexes; |
|
| 57 | } |
|
| 58 | ||
| 59 | /** |
|
| 60 | * @dev Add a value to a set. O(1). |
|
| 61 | * |
|
| 62 | * Returns true if the value was added to the set, that is if it was not |
|
| 63 | * already present. |
|
| 64 | */ |
|
| 65 | 3× | function _add(Set storage set, bytes32 value) private returns (bool) { |
| 66 | 4× | if (!_contains(set, value)) { |
| 67 | 44× | set._values.push(value); |
| 68 | // The value is stored at length-1, but we add 1 to all indexes |
|
| 69 | // and use 0 as a sentinel value |
|
| 70 | 36× | set._indexes[value] = set._values.length; |
| 71 | 4× | return true; |
| 72 | } else { |
|
| 73 | 0 | return false; |
| 74 | } |
|
| 75 | } |
|
| 76 | ||
| 77 | /** |
|
| 78 | * @dev Removes a value from a set. O(1). |
|
| 79 | * |
|
| 80 | * Returns true if the value was removed from the set, that is if it was |
|
| 81 | * present. |
|
| 82 | */ |
|
| 83 | function _remove(Set storage set, bytes32 value) private returns (bool) { |
|
| 84 | // We read and store the value's index to prevent multiple reads from the same storage slot |
|
| 85 | uint256 valueIndex = set._indexes[value]; |
|
| 86 | ||
| 87 | if (valueIndex != 0) { |
|
| 88 | // Equivalent to contains(set, value) |
|
| 89 | // To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
|
| 90 | // the array, and then remove the last element (sometimes called as 'swap and pop'). |
|
| 91 | // This modifies the order of the array, as noted in {at}. |
|
| 92 | ||
| 93 | uint256 toDeleteIndex = valueIndex - 1; |
|
| 94 | uint256 lastIndex = set._values.length - 1; |
|
| 95 | ||
| 96 | if (lastIndex != toDeleteIndex) { |
|
| 97 | bytes32 lastValue = set._values[lastIndex]; |
|
| 98 | ||
| 99 | // Move the last value to the index where the value to delete is |
|
| 100 | set._values[toDeleteIndex] = lastValue; |
|
| 101 | // Update the index for the moved value |
|
| 102 | set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex |
|
| 103 | } |
|
| 104 | ||
| 105 | // Delete the slot where the moved value was stored |
|
| 106 | set._values.pop(); |
|
| 107 | ||
| 108 | // Delete the index for the deleted slot |
|
| 109 | delete set._indexes[value]; |
|
| 110 | ||
| 111 | return true; |
|
| 112 | } else { |
|
| 113 | return false; |
|
| 114 | } |
|
| 115 | } |
|
| 116 | ||
| 117 | /** |
|
| 118 | * @dev Returns true if the value is in the set. O(1). |
|
| 119 | */ |
|
| 120 | 2× | function _contains(Set storage set, bytes32 value) private view returns (bool) { |
| 121 | 52× | return set._indexes[value] != 0; |
| 122 | } |
|
| 123 | ||
| 124 | /** |
|
| 125 | * @dev Returns the number of values on the set. O(1). |
|
| 126 | */ |
|
| 127 | 2× | function _length(Set storage set) private view returns (uint256) { |
| 128 | 4× | return set._values.length; |
| 129 | } |
|
| 130 | ||
| 131 | /** |
|
| 132 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 133 | * |
|
| 134 | * Note that there are no guarantees on the ordering of values inside the |
|
| 135 | * array, and it may change when more values are added or removed. |
|
| 136 | * |
|
| 137 | * Requirements: |
|
| 138 | * |
|
| 139 | * - `index` must be strictly less than {length}. |
|
| 140 | */ |
|
| 141 | 21× | function _at(Set storage set, uint256 index) private view returns (bytes32) { |
| 142 | 66× | return set._values[index]; |
| 143 | } |
|
| 144 | ||
| 145 | /** |
|
| 146 | * @dev Return the entire set in an array |
|
| 147 | * |
|
| 148 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 149 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 150 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 151 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 152 | */ |
|
| 153 | 24× | function _values(Set storage set) private view returns (bytes32[] memory) { |
| 154 | 276× | return set._values; |
| 155 | } |
|
| 156 | ||
| 157 | // Bytes32Set |
|
| 158 | ||
| 159 | struct Bytes32Set { |
|
| 160 | Set _inner; |
|
| 161 | } |
|
| 162 | ||
| 163 | /** |
|
| 164 | * @dev Add a value to a set. O(1). |
|
| 165 | * |
|
| 166 | * Returns true if the value was added to the set, that is if it was not |
|
| 167 | * already present. |
|
| 168 | */ |
|
| 169 | function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
|
| 170 | return _add(set._inner, value); |
|
| 171 | } |
|
| 172 | ||
| 173 | /** |
|
| 174 | * @dev Removes a value from a set. O(1). |
|
| 175 | * |
|
| 176 | * Returns true if the value was removed from the set, that is if it was |
|
| 177 | * present. |
|
| 178 | */ |
|
| 179 | function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
|
| 180 | return _remove(set._inner, value); |
|
| 181 | } |
|
| 182 | ||
| 183 | /** |
|
| 184 | * @dev Returns true if the value is in the set. O(1). |
|
| 185 | */ |
|
| 186 | function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { |
|
| 187 | return _contains(set._inner, value); |
|
| 188 | } |
|
| 189 | ||
| 190 | /** |
|
| 191 | * @dev Returns the number of values in the set. O(1). |
|
| 192 | */ |
|
| 193 | function length(Bytes32Set storage set) internal view returns (uint256) { |
|
| 194 | return _length(set._inner); |
|
| 195 | } |
|
| 196 | ||
| 197 | /** |
|
| 198 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 199 | * |
|
| 200 | * Note that there are no guarantees on the ordering of values inside the |
|
| 201 | * array, and it may change when more values are added or removed. |
|
| 202 | * |
|
| 203 | * Requirements: |
|
| 204 | * |
|
| 205 | * - `index` must be strictly less than {length}. |
|
| 206 | */ |
|
| 207 | function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { |
|
| 208 | return _at(set._inner, index); |
|
| 209 | } |
|
| 210 | ||
| 211 | /** |
|
| 212 | * @dev Return the entire set in an array |
|
| 213 | * |
|
| 214 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 215 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 216 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 217 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 218 | */ |
|
| 219 | function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { |
|
| 220 | bytes32[] memory store = _values(set._inner); |
|
| 221 | bytes32[] memory result; |
|
| 222 | ||
| 223 | /// @solidity memory-safe-assembly |
|
| 224 | assembly { |
|
| 225 | result := store |
|
| 226 | } |
|
| 227 | ||
| 228 | return result; |
|
| 229 | } |
|
| 230 | ||
| 231 | // AddressSet |
|
| 232 | ||
| 233 | struct AddressSet { |
|
| 234 | Set _inner; |
|
| 235 | } |
|
| 236 | ||
| 237 | /** |
|
| 238 | * @dev Add a value to a set. O(1). |
|
| 239 | * |
|
| 240 | * Returns true if the value was added to the set, that is if it was not |
|
| 241 | * already present. |
|
| 242 | */ |
|
| 243 | 10× | function add(AddressSet storage set, address value) internal returns (bool) { |
| 244 | 13× | return _add(set._inner, bytes32(uint256(uint160(value)))); |
| 245 | } |
|
| 246 | ||
| 247 | /** |
|
| 248 | * @dev Removes a value from a set. O(1). |
|
| 249 | * |
|
| 250 | * Returns true if the value was removed from the set, that is if it was |
|
| 251 | * present. |
|
| 252 | */ |
|
| 253 | function remove(AddressSet storage set, address value) internal returns (bool) { |
|
| 254 | return _remove(set._inner, bytes32(uint256(uint160(value)))); |
|
| 255 | } |
|
| 256 | ||
| 257 | /** |
|
| 258 | * @dev Returns true if the value is in the set. O(1). |
|
| 259 | */ |
|
| 260 | 4× | function contains(AddressSet storage set, address value) internal view returns (bool) { |
| 261 | 6× | 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 | 4× | function length(AddressSet storage set) internal view returns (uint256) { |
| 268 | 4× | 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 | 6× | function at(AddressSet storage set, uint256 index) internal view returns (address) { |
| 282 | 15× | return address(uint160(uint256(_at(set._inner, index)))); |
| 283 | } |
|
| 284 | ||
| 285 | /** |
|
| 286 | * @dev Return the entire set in an array |
|
| 287 | * |
|
| 288 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 289 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 290 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 291 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 292 | */ |
|
| 293 | 12× | function values(AddressSet storage set) internal view returns (address[] memory) { |
| 294 | 24× | bytes32[] memory store = _values(set._inner); |
| 295 | address[] memory result; |
|
| 296 | ||
| 297 | /// @solidity memory-safe-assembly |
|
| 298 | assembly { |
|
| 299 | result := store |
|
| 300 | } |
|
| 301 | ||
| 302 | return result; |
|
| 303 | } |
|
| 304 | ||
| 305 | // UintSet |
|
| 306 | ||
| 307 | struct UintSet { |
|
| 308 | Set _inner; |
|
| 309 | } |
|
| 310 | ||
| 311 | /** |
|
| 312 | * @dev Add a value to a set. O(1). |
|
| 313 | * |
|
| 314 | * Returns true if the value was added to the set, that is if it was not |
|
| 315 | * already present. |
|
| 316 | */ |
|
| 317 | function add(UintSet storage set, uint256 value) internal returns (bool) { |
|
| 318 | return _add(set._inner, bytes32(value)); |
|
| 319 | } |
|
| 320 | ||
| 321 | /** |
|
| 322 | * @dev Removes a value from a set. O(1). |
|
| 323 | * |
|
| 324 | * Returns true if the value was removed from the set, that is if it was |
|
| 325 | * present. |
|
| 326 | */ |
|
| 327 | function remove(UintSet storage set, uint256 value) internal returns (bool) { |
|
| 328 | return _remove(set._inner, bytes32(value)); |
|
| 329 | } |
|
| 330 | ||
| 331 | /** |
|
| 332 | * @dev Returns true if the value is in the set. O(1). |
|
| 333 | */ |
|
| 334 | function contains(UintSet storage set, uint256 value) internal view returns (bool) { |
|
| 335 | return _contains(set._inner, bytes32(value)); |
|
| 336 | } |
|
| 337 | ||
| 338 | /** |
|
| 339 | * @dev Returns the number of values in the set. O(1). |
|
| 340 | */ |
|
| 341 | function length(UintSet storage set) internal view returns (uint256) { |
|
| 342 | return _length(set._inner); |
|
| 343 | } |
|
| 344 | ||
| 345 | /** |
|
| 346 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 347 | * |
|
| 348 | * Note that there are no guarantees on the ordering of values inside the |
|
| 349 | * array, and it may change when more values are added or removed. |
|
| 350 | * |
|
| 351 | * Requirements: |
|
| 352 | * |
|
| 353 | * - `index` must be strictly less than {length}. |
|
| 354 | */ |
|
| 355 | function at(UintSet storage set, uint256 index) internal view returns (uint256) { |
|
| 356 | return uint256(_at(set._inner, index)); |
|
| 357 | } |
|
| 358 | ||
| 359 | /** |
|
| 360 | * @dev Return the entire set in an array |
|
| 361 | * |
|
| 362 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 363 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 364 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 365 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 366 | */ |
|
| 367 | function values(UintSet storage set) internal view returns (uint256[] memory) { |
|
| 368 | bytes32[] memory store = _values(set._inner); |
|
| 369 | uint256[] memory result; |
|
| 370 | ||
| 371 | /// @solidity memory-safe-assembly |
|
| 372 | assembly { |
|
| 373 | result := store |
|
| 374 | } |
|
| 375 | ||
| 376 | return result; |
|
| 377 | } |
|
| 378 | } |
73%
lib/setup-helpers/src/MockERC20.sol
Lines covered: 55 / 75 (73%)
| 1 | // SPDX-License-Identifier: AGPL-3.0-only |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. |
|
| 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) |
|
| 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) |
|
| 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. |
|
| 8 | abstract contract ERC20 { |
|
| 9 | /*////////////////////////////////////////////////////////////// |
|
| 10 | ERRORS |
|
| 11 | //////////////////////////////////////////////////////////////*/ |
|
| 12 | ||
| 13 | /// @notice Thrown when attempting to transfer more tokens than available balance |
|
| 14 | error InsufficientBalance(address from, uint256 balance, uint256 amount); |
|
| 15 | ||
| 16 | /// @notice Thrown when attempting to transfer more tokens than allowed |
|
| 17 | error InsufficientAllowance(address owner, address spender, uint256 allowance, uint256 amount); |
|
| 18 | ||
| 19 | /// @notice Thrown when minting would cause overflow |
|
| 20 | error MintOverflow(uint256 currentSupply, uint256 amount); |
|
| 21 | ||
| 22 | /*////////////////////////////////////////////////////////////// |
|
| 23 | EVENTS |
|
| 24 | //////////////////////////////////////////////////////////////*/ |
|
| 25 | ||
| 26 | event Transfer(address indexed from, address indexed to, uint256 amount); |
|
| 27 | ||
| 28 | event Approval(address indexed owner, address indexed spender, uint256 amount); |
|
| 29 | ||
| 30 | /*////////////////////////////////////////////////////////////// |
|
| 31 | METADATA STORAGE |
|
| 32 | //////////////////////////////////////////////////////////////*/ |
|
| 33 | ||
| 34 | 88× | string public name; |
| 35 | ||
| 36 | 0 | string public symbol; |
| 37 | ||
| 38 | 72× | uint8 public immutable decimals; |
| 39 | ||
| 40 | /*////////////////////////////////////////////////////////////// |
|
| 41 | ERC20 STORAGE |
|
| 42 | //////////////////////////////////////////////////////////////*/ |
|
| 43 | ||
| 44 | 50× | uint256 public totalSupply; |
| 45 | ||
| 46 | 336× | mapping(address => uint256) public balanceOf; |
| 47 | ||
| 48 | 0 | mapping(address => mapping(address => uint256)) public allowance; |
| 49 | ||
| 50 | /*////////////////////////////////////////////////////////////// |
|
| 51 | EIP-2612 STORAGE |
|
| 52 | //////////////////////////////////////////////////////////////*/ |
|
| 53 | ||
| 54 | uint256 internal immutable INITIAL_CHAIN_ID; |
|
| 55 | ||
| 56 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; |
|
| 57 | ||
| 58 | 0 | mapping(address => uint256) public nonces; |
| 59 | ||
| 60 | /*////////////////////////////////////////////////////////////// |
|
| 61 | CONSTRUCTOR |
|
| 62 | //////////////////////////////////////////////////////////////*/ |
|
| 63 | ||
| 64 | constructor(string memory _name, string memory _symbol, uint8 _decimals) { |
|
| 65 | 36× | name = _name; |
| 66 | 28× | symbol = _symbol; |
| 67 | 20× | decimals = _decimals; |
| 68 | ||
| 69 | 12× | INITIAL_CHAIN_ID = block.chainid; |
| 70 | 24× | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); |
| 71 | } |
|
| 72 | ||
| 73 | /*////////////////////////////////////////////////////////////// |
|
| 74 | ERC20 LOGIC |
|
| 75 | //////////////////////////////////////////////////////////////*/ |
|
| 76 | ||
| 77 | 248× | function approve(address spender, uint256 amount) public virtual returns (bool) { |
| 78 | 145× | allowance[msg.sender][spender] = amount; |
| 79 | ||
| 80 | 65× | emit Approval(msg.sender, spender, amount); |
| 81 | ||
| 82 | 5× | return true; |
| 83 | } |
|
| 84 | ||
| 85 | 182× | function transfer(address to, uint256 amount) public virtual returns (bool) { |
| 86 | 143× | uint256 fromBalance = balanceOf[msg.sender]; |
| 87 | 154× | if (fromBalance < amount) revert InsufficientBalance(msg.sender, fromBalance, amount); |
| 88 | ||
| 89 | 286× | balanceOf[msg.sender] = fromBalance - amount; |
| 90 | ||
| 91 | // Cannot overflow because the sum of all user |
|
| 92 | // balances can't exceed the max uint256 value. |
|
| 93 | unchecked { |
|
| 94 | 208× | balanceOf[to] += amount; |
| 95 | } |
|
| 96 | ||
| 97 | 169× | emit Transfer(msg.sender, to, amount); |
| 98 | ||
| 99 | 26× | return true; |
| 100 | } |
|
| 101 | ||
| 102 | 174× | function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { |
| 103 | 264× | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. |
| 104 | 110× | uint256 fromBalance = balanceOf[from]; |
| 105 | ||
| 106 | 54× | if (allowed != type(uint256).max) { |
| 107 | 292× | if (allowed < amount) revert InsufficientAllowance(from, msg.sender, allowed, amount); |
| 108 | 300× | allowance[from][msg.sender] = allowed - amount; |
| 109 | } |
|
| 110 | ||
| 111 | 150× | if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
| 112 | 280× | balanceOf[from] = fromBalance - amount; |
| 113 | ||
| 114 | // Cannot overflow because the sum of all user |
|
| 115 | // balances can't exceed the max uint256 value. |
|
| 116 | unchecked { |
|
| 117 | 160× | balanceOf[to] += amount; |
| 118 | } |
|
| 119 | ||
| 120 | 100× | emit Transfer(from, to, amount); |
| 121 | ||
| 122 | 20× | return true; |
| 123 | } |
|
| 124 | ||
| 125 | /*////////////////////////////////////////////////////////////// |
|
| 126 | EIP-2612 LOGIC |
|
| 127 | //////////////////////////////////////////////////////////////*/ |
|
| 128 | ||
| 129 | 0 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) |
| 130 | public |
|
| 131 | virtual |
|
| 132 | { |
|
| 133 | 0 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); |
| 134 | ||
| 135 | // Unchecked because the only math done is incrementing |
|
| 136 | // the owner's nonce which cannot realistically overflow. |
|
| 137 | unchecked { |
|
| 138 | 0 | address recoveredAddress = ecrecover( |
| 139 | 0 | keccak256( |
| 140 | 0 | abi.encodePacked( |
| 141 | "\x19\x01", |
|
| 142 | 0 | DOMAIN_SEPARATOR(), |
| 143 | 0 | keccak256( |
| 144 | 0 | abi.encode( |
| 145 | 0 | keccak256( |
| 146 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" |
|
| 147 | ), |
|
| 148 | owner, |
|
| 149 | spender, |
|
| 150 | value, |
|
| 151 | 0 | nonces[owner]++, |
| 152 | deadline |
|
| 153 | ) |
|
| 154 | ) |
|
| 155 | ) |
|
| 156 | ), |
|
| 157 | v, |
|
| 158 | r, |
|
| 159 | s |
|
| 160 | ); |
|
| 161 | ||
| 162 | 0 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); |
| 163 | ||
| 164 | 0 | allowance[recoveredAddress][spender] = value; |
| 165 | } |
|
| 166 | ||
| 167 | 0 | emit Approval(owner, spender, value); |
| 168 | } |
|
| 169 | ||
| 170 | 0 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { |
| 171 | 0 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); |
| 172 | } |
|
| 173 | ||
| 174 | 16× | function computeDomainSeparator() internal view virtual returns (bytes32) { |
| 175 | 32× | return keccak256( |
| 176 | 60× | abi.encode( |
| 177 | 4× | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), |
| 178 | 72× | keccak256(bytes(name)), |
| 179 | 4× | keccak256("1"), |
| 180 | 4× | block.chainid, |
| 181 | 4× | address(this) |
| 182 | ) |
|
| 183 | ); |
|
| 184 | } |
|
| 185 | ||
| 186 | /*////////////////////////////////////////////////////////////// |
|
| 187 | INTERNAL MINT/BURN LOGIC |
|
| 188 | //////////////////////////////////////////////////////////////*/ |
|
| 189 | ||
| 190 | 44× | function _mint(address to, uint256 amount) internal virtual { |
| 191 | 96× | uint256 newTotalSupply = totalSupply + amount; |
| 192 | 64× | if (newTotalSupply < totalSupply) revert MintOverflow(totalSupply, amount); |
| 193 | 32× | totalSupply = newTotalSupply; |
| 194 | ||
| 195 | // Cannot overflow because the sum of all user |
|
| 196 | // balances can't exceed the max uint256 value. |
|
| 197 | unchecked { |
|
| 198 | 168× | balanceOf[to] += amount; |
| 199 | } |
|
| 200 | ||
| 201 | 88× | emit Transfer(address(0), to, amount); |
| 202 | } |
|
| 203 | ||
| 204 | 8× | function _burn(address from, uint256 amount) internal virtual { |
| 205 | 104× | uint256 fromBalance = balanceOf[from]; |
| 206 | 118× | if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
| 207 | ||
| 208 | 138× | balanceOf[from] = fromBalance - amount; |
| 209 | ||
| 210 | // Cannot underflow because a user's balance |
|
| 211 | // will never be larger than the total supply. |
|
| 212 | unchecked { |
|
| 213 | 54× | totalSupply -= amount; |
| 214 | } |
|
| 215 | ||
| 216 | 30× | emit Transfer(from, address(0), amount); |
| 217 | } |
|
| 218 | } |
|
| 219 | ||
| 220 | 428× | contract MockERC20 is ERC20 { |
| 221 | 136× | constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {} |
| 222 | ||
| 223 | 44× | function mint(address to, uint256 value) public virtual { |
| 224 | 18× | _mint(to, value); |
| 225 | } |
|
| 226 | ||
| 227 | 0 | function burn(address from, uint256 value) public virtual { |
| 228 | 0 | _burn(from, value); |
| 229 | } |
|
| 230 | } |
31%
lib/setup-helpers/src/Utils.sol
Lines covered: 17 / 54 (31%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {Panic} from "./Panic.sol"; |
|
| 5 | ||
| 6 | 0 | contract Utils { |
| 7 | /// @dev check if the error returned from a call is the same as the expected error |
|
| 8 | /// @param err the error returned from a call |
|
| 9 | /// @param expected the expected error |
|
| 10 | /// @return true if the error is the same as the expected error, false otherwise |
|
| 11 | 4× | function checkError(bytes memory err, string memory expected) internal pure returns (bool) { |
| 12 | 11× | (string memory revertMsg, bool customError) = _getRevertMsg(err); |
| 13 | ||
| 14 | 1× | bytes32 errorBytes; |
| 15 | 1× | bytes32 expectedBytes; |
| 16 | ||
| 17 | 7× | if (customError) { |
| 18 | // Custom error returns the keccak256 hash of the error, so don't need to hash it again |
|
| 19 | 32× | errorBytes = bytes32(abi.encodePacked(revertMsg, bytes28(0))); |
| 20 | 33× | expectedBytes = bytes4(keccak256(abi.encodePacked(expected))); |
| 21 | } else { |
|
| 22 | 0 | errorBytes = keccak256(abi.encodePacked(revertMsg)); |
| 23 | 0 | expectedBytes = keccak256(abi.encodePacked(expected)); |
| 24 | } |
|
| 25 | ||
| 26 | // Check if error contains expected string |
|
| 27 | 2× | 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 | 7× | 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 | 7× | 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 | 7× | if (returnData.length == 4 + 32) { |
| 41 | // Check that the data starts with the Panic signature |
|
| 42 | 0 | bool panic = _checkIfPanic(returnData); |
| 43 | ||
| 44 | 0 | if (panic) { |
| 45 | 0 | return _getPanicCode(returnData); |
| 46 | } |
|
| 47 | } |
|
| 48 | ||
| 49 | // Get the error selector from returnData |
|
| 50 | 5× | bytes4 errorSelector = _getErrorSelector(returnData); |
| 51 | ||
| 52 | // 2. Error(string) - If it's a standard revert string |
|
| 53 | 1× | bytes4 errorStringSelector = bytes4(keccak256("Error(string)")); // Get the standard Error(string) selector |
| 54 | ||
| 55 | 6× | if (errorSelector == errorStringSelector) { |
| 56 | assembly { |
|
| 57 | // slice the sighash of the error so we can decode the string |
|
| 58 | 0 | returnData := add(returnData, 0x04) |
| 59 | } |
|
| 60 | 0 | return (abi.decode(returnData, (string)), false); |
| 61 | } |
|
| 62 | ||
| 63 | // 3. Custom error - Return the custom error selector as a string |
|
| 64 | 24× | return (string(abi.encodePacked(errorSelector)), true); |
| 65 | } |
|
| 66 | ||
| 67 | 0 | function _checkIfPanic(bytes memory returnData) internal pure returns (bool) { |
| 68 | 0 | bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)"))); |
| 69 | ||
| 70 | 0 | for (uint256 i = 0; i < 4; i++) { |
| 71 | 0 | if (returnData[i] != panicSignature[i]) { |
| 72 | 0 | return false; |
| 73 | } |
|
| 74 | } |
|
| 75 | ||
| 76 | 0 | return true; |
| 77 | } |
|
| 78 | ||
| 79 | 0 | function _getPanicCode(bytes memory returnData) internal pure returns (string memory, bool) { |
| 80 | uint256 panicCode; |
|
| 81 | 0 | for (uint256 i = 4; i < 36; i++) { |
| 82 | 0 | panicCode = panicCode << 8; |
| 83 | 0 | panicCode |= uint8(returnData[i]); |
| 84 | } |
|
| 85 | ||
| 86 | // Convert the panic code into its string representation |
|
| 87 | 0 | if (panicCode == 1) { |
| 88 | // call assert with an argument that evaluates to false |
|
| 89 | 0 | return (Panic.assertionPanic, false); |
| 90 | 0 | } else if (panicCode == 17) { |
| 91 | // arithmetic operation results in underflow or overflow |
|
| 92 | 0 | return (Panic.arithmeticPanic, false); |
| 93 | 0 | } else if (panicCode == 18) { |
| 94 | // division or modulo by zero |
|
| 95 | 0 | return (Panic.divisionPanic, false); |
| 96 | 0 | } else if (panicCode == 33) { |
| 97 | // converting a value that's too big or negative into an enum type |
|
| 98 | 0 | return (Panic.enumPanic, false); |
| 99 | 0 | } else if (panicCode == 34) { |
| 100 | // access a storage byte array that is incorrectly encoded |
|
| 101 | 0 | return (Panic.arrayPanic, false); |
| 102 | 0 | } else if (panicCode == 49) { |
| 103 | // call .pop() on an empty array |
|
| 104 | 0 | return (Panic.emptyArrayPanic, false); |
| 105 | 0 | } else if (panicCode == 50) { |
| 106 | // array access out of bounds |
|
| 107 | 0 | return (Panic.outOfBoundsPanic, false); |
| 108 | 0 | } else if (panicCode == 65) { |
| 109 | // allocate too much memory or create an array that is too large |
|
| 110 | 0 | return (Panic.memoryPanic, false); |
| 111 | 0 | } else if (panicCode == 81) { |
| 112 | // call a zero-initialized variable of internal function type |
|
| 113 | 0 | return (Panic.functionPanic, false); |
| 114 | } |
|
| 115 | ||
| 116 | 0 | return ("Undefined panic code", false); |
| 117 | } |
|
| 118 | ||
| 119 | 1× | function _getErrorSelector(bytes memory returnData) internal pure returns (bytes4 errorSelector) { |
| 120 | assembly { |
|
| 121 | 4× | errorSelector := mload(add(returnData, 0x20)) |
| 122 | } |
|
| 123 | return errorSelector; |
|
| 124 | } |
|
| 125 | } |
30%
lib/v2-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol
Lines covered: 11 / 36 (30%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {IAccessControl} from "./IAccessControl.sol"; |
|
| 7 | import {Context} from "../utils/Context.sol"; |
|
| 8 | import {IERC165, ERC165} from "../utils/introspection/ERC165.sol"; |
|
| 9 | ||
| 10 | /** |
|
| 11 | * @dev Contract module that allows children to implement role-based access |
|
| 12 | * control mechanisms. This is a lightweight version that doesn't allow enumerating role |
|
| 13 | * members except through off-chain means by accessing the contract event logs. Some |
|
| 14 | * applications may benefit from on-chain enumerability, for those cases see |
|
| 15 | * {AccessControlEnumerable}. |
|
| 16 | * |
|
| 17 | * Roles are referred to by their `bytes32` identifier. These should be exposed |
|
| 18 | * in the external API and be unique. The best way to achieve this is by |
|
| 19 | * using `public constant` hash digests: |
|
| 20 | * |
|
| 21 | * ```solidity |
|
| 22 | * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); |
|
| 23 | * ``` |
|
| 24 | * |
|
| 25 | * Roles can be used to represent a set of permissions. To restrict access to a |
|
| 26 | * function call, use {hasRole}: |
|
| 27 | * |
|
| 28 | * ```solidity |
|
| 29 | * function foo() public { |
|
| 30 | * require(hasRole(MY_ROLE, msg.sender)); |
|
| 31 | * ... |
|
| 32 | * } |
|
| 33 | * ``` |
|
| 34 | * |
|
| 35 | * Roles can be granted and revoked dynamically via the {grantRole} and |
|
| 36 | * {revokeRole} functions. Each role has an associated admin role, and only |
|
| 37 | * accounts that have a role's admin role can call {grantRole} and {revokeRole}. |
|
| 38 | * |
|
| 39 | * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means |
|
| 40 | * that only accounts with this role will be able to grant or revoke other |
|
| 41 | * roles. More complex role relationships can be created by using |
|
| 42 | * {_setRoleAdmin}. |
|
| 43 | * |
|
| 44 | * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to |
|
| 45 | * grant and revoke this role. Extra precautions should be taken to secure |
|
| 46 | * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} |
|
| 47 | * to enforce additional security measures for this role. |
|
| 48 | */ |
|
| 49 | abstract contract AccessControl is Context, IAccessControl, ERC165 { |
|
| 50 | struct RoleData { |
|
| 51 | mapping(address account => bool) hasRole; |
|
| 52 | bytes32 adminRole; |
|
| 53 | } |
|
| 54 | ||
| 55 | mapping(bytes32 role => RoleData) private _roles; |
|
| 56 | ||
| 57 | 0 | bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; |
| 58 | ||
| 59 | /** |
|
| 60 | * @dev Modifier that checks that an account has a specific role. Reverts |
|
| 61 | * with an {AccessControlUnauthorizedAccount} error including the required role. |
|
| 62 | */ |
|
| 63 | modifier onlyRole(bytes32 role) { |
|
| 64 | 35× | _checkRole(role); |
| 65 | _; |
|
| 66 | } |
|
| 67 | ||
| 68 | /// @inheritdoc IERC165 |
|
| 69 | 61× | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { |
| 70 | 12× | return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); |
| 71 | } |
|
| 72 | ||
| 73 | /** |
|
| 74 | * @dev Returns `true` if `account` has been granted `role`. |
|
| 75 | */ |
|
| 76 | 3× | function hasRole(bytes32 role, address account) public view virtual returns (bool) { |
| 77 | 26× | return _roles[role].hasRole[account]; |
| 78 | } |
|
| 79 | ||
| 80 | /** |
|
| 81 | * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` |
|
| 82 | * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. |
|
| 83 | */ |
|
| 84 | 3× | function _checkRole(bytes32 role) internal view virtual { |
| 85 | 5× | _checkRole(role, _msgSender()); |
| 86 | } |
|
| 87 | ||
| 88 | /** |
|
| 89 | * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` |
|
| 90 | * is missing `role`. |
|
| 91 | */ |
|
| 92 | 1× | function _checkRole(bytes32 role, address account) internal view virtual { |
| 93 | 8× | if (!hasRole(role, account)) { |
| 94 | 0 | revert AccessControlUnauthorizedAccount(account, role); |
| 95 | } |
|
| 96 | } |
|
| 97 | ||
| 98 | /** |
|
| 99 | * @dev Returns the admin role that controls `role`. See {grantRole} and |
|
| 100 | * {revokeRole}. |
|
| 101 | * |
|
| 102 | * To change a role's admin, use {_setRoleAdmin}. |
|
| 103 | */ |
|
| 104 | 0 | function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { |
| 105 | 0 | return _roles[role].adminRole; |
| 106 | } |
|
| 107 | ||
| 108 | /** |
|
| 109 | * @dev Grants `role` to `account`. |
|
| 110 | * |
|
| 111 | * If `account` had not been already granted `role`, emits a {RoleGranted} |
|
| 112 | * event. |
|
| 113 | * |
|
| 114 | * Requirements: |
|
| 115 | * |
|
| 116 | * - the caller must have ``role``'s admin role. |
|
| 117 | * |
|
| 118 | * May emit a {RoleGranted} event. |
|
| 119 | */ |
|
| 120 | 0 | function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { |
| 121 | 0 | _grantRole(role, account); |
| 122 | } |
|
| 123 | ||
| 124 | /** |
|
| 125 | * @dev Revokes `role` from `account`. |
|
| 126 | * |
|
| 127 | * If `account` had been granted `role`, emits a {RoleRevoked} event. |
|
| 128 | * |
|
| 129 | * Requirements: |
|
| 130 | * |
|
| 131 | * - the caller must have ``role``'s admin role. |
|
| 132 | * |
|
| 133 | * May emit a {RoleRevoked} event. |
|
| 134 | */ |
|
| 135 | 0 | function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { |
| 136 | 0 | _revokeRole(role, account); |
| 137 | } |
|
| 138 | ||
| 139 | /** |
|
| 140 | * @dev Revokes `role` from the calling account. |
|
| 141 | * |
|
| 142 | * Roles are often managed via {grantRole} and {revokeRole}: this function's |
|
| 143 | * purpose is to provide a mechanism for accounts to lose their privileges |
|
| 144 | * if they are compromised (such as when a trusted device is misplaced). |
|
| 145 | * |
|
| 146 | * If the calling account had been revoked `role`, emits a {RoleRevoked} |
|
| 147 | * event. |
|
| 148 | * |
|
| 149 | * Requirements: |
|
| 150 | * |
|
| 151 | * - the caller must be `callerConfirmation`. |
|
| 152 | * |
|
| 153 | * May emit a {RoleRevoked} event. |
|
| 154 | */ |
|
| 155 | 0 | function renounceRole(bytes32 role, address callerConfirmation) public virtual { |
| 156 | 0 | if (callerConfirmation != _msgSender()) { |
| 157 | 0 | revert AccessControlBadConfirmation(); |
| 158 | } |
|
| 159 | ||
| 160 | 0 | _revokeRole(role, callerConfirmation); |
| 161 | } |
|
| 162 | ||
| 163 | /** |
|
| 164 | * @dev Sets `adminRole` as ``role``'s admin role. |
|
| 165 | * |
|
| 166 | * Emits a {RoleAdminChanged} event. |
|
| 167 | */ |
|
| 168 | 0 | function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { |
| 169 | 0 | bytes32 previousAdminRole = getRoleAdmin(role); |
| 170 | 0 | _roles[role].adminRole = adminRole; |
| 171 | 0 | emit RoleAdminChanged(role, previousAdminRole, adminRole); |
| 172 | } |
|
| 173 | ||
| 174 | /** |
|
| 175 | * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. |
|
| 176 | * |
|
| 177 | * Internal function without access restriction. |
|
| 178 | * |
|
| 179 | * May emit a {RoleGranted} event. |
|
| 180 | */ |
|
| 181 | 0 | function _grantRole(bytes32 role, address account) internal virtual returns (bool) { |
| 182 | 1× | if (!hasRole(role, account)) { |
| 183 | 0 | _roles[role].hasRole[account] = true; |
| 184 | 0 | emit RoleGranted(role, account, _msgSender()); |
| 185 | 0 | return true; |
| 186 | } else { |
|
| 187 | 3× | return false; |
| 188 | } |
|
| 189 | } |
|
| 190 | ||
| 191 | /** |
|
| 192 | * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked. |
|
| 193 | * |
|
| 194 | * Internal function without access restriction. |
|
| 195 | * |
|
| 196 | * May emit a {RoleRevoked} event. |
|
| 197 | */ |
|
| 198 | 0 | function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { |
| 199 | 0 | if (hasRole(role, account)) { |
| 200 | 0 | _roles[role].hasRole[account] = false; |
| 201 | 0 | emit RoleRevoked(role, account, _msgSender()); |
| 202 | 0 | return true; |
| 203 | } else { |
|
| 204 | return false; |
|
| 205 | } |
|
| 206 | } |
|
| 207 | } |
29%
lib/v2-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol
Lines covered: 9 / 31 (29%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (proxy/Clones.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {Create2} from "../utils/Create2.sol"; |
|
| 7 | import {Errors} from "../utils/Errors.sol"; |
|
| 8 | ||
| 9 | /** |
|
| 10 | * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for |
|
| 11 | * deploying minimal proxy contracts, also known as "clones". |
|
| 12 | * |
|
| 13 | * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies |
|
| 14 | * > a minimal bytecode implementation that delegates all calls to a known, fixed address. |
|
| 15 | * |
|
| 16 | * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` |
|
| 17 | * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the |
|
| 18 | * deterministic method. |
|
| 19 | */ |
|
| 20 | 0 | library Clones { |
| 21 | error CloneArgumentsTooLong(); |
|
| 22 | ||
| 23 | /** |
|
| 24 | * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`. |
|
| 25 | * |
|
| 26 | * This function uses the create opcode, which should never revert. |
|
| 27 | */ |
|
| 28 | 0 | function clone(address implementation) internal returns (address instance) { |
| 29 | 0 | return clone(implementation, 0); |
| 30 | } |
|
| 31 | ||
| 32 | /** |
|
| 33 | * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency |
|
| 34 | * to the new contract. |
|
| 35 | * |
|
| 36 | * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
|
| 37 | * to always have enough balance for new deployments. Consider exposing this function under a payable method. |
|
| 38 | */ |
|
| 39 | 0 | function clone(address implementation, uint256 value) internal returns (address instance) { |
| 40 | 0 | if (address(this).balance < value) { |
| 41 | 0 | revert Errors.InsufficientBalance(address(this).balance, value); |
| 42 | } |
|
| 43 | assembly ("memory-safe") { |
|
| 44 | // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes |
|
| 45 | // of the `implementation` address with the bytecode before the address. |
|
| 46 | 0 | mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) |
| 47 | // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. |
|
| 48 | 0 | mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) |
| 49 | 0 | instance := create(value, 0x09, 0x37) |
| 50 | } |
|
| 51 | 0 | if (instance == address(0)) { |
| 52 | 0 | revert Errors.FailedDeployment(); |
| 53 | } |
|
| 54 | } |
|
| 55 | ||
| 56 | /** |
|
| 57 | * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`. |
|
| 58 | * |
|
| 59 | * This function uses the create2 opcode and a `salt` to deterministically deploy |
|
| 60 | * the clone. Using the same `implementation` and `salt` multiple times will revert, since |
|
| 61 | * the clones cannot be deployed twice at the same address. |
|
| 62 | */ |
|
| 63 | 2× | function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { |
| 64 | 6× | 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 | 1× | function cloneDeterministic( |
| 75 | address implementation, |
|
| 76 | bytes32 salt, |
|
| 77 | uint256 value |
|
| 78 | 1× | ) internal returns (address instance) { |
| 79 | 7× | if (address(this).balance < value) { |
| 80 | 0 | revert Errors.InsufficientBalance(address(this).balance, value); |
| 81 | } |
|
| 82 | assembly ("memory-safe") { |
|
| 83 | // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes |
|
| 84 | // of the `implementation` address with the bytecode before the address. |
|
| 85 | 9× | mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) |
| 86 | // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. |
|
| 87 | 7× | mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) |
| 88 | 6× | instance := create2(value, 0x09, 0x37, salt) |
| 89 | } |
|
| 90 | 4× | if (instance == address(0)) { |
| 91 | 0 | revert Errors.FailedDeployment(); |
| 92 | } |
|
| 93 | } |
|
| 94 | ||
| 95 | /** |
|
| 96 | * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. |
|
| 97 | */ |
|
| 98 | 0 | function predictDeterministicAddress( |
| 99 | address implementation, |
|
| 100 | bytes32 salt, |
|
| 101 | address deployer |
|
| 102 | ) internal pure returns (address predicted) { |
|
| 103 | assembly ("memory-safe") { |
|
| 104 | 0 | let ptr := mload(0x40) |
| 105 | 0 | mstore(add(ptr, 0x38), deployer) |
| 106 | 0 | mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) |
| 107 | 0 | mstore(add(ptr, 0x14), implementation) |
| 108 | 0 | mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) |
| 109 | 0 | mstore(add(ptr, 0x58), salt) |
| 110 | 0 | mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) |
| 111 | 0 | predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff) |
| 112 | } |
|
| 113 | } |
|
| 114 | ||
| 115 | /** |
|
| 116 | * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. |
|
| 117 | */ |
|
| 118 | function predictDeterministicAddress( |
|
| 119 | address implementation, |
|
| 120 | bytes32 salt |
|
| 121 | ) internal view returns (address predicted) { |
|
| 122 | return predictDeterministicAddress(implementation, salt, address(this)); |
|
| 123 | } |
|
| 124 | ||
| 125 | /** |
|
| 126 | * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom |
|
| 127 | * immutable arguments. These are provided through `args` and cannot be changed after deployment. To |
|
| 128 | * access the arguments within the implementation, use {fetchCloneArgs}. |
|
| 129 | * |
|
| 130 | * This function uses the create opcode, which should never revert. |
|
| 131 | */ |
|
| 132 | function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) { |
|
| 133 | return cloneWithImmutableArgs(implementation, args, 0); |
|
| 134 | } |
|
| 135 | ||
| 136 | /** |
|
| 137 | * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value` |
|
| 138 | * parameter to send native currency to the new contract. |
|
| 139 | * |
|
| 140 | * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
|
| 141 | * to always have enough balance for new deployments. Consider exposing this function under a payable method. |
|
| 142 | */ |
|
| 143 | function cloneWithImmutableArgs( |
|
| 144 | address implementation, |
|
| 145 | bytes memory args, |
|
| 146 | uint256 value |
|
| 147 | ) internal returns (address instance) { |
|
| 148 | if (address(this).balance < value) { |
|
| 149 | revert Errors.InsufficientBalance(address(this).balance, value); |
|
| 150 | } |
|
| 151 | bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
|
| 152 | assembly ("memory-safe") { |
|
| 153 | instance := create(value, add(bytecode, 0x20), mload(bytecode)) |
|
| 154 | } |
|
| 155 | if (instance == address(0)) { |
|
| 156 | revert Errors.FailedDeployment(); |
|
| 157 | } |
|
| 158 | } |
|
| 159 | ||
| 160 | /** |
|
| 161 | * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom |
|
| 162 | * immutable arguments. These are provided through `args` and cannot be changed after deployment. To |
|
| 163 | * access the arguments within the implementation, use {fetchCloneArgs}. |
|
| 164 | * |
|
| 165 | * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same |
|
| 166 | * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice |
|
| 167 | * at the same address. |
|
| 168 | */ |
|
| 169 | function cloneDeterministicWithImmutableArgs( |
|
| 170 | address implementation, |
|
| 171 | bytes memory args, |
|
| 172 | bytes32 salt |
|
| 173 | ) internal returns (address instance) { |
|
| 174 | return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0); |
|
| 175 | } |
|
| 176 | ||
| 177 | /** |
|
| 178 | * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs], |
|
| 179 | * but with a `value` parameter to send native currency to the new contract. |
|
| 180 | * |
|
| 181 | * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
|
| 182 | * to always have enough balance for new deployments. Consider exposing this function under a payable method. |
|
| 183 | */ |
|
| 184 | function cloneDeterministicWithImmutableArgs( |
|
| 185 | address implementation, |
|
| 186 | bytes memory args, |
|
| 187 | bytes32 salt, |
|
| 188 | uint256 value |
|
| 189 | ) internal returns (address instance) { |
|
| 190 | bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
|
| 191 | return Create2.deploy(value, salt, bytecode); |
|
| 192 | } |
|
| 193 | ||
| 194 | /** |
|
| 195 | * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. |
|
| 196 | */ |
|
| 197 | function predictDeterministicAddressWithImmutableArgs( |
|
| 198 | address implementation, |
|
| 199 | bytes memory args, |
|
| 200 | bytes32 salt, |
|
| 201 | address deployer |
|
| 202 | ) internal pure returns (address predicted) { |
|
| 203 | bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
|
| 204 | return Create2.computeAddress(salt, keccak256(bytecode), deployer); |
|
| 205 | } |
|
| 206 | ||
| 207 | /** |
|
| 208 | * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. |
|
| 209 | */ |
|
| 210 | function predictDeterministicAddressWithImmutableArgs( |
|
| 211 | address implementation, |
|
| 212 | bytes memory args, |
|
| 213 | bytes32 salt |
|
| 214 | ) internal view returns (address predicted) { |
|
| 215 | return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this)); |
|
| 216 | } |
|
| 217 | ||
| 218 | /** |
|
| 219 | * @dev Get the immutable args attached to a clone. |
|
| 220 | * |
|
| 221 | * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this |
|
| 222 | * function will return an empty array. |
|
| 223 | * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or |
|
| 224 | * `cloneDeterministicWithImmutableArgs`, this function will return the args array used at |
|
| 225 | * creation. |
|
| 226 | * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This |
|
| 227 | * function should only be used to check addresses that are known to be clones. |
|
| 228 | */ |
|
| 229 | function fetchCloneArgs(address instance) internal view returns (bytes memory) { |
|
| 230 | bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short |
|
| 231 | assembly ("memory-safe") { |
|
| 232 | extcodecopy(instance, add(result, 32), 45, mload(result)) |
|
| 233 | } |
|
| 234 | return result; |
|
| 235 | } |
|
| 236 | ||
| 237 | /** |
|
| 238 | * @dev Helper that prepares the initcode of the proxy with immutable args. |
|
| 239 | * |
|
| 240 | * An assembly variant of this function requires copying the `args` array, which can be efficiently done using |
|
| 241 | * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using |
|
| 242 | * abi.encodePacked is more expensive but also more portable and easier to review. |
|
| 243 | * |
|
| 244 | * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes. |
|
| 245 | * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes. |
|
| 246 | */ |
|
| 247 | function _cloneCodeWithImmutableArgs( |
|
| 248 | address implementation, |
|
| 249 | bytes memory args |
|
| 250 | ) private pure returns (bytes memory) { |
|
| 251 | if (args.length > 24531) revert CloneArgumentsTooLong(); |
|
| 252 | return |
|
| 253 | abi.encodePacked( |
|
| 254 | hex"61", |
|
| 255 | uint16(args.length + 45), |
|
| 256 | hex"3d81600a3d39f3363d3d373d3d3d363d73", |
|
| 257 | implementation, |
|
| 258 | hex"5af43d82803e903d91602b57fd5bf3", |
|
| 259 | args |
|
| 260 | ); |
|
| 261 | } |
|
| 262 | } |
94%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
Lines covered: 16 / 17 (94%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {IERC20} from "../IERC20.sol"; |
|
| 7 | import {IERC1363} from "../../../interfaces/IERC1363.sol"; |
|
| 8 | ||
| 9 | /** |
|
| 10 | * @title SafeERC20 |
|
| 11 | * @dev Wrappers around ERC-20 operations that throw on failure (when the token |
|
| 12 | * contract returns false). Tokens that return no value (and instead revert or |
|
| 13 | * throw on failure) are also supported, non-reverting calls are assumed to be |
|
| 14 | * successful. |
|
| 15 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, |
|
| 16 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. |
|
| 17 | */ |
|
| 18 | 0 | library SafeERC20 { |
| 19 | /** |
|
| 20 | * @dev An operation with an ERC-20 token failed. |
|
| 21 | */ |
|
| 22 | error SafeERC20FailedOperation(address token); |
|
| 23 | ||
| 24 | /** |
|
| 25 | * @dev Indicates a failed `decreaseAllowance` request. |
|
| 26 | */ |
|
| 27 | error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); |
|
| 28 | ||
| 29 | /** |
|
| 30 | * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, |
|
| 31 | * non-reverting calls are assumed to be successful. |
|
| 32 | */ |
|
| 33 | 9× | function safeTransfer(IERC20 token, address to, uint256 value) internal { |
| 34 | 182× | _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); |
| 35 | } |
|
| 36 | ||
| 37 | /** |
|
| 38 | * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the |
|
| 39 | * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. |
|
| 40 | */ |
|
| 41 | 19× | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { |
| 42 | 135× | _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); |
| 43 | } |
|
| 44 | ||
| 45 | /** |
|
| 46 | * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful. |
|
| 47 | */ |
|
| 48 | function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) { |
|
| 49 | return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value))); |
|
| 50 | } |
|
| 51 | ||
| 52 | /** |
|
| 53 | * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful. |
|
| 54 | */ |
|
| 55 | function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) { |
|
| 56 | return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value))); |
|
| 57 | } |
|
| 58 | ||
| 59 | /** |
|
| 60 | * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, |
|
| 61 | * non-reverting calls are assumed to be successful. |
|
| 62 | * |
|
| 63 | * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" |
|
| 64 | * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using |
|
| 65 | * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract |
|
| 66 | * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. |
|
| 67 | */ |
|
| 68 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { |
|
| 69 | uint256 oldAllowance = token.allowance(address(this), spender); |
|
| 70 | forceApprove(token, spender, oldAllowance + value); |
|
| 71 | } |
|
| 72 | ||
| 73 | /** |
|
| 74 | * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no |
|
| 75 | * value, non-reverting calls are assumed to be successful. |
|
| 76 | * |
|
| 77 | * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" |
|
| 78 | * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using |
|
| 79 | * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract |
|
| 80 | * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. |
|
| 81 | */ |
|
| 82 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { |
|
| 83 | unchecked { |
|
| 84 | uint256 currentAllowance = token.allowance(address(this), spender); |
|
| 85 | if (currentAllowance < requestedDecrease) { |
|
| 86 | revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); |
|
| 87 | } |
|
| 88 | forceApprove(token, spender, currentAllowance - requestedDecrease); |
|
| 89 | } |
|
| 90 | } |
|
| 91 | ||
| 92 | /** |
|
| 93 | * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, |
|
| 94 | * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval |
|
| 95 | * to be set to zero before setting it to a non-zero value, such as USDT. |
|
| 96 | * |
|
| 97 | * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function |
|
| 98 | * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being |
|
| 99 | * set here. |
|
| 100 | */ |
|
| 101 | function forceApprove(IERC20 token, address spender, uint256 value) internal { |
|
| 102 | bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); |
|
| 103 | ||
| 104 | if (!_callOptionalReturnBool(token, approvalCall)) { |
|
| 105 | _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); |
|
| 106 | _callOptionalReturn(token, approvalCall); |
|
| 107 | } |
|
| 108 | } |
|
| 109 | ||
| 110 | /** |
|
| 111 | * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no |
|
| 112 | * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when |
|
| 113 | * targeting contracts. |
|
| 114 | * |
|
| 115 | * Reverts if the returned value is other than `true`. |
|
| 116 | */ |
|
| 117 | function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { |
|
| 118 | if (to.code.length == 0) { |
|
| 119 | safeTransfer(token, to, value); |
|
| 120 | } else if (!token.transferAndCall(to, value, data)) { |
|
| 121 | revert SafeERC20FailedOperation(address(token)); |
|
| 122 | } |
|
| 123 | } |
|
| 124 | ||
| 125 | /** |
|
| 126 | * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target |
|
| 127 | * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when |
|
| 128 | * targeting contracts. |
|
| 129 | * |
|
| 130 | * Reverts if the returned value is other than `true`. |
|
| 131 | */ |
|
| 132 | function transferFromAndCallRelaxed( |
|
| 133 | IERC1363 token, |
|
| 134 | address from, |
|
| 135 | address to, |
|
| 136 | uint256 value, |
|
| 137 | bytes memory data |
|
| 138 | ) internal { |
|
| 139 | if (to.code.length == 0) { |
|
| 140 | safeTransferFrom(token, from, to, value); |
|
| 141 | } else if (!token.transferFromAndCall(from, to, value, data)) { |
|
| 142 | revert SafeERC20FailedOperation(address(token)); |
|
| 143 | } |
|
| 144 | } |
|
| 145 | ||
| 146 | /** |
|
| 147 | * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no |
|
| 148 | * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when |
|
| 149 | * targeting contracts. |
|
| 150 | * |
|
| 151 | * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}. |
|
| 152 | * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall} |
|
| 153 | * once without retrying, and relies on the returned value to be true. |
|
| 154 | * |
|
| 155 | * Reverts if the returned value is other than `true`. |
|
| 156 | */ |
|
| 157 | function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { |
|
| 158 | if (to.code.length == 0) { |
|
| 159 | forceApprove(token, to, value); |
|
| 160 | } else if (!token.approveAndCall(to, value, data)) { |
|
| 161 | revert SafeERC20FailedOperation(address(token)); |
|
| 162 | } |
|
| 163 | } |
|
| 164 | ||
| 165 | /** |
|
| 166 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement |
|
| 167 | * on the return value: the return value is optional (but if data is returned, it must not be false). |
|
| 168 | * @param token The token targeted by the call. |
|
| 169 | * @param data The call data (encoded using abi.encode or one of its variants). |
|
| 170 | * |
|
| 171 | * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. |
|
| 172 | */ |
|
| 173 | 7× | function _callOptionalReturn(IERC20 token, bytes memory data) private { |
| 174 | 7× | uint256 returnSize; |
| 175 | 7× | uint256 returnValue; |
| 176 | assembly ("memory-safe") { |
|
| 177 | 77× | let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) |
| 178 | // bubble errors |
|
| 179 | 28× | if iszero(success) { |
| 180 | 6× | let ptr := mload(0x40) |
| 181 | 12× | returndatacopy(ptr, 0, returndatasize()) |
| 182 | 9× | revert(ptr, returndatasize()) |
| 183 | } |
|
| 184 | 14× | returnSize := returndatasize() |
| 185 | 14× | returnValue := mload(0) |
| 186 | } |
|
| 187 | ||
| 188 | 103× | if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) { |
| 189 | 8× | revert SafeERC20FailedOperation(address(token)); |
| 190 | } |
|
| 191 | } |
|
| 192 | ||
| 193 | /** |
|
| 194 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement |
|
| 195 | * on the return value: the return value is optional (but if data is returned, it must not be false). |
|
| 196 | * @param token The token targeted by the call. |
|
| 197 | * @param data The call data (encoded using abi.encode or one of its variants). |
|
| 198 | * |
|
| 199 | * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead. |
|
| 200 | */ |
|
| 201 | function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { |
|
| 202 | bool success; |
|
| 203 | uint256 returnSize; |
|
| 204 | uint256 returnValue; |
|
| 205 | assembly ("memory-safe") { |
|
| 206 | success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) |
|
| 207 | returnSize := returndatasize() |
|
| 208 | returnValue := mload(0) |
|
| 209 | } |
|
| 210 | return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1); |
|
| 211 | } |
|
| 212 | } |
50%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Context.sol
Lines covered: 1 / 2 (50%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | /** |
|
| 7 | * @dev Provides information about the current execution context, including the |
|
| 8 | * sender of the transaction and its data. While these are generally available |
|
| 9 | * via msg.sender and msg.data, they should not be accessed in such a direct |
|
| 10 | * manner, since when dealing with meta-transactions the account sending and |
|
| 11 | * paying for execution may not be the actual sender (as far as an application |
|
| 12 | * is concerned). |
|
| 13 | * |
|
| 14 | * This contract is only required for intermediate, library-like contracts. |
|
| 15 | */ |
|
| 16 | abstract contract Context { |
|
| 17 | 0 | function _msgSender() internal view virtual returns (address) { |
| 18 | 1× | return msg.sender; |
| 19 | } |
|
| 20 | ||
| 21 | function _msgData() internal view virtual returns (bytes calldata) { |
|
| 22 | return msg.data; |
|
| 23 | } |
|
| 24 | ||
| 25 | function _contextSuffixLength() internal view virtual returns (uint256) { |
|
| 26 | return 0; |
|
| 27 | } |
|
| 28 | } |
83%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Panic.sol
Lines covered: 5 / 6 (83%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | /** |
|
| 7 | * @dev Helper library for emitting standardized panic codes. |
|
| 8 | * |
|
| 9 | * ```solidity |
|
| 10 | * contract Example { |
|
| 11 | * using Panic for uint256; |
|
| 12 | * |
|
| 13 | * // Use any of the declared internal constants |
|
| 14 | * function foo() { Panic.GENERIC.panic(); } |
|
| 15 | * |
|
| 16 | * // Alternatively |
|
| 17 | * function foo() { Panic.panic(Panic.GENERIC); } |
|
| 18 | * } |
|
| 19 | * ``` |
|
| 20 | * |
|
| 21 | * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. |
|
| 22 | * |
|
| 23 | * _Available since v5.1._ |
|
| 24 | */ |
|
| 25 | // slither-disable-next-line unused-state |
|
| 26 | 0 | library Panic { |
| 27 | /// @dev generic / unspecified error |
|
| 28 | uint256 internal constant GENERIC = 0x00; |
|
| 29 | /// @dev used by the assert() builtin |
|
| 30 | uint256 internal constant ASSERT = 0x01; |
|
| 31 | /// @dev arithmetic underflow or overflow |
|
| 32 | 4× | 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 | 4× | function panic(uint256 code) internal pure { |
| 51 | assembly ("memory-safe") { |
|
| 52 | 12× | mstore(0x00, 0x4e487b71) |
| 53 | 12× | mstore(0x20, code) |
| 54 | 12× | revert(0x1c, 0x24) |
| 55 | } |
|
| 56 | } |
|
| 57 | } |
55%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
Lines covered: 32 / 58 (55%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.20; |
|
| 5 | ||
| 6 | import {Panic} from "../Panic.sol"; |
|
| 7 | import {SafeCast} from "./SafeCast.sol"; |
|
| 8 | ||
| 9 | /** |
|
| 10 | * @dev Standard math utilities missing in the Solidity language. |
|
| 11 | */ |
|
| 12 | 0 | library Math { |
| 13 | enum Rounding { |
|
| 14 | Floor, // Toward negative infinity |
|
| 15 | Ceil, // Toward positive infinity |
|
| 16 | Trunc, // Toward zero |
|
| 17 | Expand // Away from zero |
|
| 18 | } |
|
| 19 | ||
| 20 | /** |
|
| 21 | * @dev Return the 512-bit addition of two uint256. |
|
| 22 | * |
|
| 23 | * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low. |
|
| 24 | */ |
|
| 25 | function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) { |
|
| 26 | assembly ("memory-safe") { |
|
| 27 | low := add(a, b) |
|
| 28 | high := lt(low, a) |
|
| 29 | } |
|
| 30 | } |
|
| 31 | ||
| 32 | /** |
|
| 33 | * @dev Return the 512-bit multiplication of two uint256. |
|
| 34 | * |
|
| 35 | * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low. |
|
| 36 | */ |
|
| 37 | 35× | function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) { |
| 38 | // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use |
|
| 39 | // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 |
|
| 40 | // variables such that product = high * 2²⁵⁶ + low. |
|
| 41 | assembly ("memory-safe") { |
|
| 42 | 21× | let mm := mulmod(a, b, not(0)) |
| 43 | 28× | low := mul(a, b) |
| 44 | 84× | high := sub(sub(mm, low), lt(mm, low)) |
| 45 | } |
|
| 46 | } |
|
| 47 | ||
| 48 | /** |
|
| 49 | * @dev Returns the addition of two unsigned integers, with a success flag (no overflow). |
|
| 50 | */ |
|
| 51 | function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { |
|
| 52 | unchecked { |
|
| 53 | uint256 c = a + b; |
|
| 54 | success = c >= a; |
|
| 55 | result = c * SafeCast.toUint(success); |
|
| 56 | } |
|
| 57 | } |
|
| 58 | ||
| 59 | /** |
|
| 60 | * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow). |
|
| 61 | */ |
|
| 62 | function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { |
|
| 63 | unchecked { |
|
| 64 | uint256 c = a - b; |
|
| 65 | success = c <= a; |
|
| 66 | result = c * SafeCast.toUint(success); |
|
| 67 | } |
|
| 68 | } |
|
| 69 | ||
| 70 | /** |
|
| 71 | * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow). |
|
| 72 | */ |
|
| 73 | function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { |
|
| 74 | unchecked { |
|
| 75 | uint256 c = a * b; |
|
| 76 | assembly ("memory-safe") { |
|
| 77 | // Only true when the multiplication doesn't overflow |
|
| 78 | // (c / a == b) || (a == 0) |
|
| 79 | success := or(eq(div(c, a), b), iszero(a)) |
|
| 80 | } |
|
| 81 | // equivalent to: success ? c : 0 |
|
| 82 | result = c * SafeCast.toUint(success); |
|
| 83 | } |
|
| 84 | } |
|
| 85 | ||
| 86 | /** |
|
| 87 | * @dev Returns the division of two unsigned integers, with a success flag (no division by zero). |
|
| 88 | */ |
|
| 89 | function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { |
|
| 90 | unchecked { |
|
| 91 | success = b > 0; |
|
| 92 | assembly ("memory-safe") { |
|
| 93 | // The `DIV` opcode returns zero when the denominator is 0. |
|
| 94 | result := div(a, b) |
|
| 95 | } |
|
| 96 | } |
|
| 97 | } |
|
| 98 | ||
| 99 | /** |
|
| 100 | * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). |
|
| 101 | */ |
|
| 102 | function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { |
|
| 103 | unchecked { |
|
| 104 | success = b > 0; |
|
| 105 | assembly ("memory-safe") { |
|
| 106 | // The `MOD` opcode returns zero when the denominator is 0. |
|
| 107 | result := mod(a, b) |
|
| 108 | } |
|
| 109 | } |
|
| 110 | } |
|
| 111 | ||
| 112 | /** |
|
| 113 | * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing. |
|
| 114 | */ |
|
| 115 | function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 116 | (bool success, uint256 result) = tryAdd(a, b); |
|
| 117 | return ternary(success, result, type(uint256).max); |
|
| 118 | } |
|
| 119 | ||
| 120 | /** |
|
| 121 | * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing. |
|
| 122 | */ |
|
| 123 | function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 124 | (, uint256 result) = trySub(a, b); |
|
| 125 | return result; |
|
| 126 | } |
|
| 127 | ||
| 128 | /** |
|
| 129 | * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing. |
|
| 130 | */ |
|
| 131 | function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 132 | (bool success, uint256 result) = tryMul(a, b); |
|
| 133 | return ternary(success, result, type(uint256).max); |
|
| 134 | } |
|
| 135 | ||
| 136 | /** |
|
| 137 | * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. |
|
| 138 | * |
|
| 139 | * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. |
|
| 140 | * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute |
|
| 141 | * one branch when needed, making this function more expensive. |
|
| 142 | */ |
|
| 143 | 0 | function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { |
| 144 | unchecked { |
|
| 145 | // branchless ternary works because: |
|
| 146 | // b ^ (a ^ b) == a |
|
| 147 | // b ^ 0 == b |
|
| 148 | 12× | return b ^ ((a ^ b) * SafeCast.toUint(condition)); |
| 149 | } |
|
| 150 | } |
|
| 151 | ||
| 152 | /** |
|
| 153 | * @dev Returns the largest of two numbers. |
|
| 154 | */ |
|
| 155 | function max(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 156 | return ternary(a > b, a, b); |
|
| 157 | } |
|
| 158 | ||
| 159 | /** |
|
| 160 | * @dev Returns the smallest of two numbers. |
|
| 161 | */ |
|
| 162 | 0 | function min(uint256 a, uint256 b) internal pure returns (uint256) { |
| 163 | 0 | return ternary(a < b, a, b); |
| 164 | } |
|
| 165 | ||
| 166 | /** |
|
| 167 | * @dev Returns the average of two numbers. The result is rounded towards |
|
| 168 | * zero. |
|
| 169 | */ |
|
| 170 | function average(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 171 | // (a + b) / 2 can overflow. |
|
| 172 | return (a & b) + (a ^ b) / 2; |
|
| 173 | } |
|
| 174 | ||
| 175 | /** |
|
| 176 | * @dev Returns the ceiling of the division of two numbers. |
|
| 177 | * |
|
| 178 | * This differs from standard division with `/` in that it rounds towards infinity instead |
|
| 179 | * of rounding towards zero. |
|
| 180 | */ |
|
| 181 | function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 182 | if (b == 0) { |
|
| 183 | // Guarantee the same behavior as in a regular Solidity division. |
|
| 184 | Panic.panic(Panic.DIVISION_BY_ZERO); |
|
| 185 | } |
|
| 186 | ||
| 187 | // The following calculation ensures accurate ceiling division without overflow. |
|
| 188 | // Since a is non-zero, (a - 1) / b will not overflow. |
|
| 189 | // The largest possible result occurs when (a - 1) / b is type(uint256).max, |
|
| 190 | // but the largest value we can obtain is type(uint256).max - 1, which happens |
|
| 191 | // when a = type(uint256).max and b = 1. |
|
| 192 | unchecked { |
|
| 193 | return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); |
|
| 194 | } |
|
| 195 | } |
|
| 196 | ||
| 197 | /** |
|
| 198 | * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or |
|
| 199 | * denominator == 0. |
|
| 200 | * |
|
| 201 | * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by |
|
| 202 | * Uniswap Labs also under MIT license. |
|
| 203 | */ |
|
| 204 | 38× | function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { |
| 205 | unchecked { |
|
| 206 | 84× | (uint256 high, uint256 low) = mul512(x, y); |
| 207 | ||
| 208 | // Handle non-overflow cases, 256 by 256 division. |
|
| 209 | 39× | if (high == 0) { |
| 210 | // Solidity will revert if denominator == 0, unlike the div opcode on its own. |
|
| 211 | // The surrounding unchecked block does not change this fact. |
|
| 212 | // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. |
|
| 213 | 86× | return low / denominator; |
| 214 | } |
|
| 215 | ||
| 216 | // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. |
|
| 217 | 20× | if (denominator <= high) { |
| 218 | 24× | Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); |
| 219 | } |
|
| 220 | ||
| 221 | /////////////////////////////////////////////// |
|
| 222 | // 512 by 256 division. |
|
| 223 | /////////////////////////////////////////////// |
|
| 224 | ||
| 225 | // Make division exact by subtracting the remainder from [high low]. |
|
| 226 | 4× | uint256 remainder; |
| 227 | assembly ("memory-safe") { |
|
| 228 | // Compute remainder using mulmod. |
|
| 229 | 16× | remainder := mulmod(x, y, denominator) |
| 230 | ||
| 231 | // Subtract 256 bit number from 512 bit number. |
|
| 232 | 24× | high := sub(high, gt(remainder, low)) |
| 233 | 16× | low := sub(low, remainder) |
| 234 | } |
|
| 235 | ||
| 236 | // Factor powers of two out of denominator and compute largest power of two divisor of denominator. |
|
| 237 | // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. |
|
| 238 | ||
| 239 | 24× | uint256 twos = denominator & (0 - denominator); |
| 240 | assembly ("memory-safe") { |
|
| 241 | // Divide denominator by twos. |
|
| 242 | 20× | denominator := div(denominator, twos) |
| 243 | ||
| 244 | // Divide [high low] by twos. |
|
| 245 | 16× | low := div(low, twos) |
| 246 | ||
| 247 | // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. |
|
| 248 | 36× | twos := add(div(sub(0, twos), twos), 1) |
| 249 | } |
|
| 250 | ||
| 251 | // Shift in bits from high into low. |
|
| 252 | 32× | low |= high * twos; |
| 253 | ||
| 254 | // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such |
|
| 255 | // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for |
|
| 256 | // four bits. That is, denominator * inv ≡ 1 mod 2⁴. |
|
| 257 | 24× | uint256 inverse = (3 * denominator) ^ 2; |
| 258 | ||
| 259 | // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also |
|
| 260 | // works in modular arithmetic, doubling the correct bits in each step. |
|
| 261 | 24× | inverse *= 2 - denominator * inverse; // inverse mod 2⁸ |
| 262 | 24× | inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ |
| 263 | 24× | inverse *= 2 - denominator * inverse; // inverse mod 2³² |
| 264 | 24× | inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ |
| 265 | 24× | inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ |
| 266 | 28× | inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶ |
| 267 | ||
| 268 | // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. |
|
| 269 | // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is |
|
| 270 | // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high |
|
| 271 | // is no longer required. |
|
| 272 | 8× | result = low * inverse; |
| 273 | return result; |
|
| 274 | } |
|
| 275 | } |
|
| 276 | ||
| 277 | /** |
|
| 278 | * @dev Calculates x * y / denominator with full precision, following the selected rounding direction. |
|
| 279 | */ |
|
| 280 | 18× | function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { |
| 281 | 210× | return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0); |
| 282 | } |
|
| 283 | ||
| 284 | /** |
|
| 285 | * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256. |
|
| 286 | */ |
|
| 287 | function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) { |
|
| 288 | unchecked { |
|
| 289 | (uint256 high, uint256 low) = mul512(x, y); |
|
| 290 | if (high >= 1 << n) { |
|
| 291 | Panic.panic(Panic.UNDER_OVERFLOW); |
|
| 292 | } |
|
| 293 | return (high << (256 - n)) | (low >> n); |
|
| 294 | } |
|
| 295 | } |
|
| 296 | ||
| 297 | /** |
|
| 298 | * @dev Calculates x * y >> n with full precision, following the selected rounding direction. |
|
| 299 | */ |
|
| 300 | function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) { |
|
| 301 | return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0); |
|
| 302 | } |
|
| 303 | ||
| 304 | /** |
|
| 305 | * @dev Calculate the modular multiplicative inverse of a number in Z/nZ. |
|
| 306 | * |
|
| 307 | * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0. |
|
| 308 | * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. |
|
| 309 | * |
|
| 310 | * If the input value is not inversible, 0 is returned. |
|
| 311 | * |
|
| 312 | * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the |
|
| 313 | * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}. |
|
| 314 | */ |
|
| 315 | function invMod(uint256 a, uint256 n) internal pure returns (uint256) { |
|
| 316 | unchecked { |
|
| 317 | if (n == 0) return 0; |
|
| 318 | ||
| 319 | // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version) |
|
| 320 | // Used to compute integers x and y such that: ax + ny = gcd(a, n). |
|
| 321 | // When the gcd is 1, then the inverse of a modulo n exists and it's x. |
|
| 322 | // ax + ny = 1 |
|
| 323 | // ax = 1 + (-y)n |
|
| 324 | // ax ≡ 1 (mod n) # x is the inverse of a modulo n |
|
| 325 | ||
| 326 | // If the remainder is 0 the gcd is n right away. |
|
| 327 | uint256 remainder = a % n; |
|
| 328 | uint256 gcd = n; |
|
| 329 | ||
| 330 | // Therefore the initial coefficients are: |
|
| 331 | // ax + ny = gcd(a, n) = n |
|
| 332 | // 0a + 1n = n |
|
| 333 | int256 x = 0; |
|
| 334 | int256 y = 1; |
|
| 335 | ||
| 336 | while (remainder != 0) { |
|
| 337 | uint256 quotient = gcd / remainder; |
|
| 338 | ||
| 339 | (gcd, remainder) = ( |
|
| 340 | // The old remainder is the next gcd to try. |
|
| 341 | remainder, |
|
| 342 | // Compute the next remainder. |
|
| 343 | // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd |
|
| 344 | // where gcd is at most n (capped to type(uint256).max) |
|
| 345 | gcd - remainder * quotient |
|
| 346 | ); |
|
| 347 | ||
| 348 | (x, y) = ( |
|
| 349 | // Increment the coefficient of a. |
|
| 350 | y, |
|
| 351 | // Decrement the coefficient of n. |
|
| 352 | // Can overflow, but the result is casted to uint256 so that the |
|
| 353 | // next value of y is "wrapped around" to a value between 0 and n - 1. |
|
| 354 | x - y * int256(quotient) |
|
| 355 | ); |
|
| 356 | } |
|
| 357 | ||
| 358 | if (gcd != 1) return 0; // No inverse exists. |
|
| 359 | return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. |
|
| 360 | } |
|
| 361 | } |
|
| 362 | ||
| 363 | /** |
|
| 364 | * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`. |
|
| 365 | * |
|
| 366 | * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is |
|
| 367 | * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that |
|
| 368 | * `a**(p-2)` is the modular multiplicative inverse of a in Fp. |
|
| 369 | * |
|
| 370 | * NOTE: this function does NOT check that `p` is a prime greater than `2`. |
|
| 371 | */ |
|
| 372 | function invModPrime(uint256 a, uint256 p) internal view returns (uint256) { |
|
| 373 | unchecked { |
|
| 374 | return Math.modExp(a, p - 2, p); |
|
| 375 | } |
|
| 376 | } |
|
| 377 | ||
| 378 | /** |
|
| 379 | * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) |
|
| 380 | * |
|
| 381 | * Requirements: |
|
| 382 | * - modulus can't be zero |
|
| 383 | * - underlying staticcall to precompile must succeed |
|
| 384 | * |
|
| 385 | * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make |
|
| 386 | * sure the chain you're using it on supports the precompiled contract for modular exponentiation |
|
| 387 | * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, |
|
| 388 | * the underlying function will succeed given the lack of a revert, but the result may be incorrectly |
|
| 389 | * interpreted as 0. |
|
| 390 | */ |
|
| 391 | function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) { |
|
| 392 | (bool success, uint256 result) = tryModExp(b, e, m); |
|
| 393 | if (!success) { |
|
| 394 | Panic.panic(Panic.DIVISION_BY_ZERO); |
|
| 395 | } |
|
| 396 | return result; |
|
| 397 | } |
|
| 398 | ||
| 399 | /** |
|
| 400 | * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). |
|
| 401 | * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying |
|
| 402 | * to operate modulo 0 or if the underlying precompile reverted. |
|
| 403 | * |
|
| 404 | * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain |
|
| 405 | * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in |
|
| 406 | * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack |
|
| 407 | * of a revert, but the result may be incorrectly interpreted as 0. |
|
| 408 | */ |
|
| 409 | function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) { |
|
| 410 | if (m == 0) return (false, 0); |
|
| 411 | assembly ("memory-safe") { |
|
| 412 | let ptr := mload(0x40) |
|
| 413 | // | Offset | Content | Content (Hex) | |
|
| 414 | // |-----------|------------|--------------------------------------------------------------------| |
|
| 415 | // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
|
| 416 | // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
|
| 417 | // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
|
| 418 | // | 0x60:0x7f | value of b | 0x<.............................................................b> | |
|
| 419 | // | 0x80:0x9f | value of e | 0x<.............................................................e> | |
|
| 420 | // | 0xa0:0xbf | value of m | 0x<.............................................................m> | |
|
| 421 | mstore(ptr, 0x20) |
|
| 422 | mstore(add(ptr, 0x20), 0x20) |
|
| 423 | mstore(add(ptr, 0x40), 0x20) |
|
| 424 | mstore(add(ptr, 0x60), b) |
|
| 425 | mstore(add(ptr, 0x80), e) |
|
| 426 | mstore(add(ptr, 0xa0), m) |
|
| 427 | ||
| 428 | // Given the result < m, it's guaranteed to fit in 32 bytes, |
|
| 429 | // so we can use the memory scratch space located at offset 0. |
|
| 430 | success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20) |
|
| 431 | result := mload(0x00) |
|
| 432 | } |
|
| 433 | } |
|
| 434 | ||
| 435 | /** |
|
| 436 | * @dev Variant of {modExp} that supports inputs of arbitrary length. |
|
| 437 | */ |
|
| 438 | function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) { |
|
| 439 | (bool success, bytes memory result) = tryModExp(b, e, m); |
|
| 440 | if (!success) { |
|
| 441 | Panic.panic(Panic.DIVISION_BY_ZERO); |
|
| 442 | } |
|
| 443 | return result; |
|
| 444 | } |
|
| 445 | ||
| 446 | /** |
|
| 447 | * @dev Variant of {tryModExp} that supports inputs of arbitrary length. |
|
| 448 | */ |
|
| 449 | function tryModExp( |
|
| 450 | bytes memory b, |
|
| 451 | bytes memory e, |
|
| 452 | bytes memory m |
|
| 453 | ) internal view returns (bool success, bytes memory result) { |
|
| 454 | if (_zeroBytes(m)) return (false, new bytes(0)); |
|
| 455 | ||
| 456 | uint256 mLen = m.length; |
|
| 457 | ||
| 458 | // Encode call args in result and move the free memory pointer |
|
| 459 | result = abi.encodePacked(b.length, e.length, mLen, b, e, m); |
|
| 460 | ||
| 461 | assembly ("memory-safe") { |
|
| 462 | let dataPtr := add(result, 0x20) |
|
| 463 | // Write result on top of args to avoid allocating extra memory. |
|
| 464 | success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) |
|
| 465 | // Overwrite the length. |
|
| 466 | // result.length > returndatasize() is guaranteed because returndatasize() == m.length |
|
| 467 | mstore(result, mLen) |
|
| 468 | // Set the memory pointer after the returned data. |
|
| 469 | mstore(0x40, add(dataPtr, mLen)) |
|
| 470 | } |
|
| 471 | } |
|
| 472 | ||
| 473 | /** |
|
| 474 | * @dev Returns whether the provided byte array is zero. |
|
| 475 | */ |
|
| 476 | function _zeroBytes(bytes memory byteArray) private pure returns (bool) { |
|
| 477 | for (uint256 i = 0; i < byteArray.length; ++i) { |
|
| 478 | if (byteArray[i] != 0) { |
|
| 479 | return false; |
|
| 480 | } |
|
| 481 | } |
|
| 482 | return true; |
|
| 483 | } |
|
| 484 | ||
| 485 | /** |
|
| 486 | * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded |
|
| 487 | * towards zero. |
|
| 488 | * |
|
| 489 | * This method is based on Newton's method for computing square roots; the algorithm is restricted to only |
|
| 490 | * using integer operations. |
|
| 491 | */ |
|
| 492 | function sqrt(uint256 a) internal pure returns (uint256) { |
|
| 493 | unchecked { |
|
| 494 | // Take care of easy edge cases when a == 0 or a == 1 |
|
| 495 | if (a <= 1) { |
|
| 496 | return a; |
|
| 497 | } |
|
| 498 | ||
| 499 | // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a |
|
| 500 | // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between |
|
| 501 | // the current value as `ε_n = | x_n - sqrt(a) |`. |
|
| 502 | // |
|
| 503 | // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root |
|
| 504 | // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is |
|
| 505 | // bigger than any uint256. |
|
| 506 | // |
|
| 507 | // By noticing that |
|
| 508 | // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` |
|
| 509 | // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar |
|
| 510 | // to the msb function. |
|
| 511 | uint256 aa = a; |
|
| 512 | uint256 xn = 1; |
|
| 513 | ||
| 514 | if (aa >= (1 << 128)) { |
|
| 515 | aa >>= 128; |
|
| 516 | xn <<= 64; |
|
| 517 | } |
|
| 518 | if (aa >= (1 << 64)) { |
|
| 519 | aa >>= 64; |
|
| 520 | xn <<= 32; |
|
| 521 | } |
|
| 522 | if (aa >= (1 << 32)) { |
|
| 523 | aa >>= 32; |
|
| 524 | xn <<= 16; |
|
| 525 | } |
|
| 526 | if (aa >= (1 << 16)) { |
|
| 527 | aa >>= 16; |
|
| 528 | xn <<= 8; |
|
| 529 | } |
|
| 530 | if (aa >= (1 << 8)) { |
|
| 531 | aa >>= 8; |
|
| 532 | xn <<= 4; |
|
| 533 | } |
|
| 534 | if (aa >= (1 << 4)) { |
|
| 535 | aa >>= 4; |
|
| 536 | xn <<= 2; |
|
| 537 | } |
|
| 538 | if (aa >= (1 << 2)) { |
|
| 539 | xn <<= 1; |
|
| 540 | } |
|
| 541 | ||
| 542 | // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). |
|
| 543 | // |
|
| 544 | // We can refine our estimation by noticing that the middle of that interval minimizes the error. |
|
| 545 | // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). |
|
| 546 | // This is going to be our x_0 (and ε_0) |
|
| 547 | xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) |
|
| 548 | ||
| 549 | // From here, Newton's method give us: |
|
| 550 | // x_{n+1} = (x_n + a / x_n) / 2 |
|
| 551 | // |
|
| 552 | // One should note that: |
|
| 553 | // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a |
|
| 554 | // = ((x_n² + a) / (2 * x_n))² - a |
|
| 555 | // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a |
|
| 556 | // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) |
|
| 557 | // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) |
|
| 558 | // = (x_n² - a)² / (2 * x_n)² |
|
| 559 | // = ((x_n² - a) / (2 * x_n))² |
|
| 560 | // ≥ 0 |
|
| 561 | // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n |
|
| 562 | // |
|
| 563 | // This gives us the proof of quadratic convergence of the sequence: |
|
| 564 | // ε_{n+1} = | x_{n+1} - sqrt(a) | |
|
| 565 | // = | (x_n + a / x_n) / 2 - sqrt(a) | |
|
| 566 | // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | |
|
| 567 | // = | (x_n - sqrt(a))² / (2 * x_n) | |
|
| 568 | // = | ε_n² / (2 * x_n) | |
|
| 569 | // = ε_n² / | (2 * x_n) | |
|
| 570 | // |
|
| 571 | // For the first iteration, we have a special case where x_0 is known: |
|
| 572 | // ε_1 = ε_0² / | (2 * x_0) | |
|
| 573 | // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) |
|
| 574 | // ≤ 2**(2*e-4) / (3 * 2**(e-1)) |
|
| 575 | // ≤ 2**(e-3) / 3 |
|
| 576 | // ≤ 2**(e-3-log2(3)) |
|
| 577 | // ≤ 2**(e-4.5) |
|
| 578 | // |
|
| 579 | // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: |
|
| 580 | // ε_{n+1} = ε_n² / | (2 * x_n) | |
|
| 581 | // ≤ (2**(e-k))² / (2 * 2**(e-1)) |
|
| 582 | // ≤ 2**(2*e-2*k) / 2**e |
|
| 583 | // ≤ 2**(e-2*k) |
|
| 584 | xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above |
|
| 585 | xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 |
|
| 586 | xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 |
|
| 587 | xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 |
|
| 588 | xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 |
|
| 589 | xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 |
|
| 590 | ||
| 591 | // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision |
|
| 592 | // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either |
|
| 593 | // sqrt(a) or sqrt(a) + 1. |
|
| 594 | return xn - SafeCast.toUint(xn > a / xn); |
|
| 595 | } |
|
| 596 | } |
|
| 597 | ||
| 598 | /** |
|
| 599 | * @dev Calculates sqrt(a), following the selected rounding direction. |
|
| 600 | */ |
|
| 601 | function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { |
|
| 602 | unchecked { |
|
| 603 | uint256 result = sqrt(a); |
|
| 604 | return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a); |
|
| 605 | } |
|
| 606 | } |
|
| 607 | ||
| 608 | /** |
|
| 609 | * @dev Return the log in base 2 of a positive value rounded towards zero. |
|
| 610 | * Returns 0 if given 0. |
|
| 611 | */ |
|
| 612 | function log2(uint256 x) internal pure returns (uint256 r) { |
|
| 613 | // If value has upper 128 bits set, log2 result is at least 128 |
|
| 614 | r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; |
|
| 615 | // If upper 64 bits of 128-bit half set, add 64 to result |
|
| 616 | r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; |
|
| 617 | // If upper 32 bits of 64-bit half set, add 32 to result |
|
| 618 | r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; |
|
| 619 | // If upper 16 bits of 32-bit half set, add 16 to result |
|
| 620 | r |= SafeCast.toUint((x >> r) > 0xffff) << 4; |
|
| 621 | // If upper 8 bits of 16-bit half set, add 8 to result |
|
| 622 | r |= SafeCast.toUint((x >> r) > 0xff) << 3; |
|
| 623 | // If upper 4 bits of 8-bit half set, add 4 to result |
|
| 624 | r |= SafeCast.toUint((x >> r) > 0xf) << 2; |
|
| 625 | ||
| 626 | // Shifts value right by the current result and use it as an index into this lookup table: |
|
| 627 | // |
|
| 628 | // | x (4 bits) | index | table[index] = MSB position | |
|
| 629 | // |------------|---------|-----------------------------| |
|
| 630 | // | 0000 | 0 | table[0] = 0 | |
|
| 631 | // | 0001 | 1 | table[1] = 0 | |
|
| 632 | // | 0010 | 2 | table[2] = 1 | |
|
| 633 | // | 0011 | 3 | table[3] = 1 | |
|
| 634 | // | 0100 | 4 | table[4] = 2 | |
|
| 635 | // | 0101 | 5 | table[5] = 2 | |
|
| 636 | // | 0110 | 6 | table[6] = 2 | |
|
| 637 | // | 0111 | 7 | table[7] = 2 | |
|
| 638 | // | 1000 | 8 | table[8] = 3 | |
|
| 639 | // | 1001 | 9 | table[9] = 3 | |
|
| 640 | // | 1010 | 10 | table[10] = 3 | |
|
| 641 | // | 1011 | 11 | table[11] = 3 | |
|
| 642 | // | 1100 | 12 | table[12] = 3 | |
|
| 643 | // | 1101 | 13 | table[13] = 3 | |
|
| 644 | // | 1110 | 14 | table[14] = 3 | |
|
| 645 | // | 1111 | 15 | table[15] = 3 | |
|
| 646 | // |
|
| 647 | // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes. |
|
| 648 | assembly ("memory-safe") { |
|
| 649 | r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000)) |
|
| 650 | } |
|
| 651 | } |
|
| 652 | ||
| 653 | /** |
|
| 654 | * @dev Return the log in base 2, following the selected rounding direction, of a positive value. |
|
| 655 | * Returns 0 if given 0. |
|
| 656 | */ |
|
| 657 | function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { |
|
| 658 | unchecked { |
|
| 659 | uint256 result = log2(value); |
|
| 660 | return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value); |
|
| 661 | } |
|
| 662 | } |
|
| 663 | ||
| 664 | /** |
|
| 665 | * @dev Return the log in base 10 of a positive value rounded towards zero. |
|
| 666 | * Returns 0 if given 0. |
|
| 667 | */ |
|
| 668 | 0 | function log10(uint256 value) internal pure returns (uint256) { |
| 669 | uint256 result = 0; |
|
| 670 | unchecked { |
|
| 671 | 0 | if (value >= 10 ** 64) { |
| 672 | 0 | value /= 10 ** 64; |
| 673 | 0 | result += 64; |
| 674 | } |
|
| 675 | 0 | if (value >= 10 ** 32) { |
| 676 | 0 | value /= 10 ** 32; |
| 677 | 0 | result += 32; |
| 678 | } |
|
| 679 | 0 | if (value >= 10 ** 16) { |
| 680 | 0 | value /= 10 ** 16; |
| 681 | 0 | result += 16; |
| 682 | } |
|
| 683 | 0 | if (value >= 10 ** 8) { |
| 684 | 0 | value /= 10 ** 8; |
| 685 | 0 | result += 8; |
| 686 | } |
|
| 687 | 0 | if (value >= 10 ** 4) { |
| 688 | 0 | value /= 10 ** 4; |
| 689 | 0 | result += 4; |
| 690 | } |
|
| 691 | 0 | if (value >= 10 ** 2) { |
| 692 | 0 | value /= 10 ** 2; |
| 693 | 0 | result += 2; |
| 694 | } |
|
| 695 | 0 | if (value >= 10 ** 1) { |
| 696 | 0 | result += 1; |
| 697 | } |
|
| 698 | } |
|
| 699 | 0 | return result; |
| 700 | } |
|
| 701 | ||
| 702 | /** |
|
| 703 | * @dev Return the log in base 10, following the selected rounding direction, of a positive value. |
|
| 704 | * Returns 0 if given 0. |
|
| 705 | */ |
|
| 706 | function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { |
|
| 707 | unchecked { |
|
| 708 | uint256 result = log10(value); |
|
| 709 | return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value); |
|
| 710 | } |
|
| 711 | } |
|
| 712 | ||
| 713 | /** |
|
| 714 | * @dev Return the log in base 256 of a positive value rounded towards zero. |
|
| 715 | * Returns 0 if given 0. |
|
| 716 | * |
|
| 717 | * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. |
|
| 718 | */ |
|
| 719 | function log256(uint256 x) internal pure returns (uint256 r) { |
|
| 720 | // If value has upper 128 bits set, log2 result is at least 128 |
|
| 721 | r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; |
|
| 722 | // If upper 64 bits of 128-bit half set, add 64 to result |
|
| 723 | r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; |
|
| 724 | // If upper 32 bits of 64-bit half set, add 32 to result |
|
| 725 | r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; |
|
| 726 | // If upper 16 bits of 32-bit half set, add 16 to result |
|
| 727 | r |= SafeCast.toUint((x >> r) > 0xffff) << 4; |
|
| 728 | // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8 |
|
| 729 | return (r >> 3) | SafeCast.toUint((x >> r) > 0xff); |
|
| 730 | } |
|
| 731 | ||
| 732 | /** |
|
| 733 | * @dev Return the log in base 256, following the selected rounding direction, of a positive value. |
|
| 734 | * Returns 0 if given 0. |
|
| 735 | */ |
|
| 736 | function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { |
|
| 737 | unchecked { |
|
| 738 | uint256 result = log256(value); |
|
| 739 | return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value); |
|
| 740 | } |
|
| 741 | } |
|
| 742 | ||
| 743 | /** |
|
| 744 | * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. |
|
| 745 | */ |
|
| 746 | 42× | function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { |
| 747 | 147× | return uint8(rounding) % 2 == 1; |
| 748 | } |
|
| 749 | } |
9%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
Lines covered: 2 / 22 (9%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol) |
|
| 3 | // This file was procedurally generated from scripts/generate/templates/SafeCast.js. |
|
| 4 | ||
| 5 | pragma solidity ^0.8.20; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow |
|
| 9 | * checks. |
|
| 10 | * |
|
| 11 | * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can |
|
| 12 | * easily result in undesired exploitation or bugs, since developers usually |
|
| 13 | * assume that overflows raise errors. `SafeCast` restores this intuition by |
|
| 14 | * reverting the transaction when such an operation overflows. |
|
| 15 | * |
|
| 16 | * Using this library instead of the unchecked operations eliminates an entire |
|
| 17 | * class of bugs, so it's recommended to use it always. |
|
| 18 | */ |
|
| 19 | 0 | library SafeCast { |
| 20 | /** |
|
| 21 | * @dev Value doesn't fit in an uint of `bits` size. |
|
| 22 | */ |
|
| 23 | error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); |
|
| 24 | ||
| 25 | /** |
|
| 26 | * @dev An int value doesn't fit in an uint of `bits` size. |
|
| 27 | */ |
|
| 28 | error SafeCastOverflowedIntToUint(int256 value); |
|
| 29 | ||
| 30 | /** |
|
| 31 | * @dev Value doesn't fit in an int of `bits` size. |
|
| 32 | */ |
|
| 33 | error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); |
|
| 34 | ||
| 35 | /** |
|
| 36 | * @dev An uint value doesn't fit in an int of `bits` size. |
|
| 37 | */ |
|
| 38 | error SafeCastOverflowedUintToInt(uint256 value); |
|
| 39 | ||
| 40 | /** |
|
| 41 | * @dev Returns the downcasted uint248 from uint256, reverting on |
|
| 42 | * overflow (when the input is greater than largest uint248). |
|
| 43 | * |
|
| 44 | * Counterpart to Solidity's `uint248` operator. |
|
| 45 | * |
|
| 46 | * Requirements: |
|
| 47 | * |
|
| 48 | * - input must fit into 248 bits |
|
| 49 | */ |
|
| 50 | function toUint248(uint256 value) internal pure returns (uint248) { |
|
| 51 | if (value > type(uint248).max) { |
|
| 52 | revert SafeCastOverflowedUintDowncast(248, value); |
|
| 53 | } |
|
| 54 | return uint248(value); |
|
| 55 | } |
|
| 56 | ||
| 57 | /** |
|
| 58 | * @dev Returns the downcasted uint240 from uint256, reverting on |
|
| 59 | * overflow (when the input is greater than largest uint240). |
|
| 60 | * |
|
| 61 | * Counterpart to Solidity's `uint240` operator. |
|
| 62 | * |
|
| 63 | * Requirements: |
|
| 64 | * |
|
| 65 | * - input must fit into 240 bits |
|
| 66 | */ |
|
| 67 | function toUint240(uint256 value) internal pure returns (uint240) { |
|
| 68 | if (value > type(uint240).max) { |
|
| 69 | revert SafeCastOverflowedUintDowncast(240, value); |
|
| 70 | } |
|
| 71 | return uint240(value); |
|
| 72 | } |
|
| 73 | ||
| 74 | /** |
|
| 75 | * @dev Returns the downcasted uint232 from uint256, reverting on |
|
| 76 | * overflow (when the input is greater than largest uint232). |
|
| 77 | * |
|
| 78 | * Counterpart to Solidity's `uint232` operator. |
|
| 79 | * |
|
| 80 | * Requirements: |
|
| 81 | * |
|
| 82 | * - input must fit into 232 bits |
|
| 83 | */ |
|
| 84 | function toUint232(uint256 value) internal pure returns (uint232) { |
|
| 85 | if (value > type(uint232).max) { |
|
| 86 | revert SafeCastOverflowedUintDowncast(232, value); |
|
| 87 | } |
|
| 88 | return uint232(value); |
|
| 89 | } |
|
| 90 | ||
| 91 | /** |
|
| 92 | * @dev Returns the downcasted uint224 from uint256, reverting on |
|
| 93 | * overflow (when the input is greater than largest uint224). |
|
| 94 | * |
|
| 95 | * Counterpart to Solidity's `uint224` operator. |
|
| 96 | * |
|
| 97 | * Requirements: |
|
| 98 | * |
|
| 99 | * - input must fit into 224 bits |
|
| 100 | */ |
|
| 101 | function toUint224(uint256 value) internal pure returns (uint224) { |
|
| 102 | if (value > type(uint224).max) { |
|
| 103 | revert SafeCastOverflowedUintDowncast(224, value); |
|
| 104 | } |
|
| 105 | return uint224(value); |
|
| 106 | } |
|
| 107 | ||
| 108 | /** |
|
| 109 | * @dev Returns the downcasted uint216 from uint256, reverting on |
|
| 110 | * overflow (when the input is greater than largest uint216). |
|
| 111 | * |
|
| 112 | * Counterpart to Solidity's `uint216` operator. |
|
| 113 | * |
|
| 114 | * Requirements: |
|
| 115 | * |
|
| 116 | * - input must fit into 216 bits |
|
| 117 | */ |
|
| 118 | function toUint216(uint256 value) internal pure returns (uint216) { |
|
| 119 | if (value > type(uint216).max) { |
|
| 120 | revert SafeCastOverflowedUintDowncast(216, value); |
|
| 121 | } |
|
| 122 | return uint216(value); |
|
| 123 | } |
|
| 124 | ||
| 125 | /** |
|
| 126 | * @dev Returns the downcasted uint208 from uint256, reverting on |
|
| 127 | * overflow (when the input is greater than largest uint208). |
|
| 128 | * |
|
| 129 | * Counterpart to Solidity's `uint208` operator. |
|
| 130 | * |
|
| 131 | * Requirements: |
|
| 132 | * |
|
| 133 | * - input must fit into 208 bits |
|
| 134 | */ |
|
| 135 | function toUint208(uint256 value) internal pure returns (uint208) { |
|
| 136 | if (value > type(uint208).max) { |
|
| 137 | revert SafeCastOverflowedUintDowncast(208, value); |
|
| 138 | } |
|
| 139 | return uint208(value); |
|
| 140 | } |
|
| 141 | ||
| 142 | /** |
|
| 143 | * @dev Returns the downcasted uint200 from uint256, reverting on |
|
| 144 | * overflow (when the input is greater than largest uint200). |
|
| 145 | * |
|
| 146 | * Counterpart to Solidity's `uint200` operator. |
|
| 147 | * |
|
| 148 | * Requirements: |
|
| 149 | * |
|
| 150 | * - input must fit into 200 bits |
|
| 151 | */ |
|
| 152 | function toUint200(uint256 value) internal pure returns (uint200) { |
|
| 153 | if (value > type(uint200).max) { |
|
| 154 | revert SafeCastOverflowedUintDowncast(200, value); |
|
| 155 | } |
|
| 156 | return uint200(value); |
|
| 157 | } |
|
| 158 | ||
| 159 | /** |
|
| 160 | * @dev Returns the downcasted uint192 from uint256, reverting on |
|
| 161 | * overflow (when the input is greater than largest uint192). |
|
| 162 | * |
|
| 163 | * Counterpart to Solidity's `uint192` operator. |
|
| 164 | * |
|
| 165 | * Requirements: |
|
| 166 | * |
|
| 167 | * - input must fit into 192 bits |
|
| 168 | */ |
|
| 169 | function toUint192(uint256 value) internal pure returns (uint192) { |
|
| 170 | if (value > type(uint192).max) { |
|
| 171 | revert SafeCastOverflowedUintDowncast(192, value); |
|
| 172 | } |
|
| 173 | return uint192(value); |
|
| 174 | } |
|
| 175 | ||
| 176 | /** |
|
| 177 | * @dev Returns the downcasted uint184 from uint256, reverting on |
|
| 178 | * overflow (when the input is greater than largest uint184). |
|
| 179 | * |
|
| 180 | * Counterpart to Solidity's `uint184` operator. |
|
| 181 | * |
|
| 182 | * Requirements: |
|
| 183 | * |
|
| 184 | * - input must fit into 184 bits |
|
| 185 | */ |
|
| 186 | function toUint184(uint256 value) internal pure returns (uint184) { |
|
| 187 | if (value > type(uint184).max) { |
|
| 188 | revert SafeCastOverflowedUintDowncast(184, value); |
|
| 189 | } |
|
| 190 | return uint184(value); |
|
| 191 | } |
|
| 192 | ||
| 193 | /** |
|
| 194 | * @dev Returns the downcasted uint176 from uint256, reverting on |
|
| 195 | * overflow (when the input is greater than largest uint176). |
|
| 196 | * |
|
| 197 | * Counterpart to Solidity's `uint176` operator. |
|
| 198 | * |
|
| 199 | * Requirements: |
|
| 200 | * |
|
| 201 | * - input must fit into 176 bits |
|
| 202 | */ |
|
| 203 | function toUint176(uint256 value) internal pure returns (uint176) { |
|
| 204 | if (value > type(uint176).max) { |
|
| 205 | revert SafeCastOverflowedUintDowncast(176, value); |
|
| 206 | } |
|
| 207 | return uint176(value); |
|
| 208 | } |
|
| 209 | ||
| 210 | /** |
|
| 211 | * @dev Returns the downcasted uint168 from uint256, reverting on |
|
| 212 | * overflow (when the input is greater than largest uint168). |
|
| 213 | * |
|
| 214 | * Counterpart to Solidity's `uint168` operator. |
|
| 215 | * |
|
| 216 | * Requirements: |
|
| 217 | * |
|
| 218 | * - input must fit into 168 bits |
|
| 219 | */ |
|
| 220 | function toUint168(uint256 value) internal pure returns (uint168) { |
|
| 221 | if (value > type(uint168).max) { |
|
| 222 | revert SafeCastOverflowedUintDowncast(168, value); |
|
| 223 | } |
|
| 224 | return uint168(value); |
|
| 225 | } |
|
| 226 | ||
| 227 | /** |
|
| 228 | * @dev Returns the downcasted uint160 from uint256, reverting on |
|
| 229 | * overflow (when the input is greater than largest uint160). |
|
| 230 | * |
|
| 231 | * Counterpart to Solidity's `uint160` operator. |
|
| 232 | * |
|
| 233 | * Requirements: |
|
| 234 | * |
|
| 235 | * - input must fit into 160 bits |
|
| 236 | */ |
|
| 237 | 0 | function toUint160(uint256 value) internal pure returns (uint160) { |
| 238 | 0 | if (value > type(uint160).max) { |
| 239 | 0 | revert SafeCastOverflowedUintDowncast(160, value); |
| 240 | } |
|
| 241 | 0 | return uint160(value); |
| 242 | } |
|
| 243 | ||
| 244 | /** |
|
| 245 | * @dev Returns the downcasted uint152 from uint256, reverting on |
|
| 246 | * overflow (when the input is greater than largest uint152). |
|
| 247 | * |
|
| 248 | * Counterpart to Solidity's `uint152` operator. |
|
| 249 | * |
|
| 250 | * Requirements: |
|
| 251 | * |
|
| 252 | * - input must fit into 152 bits |
|
| 253 | */ |
|
| 254 | function toUint152(uint256 value) internal pure returns (uint152) { |
|
| 255 | if (value > type(uint152).max) { |
|
| 256 | revert SafeCastOverflowedUintDowncast(152, value); |
|
| 257 | } |
|
| 258 | return uint152(value); |
|
| 259 | } |
|
| 260 | ||
| 261 | /** |
|
| 262 | * @dev Returns the downcasted uint144 from uint256, reverting on |
|
| 263 | * overflow (when the input is greater than largest uint144). |
|
| 264 | * |
|
| 265 | * Counterpart to Solidity's `uint144` operator. |
|
| 266 | * |
|
| 267 | * Requirements: |
|
| 268 | * |
|
| 269 | * - input must fit into 144 bits |
|
| 270 | */ |
|
| 271 | function toUint144(uint256 value) internal pure returns (uint144) { |
|
| 272 | if (value > type(uint144).max) { |
|
| 273 | revert SafeCastOverflowedUintDowncast(144, value); |
|
| 274 | } |
|
| 275 | return uint144(value); |
|
| 276 | } |
|
| 277 | ||
| 278 | /** |
|
| 279 | * @dev Returns the downcasted uint136 from uint256, reverting on |
|
| 280 | * overflow (when the input is greater than largest uint136). |
|
| 281 | * |
|
| 282 | * Counterpart to Solidity's `uint136` operator. |
|
| 283 | * |
|
| 284 | * Requirements: |
|
| 285 | * |
|
| 286 | * - input must fit into 136 bits |
|
| 287 | */ |
|
| 288 | function toUint136(uint256 value) internal pure returns (uint136) { |
|
| 289 | if (value > type(uint136).max) { |
|
| 290 | revert SafeCastOverflowedUintDowncast(136, value); |
|
| 291 | } |
|
| 292 | return uint136(value); |
|
| 293 | } |
|
| 294 | ||
| 295 | /** |
|
| 296 | * @dev Returns the downcasted uint128 from uint256, reverting on |
|
| 297 | * overflow (when the input is greater than largest uint128). |
|
| 298 | * |
|
| 299 | * Counterpart to Solidity's `uint128` operator. |
|
| 300 | * |
|
| 301 | * Requirements: |
|
| 302 | * |
|
| 303 | * - input must fit into 128 bits |
|
| 304 | */ |
|
| 305 | 0 | function toUint128(uint256 value) internal pure returns (uint128) { |
| 306 | 0 | if (value > type(uint128).max) { |
| 307 | 0 | revert SafeCastOverflowedUintDowncast(128, value); |
| 308 | } |
|
| 309 | 0 | return uint128(value); |
| 310 | } |
|
| 311 | ||
| 312 | /** |
|
| 313 | * @dev Returns the downcasted uint120 from uint256, reverting on |
|
| 314 | * overflow (when the input is greater than largest uint120). |
|
| 315 | * |
|
| 316 | * Counterpart to Solidity's `uint120` operator. |
|
| 317 | * |
|
| 318 | * Requirements: |
|
| 319 | * |
|
| 320 | * - input must fit into 120 bits |
|
| 321 | */ |
|
| 322 | function toUint120(uint256 value) internal pure returns (uint120) { |
|
| 323 | if (value > type(uint120).max) { |
|
| 324 | revert SafeCastOverflowedUintDowncast(120, value); |
|
| 325 | } |
|
| 326 | return uint120(value); |
|
| 327 | } |
|
| 328 | ||
| 329 | /** |
|
| 330 | * @dev Returns the downcasted uint112 from uint256, reverting on |
|
| 331 | * overflow (when the input is greater than largest uint112). |
|
| 332 | * |
|
| 333 | * Counterpart to Solidity's `uint112` operator. |
|
| 334 | * |
|
| 335 | * Requirements: |
|
| 336 | * |
|
| 337 | * - input must fit into 112 bits |
|
| 338 | */ |
|
| 339 | function toUint112(uint256 value) internal pure returns (uint112) { |
|
| 340 | if (value > type(uint112).max) { |
|
| 341 | revert SafeCastOverflowedUintDowncast(112, value); |
|
| 342 | } |
|
| 343 | return uint112(value); |
|
| 344 | } |
|
| 345 | ||
| 346 | /** |
|
| 347 | * @dev Returns the downcasted uint104 from uint256, reverting on |
|
| 348 | * overflow (when the input is greater than largest uint104). |
|
| 349 | * |
|
| 350 | * Counterpart to Solidity's `uint104` operator. |
|
| 351 | * |
|
| 352 | * Requirements: |
|
| 353 | * |
|
| 354 | * - input must fit into 104 bits |
|
| 355 | */ |
|
| 356 | function toUint104(uint256 value) internal pure returns (uint104) { |
|
| 357 | if (value > type(uint104).max) { |
|
| 358 | revert SafeCastOverflowedUintDowncast(104, value); |
|
| 359 | } |
|
| 360 | return uint104(value); |
|
| 361 | } |
|
| 362 | ||
| 363 | /** |
|
| 364 | * @dev Returns the downcasted uint96 from uint256, reverting on |
|
| 365 | * overflow (when the input is greater than largest uint96). |
|
| 366 | * |
|
| 367 | * Counterpart to Solidity's `uint96` operator. |
|
| 368 | * |
|
| 369 | * Requirements: |
|
| 370 | * |
|
| 371 | * - input must fit into 96 bits |
|
| 372 | */ |
|
| 373 | function toUint96(uint256 value) internal pure returns (uint96) { |
|
| 374 | if (value > type(uint96).max) { |
|
| 375 | revert SafeCastOverflowedUintDowncast(96, value); |
|
| 376 | } |
|
| 377 | return uint96(value); |
|
| 378 | } |
|
| 379 | ||
| 380 | /** |
|
| 381 | * @dev Returns the downcasted uint88 from uint256, reverting on |
|
| 382 | * overflow (when the input is greater than largest uint88). |
|
| 383 | * |
|
| 384 | * Counterpart to Solidity's `uint88` operator. |
|
| 385 | * |
|
| 386 | * Requirements: |
|
| 387 | * |
|
| 388 | * - input must fit into 88 bits |
|
| 389 | */ |
|
| 390 | function toUint88(uint256 value) internal pure returns (uint88) { |
|
| 391 | if (value > type(uint88).max) { |
|
| 392 | revert SafeCastOverflowedUintDowncast(88, value); |
|
| 393 | } |
|
| 394 | return uint88(value); |
|
| 395 | } |
|
| 396 | ||
| 397 | /** |
|
| 398 | * @dev Returns the downcasted uint80 from uint256, reverting on |
|
| 399 | * overflow (when the input is greater than largest uint80). |
|
| 400 | * |
|
| 401 | * Counterpart to Solidity's `uint80` operator. |
|
| 402 | * |
|
| 403 | * Requirements: |
|
| 404 | * |
|
| 405 | * - input must fit into 80 bits |
|
| 406 | */ |
|
| 407 | function toUint80(uint256 value) internal pure returns (uint80) { |
|
| 408 | if (value > type(uint80).max) { |
|
| 409 | revert SafeCastOverflowedUintDowncast(80, value); |
|
| 410 | } |
|
| 411 | return uint80(value); |
|
| 412 | } |
|
| 413 | ||
| 414 | /** |
|
| 415 | * @dev Returns the downcasted uint72 from uint256, reverting on |
|
| 416 | * overflow (when the input is greater than largest uint72). |
|
| 417 | * |
|
| 418 | * Counterpart to Solidity's `uint72` operator. |
|
| 419 | * |
|
| 420 | * Requirements: |
|
| 421 | * |
|
| 422 | * - input must fit into 72 bits |
|
| 423 | */ |
|
| 424 | function toUint72(uint256 value) internal pure returns (uint72) { |
|
| 425 | if (value > type(uint72).max) { |
|
| 426 | revert SafeCastOverflowedUintDowncast(72, value); |
|
| 427 | } |
|
| 428 | return uint72(value); |
|
| 429 | } |
|
| 430 | ||
| 431 | /** |
|
| 432 | * @dev Returns the downcasted uint64 from uint256, reverting on |
|
| 433 | * overflow (when the input is greater than largest uint64). |
|
| 434 | * |
|
| 435 | * Counterpart to Solidity's `uint64` operator. |
|
| 436 | * |
|
| 437 | * Requirements: |
|
| 438 | * |
|
| 439 | * - input must fit into 64 bits |
|
| 440 | */ |
|
| 441 | 0 | function toUint64(uint256 value) internal pure returns (uint64) { |
| 442 | 0 | if (value > type(uint64).max) { |
| 443 | 0 | revert SafeCastOverflowedUintDowncast(64, value); |
| 444 | } |
|
| 445 | 0 | return uint64(value); |
| 446 | } |
|
| 447 | ||
| 448 | /** |
|
| 449 | * @dev Returns the downcasted uint56 from uint256, reverting on |
|
| 450 | * overflow (when the input is greater than largest uint56). |
|
| 451 | * |
|
| 452 | * Counterpart to Solidity's `uint56` operator. |
|
| 453 | * |
|
| 454 | * Requirements: |
|
| 455 | * |
|
| 456 | * - input must fit into 56 bits |
|
| 457 | */ |
|
| 458 | function toUint56(uint256 value) internal pure returns (uint56) { |
|
| 459 | if (value > type(uint56).max) { |
|
| 460 | revert SafeCastOverflowedUintDowncast(56, value); |
|
| 461 | } |
|
| 462 | return uint56(value); |
|
| 463 | } |
|
| 464 | ||
| 465 | /** |
|
| 466 | * @dev Returns the downcasted uint48 from uint256, reverting on |
|
| 467 | * overflow (when the input is greater than largest uint48). |
|
| 468 | * |
|
| 469 | * Counterpart to Solidity's `uint48` operator. |
|
| 470 | * |
|
| 471 | * Requirements: |
|
| 472 | * |
|
| 473 | * - input must fit into 48 bits |
|
| 474 | */ |
|
| 475 | 0 | function toUint48(uint256 value) internal pure returns (uint48) { |
| 476 | 0 | if (value > type(uint48).max) { |
| 477 | 0 | revert SafeCastOverflowedUintDowncast(48, value); |
| 478 | } |
|
| 479 | return uint48(value); |
|
| 480 | } |
|
| 481 | ||
| 482 | /** |
|
| 483 | * @dev Returns the downcasted uint40 from uint256, reverting on |
|
| 484 | * overflow (when the input is greater than largest uint40). |
|
| 485 | * |
|
| 486 | * Counterpart to Solidity's `uint40` operator. |
|
| 487 | * |
|
| 488 | * Requirements: |
|
| 489 | * |
|
| 490 | * - input must fit into 40 bits |
|
| 491 | */ |
|
| 492 | function toUint40(uint256 value) internal pure returns (uint40) { |
|
| 493 | if (value > type(uint40).max) { |
|
| 494 | revert SafeCastOverflowedUintDowncast(40, value); |
|
| 495 | } |
|
| 496 | return uint40(value); |
|
| 497 | } |
|
| 498 | ||
| 499 | /** |
|
| 500 | * @dev Returns the downcasted uint32 from uint256, reverting on |
|
| 501 | * overflow (when the input is greater than largest uint32). |
|
| 502 | * |
|
| 503 | * Counterpart to Solidity's `uint32` operator. |
|
| 504 | * |
|
| 505 | * Requirements: |
|
| 506 | * |
|
| 507 | * - input must fit into 32 bits |
|
| 508 | */ |
|
| 509 | function toUint32(uint256 value) internal pure returns (uint32) { |
|
| 510 | if (value > type(uint32).max) { |
|
| 511 | revert SafeCastOverflowedUintDowncast(32, value); |
|
| 512 | } |
|
| 513 | return uint32(value); |
|
| 514 | } |
|
| 515 | ||
| 516 | /** |
|
| 517 | * @dev Returns the downcasted uint24 from uint256, reverting on |
|
| 518 | * overflow (when the input is greater than largest uint24). |
|
| 519 | * |
|
| 520 | * Counterpart to Solidity's `uint24` operator. |
|
| 521 | * |
|
| 522 | * Requirements: |
|
| 523 | * |
|
| 524 | * - input must fit into 24 bits |
|
| 525 | */ |
|
| 526 | function toUint24(uint256 value) internal pure returns (uint24) { |
|
| 527 | if (value > type(uint24).max) { |
|
| 528 | revert SafeCastOverflowedUintDowncast(24, value); |
|
| 529 | } |
|
| 530 | return uint24(value); |
|
| 531 | } |
|
| 532 | ||
| 533 | /** |
|
| 534 | * @dev Returns the downcasted uint16 from uint256, reverting on |
|
| 535 | * overflow (when the input is greater than largest uint16). |
|
| 536 | * |
|
| 537 | * Counterpart to Solidity's `uint16` operator. |
|
| 538 | * |
|
| 539 | * Requirements: |
|
| 540 | * |
|
| 541 | * - input must fit into 16 bits |
|
| 542 | */ |
|
| 543 | function toUint16(uint256 value) internal pure returns (uint16) { |
|
| 544 | if (value > type(uint16).max) { |
|
| 545 | revert SafeCastOverflowedUintDowncast(16, value); |
|
| 546 | } |
|
| 547 | return uint16(value); |
|
| 548 | } |
|
| 549 | ||
| 550 | /** |
|
| 551 | * @dev Returns the downcasted uint8 from uint256, reverting on |
|
| 552 | * overflow (when the input is greater than largest uint8). |
|
| 553 | * |
|
| 554 | * Counterpart to Solidity's `uint8` operator. |
|
| 555 | * |
|
| 556 | * Requirements: |
|
| 557 | * |
|
| 558 | * - input must fit into 8 bits |
|
| 559 | */ |
|
| 560 | function toUint8(uint256 value) internal pure returns (uint8) { |
|
| 561 | if (value > type(uint8).max) { |
|
| 562 | revert SafeCastOverflowedUintDowncast(8, value); |
|
| 563 | } |
|
| 564 | return uint8(value); |
|
| 565 | } |
|
| 566 | ||
| 567 | /** |
|
| 568 | * @dev Converts a signed int256 into an unsigned uint256. |
|
| 569 | * |
|
| 570 | * Requirements: |
|
| 571 | * |
|
| 572 | * - input must be greater than or equal to 0. |
|
| 573 | */ |
|
| 574 | function toUint256(int256 value) internal pure returns (uint256) { |
|
| 575 | if (value < 0) { |
|
| 576 | revert SafeCastOverflowedIntToUint(value); |
|
| 577 | } |
|
| 578 | return uint256(value); |
|
| 579 | } |
|
| 580 | ||
| 581 | /** |
|
| 582 | * @dev Returns the downcasted int248 from int256, reverting on |
|
| 583 | * overflow (when the input is less than smallest int248 or |
|
| 584 | * greater than largest int248). |
|
| 585 | * |
|
| 586 | * Counterpart to Solidity's `int248` operator. |
|
| 587 | * |
|
| 588 | * Requirements: |
|
| 589 | * |
|
| 590 | * - input must fit into 248 bits |
|
| 591 | */ |
|
| 592 | function toInt248(int256 value) internal pure returns (int248 downcasted) { |
|
| 593 | downcasted = int248(value); |
|
| 594 | if (downcasted != value) { |
|
| 595 | revert SafeCastOverflowedIntDowncast(248, value); |
|
| 596 | } |
|
| 597 | } |
|
| 598 | ||
| 599 | /** |
|
| 600 | * @dev Returns the downcasted int240 from int256, reverting on |
|
| 601 | * overflow (when the input is less than smallest int240 or |
|
| 602 | * greater than largest int240). |
|
| 603 | * |
|
| 604 | * Counterpart to Solidity's `int240` operator. |
|
| 605 | * |
|
| 606 | * Requirements: |
|
| 607 | * |
|
| 608 | * - input must fit into 240 bits |
|
| 609 | */ |
|
| 610 | function toInt240(int256 value) internal pure returns (int240 downcasted) { |
|
| 611 | downcasted = int240(value); |
|
| 612 | if (downcasted != value) { |
|
| 613 | revert SafeCastOverflowedIntDowncast(240, value); |
|
| 614 | } |
|
| 615 | } |
|
| 616 | ||
| 617 | /** |
|
| 618 | * @dev Returns the downcasted int232 from int256, reverting on |
|
| 619 | * overflow (when the input is less than smallest int232 or |
|
| 620 | * greater than largest int232). |
|
| 621 | * |
|
| 622 | * Counterpart to Solidity's `int232` operator. |
|
| 623 | * |
|
| 624 | * Requirements: |
|
| 625 | * |
|
| 626 | * - input must fit into 232 bits |
|
| 627 | */ |
|
| 628 | function toInt232(int256 value) internal pure returns (int232 downcasted) { |
|
| 629 | downcasted = int232(value); |
|
| 630 | if (downcasted != value) { |
|
| 631 | revert SafeCastOverflowedIntDowncast(232, value); |
|
| 632 | } |
|
| 633 | } |
|
| 634 | ||
| 635 | /** |
|
| 636 | * @dev Returns the downcasted int224 from int256, reverting on |
|
| 637 | * overflow (when the input is less than smallest int224 or |
|
| 638 | * greater than largest int224). |
|
| 639 | * |
|
| 640 | * Counterpart to Solidity's `int224` operator. |
|
| 641 | * |
|
| 642 | * Requirements: |
|
| 643 | * |
|
| 644 | * - input must fit into 224 bits |
|
| 645 | */ |
|
| 646 | function toInt224(int256 value) internal pure returns (int224 downcasted) { |
|
| 647 | downcasted = int224(value); |
|
| 648 | if (downcasted != value) { |
|
| 649 | revert SafeCastOverflowedIntDowncast(224, value); |
|
| 650 | } |
|
| 651 | } |
|
| 652 | ||
| 653 | /** |
|
| 654 | * @dev Returns the downcasted int216 from int256, reverting on |
|
| 655 | * overflow (when the input is less than smallest int216 or |
|
| 656 | * greater than largest int216). |
|
| 657 | * |
|
| 658 | * Counterpart to Solidity's `int216` operator. |
|
| 659 | * |
|
| 660 | * Requirements: |
|
| 661 | * |
|
| 662 | * - input must fit into 216 bits |
|
| 663 | */ |
|
| 664 | function toInt216(int256 value) internal pure returns (int216 downcasted) { |
|
| 665 | downcasted = int216(value); |
|
| 666 | if (downcasted != value) { |
|
| 667 | revert SafeCastOverflowedIntDowncast(216, value); |
|
| 668 | } |
|
| 669 | } |
|
| 670 | ||
| 671 | /** |
|
| 672 | * @dev Returns the downcasted int208 from int256, reverting on |
|
| 673 | * overflow (when the input is less than smallest int208 or |
|
| 674 | * greater than largest int208). |
|
| 675 | * |
|
| 676 | * Counterpart to Solidity's `int208` operator. |
|
| 677 | * |
|
| 678 | * Requirements: |
|
| 679 | * |
|
| 680 | * - input must fit into 208 bits |
|
| 681 | */ |
|
| 682 | function toInt208(int256 value) internal pure returns (int208 downcasted) { |
|
| 683 | downcasted = int208(value); |
|
| 684 | if (downcasted != value) { |
|
| 685 | revert SafeCastOverflowedIntDowncast(208, value); |
|
| 686 | } |
|
| 687 | } |
|
| 688 | ||
| 689 | /** |
|
| 690 | * @dev Returns the downcasted int200 from int256, reverting on |
|
| 691 | * overflow (when the input is less than smallest int200 or |
|
| 692 | * greater than largest int200). |
|
| 693 | * |
|
| 694 | * Counterpart to Solidity's `int200` operator. |
|
| 695 | * |
|
| 696 | * Requirements: |
|
| 697 | * |
|
| 698 | * - input must fit into 200 bits |
|
| 699 | */ |
|
| 700 | function toInt200(int256 value) internal pure returns (int200 downcasted) { |
|
| 701 | downcasted = int200(value); |
|
| 702 | if (downcasted != value) { |
|
| 703 | revert SafeCastOverflowedIntDowncast(200, value); |
|
| 704 | } |
|
| 705 | } |
|
| 706 | ||
| 707 | /** |
|
| 708 | * @dev Returns the downcasted int192 from int256, reverting on |
|
| 709 | * overflow (when the input is less than smallest int192 or |
|
| 710 | * greater than largest int192). |
|
| 711 | * |
|
| 712 | * Counterpart to Solidity's `int192` operator. |
|
| 713 | * |
|
| 714 | * Requirements: |
|
| 715 | * |
|
| 716 | * - input must fit into 192 bits |
|
| 717 | */ |
|
| 718 | function toInt192(int256 value) internal pure returns (int192 downcasted) { |
|
| 719 | downcasted = int192(value); |
|
| 720 | if (downcasted != value) { |
|
| 721 | revert SafeCastOverflowedIntDowncast(192, value); |
|
| 722 | } |
|
| 723 | } |
|
| 724 | ||
| 725 | /** |
|
| 726 | * @dev Returns the downcasted int184 from int256, reverting on |
|
| 727 | * overflow (when the input is less than smallest int184 or |
|
| 728 | * greater than largest int184). |
|
| 729 | * |
|
| 730 | * Counterpart to Solidity's `int184` operator. |
|
| 731 | * |
|
| 732 | * Requirements: |
|
| 733 | * |
|
| 734 | * - input must fit into 184 bits |
|
| 735 | */ |
|
| 736 | function toInt184(int256 value) internal pure returns (int184 downcasted) { |
|
| 737 | downcasted = int184(value); |
|
| 738 | if (downcasted != value) { |
|
| 739 | revert SafeCastOverflowedIntDowncast(184, value); |
|
| 740 | } |
|
| 741 | } |
|
| 742 | ||
| 743 | /** |
|
| 744 | * @dev Returns the downcasted int176 from int256, reverting on |
|
| 745 | * overflow (when the input is less than smallest int176 or |
|
| 746 | * greater than largest int176). |
|
| 747 | * |
|
| 748 | * Counterpart to Solidity's `int176` operator. |
|
| 749 | * |
|
| 750 | * Requirements: |
|
| 751 | * |
|
| 752 | * - input must fit into 176 bits |
|
| 753 | */ |
|
| 754 | function toInt176(int256 value) internal pure returns (int176 downcasted) { |
|
| 755 | downcasted = int176(value); |
|
| 756 | if (downcasted != value) { |
|
| 757 | revert SafeCastOverflowedIntDowncast(176, value); |
|
| 758 | } |
|
| 759 | } |
|
| 760 | ||
| 761 | /** |
|
| 762 | * @dev Returns the downcasted int168 from int256, reverting on |
|
| 763 | * overflow (when the input is less than smallest int168 or |
|
| 764 | * greater than largest int168). |
|
| 765 | * |
|
| 766 | * Counterpart to Solidity's `int168` operator. |
|
| 767 | * |
|
| 768 | * Requirements: |
|
| 769 | * |
|
| 770 | * - input must fit into 168 bits |
|
| 771 | */ |
|
| 772 | function toInt168(int256 value) internal pure returns (int168 downcasted) { |
|
| 773 | downcasted = int168(value); |
|
| 774 | if (downcasted != value) { |
|
| 775 | revert SafeCastOverflowedIntDowncast(168, value); |
|
| 776 | } |
|
| 777 | } |
|
| 778 | ||
| 779 | /** |
|
| 780 | * @dev Returns the downcasted int160 from int256, reverting on |
|
| 781 | * overflow (when the input is less than smallest int160 or |
|
| 782 | * greater than largest int160). |
|
| 783 | * |
|
| 784 | * Counterpart to Solidity's `int160` operator. |
|
| 785 | * |
|
| 786 | * Requirements: |
|
| 787 | * |
|
| 788 | * - input must fit into 160 bits |
|
| 789 | */ |
|
| 790 | function toInt160(int256 value) internal pure returns (int160 downcasted) { |
|
| 791 | downcasted = int160(value); |
|
| 792 | if (downcasted != value) { |
|
| 793 | revert SafeCastOverflowedIntDowncast(160, value); |
|
| 794 | } |
|
| 795 | } |
|
| 796 | ||
| 797 | /** |
|
| 798 | * @dev Returns the downcasted int152 from int256, reverting on |
|
| 799 | * overflow (when the input is less than smallest int152 or |
|
| 800 | * greater than largest int152). |
|
| 801 | * |
|
| 802 | * Counterpart to Solidity's `int152` operator. |
|
| 803 | * |
|
| 804 | * Requirements: |
|
| 805 | * |
|
| 806 | * - input must fit into 152 bits |
|
| 807 | */ |
|
| 808 | function toInt152(int256 value) internal pure returns (int152 downcasted) { |
|
| 809 | downcasted = int152(value); |
|
| 810 | if (downcasted != value) { |
|
| 811 | revert SafeCastOverflowedIntDowncast(152, value); |
|
| 812 | } |
|
| 813 | } |
|
| 814 | ||
| 815 | /** |
|
| 816 | * @dev Returns the downcasted int144 from int256, reverting on |
|
| 817 | * overflow (when the input is less than smallest int144 or |
|
| 818 | * greater than largest int144). |
|
| 819 | * |
|
| 820 | * Counterpart to Solidity's `int144` operator. |
|
| 821 | * |
|
| 822 | * Requirements: |
|
| 823 | * |
|
| 824 | * - input must fit into 144 bits |
|
| 825 | */ |
|
| 826 | function toInt144(int256 value) internal pure returns (int144 downcasted) { |
|
| 827 | downcasted = int144(value); |
|
| 828 | if (downcasted != value) { |
|
| 829 | revert SafeCastOverflowedIntDowncast(144, value); |
|
| 830 | } |
|
| 831 | } |
|
| 832 | ||
| 833 | /** |
|
| 834 | * @dev Returns the downcasted int136 from int256, reverting on |
|
| 835 | * overflow (when the input is less than smallest int136 or |
|
| 836 | * greater than largest int136). |
|
| 837 | * |
|
| 838 | * Counterpart to Solidity's `int136` operator. |
|
| 839 | * |
|
| 840 | * Requirements: |
|
| 841 | * |
|
| 842 | * - input must fit into 136 bits |
|
| 843 | */ |
|
| 844 | function toInt136(int256 value) internal pure returns (int136 downcasted) { |
|
| 845 | downcasted = int136(value); |
|
| 846 | if (downcasted != value) { |
|
| 847 | revert SafeCastOverflowedIntDowncast(136, value); |
|
| 848 | } |
|
| 849 | } |
|
| 850 | ||
| 851 | /** |
|
| 852 | * @dev Returns the downcasted int128 from int256, reverting on |
|
| 853 | * overflow (when the input is less than smallest int128 or |
|
| 854 | * greater than largest int128). |
|
| 855 | * |
|
| 856 | * Counterpart to Solidity's `int128` operator. |
|
| 857 | * |
|
| 858 | * Requirements: |
|
| 859 | * |
|
| 860 | * - input must fit into 128 bits |
|
| 861 | */ |
|
| 862 | 0 | function toInt128(int256 value) internal pure returns (int128 downcasted) { |
| 863 | 0 | downcasted = int128(value); |
| 864 | 0 | if (downcasted != value) { |
| 865 | 0 | revert SafeCastOverflowedIntDowncast(128, value); |
| 866 | } |
|
| 867 | } |
|
| 868 | ||
| 869 | /** |
|
| 870 | * @dev Returns the downcasted int120 from int256, reverting on |
|
| 871 | * overflow (when the input is less than smallest int120 or |
|
| 872 | * greater than largest int120). |
|
| 873 | * |
|
| 874 | * Counterpart to Solidity's `int120` operator. |
|
| 875 | * |
|
| 876 | * Requirements: |
|
| 877 | * |
|
| 878 | * - input must fit into 120 bits |
|
| 879 | */ |
|
| 880 | function toInt120(int256 value) internal pure returns (int120 downcasted) { |
|
| 881 | downcasted = int120(value); |
|
| 882 | if (downcasted != value) { |
|
| 883 | revert SafeCastOverflowedIntDowncast(120, value); |
|
| 884 | } |
|
| 885 | } |
|
| 886 | ||
| 887 | /** |
|
| 888 | * @dev Returns the downcasted int112 from int256, reverting on |
|
| 889 | * overflow (when the input is less than smallest int112 or |
|
| 890 | * greater than largest int112). |
|
| 891 | * |
|
| 892 | * Counterpart to Solidity's `int112` operator. |
|
| 893 | * |
|
| 894 | * Requirements: |
|
| 895 | * |
|
| 896 | * - input must fit into 112 bits |
|
| 897 | */ |
|
| 898 | function toInt112(int256 value) internal pure returns (int112 downcasted) { |
|
| 899 | downcasted = int112(value); |
|
| 900 | if (downcasted != value) { |
|
| 901 | revert SafeCastOverflowedIntDowncast(112, value); |
|
| 902 | } |
|
| 903 | } |
|
| 904 | ||
| 905 | /** |
|
| 906 | * @dev Returns the downcasted int104 from int256, reverting on |
|
| 907 | * overflow (when the input is less than smallest int104 or |
|
| 908 | * greater than largest int104). |
|
| 909 | * |
|
| 910 | * Counterpart to Solidity's `int104` operator. |
|
| 911 | * |
|
| 912 | * Requirements: |
|
| 913 | * |
|
| 914 | * - input must fit into 104 bits |
|
| 915 | */ |
|
| 916 | function toInt104(int256 value) internal pure returns (int104 downcasted) { |
|
| 917 | downcasted = int104(value); |
|
| 918 | if (downcasted != value) { |
|
| 919 | revert SafeCastOverflowedIntDowncast(104, value); |
|
| 920 | } |
|
| 921 | } |
|
| 922 | ||
| 923 | /** |
|
| 924 | * @dev Returns the downcasted int96 from int256, reverting on |
|
| 925 | * overflow (when the input is less than smallest int96 or |
|
| 926 | * greater than largest int96). |
|
| 927 | * |
|
| 928 | * Counterpart to Solidity's `int96` operator. |
|
| 929 | * |
|
| 930 | * Requirements: |
|
| 931 | * |
|
| 932 | * - input must fit into 96 bits |
|
| 933 | */ |
|
| 934 | function toInt96(int256 value) internal pure returns (int96 downcasted) { |
|
| 935 | downcasted = int96(value); |
|
| 936 | if (downcasted != value) { |
|
| 937 | revert SafeCastOverflowedIntDowncast(96, value); |
|
| 938 | } |
|
| 939 | } |
|
| 940 | ||
| 941 | /** |
|
| 942 | * @dev Returns the downcasted int88 from int256, reverting on |
|
| 943 | * overflow (when the input is less than smallest int88 or |
|
| 944 | * greater than largest int88). |
|
| 945 | * |
|
| 946 | * Counterpart to Solidity's `int88` operator. |
|
| 947 | * |
|
| 948 | * Requirements: |
|
| 949 | * |
|
| 950 | * - input must fit into 88 bits |
|
| 951 | */ |
|
| 952 | function toInt88(int256 value) internal pure returns (int88 downcasted) { |
|
| 953 | downcasted = int88(value); |
|
| 954 | if (downcasted != value) { |
|
| 955 | revert SafeCastOverflowedIntDowncast(88, value); |
|
| 956 | } |
|
| 957 | } |
|
| 958 | ||
| 959 | /** |
|
| 960 | * @dev Returns the downcasted int80 from int256, reverting on |
|
| 961 | * overflow (when the input is less than smallest int80 or |
|
| 962 | * greater than largest int80). |
|
| 963 | * |
|
| 964 | * Counterpart to Solidity's `int80` operator. |
|
| 965 | * |
|
| 966 | * Requirements: |
|
| 967 | * |
|
| 968 | * - input must fit into 80 bits |
|
| 969 | */ |
|
| 970 | function toInt80(int256 value) internal pure returns (int80 downcasted) { |
|
| 971 | downcasted = int80(value); |
|
| 972 | if (downcasted != value) { |
|
| 973 | revert SafeCastOverflowedIntDowncast(80, value); |
|
| 974 | } |
|
| 975 | } |
|
| 976 | ||
| 977 | /** |
|
| 978 | * @dev Returns the downcasted int72 from int256, reverting on |
|
| 979 | * overflow (when the input is less than smallest int72 or |
|
| 980 | * greater than largest int72). |
|
| 981 | * |
|
| 982 | * Counterpart to Solidity's `int72` operator. |
|
| 983 | * |
|
| 984 | * Requirements: |
|
| 985 | * |
|
| 986 | * - input must fit into 72 bits |
|
| 987 | */ |
|
| 988 | function toInt72(int256 value) internal pure returns (int72 downcasted) { |
|
| 989 | downcasted = int72(value); |
|
| 990 | if (downcasted != value) { |
|
| 991 | revert SafeCastOverflowedIntDowncast(72, value); |
|
| 992 | } |
|
| 993 | } |
|
| 994 | ||
| 995 | /** |
|
| 996 | * @dev Returns the downcasted int64 from int256, reverting on |
|
| 997 | * overflow (when the input is less than smallest int64 or |
|
| 998 | * greater than largest int64). |
|
| 999 | * |
|
| 1000 | * Counterpart to Solidity's `int64` operator. |
|
| 1001 | * |
|
| 1002 | * Requirements: |
|
| 1003 | * |
|
| 1004 | * - input must fit into 64 bits |
|
| 1005 | */ |
|
| 1006 | function toInt64(int256 value) internal pure returns (int64 downcasted) { |
|
| 1007 | downcasted = int64(value); |
|
| 1008 | if (downcasted != value) { |
|
| 1009 | revert SafeCastOverflowedIntDowncast(64, value); |
|
| 1010 | } |
|
| 1011 | } |
|
| 1012 | ||
| 1013 | /** |
|
| 1014 | * @dev Returns the downcasted int56 from int256, reverting on |
|
| 1015 | * overflow (when the input is less than smallest int56 or |
|
| 1016 | * greater than largest int56). |
|
| 1017 | * |
|
| 1018 | * Counterpart to Solidity's `int56` operator. |
|
| 1019 | * |
|
| 1020 | * Requirements: |
|
| 1021 | * |
|
| 1022 | * - input must fit into 56 bits |
|
| 1023 | */ |
|
| 1024 | function toInt56(int256 value) internal pure returns (int56 downcasted) { |
|
| 1025 | downcasted = int56(value); |
|
| 1026 | if (downcasted != value) { |
|
| 1027 | revert SafeCastOverflowedIntDowncast(56, value); |
|
| 1028 | } |
|
| 1029 | } |
|
| 1030 | ||
| 1031 | /** |
|
| 1032 | * @dev Returns the downcasted int48 from int256, reverting on |
|
| 1033 | * overflow (when the input is less than smallest int48 or |
|
| 1034 | * greater than largest int48). |
|
| 1035 | * |
|
| 1036 | * Counterpart to Solidity's `int48` operator. |
|
| 1037 | * |
|
| 1038 | * Requirements: |
|
| 1039 | * |
|
| 1040 | * - input must fit into 48 bits |
|
| 1041 | */ |
|
| 1042 | function toInt48(int256 value) internal pure returns (int48 downcasted) { |
|
| 1043 | downcasted = int48(value); |
|
| 1044 | if (downcasted != value) { |
|
| 1045 | revert SafeCastOverflowedIntDowncast(48, value); |
|
| 1046 | } |
|
| 1047 | } |
|
| 1048 | ||
| 1049 | /** |
|
| 1050 | * @dev Returns the downcasted int40 from int256, reverting on |
|
| 1051 | * overflow (when the input is less than smallest int40 or |
|
| 1052 | * greater than largest int40). |
|
| 1053 | * |
|
| 1054 | * Counterpart to Solidity's `int40` operator. |
|
| 1055 | * |
|
| 1056 | * Requirements: |
|
| 1057 | * |
|
| 1058 | * - input must fit into 40 bits |
|
| 1059 | */ |
|
| 1060 | function toInt40(int256 value) internal pure returns (int40 downcasted) { |
|
| 1061 | downcasted = int40(value); |
|
| 1062 | if (downcasted != value) { |
|
| 1063 | revert SafeCastOverflowedIntDowncast(40, value); |
|
| 1064 | } |
|
| 1065 | } |
|
| 1066 | ||
| 1067 | /** |
|
| 1068 | * @dev Returns the downcasted int32 from int256, reverting on |
|
| 1069 | * overflow (when the input is less than smallest int32 or |
|
| 1070 | * greater than largest int32). |
|
| 1071 | * |
|
| 1072 | * Counterpart to Solidity's `int32` operator. |
|
| 1073 | * |
|
| 1074 | * Requirements: |
|
| 1075 | * |
|
| 1076 | * - input must fit into 32 bits |
|
| 1077 | */ |
|
| 1078 | function toInt32(int256 value) internal pure returns (int32 downcasted) { |
|
| 1079 | downcasted = int32(value); |
|
| 1080 | if (downcasted != value) { |
|
| 1081 | revert SafeCastOverflowedIntDowncast(32, value); |
|
| 1082 | } |
|
| 1083 | } |
|
| 1084 | ||
| 1085 | /** |
|
| 1086 | * @dev Returns the downcasted int24 from int256, reverting on |
|
| 1087 | * overflow (when the input is less than smallest int24 or |
|
| 1088 | * greater than largest int24). |
|
| 1089 | * |
|
| 1090 | * Counterpart to Solidity's `int24` operator. |
|
| 1091 | * |
|
| 1092 | * Requirements: |
|
| 1093 | * |
|
| 1094 | * - input must fit into 24 bits |
|
| 1095 | */ |
|
| 1096 | function toInt24(int256 value) internal pure returns (int24 downcasted) { |
|
| 1097 | downcasted = int24(value); |
|
| 1098 | if (downcasted != value) { |
|
| 1099 | revert SafeCastOverflowedIntDowncast(24, value); |
|
| 1100 | } |
|
| 1101 | } |
|
| 1102 | ||
| 1103 | /** |
|
| 1104 | * @dev Returns the downcasted int16 from int256, reverting on |
|
| 1105 | * overflow (when the input is less than smallest int16 or |
|
| 1106 | * greater than largest int16). |
|
| 1107 | * |
|
| 1108 | * Counterpart to Solidity's `int16` operator. |
|
| 1109 | * |
|
| 1110 | * Requirements: |
|
| 1111 | * |
|
| 1112 | * - input must fit into 16 bits |
|
| 1113 | */ |
|
| 1114 | function toInt16(int256 value) internal pure returns (int16 downcasted) { |
|
| 1115 | downcasted = int16(value); |
|
| 1116 | if (downcasted != value) { |
|
| 1117 | revert SafeCastOverflowedIntDowncast(16, value); |
|
| 1118 | } |
|
| 1119 | } |
|
| 1120 | ||
| 1121 | /** |
|
| 1122 | * @dev Returns the downcasted int8 from int256, reverting on |
|
| 1123 | * overflow (when the input is less than smallest int8 or |
|
| 1124 | * greater than largest int8). |
|
| 1125 | * |
|
| 1126 | * Counterpart to Solidity's `int8` operator. |
|
| 1127 | * |
|
| 1128 | * Requirements: |
|
| 1129 | * |
|
| 1130 | * - input must fit into 8 bits |
|
| 1131 | */ |
|
| 1132 | function toInt8(int256 value) internal pure returns (int8 downcasted) { |
|
| 1133 | downcasted = int8(value); |
|
| 1134 | if (downcasted != value) { |
|
| 1135 | revert SafeCastOverflowedIntDowncast(8, value); |
|
| 1136 | } |
|
| 1137 | } |
|
| 1138 | ||
| 1139 | /** |
|
| 1140 | * @dev Converts an unsigned uint256 into a signed int256. |
|
| 1141 | * |
|
| 1142 | * Requirements: |
|
| 1143 | * |
|
| 1144 | * - input must be less than or equal to maxInt256. |
|
| 1145 | */ |
|
| 1146 | function toInt256(uint256 value) internal pure returns (int256) { |
|
| 1147 | // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive |
|
| 1148 | if (value > uint256(type(int256).max)) { |
|
| 1149 | revert SafeCastOverflowedUintToInt(value); |
|
| 1150 | } |
|
| 1151 | return int256(value); |
|
| 1152 | } |
|
| 1153 | ||
| 1154 | /** |
|
| 1155 | * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. |
|
| 1156 | */ |
|
| 1157 | 7× | function toUint(bool b) internal pure returns (uint256 u) { |
| 1158 | assembly ("memory-safe") { |
|
| 1159 | 21× | u := iszero(iszero(b)) |
| 1160 | } |
|
| 1161 | } |
|
| 1162 | } |
87%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
Lines covered: 35 / 40 (87%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts (last updated v5.3.0) (utils/structs/EnumerableSet.sol) |
|
| 3 | // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. |
|
| 4 | ||
| 5 | pragma solidity ^0.8.20; |
|
| 6 | ||
| 7 | import {Arrays} from "../Arrays.sol"; |
|
| 8 | import {Math} from "../math/Math.sol"; |
|
| 9 | ||
| 10 | /** |
|
| 11 | * @dev Library for managing |
|
| 12 | * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive |
|
| 13 | * types. |
|
| 14 | * |
|
| 15 | * Sets have the following properties: |
|
| 16 | * |
|
| 17 | * - Elements are added, removed, and checked for existence in constant time |
|
| 18 | * (O(1)). |
|
| 19 | * - Elements are enumerated in O(n). No guarantees are made on the ordering. |
|
| 20 | * - Set can be cleared (all elements removed) in O(n). |
|
| 21 | * |
|
| 22 | * ```solidity |
|
| 23 | * contract Example { |
|
| 24 | * // Add the library methods |
|
| 25 | * using EnumerableSet for EnumerableSet.AddressSet; |
|
| 26 | * |
|
| 27 | * // Declare a set state variable |
|
| 28 | * EnumerableSet.AddressSet private mySet; |
|
| 29 | * } |
|
| 30 | * ``` |
|
| 31 | * |
|
| 32 | * The following types are supported: |
|
| 33 | * |
|
| 34 | * - `bytes32` (`Bytes32Set`) since v3.3.0 |
|
| 35 | * - `address` (`AddressSet`) since v3.3.0 |
|
| 36 | * - `uint256` (`UintSet`) since v3.3.0 |
|
| 37 | * - `string` (`StringSet`) since v5.4.0 |
|
| 38 | * - `bytes` (`BytesSet`) since v5.4.0 |
|
| 39 | * |
|
| 40 | * [WARNING] |
|
| 41 | * ==== |
|
| 42 | * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure |
|
| 43 | * unusable. |
|
| 44 | * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. |
|
| 45 | * |
|
| 46 | * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an |
|
| 47 | * array of EnumerableSet. |
|
| 48 | * ==== |
|
| 49 | */ |
|
| 50 | 0 | library EnumerableSet { |
| 51 | // To implement this library for multiple types with as little code |
|
| 52 | // repetition as possible, we write it in terms of a generic Set type with |
|
| 53 | // bytes32 values. |
|
| 54 | // The Set implementation uses private functions, and user-facing |
|
| 55 | // implementations (such as AddressSet) are just wrappers around the |
|
| 56 | // underlying Set. |
|
| 57 | // This means that we can only create new EnumerableSets for types that fit |
|
| 58 | // in bytes32. |
|
| 59 | ||
| 60 | struct Set { |
|
| 61 | // Storage of set values |
|
| 62 | bytes32[] _values; |
|
| 63 | // Position is the index of the value in the `values` array plus 1. |
|
| 64 | // Position 0 is used to mean a value is not in the set. |
|
| 65 | mapping(bytes32 value => uint256) _positions; |
|
| 66 | } |
|
| 67 | ||
| 68 | /** |
|
| 69 | * @dev Add a value to a set. O(1). |
|
| 70 | * |
|
| 71 | * Returns true if the value was added to the set, that is if it was not |
|
| 72 | * already present. |
|
| 73 | */ |
|
| 74 | 6× | function _add(Set storage set, bytes32 value) private returns (bool) { |
| 75 | 7× | if (!_contains(set, value)) { |
| 76 | 66× | set._values.push(value); |
| 77 | // The value is stored at length-1, but we add 1 to all indexes |
|
| 78 | // and use 0 as a sentinel value |
|
| 79 | 54× | set._positions[value] = set._values.length; |
| 80 | 6× | return true; |
| 81 | } else { |
|
| 82 | 3× | 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 | 8× | function _remove(Set storage set, bytes32 value) private returns (bool) { |
| 93 | // We cache the value's position to prevent multiple reads from the same storage slot |
|
| 94 | 48× | uint256 position = set._positions[value]; |
| 95 | ||
| 96 | 17× | if (position != 0) { |
| 97 | // Equivalent to contains(set, value) |
|
| 98 | // To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
|
| 99 | // the array, and then remove the last element (sometimes called as 'swap and pop'). |
|
| 100 | // This modifies the order of the array, as noted in {at}. |
|
| 101 | ||
| 102 | 36× | uint256 valueIndex = position - 1; |
| 103 | 52× | uint256 lastIndex = set._values.length - 1; |
| 104 | ||
| 105 | 24× | if (valueIndex != lastIndex) { |
| 106 | 88× | bytes32 lastValue = set._values[lastIndex]; |
| 107 | ||
| 108 | // Move the lastValue to the index where the value to delete is |
|
| 109 | 108× | set._values[valueIndex] = lastValue; |
| 110 | // Update the tracked position of the lastValue (that was just moved) |
|
| 111 | 56× | set._positions[lastValue] = position; |
| 112 | } |
|
| 113 | ||
| 114 | // Delete the slot where the moved value was stored |
|
| 115 | 100× | set._values.pop(); |
| 116 | ||
| 117 | // Delete the tracked position for the deleted slot |
|
| 118 | 76× | delete set._positions[value]; |
| 119 | ||
| 120 | 32× | return true; |
| 121 | } else { |
|
| 122 | 6× | 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 | 9× | function _contains(Set storage set, bytes32 value) private view returns (bool) { |
| 145 | 162× | return set._positions[value] != 0; |
| 146 | } |
|
| 147 | ||
| 148 | /** |
|
| 149 | * @dev Returns the number of values on the set. O(1). |
|
| 150 | */ |
|
| 151 | 1× | function _length(Set storage set) private view returns (uint256) { |
| 152 | 2× | return set._values.length; |
| 153 | } |
|
| 154 | ||
| 155 | /** |
|
| 156 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 157 | * |
|
| 158 | * Note that there are no guarantees on the ordering of values inside the |
|
| 159 | * array, and it may change when more values are added or removed. |
|
| 160 | * |
|
| 161 | * Requirements: |
|
| 162 | * |
|
| 163 | * - `index` must be strictly less than {length}. |
|
| 164 | */ |
|
| 165 | 0 | function _at(Set storage set, uint256 index) private view returns (bytes32) { |
| 166 | 0 | return set._values[index]; |
| 167 | } |
|
| 168 | ||
| 169 | /** |
|
| 170 | * @dev Return the entire set in an array |
|
| 171 | * |
|
| 172 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 173 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 174 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 175 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 176 | */ |
|
| 177 | 12× | function _values(Set storage set) private view returns (bytes32[] memory) { |
| 178 | 138× | return set._values; |
| 179 | } |
|
| 180 | ||
| 181 | /** |
|
| 182 | * @dev Return a slice of the set in an array |
|
| 183 | * |
|
| 184 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 185 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 186 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 187 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 188 | */ |
|
| 189 | function _values(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) { |
|
| 190 | unchecked { |
|
| 191 | end = Math.min(end, _length(set)); |
|
| 192 | start = Math.min(start, end); |
|
| 193 | ||
| 194 | uint256 len = end - start; |
|
| 195 | bytes32[] memory result = new bytes32[](len); |
|
| 196 | for (uint256 i = 0; i < len; ++i) { |
|
| 197 | result[i] = Arrays.unsafeAccess(set._values, start + i).value; |
|
| 198 | } |
|
| 199 | return result; |
|
| 200 | } |
|
| 201 | } |
|
| 202 | ||
| 203 | // Bytes32Set |
|
| 204 | ||
| 205 | struct Bytes32Set { |
|
| 206 | Set _inner; |
|
| 207 | } |
|
| 208 | ||
| 209 | /** |
|
| 210 | * @dev Add a value to a set. O(1). |
|
| 211 | * |
|
| 212 | * Returns true if the value was added to the set, that is if it was not |
|
| 213 | * already present. |
|
| 214 | */ |
|
| 215 | function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
|
| 216 | return _add(set._inner, value); |
|
| 217 | } |
|
| 218 | ||
| 219 | /** |
|
| 220 | * @dev Removes a value from a set. O(1). |
|
| 221 | * |
|
| 222 | * Returns true if the value was removed from the set, that is if it was |
|
| 223 | * present. |
|
| 224 | */ |
|
| 225 | function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { |
|
| 226 | return _remove(set._inner, value); |
|
| 227 | } |
|
| 228 | ||
| 229 | /** |
|
| 230 | * @dev Removes all the values from a set. O(n). |
|
| 231 | * |
|
| 232 | * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
|
| 233 | * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
|
| 234 | */ |
|
| 235 | function clear(Bytes32Set storage set) internal { |
|
| 236 | _clear(set._inner); |
|
| 237 | } |
|
| 238 | ||
| 239 | /** |
|
| 240 | * @dev Returns true if the value is in the set. O(1). |
|
| 241 | */ |
|
| 242 | function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { |
|
| 243 | return _contains(set._inner, value); |
|
| 244 | } |
|
| 245 | ||
| 246 | /** |
|
| 247 | * @dev Returns the number of values in the set. O(1). |
|
| 248 | */ |
|
| 249 | function length(Bytes32Set storage set) internal view returns (uint256) { |
|
| 250 | return _length(set._inner); |
|
| 251 | } |
|
| 252 | ||
| 253 | /** |
|
| 254 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 255 | * |
|
| 256 | * Note that there are no guarantees on the ordering of values inside the |
|
| 257 | * array, and it may change when more values are added or removed. |
|
| 258 | * |
|
| 259 | * Requirements: |
|
| 260 | * |
|
| 261 | * - `index` must be strictly less than {length}. |
|
| 262 | */ |
|
| 263 | function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { |
|
| 264 | return _at(set._inner, index); |
|
| 265 | } |
|
| 266 | ||
| 267 | /** |
|
| 268 | * @dev Return the entire set in an array |
|
| 269 | * |
|
| 270 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 271 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 272 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 273 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 274 | */ |
|
| 275 | function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { |
|
| 276 | bytes32[] memory store = _values(set._inner); |
|
| 277 | bytes32[] memory result; |
|
| 278 | ||
| 279 | assembly ("memory-safe") { |
|
| 280 | result := store |
|
| 281 | } |
|
| 282 | ||
| 283 | return result; |
|
| 284 | } |
|
| 285 | ||
| 286 | /** |
|
| 287 | * @dev Return a slice of the set in an array |
|
| 288 | * |
|
| 289 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 290 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 291 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 292 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 293 | */ |
|
| 294 | function values(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) { |
|
| 295 | bytes32[] memory store = _values(set._inner, start, end); |
|
| 296 | bytes32[] memory result; |
|
| 297 | ||
| 298 | assembly ("memory-safe") { |
|
| 299 | result := store |
|
| 300 | } |
|
| 301 | ||
| 302 | return result; |
|
| 303 | } |
|
| 304 | ||
| 305 | // AddressSet |
|
| 306 | ||
| 307 | struct AddressSet { |
|
| 308 | Set _inner; |
|
| 309 | } |
|
| 310 | ||
| 311 | /** |
|
| 312 | * @dev Add a value to a set. O(1). |
|
| 313 | * |
|
| 314 | * Returns true if the value was added to the set, that is if it was not |
|
| 315 | * already present. |
|
| 316 | */ |
|
| 317 | 6× | function add(AddressSet storage set, address value) internal returns (bool) { |
| 318 | 18× | return _add(set._inner, bytes32(uint256(uint160(value)))); |
| 319 | } |
|
| 320 | ||
| 321 | /** |
|
| 322 | * @dev Removes a value from a set. O(1). |
|
| 323 | * |
|
| 324 | * Returns true if the value was removed from the set, that is if it was |
|
| 325 | * present. |
|
| 326 | */ |
|
| 327 | 8× | function remove(AddressSet storage set, address value) internal returns (bool) { |
| 328 | 24× | return _remove(set._inner, bytes32(uint256(uint160(value)))); |
| 329 | } |
|
| 330 | ||
| 331 | /** |
|
| 332 | * @dev Removes all the values from a set. O(n). |
|
| 333 | * |
|
| 334 | * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
|
| 335 | * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
|
| 336 | */ |
|
| 337 | function clear(AddressSet storage set) internal { |
|
| 338 | _clear(set._inner); |
|
| 339 | } |
|
| 340 | ||
| 341 | /** |
|
| 342 | * @dev Returns true if the value is in the set. O(1). |
|
| 343 | */ |
|
| 344 | 26× | function contains(AddressSet storage set, address value) internal view returns (bool) { |
| 345 | 35× | return _contains(set._inner, bytes32(uint256(uint160(value)))); |
| 346 | } |
|
| 347 | ||
| 348 | /** |
|
| 349 | * @dev Returns the number of values in the set. O(1). |
|
| 350 | */ |
|
| 351 | 2× | function length(AddressSet storage set) internal view returns (uint256) { |
| 352 | 2× | return _length(set._inner); |
| 353 | } |
|
| 354 | ||
| 355 | /** |
|
| 356 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 357 | * |
|
| 358 | * Note that there are no guarantees on the ordering of values inside the |
|
| 359 | * array, and it may change when more values are added or removed. |
|
| 360 | * |
|
| 361 | * Requirements: |
|
| 362 | * |
|
| 363 | * - `index` must be strictly less than {length}. |
|
| 364 | */ |
|
| 365 | 0 | function at(AddressSet storage set, uint256 index) internal view returns (address) { |
| 366 | 0 | return address(uint160(uint256(_at(set._inner, index)))); |
| 367 | } |
|
| 368 | ||
| 369 | /** |
|
| 370 | * @dev Return the entire set in an array |
|
| 371 | * |
|
| 372 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 373 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 374 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 375 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 376 | */ |
|
| 377 | 12× | function values(AddressSet storage set) internal view returns (address[] memory) { |
| 378 | 18× | bytes32[] memory store = _values(set._inner); |
| 379 | address[] memory result; |
|
| 380 | ||
| 381 | assembly ("memory-safe") { |
|
| 382 | result := store |
|
| 383 | } |
|
| 384 | ||
| 385 | return result; |
|
| 386 | } |
|
| 387 | ||
| 388 | /** |
|
| 389 | * @dev Return a slice of the set in an array |
|
| 390 | * |
|
| 391 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 392 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 393 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 394 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 395 | */ |
|
| 396 | function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) { |
|
| 397 | bytes32[] memory store = _values(set._inner, start, end); |
|
| 398 | address[] memory result; |
|
| 399 | ||
| 400 | assembly ("memory-safe") { |
|
| 401 | result := store |
|
| 402 | } |
|
| 403 | ||
| 404 | return result; |
|
| 405 | } |
|
| 406 | ||
| 407 | // UintSet |
|
| 408 | ||
| 409 | struct UintSet { |
|
| 410 | Set _inner; |
|
| 411 | } |
|
| 412 | ||
| 413 | /** |
|
| 414 | * @dev Add a value to a set. O(1). |
|
| 415 | * |
|
| 416 | * Returns true if the value was added to the set, that is if it was not |
|
| 417 | * already present. |
|
| 418 | */ |
|
| 419 | function add(UintSet storage set, uint256 value) internal returns (bool) { |
|
| 420 | return _add(set._inner, bytes32(value)); |
|
| 421 | } |
|
| 422 | ||
| 423 | /** |
|
| 424 | * @dev Removes a value from a set. O(1). |
|
| 425 | * |
|
| 426 | * Returns true if the value was removed from the set, that is if it was |
|
| 427 | * present. |
|
| 428 | */ |
|
| 429 | function remove(UintSet storage set, uint256 value) internal returns (bool) { |
|
| 430 | return _remove(set._inner, bytes32(value)); |
|
| 431 | } |
|
| 432 | ||
| 433 | /** |
|
| 434 | * @dev Removes all the values from a set. O(n). |
|
| 435 | * |
|
| 436 | * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
|
| 437 | * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
|
| 438 | */ |
|
| 439 | function clear(UintSet storage set) internal { |
|
| 440 | _clear(set._inner); |
|
| 441 | } |
|
| 442 | ||
| 443 | /** |
|
| 444 | * @dev Returns true if the value is in the set. O(1). |
|
| 445 | */ |
|
| 446 | function contains(UintSet storage set, uint256 value) internal view returns (bool) { |
|
| 447 | return _contains(set._inner, bytes32(value)); |
|
| 448 | } |
|
| 449 | ||
| 450 | /** |
|
| 451 | * @dev Returns the number of values in the set. O(1). |
|
| 452 | */ |
|
| 453 | function length(UintSet storage set) internal view returns (uint256) { |
|
| 454 | return _length(set._inner); |
|
| 455 | } |
|
| 456 | ||
| 457 | /** |
|
| 458 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 459 | * |
|
| 460 | * Note that there are no guarantees on the ordering of values inside the |
|
| 461 | * array, and it may change when more values are added or removed. |
|
| 462 | * |
|
| 463 | * Requirements: |
|
| 464 | * |
|
| 465 | * - `index` must be strictly less than {length}. |
|
| 466 | */ |
|
| 467 | function at(UintSet storage set, uint256 index) internal view returns (uint256) { |
|
| 468 | return uint256(_at(set._inner, index)); |
|
| 469 | } |
|
| 470 | ||
| 471 | /** |
|
| 472 | * @dev Return the entire set in an array |
|
| 473 | * |
|
| 474 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 475 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 476 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 477 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 478 | */ |
|
| 479 | function values(UintSet storage set) internal view returns (uint256[] memory) { |
|
| 480 | bytes32[] memory store = _values(set._inner); |
|
| 481 | uint256[] memory result; |
|
| 482 | ||
| 483 | assembly ("memory-safe") { |
|
| 484 | result := store |
|
| 485 | } |
|
| 486 | ||
| 487 | return result; |
|
| 488 | } |
|
| 489 | ||
| 490 | /** |
|
| 491 | * @dev Return a slice of the set in an array |
|
| 492 | * |
|
| 493 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 494 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 495 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 496 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 497 | */ |
|
| 498 | function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) { |
|
| 499 | bytes32[] memory store = _values(set._inner, start, end); |
|
| 500 | uint256[] memory result; |
|
| 501 | ||
| 502 | assembly ("memory-safe") { |
|
| 503 | result := store |
|
| 504 | } |
|
| 505 | ||
| 506 | return result; |
|
| 507 | } |
|
| 508 | ||
| 509 | struct StringSet { |
|
| 510 | // Storage of set values |
|
| 511 | string[] _values; |
|
| 512 | // Position is the index of the value in the `values` array plus 1. |
|
| 513 | // Position 0 is used to mean a value is not in the set. |
|
| 514 | mapping(string value => uint256) _positions; |
|
| 515 | } |
|
| 516 | ||
| 517 | /** |
|
| 518 | * @dev Add a value to a set. O(1). |
|
| 519 | * |
|
| 520 | * Returns true if the value was added to the set, that is if it was not |
|
| 521 | * already present. |
|
| 522 | */ |
|
| 523 | function add(StringSet storage set, string memory value) internal returns (bool) { |
|
| 524 | if (!contains(set, value)) { |
|
| 525 | set._values.push(value); |
|
| 526 | // The value is stored at length-1, but we add 1 to all indexes |
|
| 527 | // and use 0 as a sentinel value |
|
| 528 | set._positions[value] = set._values.length; |
|
| 529 | return true; |
|
| 530 | } else { |
|
| 531 | return false; |
|
| 532 | } |
|
| 533 | } |
|
| 534 | ||
| 535 | /** |
|
| 536 | * @dev Removes a value from a set. O(1). |
|
| 537 | * |
|
| 538 | * Returns true if the value was removed from the set, that is if it was |
|
| 539 | * present. |
|
| 540 | */ |
|
| 541 | function remove(StringSet storage set, string memory value) internal returns (bool) { |
|
| 542 | // We cache the value's position to prevent multiple reads from the same storage slot |
|
| 543 | uint256 position = set._positions[value]; |
|
| 544 | ||
| 545 | if (position != 0) { |
|
| 546 | // Equivalent to contains(set, value) |
|
| 547 | // To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
|
| 548 | // the array, and then remove the last element (sometimes called as 'swap and pop'). |
|
| 549 | // This modifies the order of the array, as noted in {at}. |
|
| 550 | ||
| 551 | uint256 valueIndex = position - 1; |
|
| 552 | uint256 lastIndex = set._values.length - 1; |
|
| 553 | ||
| 554 | if (valueIndex != lastIndex) { |
|
| 555 | string memory lastValue = set._values[lastIndex]; |
|
| 556 | ||
| 557 | // Move the lastValue to the index where the value to delete is |
|
| 558 | set._values[valueIndex] = lastValue; |
|
| 559 | // Update the tracked position of the lastValue (that was just moved) |
|
| 560 | set._positions[lastValue] = position; |
|
| 561 | } |
|
| 562 | ||
| 563 | // Delete the slot where the moved value was stored |
|
| 564 | set._values.pop(); |
|
| 565 | ||
| 566 | // Delete the tracked position for the deleted slot |
|
| 567 | delete set._positions[value]; |
|
| 568 | ||
| 569 | return true; |
|
| 570 | } else { |
|
| 571 | return false; |
|
| 572 | } |
|
| 573 | } |
|
| 574 | ||
| 575 | /** |
|
| 576 | * @dev Removes all the values from a set. O(n). |
|
| 577 | * |
|
| 578 | * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
|
| 579 | * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
|
| 580 | */ |
|
| 581 | function clear(StringSet storage set) internal { |
|
| 582 | uint256 len = length(set); |
|
| 583 | for (uint256 i = 0; i < len; ++i) { |
|
| 584 | delete set._positions[set._values[i]]; |
|
| 585 | } |
|
| 586 | Arrays.unsafeSetLength(set._values, 0); |
|
| 587 | } |
|
| 588 | ||
| 589 | /** |
|
| 590 | * @dev Returns true if the value is in the set. O(1). |
|
| 591 | */ |
|
| 592 | function contains(StringSet storage set, string memory value) internal view returns (bool) { |
|
| 593 | return set._positions[value] != 0; |
|
| 594 | } |
|
| 595 | ||
| 596 | /** |
|
| 597 | * @dev Returns the number of values on the set. O(1). |
|
| 598 | */ |
|
| 599 | function length(StringSet storage set) internal view returns (uint256) { |
|
| 600 | return set._values.length; |
|
| 601 | } |
|
| 602 | ||
| 603 | /** |
|
| 604 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 605 | * |
|
| 606 | * Note that there are no guarantees on the ordering of values inside the |
|
| 607 | * array, and it may change when more values are added or removed. |
|
| 608 | * |
|
| 609 | * Requirements: |
|
| 610 | * |
|
| 611 | * - `index` must be strictly less than {length}. |
|
| 612 | */ |
|
| 613 | function at(StringSet storage set, uint256 index) internal view returns (string memory) { |
|
| 614 | return set._values[index]; |
|
| 615 | } |
|
| 616 | ||
| 617 | /** |
|
| 618 | * @dev Return the entire set in an array |
|
| 619 | * |
|
| 620 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 621 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 622 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 623 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 624 | */ |
|
| 625 | function values(StringSet storage set) internal view returns (string[] memory) { |
|
| 626 | return set._values; |
|
| 627 | } |
|
| 628 | ||
| 629 | /** |
|
| 630 | * @dev Return a slice of the set in an array |
|
| 631 | * |
|
| 632 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 633 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 634 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 635 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 636 | */ |
|
| 637 | function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) { |
|
| 638 | unchecked { |
|
| 639 | end = Math.min(end, length(set)); |
|
| 640 | start = Math.min(start, end); |
|
| 641 | ||
| 642 | uint256 len = end - start; |
|
| 643 | string[] memory result = new string[](len); |
|
| 644 | for (uint256 i = 0; i < len; ++i) { |
|
| 645 | result[i] = Arrays.unsafeAccess(set._values, start + i).value; |
|
| 646 | } |
|
| 647 | return result; |
|
| 648 | } |
|
| 649 | } |
|
| 650 | ||
| 651 | struct BytesSet { |
|
| 652 | // Storage of set values |
|
| 653 | bytes[] _values; |
|
| 654 | // Position is the index of the value in the `values` array plus 1. |
|
| 655 | // Position 0 is used to mean a value is not in the set. |
|
| 656 | mapping(bytes value => uint256) _positions; |
|
| 657 | } |
|
| 658 | ||
| 659 | /** |
|
| 660 | * @dev Add a value to a set. O(1). |
|
| 661 | * |
|
| 662 | * Returns true if the value was added to the set, that is if it was not |
|
| 663 | * already present. |
|
| 664 | */ |
|
| 665 | function add(BytesSet storage set, bytes memory value) internal returns (bool) { |
|
| 666 | if (!contains(set, value)) { |
|
| 667 | set._values.push(value); |
|
| 668 | // The value is stored at length-1, but we add 1 to all indexes |
|
| 669 | // and use 0 as a sentinel value |
|
| 670 | set._positions[value] = set._values.length; |
|
| 671 | return true; |
|
| 672 | } else { |
|
| 673 | return false; |
|
| 674 | } |
|
| 675 | } |
|
| 676 | ||
| 677 | /** |
|
| 678 | * @dev Removes a value from a set. O(1). |
|
| 679 | * |
|
| 680 | * Returns true if the value was removed from the set, that is if it was |
|
| 681 | * present. |
|
| 682 | */ |
|
| 683 | function remove(BytesSet storage set, bytes memory value) internal returns (bool) { |
|
| 684 | // We cache the value's position to prevent multiple reads from the same storage slot |
|
| 685 | uint256 position = set._positions[value]; |
|
| 686 | ||
| 687 | if (position != 0) { |
|
| 688 | // Equivalent to contains(set, value) |
|
| 689 | // To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
|
| 690 | // the array, and then remove the last element (sometimes called as 'swap and pop'). |
|
| 691 | // This modifies the order of the array, as noted in {at}. |
|
| 692 | ||
| 693 | uint256 valueIndex = position - 1; |
|
| 694 | uint256 lastIndex = set._values.length - 1; |
|
| 695 | ||
| 696 | if (valueIndex != lastIndex) { |
|
| 697 | bytes memory lastValue = set._values[lastIndex]; |
|
| 698 | ||
| 699 | // Move the lastValue to the index where the value to delete is |
|
| 700 | set._values[valueIndex] = lastValue; |
|
| 701 | // Update the tracked position of the lastValue (that was just moved) |
|
| 702 | set._positions[lastValue] = position; |
|
| 703 | } |
|
| 704 | ||
| 705 | // Delete the slot where the moved value was stored |
|
| 706 | set._values.pop(); |
|
| 707 | ||
| 708 | // Delete the tracked position for the deleted slot |
|
| 709 | delete set._positions[value]; |
|
| 710 | ||
| 711 | return true; |
|
| 712 | } else { |
|
| 713 | return false; |
|
| 714 | } |
|
| 715 | } |
|
| 716 | ||
| 717 | /** |
|
| 718 | * @dev Removes all the values from a set. O(n). |
|
| 719 | * |
|
| 720 | * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
|
| 721 | * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
|
| 722 | */ |
|
| 723 | function clear(BytesSet storage set) internal { |
|
| 724 | uint256 len = length(set); |
|
| 725 | for (uint256 i = 0; i < len; ++i) { |
|
| 726 | delete set._positions[set._values[i]]; |
|
| 727 | } |
|
| 728 | Arrays.unsafeSetLength(set._values, 0); |
|
| 729 | } |
|
| 730 | ||
| 731 | /** |
|
| 732 | * @dev Returns true if the value is in the set. O(1). |
|
| 733 | */ |
|
| 734 | function contains(BytesSet storage set, bytes memory value) internal view returns (bool) { |
|
| 735 | return set._positions[value] != 0; |
|
| 736 | } |
|
| 737 | ||
| 738 | /** |
|
| 739 | * @dev Returns the number of values on the set. O(1). |
|
| 740 | */ |
|
| 741 | function length(BytesSet storage set) internal view returns (uint256) { |
|
| 742 | return set._values.length; |
|
| 743 | } |
|
| 744 | ||
| 745 | /** |
|
| 746 | * @dev Returns the value stored at position `index` in the set. O(1). |
|
| 747 | * |
|
| 748 | * Note that there are no guarantees on the ordering of values inside the |
|
| 749 | * array, and it may change when more values are added or removed. |
|
| 750 | * |
|
| 751 | * Requirements: |
|
| 752 | * |
|
| 753 | * - `index` must be strictly less than {length}. |
|
| 754 | */ |
|
| 755 | function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) { |
|
| 756 | return set._values[index]; |
|
| 757 | } |
|
| 758 | ||
| 759 | /** |
|
| 760 | * @dev Return the entire set in an array |
|
| 761 | * |
|
| 762 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 763 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 764 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 765 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 766 | */ |
|
| 767 | function values(BytesSet storage set) internal view returns (bytes[] memory) { |
|
| 768 | return set._values; |
|
| 769 | } |
|
| 770 | ||
| 771 | /** |
|
| 772 | * @dev Return a slice of the set in an array |
|
| 773 | * |
|
| 774 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
|
| 775 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
|
| 776 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
|
| 777 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
|
| 778 | */ |
|
| 779 | function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) { |
|
| 780 | unchecked { |
|
| 781 | end = Math.min(end, length(set)); |
|
| 782 | start = Math.min(start, end); |
|
| 783 | ||
| 784 | uint256 len = end - start; |
|
| 785 | bytes[] memory result = new bytes[](len); |
|
| 786 | for (uint256 i = 0; i < len; ++i) { |
|
| 787 | result[i] = Arrays.unsafeAccess(set._values, start + i).value; |
|
| 788 | } |
|
| 789 | return result; |
|
| 790 | } |
|
| 791 | } |
|
| 792 | } |
87%
lib/v2-core/src/hooks/BaseHook.sol
Lines covered: 89 / 102 (87%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 6 | ||
| 7 | // Superform |
|
| 8 | import { HookDataDecoder } from "../libraries/HookDataDecoder.sol"; |
|
| 9 | import { ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector } from "../interfaces/ISuperHook.sol"; |
|
| 10 | ||
| 11 | /// @title BaseHook |
|
| 12 | /// @author Superform Labs |
|
| 13 | /// @notice Base implementation for all hooks in the Superform system |
|
| 14 | /// @dev Provides core security validation and execution flow management for hooks |
|
| 15 | /// All specialized hooks should inherit from this base contract |
|
| 16 | /// Implements the ISuperHook interface defined lifecycle methods |
|
| 17 | /// Uses a transient storage pattern for stateful execution context |
|
| 18 | abstract contract BaseHook is ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector { |
|
| 19 | using HookDataDecoder for bytes; |
|
| 20 | ||
| 21 | /*////////////////////////////////////////////////////////////// |
|
| 22 | STORAGE |
|
| 23 | //////////////////////////////////////////////////////////////*/ |
|
| 24 | ||
| 25 | /// @notice The number of shares used by this hook's operation |
|
| 26 | /// @dev Used for accounting and tracking consumption of position shares |
|
| 27 | 0 | uint256 public transient usedShares; |
| 28 | ||
| 29 | /// @notice The special token address (if any) associated with this hook's operation |
|
| 30 | /// @dev May be used to track token addresses for various operations |
|
| 31 | 0 | address public transient spToken; |
| 32 | ||
| 33 | /// @notice The primary asset address this hook operates on |
|
| 34 | /// @dev Typically the base token or asset being processed |
|
| 35 | 0 | address public transient asset; |
| 36 | ||
| 37 | /// @notice Execution nonce for creating unique contexts |
|
| 38 | 214× | uint256 public transient executionNonce; |
| 39 | ||
| 40 | /// @notice Last execution context caller |
|
| 41 | 0 | address public transient lastCaller; |
| 42 | ||
| 43 | // Storage offsets for different state variables |
|
| 44 | 15× | uint256 private constant OUT_AMOUNT_OFFSET = 1; |
| 45 | 22× | uint256 private constant PRE_EXECUTE_MUTEX_OFFSET = 2; |
| 46 | 12× | uint256 private constant POST_EXECUTE_MUTEX_OFFSET = 3; |
| 47 | ||
| 48 | /// @notice Base storage key for hook execution state |
|
| 49 | 11× | bytes32 private constant HOOK_EXECUTION_STORAGE = keccak256("hook.execution.state"); |
| 50 | ||
| 51 | /// @notice Storage key for account context mapping |
|
| 52 | 23× | bytes32 private constant ACCOUNT_CONTEXT_STORAGE = keccak256("hook.account.context"); |
| 53 | ||
| 54 | /// @notice The specific subtype identifier for this hook |
|
| 55 | /// @dev Used to identify specialized hook types beyond the basic HookType enum |
|
| 56 | 0 | bytes32 public immutable SUB_TYPE; |
| 57 | ||
| 58 | /// @notice The type of hook (NONACCOUNTING, INFLOW, OUTFLOW) |
|
| 59 | /// @dev Determines how the hook impacts accounting in the system |
|
| 60 | 102× | ISuperHook.HookType public hookType; |
| 61 | ||
| 62 | /*////////////////////////////////////////////////////////////// |
|
| 63 | ERRORS |
|
| 64 | //////////////////////////////////////////////////////////////*/ |
|
| 65 | /// @notice Thrown when a caller attempts to execute hook methods without proper authorization |
|
| 66 | /// @dev Used by security validation to prevent unauthorized hook execution |
|
| 67 | error NOT_AUTHORIZED(); |
|
| 68 | ||
| 69 | /// @notice Thrown when an amount parameter is invalid (e.g., zero or overflow) |
|
| 70 | /// @dev Used in validation checks for asset amounts and share values |
|
| 71 | error AMOUNT_NOT_VALID(); |
|
| 72 | ||
| 73 | /// @notice Thrown when an address parameter is invalid (e.g., zero address) |
|
| 74 | /// @dev Used in validation checks for tokens, accounts, and other addresses |
|
| 75 | error ADDRESS_NOT_VALID(); |
|
| 76 | ||
| 77 | /// @notice Thrown when a caller is not authorized to execute hook methods |
|
| 78 | /// @dev Used by security validation to prevent unauthorized hook execution |
|
| 79 | error UNAUTHORIZED_CALLER(); |
|
| 80 | ||
| 81 | /// @notice Thrown when preExecute is called more than once |
|
| 82 | /// @dev Used to prevent reentrancy attacks and ensure proper execution flow |
|
| 83 | error PRE_EXECUTE_ALREADY_CALLED(); |
|
| 84 | ||
| 85 | /// @notice Thrown when postExecute is called more than once |
|
| 86 | /// @dev Used to prevent reentrancy attacks and ensure proper execution flow |
|
| 87 | error POST_EXECUTE_ALREADY_CALLED(); |
|
| 88 | ||
| 89 | /// @notice Thrown when a hook execution is incomplete |
|
| 90 | /// @dev Used to prevent incomplete hook execution |
|
| 91 | error INCOMPLETE_HOOK_EXECUTION(); |
|
| 92 | ||
| 93 | /// @notice Thrown when trying to set outAmount after preExecute or postExecute |
|
| 94 | /// @dev Used to prevent setting outAmount after preExecute or postExecute |
|
| 95 | error CANNOT_SET_OUT_AMOUNT(); |
|
| 96 | ||
| 97 | /// @notice Initializes the hook with its type and subtype |
|
| 98 | /// @dev Sets immutable parameters that define the hook's behavior |
|
| 99 | /// @param hookType_ The type classification for this hook (NONACCOUNTING, INFLOW, OUTFLOW) |
|
| 100 | /// @param subType_ The specific subtype identifier for specialized hook functionality |
|
| 101 | constructor(ISuperHook.HookType hookType_, bytes32 subType_) { |
|
| 102 | 100× | hookType = hookType_; |
| 103 | 36× | SUB_TYPE = subType_; |
| 104 | } |
|
| 105 | ||
| 106 | modifier onlyLastCaller() { |
|
| 107 | 48× | if (msg.sender != lastCaller) revert UNAUTHORIZED_CALLER(); |
| 108 | _; |
|
| 109 | } |
|
| 110 | ||
| 111 | /*////////////////////////////////////////////////////////////// |
|
| 112 | EXECUTION SECURITY |
|
| 113 | //////////////////////////////////////////////////////////////*/ |
|
| 114 | /// @inheritdoc ISuperHook |
|
| 115 | 299× | function setExecutionContext(address caller) external { |
| 116 | 115× | _createExecutionContext(caller); |
| 117 | 230× | lastCaller = msg.sender; |
| 118 | } |
|
| 119 | ||
| 120 | /// @dev Standard build pattern - MUST include preExecute first, postExecute last |
|
| 121 | /// @inheritdoc ISuperHook |
|
| 122 | 418× | function build( |
| 123 | address prevHook, |
|
| 124 | address account, |
|
| 125 | bytes calldata hookData |
|
| 126 | ) |
|
| 127 | external |
|
| 128 | view |
|
| 129 | virtual |
|
| 130 | 23× | returns (Execution[] memory executions) |
| 131 | 11× | { |
| 132 | // Get hook-specific executions |
|
| 133 | 217× | Execution[] memory hookExecutions = _buildHookExecutions(prevHook, account, hookData); |
| 134 | ||
| 135 | // Always include pre + hook + post |
|
| 136 | 627× | executions = new Execution[](hookExecutions.length + 2); |
| 137 | ||
| 138 | // FIRST: preExecute |
|
| 139 | 429× | executions[0] = Execution({ |
| 140 | 11× | target: address(this), |
| 141 | 11× | value: 0, |
| 142 | 572× | callData: abi.encodeCall(this.preExecute, (prevHook, account, hookData)) |
| 143 | }); |
|
| 144 | ||
| 145 | // MIDDLE: hook-specific operations |
|
| 146 | 165× | for (uint256 i = 0; i < hookExecutions.length; i++) { |
| 147 | 451× | executions[i + 1] = hookExecutions[i]; |
| 148 | } |
|
| 149 | ||
| 150 | // LAST: postExecute |
|
| 151 | 506× | executions[executions.length - 1] = Execution({ |
| 152 | 11× | target: address(this), |
| 153 | 11× | value: 0, |
| 154 | 572× | callData: abi.encodeCall(this.postExecute, (prevHook, account, hookData)) |
| 155 | }); |
|
| 156 | } |
|
| 157 | ||
| 158 | /// @inheritdoc ISuperHook |
|
| 159 | 121× | function preExecute(address prevHook, address account, bytes calldata data) external { |
| 160 | 77× | if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
| 161 | 88× | uint256 context = _getCurrentExecutionContext(account); |
| 162 | 99× | if (_getPreExecuteMutex(context)) revert PRE_EXECUTE_ALREADY_CALLED(); |
| 163 | 64× | _setPreExecuteMutex(context, true); |
| 164 | 63× | _preExecute(prevHook, account, data); |
| 165 | } |
|
| 166 | ||
| 167 | /// @inheritdoc ISuperHook |
|
| 168 | 178× | function postExecute(address prevHook, address account, bytes calldata data) external { |
| 169 | 42× | if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
| 170 | 48× | uint256 context = _getCurrentExecutionContext(account); |
| 171 | 54× | if (_getPostExecuteMutex(context)) revert POST_EXECUTE_ALREADY_CALLED(); |
| 172 | 37× | _setPostExecuteMutex(context, true); |
| 173 | 44× | _postExecute(prevHook, account, data); |
| 174 | } |
|
| 175 | ||
| 176 | /// @inheritdoc ISuperHookSetter |
|
| 177 | 27× | function setOutAmount(uint256 _outAmount, address caller) external { |
| 178 | 0 | uint256 context = _getCurrentExecutionContext(caller); |
| 179 | 0 | if (_getPreExecuteMutex(context) || _getPostExecuteMutex(context)) { |
| 180 | 0 | revert CANNOT_SET_OUT_AMOUNT(); |
| 181 | } |
|
| 182 | ||
| 183 | 27× | bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
| 184 | 18× | assembly { |
| 185 | 27× | tstore(key, _outAmount) |
| 186 | } |
|
| 187 | } |
|
| 188 | ||
| 189 | 84× | function getOutAmount(address caller) public view returns (uint256) { |
| 190 | 80× | return _getOutAmount(_getCurrentExecutionContext(caller)); |
| 191 | } |
|
| 192 | ||
| 193 | /// @inheritdoc ISuperHook |
|
| 194 | 84× | function resetExecutionState(address caller) external onlyLastCaller { |
| 195 | 48× | uint256 context = _getCurrentExecutionContext(caller); |
| 196 | 126× | if (!_getPreExecuteMutex(context) || !_getPostExecuteMutex(context)) { |
| 197 | 0 | revert INCOMPLETE_HOOK_EXECUTION(); |
| 198 | } |
|
| 199 | ||
| 200 | 30× | _clearExecutionState(context); |
| 201 | } |
|
| 202 | ||
| 203 | /*////////////////////////////////////////////////////////////// |
|
| 204 | VIEW METHODS |
|
| 205 | //////////////////////////////////////////////////////////////*/ |
|
| 206 | /// @inheritdoc ISuperHook |
|
| 207 | 0 | function subtype() external view returns (bytes32) { |
| 208 | 0 | return SUB_TYPE; |
| 209 | } |
|
| 210 | ||
| 211 | /// @inheritdoc ISuperHookInspector |
|
| 212 | 0 | function inspect(bytes calldata) external view virtual returns (bytes memory) { } |
| 213 | ||
| 214 | /*////////////////////////////////////////////////////////////// |
|
| 215 | INTERNAL METHODS |
|
| 216 | //////////////////////////////////////////////////////////////*/ |
|
| 217 | /// @notice Internal implementation of build |
|
| 218 | /// @dev Abstract function to be implemented by derived hooks |
|
| 219 | /// Called during build to generate hook-specific execution sequences |
|
| 220 | /// @param prevHook The previous hook in the chain, or address(0) if first hook |
|
| 221 | /// @param account The account that operations will be performed for |
|
| 222 | /// @param data Hook-specific parameters and configuration data |
|
| 223 | /// @return executions Array of execution objects containing target, value, and callData |
|
| 224 | function _buildHookExecutions( |
|
| 225 | address prevHook, |
|
| 226 | address account, |
|
| 227 | bytes calldata data |
|
| 228 | ) |
|
| 229 | internal |
|
| 230 | view |
|
| 231 | virtual |
|
| 232 | returns (Execution[] memory executions); |
|
| 233 | ||
| 234 | /// @notice Internal implementation of preExecute |
|
| 235 | /// @dev Abstract function to be implemented by derived hooks |
|
| 236 | /// Called before execution to validate inputs and prepare the hook's state |
|
| 237 | /// Typically sets up the hook context by parsing parameters from data |
|
| 238 | /// May check balances, permissions, or other preconditions |
|
| 239 | /// @param prevHook The previous hook in the chain, or address(0) if first hook |
|
| 240 | /// @param account The account that operations will be performed for |
|
| 241 | /// @param data Hook-specific parameters and configuration data |
|
| 242 | 0 | function _preExecute(address prevHook, address account, bytes calldata data) internal virtual { } |
| 243 | ||
| 244 | /// @notice Internal implementation of postExecute |
|
| 245 | /// @dev Abstract function to be implemented by derived hooks |
|
| 246 | /// Called after execution to finalize the hook's state and set output values |
|
| 247 | /// Typically calculates final results and sets outAmount, usedShares, etc. |
|
| 248 | /// May perform additional validations on execution results |
|
| 249 | /// @param prevHook The previous hook in the chain, or address(0) if first hook |
|
| 250 | /// @param account The account operations were performed for |
|
| 251 | /// @param data Hook-specific parameters and configuration data |
|
| 252 | function _postExecute(address prevHook, address account, bytes calldata data) internal virtual { } |
|
| 253 | ||
| 254 | /// @notice Decodes a boolean value from a byte array at the specified offset |
|
| 255 | /// @dev Helper function for extracting boolean values from packed data |
|
| 256 | /// Used when parsing hook-specific data parameters |
|
| 257 | /// @param data The byte array containing the encoded data |
|
| 258 | /// @param offset The position in the array to read from |
|
| 259 | /// @return The decoded boolean value (true if byte is non-zero) |
|
| 260 | 114× | function _decodeBool(bytes memory data, uint256 offset) internal pure returns (bool) { |
| 261 | 283× | return data[offset] != 0; |
| 262 | } |
|
| 263 | ||
| 264 | /// @notice Replaces an amount value within a byte array at the specified offset |
|
| 265 | /// @dev Used to modify hook calldata for amount adjustments without full re-encoding |
|
| 266 | /// Particularly useful for modifying amounts in multi-hook execution chains |
|
| 267 | /// Directly modifies the bytes in-place for gas efficiency |
|
| 268 | /// @param data The original byte array containing encoded calldata |
|
| 269 | /// @param amount The new amount value to insert |
|
| 270 | /// @param offset The position in the array where the amount starts |
|
| 271 | /// @return The modified byte array with the replaced amount |
|
| 272 | 18× | function _replaceCalldataAmount( |
| 273 | bytes memory data, |
|
| 274 | uint256 amount, |
|
| 275 | uint256 offset |
|
| 276 | ) |
|
| 277 | internal |
|
| 278 | pure |
|
| 279 | 6× | returns (bytes memory) |
| 280 | { |
|
| 281 | 138× | bytes memory newAmountEncoded = abi.encodePacked(amount); |
| 282 | 78× | for (uint256 i; i < 32; ++i) { |
| 283 | 240× | data[offset + i] = newAmountEncoded[i]; |
| 284 | } |
|
| 285 | 12× | return data; |
| 286 | } |
|
| 287 | ||
| 288 | 138× | function _makeAccountContextKey(address account) private pure returns (bytes32) { |
| 289 | 690× | return keccak256(abi.encodePacked(ACCOUNT_CONTEXT_STORAGE, account)); |
| 290 | } |
|
| 291 | ||
| 292 | 115× | function _createExecutionContext(address caller) private returns (uint256) { |
| 293 | // Always increment nonce for new execution context |
|
| 294 | 322× | executionNonce++; |
| 295 | 184× | bytes32 key = _makeAccountContextKey(caller); |
| 296 | ||
| 297 | // Store this context for the current caller |
|
| 298 | 69× | uint256 currentNonce = executionNonce; // Load into local variable for assembly |
| 299 | assembly { |
|
| 300 | 23× | tstore(key, currentNonce) |
| 301 | } |
|
| 302 | ||
| 303 | 69× | return executionNonce; |
| 304 | } |
|
| 305 | ||
| 306 | 44× | function _getCurrentExecutionContext(address caller) private view returns (uint256 context) { |
| 307 | 66× | bytes32 key = _makeAccountContextKey(caller); |
| 308 | assembly { |
|
| 309 | 22× | context := tload(key) |
| 310 | } |
|
| 311 | } |
|
| 312 | ||
| 313 | 22× | function _makeKey(uint256 context, uint256 offset) private pure returns (bytes32) { |
| 314 | 275× | return keccak256(abi.encodePacked(HOOK_EXECUTION_STORAGE, context, offset)); |
| 315 | } |
|
| 316 | ||
| 317 | 12× | function _getOutAmount(uint256 context) private view returns (uint256 value) { |
| 318 | 30× | bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
| 319 | assembly { |
|
| 320 | value := tload(key) |
|
| 321 | } |
|
| 322 | } |
|
| 323 | ||
| 324 | 9× | function _setOutAmount(uint256 value, address caller) internal { |
| 325 | 72× | uint256 context = _getCurrentExecutionContext(caller); |
| 326 | 45× | bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
| 327 | assembly { |
|
| 328 | tstore(key, value) |
|
| 329 | } |
|
| 330 | } |
|
| 331 | ||
| 332 | 22× | function _getPreExecuteMutex(uint256 context) private view returns (bool value) { |
| 333 | 33× | bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
| 334 | assembly { |
|
| 335 | value := tload(key) |
|
| 336 | } |
|
| 337 | } |
|
| 338 | ||
| 339 | 11× | function _setPreExecuteMutex(uint256 context, bool value) private { |
| 340 | 55× | bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
| 341 | assembly { |
|
| 342 | tstore(key, value) |
|
| 343 | } |
|
| 344 | } |
|
| 345 | ||
| 346 | 12× | function _getPostExecuteMutex(uint256 context) private view returns (bool value) { |
| 347 | 30× | bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
| 348 | assembly { |
|
| 349 | value := tload(key) |
|
| 350 | } |
|
| 351 | } |
|
| 352 | ||
| 353 | 39× | function _setPostExecuteMutex(uint256 context, bool value) private { |
| 354 | 63× | bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
| 355 | 11× | assembly { |
| 356 | 33× | tstore(key, value) |
| 357 | } |
|
| 358 | } |
|
| 359 | ||
| 360 | 36× | function _clearExecutionState(uint256 context) private { |
| 361 | 36× | _setPreExecuteMutex(context, false); |
| 362 | 45× | _setPostExecuteMutex(context, false); |
| 363 | } |
|
| 364 | } |
95%
lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol
Lines covered: 39 / 41 (95%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 8 | import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 13 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 14 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 15 | import { |
|
| 16 | ISuperHookResult, |
|
| 17 | ISuperHookInflowOutflow, |
|
| 18 | ISuperHookContextAware, |
|
| 19 | ISuperHookInspector |
|
| 20 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 21 | ||
| 22 | /// @title ApproveAndDeposit4626VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @notice This hook does not support tokens reverting on 0 approval |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice address token = BytesLib.toAddress(data, 52); |
|
| 29 | /// @notice uint256 amount = BytesLib.toUint256(data, 72); |
|
| 30 | /// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
|
| 31 | 413× | contract ApproveAndDeposit4626VaultHook is |
| 32 | BaseHook, |
|
| 33 | VaultBankLockableHook, |
|
| 34 | ISuperHookInflowOutflow, |
|
| 35 | ISuperHookContextAware |
|
| 36 | { |
|
| 37 | using HookDataDecoder for bytes; |
|
| 38 | ||
| 39 | 2× | uint256 private constant AMOUNT_POSITION = 72; |
| 40 | 6× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
| 41 | ||
| 42 | 7× | constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { } |
| 43 | ||
| 44 | /*////////////////////////////////////////////////////////////// |
|
| 45 | VIEW METHODS |
|
| 46 | //////////////////////////////////////////////////////////////*/ |
|
| 47 | /// @inheritdoc BaseHook |
|
| 48 | 9× | function _buildHookExecutions( |
| 49 | address prevHook, |
|
| 50 | address account, |
|
| 51 | bytes calldata data |
|
| 52 | ) |
|
| 53 | internal |
|
| 54 | view |
|
| 55 | override |
|
| 56 | 2× | returns (Execution[] memory executions) |
| 57 | 4× | { |
| 58 | 102× | address yieldSource = data.extractYieldSource(); |
| 59 | 106× | address token = BytesLib.toAddress(data, 52); |
| 60 | 102× | uint256 amount = _decodeAmount(data); |
| 61 | 102× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 62 | ||
| 63 | 10× | if (usePrevHookAmount) { |
| 64 | 55× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 65 | } |
|
| 66 | ||
| 67 | 24× | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 68 | 14× | if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
| 69 | ||
| 70 | 34× | executions = new Execution[](4); |
| 71 | 16× | executions[0] = |
| 72 | 52× | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 73 | 20× | executions[1] = |
| 74 | 53× | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 75 | 20× | executions[2] = |
| 76 | 53× | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) }); |
| 77 | 20× | executions[3] = |
| 78 | 53× | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 79 | } |
|
| 80 | ||
| 81 | /*////////////////////////////////////////////////////////////// |
|
| 82 | EXTERNAL METHODS |
|
| 83 | //////////////////////////////////////////////////////////////*/ |
|
| 84 | ||
| 85 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 86 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 87 | 0 | return _decodeAmount(data); |
| 88 | } |
|
| 89 | ||
| 90 | /// @inheritdoc ISuperHookContextAware |
|
| 91 | 32× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 92 | 8× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 93 | } |
|
| 94 | ||
| 95 | /// @inheritdoc ISuperHookInspector |
|
| 96 | 50× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 97 | 38× | return abi.encodePacked( |
| 98 | 96× | data.extractYieldSource(), |
| 99 | 100× | BytesLib.toAddress(data, 52) //token |
| 100 | ); |
|
| 101 | } |
|
| 102 | ||
| 103 | /*////////////////////////////////////////////////////////////// |
|
| 104 | INTERNAL METHODS |
|
| 105 | //////////////////////////////////////////////////////////////*/ |
|
| 106 | 6× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 107 | // store current balance |
|
| 108 | 50× | _setOutAmount(_getBalance(account, data), account); |
| 109 | 58× | spToken = data.extractYieldSource(); |
| 110 | } |
|
| 111 | ||
| 112 | 6× | function _postExecute(address, address account, bytes calldata data) internal override { |
| 113 | 65× | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 114 | } |
|
| 115 | ||
| 116 | /*////////////////////////////////////////////////////////////// |
|
| 117 | PRIVATE METHODS |
|
| 118 | //////////////////////////////////////////////////////////////*/ |
|
| 119 | 4× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 120 | 8× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 121 | } |
|
| 122 | ||
| 123 | 6× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 124 | 66× | return IERC4626(data.extractYieldSource()).balanceOf(account); |
| 125 | } |
|
| 126 | } |
62%
lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol
Lines covered: 20 / 32 (62%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 8 | ||
| 9 | // Superform |
|
| 10 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 11 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 12 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 13 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 14 | import { |
|
| 15 | ISuperHookResult, |
|
| 16 | ISuperHookInflowOutflow, |
|
| 17 | ISuperHookContextAware, |
|
| 18 | ISuperHookInspector |
|
| 19 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 20 | ||
| 21 | /// @title Deposit4626VaultHook |
|
| 22 | /// @author Superform Labs |
|
| 23 | /// @dev data has the following structure |
|
| 24 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 25 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 26 | /// @notice uint256 amount = BytesLib.toUint256(data, 52); |
|
| 27 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 28 | 296× | contract Deposit4626VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware { |
| 29 | using HookDataDecoder for bytes; |
|
| 30 | ||
| 31 | 1× | uint256 private constant AMOUNT_POSITION = 52; |
| 32 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 33 | ||
| 34 | 7× | constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { } |
| 35 | /*////////////////////////////////////////////////////////////// |
|
| 36 | VIEW METHODS |
|
| 37 | //////////////////////////////////////////////////////////////*/ |
|
| 38 | /// @inheritdoc BaseHook |
|
| 39 | ||
| 40 | 1× | function _buildHookExecutions( |
| 41 | address prevHook, |
|
| 42 | address account, |
|
| 43 | bytes calldata data |
|
| 44 | ) |
|
| 45 | internal |
|
| 46 | view |
|
| 47 | override |
|
| 48 | 1× | returns (Execution[] memory executions) |
| 49 | 0 | { |
| 50 | 51× | address yieldSource = data.extractYieldSource(); |
| 51 | 51× | uint256 amount = _decodeAmount(data); |
| 52 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 53 | ||
| 54 | 5× | if (usePrevHookAmount) { |
| 55 | 55× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 56 | } |
|
| 57 | ||
| 58 | 18× | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 59 | 0 | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 60 | ||
| 61 | 0 | executions = new Execution[](1); |
| 62 | 0 | executions[0] = |
| 63 | 0 | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) }); |
| 64 | } |
|
| 65 | ||
| 66 | /*////////////////////////////////////////////////////////////// |
|
| 67 | EXTERNAL METHODS |
|
| 68 | //////////////////////////////////////////////////////////////*/ |
|
| 69 | ||
| 70 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 71 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 72 | 0 | return _decodeAmount(data); |
| 73 | } |
|
| 74 | ||
| 75 | /// @inheritdoc ISuperHookContextAware |
|
| 76 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 77 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 78 | } |
|
| 79 | ||
| 80 | /// @inheritdoc ISuperHookInspector |
|
| 81 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 82 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 83 | } |
|
| 84 | ||
| 85 | /*////////////////////////////////////////////////////////////// |
|
| 86 | INTERNAL METHODS |
|
| 87 | //////////////////////////////////////////////////////////////*/ |
|
| 88 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 89 | // store current balance |
|
| 90 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 91 | 0 | spToken = data.extractYieldSource(); |
| 92 | } |
|
| 93 | ||
| 94 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 95 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 96 | } |
|
| 97 | ||
| 98 | /*////////////////////////////////////////////////////////////// |
|
| 99 | PRIVATE METHODS |
|
| 100 | //////////////////////////////////////////////////////////////*/ |
|
| 101 | 2× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 102 | 4× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 103 | } |
|
| 104 | ||
| 105 | 2× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 106 | 2× | return IERC4626(data.extractYieldSource()).balanceOf(account); |
| 107 | } |
|
| 108 | } |
97%
lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol
Lines covered: 46 / 47 (97%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 8 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResultOutflow, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookOutflow, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title Redeem4626VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @dev data has the following structure |
|
| 25 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 26 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 27 | /// @notice address owner = BytesLib.toAddress(data, 52); |
|
| 28 | /// @notice uint256 shares = BytesLib.toUint256(data, 72); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
|
| 30 | 426× | contract Redeem4626VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 4× | uint256 private constant AMOUNT_POSITION = 72; |
| 34 | 5× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
| 35 | ||
| 36 | 7× | constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC4626) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 16× | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 2× | returns (Execution[] memory executions) |
| 51 | 8× | { |
| 52 | 102× | address yieldSource = data.extractYieldSource(); |
| 53 | 106× | address owner = BytesLib.toAddress(data, 52); |
| 54 | 102× | uint256 shares = _decodeAmount(data); |
| 55 | 102× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 56 | ||
| 57 | 10× | if (usePrevHookAmount) { |
| 58 | 55× | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 59 | } |
|
| 60 | ||
| 61 | 38× | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 62 | 10× | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 63 | ||
| 64 | 68× | executions = new Execution[](1); |
| 65 | 80× | executions[0] = Execution({ |
| 66 | target: yieldSource, |
|
| 67 | value: 0, |
|
| 68 | 52× | callData: abi.encodeCall(IERC4626.redeem, (shares, account, owner)) |
| 69 | }); |
|
| 70 | } |
|
| 71 | ||
| 72 | /*////////////////////////////////////////////////////////////// |
|
| 73 | EXTERNAL METHODS |
|
| 74 | //////////////////////////////////////////////////////////////*/ |
|
| 75 | ||
| 76 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 77 | 24× | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 78 | 8× | return _decodeAmount(data); |
| 79 | } |
|
| 80 | ||
| 81 | /// @inheritdoc ISuperHookContextAware |
|
| 82 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 83 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 84 | } |
|
| 85 | ||
| 86 | /// @inheritdoc ISuperHookOutflow |
|
| 87 | 52× | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 88 | 16× | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 89 | } |
|
| 90 | ||
| 91 | /// @inheritdoc ISuperHookInspector |
|
| 92 | 34× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 93 | 38× | return abi.encodePacked( |
| 94 | 96× | data.extractYieldSource(), |
| 95 | 100× | BytesLib.toAddress(data, 52) // owner |
| 96 | ); |
|
| 97 | } |
|
| 98 | ||
| 99 | /*////////////////////////////////////////////////////////////// |
|
| 100 | INTERNAL METHODS |
|
| 101 | //////////////////////////////////////////////////////////////*/ |
|
| 102 | 14× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 103 | 102× | address yieldSource = data.extractYieldSource(); |
| 104 | 136× | asset = IERC4626(yieldSource).asset(); |
| 105 | 108× | _setOutAmount(_getBalance(account, data), account); |
| 106 | 104× | usedShares = _getSharesBalance(account, data); |
| 107 | 22× | spToken = yieldSource; |
| 108 | } |
|
| 109 | ||
| 110 | 6× | function _postExecute(address, address account, bytes calldata data) internal override { |
| 111 | 65× | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 112 | 60× | usedShares = usedShares - _getSharesBalance(account, data); |
| 113 | } |
|
| 114 | ||
| 115 | /*////////////////////////////////////////////////////////////// |
|
| 116 | PRIVATE METHODS |
|
| 117 | //////////////////////////////////////////////////////////////*/ |
|
| 118 | 4× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 119 | 8× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 120 | } |
|
| 121 | ||
| 122 | 6× | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 123 | 116× | return IERC20(asset).balanceOf(account); |
| 124 | } |
|
| 125 | ||
| 126 | 8× | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 127 | 16× | address yieldSource = data.extractYieldSource(); |
| 128 | 16× | address owner = BytesLib.toAddress(data, 52); |
| 129 | 10× | if (owner == address(0)) { |
| 130 | 0 | owner = account; |
| 131 | } |
|
| 132 | 114× | return IERC4626(yieldSource).balanceOf(owner); |
| 133 | } |
|
| 134 | } |
88%
lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol
Lines covered: 40 / 45 (88%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 8 | import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 13 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 14 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 15 | import { |
|
| 16 | ISuperHookResult, |
|
| 17 | ISuperHookInflowOutflow, |
|
| 18 | ISuperHookContextAware, |
|
| 19 | ISuperHookInspector |
|
| 20 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 21 | ||
| 22 | /// @title ApproveAndDeposit5115VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @notice This hook does not support tokens reverting on 0 approval |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice address tokenIn = BytesLib.toAddress(data, 52); |
|
| 29 | /// @notice uint256 amount = BytesLib.toUint256(data, 72); |
|
| 30 | /// @notice uint256 minSharesOut = BytesLib.toUint256(data, 104); |
|
| 31 | /// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
|
| 32 | 413× | contract ApproveAndDeposit5115VaultHook is |
| 33 | BaseHook, |
|
| 34 | VaultBankLockableHook, |
|
| 35 | ISuperHookInflowOutflow, |
|
| 36 | ISuperHookContextAware |
|
| 37 | { |
|
| 38 | using HookDataDecoder for bytes; |
|
| 39 | ||
| 40 | 4× | uint256 private constant AMOUNT_POSITION = 72; |
| 41 | 6× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
| 42 | ||
| 43 | 7× | constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { } |
| 44 | ||
| 45 | /*////////////////////////////////////////////////////////////// |
|
| 46 | VIEW METHODS |
|
| 47 | //////////////////////////////////////////////////////////////*/ |
|
| 48 | /// @inheritdoc BaseHook |
|
| 49 | 9× | function _buildHookExecutions( |
| 50 | address prevHook, |
|
| 51 | address account, |
|
| 52 | bytes calldata data |
|
| 53 | ) |
|
| 54 | internal |
|
| 55 | view |
|
| 56 | override |
|
| 57 | 2× | returns (Execution[] memory executions) |
| 58 | 5× | { |
| 59 | 102× | address yieldSource = data.extractYieldSource(); |
| 60 | 106× | address tokenIn = BytesLib.toAddress(data, 52); |
| 61 | 102× | uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
| 62 | 106× | uint256 minSharesOut = BytesLib.toUint256(data, 104); |
| 63 | 102× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 64 | ||
| 65 | 10× | if (usePrevHookAmount) { |
| 66 | 55× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 67 | } |
|
| 68 | ||
| 69 | 24× | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 70 | 21× | if (yieldSource == address(0) || account == address(0) || tokenIn == address(0)) revert ADDRESS_NOT_VALID(); |
| 71 | ||
| 72 | 34× | executions = new Execution[](4); |
| 73 | 16× | executions[0] = |
| 74 | 52× | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 75 | 20× | executions[1] = |
| 76 | 53× | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 77 | 39× | executions[2] = Execution({ |
| 78 | 1× | target: yieldSource, |
| 79 | 1× | value: 0, |
| 80 | 36× | callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
| 81 | }); |
|
| 82 | 20× | executions[3] = |
| 83 | 53× | Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 84 | } |
|
| 85 | ||
| 86 | /*////////////////////////////////////////////////////////////// |
|
| 87 | EXTERNAL METHODS |
|
| 88 | //////////////////////////////////////////////////////////////*/ |
|
| 89 | ||
| 90 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 91 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 92 | 0 | return _decodeAmount(data); |
| 93 | } |
|
| 94 | ||
| 95 | /// @inheritdoc ISuperHookContextAware |
|
| 96 | 32× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 97 | 8× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 98 | } |
|
| 99 | ||
| 100 | /// @inheritdoc ISuperHookInspector |
|
| 101 | 50× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 102 | 38× | return abi.encodePacked( |
| 103 | 96× | data.extractYieldSource(), |
| 104 | 100× | BytesLib.toAddress(data, 52) // tokenIn |
| 105 | ); |
|
| 106 | } |
|
| 107 | ||
| 108 | /*////////////////////////////////////////////////////////////// |
|
| 109 | INTERNAL METHODS |
|
| 110 | //////////////////////////////////////////////////////////////*/ |
|
| 111 | 6× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 112 | 50× | _setOutAmount(_getBalance(account, data), account); |
| 113 | 58× | spToken = data.extractYieldSource(); |
| 114 | 60× | asset = BytesLib.toAddress(data, 52); |
| 115 | } |
|
| 116 | ||
| 117 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 118 | 4× | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 119 | } |
|
| 120 | ||
| 121 | /*////////////////////////////////////////////////////////////// |
|
| 122 | PRIVATE METHODS |
|
| 123 | //////////////////////////////////////////////////////////////*/ |
|
| 124 | 0 | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 125 | 0 | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 126 | } |
|
| 127 | ||
| 128 | 6× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 129 | 66× | return IStandardizedYield(data.extractYieldSource()).balanceOf(account); |
| 130 | } |
|
| 131 | } |
46%
lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol
Lines covered: 18 / 39 (46%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol"; |
|
| 8 | ||
| 9 | // Superform |
|
| 10 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 11 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 12 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 13 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 14 | import { |
|
| 15 | ISuperHookResult, |
|
| 16 | ISuperHookInflowOutflow, |
|
| 17 | ISuperHookContextAware, |
|
| 18 | ISuperHookInspector |
|
| 19 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 20 | ||
| 21 | /// @title Deposit5115VaultHook |
|
| 22 | /// @author Superform Labs |
|
| 23 | /// @dev data has the following structure |
|
| 24 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 25 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 26 | /// @notice address tokenIn = BytesLib.toAddress(data, 52); |
|
| 27 | /// @notice uint256 amount = BytesLib.toUint256(data, 72); |
|
| 28 | /// @notice uint256 minSharesOut = BytesLib.toUint256(data, 104); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
|
| 30 | 296× | contract Deposit5115VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 2× | uint256 private constant AMOUNT_POSITION = 72; |
| 34 | 1× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
| 35 | ||
| 36 | 7× | constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 1× | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 1× | returns (Execution[] memory executions) |
| 51 | 0 | { |
| 52 | 51× | address yieldSource = data.extractYieldSource(); |
| 53 | 53× | address tokenIn = BytesLib.toAddress(data, 52); |
| 54 | 51× | uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
| 55 | 50× | uint256 minSharesOut = BytesLib.toUint256(data, 104); |
| 56 | 0 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 57 | ||
| 58 | 0 | if (usePrevHookAmount) { |
| 59 | 0 | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 60 | } |
|
| 61 | ||
| 62 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 63 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 64 | ||
| 65 | 0 | executions = new Execution[](1); |
| 66 | 0 | executions[0] = Execution({ |
| 67 | 0 | target: yieldSource, |
| 68 | 0 | value: tokenIn == address(0) ? amount : 0, |
| 69 | 0 | callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
| 70 | }); |
|
| 71 | } |
|
| 72 | ||
| 73 | /*////////////////////////////////////////////////////////////// |
|
| 74 | EXTERNAL METHODS |
|
| 75 | //////////////////////////////////////////////////////////////*/ |
|
| 76 | ||
| 77 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 78 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 79 | 0 | return _decodeAmount(data); |
| 80 | } |
|
| 81 | ||
| 82 | /// @inheritdoc ISuperHookContextAware |
|
| 83 | 12× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 84 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 85 | } |
|
| 86 | ||
| 87 | /// @inheritdoc ISuperHookInspector |
|
| 88 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 89 | 19× | return abi.encodePacked( |
| 90 | 48× | data.extractYieldSource(), |
| 91 | 50× | BytesLib.toAddress(data, 52) // tokenIn |
| 92 | ); |
|
| 93 | } |
|
| 94 | ||
| 95 | /*////////////////////////////////////////////////////////////// |
|
| 96 | INTERNAL METHODS |
|
| 97 | //////////////////////////////////////////////////////////////*/ |
|
| 98 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 100 | 0 | spToken = data.extractYieldSource(); |
| 101 | 0 | asset = BytesLib.toAddress(data, 52); |
| 102 | } |
|
| 103 | ||
| 104 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 105 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 106 | } |
|
| 107 | ||
| 108 | /*////////////////////////////////////////////////////////////// |
|
| 109 | PRIVATE METHODS |
|
| 110 | //////////////////////////////////////////////////////////////*/ |
|
| 111 | 0 | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 112 | 0 | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 113 | } |
|
| 114 | ||
| 115 | 2× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 116 | 2× | return IStandardizedYield(data.extractYieldSource()).balanceOf(account); |
| 117 | } |
|
| 118 | } |
61%
lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol
Lines covered: 27 / 44 (61%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 8 | import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResultOutflow, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookOutflow, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title Redeem5115VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @dev data has the following structure |
|
| 25 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 26 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 27 | /// @notice address tokenOut = BytesLib.toAddress(data, 52); |
|
| 28 | /// @notice uint256 shares = BytesLib.toUint256(data, 72); |
|
| 29 | /// @notice uint256 minTokenOut = BytesLib.toUint256(data, 104); |
|
| 30 | /// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
|
| 31 | 408× | contract Redeem5115VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware { |
| 32 | using HookDataDecoder for bytes; |
|
| 33 | ||
| 34 | 4× | uint256 private constant AMOUNT_POSITION = 72; |
| 35 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
| 36 | ||
| 37 | 7× | constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC5115) { } |
| 38 | ||
| 39 | /*////////////////////////////////////////////////////////////// |
|
| 40 | VIEW METHODS |
|
| 41 | //////////////////////////////////////////////////////////////*/ |
|
| 42 | /// @inheritdoc BaseHook |
|
| 43 | 2× | function _buildHookExecutions( |
| 44 | address prevHook, |
|
| 45 | address account, |
|
| 46 | bytes calldata data |
|
| 47 | ) |
|
| 48 | internal |
|
| 49 | view |
|
| 50 | override |
|
| 51 | 2× | returns (Execution[] memory executions) |
| 52 | 0 | { |
| 53 | 102× | address yieldSource = data.extractYieldSource(); |
| 54 | 106× | address tokenOut = BytesLib.toAddress(data, 52); |
| 55 | 102× | uint256 shares = _decodeAmount(data); |
| 56 | 103× | uint256 minTokenOut = BytesLib.toUint256(data, 104); |
| 57 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 58 | ||
| 59 | 4× | if (usePrevHookAmount) { |
| 60 | 55× | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 61 | } |
|
| 62 | ||
| 63 | 0 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 64 | 0 | if (yieldSource == address(0) || tokenOut == address(0)) revert ADDRESS_NOT_VALID(); |
| 65 | ||
| 66 | 0 | executions = new Execution[](1); |
| 67 | 0 | executions[0] = Execution({ |
| 68 | target: yieldSource, |
|
| 69 | value: 0, |
|
| 70 | 0 | callData: abi.encodeCall(IStandardizedYield.redeem, (account, shares, tokenOut, minTokenOut, false)) |
| 71 | }); |
|
| 72 | } |
|
| 73 | ||
| 74 | /*////////////////////////////////////////////////////////////// |
|
| 75 | EXTERNAL METHODS |
|
| 76 | //////////////////////////////////////////////////////////////*/ |
|
| 77 | ||
| 78 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 79 | 24× | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 80 | 8× | return _decodeAmount(data); |
| 81 | } |
|
| 82 | ||
| 83 | /// @inheritdoc ISuperHookContextAware |
|
| 84 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 85 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 86 | } |
|
| 87 | ||
| 88 | /// @inheritdoc ISuperHookOutflow |
|
| 89 | 52× | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 90 | 16× | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 91 | } |
|
| 92 | ||
| 93 | /// @inheritdoc ISuperHookInspector |
|
| 94 | 34× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 95 | 38× | return abi.encodePacked( |
| 96 | 96× | data.extractYieldSource(), |
| 97 | 100× | BytesLib.toAddress(data, 52) // tokenOut |
| 98 | ); |
|
| 99 | } |
|
| 100 | ||
| 101 | /*////////////////////////////////////////////////////////////// |
|
| 102 | INTERNAL METHODS |
|
| 103 | //////////////////////////////////////////////////////////////*/ |
|
| 104 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 105 | 0 | asset = BytesLib.toAddress(data, 52); // tokenOut from data |
| 106 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 107 | 0 | usedShares = _getSharesBalance(account, data); |
| 108 | 0 | spToken = data.extractYieldSource(); |
| 109 | } |
|
| 110 | ||
| 111 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 112 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 113 | 0 | usedShares = usedShares - _getSharesBalance(account, data); |
| 114 | } |
|
| 115 | ||
| 116 | /*////////////////////////////////////////////////////////////// |
|
| 117 | PRIVATE METHODS |
|
| 118 | //////////////////////////////////////////////////////////////*/ |
|
| 119 | 4× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 120 | 8× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 121 | } |
|
| 122 | ||
| 123 | 0 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 124 | 0 | return IERC20(asset).balanceOf(account); |
| 125 | } |
|
| 126 | ||
| 127 | 4× | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 128 | 0 | address yieldSource = data.extractYieldSource(); |
| 129 | 4× | return IStandardizedYield(yieldSource).balanceOf(account); |
| 130 | } |
|
| 131 | } |
52%
lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol
Lines covered: 22 / 42 (52%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 7 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 8 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { |
|
| 12 | ISuperHookResult, |
|
| 13 | ISuperHookInflowOutflow, |
|
| 14 | ISuperHookContextAware, |
|
| 15 | ISuperHookInspector |
|
| 16 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 17 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 18 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 19 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 20 | ||
| 21 | /// @title ApproveAndRequestDeposit7540VaultHook |
|
| 22 | /// @author Superform Labs |
|
| 23 | /// @notice This hook does not support tokens reverting on 0 approval |
|
| 24 | /// @dev data has the following structure |
|
| 25 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 26 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 27 | /// @notice address token = BytesLib.toAddress(data, 52); |
|
| 28 | /// @notice uint256 amount = BytesLib.toUint256(data, 72); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
|
| 30 | 393× | contract ApproveAndRequestDeposit7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 2× | uint256 private constant AMOUNT_POSITION = 72; |
| 34 | 6× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
| 35 | ||
| 36 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 2× | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 2× | returns (Execution[] memory executions) |
| 51 | 0 | { |
| 52 | 102× | address yieldSource = data.extractYieldSource(); |
| 53 | 106× | address token = BytesLib.toAddress(data, 52); |
| 54 | 102× | uint256 amount = _decodeAmount(data); |
| 55 | 102× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 56 | ||
| 57 | 8× | if (usePrevHookAmount) { |
| 58 | 110× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 59 | } |
|
| 60 | ||
| 61 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 62 | 0 | if (yieldSource == address(0) || account == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
| 63 | ||
| 64 | 0 | executions = new Execution[](4); |
| 65 | 0 | executions[0] = |
| 66 | 0 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 67 | 0 | executions[1] = |
| 68 | 0 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) }); |
| 69 | 0 | executions[2] = Execution({ |
| 70 | 0 | target: yieldSource, |
| 71 | 0 | value: 0, |
| 72 | 0 | callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
| 73 | }); |
|
| 74 | 0 | executions[3] = |
| 75 | 0 | Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) }); |
| 76 | } |
|
| 77 | ||
| 78 | /*////////////////////////////////////////////////////////////// |
|
| 79 | EXTERNAL METHODS |
|
| 80 | //////////////////////////////////////////////////////////////*/ |
|
| 81 | ||
| 82 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 83 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 84 | 0 | return _decodeAmount(data); |
| 85 | } |
|
| 86 | ||
| 87 | /// @inheritdoc ISuperHookContextAware |
|
| 88 | 32× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 89 | 8× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 90 | } |
|
| 91 | ||
| 92 | /// @inheritdoc ISuperHookInspector |
|
| 93 | 50× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 94 | 38× | return abi.encodePacked( |
| 95 | 96× | data.extractYieldSource(), |
| 96 | 100× | BytesLib.toAddress(data, 52) //token |
| 97 | ); |
|
| 98 | } |
|
| 99 | ||
| 100 | /*////////////////////////////////////////////////////////////// |
|
| 101 | INTERNAL METHODS |
|
| 102 | //////////////////////////////////////////////////////////////*/ |
|
| 103 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 104 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 105 | } |
|
| 106 | ||
| 107 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 108 | 0 | _setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
| 109 | } |
|
| 110 | ||
| 111 | /*////////////////////////////////////////////////////////////// |
|
| 112 | PRIVATE METHODS |
|
| 113 | //////////////////////////////////////////////////////////////*/ |
|
| 114 | 4× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 115 | 8× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 116 | } |
|
| 117 | ||
| 118 | 4× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 119 | 4× | return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account); |
| 120 | } |
|
| 121 | } |
100%
lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol
Lines covered: 13 / 13 (100%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 6 | import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol"; |
|
| 7 | ||
| 8 | // Superform |
|
| 9 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 10 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 11 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 12 | import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol"; |
|
| 13 | ||
| 14 | /// @title CancelDepositRequest7540Hook |
|
| 15 | /// @author Superform Labs |
|
| 16 | /// @dev data has the following structure |
|
| 17 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 18 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 19 | 269× | contract CancelDepositRequest7540Hook is BaseHook { |
| 20 | using HookDataDecoder for bytes; |
|
| 21 | ||
| 22 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_DEPOSIT_REQUEST) { } |
| 23 | ||
| 24 | /*////////////////////////////////////////////////////////////// |
|
| 25 | VIEW METHODS |
|
| 26 | //////////////////////////////////////////////////////////////*/ |
|
| 27 | /// @inheritdoc BaseHook |
|
| 28 | 1× | function _buildHookExecutions( |
| 29 | address, |
|
| 30 | address account, |
|
| 31 | bytes calldata data |
|
| 32 | ) |
|
| 33 | internal |
|
| 34 | pure |
|
| 35 | override |
|
| 36 | 1× | returns (Execution[] memory executions) |
| 37 | { |
|
| 38 | 50× | address yieldSource = data.extractYieldSource(); |
| 39 | ||
| 40 | 5× | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 41 | ||
| 42 | 35× | executions = new Execution[](1); |
| 43 | 25× | executions[0] = Execution({ |
| 44 | 1× | target: yieldSource, |
| 45 | 1× | value: 0, |
| 46 | 32× | callData: abi.encodeCall(IERC7540CancelDeposit.cancelDepositRequest, (0, account)) |
| 47 | }); |
|
| 48 | } |
|
| 49 | ||
| 50 | /// @inheritdoc ISuperHookInspector |
|
| 51 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 52 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 53 | } |
|
| 54 | } |
100%
lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol
Lines covered: 13 / 13 (100%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 6 | import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol"; |
|
| 7 | ||
| 8 | // Superform |
|
| 9 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 10 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 11 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 12 | import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol"; |
|
| 13 | ||
| 14 | /// @title CancelRedeemRequest7540Hook |
|
| 15 | /// @author Superform Labs |
|
| 16 | /// @dev data has the following structure |
|
| 17 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 18 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 19 | 269× | contract CancelRedeemRequest7540Hook is BaseHook { |
| 20 | using HookDataDecoder for bytes; |
|
| 21 | ||
| 22 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM_REQUEST) { } |
| 23 | ||
| 24 | /*////////////////////////////////////////////////////////////// |
|
| 25 | VIEW METHODS |
|
| 26 | //////////////////////////////////////////////////////////////*/ |
|
| 27 | /// @inheritdoc BaseHook |
|
| 28 | 1× | function _buildHookExecutions( |
| 29 | address, |
|
| 30 | address account, |
|
| 31 | bytes calldata data |
|
| 32 | ) |
|
| 33 | internal |
|
| 34 | pure |
|
| 35 | override |
|
| 36 | 1× | returns (Execution[] memory executions) |
| 37 | { |
|
| 38 | 50× | address yieldSource = data.extractYieldSource(); |
| 39 | ||
| 40 | 5× | if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
| 41 | ||
| 42 | 35× | executions = new Execution[](1); |
| 43 | 25× | executions[0] = Execution({ |
| 44 | 1× | target: yieldSource, |
| 45 | 1× | value: 0, |
| 46 | 32× | callData: abi.encodeCall(IERC7540CancelRedeem.cancelRedeemRequest, (0, account)) |
| 47 | }); |
|
| 48 | } |
|
| 49 | ||
| 50 | /// @inheritdoc ISuperHookInspector |
|
| 51 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 52 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 53 | } |
|
| 54 | } |
93%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol
Lines covered: 27 / 29 (93%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol"; |
|
| 8 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 9 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 13 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 14 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 15 | import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol"; |
|
| 16 | ||
| 17 | /// @title ClaimCancelDepositRequest7540Hook |
|
| 18 | /// @author Superform Labs |
|
| 19 | /// @dev data has the following structure |
|
| 20 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 21 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 22 | /// @notice address receiver = BytesLib.toAddress(data, 52); |
|
| 23 | 261× | contract ClaimCancelDepositRequest7540Hook is BaseHook, ISuperHookAsyncCancelations { |
| 24 | using HookDataDecoder for bytes; |
|
| 25 | ||
| 26 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_DEPOSIT_REQUEST) { } |
| 27 | ||
| 28 | /*////////////////////////////////////////////////////////////// |
|
| 29 | VIEW METHODS |
|
| 30 | //////////////////////////////////////////////////////////////*/ |
|
| 31 | /// @inheritdoc BaseHook |
|
| 32 | 8× | function _buildHookExecutions( |
| 33 | address, |
|
| 34 | address account, |
|
| 35 | bytes calldata data |
|
| 36 | ) |
|
| 37 | internal |
|
| 38 | pure |
|
| 39 | override |
|
| 40 | 1× | returns (Execution[] memory executions) |
| 41 | 2× | { |
| 42 | 51× | address yieldSource = data.extractYieldSource(); |
| 43 | 52× | address receiver = BytesLib.toAddress(data, 52); |
| 44 | ||
| 45 | 14× | if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
| 46 | ||
| 47 | 35× | executions = new Execution[](1); |
| 48 | 35× | executions[0] = Execution({ |
| 49 | 1× | target: yieldSource, |
| 50 | 1× | value: 0, |
| 51 | 34× | callData: abi.encodeCall(IERC7540CancelDeposit.claimCancelDepositRequest, (0, receiver, account)) |
| 52 | }); |
|
| 53 | } |
|
| 54 | ||
| 55 | /*////////////////////////////////////////////////////////////// |
|
| 56 | EXTERNAL METHODS |
|
| 57 | //////////////////////////////////////////////////////////////*/ |
|
| 58 | ||
| 59 | /// @inheritdoc ISuperHookAsyncCancelations |
|
| 60 | 0 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 61 | 0 | return CancelationType.INFLOW; |
| 62 | } |
|
| 63 | ||
| 64 | /// @inheritdoc ISuperHookInspector |
|
| 65 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 66 | 19× | return abi.encodePacked( |
| 67 | 48× | data.extractYieldSource(), |
| 68 | 50× | BytesLib.toAddress(data, 52) //receiver |
| 69 | ); |
|
| 70 | } |
|
| 71 | ||
| 72 | /*////////////////////////////////////////////////////////////// |
|
| 73 | INTERNAL METHODS |
|
| 74 | //////////////////////////////////////////////////////////////*/ |
|
| 75 | 8× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 76 | 51× | address yieldSource = data.extractYieldSource(); |
| 77 | 53× | address receiver = BytesLib.toAddress(data, 52); |
| 78 | 75× | asset = IERC7540(yieldSource).asset(); |
| 79 | // store current balance |
|
| 80 | 54× | _setOutAmount(_getBalance(receiver, data), account); |
| 81 | } |
|
| 82 | ||
| 83 | 1× | function _postExecute(address, address account, bytes calldata data) internal override { |
| 84 | 53× | address receiver = BytesLib.toAddress(data, 52); |
| 85 | ||
| 86 | 64× | _setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account); |
| 87 | } |
|
| 88 | ||
| 89 | /*////////////////////////////////////////////////////////////// |
|
| 90 | PRIVATE METHODS |
|
| 91 | //////////////////////////////////////////////////////////////*/ |
|
| 92 | ||
| 93 | 5× | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 94 | 60× | return IERC20(asset).balanceOf(account); |
| 95 | } |
|
| 96 | } |
92%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol
Lines covered: 25 / 27 (92%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 8 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 9 | import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 13 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 14 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 15 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 16 | import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol"; |
|
| 17 | ||
| 18 | /// @title ClaimCancelRedeemRequest7540Hook |
|
| 19 | /// @author Superform Labs |
|
| 20 | /// @dev data has the following structure |
|
| 21 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 22 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 23 | /// @notice address receiver = BytesLib.toAddress(data, 52); |
|
| 24 | 276× | contract ClaimCancelRedeemRequest7540Hook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations { |
| 25 | using HookDataDecoder for bytes; |
|
| 26 | ||
| 27 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_REDEEM_REQUEST) { } |
| 28 | ||
| 29 | /*////////////////////////////////////////////////////////////// |
|
| 30 | VIEW METHODS |
|
| 31 | //////////////////////////////////////////////////////////////*/ |
|
| 32 | /// @inheritdoc BaseHook |
|
| 33 | 8× | function _buildHookExecutions( |
| 34 | address, |
|
| 35 | address account, |
|
| 36 | bytes calldata data |
|
| 37 | ) |
|
| 38 | internal |
|
| 39 | pure |
|
| 40 | override |
|
| 41 | 1× | returns (Execution[] memory executions) |
| 42 | 2× | { |
| 43 | 51× | address yieldSource = data.extractYieldSource(); |
| 44 | 52× | address receiver = BytesLib.toAddress(data, 52); |
| 45 | ||
| 46 | 14× | if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
| 47 | ||
| 48 | 35× | executions = new Execution[](1); |
| 49 | 35× | executions[0] = Execution({ |
| 50 | 1× | target: yieldSource, |
| 51 | 1× | value: 0, |
| 52 | 34× | callData: abi.encodeCall(IERC7540CancelRedeem.claimCancelRedeemRequest, (0, receiver, account)) |
| 53 | }); |
|
| 54 | } |
|
| 55 | ||
| 56 | /*////////////////////////////////////////////////////////////// |
|
| 57 | EXTERNAL METHODS |
|
| 58 | //////////////////////////////////////////////////////////////*/ |
|
| 59 | ||
| 60 | /// @inheritdoc ISuperHookAsyncCancelations |
|
| 61 | 0 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 62 | 0 | return CancelationType.OUTFLOW; |
| 63 | } |
|
| 64 | ||
| 65 | /// @inheritdoc ISuperHookInspector |
|
| 66 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 67 | 19× | return abi.encodePacked( |
| 68 | 48× | data.extractYieldSource(), |
| 69 | 50× | BytesLib.toAddress(data, 52) //receiver |
| 70 | ); |
|
| 71 | } |
|
| 72 | ||
| 73 | /*////////////////////////////////////////////////////////////// |
|
| 74 | INTERNAL METHODS |
|
| 75 | //////////////////////////////////////////////////////////////*/ |
|
| 76 | 6× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 77 | 54× | _setOutAmount(_getBalance(account, data), account); |
| 78 | 115× | spToken = IERC7540(data.extractYieldSource()).share(); |
| 79 | } |
|
| 80 | ||
| 81 | 1× | function _postExecute(address, address account, bytes calldata data) internal override { |
| 82 | 53× | address receiver = BytesLib.toAddress(data, 52); |
| 83 | 64× | _setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account); |
| 84 | } |
|
| 85 | ||
| 86 | /*////////////////////////////////////////////////////////////// |
|
| 87 | PRIVATE METHODS |
|
| 88 | //////////////////////////////////////////////////////////////*/ |
|
| 89 | ||
| 90 | 4× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 91 | 128× | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 92 | } |
|
| 93 | } |
59%
lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol
Lines covered: 19 / 32 (59%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 8 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 13 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 14 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 15 | import { |
|
| 16 | ISuperHookResult, |
|
| 17 | ISuperHookInflowOutflow, |
|
| 18 | ISuperHookContextAware, |
|
| 19 | ISuperHookInspector |
|
| 20 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 21 | ||
| 22 | /// @title Deposit7540VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @dev data has the following structure |
|
| 25 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 26 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 27 | /// @notice uint256 amount = BytesLib.toUint256(data, 52); |
|
| 28 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 29 | 296× | contract Deposit7540VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware { |
| 30 | using HookDataDecoder for bytes; |
|
| 31 | ||
| 32 | 1× | uint256 private constant AMOUNT_POSITION = 52; |
| 33 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 34 | ||
| 35 | 7× | constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC7540) { } |
| 36 | ||
| 37 | /*////////////////////////////////////////////////////////////// |
|
| 38 | VIEW METHODS |
|
| 39 | //////////////////////////////////////////////////////////////*/ |
|
| 40 | /// @inheritdoc BaseHook |
|
| 41 | 1× | function _buildHookExecutions( |
| 42 | address prevHook, |
|
| 43 | address account, |
|
| 44 | bytes calldata data |
|
| 45 | ) |
|
| 46 | internal |
|
| 47 | view |
|
| 48 | override |
|
| 49 | 1× | returns (Execution[] memory executions) |
| 50 | 0 | { |
| 51 | 51× | address yieldSource = data.extractYieldSource(); |
| 52 | 51× | uint256 amount = _decodeAmount(data); |
| 53 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 54 | ||
| 55 | 4× | if (usePrevHookAmount) { |
| 56 | 55× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 57 | } |
|
| 58 | ||
| 59 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 60 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 61 | ||
| 62 | 0 | executions = new Execution[](1); |
| 63 | 0 | executions[0] = Execution({ |
| 64 | target: yieldSource, |
|
| 65 | value: 0, |
|
| 66 | 0 | callData: abi.encodeCall(IERC7540.deposit, (amount, account, account)) |
| 67 | }); |
|
| 68 | } |
|
| 69 | ||
| 70 | /*////////////////////////////////////////////////////////////// |
|
| 71 | EXTERNAL METHODS |
|
| 72 | //////////////////////////////////////////////////////////////*/ |
|
| 73 | ||
| 74 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 75 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 76 | 0 | return _decodeAmount(data); |
| 77 | } |
|
| 78 | ||
| 79 | /// @inheritdoc ISuperHookContextAware |
|
| 80 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 81 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 82 | } |
|
| 83 | ||
| 84 | /// @inheritdoc ISuperHookInspector |
|
| 85 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 86 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 87 | } |
|
| 88 | ||
| 89 | /*////////////////////////////////////////////////////////////// |
|
| 90 | INTERNAL METHODS |
|
| 91 | //////////////////////////////////////////////////////////////*/ |
|
| 92 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 93 | // store current balance |
|
| 94 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 95 | 0 | spToken = IERC7540(data.extractYieldSource()).share(); |
| 96 | } |
|
| 97 | ||
| 98 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 99 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 100 | } |
|
| 101 | ||
| 102 | /*////////////////////////////////////////////////////////////// |
|
| 103 | PRIVATE METHODS |
|
| 104 | //////////////////////////////////////////////////////////////*/ |
|
| 105 | 2× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 106 | 4× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 107 | } |
|
| 108 | ||
| 109 | 2× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 110 | 2× | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 111 | } |
|
| 112 | } |
100%
lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol
Lines covered: 41 / 41 (100%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 8 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResultOutflow, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookOutflow, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title Redeem7540VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @notice This hook does not support tokens reverting on 0 approval |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice uint256 shares = BytesLib.toUint256(data, 52); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 30 | 408× | contract Redeem7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 4× | uint256 private constant AMOUNT_POSITION = 52; |
| 34 | 5× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 35 | ||
| 36 | 7× | constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 16× | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 2× | returns (Execution[] memory executions) |
| 51 | 6× | { |
| 52 | 102× | address yieldSource = data.extractYieldSource(); |
| 53 | 102× | uint256 shares = _decodeAmount(data); |
| 54 | 102× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | ||
| 56 | 10× | if (usePrevHookAmount) { |
| 57 | 55× | shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
|
| 59 | ||
| 60 | 38× | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 61 | 28× | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | ||
| 63 | 68× | executions = new Execution[](1); |
| 64 | 80× | executions[0] = Execution({ |
| 65 | target: yieldSource, |
|
| 66 | value: 0, |
|
| 67 | 52× | callData: abi.encodeCall(IERC7540.redeem, (shares, account, account)) |
| 68 | }); |
|
| 69 | } |
|
| 70 | ||
| 71 | /*////////////////////////////////////////////////////////////// |
|
| 72 | EXTERNAL METHODS |
|
| 73 | //////////////////////////////////////////////////////////////*/ |
|
| 74 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 75 | 24× | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 76 | 8× | return _decodeAmount(data); |
| 77 | } |
|
| 78 | ||
| 79 | /// @inheritdoc ISuperHookContextAware |
|
| 80 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 81 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 82 | } |
|
| 83 | ||
| 84 | /// @inheritdoc ISuperHookOutflow |
|
| 85 | 52× | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 86 | 16× | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 87 | } |
|
| 88 | ||
| 89 | /// @inheritdoc ISuperHookInspector |
|
| 90 | 34× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 91 | 140× | return abi.encodePacked(data.extractYieldSource()); |
| 92 | } |
|
| 93 | ||
| 94 | /*////////////////////////////////////////////////////////////// |
|
| 95 | INTERNAL METHODS |
|
| 96 | //////////////////////////////////////////////////////////////*/ |
|
| 97 | 14× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 98 | 102× | address yieldSource = data.extractYieldSource(); |
| 99 | 136× | asset = IERC7540(yieldSource).asset(); |
| 100 | 108× | _setOutAmount(_getBalance(account, data), account); |
| 101 | 106× | usedShares = _getSharesBalance(account, data); |
| 102 | 136× | spToken = IERC7540(yieldSource).share(); |
| 103 | } |
|
| 104 | ||
| 105 | 6× | function _postExecute(address, address account, bytes calldata data) internal override { |
| 106 | 65× | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 107 | 60× | usedShares = usedShares - _getSharesBalance(account, data); |
| 108 | } |
|
| 109 | ||
| 110 | /*////////////////////////////////////////////////////////////// |
|
| 111 | PRIVATE METHODS |
|
| 112 | //////////////////////////////////////////////////////////////*/ |
|
| 113 | 4× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 114 | 8× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 115 | } |
|
| 116 | ||
| 117 | 6× | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 118 | 116× | return IERC20(asset).balanceOf(account); |
| 119 | } |
|
| 120 | ||
| 121 | 8× | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 122 | 16× | address yieldSource = data.extractYieldSource(); |
| 123 | 118× | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 124 | } |
|
| 125 | } |
59%
lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol
Lines covered: 19 / 32 (59%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 8 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResult, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookAsyncCancelations, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title RequestDeposit7540VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @dev data has the following structure |
|
| 25 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 26 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 27 | /// @notice uint256 amount = BytesLib.toUint256(data, 52); |
|
| 28 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 29 | 296× | contract RequestDeposit7540VaultHook is |
| 30 | BaseHook, |
|
| 31 | ISuperHookInflowOutflow, |
|
| 32 | ISuperHookAsyncCancelations, |
|
| 33 | ISuperHookContextAware |
|
| 34 | { |
|
| 35 | using HookDataDecoder for bytes; |
|
| 36 | ||
| 37 | 1× | uint256 private constant AMOUNT_POSITION = 52; |
| 38 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 39 | ||
| 40 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { } |
| 41 | ||
| 42 | /*////////////////////////////////////////////////////////////// |
|
| 43 | VIEW METHODS |
|
| 44 | //////////////////////////////////////////////////////////////*/ |
|
| 45 | /// @inheritdoc BaseHook |
|
| 46 | 1× | function _buildHookExecutions( |
| 47 | address prevHook, |
|
| 48 | address account, |
|
| 49 | bytes calldata data |
|
| 50 | ) |
|
| 51 | internal |
|
| 52 | view |
|
| 53 | override |
|
| 54 | 1× | returns (Execution[] memory executions) |
| 55 | 0 | { |
| 56 | 51× | address yieldSource = data.extractYieldSource(); |
| 57 | 51× | uint256 amount = _decodeAmount(data); |
| 58 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 59 | ||
| 60 | 4× | if (usePrevHookAmount) { |
| 61 | 55× | amount = ISuperHookResult(prevHook).getOutAmount(account); |
| 62 | } |
|
| 63 | ||
| 64 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 65 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 66 | ||
| 67 | 0 | executions = new Execution[](1); |
| 68 | 0 | executions[0] = Execution({ |
| 69 | target: yieldSource, |
|
| 70 | value: 0, |
|
| 71 | 0 | callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
| 72 | }); |
|
| 73 | } |
|
| 74 | ||
| 75 | /// @inheritdoc ISuperHookAsyncCancelations |
|
| 76 | 0 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 77 | return CancelationType.NONE; |
|
| 78 | } |
|
| 79 | ||
| 80 | /*////////////////////////////////////////////////////////////// |
|
| 81 | EXTERNAL METHODS |
|
| 82 | //////////////////////////////////////////////////////////////*/ |
|
| 83 | ||
| 84 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 85 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 86 | 0 | return _decodeAmount(data); |
| 87 | } |
|
| 88 | ||
| 89 | /// @inheritdoc ISuperHookContextAware |
|
| 90 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 91 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 92 | } |
|
| 93 | ||
| 94 | /// @inheritdoc ISuperHookInspector |
|
| 95 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 96 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 97 | } |
|
| 98 | ||
| 99 | /*////////////////////////////////////////////////////////////// |
|
| 100 | INTERNAL METHODS |
|
| 101 | //////////////////////////////////////////////////////////////*/ |
|
| 102 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 103 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 104 | } |
|
| 105 | ||
| 106 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | 0 | _setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
| 108 | } |
|
| 109 | ||
| 110 | /*////////////////////////////////////////////////////////////// |
|
| 111 | PRIVATE METHODS |
|
| 112 | //////////////////////////////////////////////////////////////*/ |
|
| 113 | 2× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 114 | 4× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 115 | } |
|
| 116 | ||
| 117 | 2× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 118 | 2× | return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account); |
| 119 | } |
|
| 120 | } |
59%
lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol
Lines covered: 19 / 32 (59%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 8 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 9 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 13 | import { |
|
| 14 | ISuperHookResult, |
|
| 15 | ISuperHookInflowOutflow, |
|
| 16 | ISuperHookAsyncCancelations, |
|
| 17 | ISuperHookContextAware, |
|
| 18 | ISuperHookInspector |
|
| 19 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 20 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 21 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 22 | ||
| 23 | /// @title RequestRedeem7540VaultHook |
|
| 24 | /// @author Superform Labs |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice uint256 shares = BytesLib.toUint256(data, 52); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 30 | 296× | contract RequestRedeem7540VaultHook is |
| 31 | BaseHook, |
|
| 32 | ISuperHookInflowOutflow, |
|
| 33 | ISuperHookAsyncCancelations, |
|
| 34 | ISuperHookContextAware |
|
| 35 | { |
|
| 36 | using HookDataDecoder for bytes; |
|
| 37 | ||
| 38 | 1× | uint256 private constant AMOUNT_POSITION = 52; |
| 39 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 40 | ||
| 41 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { } |
| 42 | ||
| 43 | /*////////////////////////////////////////////////////////////// |
|
| 44 | VIEW METHODS |
|
| 45 | //////////////////////////////////////////////////////////////*/ |
|
| 46 | /// @inheritdoc BaseHook |
|
| 47 | 1× | function _buildHookExecutions( |
| 48 | address prevHook, |
|
| 49 | address account, |
|
| 50 | bytes calldata data |
|
| 51 | ) |
|
| 52 | internal |
|
| 53 | view |
|
| 54 | override |
|
| 55 | 1× | returns (Execution[] memory executions) |
| 56 | 0 | { |
| 57 | 51× | address yieldSource = data.extractYieldSource(); |
| 58 | 51× | uint256 shares = _decodeAmount(data); |
| 59 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 60 | ||
| 61 | 4× | if (usePrevHookAmount) { |
| 62 | 55× | shares = ISuperHookResult(prevHook).getOutAmount(account); |
| 63 | } |
|
| 64 | ||
| 65 | 0 | if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 66 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 67 | ||
| 68 | 0 | executions = new Execution[](1); |
| 69 | 0 | executions[0] = Execution({ |
| 70 | target: yieldSource, |
|
| 71 | value: 0, |
|
| 72 | 0 | callData: abi.encodeCall(IERC7540.requestRedeem, (shares, account, account)) |
| 73 | }); |
|
| 74 | } |
|
| 75 | ||
| 76 | /// @inheritdoc ISuperHookAsyncCancelations |
|
| 77 | 0 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 78 | return CancelationType.NONE; |
|
| 79 | } |
|
| 80 | ||
| 81 | /*////////////////////////////////////////////////////////////// |
|
| 82 | EXTERNAL METHODS |
|
| 83 | //////////////////////////////////////////////////////////////*/ |
|
| 84 | ||
| 85 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 86 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 87 | 0 | return _decodeAmount(data); |
| 88 | } |
|
| 89 | ||
| 90 | /// @inheritdoc ISuperHookContextAware |
|
| 91 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 92 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 93 | } |
|
| 94 | ||
| 95 | /// @inheritdoc ISuperHookInspector |
|
| 96 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 97 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 98 | } |
|
| 99 | ||
| 100 | /*////////////////////////////////////////////////////////////// |
|
| 101 | INTERNAL METHODS |
|
| 102 | //////////////////////////////////////////////////////////////*/ |
|
| 103 | ||
| 104 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 105 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 106 | } |
|
| 107 | ||
| 108 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 109 | 0 | _setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
| 110 | } |
|
| 111 | ||
| 112 | /*////////////////////////////////////////////////////////////// |
|
| 113 | PRIVATE METHODS |
|
| 114 | //////////////////////////////////////////////////////////////*/ |
|
| 115 | ||
| 116 | 2× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 117 | 4× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 118 | } |
|
| 119 | ||
| 120 | 2× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 121 | 2× | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 122 | } |
|
| 123 | } |
51%
lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol
Lines covered: 21 / 41 (51%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 8 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResultOutflow, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookOutflow, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title Withdraw7540VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice uint256 amount = BytesLib.toUint256(data, 52); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 30 | 291× | contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 1× | uint256 private constant AMOUNT_POSITION = 52; |
| 34 | 3× | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 35 | ||
| 36 | 7× | constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 1× | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 1× | returns (Execution[] memory executions) |
| 51 | 0 | { |
| 52 | 51× | address yieldSource = data.extractYieldSource(); |
| 53 | 51× | uint256 amount = _decodeAmount(data); |
| 54 | 51× | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | ||
| 56 | 4× | if (usePrevHookAmount) { |
| 57 | 55× | amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
|
| 59 | ||
| 60 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 61 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | ||
| 63 | 0 | executions = new Execution[](1); |
| 64 | 0 | executions[0] = Execution({ |
| 65 | target: yieldSource, |
|
| 66 | value: 0, |
|
| 67 | 0 | callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
| 68 | }); |
|
| 69 | } |
|
| 70 | ||
| 71 | /*////////////////////////////////////////////////////////////// |
|
| 72 | EXTERNAL METHODS |
|
| 73 | //////////////////////////////////////////////////////////////*/ |
|
| 74 | ||
| 75 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 76 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 77 | 0 | return _decodeAmount(data); |
| 78 | } |
|
| 79 | ||
| 80 | /// @inheritdoc ISuperHookContextAware |
|
| 81 | 16× | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 82 | 4× | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 83 | } |
|
| 84 | ||
| 85 | /// @inheritdoc ISuperHookOutflow |
|
| 86 | 14× | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 87 | 3× | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 88 | } |
|
| 89 | ||
| 90 | /// @inheritdoc ISuperHookInspector |
|
| 91 | 17× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 92 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 93 | } |
|
| 94 | ||
| 95 | /*////////////////////////////////////////////////////////////// |
|
| 96 | INTERNAL METHODS |
|
| 97 | //////////////////////////////////////////////////////////////*/ |
|
| 98 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | 0 | address yieldSource = data.extractYieldSource(); |
| 100 | 0 | asset = IERC7540(yieldSource).asset(); |
| 101 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 102 | 0 | usedShares = _getSharesBalance(account, data); |
| 103 | 0 | spToken = IERC7540(yieldSource).share(); |
| 104 | } |
|
| 105 | ||
| 106 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 108 | 0 | usedShares = usedShares - _getSharesBalance(account, data); |
| 109 | } |
|
| 110 | ||
| 111 | /*////////////////////////////////////////////////////////////// |
|
| 112 | PRIVATE METHODS |
|
| 113 | //////////////////////////////////////////////////////////////*/ |
|
| 114 | 2× | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 115 | 4× | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 116 | } |
|
| 117 | ||
| 118 | 0 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 119 | 0 | return IERC20(asset).balanceOf(account); |
| 120 | } |
|
| 121 | ||
| 122 | 2× | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 123 | 0 | address yieldSource = data.extractYieldSource(); |
| 124 | 2× | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 125 | } |
|
| 126 | } |
85%
lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol
Lines covered: 17 / 20 (85%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 6 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 8 | ||
| 9 | // Superform |
|
| 10 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 11 | import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol"; |
|
| 12 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 13 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 14 | import { ISuperVault } from "../../../vendor/superform/ISuperVault.sol"; |
|
| 15 | import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol"; |
|
| 16 | ||
| 17 | /// @title CancelRedeemHook |
|
| 18 | /// @author Superform Labs |
|
| 19 | /// @dev data has the following structure |
|
| 20 | /// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 21 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 22 | 256× | contract CancelRedeemHook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations { |
| 23 | using HookDataDecoder for bytes; |
|
| 24 | ||
| 25 | 6× | constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM) { } |
| 26 | ||
| 27 | /*////////////////////////////////////////////////////////////// |
|
| 28 | VIEW METHODS |
|
| 29 | //////////////////////////////////////////////////////////////*/ |
|
| 30 | 1× | function _buildHookExecutions( |
| 31 | address, |
|
| 32 | address account, |
|
| 33 | bytes calldata data |
|
| 34 | ) |
|
| 35 | internal |
|
| 36 | pure |
|
| 37 | override |
|
| 38 | 1× | returns (Execution[] memory executions) |
| 39 | { |
|
| 40 | 50× | address yieldSource = data.extractYieldSource(); |
| 41 | ||
| 42 | 14× | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 43 | ||
| 44 | 34× | executions = new Execution[](1); |
| 45 | 6× | executions[0] = |
| 46 | 50× | Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(ISuperVault.cancelRedeem, (account)) }); |
| 47 | } |
|
| 48 | ||
| 49 | /*////////////////////////////////////////////////////////////// |
|
| 50 | EX`TERNAL METHODS |
|
| 51 | //////////////////////////////////////////////////////////////*/ |
|
| 52 | ||
| 53 | /// @inheritdoc ISuperHookAsyncCancelations |
|
| 54 | 0 | function isAsyncCancelHook() external pure returns (CancelationType) { |
| 55 | 0 | return CancelationType.OUTFLOW; |
| 56 | } |
|
| 57 | ||
| 58 | /// @inheritdoc ISuperHookInspector |
|
| 59 | 25× | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 60 | 70× | return abi.encodePacked(data.extractYieldSource()); |
| 61 | } |
|
| 62 | ||
| 63 | /*////////////////////////////////////////////////////////////// |
|
| 64 | INTERNAL METHODS |
|
| 65 | //////////////////////////////////////////////////////////////*/ |
|
| 66 | 6× | function _preExecute(address, address account, bytes calldata data) internal override { |
| 67 | 50× | _setOutAmount(_getBalance(account, data), account); |
| 68 | 58× | spToken = data.extractYieldSource(); |
| 69 | } |
|
| 70 | ||
| 71 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 72 | 4× | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 73 | } |
|
| 74 | /*////////////////////////////////////////////////////////////// |
|
| 75 | PRIVATE METHODS |
|
| 76 | //////////////////////////////////////////////////////////////*/ |
|
| 77 | ||
| 78 | 4× | function _getBalance(address account, bytes memory data) private view returns (uint256) { |
| 79 | 128× | return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
| 80 | } |
|
| 81 | } |
4%
lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol
Lines covered: 2 / 41 (4%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../../../vendor/BytesLib.sol"; |
|
| 6 | import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol"; |
|
| 7 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; |
|
| 8 | import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { BaseHook } from "../../BaseHook.sol"; |
|
| 12 | import { |
|
| 13 | ISuperHookResultOutflow, |
|
| 14 | ISuperHookInflowOutflow, |
|
| 15 | ISuperHookOutflow, |
|
| 16 | ISuperHookContextAware, |
|
| 17 | ISuperHookInspector |
|
| 18 | } from "../../../interfaces/ISuperHook.sol"; |
|
| 19 | import { HookSubTypes } from "../../../libraries/HookSubTypes.sol"; |
|
| 20 | import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol"; |
|
| 21 | ||
| 22 | /// @title Withdraw7540VaultHook |
|
| 23 | /// @author Superform Labs |
|
| 24 | /// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible |
|
| 25 | /// @dev data has the following structure |
|
| 26 | /// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
|
| 27 | /// @notice address yieldSource = BytesLib.toAddress(data, 32); |
|
| 28 | /// @notice uint256 amount = BytesLib.toUint256(data, 52); |
|
| 29 | /// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
|
| 30 | 224× | contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware { |
| 31 | using HookDataDecoder for bytes; |
|
| 32 | ||
| 33 | 0 | uint256 private constant AMOUNT_POSITION = 52; |
| 34 | 0 | uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
| 35 | ||
| 36 | 7× | constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { } |
| 37 | ||
| 38 | /*////////////////////////////////////////////////////////////// |
|
| 39 | VIEW METHODS |
|
| 40 | //////////////////////////////////////////////////////////////*/ |
|
| 41 | /// @inheritdoc BaseHook |
|
| 42 | 0 | function _buildHookExecutions( |
| 43 | address prevHook, |
|
| 44 | address account, |
|
| 45 | bytes calldata data |
|
| 46 | ) |
|
| 47 | internal |
|
| 48 | view |
|
| 49 | override |
|
| 50 | 0 | returns (Execution[] memory executions) |
| 51 | 0 | { |
| 52 | 0 | address yieldSource = data.extractYieldSource(); |
| 53 | 0 | uint256 amount = _decodeAmount(data); |
| 54 | 0 | bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 55 | ||
| 56 | 0 | if (usePrevHookAmount) { |
| 57 | 0 | amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
| 58 | } |
|
| 59 | ||
| 60 | 0 | if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 61 | 0 | if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
| 62 | ||
| 63 | 0 | executions = new Execution[](1); |
| 64 | 0 | executions[0] = Execution({ |
| 65 | target: yieldSource, |
|
| 66 | value: 0, |
|
| 67 | 0 | callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
| 68 | }); |
|
| 69 | } |
|
| 70 | ||
| 71 | /*////////////////////////////////////////////////////////////// |
|
| 72 | EXTERNAL METHODS |
|
| 73 | //////////////////////////////////////////////////////////////*/ |
|
| 74 | ||
| 75 | /// @inheritdoc ISuperHookInflowOutflow |
|
| 76 | 0 | function decodeAmount(bytes memory data) external pure returns (uint256) { |
| 77 | 0 | return _decodeAmount(data); |
| 78 | } |
|
| 79 | ||
| 80 | /// @inheritdoc ISuperHookContextAware |
|
| 81 | 0 | function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) { |
| 82 | 0 | return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
| 83 | } |
|
| 84 | ||
| 85 | /// @inheritdoc ISuperHookOutflow |
|
| 86 | 0 | function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) { |
| 87 | 0 | return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
| 88 | } |
|
| 89 | ||
| 90 | /// @inheritdoc ISuperHookInspector |
|
| 91 | 0 | function inspect(bytes calldata data) external pure override returns (bytes memory) { |
| 92 | 0 | return abi.encodePacked(data.extractYieldSource()); |
| 93 | } |
|
| 94 | ||
| 95 | /*////////////////////////////////////////////////////////////// |
|
| 96 | INTERNAL METHODS |
|
| 97 | //////////////////////////////////////////////////////////////*/ |
|
| 98 | 0 | function _preExecute(address, address account, bytes calldata data) internal override { |
| 99 | 0 | address yieldSource = data.extractYieldSource(); |
| 100 | 0 | asset = IERC7540(yieldSource).asset(); |
| 101 | 0 | _setOutAmount(_getBalance(account, data), account); |
| 102 | 0 | usedShares = _getSharesBalance(account, data); |
| 103 | 0 | spToken = IERC7540(yieldSource).share(); |
| 104 | } |
|
| 105 | ||
| 106 | 0 | function _postExecute(address, address account, bytes calldata data) internal override { |
| 107 | 0 | _setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
| 108 | 0 | usedShares = usedShares - _getSharesBalance(account, data); |
| 109 | } |
|
| 110 | ||
| 111 | /*////////////////////////////////////////////////////////////// |
|
| 112 | PRIVATE METHODS |
|
| 113 | //////////////////////////////////////////////////////////////*/ |
|
| 114 | 0 | function _decodeAmount(bytes memory data) private pure returns (uint256) { |
| 115 | 0 | return BytesLib.toUint256(data, AMOUNT_POSITION); |
| 116 | } |
|
| 117 | ||
| 118 | 0 | function _getBalance(address account, bytes memory) private view returns (uint256) { |
| 119 | 0 | return IERC20(asset).balanceOf(account); |
| 120 | } |
|
| 121 | ||
| 122 | 0 | function _getSharesBalance(address account, bytes memory data) private view returns (uint256) { |
| 123 | 0 | address yieldSource = data.extractYieldSource(); |
| 124 | 0 | return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
| 125 | } |
|
| 126 | } |
40%
lib/v2-core/src/libraries/HookDataDecoder.sol
Lines covered: 2 / 5 (40%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { BytesLib } from "../vendor/BytesLib.sol"; |
|
| 5 | ||
| 6 | /// @title HookDataDecoder |
|
| 7 | /// @author Superform Labs |
|
| 8 | /// @notice Library for decoding hook data |
|
| 9 | 0 | library HookDataDecoder { |
| 10 | 0 | function extractYieldSourceOracleId(bytes memory data) internal pure returns (bytes32) { |
| 11 | 0 | return bytes32(BytesLib.slice(data, 0, 32)); |
| 12 | } |
|
| 13 | ||
| 14 | 50× | function extractYieldSource(bytes memory data) internal pure returns (address) { |
| 15 | 99× | return BytesLib.toAddress(data, 32); |
| 16 | } |
|
| 17 | } |
34%
lib/v2-core/src/libraries/HookSubTypes.sol
Lines covered: 8 / 23 (34%)
| 1 | // SPDX-License-Identifier: Apache-2.0 |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | /// @title HookSubTypes |
|
| 5 | /// @author Superform Labs |
|
| 6 | /// @notice Library for hook subtypes |
|
| 7 | 0 | library HookSubTypes { |
| 8 | 0 | bytes32 public constant BRIDGE = keccak256(bytes("Bridge")); |
| 9 | 0 | bytes32 public constant CANCEL_DEPOSIT = keccak256(bytes("CancelDeposit")); |
| 10 | 19× | bytes32 public constant CANCEL_DEPOSIT_REQUEST = keccak256(bytes("CancelDepositRequest")); |
| 11 | 18× | bytes32 public constant CANCEL_REDEEM = keccak256(bytes("CancelRedeem")); |
| 12 | 19× | bytes32 public constant CANCEL_REDEEM_REQUEST = keccak256(bytes("CancelRedeemRequest")); |
| 13 | 0 | bytes32 public constant CLAIM = keccak256(bytes("Claim")); |
| 14 | 19× | bytes32 public constant CLAIM_CANCEL_DEPOSIT_REQUEST = keccak256(bytes("ClaimCancelDepositRequest")); |
| 15 | 19× | bytes32 public constant CLAIM_CANCEL_REDEEM_REQUEST = keccak256(bytes("ClaimCancelRedeemRequest")); |
| 16 | 0 | bytes32 public constant COOLDOWN = keccak256(bytes("Cooldown")); |
| 17 | 0 | bytes32 public constant ETHENA = keccak256(bytes("Ethena")); |
| 18 | 54× | bytes32 public constant ERC4626 = keccak256(bytes("ERC4626")); |
| 19 | 54× | bytes32 public constant ERC5115 = keccak256(bytes("ERC5115")); |
| 20 | 126× | bytes32 public constant ERC7540 = keccak256(bytes("ERC7540")); |
| 21 | 0 | bytes32 public constant LOAN = keccak256(bytes("Loan")); |
| 22 | 0 | bytes32 public constant LOAN_REPAY = keccak256(bytes("LoanRepay")); |
| 23 | 0 | bytes32 public constant MISC = keccak256(bytes("Misc")); |
| 24 | 0 | bytes32 public constant STAKE = keccak256(bytes("Stake")); |
| 25 | 0 | bytes32 public constant SWAP = keccak256(bytes("Swap")); |
| 26 | 0 | bytes32 public constant TOKEN = keccak256(bytes("Token")); |
| 27 | 0 | bytes32 public constant UNSTAKE = keccak256(bytes("Unstake")); |
| 28 | 0 | bytes32 public constant PTYT = keccak256(bytes("PTYT")); |
| 29 | 0 | bytes32 public constant VAULT_BANK = keccak256(bytes("VaultBank")); |
| 30 | ||
| 31 | function getHookSubType(string memory hookSubtype) internal pure returns (bytes32) { |
|
| 32 | return keccak256(bytes(hookSubtype)); |
|
| 33 | } |
|
| 34 | } |
15%
lib/v2-core/src/vendor/BytesLib.sol
Lines covered: 6 / 40 (15%)
| 1 | // SPDX-License-Identifier: Unlicense |
|
| 2 | /* |
|
| 3 | * @title Solidity Bytes Arrays Utils |
|
| 4 | * @author Gonçalo Sá <goncalo.sa@consensys.net> |
|
| 5 | * |
|
| 6 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. |
|
| 7 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. |
|
| 8 | */ |
|
| 9 | pragma solidity 0.8.30; |
|
| 10 | ||
| 11 | 0 | library BytesLib { |
| 12 | function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { |
|
| 13 | bytes memory tempBytes; |
|
| 14 | ||
| 15 | assembly { |
|
| 16 | // Get a location of some free memory and store it in tempBytes as |
|
| 17 | // Solidity does for memory variables. |
|
| 18 | tempBytes := mload(0x40) |
|
| 19 | ||
| 20 | // Store the length of the first bytes array at the beginning of |
|
| 21 | // the memory for tempBytes. |
|
| 22 | let length := mload(_preBytes) |
|
| 23 | mstore(tempBytes, length) |
|
| 24 | ||
| 25 | // Maintain a memory counter for the current write location in the |
|
| 26 | // temp bytes array by adding the 32 bytes for the array length to |
|
| 27 | // the starting location. |
|
| 28 | let mc := add(tempBytes, 0x20) |
|
| 29 | // Stop copying when the memory counter reaches the length of the |
|
| 30 | // first bytes array. |
|
| 31 | let end := add(mc, length) |
|
| 32 | ||
| 33 | for { |
|
| 34 | // Initialize a copy counter to the start of the _preBytes data, |
|
| 35 | // 32 bytes into its memory. |
|
| 36 | let cc := add(_preBytes, 0x20) |
|
| 37 | } lt(mc, end) { |
|
| 38 | // Increase both counters by 32 bytes each iteration. |
|
| 39 | mc := add(mc, 0x20) |
|
| 40 | cc := add(cc, 0x20) |
|
| 41 | } { |
|
| 42 | // Write the _preBytes data into the tempBytes memory 32 bytes |
|
| 43 | // at a time. |
|
| 44 | mstore(mc, mload(cc)) |
|
| 45 | } |
|
| 46 | ||
| 47 | // Add the length of _postBytes to the current length of tempBytes |
|
| 48 | // and store it as the new length in the first 32 bytes of the |
|
| 49 | // tempBytes memory. |
|
| 50 | length := mload(_postBytes) |
|
| 51 | mstore(tempBytes, add(length, mload(tempBytes))) |
|
| 52 | ||
| 53 | // Move the memory counter back from a multiple of 0x20 to the |
|
| 54 | // actual end of the _preBytes data. |
|
| 55 | mc := end |
|
| 56 | // Stop copying when the memory counter reaches the new combined |
|
| 57 | // length of the arrays. |
|
| 58 | end := add(mc, length) |
|
| 59 | ||
| 60 | for { let cc := add(_postBytes, 0x20) } lt(mc, end) { |
|
| 61 | mc := add(mc, 0x20) |
|
| 62 | cc := add(cc, 0x20) |
|
| 63 | } { mstore(mc, mload(cc)) } |
|
| 64 | ||
| 65 | // Update the free-memory pointer by padding our last write location |
|
| 66 | // to 32 bytes: add 31 bytes to the end of tempBytes to move to the |
|
| 67 | // next 32 byte block, then round down to the nearest multiple of |
|
| 68 | // 32. If the sum of the length of the two arrays is zero then add |
|
| 69 | // one before rounding down to leave a blank 32 bytes (the length block with 0). |
|
| 70 | mstore( |
|
| 71 | 0x40, |
|
| 72 | and( |
|
| 73 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), |
|
| 74 | not(31) // Round down to the nearest 32 bytes. |
|
| 75 | ) |
|
| 76 | ) |
|
| 77 | } |
|
| 78 | ||
| 79 | return tempBytes; |
|
| 80 | } |
|
| 81 | ||
| 82 | function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { |
|
| 83 | assembly { |
|
| 84 | // Read the first 32 bytes of _preBytes storage, which is the length |
|
| 85 | // of the array. (We don't need to use the offset into the slot |
|
| 86 | // because arrays use the entire slot.) |
|
| 87 | let fslot := sload(_preBytes.slot) |
|
| 88 | // Arrays of 31 bytes or less have an even value in their slot, |
|
| 89 | // while longer arrays have an odd value. The actual length is |
|
| 90 | // the slot divided by two for odd values, and the lowest order |
|
| 91 | // byte divided by two for even values. |
|
| 92 | // If the slot is even, bitwise and the slot with 255 and divide by |
|
| 93 | // two to get the length. If the slot is odd, bitwise and the slot |
|
| 94 | // with -1 and divide by two. |
|
| 95 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
|
| 96 | let mlength := mload(_postBytes) |
|
| 97 | let newlength := add(slength, mlength) |
|
| 98 | // slength can contain both the length and contents of the array |
|
| 99 | // if length < 32 bytes so let's prepare for that |
|
| 100 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
|
| 101 | switch add(lt(slength, 32), lt(newlength, 32)) |
|
| 102 | case 2 { |
|
| 103 | // Since the new array still fits in the slot, we just need to |
|
| 104 | // update the contents of the slot. |
|
| 105 | // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length |
|
| 106 | sstore( |
|
| 107 | _preBytes.slot, |
|
| 108 | // all the modifications to the slot are inside this |
|
| 109 | // next block |
|
| 110 | add( |
|
| 111 | // we can just add to the slot contents because the |
|
| 112 | // bytes we want to change are the LSBs |
|
| 113 | fslot, |
|
| 114 | add( |
|
| 115 | mul( |
|
| 116 | div( |
|
| 117 | // load the bytes from memory |
|
| 118 | mload(add(_postBytes, 0x20)), |
|
| 119 | // zero all bytes to the right |
|
| 120 | exp(0x100, sub(32, mlength)) |
|
| 121 | ), |
|
| 122 | // and now shift left the number of bytes to |
|
| 123 | // leave space for the length in the slot |
|
| 124 | exp(0x100, sub(32, newlength)) |
|
| 125 | ), |
|
| 126 | // increase length by the double of the memory |
|
| 127 | // bytes length |
|
| 128 | mul(mlength, 2) |
|
| 129 | ) |
|
| 130 | ) |
|
| 131 | ) |
|
| 132 | } |
|
| 133 | case 1 { |
|
| 134 | // The stored value fits in the slot, but the combined value |
|
| 135 | // will exceed it. |
|
| 136 | // get the keccak hash to get the contents of the array |
|
| 137 | mstore(0x0, _preBytes.slot) |
|
| 138 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
|
| 139 | ||
| 140 | // save new length |
|
| 141 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
|
| 142 | ||
| 143 | // The contents of the _postBytes array start 32 bytes into |
|
| 144 | // the structure. Our first read should obtain the `submod` |
|
| 145 | // bytes that can fit into the unused space in the last word |
|
| 146 | // of the stored array. To get this, we read 32 bytes starting |
|
| 147 | // from `submod`, so the data we read overlaps with the array |
|
| 148 | // contents by `submod` bytes. Masking the lowest-order |
|
| 149 | // `submod` bytes allows us to add that value directly to the |
|
| 150 | // stored value. |
|
| 151 | ||
| 152 | let submod := sub(32, slength) |
|
| 153 | let mc := add(_postBytes, submod) |
|
| 154 | let end := add(_postBytes, mlength) |
|
| 155 | let mask := sub(exp(0x100, submod), 1) |
|
| 156 | ||
| 157 | sstore( |
|
| 158 | sc, |
|
| 159 | add( |
|
| 160 | and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), |
|
| 161 | and(mload(mc), mask) |
|
| 162 | ) |
|
| 163 | ) |
|
| 164 | ||
| 165 | for { |
|
| 166 | mc := add(mc, 0x20) |
|
| 167 | sc := add(sc, 1) |
|
| 168 | } lt(mc, end) { |
|
| 169 | sc := add(sc, 1) |
|
| 170 | mc := add(mc, 0x20) |
|
| 171 | } { sstore(sc, mload(mc)) } |
|
| 172 | ||
| 173 | mask := exp(0x100, sub(mc, end)) |
|
| 174 | ||
| 175 | sstore(sc, mul(div(mload(mc), mask), mask)) |
|
| 176 | } |
|
| 177 | default { |
|
| 178 | // get the keccak hash to get the contents of the array |
|
| 179 | mstore(0x0, _preBytes.slot) |
|
| 180 | // Start copying to the last used word of the stored array. |
|
| 181 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
|
| 182 | ||
| 183 | // save new length |
|
| 184 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
|
| 185 | ||
| 186 | // Copy over the first `submod` bytes of the new data as in |
|
| 187 | // case 1 above. |
|
| 188 | let slengthmod := mod(slength, 32) |
|
| 189 | let mlengthmod := mod(mlength, 32) |
|
| 190 | let submod := sub(32, slengthmod) |
|
| 191 | let mc := add(_postBytes, submod) |
|
| 192 | let end := add(_postBytes, mlength) |
|
| 193 | let mask := sub(exp(0x100, submod), 1) |
|
| 194 | ||
| 195 | sstore(sc, add(sload(sc), and(mload(mc), mask))) |
|
| 196 | ||
| 197 | for { |
|
| 198 | sc := add(sc, 1) |
|
| 199 | mc := add(mc, 0x20) |
|
| 200 | } lt(mc, end) { |
|
| 201 | sc := add(sc, 1) |
|
| 202 | mc := add(mc, 0x20) |
|
| 203 | } { sstore(sc, mload(mc)) } |
|
| 204 | ||
| 205 | mask := exp(0x100, sub(mc, end)) |
|
| 206 | ||
| 207 | sstore(sc, mul(div(mload(mc), mask), mask)) |
|
| 208 | } |
|
| 209 | } |
|
| 210 | } |
|
| 211 | ||
| 212 | 0 | function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { |
| 213 | // We're using the unchecked block below because otherwise execution ends |
|
| 214 | // with the native overflow error code. |
|
| 215 | unchecked { |
|
| 216 | 0 | require(_length + 31 >= _length, "slice_overflow"); |
| 217 | } |
|
| 218 | 0 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); |
| 219 | ||
| 220 | 0 | bytes memory tempBytes; |
| 221 | ||
| 222 | assembly { |
|
| 223 | 0 | switch iszero(_length) |
| 224 | 0 | case 0 { |
| 225 | // Get a location of some free memory and store it in tempBytes as |
|
| 226 | // Solidity does for memory variables. |
|
| 227 | 0 | tempBytes := mload(0x40) |
| 228 | ||
| 229 | // The first word of the slice result is potentially a partial |
|
| 230 | // word read from the original array. To read it, we calculate |
|
| 231 | // the length of that partial word and start copying that many |
|
| 232 | // bytes into the array. The first word we copy will start with |
|
| 233 | // data we don't care about, but the last `lengthmod` bytes will |
|
| 234 | // land at the beginning of the contents of the new array. When |
|
| 235 | // we're done copying, we overwrite the full first word with |
|
| 236 | // the actual length of the slice. |
|
| 237 | 0 | let lengthmod := and(_length, 31) |
| 238 | ||
| 239 | // The multiplication in the next line is necessary |
|
| 240 | // because when slicing multiples of 32 bytes (lengthmod == 0) |
|
| 241 | // the following copy loop was copying the origin's length |
|
| 242 | // and then ending prematurely not copying everything it should. |
|
| 243 | 0 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) |
| 244 | 0 | let end := add(mc, _length) |
| 245 | ||
| 246 | 0 | for { |
| 247 | // The multiplication in the next line has the same exact purpose |
|
| 248 | // as the one above. |
|
| 249 | 0 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) |
| 250 | 0 | } lt(mc, end) { |
| 251 | 0 | mc := add(mc, 0x20) |
| 252 | 0 | cc := add(cc, 0x20) |
| 253 | 0 | } { mstore(mc, mload(cc)) } |
| 254 | ||
| 255 | 0 | mstore(tempBytes, _length) |
| 256 | ||
| 257 | //update free-memory pointer |
|
| 258 | //allocating the array padded to 32 bytes like the compiler does now |
|
| 259 | 0 | mstore(0x40, and(add(mc, 31), not(31))) |
| 260 | } |
|
| 261 | //if we want a zero-length slice let's just return a zero-length array |
|
| 262 | default { |
|
| 263 | 0 | tempBytes := mload(0x40) |
| 264 | //zero out the 32 bytes slice we are about to return |
|
| 265 | //we need to do it because Solidity does not garbage collect |
|
| 266 | 0 | mstore(tempBytes, 0) |
| 267 | ||
| 268 | 0 | mstore(0x40, add(tempBytes, 0x20)) |
| 269 | } |
|
| 270 | } |
|
| 271 | ||
| 272 | 0 | return tempBytes; |
| 273 | } |
|
| 274 | ||
| 275 | 75× | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { |
| 276 | 357× | require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); |
| 277 | address tempAddress; |
|
| 278 | ||
| 279 | assembly { |
|
| 280 | 175× | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) |
| 281 | } |
|
| 282 | ||
| 283 | return tempAddress; |
|
| 284 | } |
|
| 285 | ||
| 286 | 0 | function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { |
| 287 | 0 | require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); |
| 288 | uint8 tempUint; |
|
| 289 | ||
| 290 | assembly { |
|
| 291 | 0 | tempUint := mload(add(add(_bytes, 0x1), _start)) |
| 292 | } |
|
| 293 | ||
| 294 | return tempUint; |
|
| 295 | } |
|
| 296 | ||
| 297 | function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { |
|
| 298 | require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); |
|
| 299 | uint16 tempUint; |
|
| 300 | ||
| 301 | assembly { |
|
| 302 | tempUint := mload(add(add(_bytes, 0x2), _start)) |
|
| 303 | } |
|
| 304 | ||
| 305 | return tempUint; |
|
| 306 | } |
|
| 307 | ||
| 308 | 0 | function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { |
| 309 | 0 | require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); |
| 310 | uint32 tempUint; |
|
| 311 | ||
| 312 | assembly { |
|
| 313 | 0 | tempUint := mload(add(add(_bytes, 0x4), _start)) |
| 314 | } |
|
| 315 | ||
| 316 | return tempUint; |
|
| 317 | } |
|
| 318 | ||
| 319 | 0 | function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { |
| 320 | 0 | require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); |
| 321 | uint64 tempUint; |
|
| 322 | ||
| 323 | assembly { |
|
| 324 | 0 | tempUint := mload(add(add(_bytes, 0x8), _start)) |
| 325 | } |
|
| 326 | ||
| 327 | return tempUint; |
|
| 328 | } |
|
| 329 | ||
| 330 | function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { |
|
| 331 | require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); |
|
| 332 | uint96 tempUint; |
|
| 333 | ||
| 334 | assembly { |
|
| 335 | tempUint := mload(add(add(_bytes, 0xc), _start)) |
|
| 336 | } |
|
| 337 | ||
| 338 | return tempUint; |
|
| 339 | } |
|
| 340 | ||
| 341 | function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { |
|
| 342 | require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); |
|
| 343 | uint128 tempUint; |
|
| 344 | ||
| 345 | assembly { |
|
| 346 | tempUint := mload(add(add(_bytes, 0x10), _start)) |
|
| 347 | } |
|
| 348 | ||
| 349 | return tempUint; |
|
| 350 | } |
|
| 351 | ||
| 352 | 54× | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { |
| 353 | 266× | require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); |
| 354 | uint256 tempUint; |
|
| 355 | ||
| 356 | assembly { |
|
| 357 | 90× | tempUint := mload(add(add(_bytes, 0x20), _start)) |
| 358 | } |
|
| 359 | ||
| 360 | return tempUint; |
|
| 361 | } |
|
| 362 | ||
| 363 | 0 | function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { |
| 364 | 0 | require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); |
| 365 | bytes32 tempBytes32; |
|
| 366 | ||
| 367 | assembly { |
|
| 368 | tempBytes32 := mload(add(add(_bytes, 0x20), _start)) |
|
| 369 | } |
|
| 370 | ||
| 371 | return tempBytes32; |
|
| 372 | } |
|
| 373 | ||
| 374 | function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { |
|
| 375 | bool success = true; |
|
| 376 | ||
| 377 | assembly { |
|
| 378 | let length := mload(_preBytes) |
|
| 379 | ||
| 380 | // if lengths don't match the arrays are not equal |
|
| 381 | switch eq(length, mload(_postBytes)) |
|
| 382 | case 1 { |
|
| 383 | // cb is a circuit breaker in the for loop since there's |
|
| 384 | // no said feature for inline assembly loops |
|
| 385 | // cb = 1 - don't breaker |
|
| 386 | // cb = 0 - break |
|
| 387 | let cb := 1 |
|
| 388 | ||
| 389 | let mc := add(_preBytes, 0x20) |
|
| 390 | let end := add(mc, length) |
|
| 391 | ||
| 392 | for { let cc := add(_postBytes, 0x20) } |
|
| 393 | // the next line is the loop condition: |
|
| 394 | // while(uint256(mc < end) + cb == 2) |
|
| 395 | eq(add(lt(mc, end), cb), 2) { |
|
| 396 | mc := add(mc, 0x20) |
|
| 397 | cc := add(cc, 0x20) |
|
| 398 | } { |
|
| 399 | // if any of these checks fails then arrays are not equal |
|
| 400 | if iszero(eq(mload(mc), mload(cc))) { |
|
| 401 | // unsuccess: |
|
| 402 | success := 0 |
|
| 403 | cb := 0 |
|
| 404 | } |
|
| 405 | } |
|
| 406 | } |
|
| 407 | default { |
|
| 408 | // unsuccess: |
|
| 409 | success := 0 |
|
| 410 | } |
|
| 411 | } |
|
| 412 | ||
| 413 | return success; |
|
| 414 | } |
|
| 415 | ||
| 416 | function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { |
|
| 417 | bool success = true; |
|
| 418 | ||
| 419 | assembly { |
|
| 420 | // we know _preBytes_offset is 0 |
|
| 421 | let fslot := sload(_preBytes.slot) |
|
| 422 | // Decode the length of the stored array like in concatStorage(). |
|
| 423 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
|
| 424 | let mlength := mload(_postBytes) |
|
| 425 | ||
| 426 | // if lengths don't match the arrays are not equal |
|
| 427 | switch eq(slength, mlength) |
|
| 428 | case 1 { |
|
| 429 | // slength can contain both the length and contents of the array |
|
| 430 | // if length < 32 bytes so let's prepare for that |
|
| 431 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
|
| 432 | if iszero(iszero(slength)) { |
|
| 433 | switch lt(slength, 32) |
|
| 434 | case 1 { |
|
| 435 | // blank the last byte which is the length |
|
| 436 | fslot := mul(div(fslot, 0x100), 0x100) |
|
| 437 | ||
| 438 | if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { |
|
| 439 | // unsuccess: |
|
| 440 | success := 0 |
|
| 441 | } |
|
| 442 | } |
|
| 443 | default { |
|
| 444 | // cb is a circuit breaker in the for loop since there's |
|
| 445 | // no said feature for inline assembly loops |
|
| 446 | // cb = 1 - don't breaker |
|
| 447 | // cb = 0 - break |
|
| 448 | let cb := 1 |
|
| 449 | ||
| 450 | // get the keccak hash to get the contents of the array |
|
| 451 | mstore(0x0, _preBytes.slot) |
|
| 452 | let sc := keccak256(0x0, 0x20) |
|
| 453 | ||
| 454 | let mc := add(_postBytes, 0x20) |
|
| 455 | let end := add(mc, mlength) |
|
| 456 | ||
| 457 | // the next line is the loop condition: |
|
| 458 | // while(uint256(mc < end) + cb == 2) |
|
| 459 | for { } eq(add(lt(mc, end), cb), 2) { |
|
| 460 | sc := add(sc, 1) |
|
| 461 | mc := add(mc, 0x20) |
|
| 462 | } { |
|
| 463 | if iszero(eq(sload(sc), mload(mc))) { |
|
| 464 | // unsuccess: |
|
| 465 | success := 0 |
|
| 466 | cb := 0 |
|
| 467 | } |
|
| 468 | } |
|
| 469 | } |
|
| 470 | } |
|
| 471 | } |
|
| 472 | default { |
|
| 473 | // unsuccess: |
|
| 474 | success := 0 |
|
| 475 | } |
|
| 476 | } |
|
| 477 | ||
| 478 | return success; |
|
| 479 | } |
|
| 480 | } |
0%
src/Bank.sol
Lines covered: 0 / 35 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; |
|
| 6 | import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; |
|
| 7 | ||
| 8 | // Superform |
|
| 9 | import { IHookExecutionData } from "./interfaces/IHookExecutionData.sol"; |
|
| 10 | import { ISuperHook, Execution } from "@superform-v2-core/src/interfaces/ISuperHook.sol"; |
|
| 11 | ||
| 12 | abstract contract Bank is ReentrancyGuard { |
|
| 13 | /*////////////////////////////////////////////////////////////// |
|
| 14 | ERRORS |
|
| 15 | //////////////////////////////////////////////////////////////*/ |
|
| 16 | error INVALID_HOOK(); |
|
| 17 | error INVALID_MERKLE_PROOF(); |
|
| 18 | error HOOK_EXECUTION_FAILED(); |
|
| 19 | error ZERO_LENGTH_ARRAY(); |
|
| 20 | error INVALID_ARRAY_LENGTH(); |
|
| 21 | ||
| 22 | /*////////////////////////////////////////////////////////////// |
|
| 23 | EVENTS |
|
| 24 | //////////////////////////////////////////////////////////////*/ |
|
| 25 | /// @notice Emitted when hooks are executed. |
|
| 26 | /// @param hooks The addresses of the hooks that were executed. |
|
| 27 | /// @param data The data passed to each hook. |
|
| 28 | event HooksExecuted(address[] hooks, bytes[] data); |
|
| 29 | ||
| 30 | /*////////////////////////////////////////////////////////////// |
|
| 31 | INTERNAL FUNCTIONS |
|
| 32 | //////////////////////////////////////////////////////////////*/ |
|
| 33 | function _getMerkleRootForHook(address hookAddress) internal view virtual returns (bytes32); |
|
| 34 | ||
| 35 | 0 | function _executeHooks(IHookExecutionData.HookExecutionData calldata executionData) internal virtual nonReentrant { |
| 36 | 0 | uint256 hooksLength = executionData.hooks.length; |
| 37 | 0 | if (hooksLength == 0) revert ZERO_LENGTH_ARRAY(); |
| 38 | ||
| 39 | // Validate arrays have matching lengths |
|
| 40 | 0 | if (hooksLength != executionData.data.length || hooksLength != executionData.merkleProofs.length) { |
| 41 | 0 | revert INVALID_ARRAY_LENGTH(); |
| 42 | } |
|
| 43 | ||
| 44 | 0 | address prevHook; |
| 45 | 0 | address hookAddress; |
| 46 | 0 | bytes memory hookData; |
| 47 | 0 | bytes32[] memory merkleProof; |
| 48 | 0 | ISuperHook hook; |
| 49 | 0 | bytes32 merkleRoot; |
| 50 | 0 | Execution[] memory executions; |
| 51 | 0 | Execution memory executionStep; |
| 52 | 0 | bytes32 targetLeaf; |
| 53 | 0 | bool success; |
| 54 | ||
| 55 | 0 | for (uint256 i; i < hooksLength; i++) { |
| 56 | 0 | hookAddress = executionData.hooks[i]; |
| 57 | 0 | hookData = executionData.data[i]; |
| 58 | 0 | merkleProof = executionData.merkleProofs[i]; |
| 59 | ||
| 60 | 0 | hook = ISuperHook(hookAddress); |
| 61 | ||
| 62 | // 1. Get the Merkle root specific to this hook |
|
| 63 | 0 | merkleRoot = _getMerkleRootForHook(hookAddress); |
| 64 | ||
| 65 | 0 | ISuperHook(hookAddress).setExecutionContext(address(this)); |
| 66 | ||
| 67 | // 2. Build Execution Steps |
|
| 68 | 0 | executions = hook.build(prevHook, address(this), hookData); |
| 69 | ||
| 70 | // 3. Execute Target Calls and verify each target |
|
| 71 | 0 | for (uint256 j; j < executions.length; ++j) { |
| 72 | 0 | executionStep = executions[j]; |
| 73 | ||
| 74 | // valid hooks encapsulate execution between a `.preExecute` and ` .postExecute` |
|
| 75 | // target for preExecute and postExecute is the hook address |
|
| 76 | // keep the original behavior for validating the tree against the actual execution steps |
|
| 77 | 0 | if (executionStep.target != hookAddress) { |
| 78 | // Verify that this target is allowed for this hook using the Merkle proof |
|
| 79 | // The leaf is the hash of the target address |
|
| 80 | 0 | targetLeaf = keccak256(bytes.concat(keccak256(abi.encodePacked(executionStep.target)))); |
| 81 | // Verify this target is allowed using the corresponding Merkle proof |
|
| 82 | 0 | if (!MerkleProof.verify(merkleProof, merkleRoot, targetLeaf)) { |
| 83 | 0 | revert INVALID_MERKLE_PROOF(); |
| 84 | } |
|
| 85 | } |
|
| 86 | ||
| 87 | // Execute the call after verification |
|
| 88 | 0 | (success,) = executionStep.target.call{ value: executionStep.value }(executionStep.callData); |
| 89 | 0 | if (!success) { |
| 90 | 0 | revert HOOK_EXECUTION_FAILED(); |
| 91 | } |
|
| 92 | } |
|
| 93 | ||
| 94 | // Reset execution state after each hook |
|
| 95 | 0 | ISuperHook(hookAddress).resetExecutionState(address(this)); |
| 96 | ||
| 97 | 0 | prevHook = hookAddress; |
| 98 | } |
|
| 99 | ||
| 100 | 0 | emit HooksExecuted(executionData.hooks, executionData.data); |
| 101 | } |
|
| 102 | } |
0%
src/SuperAsset/IncentiveCalculationContract.sol
Lines covered: 0 / 39 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 5 | import { IIncentiveCalculationContract } from "../interfaces/SuperAsset/IIncentiveCalculationContract.sol"; |
|
| 6 | ||
| 7 | /// @title IncentiveCalculationContract |
|
| 8 | /// @author Superform Labs |
|
| 9 | /// @notice A stateless contract for calculating incentives. |
|
| 10 | 0 | contract IncentiveCalculationContract is IIncentiveCalculationContract { |
| 11 | using Math for uint256; |
|
| 12 | ||
| 13 | // --- Constants --- |
|
| 14 | 0 | uint256 public constant PRECISION = 1e18; |
| 15 | 0 | uint256 public constant PERC = 100e18; |
| 16 | ||
| 17 | // --- View Functions --- |
|
| 18 | /// @inheritdoc IIncentiveCalculationContract |
|
| 19 | 0 | function energy( |
| 20 | uint256[] memory currentAllocation, |
|
| 21 | uint256[] memory allocationTarget, |
|
| 22 | uint256[] memory weights, |
|
| 23 | uint256 totalCurrentAllocation, |
|
| 24 | uint256 totalAllocationTarget |
|
| 25 | ) |
|
| 26 | public |
|
| 27 | pure |
|
| 28 | 0 | returns (uint256 res, bool isSuccess) |
| 29 | { |
|
| 30 | 0 | if (currentAllocation.length != allocationTarget.length || currentAllocation.length != weights.length) { |
| 31 | // NOTE: This is a really corner a case that should never happen, this is why we let it revert even though |
|
| 32 | // in general we do not allow view and pure functions to revert. |
|
| 33 | 0 | revert INVALID_ARRAY_LENGTH(); |
| 34 | } |
|
| 35 | ||
| 36 | // NOTE: This is to ensure we won't divide by zero in the subsequent calculations |
|
| 37 | 0 | if (totalCurrentAllocation == 0 || totalAllocationTarget == 0) { |
| 38 | 0 | return (0, false); |
| 39 | } |
|
| 40 | ||
| 41 | 0 | uint256 length = currentAllocation.length; |
| 42 | 0 | for (uint256 i; i < length; i++) { |
| 43 | 0 | uint256 _currentAllocation = Math.mulDiv(currentAllocation[i], PERC, totalCurrentAllocation); |
| 44 | 0 | uint256 _targetAllocation = Math.mulDiv(allocationTarget[i], PERC, totalAllocationTarget); |
| 45 | 0 | int256 diff = int256(_currentAllocation) - int256(_targetAllocation); |
| 46 | // Square the difference and maintain precision |
|
| 47 | 0 | uint256 diff2 = Math.mulDiv(uint256(diff * diff), 1, PRECISION); |
| 48 | // Apply weight and maintain precision |
|
| 49 | 0 | res += Math.mulDiv(diff2, weights[i], PRECISION); |
| 50 | } |
|
| 51 | 0 | return (res, true); |
| 52 | } |
|
| 53 | ||
| 54 | /// @inheritdoc IIncentiveCalculationContract |
|
| 55 | 0 | function calculateIncentive( |
| 56 | uint256[] memory allocationPreOperation, |
|
| 57 | uint256[] memory allocationPostOperation, |
|
| 58 | uint256[] memory allocationTarget, |
|
| 59 | uint256[] memory weights, |
|
| 60 | uint256 totalAllocationPreOperation, |
|
| 61 | uint256 totalAllocationPostOperation, |
|
| 62 | uint256 totalAllocationTarget, |
|
| 63 | uint256 energyToUSDExchangeRatio |
|
| 64 | ) |
|
| 65 | public |
|
| 66 | pure |
|
| 67 | 0 | returns (int256 incentiveUSD, bool isSuccess) |
| 68 | { |
|
| 69 | 0 | if ( |
| 70 | 0 | allocationPreOperation.length != allocationPostOperation.length |
| 71 | 0 | || allocationPreOperation.length != allocationTarget.length |
| 72 | ) { |
|
| 73 | 0 | revert INVALID_ARRAY_LENGTH(); |
| 74 | } |
|
| 75 | ||
| 76 | 0 | uint256 energyBefore; |
| 77 | 0 | uint256 energyAfter; |
| 78 | 0 | bool _isSuccess; |
| 79 | ||
| 80 | 0 | (energyBefore, _isSuccess) = energy( |
| 81 | 0 | allocationPreOperation, allocationTarget, weights, totalAllocationPreOperation, totalAllocationTarget |
| 82 | ); |
|
| 83 | ||
| 84 | 0 | if (!_isSuccess) { |
| 85 | 0 | return (0, false); |
| 86 | } |
|
| 87 | ||
| 88 | 0 | (energyAfter, _isSuccess) = energy( |
| 89 | 0 | allocationPostOperation, allocationTarget, weights, totalAllocationPostOperation, totalAllocationTarget |
| 90 | ); |
|
| 91 | ||
| 92 | 0 | if (!_isSuccess) { |
| 93 | 0 | return (0, false); |
| 94 | } |
|
| 95 | ||
| 96 | // Calculate energy difference first |
|
| 97 | 0 | int256 energyDiff = int256(energyBefore) - int256(energyAfter); |
| 98 | ||
| 99 | // Handle positive and negative cases separately for safe multiplication |
|
| 100 | 0 | if (energyDiff >= 0) { |
| 101 | 0 | incentiveUSD = int256(Math.mulDiv(uint256(energyDiff), energyToUSDExchangeRatio, PRECISION)); |
| 102 | } else { |
|
| 103 | 0 | incentiveUSD = -int256(Math.mulDiv(uint256(-energyDiff), energyToUSDExchangeRatio, PRECISION)); |
| 104 | } |
|
| 105 | 0 | return (incentiveUSD, true); |
| 106 | } |
|
| 107 | } |
0%
src/SuperAsset/IncentiveFundContract.sol
Lines covered: 0 / 69 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
|
| 5 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 8 | import { IIncentiveFundContract } from "../interfaces/SuperAsset/IIncentiveFundContract.sol"; |
|
| 9 | import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol"; |
|
| 10 | import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol"; |
|
| 11 | import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol"; |
|
| 12 | ||
| 13 | /// @title Incentive Fund Contract |
|
| 14 | /// @author Superform Labs |
|
| 15 | /// @notice Manages incentive tokens in the SuperAsset system |
|
| 16 | /// @dev This contract is responsible for handling the incentive fund, including paying and taking incentives. |
|
| 17 | /// @dev For now it is OK to keep Access Control but it will be managed by SuperGovernor when ready, see |
|
| 18 | /// https://github.com/superform-xyz/v2-contracts/pull/377#discussion_r2058893391 |
|
| 19 | 0 | contract IncentiveFundContract is IIncentiveFundContract { |
| 20 | using SafeERC20 for IERC20; |
|
| 21 | ||
| 22 | // --- State Variables --- |
|
| 23 | 0 | address public tokenInIncentive; |
| 24 | 0 | address public tokenOutIncentive; |
| 25 | ||
| 26 | 0 | ISuperAsset public superAsset; |
| 27 | 0 | ISuperGovernor public superGovernor; |
| 28 | ||
| 29 | 0 | bool public incentivesActive; |
| 30 | ||
| 31 | // --- Modifiers --- |
|
| 32 | 0 | modifier onlyManager() { |
| 33 | 0 | ISuperAssetFactory factory = ISuperAssetFactory(superGovernor.getAddress(superGovernor.SUPER_ASSET_FACTORY())); |
| 34 | 0 | address manager = factory.getIncentiveFundManager(address(superAsset)); |
| 35 | 0 | if (msg.sender != manager) revert UNAUTHORIZED(); |
| 36 | 0 | _; |
| 37 | } |
|
| 38 | ||
| 39 | /// @inheritdoc IIncentiveFundContract |
|
| 40 | 0 | function initialize( |
| 41 | address _superGovernor, |
|
| 42 | address superAsset_, |
|
| 43 | address tokenInIncentive_, |
|
| 44 | address tokenOutIncentive_ |
|
| 45 | ) |
|
| 46 | external |
|
| 47 | { |
|
| 48 | 0 | if (_superGovernor == address(0)) revert ZERO_ADDRESS(); |
| 49 | 0 | superGovernor = ISuperGovernor(_superGovernor); |
| 50 | ||
| 51 | // Ensure this can only be called once |
|
| 52 | 0 | if (address(superAsset) != address(0)) revert ALREADY_INITIALIZED(); |
| 53 | ||
| 54 | 0 | if (superAsset_ == address(0)) revert ZERO_ADDRESS(); |
| 55 | 0 | if (tokenInIncentive_ == address(0)) revert ZERO_ADDRESS(); |
| 56 | 0 | if (tokenOutIncentive_ == address(0)) revert ZERO_ADDRESS(); |
| 57 | ||
| 58 | 0 | superAsset = ISuperAsset(superAsset_); |
| 59 | 0 | tokenInIncentive = tokenInIncentive_; |
| 60 | 0 | tokenOutIncentive = tokenOutIncentive_; |
| 61 | } |
|
| 62 | ||
| 63 | /*////////////////////////////////////////////////////////////// |
|
| 64 | EXTERNAL FUNCTIONS |
|
| 65 | //////////////////////////////////////////////////////////////*/ |
|
| 66 | /// @inheritdoc IIncentiveFundContract |
|
| 67 | 0 | function setTokenInIncentive(address token) external onlyManager { |
| 68 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 69 | ||
| 70 | 0 | bool isWhitelisted = superGovernor.isWhitelistedIncentiveToken(token); |
| 71 | ||
| 72 | 0 | if (isWhitelisted) { |
| 73 | 0 | tokenInIncentive = token; |
| 74 | } else { |
|
| 75 | 0 | revert TOKEN_NOT_WHITELISTED(); |
| 76 | } |
|
| 77 | ||
| 78 | 0 | emit SettlementTokenInSet(token); |
| 79 | } |
|
| 80 | ||
| 81 | /// @inheritdoc IIncentiveFundContract |
|
| 82 | 0 | function setTokenOutIncentive(address token) external onlyManager { |
| 83 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 84 | ||
| 85 | 0 | bool isWhitelisted = superGovernor.isWhitelistedIncentiveToken(token); |
| 86 | ||
| 87 | 0 | if (isWhitelisted) { |
| 88 | 0 | tokenOutIncentive = token; |
| 89 | } else { |
|
| 90 | revert TOKEN_NOT_WHITELISTED(); |
|
| 91 | } |
|
| 92 | ||
| 93 | emit SettlementTokenInSet(token); |
|
| 94 | } |
|
| 95 | ||
| 96 | /// @inheritdoc IIncentiveFundContract |
|
| 97 | 0 | function toggleIncentives(bool enabled) external onlyManager { |
| 98 | 0 | incentivesActive = enabled; |
| 99 | 0 | emit IncentivesToggled(enabled); |
| 100 | } |
|
| 101 | ||
| 102 | /// @inheritdoc IIncentiveFundContract |
|
| 103 | 0 | function payIncentive(address receiver, uint256 amountUSD) external onlyManager returns (uint256 amountToken) { |
| 104 | 0 | if (!incentivesActive) { |
| 105 | 0 | return 0; |
| 106 | } |
|
| 107 | ||
| 108 | 0 | _validateInput(receiver, amountUSD); |
| 109 | 0 | if (tokenOutIncentive == address(0)) revert TOKEN_OUT_NOT_SET(); |
| 110 | ||
| 111 | // Get token price and check circuit breakers |
|
| 112 | 0 | (uint256 priceUSD,,,) = superAsset.getPriceAndCircuitBreakers(tokenOutIncentive); |
| 113 | ||
| 114 | 0 | if (priceUSD > 0) { |
| 115 | // Convert USD amount to token amount using price |
|
| 116 | // amountToken = amountUSD / priceUSD |
|
| 117 | 0 | amountToken = Math.mulDiv(amountUSD, IERC20Metadata(tokenInIncentive).decimals(), priceUSD); |
| 118 | ||
| 119 | 0 | IERC20(tokenOutIncentive).safeTransfer(receiver, amountToken); |
| 120 | 0 | emit IncentivePaid(receiver, tokenOutIncentive, amountToken); |
| 121 | } |
|
| 122 | ||
| 123 | 0 | emit IncentivePaid(receiver, tokenOutIncentive, 0); |
| 124 | return 0; |
|
| 125 | } |
|
| 126 | ||
| 127 | /// @inheritdoc IIncentiveFundContract |
|
| 128 | 0 | function takeIncentive(address sender, uint256 amountUSD) external onlyManager returns (uint256 amountToken) { |
| 129 | 0 | if (!incentivesActive) { |
| 130 | 0 | return 0; |
| 131 | } |
|
| 132 | ||
| 133 | 0 | _validateInput(sender, amountUSD); |
| 134 | 0 | if (tokenInIncentive == address(0)) revert TOKEN_IN_NOT_SET(); |
| 135 | ||
| 136 | // Get token price and check circuit breakers |
|
| 137 | 0 | (uint256 priceUSD,,,) = superAsset.getPriceAndCircuitBreakers(tokenInIncentive); |
| 138 | ||
| 139 | 0 | if (priceUSD > 0) { |
| 140 | // Convert USD amount to token amount using price |
|
| 141 | // amountToken = amountUSD / priceUSD |
|
| 142 | 0 | amountToken = Math.mulDiv(amountUSD, IERC20Metadata(tokenInIncentive).decimals(), priceUSD); |
| 143 | ||
| 144 | 0 | IERC20(tokenInIncentive).safeTransferFrom(sender, address(this), amountToken); |
| 145 | 0 | emit IncentiveTaken(sender, tokenInIncentive, amountToken); |
| 146 | } |
|
| 147 | ||
| 148 | 0 | emit IncentiveTaken(sender, tokenInIncentive, 0); |
| 149 | 0 | return 0; |
| 150 | } |
|
| 151 | ||
| 152 | /// @inheritdoc IIncentiveFundContract |
|
| 153 | 0 | function withdraw(address receiver, address tokenOut, uint256 amount) external onlyManager { |
| 154 | 0 | _validateInput(receiver, amount); |
| 155 | 0 | if (tokenOut == address(0)) revert ZERO_ADDRESS(); |
| 156 | ||
| 157 | 0 | IERC20(tokenOut).safeTransfer(receiver, amount); |
| 158 | 0 | emit RebalanceWithdrawal(receiver, tokenOut, amount); |
| 159 | } |
|
| 160 | ||
| 161 | /// @inheritdoc IIncentiveFundContract |
|
| 162 | 0 | function incentivesEnabled() external view returns (bool) { |
| 163 | 0 | return incentivesActive; |
| 164 | } |
|
| 165 | ||
| 166 | /*////////////////////////////////////////////////////////////// |
|
| 167 | INTERNAL FUNCTIONS |
|
| 168 | //////////////////////////////////////////////////////////////*/ |
|
| 169 | 0 | function _validateInput(address user, uint256 amount) internal pure { |
| 170 | 0 | if (user == address(0)) revert ZERO_ADDRESS(); |
| 171 | 0 | if (amount == 0) revert ZERO_AMOUNT(); |
| 172 | } |
|
| 173 | } |
0%
src/SuperAsset/SuperAsset.sol
Lines covered: 0 / 494 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 5 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 8 | import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
|
| 9 | import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
|
| 10 | ||
| 11 | import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol"; |
|
| 12 | import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol"; |
|
| 13 | import { SuperAssetPriceLib } from "../libraries/SuperAssetPriceLib.sol"; |
|
| 14 | import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol"; |
|
| 15 | import { IIncentiveCalculationContract } from "../interfaces/SuperAsset/IIncentiveCalculationContract.sol"; |
|
| 16 | import { IIncentiveFundContract } from "../interfaces/SuperAsset/IIncentiveFundContract.sol"; |
|
| 17 | ||
| 18 | /// @title SuperAsset |
|
| 19 | /// @author Superform Labs |
|
| 20 | /// @notice A meta-vault that manages deposits and redemptions across multiple underlying vaults. |
|
| 21 | /// @dev Implements ERC20 standard for better compatibility with integrators. |
|
| 22 | 0 | contract SuperAsset is ERC20, ISuperAsset { |
| 23 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 24 | using SafeERC20 for IERC20; |
|
| 25 | using Math for uint256; |
|
| 26 | ||
| 27 | // --- Storage for ERC20 variables --- |
|
| 28 | string private tokenName; |
|
| 29 | string private tokenSymbol; |
|
| 30 | ||
| 31 | // --- Interfaces --- |
|
| 32 | ISuperGovernor private superGovernor; |
|
| 33 | ISuperAssetFactory private factory; |
|
| 34 | ||
| 35 | // --- Constants --- |
|
| 36 | 0 | uint256 private constant PRECISION = 1e18; |
| 37 | 0 | uint256 private constant DECIMALS = 18; |
| 38 | 0 | uint256 private constant DEPEG_LOWER_THRESHOLD = 98e16; // 0.98 |
| 39 | 0 | uint256 private constant DEPEG_UPPER_THRESHOLD = 102e16; // 1.02 |
| 40 | 0 | uint256 private constant DISPERSION_THRESHOLD = 1e16; // 1% relative standard deviation threshold |
| 41 | ||
| 42 | // --- Fee Constants --- |
|
| 43 | 0 | uint256 public constant SWAP_FEE_PERC = 10 ** 6; |
| 44 | 0 | uint256 public constant MAX_SWAP_FEE_PERC = 10 ** 4; // Max 10% (1000 basis points) |
| 45 | ||
| 46 | // --- State Variables --- |
|
| 47 | mapping(address token => TokenData data) private tokenData; |
|
| 48 | ||
| 49 | // @notice Contains supported Vaults shares and standard ERC20s |
|
| 50 | EnumerableSet.AddressSet private _supportedAssets; |
|
| 51 | ||
| 52 | 0 | uint256 public swapFeeInPercentage; // Swap fee as a percentage (e.g., 10 for 0.1%) |
| 53 | 0 | uint256 public swapFeeOutPercentage; // Swap fee as a percentage (e.g., 10 for 0.1%) |
| 54 | 0 | uint256 public energyToUSDExchangeRatio; |
| 55 | ||
| 56 | // --- Addresses --- |
|
| 57 | 0 | address private constant USD = address(840); |
| 58 | 0 | address public primaryAsset; |
| 59 | ||
| 60 | // SuperOracle related |
|
| 61 | bytes32 private constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
|
| 62 | ||
| 63 | /*////////////////////////////////////////////////////////////// |
|
| 64 | CONTRACT INITIALIZATION |
|
| 65 | //////////////////////////////////////////////////////////////*/ |
|
| 66 | 0 | constructor() ERC20("", "") { } |
| 67 | ||
| 68 | /// @inheritdoc ISuperAsset |
|
| 69 | 0 | function initialize( |
| 70 | string memory name_, |
|
| 71 | string memory symbol_, |
|
| 72 | address asset, |
|
| 73 | address superGovernor_, |
|
| 74 | uint256 swapFeeInPercentage_, |
|
| 75 | uint256 swapFeeOutPercentage_ |
|
| 76 | ) |
|
| 77 | external |
|
| 78 | { |
|
| 79 | // Ensure this can only be called once |
|
| 80 | 0 | if (address(superGovernor) != address(0)) revert ALREADY_INITIALIZED(); |
| 81 | ||
| 82 | 0 | if (swapFeeInPercentage_ > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE(); |
| 83 | 0 | if (swapFeeOutPercentage_ > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE(); |
| 84 | ||
| 85 | 0 | swapFeeInPercentage = swapFeeInPercentage_; |
| 86 | 0 | swapFeeOutPercentage = swapFeeOutPercentage_; |
| 87 | ||
| 88 | // Initialize ERC20 name and symbol |
|
| 89 | 0 | tokenName = name_; |
| 90 | 0 | tokenSymbol = symbol_; |
| 91 | ||
| 92 | 0 | primaryAsset = asset; |
| 93 | ||
| 94 | 0 | superGovernor = ISuperGovernor(superGovernor_); |
| 95 | 0 | factory = ISuperAssetFactory(superGovernor.getAddress(superGovernor.SUPER_ASSET_FACTORY())); |
| 96 | } |
|
| 97 | ||
| 98 | /*////////////////////////////////////////////////////////////// |
|
| 99 | MANAGER FUNCTIONS |
|
| 100 | //////////////////////////////////////////////////////////////*/ |
|
| 101 | /// @inheritdoc ISuperAsset |
|
| 102 | 0 | function whitelistERC20(address token) external { |
| 103 | 0 | _onlyManager(); |
| 104 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 105 | 0 | if (tokenData[token].isSupportedERC20) revert ALREADY_WHITELISTED(); |
| 106 | ||
| 107 | // Set token as supported and active |
|
| 108 | 0 | tokenData[token].isSupportedERC20 = true; |
| 109 | 0 | tokenData[token].isActive = true; |
| 110 | ||
| 111 | 0 | tokenData[token].oracle = superGovernor.getAddress(superGovernor.SUPER_ORACLE()); |
| 112 | 0 | _supportedAssets.add(token); |
| 113 | ||
| 114 | 0 | emit ERC20Whitelisted(token); |
| 115 | } |
|
| 116 | ||
| 117 | /// @inheritdoc ISuperAsset |
|
| 118 | 0 | function removeERC20(address token) external { |
| 119 | 0 | _onlyManager(); |
| 120 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 121 | ||
| 122 | // Mark token as inactive |
|
| 123 | 0 | tokenData[token].isActive = false; |
| 124 | ||
| 125 | // Prevent full purge if token has 0 balance |
|
| 126 | 0 | if (IERC20(token).balanceOf(address(this)) == 0) { |
| 127 | // Full removal - clear all data |
|
| 128 | 0 | _supportedAssets.remove(token); |
| 129 | 0 | tokenData[token].oracle = address(0); |
| 130 | 0 | tokenData[token].isSupportedERC20 = false; |
| 131 | } |
|
| 132 | ||
| 133 | 0 | emit ERC20Removed(token); |
| 134 | } |
|
| 135 | ||
| 136 | /// @inheritdoc ISuperAsset |
|
| 137 | 0 | function activateERC20(address token) external { |
| 138 | 0 | _onlyManager(); |
| 139 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 140 | 0 | if (!tokenData[token].isSupportedERC20) revert TOKEN_NOT_SUPPORTED(); |
| 141 | 0 | if (tokenData[token].isActive) revert TOKEN_ALREADY_ACTIVE(); |
| 142 | ||
| 143 | // Reactivate the token |
|
| 144 | 0 | tokenData[token].isActive = true; |
| 145 | ||
| 146 | 0 | emit ERC20Activated(token); |
| 147 | } |
|
| 148 | ||
| 149 | /// @inheritdoc ISuperAsset |
|
| 150 | 0 | function whitelistVault(address vault, address yieldSourceOracle) external { |
| 151 | 0 | _onlyManager(); |
| 152 | 0 | if (vault == address(0) || yieldSourceOracle == address(0)) revert ZERO_ADDRESS(); |
| 153 | 0 | if (tokenData[vault].isSupportedUnderlyingVault) revert ALREADY_WHITELISTED(); |
| 154 | ||
| 155 | // Set vault as supported and active |
|
| 156 | 0 | tokenData[vault].isSupportedUnderlyingVault = true; |
| 157 | 0 | tokenData[vault].isActive = true; |
| 158 | ||
| 159 | 0 | tokenData[vault].oracle = yieldSourceOracle; |
| 160 | 0 | _supportedAssets.add(vault); |
| 161 | ||
| 162 | 0 | emit VaultWhitelisted(vault); |
| 163 | } |
|
| 164 | ||
| 165 | /// @inheritdoc ISuperAsset |
|
| 166 | 0 | function removeVault(address vault) external { |
| 167 | 0 | _onlyManager(); |
| 168 | 0 | if (vault == address(0)) revert ZERO_ADDRESS(); |
| 169 | ||
| 170 | // Mark vault as inactive |
|
| 171 | 0 | tokenData[vault].isActive = false; |
| 172 | ||
| 173 | // Prevent full purge if vault has 9 balance |
|
| 174 | 0 | if (IERC20(vault).balanceOf(address(this)) == 0) { |
| 175 | // Full removal - clear all data |
|
| 176 | 0 | _supportedAssets.remove(vault); |
| 177 | 0 | tokenData[vault].oracle = address(0); |
| 178 | 0 | tokenData[vault].isSupportedUnderlyingVault = false; |
| 179 | } |
|
| 180 | ||
| 181 | 0 | emit VaultRemoved(vault); |
| 182 | } |
|
| 183 | ||
| 184 | /// @inheritdoc ISuperAsset |
|
| 185 | 0 | function activateVault(address vault) external { |
| 186 | 0 | _onlyManager(); |
| 187 | 0 | if (vault == address(0)) revert ZERO_ADDRESS(); |
| 188 | 0 | if (!tokenData[vault].isSupportedUnderlyingVault) revert VAULT_NOT_SUPPORTED(); |
| 189 | 0 | if (tokenData[vault].isActive) revert TOKEN_ALREADY_ACTIVE(); |
| 190 | ||
| 191 | // Reactivate the vault |
|
| 192 | 0 | tokenData[vault].isActive = true; |
| 193 | ||
| 194 | 0 | emit VaultActivated(vault); |
| 195 | } |
|
| 196 | ||
| 197 | /// @inheritdoc ISuperAsset |
|
| 198 | 0 | function setSwapFeeInPercentage(uint256 _feePercentage) external { |
| 199 | 0 | _onlyManager(); |
| 200 | 0 | if (_feePercentage > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE(); |
| 201 | 0 | swapFeeInPercentage = _feePercentage; |
| 202 | } |
|
| 203 | ||
| 204 | /// @inheritdoc ISuperAsset |
|
| 205 | 0 | function setSwapFeeOutPercentage(uint256 _feePercentage) external { |
| 206 | 0 | _onlyManager(); |
| 207 | 0 | if (_feePercentage > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE(); |
| 208 | 0 | swapFeeOutPercentage = _feePercentage; |
| 209 | } |
|
| 210 | ||
| 211 | /// @inheritdoc ISuperAsset |
|
| 212 | 0 | function setWeight(address vault, uint256 weight) external { |
| 213 | 0 | _onlyManager(); |
| 214 | 0 | if (vault == address(0)) revert ZERO_ADDRESS(); |
| 215 | 0 | if (!tokenData[vault].isSupportedUnderlyingVault && !tokenData[vault].isSupportedERC20) revert NOT_SUPPORTED_TOKEN(); |
| 216 | 0 | tokenData[vault].weights = weight; |
| 217 | 0 | emit WeightSet(vault, weight); |
| 218 | } |
|
| 219 | ||
| 220 | /// @inheritdoc ISuperAsset |
|
| 221 | 0 | function setEnergyToUSDExchangeRatio(uint256 newRatio) external { |
| 222 | 0 | _onlyManager(); |
| 223 | 0 | energyToUSDExchangeRatio = newRatio; |
| 224 | 0 | emit EnergyToUSDExchangeRatioSet(newRatio); |
| 225 | } |
|
| 226 | ||
| 227 | /// @inheritdoc ISuperAsset |
|
| 228 | 0 | function mint(address to, uint256 amount) external { |
| 229 | 0 | _onlyManager(); |
| 230 | 0 | _mint(to, amount); |
| 231 | } |
|
| 232 | ||
| 233 | /// @inheritdoc ISuperAsset |
|
| 234 | 0 | function burn(address from, uint256 amount) external { |
| 235 | 0 | _onlyManager(); |
| 236 | 0 | _burn(from, amount); |
| 237 | } |
|
| 238 | ||
| 239 | /*////////////////////////////////////////////////////////////// |
|
| 240 | STRATEGIST FUNCTIONS |
|
| 241 | //////////////////////////////////////////////////////////////*/ |
|
| 242 | /// @inheritdoc ISuperAsset |
|
| 243 | 0 | function setTargetAllocations(address[] calldata tokens, uint256[] calldata allocations) external { |
| 244 | 0 | _onlyStrategist(); |
| 245 | 0 | uint256 lenTokens = tokens.length; |
| 246 | 0 | if (lenTokens != allocations.length) revert INVALID_INPUT(); |
| 247 | ||
| 248 | 0 | for (uint256 i; i < lenTokens; i++) { |
| 249 | 0 | if (tokens[i] == address(0)) revert ZERO_ADDRESS(); |
| 250 | 0 | if (!tokenData[tokens[i]].isSupportedUnderlyingVault && !tokenData[tokens[i]].isSupportedERC20) { |
| 251 | 0 | revert NOT_SUPPORTED_TOKEN(); |
| 252 | } |
|
| 253 | } |
|
| 254 | ||
| 255 | 0 | for (uint256 i; i < lenTokens; i++) { |
| 256 | 0 | tokenData[tokens[i]].targetAllocations = allocations[i]; |
| 257 | 0 | emit TargetAllocationSet(tokens[i], allocations[i]); |
| 258 | } |
|
| 259 | } |
|
| 260 | ||
| 261 | /// @inheritdoc ISuperAsset |
|
| 262 | 0 | function setTargetAllocation(address token, uint256 allocation) external { |
| 263 | 0 | _onlyStrategist(); |
| 264 | 0 | if (token == address(0)) revert ZERO_ADDRESS(); |
| 265 | 0 | if (!tokenData[token].isSupportedUnderlyingVault && !tokenData[token].isSupportedERC20) { |
| 266 | 0 | revert NOT_SUPPORTED_TOKEN(); |
| 267 | } |
|
| 268 | ||
| 269 | // @dev Allocations get normalized inside the ICC so we dont need to additional checks here |
|
| 270 | 0 | tokenData[token].targetAllocations = allocation; |
| 271 | 0 | emit TargetAllocationSet(token, allocation); |
| 272 | } |
|
| 273 | ||
| 274 | /*////////////////////////////////////////////////////////////// |
|
| 275 | EXTERNAL FUNCTIONS |
|
| 276 | //////////////////////////////////////////////////////////////*/ |
|
| 277 | /// @inheritdoc ISuperAsset |
|
| 278 | 0 | function deposit(DepositArgs memory args) public returns (DepositReturnVars memory ret) { |
| 279 | // First all the non state changing functions |
|
| 280 | 0 | if (args.receiver == address(0) || args.tokenIn == address(0)) revert ZERO_ADDRESS(); |
| 281 | 0 | if (args.amountTokenToDeposit == 0) revert ZERO_AMOUNT(); |
| 282 | 0 | if ( |
| 283 | 0 | !tokenData[args.tokenIn].isSupportedERC20 && !tokenData[args.tokenIn].isSupportedUnderlyingVault |
| 284 | 0 | || !tokenData[args.tokenIn].isActive |
| 285 | ) { |
|
| 286 | 0 | revert NOT_SUPPORTED_TOKEN(); |
| 287 | } |
|
| 288 | ||
| 289 | // Create preview deposit args |
|
| 290 | 0 | PreviewDepositArgs memory previewArgs = PreviewDepositArgs({ |
| 291 | 0 | tokenIn: args.tokenIn, |
| 292 | 0 | amountTokenToDeposit: args.amountTokenToDeposit, |
| 293 | isSoft: false |
|
| 294 | }); |
|
| 295 | ||
| 296 | // Call previewDeposit with the new struct approach |
|
| 297 | 0 | PreviewDepositReturnVars memory previewRet = previewDeposit(previewArgs); |
| 298 | ||
| 299 | // Store results in return variable |
|
| 300 | 0 | ret.amountSharesMinted = previewRet.amountSharesMinted; |
| 301 | 0 | ret.swapFee = previewRet.swapFee; |
| 302 | 0 | ret.amountIncentiveUSDDeposit = previewRet.amountIncentiveUSDDeposit; |
| 303 | ||
| 304 | // incentiveCalculationSuccess == false but totalSupply == 0 for the first deposit, this check allows for the |
|
| 305 | // first deposit (in which case calculateIncentive() will fail) to pass |
|
| 306 | 0 | if (!previewRet.incentiveCalculationSuccess && totalSupply() != 0) revert INCENTIVE_CALCULATION_FAILED(); |
| 307 | ||
| 308 | // Slippage Check |
|
| 309 | 0 | if (ret.amountSharesMinted < args.minSharesOut) revert SLIPPAGE_PROTECTION(); |
| 310 | ||
| 311 | 0 | if (previewRet.assetWithBreakerTriggered != address(0)) { |
| 312 | // Circuit Breaker Checks |
|
| 313 | 0 | if (previewRet.isDepeg) { |
| 314 | 0 | revert SUPPORTED_ASSET_PRICE_DEPEG(previewRet.assetWithBreakerTriggered); |
| 315 | } |
|
| 316 | 0 | if (previewRet.isOracleOff) { |
| 317 | 0 | revert SUPPORTED_ASSET_PRICE_ORACLE_OFF(previewRet.assetWithBreakerTriggered); |
| 318 | } |
|
| 319 | 0 | if (previewRet.isDispersion) { |
| 320 | 0 | revert SUPPORTED_ASSET_PRICE_DISPERSION(previewRet.assetWithBreakerTriggered); |
| 321 | } |
|
| 322 | 0 | if (previewRet.oraclePriceUSD == 0) { |
| 323 | 0 | revert SUPPORTED_ASSET_PRICE_ZERO(previewRet.assetWithBreakerTriggered); |
| 324 | } |
|
| 325 | } |
|
| 326 | ||
| 327 | 0 | if (!previewRet.tokenInFound) { |
| 328 | 0 | revert NOT_SUPPORTED_TOKEN(); |
| 329 | } |
|
| 330 | ||
| 331 | // Settle Incentives |
|
| 332 | 0 | if (ret.amountIncentiveUSDDeposit > 0) { |
| 333 | 0 | _settleIncentive(args.receiver, ret.amountIncentiveUSDDeposit); |
| 334 | } |
|
| 335 | ||
| 336 | // Transfer the tokenIn from the sender to this contract |
|
| 337 | 0 | IERC20(args.tokenIn).safeTransferFrom(msg.sender, address(this), args.amountTokenToDeposit); |
| 338 | ||
| 339 | // Transfer swap fees to SuperBank |
|
| 340 | 0 | IERC20(args.tokenIn).safeTransfer(superGovernor.getAddress(superGovernor.SUPER_BANK()), ret.swapFee); |
| 341 | // Mint SuperUSD shares |
|
| 342 | 0 | _mint(args.receiver, ret.amountSharesMinted); |
| 343 | ||
| 344 | 0 | emit Deposit( |
| 345 | 0 | args.receiver, |
| 346 | 0 | args.tokenIn, |
| 347 | 0 | args.amountTokenToDeposit, |
| 348 | 0 | ret.amountSharesMinted, |
| 349 | 0 | ret.swapFee, |
| 350 | 0 | ret.amountIncentiveUSDDeposit |
| 351 | ); |
|
| 352 | } |
|
| 353 | ||
| 354 | ||
| 355 | /// @inheritdoc ISuperAsset |
|
| 356 | 0 | function redeem(RedeemArgs memory args) public returns (RedeemReturnVars memory ret) { |
| 357 | // First validate parameters |
|
| 358 | 0 | if (args.receiver == address(0) || args.tokenOut == address(0)) revert ZERO_ADDRESS(); |
| 359 | 0 | if (args.amountSharesToRedeem == 0) revert ZERO_AMOUNT(); |
| 360 | ||
| 361 | // NOTE: Only revert if the token was not in the whitelist even before |
|
| 362 | 0 | if (!_supportedAssets.contains(args.tokenOut)) revert NOT_SUPPORTED_TOKEN(); |
| 363 | ||
| 364 | // Create preview redeem args |
|
| 365 | 0 | PreviewRedeemArgs memory previewArgs = PreviewRedeemArgs({ |
| 366 | 0 | tokenOut: args.tokenOut, |
| 367 | 0 | amountSharesToRedeem: args.amountSharesToRedeem, |
| 368 | // NOTE: Here we set isSoft=true on purpose since the desired behavior is to make the redeem() flow not to revert even in case of circtuit breakers triggered |
|
| 369 | // The reason is the redeem() allows SuperAsset to sell assets and if an asset has circuit breakers triggered then it's likely an asset whose risk profile does not match the kind of risk that we desire in the SuperAsset balance sheet |
|
| 370 | // This can be considered opinionated and an argument could be it should be the SuperAsset manager making decision on that, we think it can be discussed |
|
| 371 | 0 | isSoft: true // isSoft = true for soft checks that won't revert on circuit breaker triggers |
| 372 | }); |
|
| 373 | ||
| 374 | // Call previewRedeem with the new struct approach |
|
| 375 | 0 | PreviewRedeemReturnVars memory previewRet = previewRedeem(previewArgs); |
| 376 | ||
| 377 | // Store results in return variable |
|
| 378 | 0 | ret.amountTokenOutAfterFees = previewRet.amountTokenOutAfterFees; |
| 379 | 0 | ret.swapFee = previewRet.swapFee; |
| 380 | 0 | ret.amountIncentiveUSDRedeem = previewRet.amountIncentiveUSDRedeem; |
| 381 | ||
| 382 | // --- Post-preview checks --- |
|
| 383 | // incentiveCalculationSuccess == false but totalSupply == 0 for the first deposit, this check allows for the |
|
| 384 | // first deposit (in which case calculateIncentive() will fail) to pass. Similar logic applies to redeem. |
|
| 385 | 0 | if (!previewRet.incentiveCalculationSuccess && totalSupply() != 0) revert INCENTIVE_CALCULATION_FAILED(); |
| 386 | ||
| 387 | // Slippage Check |
|
| 388 | 0 | if (ret.amountTokenOutAfterFees < args.minTokenOut) revert SLIPPAGE_PROTECTION(); |
| 389 | ||
| 390 | // Circuit Breaker Checks |
|
| 391 | 0 | if (previewRet.assetWithBreakerTriggered != address(0)) { |
| 392 | 0 | if (previewRet.isDepeg) { |
| 393 | 0 | revert SUPPORTED_ASSET_PRICE_DEPEG(previewRet.assetWithBreakerTriggered); |
| 394 | } |
|
| 395 | 0 | if (previewRet.isOracleOff) { |
| 396 | 0 | revert SUPPORTED_ASSET_PRICE_ORACLE_OFF(previewRet.assetWithBreakerTriggered); |
| 397 | } |
|
| 398 | 0 | if (previewRet.isDispersion) { |
| 399 | 0 | revert SUPPORTED_ASSET_PRICE_DISPERSION(previewRet.assetWithBreakerTriggered); |
| 400 | } |
|
| 401 | 0 | if (previewRet.oraclePriceUSD == 0) { |
| 402 | 0 | revert SUPPORTED_ASSET_PRICE_ZERO(previewRet.assetWithBreakerTriggered); |
| 403 | } |
|
| 404 | } |
|
| 405 | ||
| 406 | 0 | if (!previewRet.tokenOutFound) { |
| 407 | 0 | revert NOT_SUPPORTED_TOKEN(); // Or a more specific error if tokenOut_ was expected to be found by preview |
| 408 | } |
|
| 409 | ||
| 410 | // --- State Changing Operations --- |
|
| 411 | ||
| 412 | // Settle Incentives |
|
| 413 | 0 | if (ret.amountIncentiveUSDRedeem > 0) { |
| 414 | 0 | _settleIncentive(args.receiver, ret.amountIncentiveUSDRedeem); |
| 415 | } |
|
| 416 | // Burn SuperUSD shares from the sender |
|
| 417 | 0 | _burn(msg.sender, args.amountSharesToRedeem); |
| 418 | ||
| 419 | // Transfer swap fees to SuperBank |
|
| 420 | 0 | IERC20(args.tokenOut).safeTransfer(superGovernor.getAddress(superGovernor.SUPER_BANK()), ret.swapFee); |
| 421 | ||
| 422 | // Transfer assets to receiver |
|
| 423 | 0 | IERC20(args.tokenOut).safeTransfer(args.receiver, ret.amountTokenOutAfterFees); |
| 424 | ||
| 425 | // --- Emit event and set return values --- |
|
| 426 | 0 | emit Redeem( |
| 427 | 0 | args.receiver, |
| 428 | 0 | args.tokenOut, |
| 429 | 0 | args.amountSharesToRedeem, |
| 430 | 0 | ret.amountTokenOutAfterFees, |
| 431 | 0 | ret.swapFee, |
| 432 | 0 | ret.amountIncentiveUSDRedeem |
| 433 | ); |
|
| 434 | } |
|
| 435 | ||
| 436 | /// @inheritdoc ISuperAsset |
|
| 437 | 0 | function swap(SwapArgs memory args) external returns (SwapReturnVars memory ret) { |
| 438 | 0 | if (args.receiver == address(0) || args.tokenIn == address(0) || args.tokenOut == address(0)) { |
| 439 | 0 | revert ZERO_ADDRESS(); |
| 440 | } |
|
| 441 | ||
| 442 | // Create deposit args from swap args |
|
| 443 | 0 | DepositArgs memory depositArgs = DepositArgs({ |
| 444 | 0 | receiver: msg.sender, |
| 445 | 0 | tokenIn: args.tokenIn, |
| 446 | 0 | amountTokenToDeposit: args.amountTokenToDeposit, |
| 447 | minSharesOut: 0 |
|
| 448 | }); |
|
| 449 | 0 | DepositReturnVars memory depositRet = deposit(depositArgs); |
| 450 | ||
| 451 | // Create redeem args from swap args and deposit result |
|
| 452 | 0 | RedeemArgs memory redeemArgs = RedeemArgs({ |
| 453 | 0 | receiver: args.receiver, |
| 454 | 0 | amountSharesToRedeem: depositRet.amountSharesMinted, |
| 455 | 0 | tokenOut: args.tokenOut, |
| 456 | 0 | minTokenOut: args.minTokenOut |
| 457 | }); |
|
| 458 | 0 | RedeemReturnVars memory redeemRet = redeem(redeemArgs); |
| 459 | ||
| 460 | // Fill the return struct |
|
| 461 | 0 | ret.amountSharesIntermediateStep = depositRet.amountSharesMinted; |
| 462 | 0 | ret.amountTokenOutAfterFees = redeemRet.amountTokenOutAfterFees; |
| 463 | 0 | ret.swapFeeIn = depositRet.swapFee; |
| 464 | 0 | ret.swapFeeOut = redeemRet.swapFee; |
| 465 | 0 | ret.amountIncentivesIn = depositRet.amountIncentiveUSDDeposit; |
| 466 | 0 | ret.amountIncentivesOut = redeemRet.amountIncentiveUSDRedeem; |
| 467 | ||
| 468 | 0 | emit Swap( |
| 469 | 0 | args.receiver, |
| 470 | 0 | args.tokenIn, |
| 471 | 0 | args.amountTokenToDeposit, |
| 472 | 0 | args.tokenOut, |
| 473 | 0 | ret.amountSharesIntermediateStep, |
| 474 | 0 | ret.amountTokenOutAfterFees, |
| 475 | 0 | ret.swapFeeIn, |
| 476 | 0 | ret.swapFeeOut, |
| 477 | 0 | ret.amountIncentivesIn, |
| 478 | 0 | ret.amountIncentivesOut |
| 479 | ); |
|
| 480 | } |
|
| 481 | ||
| 482 | /*////////////////////////////////////////////////////////////// |
|
| 483 | PREVIEW FUNCTIONS |
|
| 484 | //////////////////////////////////////////////////////////////*/ |
|
| 485 | /// @inheritdoc ISuperAsset |
|
| 486 | 0 | function previewDeposit(PreviewDepositArgs memory args) public view returns (PreviewDepositReturnVars memory ret) { |
| 487 | // Calculate swap fees |
|
| 488 | 0 | ret.swapFee = Math.mulDiv(args.amountTokenToDeposit, swapFeeInPercentage, SWAP_FEE_PERC); |
| 489 | ||
| 490 | // Get current and post-operation allocations using the struct-based return value |
|
| 491 | 0 | ISuperAsset.AllocationOperationReturnVars memory allocRet = getAllocationsPrePostOperationDeposit( |
| 492 | 0 | args.tokenIn, args.amountTokenToDeposit, args.amountTokenToDeposit - ret.swapFee, args.isSoft |
| 493 | ); |
|
| 494 | ||
| 495 | // Copy values from allocation return struct to our local variable |
|
| 496 | 0 | ret.amountSharesMinted = allocRet.amountAssets; |
| 497 | ||
| 498 | // Copy circuit breaker info to return struct |
|
| 499 | 0 | ret.assetWithBreakerTriggered = allocRet.assetWithBreakerTriggered; |
| 500 | 0 | ret.oraclePriceUSD = allocRet.oraclePriceUSD; |
| 501 | 0 | ret.isDepeg = allocRet.isDepeg; |
| 502 | 0 | ret.isDispersion = allocRet.isDispersion; |
| 503 | 0 | ret.isOracleOff = allocRet.isOracleOff; |
| 504 | 0 | ret.tokenInFound = allocRet.tokenFound; |
| 505 | ||
| 506 | 0 | if ((ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) { |
| 507 | // Early return with empty result but with circuit breaker info |
|
| 508 | 0 | ret.amountSharesMinted = 0; |
| 509 | 0 | ret.swapFee = 0; |
| 510 | 0 | ret.amountIncentiveUSDDeposit = 0; |
| 511 | 0 | ret.incentiveCalculationSuccess = false; |
| 512 | ||
| 513 | return ret; |
|
| 514 | } |
|
| 515 | ||
| 516 | // Calculate incentives (via ICC) |
|
| 517 | 0 | if (IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).incentivesEnabled()) { |
| 518 | (ret.amountIncentiveUSDDeposit, ret.incentiveCalculationSuccess) = IIncentiveCalculationContract( |
|
| 519 | factory.getIncentiveCalculationContract(address(this)) |
|
| 520 | ).calculateIncentive( |
|
| 521 | allocRet.absoluteAllocationPreOperation, |
|
| 522 | allocRet.absoluteAllocationPostOperation, |
|
| 523 | allocRet.absoluteTargetAllocation, |
|
| 524 | allocRet.vaultWeights, |
|
| 525 | allocRet.totalAllocationPreOperation, |
|
| 526 | allocRet.totalAllocationPostOperation, |
|
| 527 | allocRet.totalTargetAllocation, |
|
| 528 | energyToUSDExchangeRatio |
|
| 529 | ); |
|
| 530 | } else { |
|
| 531 | // if incentives disabled, soft return incentiveCalculationSuccess as true |
|
| 532 | ret.incentiveCalculationSuccess = true; |
|
| 533 | } |
|
| 534 | } |
|
| 535 | ||
| 536 | /// @inheritdoc ISuperAsset |
|
| 537 | 0 | function previewRedeem(PreviewRedeemArgs memory args) public view returns (PreviewRedeemReturnVars memory ret) { |
| 538 | // Get current and post-operation allocations using the struct-based return value |
|
| 539 | 0 | ISuperAsset.AllocationOperationReturnVars memory allocRet = |
| 540 | 0 | getAllocationsPrePostOperationRedeem(args.tokenOut, args.amountSharesToRedeem, args.isSoft); |
| 541 | ||
| 542 | // Copy circuit breaker info to return struct |
|
| 543 | 0 | ret.assetWithBreakerTriggered = allocRet.assetWithBreakerTriggered; |
| 544 | 0 | ret.oraclePriceUSD = allocRet.oraclePriceUSD; |
| 545 | 0 | ret.isDepeg = allocRet.isDepeg; |
| 546 | 0 | ret.isDispersion = allocRet.isDispersion; |
| 547 | 0 | ret.isOracleOff = allocRet.isOracleOff; |
| 548 | 0 | ret.tokenOutFound = allocRet.tokenFound; |
| 549 | ||
| 550 | 0 | if ((ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) { |
| 551 | // Early return with empty result but with circuit breaker info |
|
| 552 | 0 | ret.amountTokenOutAfterFees = 0; |
| 553 | 0 | ret.swapFee = 0; |
| 554 | 0 | ret.amountIncentiveUSDRedeem = 0; |
| 555 | 0 | ret.incentiveCalculationSuccess = false; |
| 556 | ||
| 557 | return ret; |
|
| 558 | } |
|
| 559 | ||
| 560 | // Calculate swap fee |
|
| 561 | 0 | ret.swapFee = Math.mulDiv(allocRet.amountAssets, swapFeeOutPercentage, SWAP_FEE_PERC); // 0.1% |
| 562 | 0 | ret.amountTokenOutAfterFees = allocRet.amountAssets - ret.swapFee; |
| 563 | ||
| 564 | // Calculate incentives (via ICC) |
|
| 565 | 0 | if (IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).incentivesEnabled()) { |
| 566 | 0 | (ret.amountIncentiveUSDRedeem, ret.incentiveCalculationSuccess) = IIncentiveCalculationContract( |
| 567 | 0 | factory.getIncentiveCalculationContract(address(this)) |
| 568 | ).calculateIncentive( |
|
| 569 | 0 | allocRet.absoluteAllocationPreOperation, |
| 570 | 0 | allocRet.absoluteAllocationPostOperation, |
| 571 | 0 | allocRet.absoluteTargetAllocation, |
| 572 | 0 | allocRet.vaultWeights, |
| 573 | 0 | allocRet.totalAllocationPreOperation, |
| 574 | 0 | allocRet.totalAllocationPostOperation, |
| 575 | 0 | allocRet.totalTargetAllocation, |
| 576 | 0 | energyToUSDExchangeRatio |
| 577 | ); |
|
| 578 | } else { |
|
| 579 | // if incentives disabled, soft return incentiveCalculationSuccess as true |
|
| 580 | 0 | ret.incentiveCalculationSuccess = true; |
| 581 | } |
|
| 582 | } |
|
| 583 | ||
| 584 | /// @inheritdoc ISuperAsset |
|
| 585 | 0 | function previewSwap(PreviewSwapArgs memory args) external view returns (PreviewSwapReturnVars memory ret) { |
| 586 | 0 | uint256 amountSharesMinted; |
| 587 | // Create preview deposit args |
|
| 588 | 0 | PreviewDepositArgs memory depositArgs = PreviewDepositArgs({ |
| 589 | 0 | tokenIn: args.tokenIn, |
| 590 | 0 | amountTokenToDeposit: args.amountTokenToDeposit, |
| 591 | 0 | isSoft: args.isSoft |
| 592 | }); |
|
| 593 | ||
| 594 | // Call previewDeposit with the new struct approach |
|
| 595 | 0 | PreviewDepositReturnVars memory depositRet = previewDeposit(depositArgs); |
| 596 | ||
| 597 | // Store results |
|
| 598 | 0 | amountSharesMinted = depositRet.amountSharesMinted; |
| 599 | 0 | ret.swapFeeIn = depositRet.swapFee; |
| 600 | 0 | ret.amountIncentiveUSDDeposit = depositRet.amountIncentiveUSDDeposit; |
| 601 | 0 | ret.assetWithBreakerTriggered = depositRet.assetWithBreakerTriggered; |
| 602 | 0 | ret.oraclePriceUSD = depositRet.oraclePriceUSD; |
| 603 | 0 | ret.isDepeg = depositRet.isDepeg; |
| 604 | 0 | ret.isDispersion = depositRet.isDispersion; |
| 605 | 0 | ret.isOracleOff = depositRet.isOracleOff; |
| 606 | 0 | ret.tokenInFound = depositRet.tokenInFound; |
| 607 | 0 | ret.incentiveCalculationSuccess = depositRet.incentiveCalculationSuccess; |
| 608 | ||
| 609 | 0 | if ( |
| 610 | 0 | !ret.tokenInFound || ret.isDepeg || ret.isDispersion || ret.isOracleOff || !ret.incentiveCalculationSuccess |
| 611 | 0 | || ret.oraclePriceUSD == 0 |
| 612 | ) { |
|
| 613 | // Return empty result but with circuit breaker info |
|
| 614 | 0 | ret.amountTokenOutAfterFees = 0; |
| 615 | 0 | ret.swapFeeOut = 0; |
| 616 | 0 | ret.amountIncentiveUSDRedeem = 0; |
| 617 | return ret; |
|
| 618 | } |
|
| 619 | ||
| 620 | // note: it is possible to optimize the calls to oracle if the logic is extracted inside the preview functions |
|
| 621 | ||
| 622 | // Create preview redeem args |
|
| 623 | 0 | PreviewRedeemArgs memory redeemArgs = PreviewRedeemArgs({ |
| 624 | 0 | tokenOut: args.tokenOut, |
| 625 | 0 | amountSharesToRedeem: amountSharesMinted, |
| 626 | 0 | isSoft: args.isSoft |
| 627 | }); |
|
| 628 | ||
| 629 | // Call previewRedeem with the new struct approach |
|
| 630 | 0 | PreviewRedeemReturnVars memory redeemRet = previewRedeem(redeemArgs); |
| 631 | ||
| 632 | // Store results |
|
| 633 | 0 | ret.amountTokenOutAfterFees = redeemRet.amountTokenOutAfterFees; |
| 634 | 0 | ret.swapFeeOut = redeemRet.swapFee; |
| 635 | 0 | ret.amountIncentiveUSDRedeem = redeemRet.amountIncentiveUSDRedeem; |
| 636 | 0 | ret.assetWithBreakerTriggered = redeemRet.assetWithBreakerTriggered; |
| 637 | 0 | ret.oraclePriceUSD = redeemRet.oraclePriceUSD; |
| 638 | 0 | ret.isDepeg = redeemRet.isDepeg; |
| 639 | 0 | ret.isDispersion = redeemRet.isDispersion; |
| 640 | 0 | ret.isOracleOff = redeemRet.isOracleOff; |
| 641 | 0 | ret.tokenInFound = redeemRet.tokenOutFound; // Mapped from tokenOutFound |
| 642 | 0 | ret.incentiveCalculationSuccess = redeemRet.incentiveCalculationSuccess; |
| 643 | } |
|
| 644 | ||
| 645 | /*////////////////////////////////////////////////////////////// |
|
| 646 | EXTERNAL GETTER FUNCTIONS |
|
| 647 | //////////////////////////////////////////////////////////////*/ |
|
| 648 | /// @inheritdoc ISuperAsset |
|
| 649 | 0 | function getTokenData(address token) external view returns (TokenData memory) { |
| 650 | 0 | return tokenData[token]; |
| 651 | } |
|
| 652 | ||
| 653 | /// @inheritdoc ISuperAsset |
|
| 654 | 0 | function getPrecision() external pure returns (uint256) { |
| 655 | return PRECISION; |
|
| 656 | } |
|
| 657 | ||
| 658 | /*////////////////////////////////////////////////////////////// |
|
| 659 | PUBLIC GETTER FUNCTIONS |
|
| 660 | //////////////////////////////////////////////////////////////*/ |
|
| 661 | /// @inheritdoc ISuperAsset |
|
| 662 | 0 | function getPriceAndCircuitBreakers(address token) |
| 663 | public |
|
| 664 | view |
|
| 665 | 0 | returns (uint256 priceUSD, bool isDepeg, bool isDispersion, bool isOracleOff) |
| 666 | { |
|
| 667 | 0 | address superOracle = superGovernor.getAddress(superGovernor.SUPER_ORACLE()); |
| 668 | ||
| 669 | 0 | return SuperAssetPriceLib.getPriceWithCircuitBreakers( |
| 670 | 0 | ISuperAsset.PriceArgs({ |
| 671 | superOracle: superOracle, |
|
| 672 | 0 | superAsset: address(this), |
| 673 | token: token, |
|
| 674 | usd: USD, |
|
| 675 | depegLowerThreshold: DEPEG_LOWER_THRESHOLD, |
|
| 676 | depegUpperThreshold: DEPEG_UPPER_THRESHOLD, |
|
| 677 | dispersionThreshold: DISPERSION_THRESHOLD |
|
| 678 | }) |
|
| 679 | ); |
|
| 680 | } |
|
| 681 | ||
| 682 | /// @inheritdoc ISuperAsset |
|
| 683 | 0 | function getSuperAssetPPS() |
| 684 | external |
|
| 685 | view |
|
| 686 | returns ( |
|
| 687 | 0 | address[] memory activeTokens, |
| 688 | 0 | uint256[] memory pricePerTokenUSD, |
| 689 | 0 | bool[] memory isDepeg, |
| 690 | 0 | bool[] memory isDispersion, |
| 691 | 0 | bool[] memory isOracleOff, |
| 692 | 0 | uint256 pps |
| 693 | ) |
|
| 694 | 0 | { |
| 695 | 0 | uint256 len = _supportedAssets.length(); |
| 696 | 0 | activeTokens = new address[](len); |
| 697 | 0 | pricePerTokenUSD = new uint256[](len); |
| 698 | 0 | isDepeg = new bool[](len); |
| 699 | 0 | isDispersion = new bool[](len); |
| 700 | 0 | isOracleOff = new bool[](len); |
| 701 | ||
| 702 | 0 | uint256 totalValueUSD; |
| 703 | 0 | for (uint256 i; i < len; i++) { |
| 704 | 0 | address token = _supportedAssets.at(i); |
| 705 | 0 | activeTokens[i] = token; |
| 706 | ||
| 707 | 0 | (uint256 priceUSD, bool isTokenDepeg, bool isTokenDispersion, bool isTokenOracleOff) = |
| 708 | 0 | getPriceAndCircuitBreakers(token); |
| 709 | ||
| 710 | 0 | pricePerTokenUSD[i] = priceUSD; |
| 711 | 0 | isDepeg[i] = isTokenDepeg; |
| 712 | 0 | isDispersion[i] = isTokenDispersion; |
| 713 | 0 | isOracleOff[i] = isTokenOracleOff; |
| 714 | ||
| 715 | 0 | uint256 balance = IERC20(token).balanceOf(address(this)); |
| 716 | ||
| 717 | 0 | if (balance > 0) { |
| 718 | 0 | totalValueUSD += Math.mulDiv(balance, priceUSD, 10 ** IERC20Metadata(token).decimals()); |
| 719 | } |
|
| 720 | } |
|
| 721 | ||
| 722 | 0 | uint256 totalSupply_ = totalSupply(); |
| 723 | // PPS = Total Value in USD / Total Supply, normalized to PRECISION |
|
| 724 | 0 | if (totalSupply_ == 0) { |
| 725 | 0 | pps = PRECISION; |
| 726 | } else { |
|
| 727 | 0 | pps = Math.mulDiv(totalValueUSD, PRECISION, totalSupply_); |
| 728 | } |
|
| 729 | } |
|
| 730 | ||
| 731 | /// @inheritdoc ISuperAsset |
|
| 732 | 0 | function getAllocations() |
| 733 | external |
|
| 734 | view |
|
| 735 | returns ( |
|
| 736 | 0 | uint256[] memory absoluteCurrentAllocation, |
| 737 | 0 | uint256 totalCurrentAllocation, |
| 738 | 0 | uint256[] memory absoluteTargetAllocation, |
| 739 | 0 | uint256 totalTargetAllocation |
| 740 | ) |
|
| 741 | 0 | { |
| 742 | 0 | uint256 length = _supportedAssets.length(); |
| 743 | 0 | absoluteCurrentAllocation = new uint256[](length); |
| 744 | 0 | absoluteTargetAllocation = new uint256[](length); |
| 745 | 0 | for (uint256 i; i < length; i++) { |
| 746 | 0 | address vault = _supportedAssets.at(i); |
| 747 | 0 | absoluteCurrentAllocation[i] = IERC20(vault).balanceOf(address(this)); |
| 748 | 0 | totalCurrentAllocation += absoluteCurrentAllocation[i]; |
| 749 | 0 | absoluteTargetAllocation[i] = tokenData[vault].targetAllocations; |
| 750 | 0 | totalTargetAllocation += absoluteTargetAllocation[i]; |
| 751 | } |
|
| 752 | } |
|
| 753 | ||
| 754 | /// @inheritdoc ISuperAsset |
|
| 755 | 0 | function getAllocationsPrePostOperationDeposit( |
| 756 | address token, |
|
| 757 | uint256 deltaToken, |
|
| 758 | uint256 amountToken, |
|
| 759 | bool isSoft |
|
| 760 | ) |
|
| 761 | public |
|
| 762 | view |
|
| 763 | 0 | returns (ISuperAsset.AllocationOperationReturnVars memory ret) |
| 764 | 0 | { |
| 765 | 0 | GetAllocationsPrePostOperationsDeposit memory s; |
| 766 | ||
| 767 | 0 | s.extendedLength = _supportedAssets.length(); |
| 768 | // Initialize the arrays in the return struct |
|
| 769 | 0 | ret.absoluteAllocationPreOperation = new uint256[](s.extendedLength); |
| 770 | 0 | ret.absoluteAllocationPostOperation = new uint256[](s.extendedLength); |
| 771 | 0 | ret.absoluteTargetAllocation = new uint256[](s.extendedLength); |
| 772 | 0 | ret.vaultWeights = new uint256[](s.extendedLength); |
| 773 | 0 | s.totalValueUSD = 0; |
| 774 | 0 | s.priceUSDToken = 0; |
| 775 | ||
| 776 | 0 | for (uint256 i; i < s.extendedLength; i++) { |
| 777 | 0 | s.token = _supportedAssets.at(i); |
| 778 | 0 | (ret.oraclePriceUSD, ret.isDepeg, ret.isDispersion, ret.isOracleOff) = getPriceAndCircuitBreakers(s.token); |
| 779 | ||
| 780 | 0 | if (!isSoft && (ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) { |
| 781 | // Return early with circuit breaker information |
|
| 782 | 0 | ret.assetWithBreakerTriggered = s.token; |
| 783 | 0 | return ret; |
| 784 | } |
|
| 785 | ||
| 786 | 0 | s.balance = IERC20(s.token).balanceOf(address(this)); |
| 787 | 0 | uint256 decimals = IERC20Metadata(s.token).decimals(); |
| 788 | 0 | if (s.balance > 0) { |
| 789 | 0 | s.totalValueUSD += Math.mulDiv(s.balance, ret.oraclePriceUSD, 10 ** decimals); |
| 790 | } |
|
| 791 | // Convert balance to USD value using price |
|
| 792 | 0 | ret.absoluteAllocationPreOperation[i] = Math.mulDiv(s.balance, ret.oraclePriceUSD, 10 ** decimals); |
| 793 | 0 | ret.totalAllocationPreOperation += ret.absoluteAllocationPreOperation[i]; |
| 794 | 0 | ret.absoluteAllocationPostOperation[i] = ret.absoluteAllocationPreOperation[i]; |
| 795 | 0 | if (s.token == token) { |
| 796 | 0 | s.priceUSDToken = ret.oraclePriceUSD; |
| 797 | 0 | ret.tokenFound = true; |
| 798 | 0 | s.absDeltaValue = Math.mulDiv(deltaToken, s.priceUSDToken, 10 ** decimals); |
| 799 | 0 | s.deltaValue = int256(s.absDeltaValue); |
| 800 | 0 | ret.absoluteAllocationPostOperation[i] = |
| 801 | 0 | uint256(int256(ret.absoluteAllocationPreOperation[i]) + s.deltaValue); |
| 802 | } |
|
| 803 | 0 | ret.totalAllocationPostOperation += ret.absoluteAllocationPostOperation[i]; |
| 804 | 0 | ret.absoluteTargetAllocation[i] = tokenData[s.token].targetAllocations; |
| 805 | 0 | ret.totalTargetAllocation += ret.absoluteTargetAllocation[i]; |
| 806 | 0 | ret.vaultWeights[i] = tokenData[s.token].weights; |
| 807 | } |
|
| 808 | ||
| 809 | 0 | uint256 superAssetPPS; |
| 810 | 0 | uint256 totalSupply_ = totalSupply(); |
| 811 | // PPS = Total Value in USD / Total Supply, normalized to PRECISION |
|
| 812 | 0 | if (totalSupply_ == 0) { |
| 813 | 0 | superAssetPPS = PRECISION; |
| 814 | } else { |
|
| 815 | 0 | superAssetPPS = Math.mulDiv(s.totalValueUSD, PRECISION, totalSupply_); |
| 816 | } |
|
| 817 | ||
| 818 | 0 | ret.amountAssets = Math.mulDiv(amountToken, s.priceUSDToken, superAssetPPS); |
| 819 | ||
| 820 | 0 | uint8 decimalsToken = IERC20Metadata(token).decimals(); |
| 821 | ||
| 822 | // Adjust for decimals |
|
| 823 | 0 | if (decimalsToken < DECIMALS) { |
| 824 | 0 | ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (DECIMALS - decimalsToken), PRECISION); |
| 825 | 0 | } else if (decimalsToken > DECIMALS) { |
| 826 | 0 | ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (decimalsToken - DECIMALS), PRECISION); |
| 827 | } |
|
| 828 | } |
|
| 829 | ||
| 830 | /// @inheritdoc ISuperAsset |
|
| 831 | 0 | function getAllocationsPrePostOperationRedeem( |
| 832 | address token, |
|
| 833 | uint256 amountToken, |
|
| 834 | bool isSoft |
|
| 835 | ) |
|
| 836 | public |
|
| 837 | view |
|
| 838 | 0 | returns (ISuperAsset.AllocationOperationReturnVars memory ret) |
| 839 | 0 | { |
| 840 | // 1. if deposit, deltaToken is amountTokenToDeposit (that the user sent) |
|
| 841 | // 2. however, if redeem, all prices are fetched first (priceUSD of token out and superAsset PPS) |
|
| 842 | // 2.1 then, these prices are used to calculate amountTokenOutBeforeFees, which is the delta token |
|
| 843 | 0 | GetAllocationsPrePostOperationsRedeem memory s; |
| 844 | ||
| 845 | 0 | s.extendedLength = _supportedAssets.length(); |
| 846 | 0 | s.oraclePriceUSDs = new uint256[](s.extendedLength); |
| 847 | 0 | s.balances = new uint256[](s.extendedLength); |
| 848 | 0 | s.decimals = new uint256[](s.extendedLength); |
| 849 | 0 | s.isDepegs = new bool[](s.extendedLength); |
| 850 | 0 | s.isDispersions = new bool[](s.extendedLength); |
| 851 | 0 | s.isOracleOffs = new bool[](s.extendedLength); |
| 852 | 0 | s.totalValueUSD = 0; |
| 853 | 0 | s.priceUSDToken = 0; |
| 854 | ||
| 855 | 0 | for (uint256 i; i < s.extendedLength; i++) { |
| 856 | 0 | s.token = _supportedAssets.at(i); |
| 857 | 0 | (s.oraclePriceUSDs[i], s.isDepegs[i], s.isDispersions[i], s.isOracleOffs[i]) = |
| 858 | 0 | getPriceAndCircuitBreakers(s.token); |
| 859 | ||
| 860 | 0 | if (!isSoft && (s.isDepegs[i] || s.isDispersions[i] || s.isOracleOffs[i] || s.oraclePriceUSDs[i] == 0)) { |
| 861 | // Return early with circuit breaker information |
|
| 862 | 0 | ret.assetWithBreakerTriggered = s.token; |
| 863 | 0 | ret.oraclePriceUSD = s.oraclePriceUSDs[i]; |
| 864 | 0 | ret.isDepeg = s.isDepegs[i]; |
| 865 | 0 | ret.isDispersion = s.isDispersions[i]; |
| 866 | 0 | ret.isOracleOff = s.isOracleOffs[i]; |
| 867 | 0 | return ret; |
| 868 | } |
|
| 869 | ||
| 870 | 0 | s.balances[i] = IERC20(s.token).balanceOf(address(this)); |
| 871 | 0 | s.decimals[i] = IERC20Metadata(s.token).decimals(); |
| 872 | 0 | if (s.balances[i] > 0) { |
| 873 | 0 | s.totalValueUSD += Math.mulDiv(s.balances[i], s.oraclePriceUSDs[i], 10 ** s.decimals[i]); |
| 874 | } |
|
| 875 | 0 | if (s.token == token) { |
| 876 | 0 | s.priceUSDToken = s.oraclePriceUSDs[i]; |
| 877 | 0 | ret.tokenFound = true; |
| 878 | } |
|
| 879 | ||
| 880 | 0 | if (i == s.extendedLength - 1) { |
| 881 | // Assign critical info to re-assure no breaker triggered |
|
| 882 | 0 | ret.assetWithBreakerTriggered = s.token; |
| 883 | 0 | ret.oraclePriceUSD = s.oraclePriceUSDs[i]; |
| 884 | 0 | ret.isDepeg = s.isDepegs[i]; |
| 885 | 0 | ret.isDispersion = s.isDispersions[i]; |
| 886 | 0 | ret.isOracleOff = s.isOracleOffs[i]; |
| 887 | } |
|
| 888 | } |
|
| 889 | ||
| 890 | // Initialize the arrays in the return struct |
|
| 891 | 0 | ret.absoluteAllocationPreOperation = new uint256[](s.extendedLength); |
| 892 | 0 | ret.absoluteAllocationPostOperation = new uint256[](s.extendedLength); |
| 893 | 0 | ret.absoluteTargetAllocation = new uint256[](s.extendedLength); |
| 894 | 0 | ret.vaultWeights = new uint256[](s.extendedLength); |
| 895 | ||
| 896 | 0 | uint256 totalSupply_ = totalSupply(); |
| 897 | // PPS = Total Value in USD / Total Supply, normalized to PRECISION |
|
| 898 | 0 | if (totalSupply_ == 0) { |
| 899 | 0 | s.superAssetPPS = PRECISION; |
| 900 | } else { |
|
| 901 | 0 | s.superAssetPPS = Math.mulDiv(s.totalValueUSD, PRECISION, totalSupply_); |
| 902 | } |
|
| 903 | ||
| 904 | 0 | ret.amountAssets = Math.mulDiv(amountToken, s.superAssetPPS, s.priceUSDToken); |
| 905 | ||
| 906 | 0 | s.decimalsToken = IERC20Metadata(token).decimals(); |
| 907 | ||
| 908 | // Adjust for decimals |
|
| 909 | 0 | if (s.decimalsToken < DECIMALS) { |
| 910 | 0 | ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (DECIMALS - s.decimalsToken), PRECISION); |
| 911 | 0 | } else if (s.decimalsToken > DECIMALS) { |
| 912 | 0 | ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (s.decimalsToken - DECIMALS), PRECISION); |
| 913 | } |
|
| 914 | ||
| 915 | 0 | s.balanceOfDeltaToken = IERC20(token).balanceOf(address(this)); |
| 916 | 0 | if (ret.amountAssets > s.balanceOfDeltaToken) { |
| 917 | // NOTE: Since we do not want this function to revert, we re-set the amount out to the max possible amount |
|
| 918 | // out which is the balance of this token |
|
| 919 | // NOTE: This should be OK since the user can control the min amount out they desire with the slippage |
|
| 920 | // protection |
|
| 921 | 0 | s.deltaToken = s.balanceOfDeltaToken; |
| 922 | } else { |
|
| 923 | 0 | s.deltaToken = ret.amountAssets; |
| 924 | } |
|
| 925 | ||
| 926 | 0 | for (uint256 i; i < s.extendedLength; i++) { |
| 927 | 0 | s.token = _supportedAssets.at(i); |
| 928 | // Convert balance to USD value using price |
|
| 929 | 0 | ret.absoluteAllocationPreOperation[i] = |
| 930 | 0 | Math.mulDiv(s.balances[i], s.oraclePriceUSDs[i], 10 ** s.decimals[i]); |
| 931 | 0 | ret.totalAllocationPreOperation += ret.absoluteAllocationPreOperation[i]; |
| 932 | 0 | ret.absoluteAllocationPostOperation[i] = ret.absoluteAllocationPreOperation[i]; |
| 933 | 0 | if (s.token == token) { |
| 934 | 0 | s.absDeltaValue = Math.mulDiv(s.deltaToken, s.oraclePriceUSDs[i], 10 ** s.decimals[i]); |
| 935 | 0 | s.deltaValue = -int256(s.absDeltaValue); |
| 936 | 0 | ret.absoluteAllocationPostOperation[i] = |
| 937 | 0 | uint256(int256(ret.absoluteAllocationPreOperation[i]) + s.deltaValue); |
| 938 | } |
|
| 939 | 0 | ret.totalAllocationPostOperation += ret.absoluteAllocationPostOperation[i]; |
| 940 | 0 | ret.absoluteTargetAllocation[i] = tokenData[s.token].targetAllocations; |
| 941 | 0 | ret.totalTargetAllocation += ret.absoluteTargetAllocation[i]; |
| 942 | 0 | ret.vaultWeights[i] = tokenData[s.token].weights; |
| 943 | } |
|
| 944 | } |
|
| 945 | ||
| 946 | /// @inheritdoc ISuperAsset |
|
| 947 | 0 | function getPrimaryAsset() external view returns (address) { |
| 948 | 0 | return primaryAsset; |
| 949 | } |
|
| 950 | ||
| 951 | /*////////////////////////////////////////////////////////////// |
|
| 952 | ERC20 OVERRIDES |
|
| 953 | //////////////////////////////////////////////////////////////*/ |
|
| 954 | /// @inheritdoc ERC20 |
|
| 955 | ||
| 956 | 0 | function name() public view override returns (string memory) { |
| 957 | 0 | return tokenName; |
| 958 | } |
|
| 959 | ||
| 960 | /// @inheritdoc ERC20 |
|
| 961 | 0 | function symbol() public view override returns (string memory) { |
| 962 | 0 | return tokenSymbol; |
| 963 | } |
|
| 964 | ||
| 965 | /*////////////////////////////////////////////////////////////// |
|
| 966 | INTERNAL FUNCTIONS |
|
| 967 | //////////////////////////////////////////////////////////////*/ |
|
| 968 | /// @dev Settles incentives for a user |
|
| 969 | /// @param user The address of the user to settle incentives for |
|
| 970 | /// @param amountIncentiveUSD The amount of incentives to settle |
|
| 971 | 0 | function _settleIncentive(address user, int256 amountIncentiveUSD) internal { |
| 972 | // Pay or take incentives based on the sign of amountIncentive |
|
| 973 | 0 | if (amountIncentiveUSD > 0) { |
| 974 | 0 | IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).payIncentive( |
| 975 | user, uint256(amountIncentiveUSD) |
|
| 976 | ); |
|
| 977 | 0 | } else if (amountIncentiveUSD < 0) { |
| 978 | 0 | IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).takeIncentive( |
| 979 | 0 | user, uint256(-amountIncentiveUSD) |
| 980 | ); |
|
| 981 | } |
|
| 982 | } |
|
| 983 | ||
| 984 | // --- Modifiers --- |
|
| 985 | 0 | function _onlyStrategist() internal view { |
| 986 | 0 | if (msg.sender != factory.getSuperAssetStrategist(address(this))) revert UNAUTHORIZED(); |
| 987 | } |
|
| 988 | ||
| 989 | 0 | function _onlyManager() internal view { |
| 990 | 0 | if (msg.sender != factory.getSuperAssetManager(address(this))) revert UNAUTHORIZED(); |
| 991 | } |
|
| 992 | } |
0%
src/SuperAsset/SuperAssetFactory.sol
Lines covered: 0 / 70 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; |
|
| 5 | import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol"; |
|
| 6 | import { SuperAsset } from "./SuperAsset.sol"; |
|
| 7 | import { IncentiveFundContract } from "./IncentiveFundContract.sol"; |
|
| 8 | import { IncentiveCalculationContract } from "./IncentiveCalculationContract.sol"; |
|
| 9 | ||
| 10 | /** |
|
| 11 | * @title SuperAssetFactory |
|
| 12 | * @author Superform Labs |
|
| 13 | * @notice Factory contract that deploys SuperAsset and its dependencies |
|
| 14 | */ |
|
| 15 | 0 | contract SuperAssetFactory is ISuperAssetFactory { |
| 16 | using Clones for address; |
|
| 17 | ||
| 18 | /*////////////////////////////////////////////////////////////// |
|
| 19 | STATE |
|
| 20 | //////////////////////////////////////////////////////////////*/ |
|
| 21 | 0 | address public immutable superAssetImplementation; |
| 22 | 0 | address public immutable incentiveFundImplementation; |
| 23 | // Single instances |
|
| 24 | 0 | address public immutable superGovernor; |
| 25 | ||
| 26 | 0 | mapping(address superAsset => SuperAssetData data) public data; |
| 27 | 0 | mapping(address icc => bool isValid) public incentiveCalculationContractsWhitelist; |
| 28 | ||
| 29 | /*////////////////////////////////////////////////////////////// |
|
| 30 | CONSTRUCTOR |
|
| 31 | //////////////////////////////////////////////////////////////*/ |
|
| 32 | 0 | constructor(address _superGovernor) { |
| 33 | 0 | if (_superGovernor == address(0)) revert ZERO_ADDRESS(); |
| 34 | 0 | superGovernor = _superGovernor; |
| 35 | ||
| 36 | 0 | superAssetImplementation = address(new SuperAsset()); |
| 37 | 0 | incentiveFundImplementation = address(new IncentiveFundContract()); |
| 38 | } |
|
| 39 | ||
| 40 | /// @inheritdoc ISuperAssetFactory |
|
| 41 | 0 | function addICCToWhitelist(address icc) external { |
| 42 | 0 | if (msg.sender != superGovernor) revert UNAUTHORIZED(); |
| 43 | 0 | incentiveCalculationContractsWhitelist[icc] = true; |
| 44 | } |
|
| 45 | ||
| 46 | /// @inheritdoc ISuperAssetFactory |
|
| 47 | 0 | function removeICCFromWhitelist(address icc) external { |
| 48 | 0 | if (msg.sender != superGovernor) revert UNAUTHORIZED(); |
| 49 | 0 | incentiveCalculationContractsWhitelist[icc] = false; |
| 50 | } |
|
| 51 | ||
| 52 | /// @inheritdoc ISuperAssetFactory |
|
| 53 | 0 | function isICCWhitelisted(address icc) external view returns (bool) { |
| 54 | 0 | return incentiveCalculationContractsWhitelist[icc]; |
| 55 | } |
|
| 56 | ||
| 57 | /// @inheritdoc ISuperAssetFactory |
|
| 58 | 0 | function setSuperAssetManager(address superAsset, address _superAssetManager) external { |
| 59 | 0 | if (_superAssetManager == address(0)) revert ZERO_ADDRESS(); |
| 60 | 0 | if ( |
| 61 | 0 | (msg.sender != data[superAsset].superAssetManager) && (msg.sender != superGovernor) // NOTE: This role can |
| 62 | // take over |
|
| 63 | 0 | ) revert UNAUTHORIZED(); |
| 64 | 0 | data[superAsset].superAssetManager = _superAssetManager; |
| 65 | } |
|
| 66 | ||
| 67 | /// @inheritdoc ISuperAssetFactory |
|
| 68 | 0 | function setSuperAssetStrategist(address superAsset, address _superAssetStrategist) external { |
| 69 | 0 | if (_superAssetStrategist == address(0)) revert ZERO_ADDRESS(); |
| 70 | 0 | if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED(); |
| 71 | 0 | data[superAsset].superAssetStrategist = _superAssetStrategist; |
| 72 | } |
|
| 73 | ||
| 74 | /// @inheritdoc ISuperAssetFactory |
|
| 75 | 0 | function setIncentiveFundManager(address superAsset, address _incentiveFundManager) external { |
| 76 | 0 | if (_incentiveFundManager == address(0)) revert ZERO_ADDRESS(); |
| 77 | 0 | if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED(); |
| 78 | 0 | data[superAsset].incentiveFundManager = _incentiveFundManager; |
| 79 | } |
|
| 80 | ||
| 81 | /// @inheritdoc ISuperAssetFactory |
|
| 82 | 0 | function setIncentiveCalculationContract(address superAsset, address _incentiveCalculationContract) external { |
| 83 | 0 | if (_incentiveCalculationContract == address(0)) revert ZERO_ADDRESS(); |
| 84 | 0 | if (!incentiveCalculationContractsWhitelist[_incentiveCalculationContract]) revert ICC_NOT_WHITELISTED(); |
| 85 | 0 | if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED(); |
| 86 | 0 | data[superAsset].incentiveCalculationContract = _incentiveCalculationContract; |
| 87 | } |
|
| 88 | ||
| 89 | /// @inheritdoc ISuperAssetFactory |
|
| 90 | 0 | function getSuperAssetManager(address superAsset) external view returns (address) { |
| 91 | 0 | return data[superAsset].superAssetManager; |
| 92 | } |
|
| 93 | ||
| 94 | /// @inheritdoc ISuperAssetFactory |
|
| 95 | 0 | function getSuperAssetStrategist(address superAsset) external view returns (address) { |
| 96 | 0 | return data[superAsset].superAssetStrategist; |
| 97 | } |
|
| 98 | ||
| 99 | /// @inheritdoc ISuperAssetFactory |
|
| 100 | 0 | function getIncentiveFundManager(address superAsset) external view returns (address) { |
| 101 | 0 | return data[superAsset].incentiveFundManager; |
| 102 | } |
|
| 103 | ||
| 104 | /// @inheritdoc ISuperAssetFactory |
|
| 105 | 0 | function getIncentiveCalculationContract(address superAsset) external view returns (address) { |
| 106 | 0 | return data[superAsset].incentiveCalculationContract; |
| 107 | } |
|
| 108 | ||
| 109 | /// @inheritdoc ISuperAssetFactory |
|
| 110 | 0 | function getIncentiveFundContract(address superAsset) external view returns (address) { |
| 111 | 0 | return data[superAsset].incentiveFundContract; |
| 112 | } |
|
| 113 | ||
| 114 | /*////////////////////////////////////////////////////////////// |
|
| 115 | EXTERNAL FUNCTIONS |
|
| 116 | //////////////////////////////////////////////////////////////*/ |
|
| 117 | /// @inheritdoc ISuperAssetFactory |
|
| 118 | 0 | function createSuperAsset(AssetCreationParams calldata params) |
| 119 | external |
|
| 120 | 0 | returns (address superAsset, address incentiveFundContract) |
| 121 | { |
|
| 122 | // TODO: Decide whether to make this method permissionless or permissioned |
|
| 123 | ||
| 124 | 0 | if (params.incentiveCalculationContract == address(0)) revert ZERO_ADDRESS(); |
| 125 | 0 | if (!incentiveCalculationContractsWhitelist[params.incentiveCalculationContract]) revert ICC_NOT_WHITELISTED(); |
| 126 | ||
| 127 | // Deploy IncentiveFund (this one needs to be unique per SuperAsset) |
|
| 128 | 0 | incentiveFundContract = incentiveFundImplementation.clone(); |
| 129 | ||
| 130 | // Deploy SuperAsset with its dependencies |
|
| 131 | 0 | superAsset = superAssetImplementation.clone(); |
| 132 | 0 | SuperAsset(superAsset).initialize( |
| 133 | 0 | params.name, |
| 134 | 0 | params.symbol, |
| 135 | 0 | params.asset, |
| 136 | 0 | superGovernor, |
| 137 | 0 | params.swapFeeInPercentage, |
| 138 | 0 | params.swapFeeOutPercentage |
| 139 | ); |
|
| 140 | ||
| 141 | // Initialize IncentiveFund |
|
| 142 | 0 | IncentiveFundContract(incentiveFundContract).initialize( |
| 143 | 0 | superGovernor, superAsset, params.tokenInIncentive, params.tokenOutIncentive |
| 144 | ); |
|
| 145 | ||
| 146 | 0 | data[superAsset] = SuperAssetData({ |
| 147 | 0 | superAssetManager: params.superAssetManager, |
| 148 | 0 | superAssetStrategist: params.superAssetStrategist, |
| 149 | 0 | incentiveFundManager: params.incentiveFundManager, |
| 150 | 0 | incentiveCalculationContract: params.incentiveCalculationContract, |
| 151 | incentiveFundContract: incentiveFundContract |
|
| 152 | }); |
|
| 153 | ||
| 154 | 0 | emit SuperAssetCreated( |
| 155 | 0 | superAsset, incentiveFundContract, params.incentiveCalculationContract, params.name, params.symbol |
| 156 | ); |
|
| 157 | } |
|
| 158 | } |
0%
src/SuperBank.sol
Lines covered: 0 / 27 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; |
|
| 8 | ||
| 9 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { ISuperBank } from "./interfaces/ISuperBank.sol"; |
|
| 13 | import { ISuperGovernor, FeeType } from "./interfaces/ISuperGovernor.sol"; |
|
| 14 | import { Bank } from "./Bank.sol"; |
|
| 15 | ||
| 16 | /// @title SuperBank |
|
| 17 | /// @notice Compounds protocol revenue into UP and distributes it to sUP and treasury. |
|
| 18 | 0 | contract SuperBank is ISuperBank, Bank { |
| 19 | using SafeERC20 for IERC20; |
|
| 20 | using Math for uint256; |
|
| 21 | ||
| 22 | 0 | uint256 private constant BPS_MAX = 10_000; |
| 23 | 0 | ISuperGovernor public immutable SUPER_GOVERNOR; |
| 24 | ||
| 25 | 0 | constructor(address superGovernor_) { |
| 26 | 0 | if (superGovernor_ == address(0)) revert INVALID_ADDRESS(); |
| 27 | 0 | SUPER_GOVERNOR = ISuperGovernor(superGovernor_); |
| 28 | } |
|
| 29 | ||
| 30 | modifier onlyBankManager() { |
|
| 31 | 0 | if (!IAccessControl(address(SUPER_GOVERNOR)).hasRole(SUPER_GOVERNOR.BANK_MANAGER_ROLE(), msg.sender)) { |
| 32 | 0 | revert INVALID_BANK_MANAGER(); |
| 33 | } |
|
| 34 | _; |
|
| 35 | } |
|
| 36 | ||
| 37 | /*////////////////////////////////////////////////////////////// |
|
| 38 | EXTERNAL FUNCTIONS |
|
| 39 | //////////////////////////////////////////////////////////////*/ |
|
| 40 | ||
| 41 | // Receive function to accept direct ETH transfers if needed for hooks/executions |
|
| 42 | receive() external payable { } |
|
| 43 | ||
| 44 | /// @inheritdoc ISuperBank |
|
| 45 | 0 | function distribute(uint256 upAmount) external onlyBankManager { |
| 46 | 0 | if (upAmount == 0) revert ZERO_LENGTH_ARRAY(); |
| 47 | ||
| 48 | // Get UP token address from SuperGovernor |
|
| 49 | 0 | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 50 | 0 | address supToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUP()); |
| 51 | 0 | address treasury = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.TREASURY()); |
| 52 | ||
| 53 | // Get revenue share percentage from SuperGovernor |
|
| 54 | 0 | uint256 revenueShare = SUPER_GOVERNOR.getFee(FeeType.REVENUE_SHARE); |
| 55 | ||
| 56 | // Calculate amounts for sUP and Treasury |
|
| 57 | 0 | uint256 supAmount = upAmount.mulDiv(revenueShare, BPS_MAX); |
| 58 | ||
| 59 | 0 | uint256 treasuryAmount = upAmount - supAmount; |
| 60 | ||
| 61 | // Get the UP token instance |
|
| 62 | 0 | IERC20 up = IERC20(upToken); |
| 63 | ||
| 64 | // Ensure we have the tokens |
|
| 65 | 0 | if (up.balanceOf(address(this)) < upAmount) revert INVALID_UP_AMOUNT_TO_DISTRIBUTE(); |
| 66 | ||
| 67 | // Transfer tokens to sUP and Treasury |
|
| 68 | 0 | if (supAmount > 0) { |
| 69 | 0 | up.safeTransfer(supToken, supAmount); |
| 70 | } |
|
| 71 | ||
| 72 | 0 | if (treasuryAmount > 0) { |
| 73 | 0 | up.safeTransfer(treasury, treasuryAmount); |
| 74 | } |
|
| 75 | ||
| 76 | 0 | emit RevenueDistributed(upToken, supToken, treasury, supAmount, treasuryAmount); |
| 77 | } |
|
| 78 | ||
| 79 | /// @inheritdoc ISuperBank |
|
| 80 | 0 | function executeHooks(ISuperBank.HookExecutionData calldata executionData) external payable onlyBankManager { |
| 81 | 0 | _executeHooks(executionData); |
| 82 | } |
|
| 83 | ||
| 84 | /*////////////////////////////////////////////////////////////// |
|
| 85 | INTERNAL FUNCTIONS |
|
| 86 | //////////////////////////////////////////////////////////////*/ |
|
| 87 | 0 | function _getMerkleRootForHook(address hookAddress) internal view override returns (bytes32) { |
| 88 | 0 | return SUPER_GOVERNOR.getSuperBankHookMerkleRoot(hookAddress); |
| 89 | } |
|
| 90 | } |
24%
src/SuperGovernor.sol
Lines covered: 112 / 457 (24%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; |
|
| 6 | import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
|
| 7 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 8 | ||
| 9 | // Superform |
|
| 10 | import { ISuperGovernor, FeeType } from "./interfaces/ISuperGovernor.sol"; |
|
| 11 | import { ISuperVaultAggregator } from "./interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 12 | import { ISuperAssetFactory } from "./interfaces/SuperAsset/ISuperAssetFactory.sol"; |
|
| 13 | import { ISuperOracle } from "./interfaces/oracles/ISuperOracle.sol"; |
|
| 14 | import { ISuperOracleL2 } from "./interfaces/oracles/ISuperOracleL2.sol"; |
|
| 15 | ||
| 16 | /// @title SuperGovernor |
|
| 17 | /// @author Superform Labs |
|
| 18 | /// @notice Central registry for all deployed contracts in the Superform periphery |
|
| 19 | 1254× | contract SuperGovernor is ISuperGovernor, AccessControl { |
| 20 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 21 | ||
| 22 | /*////////////////////////////////////////////////////////////// |
|
| 23 | STORAGE |
|
| 24 | //////////////////////////////////////////////////////////////*/ |
|
| 25 | // Address registry |
|
| 26 | mapping(bytes32 id => address address_) private _addressRegistry; |
|
| 27 | ||
| 28 | // PPS Oracle management |
|
| 29 | // Active PPS Oracle quorum requirement |
|
| 30 | uint256 private _activePPSOracleQuorum; |
|
| 31 | // Current active PPS Oracle |
|
| 32 | address private _activePPSOracle; |
|
| 33 | // Proposed new active PPS Oracle |
|
| 34 | address private _proposedActivePPSOracle; |
|
| 35 | // Effective time for proposed active PPS Oracle change |
|
| 36 | uint256 private _activePPSOracleEffectiveTime; |
|
| 37 | ||
| 38 | // Hook registry |
|
| 39 | EnumerableSet.AddressSet private _registeredHooks; |
|
| 40 | EnumerableSet.AddressSet private _registeredFulfillRequestsHooks; |
|
| 41 | ||
| 42 | // SuperBank Hook Target validation |
|
| 43 | mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private superBankHooksMerkleRoots; |
|
| 44 | ||
| 45 | // VaultBank registry |
|
| 46 | EnumerableSet.AddressSet private _vaultBanks; |
|
| 47 | mapping(uint64 chainId => address vaultBank) private _vaultBanksByChainId; |
|
| 48 | ||
| 49 | // VaultBank Hook Target validation |
|
| 50 | mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private vaultBankHooksMerkleRoots; |
|
| 51 | ||
| 52 | // Global freeze for manager takeovers |
|
| 53 | bool private _managerTakeoversFrozen; |
|
| 54 | ||
| 55 | // Validator registry |
|
| 56 | EnumerableSet.AddressSet private _validators; |
|
| 57 | ||
| 58 | // Relayer registry |
|
| 59 | EnumerableSet.AddressSet private _relayers; |
|
| 60 | ||
| 61 | // Protected keepers registry (cannot be added as authorized callers by managers) |
|
| 62 | EnumerableSet.AddressSet private _protectedKeepers; |
|
| 63 | ||
| 64 | // Executor registry |
|
| 65 | EnumerableSet.AddressSet private _executors; |
|
| 66 | ||
| 67 | // Polymer prover |
|
| 68 | address private _prover; |
|
| 69 | ||
| 70 | // Whitelisted incentive tokens |
|
| 71 | mapping(address token => bool isWhitelisted) private _isWhitelistedIncentiveToken; |
|
| 72 | EnumerableSet.AddressSet private _proposedWhitelistedIncentiveTokens; |
|
| 73 | EnumerableSet.AddressSet private _proposedRemoveWhitelistedIncentiveTokens; |
|
| 74 | uint256 private _proposedAddWhitelistedIncentiveTokensEffectiveTime; |
|
| 75 | uint256 private _proposedRemoveWhitelistedIncentiveTokensEffectiveTime; |
|
| 76 | ||
| 77 | // Fee management |
|
| 78 | // Current fee values |
|
| 79 | mapping(FeeType type_ => uint256 value) private _feeValues; |
|
| 80 | // Proposed fee values |
|
| 81 | mapping(FeeType type_ => uint256 proposedValue) private _proposedFeeValues; |
|
| 82 | // Effective times for proposed fee updates |
|
| 83 | mapping(FeeType type_ => uint256 effectiveTime) private _feeEffectiveTimes; |
|
| 84 | ||
| 85 | mapping(address _oracle => GasInfo info) private _oracleGasInfo; |
|
| 86 | ||
| 87 | // Upkeep control |
|
| 88 | bool private _upkeepPaymentsEnabled; |
|
| 89 | bool private _proposedUpkeepPaymentsEnabled; |
|
| 90 | uint256 private _upkeepPaymentsChangeEffectiveTime; |
|
| 91 | ||
| 92 | // Superform managers (exempt from upkeep costs) |
|
| 93 | EnumerableSet.AddressSet private _superformManagers; |
|
| 94 | ||
| 95 | // Min staleness configuration to prevent maxStaleness from being set too low |
|
| 96 | uint256 private _minStaleness; |
|
| 97 | uint256 private _proposedMinStaleness; |
|
| 98 | uint256 private _minStalenesEffectiveTime; |
|
| 99 | ||
| 100 | // Oracle constants |
|
| 101 | 0 | address private constant NATIVE_TOKEN = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); |
| 102 | 0 | address private constant USD_TOKEN = address(840); |
| 103 | address private constant GAS_QUOTE = |
|
| 104 | address(uint160(uint256(keccak256("GAS_QUOTE")))); |
|
| 105 | address private constant GWEI_QUOTE = |
|
| 106 | address(uint160(uint256(keccak256("GWEI_QUOTE")))); |
|
| 107 | 0 | bytes32 private constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
| 108 | ||
| 109 | // Timelock configuration |
|
| 110 | 4× | uint256 private constant TIMELOCK = 7 days; |
| 111 | 1× | uint256 private constant BPS_MAX = 10_000; // 100% in basis points |
| 112 | ||
| 113 | // Role definitions |
|
| 114 | bytes32 private constant _SUPER_GOVERNOR_ROLE = keccak256("SUPER_GOVERNOR_ROLE"); |
|
| 115 | bytes32 private constant _GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); |
|
| 116 | 0 | bytes32 private constant _BANK_MANAGER_ROLE = keccak256("BANK_MANAGER_ROLE"); |
| 117 | 0 | bytes32 private constant _GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); |
| 118 | bytes32 private constant _SUPER_ASSET_FACTORY = keccak256("SUPER_ASSET_FACTORY"); |
|
| 119 | 0 | bytes32 private constant _GAS_MANAGER_ROLE = keccak256("GAS_MANAGER_ROLE"); |
| 120 | ||
| 121 | // Common contract keys |
|
| 122 | 10× | bytes32 public constant UP = keccak256("UP"); |
| 123 | 0 | bytes32 public constant SUP = keccak256("SUP"); |
| 124 | 0 | bytes32 public constant TREASURY = keccak256("TREASURY"); |
| 125 | 0 | bytes32 public constant VAULT_BANK = keccak256("VAULT_BANK"); |
| 126 | 5× | bytes32 public constant SUPER_BANK = keccak256("SUPER_BANK"); |
| 127 | 0 | bytes32 public constant SUPER_ORACLE = keccak256("SUPER_ORACLE"); |
| 128 | 0 | bytes32 public constant BANK_MANAGER = keccak256("BANK_MANAGER"); |
| 129 | 0 | bytes32 public constant ECDSAPPSORACLE = keccak256("ECDSAPPSORACLE"); |
| 130 | 16× | bytes32 public constant SUPER_VAULT_AGGREGATOR = keccak256("SUPER_VAULT_AGGREGATOR"); |
| 131 | ||
| 132 | /*////////////////////////////////////////////////////////////// |
|
| 133 | CONSTRUCTOR |
|
| 134 | //////////////////////////////////////////////////////////////*/ |
|
| 135 | /// @notice Initializes the SuperGovernor contract |
|
| 136 | /// @param superGovernor Address of the default admin (will have SUPER_GOVERNOR_ROLE) |
|
| 137 | /// @param governor Address that will have the GOVERNOR_ROLE for daily operations |
|
| 138 | /// @param bankManager Address that will have the BANK_MANAGER_ROLE for daily operations |
|
| 139 | /// @param treasury_ Address of the treasury |
|
| 140 | /// @param prover_ Address of the prover |
|
| 141 | 0 | constructor(address superGovernor, address governor, address bankManager, address gasManager, address treasury_, address prover_) { |
| 142 | 0 | if ( |
| 143 | 0 | superGovernor == address(0) || treasury_ == address(0) || governor == address(0) |
| 144 | 0 | || bankManager == address(0) || prover_ == address(0) || gasManager == address(0) |
| 145 | 0 | ) revert INVALID_ADDRESS(); |
| 146 | ||
| 147 | // Set up roles |
|
| 148 | 0 | _grantRole(DEFAULT_ADMIN_ROLE, superGovernor); |
| 149 | 0 | _grantRole(_SUPER_GOVERNOR_ROLE, superGovernor); |
| 150 | 0 | _grantRole(_GOVERNOR_ROLE, governor); |
| 151 | 0 | _grantRole(_BANK_MANAGER_ROLE, bankManager); |
| 152 | 0 | _grantRole(_GAS_MANAGER_ROLE, gasManager); |
| 153 | // Setup GUARDIAN_ROLE without assigning any address |
|
| 154 | 0 | _setRoleAdmin(_GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE); |
| 155 | ||
| 156 | // Set role admins |
|
| 157 | 0 | _setRoleAdmin(_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
| 158 | 0 | _setRoleAdmin(_SUPER_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
| 159 | 0 | _setRoleAdmin(_BANK_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
| 160 | 0 | _setRoleAdmin(_GAS_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
| 161 | ||
| 162 | // Initialize with default fees |
|
| 163 | 0 | _feeValues[FeeType.REVENUE_SHARE] = 2000; // 20% revenue share |
| 164 | 0 | _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE] = 2000; // 20% performance fee |
| 165 | 0 | _feeValues[FeeType.SUPER_ASSET_SWAP_FEE] = 4000; // 40% swap fee |
| 166 | 0 | emit FeeUpdated(FeeType.REVENUE_SHARE, _feeValues[FeeType.REVENUE_SHARE]); |
| 167 | 0 | emit FeeUpdated(FeeType.SUPER_VAULT_PERFORMANCE_FEE, _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE]); |
| 168 | 0 | emit FeeUpdated(FeeType.SUPER_ASSET_SWAP_FEE, _feeValues[FeeType.SUPER_ASSET_SWAP_FEE]); |
| 169 | ||
| 170 | // Set treasury in address registry |
|
| 171 | 0 | _addressRegistry[TREASURY] = treasury_; |
| 172 | 0 | emit AddressSet(TREASURY, treasury_); |
| 173 | ||
| 174 | // Initialize minimum staleness (5 minutes to prevent extremely low staleness values) |
|
| 175 | 0 | _minStaleness = 300; // 5 minutes in seconds |
| 176 | ||
| 177 | // Initialize prover |
|
| 178 | 0 | _prover = prover_; |
| 179 | 0 | emit ProverSet(prover_); |
| 180 | } |
|
| 181 | ||
| 182 | /*////////////////////////////////////////////////////////////// |
|
| 183 | CONTRACT REGISTRY FUNCTIONS |
|
| 184 | //////////////////////////////////////////////////////////////*/ |
|
| 185 | /// @inheritdoc ISuperGovernor |
|
| 186 | 0 | function setAddress(bytes32 key, address value) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 187 | 0 | if (value == address(0)) revert INVALID_ADDRESS(); |
| 188 | ||
| 189 | 0 | _addressRegistry[key] = value; |
| 190 | 0 | emit AddressSet(key, value); |
| 191 | } |
|
| 192 | ||
| 193 | /*////////////////////////////////////////////////////////////// |
|
| 194 | PERIPHERY CONFIGURATIONS |
|
| 195 | //////////////////////////////////////////////////////////////*/ |
|
| 196 | /// @inheritdoc ISuperGovernor |
|
| 197 | 0 | function setProver(address prover) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 198 | 0 | if (prover == address(0)) revert INVALID_ADDRESS(); |
| 199 | ||
| 200 | 0 | _prover = prover; |
| 201 | 0 | emit ProverSet(prover); |
| 202 | } |
|
| 203 | ||
| 204 | /// @inheritdoc ISuperGovernor |
|
| 205 | 11× | function changePrimaryManager(address strategy, address newManager) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 206 | // Check if takeovers are globally frozen |
|
| 207 | 8× | if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN(); |
| 208 | ||
| 209 | 8× | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 210 | 3× | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 211 | ||
| 212 | // Call the interface method to change the manager |
|
| 213 | // This function can only be called by the SuperGovernor and bypasses the timelock |
|
| 214 | 13× | ISuperVaultAggregator(aggregator).changePrimaryManager(strategy, newManager); |
| 215 | } |
|
| 216 | ||
| 217 | /// @inheritdoc ISuperGovernor |
|
| 218 | 0 | function freezeManagerTakeover() external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 219 | 0 | if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN(); |
| 220 | ||
| 221 | // Set frozen status to true (permanent, cannot be undone) |
|
| 222 | 0 | _managerTakeoversFrozen = true; |
| 223 | ||
| 224 | // Emit event for the frozen status |
|
| 225 | 0 | emit ManagerTakeoversFrozen(); |
| 226 | } |
|
| 227 | ||
| 228 | /// @inheritdoc ISuperGovernor |
|
| 229 | 0 | function changeHooksRootUpdateTimelock(uint256 newTimelock) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 230 | 0 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 231 | 0 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 232 | ||
| 233 | // Call the SuperVaultAggregator to change the hooks root update timelock |
|
| 234 | 0 | ISuperVaultAggregator(aggregator).setHooksRootUpdateTimelock(newTimelock); |
| 235 | } |
|
| 236 | ||
| 237 | /// @inheritdoc ISuperGovernor |
|
| 238 | 11× | function proposeGlobalHooksRoot(bytes32 newRoot) external onlyRole(_GOVERNOR_ROLE) { |
| 239 | 8× | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 240 | 3× | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 241 | ||
| 242 | 13× | ISuperVaultAggregator(aggregator).proposeGlobalHooksRoot(newRoot); |
| 243 | } |
|
| 244 | ||
| 245 | /// @inheritdoc ISuperGovernor |
|
| 246 | 0 | function setGlobalHooksRootVetoStatus(bool vetoed) external onlyRole(_GUARDIAN_ROLE) { |
| 247 | 0 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 248 | 0 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 249 | ||
| 250 | 0 | ISuperVaultAggregator(aggregator).setGlobalHooksRootVetoStatus(vetoed); |
| 251 | } |
|
| 252 | ||
| 253 | /// @inheritdoc ISuperGovernor |
|
| 254 | 0 | function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external onlyRole(_GUARDIAN_ROLE) { |
| 255 | 0 | if (strategy == address(0)) revert INVALID_ADDRESS(); |
| 256 | ||
| 257 | 0 | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 258 | 0 | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 259 | ||
| 260 | 0 | ISuperVaultAggregator(aggregator).setStrategyHooksRootVetoStatus(strategy, vetoed); |
| 261 | } |
|
| 262 | ||
| 263 | /// @inheritdoc ISuperGovernor |
|
| 264 | 0 | function setSuperAssetManager(address superAsset, address superAssetManager) external onlyRole(_GOVERNOR_ROLE) { |
| 265 | 0 | if (superAsset == address(0) || superAssetManager == address(0)) revert INVALID_ADDRESS(); |
| 266 | 0 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 267 | 0 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 268 | 0 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 269 | 0 | factory.setSuperAssetManager(superAsset, superAssetManager); |
| 270 | } |
|
| 271 | ||
| 272 | /// @inheritdoc ISuperGovernor |
|
| 273 | 0 | function addICCToWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 274 | 0 | if (icc == address(0)) revert INVALID_ADDRESS(); |
| 275 | 0 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 276 | 0 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 277 | 0 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 278 | 0 | factory.addICCToWhitelist(icc); |
| 279 | } |
|
| 280 | ||
| 281 | /// @inheritdoc ISuperGovernor |
|
| 282 | 0 | function removeICCFromWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 283 | 0 | if (icc == address(0)) revert INVALID_ADDRESS(); |
| 284 | 0 | address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
| 285 | 0 | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 286 | 0 | ISuperAssetFactory factory = ISuperAssetFactory(value); |
| 287 | 0 | factory.removeICCFromWhitelist(icc); |
| 288 | } |
|
| 289 | ||
| 290 | /// @inheritdoc ISuperGovernor |
|
| 291 | 0 | function setOracleMaxStaleness(uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) { |
| 292 | 0 | if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 293 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 294 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 295 | ||
| 296 | 0 | ISuperOracle(oracle).setMaxStaleness(newMaxStaleness); |
| 297 | } |
|
| 298 | ||
| 299 | /// @inheritdoc ISuperGovernor |
|
| 300 | 5× | function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) { |
| 301 | 0 | if (feed == address(0)) revert INVALID_ADDRESS(); |
| 302 | 0 | if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 303 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 304 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 305 | ||
| 306 | 31× | ISuperOracle(oracle).setFeedMaxStaleness(feed, newMaxStaleness); |
| 307 | } |
|
| 308 | ||
| 309 | /// @inheritdoc ISuperGovernor |
|
| 310 | 0 | function setOracleFeedMaxStalenessBatch( |
| 311 | address[] calldata feeds_, |
|
| 312 | uint256[] calldata newMaxStalenessList_ |
|
| 313 | ) |
|
| 314 | external |
|
| 315 | onlyRole(_GOVERNOR_ROLE) |
|
| 316 | { |
|
| 317 | // Validate all staleness values before proceeding |
|
| 318 | 0 | for (uint256 i; i < newMaxStalenessList_.length; i++) { |
| 319 | 0 | if (newMaxStalenessList_[i] < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
| 320 | } |
|
| 321 | ||
| 322 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 323 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 324 | ||
| 325 | 0 | ISuperOracle(oracle).setFeedMaxStalenessBatch(feeds_, newMaxStalenessList_); |
| 326 | } |
|
| 327 | ||
| 328 | /// @inheritdoc ISuperGovernor |
|
| 329 | 0 | function queueOracleUpdate( |
| 330 | address[] calldata bases_, |
|
| 331 | address[] calldata quotes_, |
|
| 332 | bytes32[] calldata providers_, |
|
| 333 | address[] calldata feeds_ |
|
| 334 | ) |
|
| 335 | external |
|
| 336 | onlyRole(_GOVERNOR_ROLE) |
|
| 337 | 0 | { |
| 338 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 339 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 340 | ||
| 341 | 0 | ISuperOracle(oracle).queueOracleUpdate(bases_, quotes_, providers_, feeds_); |
| 342 | } |
|
| 343 | ||
| 344 | /// @inheritdoc ISuperGovernor |
|
| 345 | 0 | function queueOracleProviderRemoval(bytes32[] calldata providers) external onlyRole(_GOVERNOR_ROLE) { |
| 346 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 347 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 348 | ||
| 349 | 0 | ISuperOracle(oracle).queueProviderRemoval(providers); |
| 350 | } |
|
| 351 | ||
| 352 | /// @inheritdoc ISuperGovernor |
|
| 353 | 0 | function batchSetOracleUptimeFeed( |
| 354 | address[] calldata dataOracles_, |
|
| 355 | address[] calldata uptimeOracles_, |
|
| 356 | uint256[] calldata gracePeriods_ |
|
| 357 | ) |
|
| 358 | external |
|
| 359 | onlyRole(_GOVERNOR_ROLE) |
|
| 360 | 0 | { |
| 361 | 0 | address oracleL2 = _addressRegistry[SUPER_ORACLE]; |
| 362 | 0 | if (oracleL2 == address(0)) revert CONTRACT_NOT_FOUND(); |
| 363 | ||
| 364 | 0 | ISuperOracleL2(oracleL2).batchSetUptimeFeed(dataOracles_, uptimeOracles_, gracePeriods_); |
| 365 | } |
|
| 366 | ||
| 367 | /// @inheritdoc ISuperGovernor |
|
| 368 | 0 | function setEmergencyPrice(address token, uint256 price) external onlyRole(_GOVERNOR_ROLE) { |
| 369 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 370 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 371 | ||
| 372 | 0 | ISuperOracle(oracle).setEmergencyPrice(token, price); |
| 373 | } |
|
| 374 | ||
| 375 | /// @inheritdoc ISuperGovernor |
|
| 376 | 2× | function batchSetEmergencyPrices( |
| 377 | address[] calldata tokens_, |
|
| 378 | uint256[] calldata prices_ |
|
| 379 | ) |
|
| 380 | external |
|
| 381 | onlyRole(_GOVERNOR_ROLE) |
|
| 382 | 0 | { |
| 383 | 0 | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 384 | 0 | if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
| 385 | ||
| 386 | 0 | ISuperOracle(oracle).batchSetEmergencyPrice(tokens_, prices_); |
| 387 | } |
|
| 388 | ||
| 389 | /*////////////////////////////////////////////////////////////// |
|
| 390 | HOOK MANAGEMENT |
|
| 391 | //////////////////////////////////////////////////////////////*/ |
|
| 392 | /// @inheritdoc ISuperGovernor |
|
| 393 | 0 | function registerHook(address hook, bool isFulfillRequestsHook) external onlyRole(_GOVERNOR_ROLE) { |
| 394 | 0 | if (hook == address(0)) revert INVALID_ADDRESS(); |
| 395 | ||
| 396 | 0 | if (isFulfillRequestsHook && _registeredFulfillRequestsHooks.add(hook)) { |
| 397 | 0 | emit FulfillRequestsHookRegistered(hook); |
| 398 | } |
|
| 399 | 0 | if (_registeredHooks.add(hook)) { |
| 400 | 0 | emit HookApproved(hook); |
| 401 | } |
|
| 402 | } |
|
| 403 | ||
| 404 | /// @inheritdoc ISuperGovernor |
|
| 405 | 3× | function unregisterHook(address hook) external onlyRole(_GOVERNOR_ROLE) { |
| 406 | 0 | if (_registeredFulfillRequestsHooks.remove(hook)) { |
| 407 | 0 | emit FulfillRequestsHookUnregistered(hook); |
| 408 | } |
|
| 409 | 1× | if (_registeredHooks.remove(hook)) { |
| 410 | // Clear merkle root data for the unregistered hook to prevent stale data |
|
| 411 | 0 | delete superBankHooksMerkleRoots[hook]; |
| 412 | 0 | delete vaultBankHooksMerkleRoots[hook]; |
| 413 | ||
| 414 | 0 | emit HookRemoved(hook); |
| 415 | } |
|
| 416 | } |
|
| 417 | ||
| 418 | /*////////////////////////////////////////////////////////////// |
|
| 419 | EXECUTORS MANAGEMENT |
|
| 420 | //////////////////////////////////////////////////////////////*/ |
|
| 421 | /// @inheritdoc ISuperGovernor |
|
| 422 | 0 | function addExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) { |
| 423 | 0 | if (executor == address(0)) revert INVALID_ADDRESS(); |
| 424 | 0 | if (!_executors.add(executor)) revert EXECUTOR_ALREADY_REGISTERED(); |
| 425 | ||
| 426 | 0 | emit ExecutorAdded(executor); |
| 427 | } |
|
| 428 | ||
| 429 | /// @inheritdoc ISuperGovernor |
|
| 430 | 0 | function removeExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) { |
| 431 | 0 | if (!_executors.remove(executor)) revert EXECUTOR_NOT_REGISTERED(); |
| 432 | ||
| 433 | 0 | emit ExecutorRemoved(executor); |
| 434 | } |
|
| 435 | ||
| 436 | /*////////////////////////////////////////////////////////////// |
|
| 437 | RELAYER MANAGEMENT |
|
| 438 | //////////////////////////////////////////////////////////////*/ |
|
| 439 | /// @inheritdoc ISuperGovernor |
|
| 440 | 0 | function addRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) { |
| 441 | 0 | if (relayer == address(0)) revert INVALID_ADDRESS(); |
| 442 | 0 | if (!_relayers.add(relayer)) revert RELAYER_ALREADY_REGISTERED(); |
| 443 | ||
| 444 | 0 | emit RelayerAdded(relayer); |
| 445 | } |
|
| 446 | ||
| 447 | /// @inheritdoc ISuperGovernor |
|
| 448 | 0 | function removeRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) { |
| 449 | 0 | if (!_relayers.remove(relayer)) revert RELAYER_NOT_REGISTERED(); |
| 450 | ||
| 451 | 0 | emit RelayerRemoved(relayer); |
| 452 | } |
|
| 453 | ||
| 454 | /*////////////////////////////////////////////////////////////// |
|
| 455 | VALIDATOR MANAGEMENT |
|
| 456 | //////////////////////////////////////////////////////////////*/ |
|
| 457 | /// @inheritdoc ISuperGovernor |
|
| 458 | 0 | function addValidator(address validator) external onlyRole(_GOVERNOR_ROLE) { |
| 459 | 0 | if (validator == address(0)) revert INVALID_ADDRESS(); |
| 460 | 0 | if (!_validators.add(validator)) revert VALIDATOR_ALREADY_REGISTERED(); |
| 461 | ||
| 462 | 0 | emit ValidatorAdded(validator); |
| 463 | } |
|
| 464 | ||
| 465 | /// @inheritdoc ISuperGovernor |
|
| 466 | 0 | function removeValidator(address validator) external onlyRole(_GOVERNOR_ROLE) { |
| 467 | 0 | if (!_validators.remove(validator)) revert VALIDATOR_NOT_REGISTERED(); |
| 468 | ||
| 469 | 0 | emit ValidatorRemoved(validator); |
| 470 | } |
|
| 471 | ||
| 472 | /*////////////////////////////////////////////////////////////// |
|
| 473 | PPS ORACLE MANAGEMENT |
|
| 474 | //////////////////////////////////////////////////////////////*/ |
|
| 475 | /// @inheritdoc ISuperGovernor |
|
| 476 | 0 | function setActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 477 | 0 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 478 | ||
| 479 | // If this is the first oracle or replacing a zero oracle, set it immediately |
|
| 480 | 0 | if (_activePPSOracle == address(0)) { |
| 481 | 0 | _activePPSOracle = oracle; |
| 482 | 0 | emit ActivePPSOracleSet(oracle); |
| 483 | } else { |
|
| 484 | // Otherwise require the timelock process |
|
| 485 | 0 | revert MUST_USE_TIMELOCK_FOR_CHANGE(); |
| 486 | } |
|
| 487 | } |
|
| 488 | ||
| 489 | /// @inheritdoc ISuperGovernor |
|
| 490 | 3× | function proposeActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 491 | 0 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 492 | ||
| 493 | 0 | _proposedActivePPSOracle = oracle; |
| 494 | 0 | _activePPSOracleEffectiveTime = block.timestamp + TIMELOCK; |
| 495 | ||
| 496 | 8× | emit ActivePPSOracleProposed(oracle, _activePPSOracleEffectiveTime); |
| 497 | } |
|
| 498 | ||
| 499 | /// @inheritdoc ISuperGovernor |
|
| 500 | 0 | function executeActivePPSOracleChange() external { |
| 501 | 0 | if (_proposedActivePPSOracle == address(0)) revert NO_PROPOSED_PPS_ORACLE(); |
| 502 | ||
| 503 | 0 | if (block.timestamp < _activePPSOracleEffectiveTime) { |
| 504 | 0 | revert TIMELOCK_NOT_EXPIRED(); |
| 505 | } |
|
| 506 | ||
| 507 | 0 | address oldOracle = _activePPSOracle; |
| 508 | 0 | _activePPSOracle = _proposedActivePPSOracle; |
| 509 | ||
| 510 | // Reset proposal data |
|
| 511 | 0 | _proposedActivePPSOracle = address(0); |
| 512 | 0 | _activePPSOracleEffectiveTime = 0; |
| 513 | ||
| 514 | 0 | emit ActivePPSOracleChanged(oldOracle, _activePPSOracle); |
| 515 | } |
|
| 516 | ||
| 517 | /// @inheritdoc ISuperGovernor |
|
| 518 | 0 | function setPPSOracleQuorum(uint256 quorum) external onlyRole(_GOVERNOR_ROLE) { |
| 519 | 0 | if (quorum == 0) revert INVALID_QUORUM(); |
| 520 | ||
| 521 | 0 | _activePPSOracleQuorum = quorum; |
| 522 | 0 | emit PPSOracleQuorumUpdated(quorum); |
| 523 | } |
|
| 524 | ||
| 525 | /*////////////////////////////////////////////////////////////// |
|
| 526 | REVENUE SHARE MANAGEMENT |
|
| 527 | //////////////////////////////////////////////////////////////*/ |
|
| 528 | /// @inheritdoc ISuperGovernor |
|
| 529 | 15× | function proposeFee(FeeType feeType, uint256 value) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 530 | 19× | if (value > BPS_MAX) revert INVALID_FEE_VALUE(); |
| 531 | ||
| 532 | 31× | _proposedFeeValues[feeType] = value; |
| 533 | 35× | _feeEffectiveTimes[feeType] = block.timestamp + TIMELOCK; |
| 534 | ||
| 535 | 53× | emit FeeProposed(feeType, value, _feeEffectiveTimes[feeType]); |
| 536 | } |
|
| 537 | ||
| 538 | /// @inheritdoc ISuperGovernor |
|
| 539 | 11× | function executeFeeUpdate(FeeType feeType) external { |
| 540 | 32× | uint256 effectiveTime = _feeEffectiveTimes[feeType]; |
| 541 | 26× | if (effectiveTime == 0) revert NO_PROPOSED_FEE(feeType); |
| 542 | 7× | if (block.timestamp < effectiveTime) { |
| 543 | 13× | revert TIMELOCK_NOT_EXPIRED(); |
| 544 | } |
|
| 545 | ||
| 546 | // Update the fee value |
|
| 547 | 61× | _feeValues[feeType] = _proposedFeeValues[feeType]; |
| 548 | ||
| 549 | // Reset proposal data |
|
| 550 | 31× | delete _proposedFeeValues[feeType]; |
| 551 | 31× | delete _feeEffectiveTimes[feeType]; |
| 552 | ||
| 553 | 42× | emit FeeUpdated(feeType, _feeValues[feeType]); |
| 554 | } |
|
| 555 | ||
| 556 | /// @inheritdoc ISuperGovernor |
|
| 557 | 15× | function executeUpkeepClaim(uint256 amount) external onlyRole(_GOVERNOR_ROLE) { |
| 558 | 8× | address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
| 559 | 3× | if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
| 560 | ||
| 561 | 50× | ISuperVaultAggregator(aggregator).claimUpkeep(amount); |
| 562 | } |
|
| 563 | ||
| 564 | /*////////////////////////////////////////////////////////////// |
|
| 565 | UPKEEP COST MANAGEMENT |
|
| 566 | //////////////////////////////////////////////////////////////*/ |
|
| 567 | /// @inheritdoc ISuperGovernor |
|
| 568 | 0 | function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external onlyRole(_GAS_MANAGER_ROLE) { |
| 569 | 0 | if (oracle == address(0)) revert INVALID_ADDRESS(); |
| 570 | 0 | if (baseGasBatch == 0 || gasIncreasePerEntryBatch == 0) revert INVALID_GAS_INFO(); |
| 571 | ||
| 572 | 0 | _oracleGasInfo[oracle] = GasInfo({baseGasBatch: baseGasBatch, gasIncreasePerEntryBatch: gasIncreasePerEntryBatch}); |
| 573 | 0 | emit GasInfoSet(oracle, baseGasBatch, gasIncreasePerEntryBatch); |
| 574 | } |
|
| 575 | ||
| 576 | /// @notice Proposes a change to the upkeep payments enabled status |
|
| 577 | /// @param enabled The proposed new status for upkeep payments |
|
| 578 | 11× | function proposeUpkeepPaymentsChange(bool enabled) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 579 | 12× | _proposedUpkeepPaymentsEnabled = enabled; |
| 580 | 9× | _upkeepPaymentsChangeEffectiveTime = block.timestamp + TIMELOCK; |
| 581 | ||
| 582 | 6× | emit UpkeepPaymentsChangeProposed(enabled, _upkeepPaymentsChangeEffectiveTime); |
| 583 | } |
|
| 584 | ||
| 585 | /// @notice Executes a previously proposed change to upkeep payments status after timelock expires |
|
| 586 | 6× | function executeUpkeepPaymentsChange() external { |
| 587 | 20× | if (_upkeepPaymentsChangeEffectiveTime == 0) revert NO_PENDING_CHANGE(); |
| 588 | 21× | if (block.timestamp < _upkeepPaymentsChangeEffectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 589 | ||
| 590 | 14× | _upkeepPaymentsEnabled = _proposedUpkeepPaymentsEnabled; |
| 591 | 2× | _upkeepPaymentsChangeEffectiveTime = 0; |
| 592 | 11× | _proposedUpkeepPaymentsEnabled = false; |
| 593 | ||
| 594 | 13× | emit UpkeepPaymentsChanged(_upkeepPaymentsEnabled); |
| 595 | } |
|
| 596 | ||
| 597 | /*////////////////////////////////////////////////////////////// |
|
| 598 | MIN STALENESS MANAGEMENT |
|
| 599 | //////////////////////////////////////////////////////////////*/ |
|
| 600 | /// @inheritdoc ISuperGovernor |
|
| 601 | 11× | function proposeMinStaleness(uint256 newMinStaleness) external onlyRole(_SUPER_GOVERNOR_ROLE) { |
| 602 | 4× | _proposedMinStaleness = newMinStaleness; |
| 603 | 9× | _minStalenesEffectiveTime = block.timestamp + TIMELOCK; |
| 604 | ||
| 605 | 6× | emit MinStalenesProposed(newMinStaleness, _minStalenesEffectiveTime); |
| 606 | } |
|
| 607 | ||
| 608 | /// @inheritdoc ISuperGovernor |
|
| 609 | 7× | function executeMinStalenesChange() external { |
| 610 | 3× | uint256 minStalenesEffectiveTime = _minStalenesEffectiveTime; |
| 611 | 19× | if (minStalenesEffectiveTime == 0) revert NO_PROPOSED_MIN_STALENESS(); |
| 612 | 20× | if (block.timestamp < minStalenesEffectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 613 | ||
| 614 | 7× | _minStaleness = _proposedMinStaleness; |
| 615 | ||
| 616 | // Reset proposal data |
|
| 617 | 4× | _proposedMinStaleness = 0; |
| 618 | 5× | _minStalenesEffectiveTime = 0; |
| 619 | ||
| 620 | 11× | emit MinStalenesChanged(_minStaleness); |
| 621 | } |
|
| 622 | ||
| 623 | /*////////////////////////////////////////////////////////////// |
|
| 624 | SUPERFORM MANAGER MANAGEMENT |
|
| 625 | //////////////////////////////////////////////////////////////*/ |
|
| 626 | /// @inheritdoc ISuperGovernor |
|
| 627 | 0 | function addSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) { |
| 628 | 0 | if (manager == address(0)) revert INVALID_ADDRESS(); |
| 629 | 0 | if (!_superformManagers.add(manager)) revert MANAGER_ALREADY_REGISTERED(); |
| 630 | ||
| 631 | 0 | emit SuperformManagerAdded(manager); |
| 632 | } |
|
| 633 | ||
| 634 | /// @inheritdoc ISuperGovernor |
|
| 635 | 0 | function removeSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) { |
| 636 | 0 | if (!_superformManagers.remove(manager)) revert MANAGER_NOT_REGISTERED(); |
| 637 | ||
| 638 | 0 | emit SuperformManagerRemoved(manager); |
| 639 | } |
|
| 640 | ||
| 641 | /*////////////////////////////////////////////////////////////// |
|
| 642 | VAULT HOOKS MGMT |
|
| 643 | //////////////////////////////////////////////////////////////*/ |
|
| 644 | ||
| 645 | /// @inheritdoc ISuperGovernor |
|
| 646 | 0 | function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) { |
| 647 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 648 | 0 | if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
| 649 | ||
| 650 | 0 | uint256 effectiveTime = block.timestamp + TIMELOCK; |
| 651 | 0 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 652 | 0 | data.proposedRoot = proposedRoot; |
| 653 | 0 | data.effectiveTime = effectiveTime; |
| 654 | ||
| 655 | 0 | emit VaultBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
| 656 | } |
|
| 657 | ||
| 658 | /// @inheritdoc ISuperGovernor |
|
| 659 | 0 | function executeVaultBankHookMerkleRootUpdate(address hook) external { |
| 660 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 661 | ||
| 662 | 0 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 663 | ||
| 664 | // Check if there's a proposed update |
|
| 665 | 0 | bytes32 proposedRoot = data.proposedRoot; |
| 666 | 0 | if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
| 667 | ||
| 668 | // Check if the effective time has passed |
|
| 669 | 0 | if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 670 | ||
| 671 | // Update the Merkle root |
|
| 672 | 0 | data.currentRoot = proposedRoot; |
| 673 | ||
| 674 | // Reset the proposal |
|
| 675 | 0 | data.proposedRoot = bytes32(0); |
| 676 | 0 | data.effectiveTime = 0; |
| 677 | ||
| 678 | 0 | emit VaultBankHookMerkleRootUpdated(hook, proposedRoot); |
| 679 | } |
|
| 680 | ||
| 681 | /*////////////////////////////////////////////////////////////// |
|
| 682 | SUPERBANK HOOKS MGMT |
|
| 683 | //////////////////////////////////////////////////////////////*/ |
|
| 684 | /// @inheritdoc ISuperGovernor |
|
| 685 | 0 | function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) { |
| 686 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 687 | 0 | if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
| 688 | ||
| 689 | 0 | uint256 effectiveTime = block.timestamp + TIMELOCK; |
| 690 | 0 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 691 | 0 | data.proposedRoot = proposedRoot; |
| 692 | 0 | data.effectiveTime = effectiveTime; |
| 693 | ||
| 694 | 0 | emit SuperBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
| 695 | } |
|
| 696 | ||
| 697 | /// @inheritdoc ISuperGovernor |
|
| 698 | 0 | function executeSuperBankHookMerkleRootUpdate(address hook) external { |
| 699 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 700 | ||
| 701 | 0 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 702 | ||
| 703 | // Check if there's a proposed update |
|
| 704 | 0 | bytes32 proposedRoot = data.proposedRoot; |
| 705 | 0 | if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
| 706 | ||
| 707 | // Check if the effective time has passed |
|
| 708 | 0 | if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 709 | ||
| 710 | // Update the Merkle root |
|
| 711 | 0 | data.currentRoot = proposedRoot; |
| 712 | ||
| 713 | // Reset the proposal |
|
| 714 | 0 | data.proposedRoot = bytes32(0); |
| 715 | 0 | data.effectiveTime = 0; |
| 716 | ||
| 717 | 0 | emit SuperBankHookMerkleRootUpdated(hook, proposedRoot); |
| 718 | } |
|
| 719 | ||
| 720 | /*////////////////////////////////////////////////////////////// |
|
| 721 | VAULT BANK MANAGEMENT |
|
| 722 | //////////////////////////////////////////////////////////////*/ |
|
| 723 | /// @inheritdoc ISuperGovernor |
|
| 724 | 0 | function addVaultBank(uint64 chainId, address vaultBank) external onlyRole(_GOVERNOR_ROLE) { |
| 725 | 0 | if (chainId == 0) revert INVALID_CHAIN_ID(); |
| 726 | 0 | if (vaultBank == address(0)) revert INVALID_ADDRESS(); |
| 727 | ||
| 728 | 0 | if (_vaultBanksByChainId[chainId] != address(0)) { |
| 729 | 0 | _vaultBanks.remove(_vaultBanksByChainId[chainId]); |
| 730 | } |
|
| 731 | ||
| 732 | 0 | _vaultBanks.add(vaultBank); |
| 733 | 0 | _vaultBanksByChainId[chainId] = vaultBank; |
| 734 | ||
| 735 | 0 | emit VaultBankAddressAdded(chainId, vaultBank); |
| 736 | } |
|
| 737 | ||
| 738 | /// @inheritdoc ISuperGovernor |
|
| 739 | 0 | function getVaultBank(uint64 chainId) external view returns (address) { |
| 740 | 0 | return _vaultBanksByChainId[chainId]; |
| 741 | } |
|
| 742 | ||
| 743 | /*////////////////////////////////////////////////////////////// |
|
| 744 | INCENTIVE TOKEN MANAGEMENT |
|
| 745 | //////////////////////////////////////////////////////////////*/ |
|
| 746 | /// @inheritdoc ISuperGovernor |
|
| 747 | 14× | function proposeAddIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) { |
| 748 | 14× | for (uint256 i; i < tokens.length; i++) { |
| 749 | 35× | if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
| 750 | 25× | _proposedWhitelistedIncentiveTokens.add(tokens[i]); |
| 751 | } |
|
| 752 | ||
| 753 | 7× | _proposedAddWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
| 754 | ||
| 755 | 17× | emit WhitelistedIncentiveTokensProposed( |
| 756 | 7× | _proposedWhitelistedIncentiveTokens.values(), _proposedAddWhitelistedIncentiveTokensEffectiveTime |
| 757 | ); |
|
| 758 | } |
|
| 759 | ||
| 760 | /// @inheritdoc ISuperGovernor |
|
| 761 | 6× | function executeAddIncentiveTokens() external { |
| 762 | 4× | if ( |
| 763 | 8× | _proposedAddWhitelistedIncentiveTokensEffectiveTime == 0 |
| 764 | 4× | || block.timestamp < _proposedAddWhitelistedIncentiveTokensEffectiveTime |
| 765 | 13× | ) revert TIMELOCK_NOT_EXPIRED(); |
| 766 | ||
| 767 | // Get all proposed tokens before modifying the set |
|
| 768 | 8× | address[] memory tokensToAdd = _proposedWhitelistedIncentiveTokens.values(); |
| 769 | 4× | uint256 len = tokensToAdd.length; |
| 770 | address token; |
|
| 771 | 13× | for (uint256 i; i < len; i++) { |
| 772 | 22× | token = tokensToAdd[i]; |
| 773 | 21× | _isWhitelistedIncentiveToken[token] = true; |
| 774 | // Remove from proposed whitelisted tokens |
|
| 775 | 5× | _proposedWhitelistedIncentiveTokens.remove(token); |
| 776 | } |
|
| 777 | ||
| 778 | // Emit event once with all tokens |
|
| 779 | 17× | emit WhitelistedIncentiveTokensAdded(tokensToAdd); |
| 780 | ||
| 781 | // Reset proposal timestamp |
|
| 782 | 3× | _proposedAddWhitelistedIncentiveTokensEffectiveTime = 0; |
| 783 | } |
|
| 784 | ||
| 785 | /// @inheritdoc ISuperGovernor |
|
| 786 | 0 | function proposeRemoveIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) { |
| 787 | 0 | for (uint256 i; i < tokens.length; i++) { |
| 788 | 0 | if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
| 789 | 0 | if (!_isWhitelistedIncentiveToken[tokens[i]]) revert NOT_WHITELISTED_INCENTIVE_TOKEN(); |
| 790 | ||
| 791 | 0 | _proposedRemoveWhitelistedIncentiveTokens.add(tokens[i]); |
| 792 | } |
|
| 793 | ||
| 794 | 0 | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
| 795 | ||
| 796 | 0 | emit WhitelistedIncentiveTokensProposed( |
| 797 | 0 | _proposedRemoveWhitelistedIncentiveTokens.values(), _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
| 798 | ); |
|
| 799 | } |
|
| 800 | ||
| 801 | /// @inheritdoc ISuperGovernor |
|
| 802 | 5× | function executeRemoveIncentiveTokens() external { |
| 803 | 3× | if ( |
| 804 | 7× | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime == 0 |
| 805 | 0 | || block.timestamp < _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
| 806 | 13× | ) revert TIMELOCK_NOT_EXPIRED(); |
| 807 | ||
| 808 | // Get all proposed tokens before modifying the set |
|
| 809 | 0 | address[] memory tokensToRemove = _proposedRemoveWhitelistedIncentiveTokens.values(); |
| 810 | 0 | uint256 len = tokensToRemove.length; |
| 811 | address token; |
|
| 812 | 0 | for (uint256 i; i < len; i++) { |
| 813 | 0 | token = tokensToRemove[i]; |
| 814 | 0 | if (_isWhitelistedIncentiveToken[token]) { |
| 815 | 0 | _isWhitelistedIncentiveToken[token] = false; |
| 816 | } |
|
| 817 | // Remove from proposed whitelisted tokens to be removed |
|
| 818 | 0 | _proposedRemoveWhitelistedIncentiveTokens.remove(token); |
| 819 | } |
|
| 820 | ||
| 821 | // Emit event once with all tokens |
|
| 822 | 0 | emit WhitelistedIncentiveTokensRemoved(tokensToRemove); |
| 823 | ||
| 824 | // Reset proposal timestamp |
|
| 825 | 0 | _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = 0; |
| 826 | } |
|
| 827 | ||
| 828 | /*////////////////////////////////////////////////////////////// |
|
| 829 | EXTERNAL VIEW FUNCTIONS |
|
| 830 | //////////////////////////////////////////////////////////////*/ |
|
| 831 | /// @inheritdoc ISuperGovernor |
|
| 832 | 0 | function SUPER_GOVERNOR_ROLE() external pure returns (bytes32) { |
| 833 | return _SUPER_GOVERNOR_ROLE; |
|
| 834 | } |
|
| 835 | ||
| 836 | /// @inheritdoc ISuperGovernor |
|
| 837 | 0 | function GOVERNOR_ROLE() external pure returns (bytes32) { |
| 838 | return _GOVERNOR_ROLE; |
|
| 839 | } |
|
| 840 | ||
| 841 | /// @inheritdoc ISuperGovernor |
|
| 842 | 0 | function BANK_MANAGER_ROLE() external pure returns (bytes32) { |
| 843 | return _BANK_MANAGER_ROLE; |
|
| 844 | } |
|
| 845 | ||
| 846 | /// @inheritdoc ISuperGovernor |
|
| 847 | 0 | function GAS_MANAGER_ROLE() external pure returns (bytes32) { |
| 848 | return _GAS_MANAGER_ROLE; |
|
| 849 | } |
|
| 850 | ||
| 851 | /// @inheritdoc ISuperGovernor |
|
| 852 | 0 | function GUARDIAN_ROLE() external pure returns (bytes32) { |
| 853 | return _GUARDIAN_ROLE; |
|
| 854 | } |
|
| 855 | ||
| 856 | /// @inheritdoc ISuperGovernor |
|
| 857 | 0 | function SUPER_ASSET_FACTORY() external pure returns (bytes32) { |
| 858 | return _SUPER_ASSET_FACTORY; |
|
| 859 | } |
|
| 860 | ||
| 861 | /// @inheritdoc ISuperGovernor |
|
| 862 | 80× | function getAddress(bytes32 key) external view returns (address) { |
| 863 | 60× | address value = _addressRegistry[key]; |
| 864 | 10× | if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
| 865 | return value; |
|
| 866 | } |
|
| 867 | ||
| 868 | /// @inheritdoc ISuperGovernor |
|
| 869 | 0 | function isManagerTakeoverFrozen() external view returns (bool) { |
| 870 | 0 | return _managerTakeoversFrozen; |
| 871 | } |
|
| 872 | ||
| 873 | /// @inheritdoc ISuperGovernor |
|
| 874 | 24× | function isHookRegistered(address hook) external view returns (bool) { |
| 875 | 10× | return _registeredHooks.contains(hook); |
| 876 | } |
|
| 877 | ||
| 878 | /// @inheritdoc ISuperGovernor |
|
| 879 | 24× | function isFulfillRequestsHookRegistered(address hook) external view returns (bool) { |
| 880 | 10× | return _registeredFulfillRequestsHooks.contains(hook); |
| 881 | } |
|
| 882 | ||
| 883 | /// @inheritdoc ISuperGovernor |
|
| 884 | 0 | function getRegisteredHooks() external view returns (address[] memory) { |
| 885 | 0 | return _registeredHooks.values(); |
| 886 | } |
|
| 887 | ||
| 888 | /// @inheritdoc ISuperGovernor |
|
| 889 | 0 | function getRegisteredFulfillRequestsHooks() external view returns (address[] memory) { |
| 890 | 0 | return _registeredFulfillRequestsHooks.values(); |
| 891 | } |
|
| 892 | ||
| 893 | /// @inheritdoc ISuperGovernor |
|
| 894 | 0 | function isValidator(address validator) external view returns (bool) { |
| 895 | 0 | return _validators.contains(validator); |
| 896 | } |
|
| 897 | ||
| 898 | /// @inheritdoc ISuperGovernor |
|
| 899 | 0 | function isGuardian(address guardian) external view returns (bool) { |
| 900 | 0 | return hasRole(_GUARDIAN_ROLE, guardian); |
| 901 | } |
|
| 902 | ||
| 903 | /// @inheritdoc ISuperGovernor |
|
| 904 | 0 | function isRelayer(address relayer) external view returns (bool) { |
| 905 | 0 | return _relayers.contains(relayer); |
| 906 | } |
|
| 907 | ||
| 908 | /// @inheritdoc ISuperGovernor |
|
| 909 | 0 | function isExecutor(address executor) external view returns (bool) { |
| 910 | 0 | return _executors.contains(executor); |
| 911 | } |
|
| 912 | ||
| 913 | /// @inheritdoc ISuperGovernor |
|
| 914 | 0 | function getValidators() external view returns (address[] memory) { |
| 915 | 0 | return _validators.values(); |
| 916 | } |
|
| 917 | ||
| 918 | /// @inheritdoc ISuperGovernor |
|
| 919 | 0 | function getRelayers() external view returns (address[] memory) { |
| 920 | 0 | return _relayers.values(); |
| 921 | } |
|
| 922 | ||
| 923 | /// @inheritdoc ISuperGovernor |
|
| 924 | 0 | function getExecutors() external view returns (address[] memory) { |
| 925 | 0 | return _executors.values(); |
| 926 | } |
|
| 927 | ||
| 928 | /// @inheritdoc ISuperGovernor |
|
| 929 | 0 | function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime) { |
| 930 | 0 | return (_proposedActivePPSOracle, _activePPSOracleEffectiveTime); |
| 931 | } |
|
| 932 | ||
| 933 | /// @inheritdoc ISuperGovernor |
|
| 934 | 0 | function getPPSOracleQuorum() external view returns (uint256) { |
| 935 | 0 | return _activePPSOracleQuorum; |
| 936 | } |
|
| 937 | ||
| 938 | /// @inheritdoc ISuperGovernor |
|
| 939 | 0 | function getActivePPSOracle() external view returns (address) { |
| 940 | 0 | if (_activePPSOracle == address(0)) revert NO_ACTIVE_PPS_ORACLE(); |
| 941 | 0 | return _activePPSOracle; |
| 942 | } |
|
| 943 | ||
| 944 | /// @inheritdoc ISuperGovernor |
|
| 945 | 9× | function isActivePPSOracle(address oracle) external view returns (bool) { |
| 946 | 9× | return oracle == _activePPSOracle; |
| 947 | } |
|
| 948 | ||
| 949 | /// @inheritdoc ISuperGovernor |
|
| 950 | 20× | function getFee(FeeType feeType) external view returns (uint256) { |
| 951 | 0 | return _feeValues[feeType]; |
| 952 | } |
|
| 953 | ||
| 954 | /// @inheritdoc ISuperGovernor |
|
| 955 | 0 | function getGasInfo(address oracle_) external view returns (GasInfo memory) { |
| 956 | 0 | return _oracleGasInfo[oracle_]; |
| 957 | } |
|
| 958 | ||
| 959 | /// @inheritdoc ISuperGovernor |
|
| 960 | 14× | function getUpkeepCostPerBatchUpdate(address oracle_, uint256 chargeableEntries_) external view returns (uint256) { |
| 961 | // Calculate total gas cost |
|
| 962 | 21× | uint256 totalGas = _oracleGasInfo[oracle_].baseGasBatch + |
| 963 | 21× | (_oracleGasInfo[oracle_].gasIncreasePerEntryBatch * chargeableEntries_); |
| 964 | ||
| 965 | 4× | return _convertGasToUp(totalGas); |
| 966 | } |
|
| 967 | ||
| 968 | /// @inheritdoc ISuperGovernor |
|
| 969 | 3× | function getMinStaleness() external view returns (uint256) { |
| 970 | 2× | return _minStaleness; |
| 971 | } |
|
| 972 | ||
| 973 | /// @inheritdoc ISuperGovernor |
|
| 974 | 0 | function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime) { |
| 975 | 0 | return (_proposedMinStaleness, _minStalenesEffectiveTime); |
| 976 | } |
|
| 977 | ||
| 978 | /// @inheritdoc ISuperGovernor |
|
| 979 | 0 | function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32) { |
| 980 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 981 | 0 | return superBankHooksMerkleRoots[hook].currentRoot; |
| 982 | } |
|
| 983 | ||
| 984 | /// @inheritdoc ISuperGovernor |
|
| 985 | 0 | function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32) { |
| 986 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 987 | 0 | return vaultBankHooksMerkleRoots[hook].currentRoot; |
| 988 | } |
|
| 989 | ||
| 990 | /// @inheritdoc ISuperGovernor |
|
| 991 | 0 | function getProposedSuperBankHookMerkleRoot(address hook) |
| 992 | external |
|
| 993 | view |
|
| 994 | 0 | returns (bytes32 proposedRoot, uint256 effectiveTime) |
| 995 | { |
|
| 996 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 997 | 0 | ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
| 998 | 0 | return (data.proposedRoot, data.effectiveTime); |
| 999 | } |
|
| 1000 | ||
| 1001 | /// @inheritdoc ISuperGovernor |
|
| 1002 | 0 | function getProposedVaultBankHookMerkleRoot(address hook) |
| 1003 | external |
|
| 1004 | view |
|
| 1005 | 0 | returns (bytes32 proposedRoot, uint256 effectiveTime) |
| 1006 | { |
|
| 1007 | 0 | if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
| 1008 | 0 | ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
| 1009 | 0 | return (data.proposedRoot, data.effectiveTime); |
| 1010 | } |
|
| 1011 | ||
| 1012 | /// @inheritdoc ISuperGovernor |
|
| 1013 | 0 | function getProver() external view returns (address) { |
| 1014 | 0 | return _prover; |
| 1015 | } |
|
| 1016 | ||
| 1017 | /// @inheritdoc ISuperGovernor |
|
| 1018 | 3× | function isUpkeepPaymentsEnabled() external view returns (bool enabled) { |
| 1019 | 4× | return _upkeepPaymentsEnabled; |
| 1020 | } |
|
| 1021 | ||
| 1022 | /// @inheritdoc ISuperGovernor |
|
| 1023 | 0 | function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime) { |
| 1024 | 0 | return (_proposedUpkeepPaymentsEnabled, _upkeepPaymentsChangeEffectiveTime); |
| 1025 | } |
|
| 1026 | ||
| 1027 | /// @inheritdoc ISuperGovernor |
|
| 1028 | 12× | function isSuperformManager(address manager) external view returns (bool isSuperform) { |
| 1029 | 5× | return _superformManagers.contains(manager); |
| 1030 | } |
|
| 1031 | ||
| 1032 | /// @inheritdoc ISuperGovernor |
|
| 1033 | 0 | function getAllSuperformManagers() external view returns (address[] memory managers) { |
| 1034 | 0 | return _superformManagers.values(); |
| 1035 | } |
|
| 1036 | ||
| 1037 | /// @inheritdoc ISuperGovernor |
|
| 1038 | 0 | function getManagersPaginated( |
| 1039 | uint256 cursor, |
|
| 1040 | uint256 limit |
|
| 1041 | ) |
|
| 1042 | external |
|
| 1043 | view |
|
| 1044 | 0 | returns (address[] memory chunkOfManagers, uint256 next) |
| 1045 | 0 | { |
| 1046 | 0 | uint256 len = _superformManagers.length(); |
| 1047 | ||
| 1048 | // clamp limit so we don’t read past end |
|
| 1049 | 0 | uint256 realLimit = limit; |
| 1050 | // If cursor is beyond the end, return empty array |
|
| 1051 | 0 | if (cursor >= len) { |
| 1052 | 0 | return (new address[](0), 0); |
| 1053 | } |
|
| 1054 | ||
| 1055 | 0 | uint256 remaining = len - cursor; |
| 1056 | 0 | if (realLimit > remaining) realLimit = remaining; |
| 1057 | ||
| 1058 | 0 | chunkOfManagers = new address[](realLimit); |
| 1059 | 0 | for (uint256 i; i < realLimit; ++i) { |
| 1060 | 0 | chunkOfManagers[i] = _superformManagers.at(cursor + i); |
| 1061 | } |
|
| 1062 | ||
| 1063 | 0 | next = (cursor + realLimit < len) ? cursor + realLimit : 0; |
| 1064 | } |
|
| 1065 | ||
| 1066 | /// @inheritdoc ISuperGovernor |
|
| 1067 | 0 | function getSuperformManagersCount() external view returns (uint256) { |
| 1068 | 0 | return _superformManagers.length(); |
| 1069 | } |
|
| 1070 | ||
| 1071 | /// @inheritdoc ISuperGovernor |
|
| 1072 | 0 | function isWhitelistedIncentiveToken(address token) external view returns (bool) { |
| 1073 | 0 | return _isWhitelistedIncentiveToken[token]; |
| 1074 | } |
|
| 1075 | ||
| 1076 | /// @inheritdoc ISuperGovernor |
|
| 1077 | 0 | function registerProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) { |
| 1078 | 0 | if (keeper == address(0)) revert INVALID_ADDRESS(); |
| 1079 | 0 | if (_protectedKeepers.contains(keeper)) revert KEEPER_ALREADY_REGISTERED(); |
| 1080 | ||
| 1081 | 0 | _protectedKeepers.add(keeper); |
| 1082 | 0 | emit ProtectedKeeperRegistered(keeper); |
| 1083 | } |
|
| 1084 | ||
| 1085 | /// @inheritdoc ISuperGovernor |
|
| 1086 | 0 | function unregisterProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) { |
| 1087 | 0 | if (!_protectedKeepers.contains(keeper)) revert KEEPER_NOT_REGISTERED(); |
| 1088 | ||
| 1089 | 0 | _protectedKeepers.remove(keeper); |
| 1090 | 0 | emit ProtectedKeeperUnregistered(keeper); |
| 1091 | } |
|
| 1092 | ||
| 1093 | /// @inheritdoc ISuperGovernor |
|
| 1094 | 12× | function isProtectedKeeper(address keeper) external view returns (bool) { |
| 1095 | 5× | return _protectedKeepers.contains(keeper); |
| 1096 | } |
|
| 1097 | ||
| 1098 | /// @inheritdoc ISuperGovernor |
|
| 1099 | 0 | function getProtectedKeepers() external view returns (address[] memory) { |
| 1100 | 0 | return _protectedKeepers.values(); |
| 1101 | } |
|
| 1102 | ||
| 1103 | /// @inheritdoc ISuperGovernor |
|
| 1104 | 0 | function getProtectedKeepersCount() external view returns (uint256) { |
| 1105 | 0 | return _protectedKeepers.length(); |
| 1106 | } |
|
| 1107 | ||
| 1108 | /*////////////////////////////////////////////////////////////// |
|
| 1109 | INTERNAL FUNCTIONS |
|
| 1110 | //////////////////////////////////////////////////////////////*/ |
|
| 1111 | 2× | function _convertGasToUp(uint256 gasAmount) internal view returns (uint256) { |
| 1112 | 9× | address oracle = _addressRegistry[SUPER_ORACLE]; |
| 1113 | 15× | if (oracle == address(0)) revert SUPER_ORACLE_NOT_FOUND(); |
| 1114 | 0 | address upToken = _addressRegistry[UP]; |
| 1115 | 0 | if (upToken == address(0)) revert UP_NOT_FOUND(); |
| 1116 | ||
| 1117 | // Step 1: convert gas to ETH |
|
| 1118 | 0 | (uint256 ethAmount,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
| 1119 | gasAmount, |
|
| 1120 | GAS_QUOTE, |
|
| 1121 | GWEI_QUOTE, |
|
| 1122 | AVERAGE_PROVIDER |
|
| 1123 | ); |
|
| 1124 | ||
| 1125 | // Step 2: convert ETH to USD |
|
| 1126 | 0 | (uint256 ethToUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
| 1127 | ethAmount, |
|
| 1128 | NATIVE_TOKEN, |
|
| 1129 | USD_TOKEN, |
|
| 1130 | AVERAGE_PROVIDER |
|
| 1131 | ); |
|
| 1132 | ||
| 1133 | // Step 3: convert USD to UP (how much USD per UP token) |
|
| 1134 | 0 | (uint256 upPerUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
| 1135 | 0 | 1e18, // 1 UP token (18 decimals) |
| 1136 | upToken, |
|
| 1137 | USD_TOKEN, |
|
| 1138 | AVERAGE_PROVIDER |
|
| 1139 | ); |
|
| 1140 | ||
| 1141 | // Calculate required UP tokens |
|
| 1142 | // usdAmount / upPerUsd = required UP tokens |
|
| 1143 | 0 | uint256 requiredUpTokens = Math.mulDiv(ethToUsd, 1e18, upPerUsd, Math.Rounding.Ceil); |
| 1144 | return requiredUpTokens; |
|
| 1145 | } |
|
| 1146 | } |
86%
src/SuperVault/SuperVault.sol
Lines covered: 167 / 192 (86%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 6 | import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; |
|
| 7 | import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 8 | ||
| 9 | // OpenZeppelin Upgradeable |
|
| 10 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
|
| 11 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; |
|
| 12 | import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; |
|
| 13 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 14 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 15 | import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; |
|
| 16 | import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; |
|
| 17 | import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; |
|
| 18 | ||
| 19 | // Interfaces |
|
| 20 | import { ISuperVault } from "../interfaces/SuperVault/ISuperVault.sol"; |
|
| 21 | import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 22 | import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol"; |
|
| 23 | import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 24 | import { IERC7540Operator, IERC7540Redeem, IERC7741 } from "../vendor/standards/ERC7540/IERC7540Vault.sol"; |
|
| 25 | import { IERC7575 } from "../vendor/standards/ERC7575/IERC7575.sol"; |
|
| 26 | import { ISuperVaultEscrow } from "../interfaces/SuperVault/ISuperVaultEscrow.sol"; |
|
| 27 | ||
| 28 | // Libraries |
|
| 29 | import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol"; |
|
| 30 | ||
| 31 | /// @title SuperVault |
|
| 32 | /// @author Superform Labs |
|
| 33 | /// @notice SuperVault vault contract implementing ERC4626 with synchronous deposits and asynchronous redeems via |
|
| 34 | /// ERC7540 |
|
| 35 | 927× | contract SuperVault is |
| 36 | Initializable, |
|
| 37 | ERC20Upgradeable, |
|
| 38 | IERC7540Redeem, |
|
| 39 | IERC7741, |
|
| 40 | IERC4626, |
|
| 41 | ISuperVault, |
|
| 42 | ReentrancyGuardUpgradeable, |
|
| 43 | EIP712Upgradeable |
|
| 44 | { |
|
| 45 | using AssetMetadataLib for address; |
|
| 46 | using SafeERC20 for IERC20; |
|
| 47 | using Math for uint256; |
|
| 48 | ||
| 49 | /*////////////////////////////////////////////////////////////// |
|
| 50 | CONSTANTS |
|
| 51 | //////////////////////////////////////////////////////////////*/ |
|
| 52 | 5× | uint256 private constant REQUEST_ID = 0; |
| 53 | 8× | uint256 private constant BPS_PRECISION = 10_000; |
| 54 | ||
| 55 | // EIP712 TypeHash |
|
| 56 | 0 | bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH = |
| 57 | 0 | keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)"); |
| 58 | ||
| 59 | /*////////////////////////////////////////////////////////////// |
|
| 60 | STATE |
|
| 61 | //////////////////////////////////////////////////////////////*/ |
|
| 62 | 16× | address public share; |
| 63 | IERC20 private _asset; |
|
| 64 | uint8 private _underlyingDecimals; |
|
| 65 | 0 | ISuperVaultStrategy public strategy; |
| 66 | 0 | address public escrow; |
| 67 | 12× | uint256 public PRECISION; |
| 68 | ||
| 69 | // Core contracts |
|
| 70 | 12× | ISuperGovernor public immutable superGovernor; |
| 71 | ||
| 72 | /// @inheritdoc IERC7540Operator |
|
| 73 | 0 | mapping(address owner => mapping(address operator => bool)) public isOperator; |
| 74 | ||
| 75 | // Authorization tracking |
|
| 76 | mapping(address controller => mapping(bytes32 nonce => bool used)) private _authorizations; |
|
| 77 | ||
| 78 | /*////////////////////////////////////////////////////////////// |
|
| 79 | CONSTRUCTOR |
|
| 80 | //////////////////////////////////////////////////////////////*/ |
|
| 81 | ||
| 82 | 28× | constructor(address superGovernor_) { |
| 83 | 5× | if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
| 84 | 6× | superGovernor = ISuperGovernor(superGovernor_); |
| 85 | 7× | emit SuperGovernorSet(superGovernor_); |
| 86 | ||
| 87 | 4× | _disableInitializers(); |
| 88 | } |
|
| 89 | ||
| 90 | /*////////////////////////////////////////////////////////////// |
|
| 91 | INITIALIZATION |
|
| 92 | //////////////////////////////////////////////////////////////*/ |
|
| 93 | ||
| 94 | /// @notice Initialize the vault with required parameters |
|
| 95 | /// @param asset_ The underlying asset token address |
|
| 96 | /// @param name_ The name of the vault token |
|
| 97 | /// @param symbol_ The symbol of the vault token |
|
| 98 | /// @param strategy_ The strategy contract address |
|
| 99 | /// @param escrow_ The escrow contract address |
|
| 100 | 17× | function initialize( |
| 101 | address asset_, |
|
| 102 | string memory name_, |
|
| 103 | string memory symbol_, |
|
| 104 | address strategy_, |
|
| 105 | address escrow_ |
|
| 106 | ) |
|
| 107 | external |
|
| 108 | initializer |
|
| 109 | { |
|
| 110 | 5× | if (asset_ == address(0)) revert INVALID_ASSET(); |
| 111 | 5× | if (strategy_ == address(0)) revert INVALID_STRATEGY(); |
| 112 | 5× | if (escrow_ == address(0)) revert INVALID_ESCROW(); |
| 113 | ||
| 114 | // Initialize parent contracts |
|
| 115 | 6× | __ERC20_init(name_, symbol_); |
| 116 | 4× | __ReentrancyGuard_init(); |
| 117 | 21× | __EIP712_init(name_, "1"); |
| 118 | ||
| 119 | // Set asset and precision |
|
| 120 | 12× | _asset = IERC20(asset_); |
| 121 | 9× | (bool success, uint8 assetDecimals) = asset_.tryGetAssetDecimals(); |
| 122 | 17× | if (!success) revert INVALID_ASSET(); |
| 123 | 18× | _underlyingDecimals = assetDecimals; |
| 124 | 10× | PRECISION = 10 ** _underlyingDecimals; |
| 125 | 11× | share = address(this); |
| 126 | 15× | strategy = ISuperVaultStrategy(strategy_); |
| 127 | 12× | escrow = escrow_; |
| 128 | } |
|
| 129 | ||
| 130 | /*////////////////////////////////////////////////////////////// |
|
| 131 | ERC20 OVERRIDES |
|
| 132 | //////////////////////////////////////////////////////////////*/ |
|
| 133 | ||
| 134 | /*////////////////////////////////////////////////////////////// |
|
| 135 | USER EXTERNAL FUNCTIONS |
|
| 136 | //////////////////////////////////////////////////////////////*/ |
|
| 137 | ||
| 138 | /// @inheritdoc IERC4626 |
|
| 139 | 24× | function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) { |
| 140 | 36× | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 141 | 25× | if (assets == 0) revert ZERO_AMOUNT(); |
| 142 | ||
| 143 | // Forward assets from msg-sender to strategy |
|
| 144 | 34× | _asset.safeTransferFrom(msg.sender, address(strategy), assets); |
| 145 | ||
| 146 | // Single executor call: strategy skims entry fee, accounts on NET, returns net shares |
|
| 147 | 138× | shares = strategy.handleOperations4626Deposit(receiver, assets); |
| 148 | 12× | if (shares == 0) revert ZERO_AMOUNT(); |
| 149 | ||
| 150 | // Mint the net shares |
|
| 151 | 12× | _mint(receiver, shares); |
| 152 | ||
| 153 | 34× | emit Deposit(msg.sender, receiver, assets, shares); |
| 154 | } |
|
| 155 | ||
| 156 | /// @inheritdoc IERC4626 |
|
| 157 | 26× | function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) { |
| 158 | 36× | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 159 | 25× | if (shares == 0) revert ZERO_AMOUNT(); |
| 160 | ||
| 161 | 4× | uint256 assetsNet; |
| 162 | 138× | (assets, assetsNet) = strategy.quoteMintAssetsGross(shares); |
| 163 | ||
| 164 | // Forward quoted gross assets from msg-sender to strategy |
|
| 165 | 32× | _asset.safeTransferFrom(msg.sender, address(strategy), assets); |
| 166 | ||
| 167 | // Single executor call: strategy handles fees and accounts on NET |
|
| 168 | 104× | strategy.handleOperations4626Mint(receiver, shares, assets, assetsNet); |
| 169 | ||
| 170 | // Mint the exact shares asked |
|
| 171 | 12× | _mint(receiver, shares); |
| 172 | ||
| 173 | 34× | emit Deposit(msg.sender, receiver, assets, shares); |
| 174 | } |
|
| 175 | ||
| 176 | /// @inheritdoc IERC7540Redeem |
|
| 177 | /// @notice Once owner has authorized an operator, the operator can request a redeem with any controller address |
|
| 178 | 18× | function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256) { |
| 179 | 19× | if (shares == 0) revert ZERO_AMOUNT(); |
| 180 | 14× | if (owner == address(0) || controller == address(0)) revert ZERO_ADDRESS(); |
| 181 | 14× | if (owner != msg.sender && !isOperator[owner][msg.sender]) revert INVALID_OWNER_OR_OPERATOR(); |
| 182 | 24× | if (balanceOf(owner) < shares) revert INVALID_AMOUNT(); |
| 183 | ||
| 184 | // Enforce auditor's invariant for current accounting model |
|
| 185 | 8× | if (controller != owner) revert CONTROLLER_MUST_EQUAL_OWNER(); |
| 186 | ||
| 187 | // Transfer shares to escrow for temporary locking |
|
| 188 | 11× | _approve(owner, escrow, shares); |
| 189 | 41× | ISuperVaultEscrow(escrow).escrowShares(owner, shares); |
| 190 | ||
| 191 | // Forward to strategy (7540 path) |
|
| 192 | 60× | strategy.handleOperations7540(ISuperVaultStrategy.Operation.RedeemRequest, controller, address(0), shares); |
| 193 | ||
| 194 | 20× | emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares); |
| 195 | return REQUEST_ID; |
|
| 196 | } |
|
| 197 | ||
| 198 | /// @inheritdoc ISuperVault |
|
| 199 | 14× | function cancelRedeem(address controller) external { |
| 200 | 5× | _validateController(controller); |
| 201 | ||
| 202 | 61× | uint256 shares = strategy.pendingRedeemRequest(controller); |
| 203 | ||
| 204 | // Forward to strategy (7540 path) |
|
| 205 | 57× | strategy.handleOperations7540(ISuperVaultStrategy.Operation.CancelRedeem, controller, address(0), 0); |
| 206 | ||
| 207 | // Return shares to controller |
|
| 208 | 41× | ISuperVaultEscrow(escrow).returnShares(controller, shares); |
| 209 | ||
| 210 | 12× | emit RedeemRequestCancelled(controller, msg.sender); |
| 211 | } |
|
| 212 | ||
| 213 | /// @inheritdoc IERC7540Operator |
|
| 214 | 17× | function setOperator(address operator, bool approved) external returns (bool success) { |
| 215 | 20× | if (msg.sender == operator) revert UNAUTHORIZED(); |
| 216 | 41× | isOperator[msg.sender][operator] = approved; |
| 217 | 11× | emit OperatorSet(msg.sender, operator, approved); |
| 218 | 1× | return true; |
| 219 | } |
|
| 220 | ||
| 221 | /// @inheritdoc IERC7741 |
|
| 222 | 0 | function authorizeOperator( |
| 223 | address controller, |
|
| 224 | address operator, |
|
| 225 | bool approved, |
|
| 226 | bytes32 nonce, |
|
| 227 | uint256 deadline, |
|
| 228 | bytes memory signature |
|
| 229 | ) |
|
| 230 | external |
|
| 231 | 0 | returns (bool) |
| 232 | { |
|
| 233 | 0 | if (controller == operator) revert UNAUTHORIZED(); |
| 234 | 0 | if (block.timestamp > deadline) revert DEADLINE_PASSED(); |
| 235 | 0 | if (_authorizations[controller][nonce]) revert UNAUTHORIZED(); |
| 236 | ||
| 237 | 0 | _authorizations[controller][nonce] = true; |
| 238 | ||
| 239 | 0 | bytes32 structHash = |
| 240 | 0 | keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline)); |
| 241 | 0 | bytes32 digest = _hashTypedDataV4(structHash); |
| 242 | ||
| 243 | 0 | if (!_isValidSignature(controller, digest, signature)) revert INVALID_SIGNATURE(); |
| 244 | ||
| 245 | 0 | isOperator[controller][operator] = approved; |
| 246 | 0 | emit OperatorSet(controller, operator, approved); |
| 247 | ||
| 248 | 0 | return true; |
| 249 | } |
|
| 250 | ||
| 251 | /*////////////////////////////////////////////////////////////// |
|
| 252 | USER EXTERNAL VIEW FUNCTIONS |
|
| 253 | //////////////////////////////////////////////////////////////*/ |
|
| 254 | ||
| 255 | //--ERC7540-- |
|
| 256 | ||
| 257 | /// @inheritdoc IERC7540Redeem |
|
| 258 | 22× | function pendingRedeemRequest( |
| 259 | uint256, /*requestId*/ |
|
| 260 | address controller |
|
| 261 | ) |
|
| 262 | external |
|
| 263 | view |
|
| 264 | 4× | returns (uint256 pendingShares) |
| 265 | { |
|
| 266 | 112× | return strategy.pendingRedeemRequest(controller); |
| 267 | } |
|
| 268 | ||
| 269 | /// @inheritdoc IERC7540Redeem |
|
| 270 | 22× | function claimableRedeemRequest( |
| 271 | uint256, /*requestId*/ |
|
| 272 | address controller |
|
| 273 | ) |
|
| 274 | external |
|
| 275 | view |
|
| 276 | 2× | returns (uint256 claimableShares) |
| 277 | { |
|
| 278 | 8× | return maxRedeem(controller); |
| 279 | } |
|
| 280 | ||
| 281 | //--Operator Management-- |
|
| 282 | ||
| 283 | /// @inheritdoc IERC7741 |
|
| 284 | 0 | function authorizations(address controller, bytes32 nonce) external view returns (bool used) { |
| 285 | 0 | return _authorizations[controller][nonce]; |
| 286 | } |
|
| 287 | ||
| 288 | /// @inheritdoc IERC7741 |
|
| 289 | 4× | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { |
| 290 | 6× | return _domainSeparatorV4(); |
| 291 | } |
|
| 292 | ||
| 293 | /// @inheritdoc IERC7741 |
|
| 294 | 13× | function invalidateNonce(bytes32 nonce) external { |
| 295 | 42× | if (_authorizations[msg.sender][nonce]) revert INVALID_NONCE(); |
| 296 | 33× | _authorizations[msg.sender][nonce] = true; |
| 297 | ||
| 298 | 4× | emit NonceInvalidated(msg.sender, nonce); |
| 299 | } |
|
| 300 | ||
| 301 | /*////////////////////////////////////////////////////////////// |
|
| 302 | ERC4626 IMPLEMENTATION |
|
| 303 | //////////////////////////////////////////////////////////////*/ |
|
| 304 | 16× | function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) { |
| 305 | 24× | return _underlyingDecimals; |
| 306 | } |
|
| 307 | ||
| 308 | 9× | function asset() public view virtual override returns (address) { |
| 309 | 9× | return address(_asset); |
| 310 | } |
|
| 311 | ||
| 312 | /// @inheritdoc IERC4626 |
|
| 313 | 59× | function totalAssets() external view override returns (uint256) { |
| 314 | 5× | uint256 supply = totalSupply(); |
| 315 | 10× | if (supply == 0) return 0; |
| 316 | 7× | uint256 currentPPS = _getStoredPPS(); |
| 317 | 13× | return Math.mulDiv(supply, currentPPS, PRECISION, Math.Rounding.Floor); |
| 318 | } |
|
| 319 | ||
| 320 | /// @inheritdoc IERC4626 |
|
| 321 | 14× | function convertToShares(uint256 assets) public view override returns (uint256) { |
| 322 | 7× | uint256 pps = _getStoredPPS(); |
| 323 | 8× | if (pps == 0) return 0; |
| 324 | 8× | return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor); |
| 325 | } |
|
| 326 | ||
| 327 | /// @inheritdoc IERC4626 |
|
| 328 | 34× | function convertToAssets(uint256 shares) public view override returns (uint256) { |
| 329 | 14× | uint256 currentPPS = _getStoredPPS(); |
| 330 | 16× | if (currentPPS == 0) return 0; |
| 331 | 22× | return Math.mulDiv(shares, currentPPS, PRECISION, Math.Rounding.Floor); |
| 332 | } |
|
| 333 | ||
| 334 | /// @inheritdoc IERC4626 |
|
| 335 | 32× | function maxDeposit(address) public view override returns (uint256) { |
| 336 | 20× | if (_isPaused()) return 0; |
| 337 | 2× | return type(uint256).max; |
| 338 | } |
|
| 339 | ||
| 340 | /// @inheritdoc IERC4626 |
|
| 341 | function maxMint(address) external view override returns (uint256) { |
|
| 342 | if (_isPaused()) return 0; |
|
| 343 | return type(uint256).max; |
|
| 344 | } |
|
| 345 | ||
| 346 | /// @inheritdoc IERC4626 |
|
| 347 | 26× | function maxWithdraw(address owner) public view override returns (uint256) { |
| 348 | 112× | return strategy.claimableWithdraw(owner); |
| 349 | } |
|
| 350 | ||
| 351 | /// @inheritdoc IERC4626 |
|
| 352 | 34× | function maxRedeem(address owner) public view override returns (uint256) { |
| 353 | 120× | uint256 withdrawPrice = strategy.getAverageWithdrawPrice(owner); |
| 354 | 15× | if (withdrawPrice == 0) return 0; |
| 355 | 15× | return maxWithdraw(owner).mulDiv(PRECISION, withdrawPrice, Math.Rounding.Floor); |
| 356 | } |
|
| 357 | ||
| 358 | /// @inheritdoc IERC4626 |
|
| 359 | 36× | function previewDeposit(uint256 assets) public view override returns (uint256) { |
| 360 | 14× | uint256 pps = _getStoredPPS(); |
| 361 | 16× | if (pps == 0) return 0; |
| 362 | ||
| 363 | 16× | (uint256 feeBps,) = _getManagementFeeConfig(); |
| 364 | ||
| 365 | 32× | if (feeBps == 0) return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor); |
| 366 | // fee-on-gross: fee = ceil(gross * feeBps / BPS) |
|
| 367 | 20× | uint256 fee = Math.mulDiv(assets, feeBps, BPS_PRECISION, Math.Rounding.Ceil); |
| 368 | ||
| 369 | 16× | uint256 assetsNet = assets - fee; |
| 370 | 20× | return Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
| 371 | } |
|
| 372 | ||
| 373 | /// @inheritdoc IERC4626 |
|
| 374 | 40× | function previewMint(uint256 shares) public view override returns (uint256) { |
| 375 | 14× | uint256 pps = _getStoredPPS(); |
| 376 | 16× | if (pps == 0) return 0; |
| 377 | ||
| 378 | 24× | uint256 assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
| 379 | ||
| 380 | 16× | (uint256 feeBps,) = _getManagementFeeConfig(); |
| 381 | 14× | if (feeBps == 0) return assetsNet; |
| 382 | 14× | if (feeBps >= BPS_PRECISION) return 0; // impossible to mint (would require infinite gross) |
| 383 | ||
| 384 | 24× | return Math.mulDiv(assetsNet, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil); |
| 385 | } |
|
| 386 | ||
| 387 | /// @inheritdoc IERC4626 |
|
| 388 | 12× | function previewWithdraw(uint256 /* assets*/ ) public pure override returns (uint256) { |
| 389 | 13× | revert NOT_IMPLEMENTED(); |
| 390 | } |
|
| 391 | ||
| 392 | /// @inheritdoc IERC4626 |
|
| 393 | function previewRedeem(uint256 /* shares*/ ) public pure override returns (uint256) { |
|
| 394 | revert NOT_IMPLEMENTED(); |
|
| 395 | } |
|
| 396 | ||
| 397 | /// @inheritdoc IERC4626 |
|
| 398 | 22× | function withdraw( |
| 399 | uint256 assets, |
|
| 400 | address receiver, |
|
| 401 | address controller |
|
| 402 | ) |
|
| 403 | public |
|
| 404 | override |
|
| 405 | nonReentrant |
|
| 406 | 2× | returns (uint256 shares) |
| 407 | 2× | { |
| 408 | 23× | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 409 | 10× | _validateController(controller); |
| 410 | ||
| 411 | 122× | uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
| 412 | 37× | if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE(); |
| 413 | ||
| 414 | 8× | uint256 maxWithdrawAmount = maxWithdraw(controller); |
| 415 | 7× | if (assets > maxWithdrawAmount) revert INVALID_AMOUNT(); |
| 416 | ||
| 417 | // Calculate shares based on assets and average withdraw price |
|
| 418 | 13× | shares = assets.mulDiv(PRECISION, averageWithdrawPrice, Math.Rounding.Floor); |
| 419 | ||
| 420 | // Take assets from strategy (7540 path) |
|
| 421 | 51× | strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
| 422 | ||
| 423 | 22× | emit Withdraw(msg.sender, receiver, controller, assets, shares); |
| 424 | } |
|
| 425 | ||
| 426 | /// @inheritdoc IERC4626 |
|
| 427 | 22× | function redeem( |
| 428 | uint256 shares, |
|
| 429 | address receiver, |
|
| 430 | address controller |
|
| 431 | ) |
|
| 432 | public |
|
| 433 | override |
|
| 434 | nonReentrant |
|
| 435 | 2× | returns (uint256 assets) |
| 436 | { |
|
| 437 | 23× | if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 438 | 10× | _validateController(controller); |
| 439 | ||
| 440 | 122× | uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
| 441 | 37× | if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE(); |
| 442 | ||
| 443 | // Calculate assets based on shares and average withdraw price |
|
| 444 | 14× | assets = shares.mulDiv(averageWithdrawPrice, PRECISION, Math.Rounding.Floor); |
| 445 | ||
| 446 | 8× | uint256 maxWithdrawAmount = maxWithdraw(controller); |
| 447 | 7× | if (assets > maxWithdrawAmount) revert INVALID_AMOUNT(); |
| 448 | ||
| 449 | // Take assets from strategy (7540 path) |
|
| 450 | 53× | strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
| 451 | ||
| 452 | 15× | emit Withdraw(msg.sender, receiver, controller, assets, shares); |
| 453 | } |
|
| 454 | ||
| 455 | // @inheritdoc ISuperVault |
|
| 456 | 32× | function burnShares(uint256 amount) external { |
| 457 | 28× | if (msg.sender != address(strategy)) revert UNAUTHORIZED(); |
| 458 | 13× | _burn(escrow, amount); |
| 459 | } |
|
| 460 | ||
| 461 | // @inheritdoc ISuperVault |
|
| 462 | 24× | function onRedeemClaimable( |
| 463 | address user, |
|
| 464 | uint256 assets, |
|
| 465 | uint256 shares, |
|
| 466 | uint256 averageWithdrawPrice, |
|
| 467 | uint256 accumulatorShares, |
|
| 468 | uint256 accumulatorCostBasis |
|
| 469 | ) |
|
| 470 | external |
|
| 471 | { |
|
| 472 | 8× | if (msg.sender != address(strategy)) revert UNAUTHORIZED(); |
| 473 | 15× | emit RedeemClaimable( |
| 474 | user, REQUEST_ID, assets, shares, averageWithdrawPrice, accumulatorShares, accumulatorCostBasis |
|
| 475 | ); |
|
| 476 | } |
|
| 477 | ||
| 478 | /*////////////////////////////////////////////////////////////// |
|
| 479 | ERC165 INTERFACE |
|
| 480 | //////////////////////////////////////////////////////////////*/ |
|
| 481 | 20× | function supportsInterface(bytes4 interfaceId) public pure returns (bool) { |
| 482 | 4× | return interfaceId == type(IERC7540Redeem).interfaceId || interfaceId == type(IERC165).interfaceId |
| 483 | 0 | || interfaceId == type(IERC7741).interfaceId || interfaceId == type(IERC4626).interfaceId |
| 484 | 0 | || interfaceId == type(IERC7575).interfaceId || interfaceId == type(IERC7540Operator).interfaceId; |
| 485 | } |
|
| 486 | ||
| 487 | /*////////////////////////////////////////////////////////////// |
|
| 488 | INTERNAL FUNCTIONS |
|
| 489 | //////////////////////////////////////////////////////////////*/ |
|
| 490 | /// @notice Validates that the controller is the msg.sender or has been authorized by the controller |
|
| 491 | /// @param controller The controller to validate |
|
| 492 | 2× | function _validateController(address controller) internal view { |
| 493 | 106× | if (controller != msg.sender && !isOperator[controller][msg.sender]) revert INVALID_CONTROLLER(); |
| 494 | } |
|
| 495 | ||
| 496 | /// @notice Verify an EIP712 signature using OpenZeppelin's ECDSA library |
|
| 497 | /// @param signer The signer to verify |
|
| 498 | /// @param digest The digest to verify |
|
| 499 | /// @param signature The signature to verify |
|
| 500 | 0 | function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal pure returns (bool) { |
| 501 | 0 | address recoveredSigner = ECDSA.recover(digest, signature); |
| 502 | 0 | return recoveredSigner == signer; |
| 503 | } |
|
| 504 | ||
| 505 | /// @notice Overrides the ERC20 _update function to update the state of the vault when a transfer occurs |
|
| 506 | /// @param from The address of the sender |
|
| 507 | /// @param to The address of the recipient |
|
| 508 | /// @param value The amount of shares being transferred |
|
| 509 | 4× | function _update(address from, address to, uint256 value) internal override(ERC20Upgradeable) { |
| 510 | /// @dev Move only accumulators pro-rata between actual users, not to/from infrastructure contracts |
|
| 511 | 183× | if (from != address(0) && to != address(0) && to != address(escrow) && from != address(escrow)) { |
| 512 | 3× | uint256 shares = value; |
| 513 | // Zero-value transfers are legal: treat as accounting no-op. |
|
| 514 | 15× | if (shares > 0) { |
| 515 | 135× | strategy.moveAccumulatorOnTransfer(from, to, shares); |
| 516 | } |
|
| 517 | } |
|
| 518 | 24× | super._update(from, to, value); |
| 519 | } |
|
| 520 | ||
| 521 | 6× | function _getStoredPPS() internal view returns (uint256) { |
| 522 | 126× | return strategy.getStoredPPS(); |
| 523 | } |
|
| 524 | ||
| 525 | /// @notice Checks if the vault is currently paused |
|
| 526 | /// @dev This accesses the SuperVaultAggregator directly via the governor to determine pause status |
|
| 527 | /// @return True if the vault is paused, false otherwise |
|
| 528 | 8× | function _isPaused() internal view returns (bool) { |
| 529 | 244× | address aggregatorAddress = superGovernor.getAddress(superGovernor.SUPER_VAULT_AGGREGATOR()); |
| 530 | 128× | return ISuperVaultAggregator(aggregatorAddress).isStrategyPaused(address(strategy)); |
| 531 | } |
|
| 532 | ||
| 533 | /// @dev Read management fee config (view-only for previews) |
|
| 534 | 12× | function _getManagementFeeConfig() internal view returns (uint256 feeBps, address recipient) { |
| 535 | 140× | ISuperVaultStrategy.FeeConfig memory cfg = strategy.getConfigInfo(); |
| 536 | 26× | return (cfg.managementFeeBps, cfg.recipient); |
| 537 | } |
|
| 538 | } |
56%
src/SuperVault/SuperVaultAggregator.sol
Lines covered: 353 / 621 (56%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 6 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 7 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 8 | import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; |
|
| 9 | import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
|
| 10 | import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; |
|
| 11 | ||
| 12 | // Superform |
|
| 13 | import {SuperVault} from "./SuperVault.sol"; |
|
| 14 | import {SuperVaultStrategy} from "./SuperVaultStrategy.sol"; |
|
| 15 | import {SuperVaultEscrow} from "./SuperVaultEscrow.sol"; |
|
| 16 | import {ISuperGovernor} from "../interfaces/ISuperGovernor.sol"; |
|
| 17 | import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 18 | // Libraries |
|
| 19 | import {AssetMetadataLib} from "../libraries/AssetMetadataLib.sol"; |
|
| 20 | ||
| 21 | /// @title SuperVaultAggregator |
|
| 22 | /// @author Superform Labs |
|
| 23 | /// @notice Registry and PPS oracle for all SuperVaults |
|
| 24 | /// @dev Creates new SuperVault trios and manages PPS updates |
|
| 25 | 0 | contract SuperVaultAggregator is ISuperVaultAggregator { |
| 26 | using AssetMetadataLib for address; |
|
| 27 | using Clones for address; |
|
| 28 | using SafeERC20 for IERC20; |
|
| 29 | using Math for uint256; |
|
| 30 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 31 | ||
| 32 | /*////////////////////////////////////////////////////////////// |
|
| 33 | STORAGE |
|
| 34 | //////////////////////////////////////////////////////////////*/ |
|
| 35 | // Vault implementation contracts |
|
| 36 | 0 | address public immutable VAULT_IMPLEMENTATION; |
| 37 | 0 | address public immutable STRATEGY_IMPLEMENTATION; |
| 38 | 0 | address public immutable ESCROW_IMPLEMENTATION; |
| 39 | ||
| 40 | // Governance |
|
| 41 | 0 | ISuperGovernor public immutable SUPER_GOVERNOR; |
| 42 | ||
| 43 | // Claimable upkeep |
|
| 44 | 66× | uint256 public claimableUpkeep; |
| 45 | ||
| 46 | // Strategy data storage |
|
| 47 | mapping(address strategy => StrategyData) private _strategyData; |
|
| 48 | ||
| 49 | // Upkeep balances |
|
| 50 | mapping(address manager => uint256 upkeep) private _managerUpkeepBalance; |
|
| 51 | ||
| 52 | // Stake balances |
|
| 53 | mapping(address manager => uint256 stake) private _managerStakeBalance; |
|
| 54 | ||
| 55 | // Registry of created vaults |
|
| 56 | EnumerableSet.AddressSet private _superVaults; |
|
| 57 | EnumerableSet.AddressSet private _superVaultStrategies; |
|
| 58 | EnumerableSet.AddressSet private _superVaultEscrows; |
|
| 59 | ||
| 60 | // Constant for PPS decimals |
|
| 61 | 0 | uint256 public constant PPS_DECIMALS = 18; |
| 62 | ||
| 63 | // Maximum number of secondary managers per strategy to prevent governance DoS on manager replacement |
|
| 64 | 6× | uint256 public constant MAX_SECONDARY_MANAGERS = 5; |
| 65 | ||
| 66 | // Maximum number of strategies to process in `batchForwardPPS` |
|
| 67 | 1× | uint256 public constant MAX_STRATEGIES = 300; |
| 68 | ||
| 69 | // Timelock for manager changes and Merkle root updates |
|
| 70 | 1× | uint256 private constant _MANAGER_CHANGE_TIMELOCK = 7 days; |
| 71 | 3× | uint256 private _hooksRootUpdateTimelock = 15 minutes; |
| 72 | ||
| 73 | // Global hooks Merkle root data |
|
| 74 | bytes32 private _globalHooksRoot; |
|
| 75 | bytes32 private _proposedGlobalHooksRoot; |
|
| 76 | uint256 private _globalHooksRootEffectiveTime; |
|
| 77 | bool private _globalHooksRootVetoed; |
|
| 78 | ||
| 79 | // Nonce for vault creation tracking |
|
| 80 | uint256 private _vaultCreationNonce; |
|
| 81 | ||
| 82 | /*////////////////////////////////////////////////////////////// |
|
| 83 | MODIFIERS |
|
| 84 | //////////////////////////////////////////////////////////////*/ |
|
| 85 | /// @notice Validates that msg.sender is the active PPS Oracle |
|
| 86 | modifier onlyPPSOracle() { |
|
| 87 | 60× | if (!SUPER_GOVERNOR.isActivePPSOracle(msg.sender)) { |
| 88 | 0 | revert UNAUTHORIZED_PPS_ORACLE(); |
| 89 | } |
|
| 90 | _; |
|
| 91 | } |
|
| 92 | ||
| 93 | /// @notice Validates that a strategy exists (has been created by this aggregator) |
|
| 94 | modifier validStrategy(address strategy) { |
|
| 95 | 120× | if (!_superVaultStrategies.contains(strategy)) |
| 96 | 104× | revert UNKNOWN_STRATEGY(); |
| 97 | 2× | _; |
| 98 | } |
|
| 99 | ||
| 100 | /*////////////////////////////////////////////////////////////// |
|
| 101 | CONSTRUCTOR |
|
| 102 | //////////////////////////////////////////////////////////////*/ |
|
| 103 | /// @notice Initializes the SuperVaultAggregator |
|
| 104 | /// @param superGovernor_ Address of the SuperGovernor contract |
|
| 105 | /// @param vaultImpl_ Address of the pre-deployed SuperVault implementation |
|
| 106 | /// @param strategyImpl_ Address of the pre-deployed SuperVaultStrategy implementation |
|
| 107 | /// @param escrowImpl_ Address of the pre-deployed SuperVaultEscrow implementation |
|
| 108 | 0 | constructor( |
| 109 | address superGovernor_, |
|
| 110 | address vaultImpl_, |
|
| 111 | address strategyImpl_, |
|
| 112 | address escrowImpl_ |
|
| 113 | ) { |
|
| 114 | 5× | if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
| 115 | 5× | if (vaultImpl_ == address(0)) revert ZERO_ADDRESS(); |
| 116 | 5× | if (strategyImpl_ == address(0)) revert ZERO_ADDRESS(); |
| 117 | 5× | if (escrowImpl_ == address(0)) revert ZERO_ADDRESS(); |
| 118 | ||
| 119 | 5× | SUPER_GOVERNOR = ISuperGovernor(superGovernor_); |
| 120 | 5× | VAULT_IMPLEMENTATION = vaultImpl_; |
| 121 | 4× | STRATEGY_IMPLEMENTATION = strategyImpl_; |
| 122 | 3× | ESCROW_IMPLEMENTATION = escrowImpl_; |
| 123 | } |
|
| 124 | ||
| 125 | /*////////////////////////////////////////////////////////////// |
|
| 126 | VAULT CREATION |
|
| 127 | //////////////////////////////////////////////////////////////*/ |
|
| 128 | /// @inheritdoc ISuperVaultAggregator |
|
| 129 | 22× | function createVault( |
| 130 | VaultCreationParams calldata params |
|
| 131 | 4× | ) external returns (address superVault, address strategy, address escrow) { |
| 132 | // Input validation |
|
| 133 | 4× | if ( |
| 134 | 18× | params.asset == address(0) || |
| 135 | 13× | params.mainManager == address(0) || |
| 136 | 13× | params.feeConfig.recipient == address(0) |
| 137 | ) { |
|
| 138 | 13× | revert ZERO_ADDRESS(); |
| 139 | } |
|
| 140 | ||
| 141 | // Initialize local variables struct to avoid stack too deep |
|
| 142 | VaultCreationLocalVars memory vars; |
|
| 143 | ||
| 144 | // Increment nonce before creating proxies |
|
| 145 | 13× | vars.currentNonce = _vaultCreationNonce++; |
| 146 | 12× | vars.salt = keccak256( |
| 147 | 28× | abi.encode( |
| 148 | 1× | msg.sender, |
| 149 | 8× | params.asset, |
| 150 | 8× | params.name, |
| 151 | 8× | params.symbol, |
| 152 | 3× | vars.currentNonce |
| 153 | ) |
|
| 154 | ); |
|
| 155 | ||
| 156 | // Create minimal proxies |
|
| 157 | 10× | superVault = VAULT_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 158 | 17× | escrow = ESCROW_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 159 | 16× | strategy = STRATEGY_IMPLEMENTATION.cloneDeterministic(vars.salt); |
| 160 | ||
| 161 | // Initialize superVault |
|
| 162 | 59× | SuperVault(superVault).initialize( |
| 163 | 8× | params.asset, |
| 164 | 8× | params.name, |
| 165 | 8× | params.symbol, |
| 166 | 1× | strategy, |
| 167 | 1× | escrow |
| 168 | ); |
|
| 169 | ||
| 170 | // Initialize escrow |
|
| 171 | 38× | SuperVaultEscrow(escrow).initialize(superVault, strategy); |
| 172 | ||
| 173 | // Initialize strategy |
|
| 174 | 53× | SuperVaultStrategy(payable(strategy)).initialize( |
| 175 | 2× | superVault, |
| 176 | 4× | params.feeConfig |
| 177 | ); |
|
| 178 | ||
| 179 | // Store vault trio in registry |
|
| 180 | 11× | _superVaults.add(superVault); |
| 181 | 6× | _superVaultStrategies.add(strategy); |
| 182 | 6× | _superVaultEscrows.add(escrow); |
| 183 | ||
| 184 | // Get asset decimals |
|
| 185 | 19× | (bool success, uint8 assetDecimals) = params |
| 186 | .asset |
|
| 187 | .tryGetAssetDecimals(); |
|
| 188 | 4× | if (!success) revert INVALID_ASSET(); |
| 189 | 14× | vars.initialPPS = 10 ** assetDecimals; // 1.0 as initial PPS |
| 190 | ||
| 191 | // Validate maxStaleness against minimum required staleness |
|
| 192 | 67× | if (params.maxStaleness < SUPER_GOVERNOR.getMinStaleness()) { |
| 193 | 13× | revert MAX_STALENESS_TOO_LOW(); |
| 194 | } |
|
| 195 | ||
| 196 | // Initialize StrategyData individually to avoid mapping assignment issues |
|
| 197 | 20× | _strategyData[strategy].pps = vars.initialPPS; |
| 198 | // Initialize standard deviation to 0 |
|
| 199 | 5× | _strategyData[strategy].lastUpdateTimestamp = block.timestamp; |
| 200 | 8× | _strategyData[strategy].minUpdateInterval = params.minUpdateInterval; |
| 201 | 8× | _strategyData[strategy].maxStaleness = params.maxStaleness; |
| 202 | 7× | _strategyData[strategy].isPaused = false; |
| 203 | 42× | _strategyData[strategy].mainManager = params.mainManager; |
| 204 | ||
| 205 | 12× | uint256 secondaryLen = params.secondaryManagers.length; |
| 206 | 13× | for (uint256 i; i < secondaryLen; ++i) { |
| 207 | 19× | _strategyData[strategy].secondaryManagers.add( |
| 208 | 30× | params.secondaryManagers[i] |
| 209 | ); |
|
| 210 | } |
|
| 211 | 3× | if ( |
| 212 | 20× | _strategyData[strategy].secondaryManagers.length() >= |
| 213 | MAX_SECONDARY_MANAGERS |
|
| 214 | ) { |
|
| 215 | 13× | revert TOO_MANY_SECONDARY_MANAGERS(); |
| 216 | } |
|
| 217 | ||
| 218 | // Set default threshold values |
|
| 219 | 22× | _strategyData[strategy].dispersionThreshold = type(uint256).max; // Default: max (disabled) |
| 220 | 5× | _strategyData[strategy].deviationThreshold = type(uint256).max; // Default: max (disabled) |
| 221 | ||
| 222 | 25× | emit VaultDeployed( |
| 223 | superVault, |
|
| 224 | strategy, |
|
| 225 | 2× | escrow, |
| 226 | 8× | params.asset, |
| 227 | 8× | params.name, |
| 228 | 11× | params.symbol, |
| 229 | 3× | vars.currentNonce |
| 230 | ); |
|
| 231 | 14× | emit PPSUpdated( |
| 232 | strategy, |
|
| 233 | 5× | vars.initialPPS, |
| 234 | 1× | 0, |
| 235 | 0, |
|
| 236 | 0, |
|
| 237 | 15× | _strategyData[strategy].lastUpdateTimestamp |
| 238 | ); |
|
| 239 | ||
| 240 | 4× | return (superVault, strategy, escrow); |
| 241 | } |
|
| 242 | ||
| 243 | /*////////////////////////////////////////////////////////////// |
|
| 244 | PPS UPDATE FUNCTIONS |
|
| 245 | //////////////////////////////////////////////////////////////*/ |
|
| 246 | /// @inheritdoc ISuperVaultAggregator |
|
| 247 | 18× | function forwardPPS(ForwardPPSArgs calldata args) external onlyPPSOracle { |
| 248 | 8× | uint256 strategiesLength = args.strategies.length; |
| 249 | 6× | if (strategiesLength > MAX_STRATEGIES) revert MAX_STRATEGIES_EXCEEDED(); |
| 250 | ||
| 251 | 6× | if (strategiesLength == 0) revert ZERO_ARRAY_LENGTH(); |
| 252 | // Validate input array lengths |
|
| 253 | 4× | if ( |
| 254 | 29× | strategiesLength != args.ppss.length || |
| 255 | 13× | strategiesLength != args.ppsStdevs.length || |
| 256 | 13× | strategiesLength != args.validatorSets.length || |
| 257 | 13× | strategiesLength != args.timestamps.length || |
| 258 | 13× | strategiesLength != args.totalValidators.length |
| 259 | 13× | ) revert ARRAY_LENGTH_MISMATCH(); |
| 260 | ||
| 261 | 61× | bool paymentsEnabled = SUPER_GOVERNOR.isUpkeepPaymentsEnabled(); |
| 262 | 1× | uint256 chargeableCount; |
| 263 | 5× | if (paymentsEnabled) { |
| 264 | 16× | for (uint256 i; i < strategiesLength; ++i) { |
| 265 | // Skip invalid strategies without reverting |
|
| 266 | 37× | if (!_superVaultStrategies.contains(args.strategies[i])) { |
| 267 | 39× | emit UnknownStrategy(args.strategies[i]); |
| 268 | 2× | continue; |
| 269 | } |
|
| 270 | ||
| 271 | // Skip when invalid timestamp is provided |
|
| 272 | 27× | if (args.timestamps[i] > block.timestamp) { |
| 273 | 16× | emit ProvidedTimestampExceedsBlockTimestamp( |
| 274 | 28× | args.strategies[i], |
| 275 | 21× | args.timestamps[i], |
| 276 | 1× | block.timestamp |
| 277 | ); |
|
| 278 | 2× | continue; |
| 279 | } |
|
| 280 | ||
| 281 | // Skip Superform manager |
|
| 282 | 58× | address manager = _strategyData[args.strategies[i]].mainManager; |
| 283 | 62× | if (SUPER_GOVERNOR.isSuperformManager(manager)) { |
| 284 | 0 | emit SuperformManager(args.strategies[i], manager); |
| 285 | 0 | continue; |
| 286 | } |
|
| 287 | ||
| 288 | // Skip if updateAuthority is in the authorized callers list |
|
| 289 | // These are manager-designated keepers that should be exempt from fees |
|
| 290 | // NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller) |
|
| 291 | /// @dev: cannot underflow; it's checked above already and it skips the entry if that's the case |
|
| 292 | 4× | if ( |
| 293 | 50× | _strategyData[args.strategies[i]] |
| 294 | .authorizedCallers |
|
| 295 | 10× | .contains(args.updateAuthority) |
| 296 | ) { |
|
| 297 | 12× | emit AuthorizedCaller( |
| 298 | 28× | args.strategies[i], |
| 299 | 10× | args.updateAuthority |
| 300 | ); |
|
| 301 | 3× | continue; |
| 302 | } |
|
| 303 | ||
| 304 | // Count only non-stale entries as chargeable |
|
| 305 | 3× | if ( |
| 306 | 29× | block.timestamp - args.timestamps[i] <= |
| 307 | 46× | _strategyData[args.strategies[i]].maxStaleness |
| 308 | ) { |
|
| 309 | 7× | ++chargeableCount; |
| 310 | } |
|
| 311 | } |
|
| 312 | } |
|
| 313 | ||
| 314 | ///@dev Total upkeep cost is determined by the oracle based on the number of chargeable entries |
|
| 315 | 8× | uint256 totalCost = paymentsEnabled |
| 316 | 35× | ? SUPER_GOVERNOR.getUpkeepCostPerBatchUpdate( |
| 317 | 1× | msg.sender, |
| 318 | chargeableCount |
|
| 319 | ) |
|
| 320 | 1× | : 0; |
| 321 | ||
| 322 | // Compute per-entry charge |
|
| 323 | 1× | uint256 perEntry = 0; |
| 324 | 10× | if (paymentsEnabled && chargeableCount > 0) { |
| 325 | 0 | perEntry = totalCost / chargeableCount; |
| 326 | } |
|
| 327 | ||
| 328 | // Process all valid strategies |
|
| 329 | 16× | for (uint256 i; i < strategiesLength; ++i) { |
| 330 | // Skip invalid strategies without reverting |
|
| 331 | 10× | if (!_superVaultStrategies.contains(args.strategies[i])) continue; |
| 332 | ||
| 333 | // Skip when invalid timestamp is provided (future timestamp) |
|
| 334 | 27× | if (args.timestamps[i] > block.timestamp) { |
| 335 | 16× | emit ProvidedTimestampExceedsBlockTimestamp( |
| 336 | 28× | args.strategies[i], |
| 337 | 21× | args.timestamps[i], |
| 338 | 1× | block.timestamp |
| 339 | ); |
|
| 340 | 2× | continue; |
| 341 | } |
|
| 342 | ||
| 343 | 1× | uint256 upkeepCost; |
| 344 | 4× | 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 | 1× | if ( |
| 348 | 0 | block.timestamp - args.timestamps[i] > |
| 349 | 0 | _strategyData[args.strategies[i]].maxStaleness |
| 350 | ) { |
|
| 351 | 0 | upkeepCost = 0; |
| 352 | 0 | emit StaleUpdate( |
| 353 | 0 | args.strategies[i], |
| 354 | 0 | args.updateAuthority, |
| 355 | 0 | args.timestamps[i] |
| 356 | ); |
|
| 357 | 0 | } else { |
| 358 | 0 | address manager = _strategyData[args.strategies[i]] |
| 359 | .mainManager; |
|
| 360 | 0 | if ( |
| 361 | 0 | SUPER_GOVERNOR.isSuperformManager(manager) || |
| 362 | 0 | _strategyData[args.strategies[i]] |
| 363 | .authorizedCallers |
|
| 364 | 0 | .contains(args.updateAuthority) |
| 365 | ) { |
|
| 366 | 0 | upkeepCost = 0; |
| 367 | } else { |
|
| 368 | // Split the total batch cost fairly across chargeable entries |
|
| 369 | 0 | upkeepCost = perEntry; |
| 370 | } |
|
| 371 | } |
|
| 372 | } |
|
| 373 | ||
| 374 | // Forward update |
|
| 375 | 5× | _forwardPPS( |
| 376 | 44× | PPSUpdateData({ |
| 377 | 28× | strategy: args.strategies[i], |
| 378 | 6× | isExempt: (!paymentsEnabled) || (upkeepCost == 0), // If payments are disabled or the update is exempt from UP payments |
| 379 | 24× | pps: args.ppss[i], |
| 380 | 24× | ppsStdev: args.ppsStdevs[i], |
| 381 | 24× | validatorSet: args.validatorSets[i], |
| 382 | 24× | totalValidators: args.totalValidators[i], |
| 383 | 24× | timestamp: args.timestamps[i], |
| 384 | 1× | upkeepCost: upkeepCost |
| 385 | }) |
|
| 386 | ); |
|
| 387 | } |
|
| 388 | } |
|
| 389 | ||
| 390 | /*////////////////////////////////////////////////////////////// |
|
| 391 | UPKEEP MANAGEMENT |
|
| 392 | //////////////////////////////////////////////////////////////*/ |
|
| 393 | /// @inheritdoc ISuperVaultAggregator |
|
| 394 | 11× | function depositUpkeep(address manager, uint256 amount) external { |
| 395 | 19× | if (amount == 0) revert ZERO_AMOUNT(); |
| 396 | ||
| 397 | // Get the UP token address from SUPER_GOVERNOR |
|
| 398 | 121× | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 399 | ||
| 400 | // Transfer UP tokens from msg.sender to this contract |
|
| 401 | 9× | IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount); |
| 402 | ||
| 403 | // Update upkeep balance |
|
| 404 | 27× | _managerUpkeepBalance[manager] += amount; |
| 405 | ||
| 406 | 8× | emit UpkeepDeposited(manager, amount); |
| 407 | } |
|
| 408 | ||
| 409 | /// @inheritdoc ISuperVaultAggregator |
|
| 410 | 22× | function claimUpkeep(uint256 amount) external { |
| 411 | // Only SUPER_GOVERNOR can claim upkeep |
|
| 412 | 13× | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 413 | 13× | revert CALLER_NOT_AUTHORIZED(); |
| 414 | } |
|
| 415 | ||
| 416 | 21× | if (claimableUpkeep < amount) revert INSUFFICIENT_UPKEEP(); |
| 417 | 19× | claimableUpkeep -= amount; |
| 418 | ||
| 419 | // Get the UP token address from SUPER_GOVERNOR |
|
| 420 | 122× | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 421 | ||
| 422 | // Transfer UP tokens to `SuperBank` |
|
| 423 | 7× | address _superBank = _getSuperBank(); |
| 424 | 7× | IERC20(upToken).safeTransfer(_superBank, amount); |
| 425 | 8× | emit UpkeepClaimed(_superBank, amount); |
| 426 | } |
|
| 427 | ||
| 428 | /// @inheritdoc ISuperVaultAggregator |
|
| 429 | 11× | function withdrawUpkeep(uint256 amount) external { |
| 430 | 19× | if (amount == 0) revert ZERO_AMOUNT(); |
| 431 | ||
| 432 | // Check sufficient balance |
|
| 433 | 17× | if (_managerUpkeepBalance[msg.sender] < amount) { |
| 434 | 13× | revert INSUFFICIENT_UPKEEP_BALANCE(); |
| 435 | } |
|
| 436 | ||
| 437 | // Get the UP token address from SUPER_GOVERNOR |
|
| 438 | 122× | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 439 | ||
| 440 | // Update upkeep balance |
|
| 441 | unchecked { |
|
| 442 | 19× | _managerUpkeepBalance[msg.sender] -= amount; |
| 443 | } |
|
| 444 | ||
| 445 | // Transfer UP tokens to manager |
|
| 446 | 8× | IERC20(upToken).safeTransfer(msg.sender, amount); |
| 447 | ||
| 448 | 7× | emit UpkeepWithdrawn(msg.sender, amount); |
| 449 | } |
|
| 450 | ||
| 451 | /*////////////////////////////////////////////////////////////// |
|
| 452 | STAKE MANAGEMENT |
|
| 453 | //////////////////////////////////////////////////////////////*/ |
|
| 454 | /// @notice Deposits UP tokens as stake for manager economic security |
|
| 455 | /// @param manager Address of the manager to deposit stake for |
|
| 456 | /// @param amount Amount of UP tokens to deposit as stake |
|
| 457 | 19× | function depositStake(address manager, uint256 amount) external { |
| 458 | 19× | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 459 | 18× | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 460 | ||
| 461 | // Get the UP token address from SUPER_GOVERNOR |
|
| 462 | 121× | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 463 | ||
| 464 | // Transfer UP tokens from msg.sender to this contract |
|
| 465 | 9× | IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount); |
| 466 | ||
| 467 | // Update stake balance |
|
| 468 | 27× | _managerStakeBalance[manager] += amount; |
| 469 | ||
| 470 | 23× | emit StakeDeposited(manager, amount); |
| 471 | } |
|
| 472 | ||
| 473 | /// @notice Withdraws UP tokens from stake balance |
|
| 474 | /// @param amount Amount of UP tokens to withdraw from stake |
|
| 475 | 11× | function withdrawStake(uint256 amount) external { |
| 476 | 19× | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 477 | ||
| 478 | // Check sufficient balance |
|
| 479 | 17× | if (_managerStakeBalance[msg.sender] < amount) { |
| 480 | 13× | revert INSUFFICIENT_STAKE_BALANCE(); |
| 481 | } |
|
| 482 | ||
| 483 | // Get the UP token address from SUPER_GOVERNOR |
|
| 484 | 122× | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 485 | ||
| 486 | // Update stake balance |
|
| 487 | unchecked { |
|
| 488 | 19× | _managerStakeBalance[msg.sender] -= amount; |
| 489 | } |
|
| 490 | ||
| 491 | // Transfer UP tokens to manager |
|
| 492 | 8× | IERC20(upToken).safeTransfer(msg.sender, amount); |
| 493 | ||
| 494 | 7× | emit StakeWithdrawn(msg.sender, amount); |
| 495 | } |
|
| 496 | ||
| 497 | /// @notice Slashes a manager's stake balance by a specified amount |
|
| 498 | /// @param manager The manager whose stake will be slashed |
|
| 499 | /// @param amount The amount of UP tokens to slash from the manager's stake balance |
|
| 500 | 11× | function slashStake(address manager, uint256 amount) external { |
| 501 | // Only SUPER_GOVERNOR can slash stake |
|
| 502 | 6× | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 503 | 13× | revert CALLER_NOT_AUTHORIZED(); |
| 504 | } |
|
| 505 | ||
| 506 | // Validate inputs |
|
| 507 | 0 | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 508 | 0 | if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 509 | ||
| 510 | // Check if manager has sufficient stake balance to slash |
|
| 511 | 0 | if (_managerStakeBalance[manager] < amount) { |
| 512 | 0 | revert INSUFFICIENT_STAKE_BALANCE(); |
| 513 | } |
|
| 514 | ||
| 515 | // Reduce manager's stake balance |
|
| 516 | 0 | _managerStakeBalance[manager] -= amount; |
| 517 | ||
| 518 | // Get the UP token address and SuperBank address |
|
| 519 | 0 | address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
| 520 | 0 | address superBank = _getSuperBank(); |
| 521 | ||
| 522 | // Transfer slashed amount directly to SuperBank |
|
| 523 | 0 | IERC20(upToken).safeTransfer(superBank, amount); |
| 524 | ||
| 525 | // Emit event for transparency |
|
| 526 | 0 | emit StakeSlashed(manager, amount); |
| 527 | } |
|
| 528 | ||
| 529 | /*////////////////////////////////////////////////////////////// |
|
| 530 | AUTHORIZED CALLER MANAGEMENT |
|
| 531 | //////////////////////////////////////////////////////////////*/ |
|
| 532 | /// @inheritdoc ISuperVaultAggregator |
|
| 533 | 15× | function addAuthorizedCaller( |
| 534 | address strategy, |
|
| 535 | address caller |
|
| 536 | 2× | ) external validStrategy(strategy) { |
| 537 | // Either primary or secondary manager can add authorized callers |
|
| 538 | 9× | if (!isAnyManager(msg.sender, strategy)) |
| 539 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 540 | ||
| 541 | 18× | if (caller == address(0)) revert ZERO_ADDRESS(); |
| 542 | ||
| 543 | // Prevent managers from adding protected keepers to circumvent fees |
|
| 544 | 60× | if (SUPER_GOVERNOR.isProtectedKeeper(caller)) { |
| 545 | 0 | revert CANNOT_ADD_PROTECTED_KEEPER(); |
| 546 | } |
|
| 547 | ||
| 548 | // Check if caller is already authorized and add if not |
|
| 549 | 23× | if (!_strategyData[strategy].authorizedCallers.add(caller)) { |
| 550 | 13× | revert CALLER_ALREADY_AUTHORIZED(); |
| 551 | } |
|
| 552 | 14× | emit AuthorizedCallerAdded(strategy, caller); |
| 553 | } |
|
| 554 | ||
| 555 | /// @inheritdoc ISuperVaultAggregator |
|
| 556 | 15× | function removeAuthorizedCaller( |
| 557 | address strategy, |
|
| 558 | address caller |
|
| 559 | 2× | ) external validStrategy(strategy) { |
| 560 | // Either primary or secondary manager can remove authorized callers |
|
| 561 | 9× | if (!isAnyManager(msg.sender, strategy)) |
| 562 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 563 | ||
| 564 | // Remove the caller |
|
| 565 | 23× | if (!_strategyData[strategy].authorizedCallers.remove(caller)) { |
| 566 | 13× | revert CALLER_NOT_AUTHORIZED(); |
| 567 | } |
|
| 568 | 14× | emit AuthorizedCallerRemoved(strategy, caller); |
| 569 | } |
|
| 570 | ||
| 571 | /*////////////////////////////////////////////////////////////// |
|
| 572 | MANAGER MANAGEMENT FUNCTIONS |
|
| 573 | //////////////////////////////////////////////////////////////*/ |
|
| 574 | /// @inheritdoc ISuperVaultAggregator |
|
| 575 | 15× | function addSecondaryManager( |
| 576 | address strategy, |
|
| 577 | address manager |
|
| 578 | 2× | ) external validStrategy(strategy) { |
| 579 | // Only the primary manager can add secondary managers |
|
| 580 | 25× | if (msg.sender != _strategyData[strategy].mainManager) |
| 581 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 582 | ||
| 583 | 18× | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 584 | ||
| 585 | // Check if manager is already the primary manager |
|
| 586 | 30× | if (_strategyData[strategy].mainManager == manager) |
| 587 | 13× | revert MANAGER_ALREADY_EXISTS(); |
| 588 | ||
| 589 | // Enforce a cap on secondary managers to prevent governance DoS on changePrimaryManager |
|
| 590 | 3× | if ( |
| 591 | 20× | _strategyData[strategy].secondaryManagers.length() >= |
| 592 | MAX_SECONDARY_MANAGERS |
|
| 593 | ) { |
|
| 594 | 0 | revert TOO_MANY_SECONDARY_MANAGERS(); |
| 595 | } |
|
| 596 | ||
| 597 | // Add as secondary manager using EnumerableSet |
|
| 598 | 23× | if (!_strategyData[strategy].secondaryManagers.add(manager)) |
| 599 | 13× | revert MANAGER_ALREADY_EXISTS(); |
| 600 | ||
| 601 | 14× | emit SecondaryManagerAdded(strategy, manager); |
| 602 | } |
|
| 603 | ||
| 604 | /// @inheritdoc ISuperVaultAggregator |
|
| 605 | 15× | function removeSecondaryManager( |
| 606 | address strategy, |
|
| 607 | address manager |
|
| 608 | 2× | ) external validStrategy(strategy) { |
| 609 | // Only the primary manager can remove secondary managers |
|
| 610 | 25× | if (msg.sender != _strategyData[strategy].mainManager) |
| 611 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 612 | ||
| 613 | // Remove the manager using EnumerableSet |
|
| 614 | 23× | if (!_strategyData[strategy].secondaryManagers.remove(manager)) |
| 615 | 13× | revert MANAGER_NOT_FOUND(); |
| 616 | ||
| 617 | 14× | emit SecondaryManagerRemoved(strategy, manager); |
| 618 | } |
|
| 619 | ||
| 620 | /// @inheritdoc ISuperVaultAggregator |
|
| 621 | 17× | function updatePPSVerificationThresholds( |
| 622 | address strategy, |
|
| 623 | uint256 dispersionThreshold_, |
|
| 624 | uint256 deviationThreshold_, |
|
| 625 | uint256 mnThreshold_ |
|
| 626 | 2× | ) external validStrategy(strategy) { |
| 627 | // Since this is a risky call, we only allow main managers as callers |
|
| 628 | 25× | if (msg.sender != _strategyData[strategy].mainManager) { |
| 629 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 630 | } |
|
| 631 | ||
| 632 | // Update the thresholds |
|
| 633 | 22× | _strategyData[strategy].dispersionThreshold = dispersionThreshold_; |
| 634 | 6× | _strategyData[strategy].deviationThreshold = deviationThreshold_; |
| 635 | 5× | _strategyData[strategy].mnThreshold = mnThreshold_; |
| 636 | ||
| 637 | // Emit the event |
|
| 638 | 12× | emit PPSVerificationThresholdsUpdated( |
| 639 | strategy, |
|
| 640 | dispersionThreshold_, |
|
| 641 | deviationThreshold_, |
|
| 642 | mnThreshold_ |
|
| 643 | ); |
|
| 644 | } |
|
| 645 | ||
| 646 | /// @inheritdoc ISuperVaultAggregator |
|
| 647 | 0 | function changeGlobalLeavesStatus( |
| 648 | bytes32[] memory leaves, |
|
| 649 | bool[] memory statuses, |
|
| 650 | address strategy |
|
| 651 | 0 | ) external validStrategy(strategy) { |
| 652 | // Only the primary manager can change global leaves status |
|
| 653 | 0 | if (msg.sender != _strategyData[strategy].mainManager) { |
| 654 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 655 | } |
|
| 656 | 0 | uint256 leavesLen = leaves.length; |
| 657 | // Check array lengths match |
|
| 658 | 0 | if (leavesLen != statuses.length) { |
| 659 | 0 | revert MISMATCHED_ARRAY_LENGTHS(); |
| 660 | } |
|
| 661 | ||
| 662 | // Update banned status for each leaf |
|
| 663 | 0 | for (uint256 i; i < leavesLen; i++) { |
| 664 | 0 | _strategyData[strategy].bannedLeaves[leaves[i]] = statuses[i]; |
| 665 | } |
|
| 666 | ||
| 667 | // Emit event |
|
| 668 | 0 | emit GlobalLeavesStatusChanged(strategy, leaves, statuses); |
| 669 | } |
|
| 670 | ||
| 671 | /// @inheritdoc ISuperVaultAggregator |
|
| 672 | 26× | function changePrimaryManager( |
| 673 | address strategy, |
|
| 674 | address newManager |
|
| 675 | 6× | ) external validStrategy(strategy) { |
| 676 | // Only SuperGovernor can call this |
|
| 677 | 13× | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 678 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 679 | } |
|
| 680 | ||
| 681 | 5× | if (strategy == address(0)) revert ZERO_ADDRESS(); |
| 682 | ||
| 683 | 5× | if (newManager == address(0)) revert ZERO_ADDRESS(); |
| 684 | ||
| 685 | 24× | address oldManager = _strategyData[strategy].mainManager; |
| 686 | ||
| 687 | // SECURITY: Clear any pending manager proposals to prevent malicious re-takeover |
|
| 688 | 8× | _strategyData[strategy].proposedManager = address(0); |
| 689 | 6× | _strategyData[strategy].managerChangeEffectiveTime = 0; |
| 690 | ||
| 691 | // SECURITY: Clear any pending hooks root proposals to prevent malicious hook updates |
|
| 692 | 6× | _strategyData[strategy].proposedHooksRoot = bytes32(0); |
| 693 | 6× | _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 | 8× | address[] memory clearedSecondaryManagers = _strategyData[strategy] |
| 698 | .secondaryManagers |
|
| 699 | .values(); |
|
| 700 | ||
| 701 | // Clear the entire secondary managers set |
|
| 702 | 14× | for (uint256 i = 0; i < clearedSecondaryManagers.length; i++) { |
| 703 | 28× | _strategyData[strategy].secondaryManagers.remove( |
| 704 | 15× | clearedSecondaryManagers[i] |
| 705 | ); |
|
| 706 | 28× | emit SecondaryManagerRemoved(strategy, clearedSecondaryManagers[i]); |
| 707 | } |
|
| 708 | ||
| 709 | // Set the new primary manager |
|
| 710 | 36× | _strategyData[strategy].mainManager = newManager; |
| 711 | ||
| 712 | 8× | emit PrimaryManagerChanged(strategy, oldManager, newManager); |
| 713 | } |
|
| 714 | ||
| 715 | /// @inheritdoc ISuperVaultAggregator |
|
| 716 | 15× | function proposeChangePrimaryManager( |
| 717 | address strategy, |
|
| 718 | address newManager |
|
| 719 | 3× | ) external validStrategy(strategy) { |
| 720 | // Only secondary managers can propose changes to the primary manager |
|
| 721 | 23× | if (!_strategyData[strategy].secondaryManagers.contains(msg.sender)) { |
| 722 | 13× | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 723 | } |
|
| 724 | ||
| 725 | 5× | if (newManager == address(0)) revert ZERO_ADDRESS(); |
| 726 | ||
| 727 | // Set up the proposal with 7-day timelock |
|
| 728 | 6× | uint256 effectiveTime = block.timestamp + _MANAGER_CHANGE_TIMELOCK; |
| 729 | ||
| 730 | // Store proposal in the strategy data |
|
| 731 | 33× | _strategyData[strategy].proposedManager = newManager; |
| 732 | 7× | _strategyData[strategy].managerChangeEffectiveTime = effectiveTime; |
| 733 | ||
| 734 | 11× | emit PrimaryManagerChangeProposed( |
| 735 | strategy, |
|
| 736 | 2× | msg.sender, |
| 737 | newManager, |
|
| 738 | effectiveTime |
|
| 739 | ); |
|
| 740 | } |
|
| 741 | ||
| 742 | /// @inheritdoc ISuperVaultAggregator |
|
| 743 | 14× | function executeChangePrimaryManager( |
| 744 | address strategy |
|
| 745 | 4× | ) external validStrategy(strategy) { |
| 746 | // Check if there is a pending proposal |
|
| 747 | 20× | if (_strategyData[strategy].proposedManager == address(0)) |
| 748 | 13× | revert NO_PENDING_MANAGER_CHANGE(); |
| 749 | ||
| 750 | // Check if the timelock period has passed |
|
| 751 | 4× | if ( |
| 752 | 17× | block.timestamp < _strategyData[strategy].managerChangeEffectiveTime |
| 753 | 13× | ) revert TIMELOCK_NOT_EXPIRED(); |
| 754 | ||
| 755 | 23× | address newManager = _strategyData[strategy].proposedManager; |
| 756 | 9× | address oldManager = _strategyData[strategy].mainManager; |
| 757 | ||
| 758 | // If new manager is already a secondary manager, remove them |
|
| 759 | 7× | _strategyData[strategy].secondaryManagers.remove(newManager); |
| 760 | ||
| 761 | // Make the old primary manager a secondary manager |
|
| 762 | 4× | if ( |
| 763 | 20× | _strategyData[strategy].secondaryManagers.length() < |
| 764 | MAX_SECONDARY_MANAGERS |
|
| 765 | ) { |
|
| 766 | 21× | _strategyData[strategy].secondaryManagers.add(oldManager); |
| 767 | } |
|
| 768 | ||
| 769 | // Set the new primary manager |
|
| 770 | 37× | _strategyData[strategy].mainManager = newManager; |
| 771 | ||
| 772 | // Clear the proposal |
|
| 773 | 9× | _strategyData[strategy].proposedManager = address(0); |
| 774 | ||
| 775 | 8× | emit PrimaryManagerChanged(strategy, oldManager, newManager); |
| 776 | } |
|
| 777 | ||
| 778 | /*////////////////////////////////////////////////////////////// |
|
| 779 | HOOK VALIDATION FUNCTIONS |
|
| 780 | //////////////////////////////////////////////////////////////*/ |
|
| 781 | /// @inheritdoc ISuperVaultAggregator |
|
| 782 | 0 | function setHooksRootUpdateTimelock(uint256 newTimelock) external { |
| 783 | // Only SUPER_GOVERNOR can update the timelock |
|
| 784 | 0 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 785 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 786 | } |
|
| 787 | ||
| 788 | // Update the timelock |
|
| 789 | 0 | _hooksRootUpdateTimelock = newTimelock; |
| 790 | ||
| 791 | 0 | emit HooksRootUpdateTimelockChanged(newTimelock); |
| 792 | } |
|
| 793 | ||
| 794 | /// @inheritdoc ISuperVaultAggregator |
|
| 795 | 11× | function proposeGlobalHooksRoot(bytes32 newRoot) external { |
| 796 | // Only SUPER_GOVERNOR can update the global hooks root |
|
| 797 | 7× | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 798 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 799 | } |
|
| 800 | ||
| 801 | // Set new root with timelock |
|
| 802 | 4× | _proposedGlobalHooksRoot = newRoot; |
| 803 | 12× | uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
| 804 | 4× | _globalHooksRootEffectiveTime = effectiveTime; |
| 805 | ||
| 806 | 7× | emit GlobalHooksRootUpdateProposed(newRoot, effectiveTime); |
| 807 | } |
|
| 808 | ||
| 809 | /// @inheritdoc ISuperVaultAggregator |
|
| 810 | 10× | function executeGlobalHooksRootUpdate() external { |
| 811 | 0 | bytes32 proposedRoot = _proposedGlobalHooksRoot; |
| 812 | // Ensure there is a pending proposal |
|
| 813 | 0 | if (proposedRoot == bytes32(0)) { |
| 814 | 0 | revert NO_PENDING_GLOBAL_ROOT_CHANGE(); |
| 815 | } |
|
| 816 | ||
| 817 | // Check if timelock period has elapsed |
|
| 818 | 0 | if (block.timestamp < _globalHooksRootEffectiveTime) { |
| 819 | 0 | revert ROOT_UPDATE_NOT_READY(); |
| 820 | } |
|
| 821 | ||
| 822 | // Update the global hooks root |
|
| 823 | 0 | bytes32 oldRoot = _globalHooksRoot; |
| 824 | 0 | _globalHooksRoot = _proposedGlobalHooksRoot; |
| 825 | 0 | _globalHooksRootEffectiveTime = 0; |
| 826 | 0 | _proposedGlobalHooksRoot = bytes32(0); |
| 827 | ||
| 828 | 16× | emit GlobalHooksRootUpdated(oldRoot, proposedRoot); |
| 829 | } |
|
| 830 | ||
| 831 | /// @inheritdoc ISuperVaultAggregator |
|
| 832 | 0 | function setGlobalHooksRootVetoStatus(bool vetoed) external { |
| 833 | // Only SuperGovernor can call this |
|
| 834 | 0 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 835 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 836 | } |
|
| 837 | ||
| 838 | // Don't emit event if status doesn't change |
|
| 839 | 0 | if (_globalHooksRootVetoed == vetoed) { |
| 840 | return; |
|
| 841 | } |
|
| 842 | ||
| 843 | // Update veto status |
|
| 844 | 0 | _globalHooksRootVetoed = vetoed; |
| 845 | ||
| 846 | 0 | emit GlobalHooksRootVetoStatusChanged(vetoed, _globalHooksRoot); |
| 847 | } |
|
| 848 | ||
| 849 | /// @inheritdoc ISuperVaultAggregator |
|
| 850 | 0 | function proposeStrategyHooksRoot( |
| 851 | address strategy, |
|
| 852 | bytes32 newRoot |
|
| 853 | 0 | ) external validStrategy(strategy) { |
| 854 | // Only the main manager can propose strategy-specific hooks root |
|
| 855 | 0 | if (_strategyData[strategy].mainManager != msg.sender) { |
| 856 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 857 | } |
|
| 858 | ||
| 859 | // Set proposed root with timelock |
|
| 860 | 0 | _strategyData[strategy].proposedHooksRoot = newRoot; |
| 861 | 0 | uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
| 862 | 0 | _strategyData[strategy].hooksRootEffectiveTime = effectiveTime; |
| 863 | ||
| 864 | 0 | emit StrategyHooksRootUpdateProposed( |
| 865 | strategy, |
|
| 866 | 0 | msg.sender, |
| 867 | newRoot, |
|
| 868 | effectiveTime |
|
| 869 | ); |
|
| 870 | } |
|
| 871 | ||
| 872 | /// @inheritdoc ISuperVaultAggregator |
|
| 873 | 0 | function executeStrategyHooksRootUpdate( |
| 874 | address strategy |
|
| 875 | 0 | ) external validStrategy(strategy) { |
| 876 | 0 | bytes32 proposedRoot = _strategyData[strategy].proposedHooksRoot; |
| 877 | // Ensure there is a pending proposal |
|
| 878 | 0 | if (proposedRoot == bytes32(0)) { |
| 879 | 0 | revert NO_PENDING_MANAGER_CHANGE(); // Reusing error for simplicity |
| 880 | } |
|
| 881 | ||
| 882 | // Check if timelock period has elapsed |
|
| 883 | 0 | if (block.timestamp < _strategyData[strategy].hooksRootEffectiveTime) { |
| 884 | 0 | revert ROOT_UPDATE_NOT_READY(); |
| 885 | } |
|
| 886 | ||
| 887 | // Update the strategy's hooks root |
|
| 888 | 0 | bytes32 oldRoot = _strategyData[strategy].managerHooksRoot; |
| 889 | 0 | _strategyData[strategy].managerHooksRoot = proposedRoot; |
| 890 | ||
| 891 | // Reset proposal state |
|
| 892 | 0 | _strategyData[strategy].proposedHooksRoot = bytes32(0); |
| 893 | 0 | _strategyData[strategy].hooksRootEffectiveTime = 0; |
| 894 | ||
| 895 | 0 | emit StrategyHooksRootUpdated(strategy, oldRoot, proposedRoot); |
| 896 | } |
|
| 897 | ||
| 898 | /// @inheritdoc ISuperVaultAggregator |
|
| 899 | 8× | function setStrategyHooksRootVetoStatus( |
| 900 | address strategy, |
|
| 901 | bool vetoed |
|
| 902 | 0 | ) external validStrategy(strategy) { |
| 903 | // Only SuperGovernor can call this |
|
| 904 | 0 | if (msg.sender != address(SUPER_GOVERNOR)) { |
| 905 | 0 | revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 906 | } |
|
| 907 | ||
| 908 | // Don't emit event if status doesn't change |
|
| 909 | 0 | if (_strategyData[strategy].hooksRootVetoed == vetoed) { |
| 910 | 0 | return; |
| 911 | } |
|
| 912 | ||
| 913 | // Update veto status |
|
| 914 | 0 | _strategyData[strategy].hooksRootVetoed = vetoed; |
| 915 | ||
| 916 | 0 | emit StrategyHooksRootVetoStatusChanged( |
| 917 | strategy, |
|
| 918 | vetoed, |
|
| 919 | 0 | _strategyData[strategy].managerHooksRoot |
| 920 | ); |
|
| 921 | } |
|
| 922 | /// @inheritdoc ISuperVaultAggregator |
|
| 923 | ||
| 924 | 6× | function isGlobalHooksRootVetoed() external view returns (bool vetoed) { |
| 925 | 8× | return _globalHooksRootVetoed; |
| 926 | } |
|
| 927 | ||
| 928 | /// @inheritdoc ISuperVaultAggregator |
|
| 929 | 0 | function isStrategyHooksRootVetoed( |
| 930 | address strategy |
|
| 931 | 0 | ) external view returns (bool vetoed) { |
| 932 | 0 | return _strategyData[strategy].hooksRootVetoed; |
| 933 | } |
|
| 934 | ||
| 935 | /*////////////////////////////////////////////////////////////// |
|
| 936 | VIEW FUNCTIONS |
|
| 937 | //////////////////////////////////////////////////////////////*/ |
|
| 938 | ||
| 939 | /// @inheritdoc ISuperVaultAggregator |
|
| 940 | 0 | function getCurrentNonce() external view returns (uint256) { |
| 941 | 0 | return _vaultCreationNonce; |
| 942 | } |
|
| 943 | ||
| 944 | /// @inheritdoc ISuperVaultAggregator |
|
| 945 | 0 | function getHooksRootUpdateTimelock() external view returns (uint256) { |
| 946 | 0 | return _hooksRootUpdateTimelock; |
| 947 | } |
|
| 948 | ||
| 949 | /// @inheritdoc ISuperVaultAggregator |
|
| 950 | 72× | function getPPS( |
| 951 | address strategy |
|
| 952 | 18× | ) external view validStrategy(strategy) returns (uint256 pps) { |
| 953 | 78× | return _strategyData[strategy].pps; |
| 954 | } |
|
| 955 | ||
| 956 | /// @inheritdoc ISuperVaultAggregator |
|
| 957 | 0 | function getPPSWithStdDev( |
| 958 | address strategy |
|
| 959 | ) |
|
| 960 | external |
|
| 961 | view |
|
| 962 | 0 | validStrategy(strategy) |
| 963 | 0 | returns (uint256 pps, uint256 ppsStdev) |
| 964 | { |
|
| 965 | 0 | return (_strategyData[strategy].pps, _strategyData[strategy].ppsStdev); |
| 966 | } |
|
| 967 | ||
| 968 | /// @inheritdoc ISuperVaultAggregator |
|
| 969 | 0 | function getLastUpdateTimestamp( |
| 970 | address strategy |
|
| 971 | 0 | ) external view returns (uint256 timestamp) { |
| 972 | 0 | return _strategyData[strategy].lastUpdateTimestamp; |
| 973 | } |
|
| 974 | ||
| 975 | /// @inheritdoc ISuperVaultAggregator |
|
| 976 | 0 | function getMinUpdateInterval( |
| 977 | address strategy |
|
| 978 | 0 | ) external view returns (uint256 interval) { |
| 979 | 0 | return _strategyData[strategy].minUpdateInterval; |
| 980 | } |
|
| 981 | ||
| 982 | /// @inheritdoc ISuperVaultAggregator |
|
| 983 | 0 | function getMaxStaleness( |
| 984 | address strategy |
|
| 985 | 0 | ) external view returns (uint256 staleness) { |
| 986 | 0 | return _strategyData[strategy].maxStaleness; |
| 987 | } |
|
| 988 | ||
| 989 | /// @inheritdoc ISuperVaultAggregator |
|
| 990 | 0 | function getPPSVerificationThresholds( |
| 991 | address strategy |
|
| 992 | ) |
|
| 993 | external |
|
| 994 | view |
|
| 995 | 0 | validStrategy(strategy) |
| 996 | returns ( |
|
| 997 | 0 | uint256 dispersionThreshold, |
| 998 | uint256 deviationThreshold, |
|
| 999 | uint256 mnThreshold |
|
| 1000 | ) |
|
| 1001 | { |
|
| 1002 | return ( |
|
| 1003 | 0 | _strategyData[strategy].dispersionThreshold, |
| 1004 | 0 | _strategyData[strategy].deviationThreshold, |
| 1005 | 0 | _strategyData[strategy].mnThreshold |
| 1006 | ); |
|
| 1007 | } |
|
| 1008 | ||
| 1009 | /// @inheritdoc ISuperVaultAggregator |
|
| 1010 | 54× | function isStrategyPaused( |
| 1011 | address strategy |
|
| 1012 | 6× | ) external view returns (bool isPaused) { |
| 1013 | 96× | return _strategyData[strategy].isPaused; |
| 1014 | } |
|
| 1015 | ||
| 1016 | /// @inheritdoc ISuperVaultAggregator |
|
| 1017 | 0 | function getUpkeepBalance( |
| 1018 | address manager |
|
| 1019 | 0 | ) external view returns (uint256 balance) { |
| 1020 | 0 | return _managerUpkeepBalance[manager]; |
| 1021 | } |
|
| 1022 | ||
| 1023 | /// @notice Gets the current stake balance for a manager |
|
| 1024 | /// @param manager Address of the manager |
|
| 1025 | /// @return balance Current stake balance in UP tokens |
|
| 1026 | 0 | function getStakeBalance( |
| 1027 | address manager |
|
| 1028 | 0 | ) external view returns (uint256 balance) { |
| 1029 | 0 | return _managerStakeBalance[manager]; |
| 1030 | } |
|
| 1031 | ||
| 1032 | /// @inheritdoc ISuperVaultAggregator |
|
| 1033 | 0 | function getAuthorizedCallers( |
| 1034 | address strategy |
|
| 1035 | 0 | ) external view returns (address[] memory callers) { |
| 1036 | 0 | return _strategyData[strategy].authorizedCallers.values(); |
| 1037 | } |
|
| 1038 | ||
| 1039 | /// @inheritdoc ISuperVaultAggregator |
|
| 1040 | 0 | function getMainManager( |
| 1041 | address strategy |
|
| 1042 | 0 | ) external view returns (address manager) { |
| 1043 | 0 | manager = _strategyData[strategy].mainManager; |
| 1044 | 0 | if (manager == address(0)) revert ZERO_ADDRESS(); |
| 1045 | ||
| 1046 | return manager; |
|
| 1047 | } |
|
| 1048 | ||
| 1049 | /// @inheritdoc ISuperVaultAggregator |
|
| 1050 | 9× | function isMainManager( |
| 1051 | address manager, |
|
| 1052 | address strategy |
|
| 1053 | 1× | ) external view returns (bool) { |
| 1054 | 24× | return _strategyData[strategy].mainManager == manager; |
| 1055 | } |
|
| 1056 | ||
| 1057 | /// @inheritdoc ISuperVaultAggregator |
|
| 1058 | 0 | function getSecondaryManagers( |
| 1059 | address strategy |
|
| 1060 | 0 | ) external view returns (address[] memory) { |
| 1061 | 0 | return _strategyData[strategy].secondaryManagers.values(); |
| 1062 | } |
|
| 1063 | ||
| 1064 | /// @inheritdoc ISuperVaultAggregator |
|
| 1065 | 0 | function isSecondaryManager( |
| 1066 | address manager, |
|
| 1067 | address strategy |
|
| 1068 | 0 | ) external view returns (bool) { |
| 1069 | 0 | return _strategyData[strategy].secondaryManagers.contains(manager); |
| 1070 | } |
|
| 1071 | ||
| 1072 | /// @inheritdoc ISuperVaultAggregator |
|
| 1073 | 23× | function isAnyManager( |
| 1074 | address manager, |
|
| 1075 | address strategy |
|
| 1076 | 9× | ) public view returns (bool) { |
| 1077 | // Check if primary manager |
|
| 1078 | 81× | if (_strategyData[strategy].mainManager == manager) { |
| 1079 | 9× | return true; |
| 1080 | } |
|
| 1081 | ||
| 1082 | // Check if secondary manager using EnumerableSet |
|
| 1083 | 57× | return _strategyData[strategy].secondaryManagers.contains(manager); |
| 1084 | } |
|
| 1085 | ||
| 1086 | /// @inheritdoc ISuperVaultAggregator |
|
| 1087 | 0 | function getAllSuperVaults() external view returns (address[] memory) { |
| 1088 | 0 | return _superVaults.values(); |
| 1089 | } |
|
| 1090 | ||
| 1091 | /// @inheritdoc ISuperVaultAggregator |
|
| 1092 | 8× | function superVaults(uint256 index) external view returns (address) { |
| 1093 | 0 | if (index >= _superVaults.length()) revert INDEX_OUT_OF_BOUNDS(); |
| 1094 | 8× | return _superVaults.at(index); |
| 1095 | } |
|
| 1096 | ||
| 1097 | /// @inheritdoc ISuperVaultAggregator |
|
| 1098 | 0 | function getAllSuperVaultStrategies() |
| 1099 | external |
|
| 1100 | view |
|
| 1101 | 0 | returns (address[] memory) |
| 1102 | { |
|
| 1103 | 0 | return _superVaultStrategies.values(); |
| 1104 | } |
|
| 1105 | ||
| 1106 | /// @inheritdoc ISuperVaultAggregator |
|
| 1107 | 0 | function superVaultStrategies( |
| 1108 | uint256 index |
|
| 1109 | 0 | ) external view returns (address) { |
| 1110 | 0 | if (index >= _superVaultStrategies.length()) |
| 1111 | 0 | revert INDEX_OUT_OF_BOUNDS(); |
| 1112 | 0 | return _superVaultStrategies.at(index); |
| 1113 | } |
|
| 1114 | ||
| 1115 | /// @inheritdoc ISuperVaultAggregator |
|
| 1116 | 2× | function getAllSuperVaultEscrows() |
| 1117 | external |
|
| 1118 | view |
|
| 1119 | 0 | returns (address[] memory) |
| 1120 | { |
|
| 1121 | 3× | return _superVaultEscrows.values(); |
| 1122 | } |
|
| 1123 | ||
| 1124 | /// @inheritdoc ISuperVaultAggregator |
|
| 1125 | 0 | function superVaultEscrows(uint256 index) external view returns (address) { |
| 1126 | 0 | if (index >= _superVaultEscrows.length()) revert INDEX_OUT_OF_BOUNDS(); |
| 1127 | 0 | return _superVaultEscrows.at(index); |
| 1128 | } |
|
| 1129 | ||
| 1130 | /// @inheritdoc ISuperVaultAggregator |
|
| 1131 | 0 | function validateHook( |
| 1132 | address strategy, |
|
| 1133 | ValidateHookArgs calldata args |
|
| 1134 | 0 | ) external view virtual returns (bool isValid) { |
| 1135 | // Cache all state variables in struct |
|
| 1136 | 0 | HookValidationCache memory cache = HookValidationCache({ |
| 1137 | 0 | globalHooksRootVetoed: _globalHooksRootVetoed, |
| 1138 | 0 | globalHooksRoot: _globalHooksRoot, |
| 1139 | 0 | strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
| 1140 | 0 | strategyRoot: _strategyData[strategy].managerHooksRoot |
| 1141 | }); |
|
| 1142 | ||
| 1143 | // Early return false if either global or strategy hooks root is vetoed |
|
| 1144 | 0 | if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) { |
| 1145 | 0 | return false; |
| 1146 | } |
|
| 1147 | ||
| 1148 | // Try to validate against global root first |
|
| 1149 | 0 | if ( |
| 1150 | 0 | _validateSingleHook( |
| 1151 | 0 | args.hookAddress, |
| 1152 | 0 | args.hookArgs, |
| 1153 | 0 | args.globalProof, |
| 1154 | 0 | true, |
| 1155 | 0 | cache, |
| 1156 | 0 | strategy |
| 1157 | ) |
|
| 1158 | ) { |
|
| 1159 | 0 | return true; |
| 1160 | } |
|
| 1161 | ||
| 1162 | // If global validation fails, try strategy root |
|
| 1163 | 0 | return |
| 1164 | 0 | _validateSingleHook( |
| 1165 | 0 | args.hookAddress, |
| 1166 | 0 | args.hookArgs, |
| 1167 | 0 | args.strategyProof, |
| 1168 | 0 | false, |
| 1169 | 0 | cache, |
| 1170 | 0 | strategy |
| 1171 | ); |
|
| 1172 | } |
|
| 1173 | ||
| 1174 | /// @inheritdoc ISuperVaultAggregator |
|
| 1175 | 42× | function validateHooks( |
| 1176 | address strategy, |
|
| 1177 | ValidateHookArgs[] calldata argsArray |
|
| 1178 | 0 | ) external view returns (bool[] memory validHooks) { |
| 1179 | 0 | uint256 length = argsArray.length; |
| 1180 | ||
| 1181 | // Cache all state variables in struct |
|
| 1182 | 0 | HookValidationCache memory cache = HookValidationCache({ |
| 1183 | 0 | globalHooksRootVetoed: _globalHooksRootVetoed, |
| 1184 | 0 | globalHooksRoot: _globalHooksRoot, |
| 1185 | 0 | strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
| 1186 | 0 | strategyRoot: _strategyData[strategy].managerHooksRoot |
| 1187 | }); |
|
| 1188 | ||
| 1189 | // Early return all false if either global or strategy hooks root is vetoed |
|
| 1190 | 0 | if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) { |
| 1191 | 0 | return new bool[](length); // Array initialized with all false values |
| 1192 | } |
|
| 1193 | ||
| 1194 | // Validate each hook |
|
| 1195 | 0 | validHooks = new bool[](length); |
| 1196 | 0 | for (uint256 i; i < length; i++) { |
| 1197 | // Try global root first |
|
| 1198 | 0 | if ( |
| 1199 | 0 | _validateSingleHook( |
| 1200 | 0 | argsArray[i].hookAddress, |
| 1201 | 0 | argsArray[i].hookArgs, |
| 1202 | 0 | argsArray[i].globalProof, |
| 1203 | 0 | true, |
| 1204 | 0 | cache, |
| 1205 | 0 | strategy |
| 1206 | ) |
|
| 1207 | ) { |
|
| 1208 | 0 | validHooks[i] = true; |
| 1209 | } else { |
|
| 1210 | // Try strategy root |
|
| 1211 | 0 | validHooks[i] = _validateSingleHook( |
| 1212 | 0 | argsArray[i].hookAddress, |
| 1213 | 0 | argsArray[i].hookArgs, |
| 1214 | 0 | argsArray[i].strategyProof, |
| 1215 | 0 | false, |
| 1216 | 0 | cache, |
| 1217 | 0 | strategy |
| 1218 | ); |
|
| 1219 | } |
|
| 1220 | // If both conditions fail, validHooks[i] remains false (default value) |
|
| 1221 | } |
|
| 1222 | ||
| 1223 | 0 | return validHooks; |
| 1224 | } |
|
| 1225 | ||
| 1226 | /// @inheritdoc ISuperVaultAggregator |
|
| 1227 | 0 | function getGlobalHooksRoot() external view returns (bytes32 root) { |
| 1228 | 0 | return _globalHooksRoot; |
| 1229 | } |
|
| 1230 | ||
| 1231 | /// @inheritdoc ISuperVaultAggregator |
|
| 1232 | 0 | function getProposedGlobalHooksRoot() |
| 1233 | external |
|
| 1234 | view |
|
| 1235 | returns (bytes32 root, uint256 effectiveTime) |
|
| 1236 | { |
|
| 1237 | 0 | return (_proposedGlobalHooksRoot, _globalHooksRootEffectiveTime); |
| 1238 | } |
|
| 1239 | ||
| 1240 | /// @notice Checks if the global hooks root is active (timelock period has passed) |
|
| 1241 | /// @return isActive True if the global hooks root is active |
|
| 1242 | 24× | function isGlobalHooksRootActive() external view returns (bool) { |
| 1243 | return |
|
| 1244 | 0 | block.timestamp >= _globalHooksRootEffectiveTime && |
| 1245 | 0 | _globalHooksRoot != bytes32(0); |
| 1246 | } |
|
| 1247 | ||
| 1248 | /// @inheritdoc ISuperVaultAggregator |
|
| 1249 | 0 | function getStrategyHooksRoot( |
| 1250 | address strategy |
|
| 1251 | 0 | ) external view returns (bytes32 root) { |
| 1252 | 0 | return _strategyData[strategy].managerHooksRoot; |
| 1253 | } |
|
| 1254 | ||
| 1255 | /// @inheritdoc ISuperVaultAggregator |
|
| 1256 | 0 | function getProposedStrategyHooksRoot( |
| 1257 | address strategy |
|
| 1258 | 0 | ) external view returns (bytes32 root, uint256 effectiveTime) { |
| 1259 | return ( |
|
| 1260 | 0 | _strategyData[strategy].proposedHooksRoot, |
| 1261 | 0 | _strategyData[strategy].hooksRootEffectiveTime |
| 1262 | ); |
|
| 1263 | } |
|
| 1264 | ||
| 1265 | /*////////////////////////////////////////////////////////////// |
|
| 1266 | INTERNAL HELPER FUNCTIONS |
|
| 1267 | //////////////////////////////////////////////////////////////*/ |
|
| 1268 | /// @notice Internal implementation of forwarding PPS updates |
|
| 1269 | /// @param args Struct containing all parameters for PPS update |
|
| 1270 | 12× | function _forwardPPS(PPSUpdateData memory args) internal { |
| 1271 | // Check rate limiting |
|
| 1272 | 20× | uint256 minInterval = _strategyData[args.strategy].minUpdateInterval; |
| 1273 | 13× | uint256 lastUpdate = _strategyData[args.strategy].lastUpdateTimestamp; |
| 1274 | 10× | if (block.timestamp - lastUpdate < minInterval) { |
| 1275 | 7× | emit UpdateTooFrequent(); |
| 1276 | 2× | return; |
| 1277 | } |
|
| 1278 | ||
| 1279 | // Ensure timestamp is monotonically increasing to prevent out-of-order updates |
|
| 1280 | 9× | if (args.timestamp <= lastUpdate) { |
| 1281 | 7× | emit TimestampNotMonotonic(); |
| 1282 | 2× | return; |
| 1283 | } |
|
| 1284 | ||
| 1285 | // Get the strategy's manager to deduct upkeep cost from |
|
| 1286 | 27× | address manager = _strategyData[args.strategy].mainManager; |
| 1287 | ||
| 1288 | // Flag to track if any check failed |
|
| 1289 | bool checksFailed = false; |
|
| 1290 | ||
| 1291 | // C2.1) Dispersion Check: Check if the standard deviation is too high relative to mean |
|
| 1292 | 4× | if ( |
| 1293 | 20× | _strategyData[args.strategy].dispersionThreshold != |
| 1294 | type(uint256).max && |
|
| 1295 | 6× | args.pps > 0 |
| 1296 | 1× | ) { |
| 1297 | // Calculate dispersion as stddev/mean |
|
| 1298 | 24× | uint256 dispersion = (args.ppsStdev * 1e18) / args.pps; // Scaled by 1e18 for precision |
| 1299 | 22× | if (dispersion > _strategyData[args.strategy].dispersionThreshold) { |
| 1300 | 0 | checksFailed = true; |
| 1301 | 0 | emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DISPERSION"); |
| 1302 | } |
|
| 1303 | } |
|
| 1304 | ||
| 1305 | // C2.2) Deviation Check: Check if new PPS deviates too much from current PPS |
|
| 1306 | 17× | uint256 currentPPS = _strategyData[args.strategy].pps; |
| 1307 | 4× | if ( |
| 1308 | 20× | _strategyData[args.strategy].deviationThreshold != |
| 1309 | type(uint256).max && |
|
| 1310 | 3× | currentPPS > 0 |
| 1311 | 2× | ) { |
| 1312 | // Calculate absolute deviation, scaled by 1e18 |
|
| 1313 | 14× | uint256 absDiff = args.pps > currentPPS |
| 1314 | 11× | ? (args.pps - currentPPS) |
| 1315 | 10× | : (currentPPS - args.pps); |
| 1316 | 15× | uint256 relativeDeviation = (absDiff * 1e18) / currentPPS; |
| 1317 | 4× | if ( |
| 1318 | 2× | relativeDeviation > |
| 1319 | 16× | _strategyData[args.strategy].deviationThreshold |
| 1320 | ) { |
|
| 1321 | 3× | checksFailed = true; |
| 1322 | 18× | emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DEVIATION"); |
| 1323 | } |
|
| 1324 | } |
|
| 1325 | ||
| 1326 | // C2.3) M/N Check: Check if enough validators participated |
|
| 1327 | 4× | if ( |
| 1328 | 11× | args.totalValidators > 0 && |
| 1329 | 0 | _strategyData[args.strategy].mnThreshold > 0 |
| 1330 | 0 | ) { |
| 1331 | // Calculate participation rate, scaled by 1e18 |
|
| 1332 | 0 | uint256 participationRate = (args.validatorSet * 1e18) / |
| 1333 | 0 | args.totalValidators; |
| 1334 | 0 | if (participationRate < _strategyData[args.strategy].mnThreshold) { |
| 1335 | 0 | checksFailed = true; |
| 1336 | 0 | emit StrategyCheckFailed( |
| 1337 | 0 | args.strategy, |
| 1338 | "INSUFFICIENT_VALIDATOR_PARTICIPATION" |
|
| 1339 | ); |
|
| 1340 | } |
|
| 1341 | } |
|
| 1342 | ||
| 1343 | // Pause strategy if any check failed |
|
| 1344 | 31× | if (checksFailed && !_strategyData[args.strategy].isPaused) { |
| 1345 | 30× | _strategyData[args.strategy].isPaused = true; |
| 1346 | 10× | emit StrategyPaused(args.strategy); |
| 1347 | } |
|
| 1348 | // Unpause strategy if all checks passed and strategy was previously paused |
|
| 1349 | 29× | else if (!checksFailed && _strategyData[args.strategy].isPaused) { |
| 1350 | 24× | _strategyData[args.strategy].isPaused = false; |
| 1351 | 10× | emit StrategyUnpaused(args.strategy); |
| 1352 | } |
|
| 1353 | ||
| 1354 | // Handle upkeep costs unless exempt |
|
| 1355 | 7× | if (!args.isExempt) { |
| 1356 | // Check if manager has sufficient upkeep balance |
|
| 1357 | 0 | if (_managerUpkeepBalance[manager] < args.upkeepCost) { |
| 1358 | 0 | emit InsufficientUpkeep( |
| 1359 | 0 | args.strategy, |
| 1360 | manager, |
|
| 1361 | 0 | _managerUpkeepBalance[manager], |
| 1362 | 0 | args.upkeepCost |
| 1363 | ); |
|
| 1364 | 0 | return; |
| 1365 | } |
|
| 1366 | ||
| 1367 | // Deduct the upkeep cost and emit event |
|
| 1368 | 0 | _managerUpkeepBalance[manager] -= args.upkeepCost; |
| 1369 | ||
| 1370 | // Add claimable upkeep for the `feeRecipient` |
|
| 1371 | 0 | claimableUpkeep += args.upkeepCost; |
| 1372 | ||
| 1373 | 0 | emit UpkeepSpent(manager, args.upkeepCost); |
| 1374 | } |
|
| 1375 | ||
| 1376 | // Update PPS, ppsStdev and timestamp in StrategyData |
|
| 1377 | 27× | _strategyData[args.strategy].pps = args.pps; |
| 1378 | 21× | _strategyData[args.strategy].ppsStdev = args.ppsStdev; |
| 1379 | 21× | _strategyData[args.strategy].lastUpdateTimestamp = args.timestamp; |
| 1380 | ||
| 1381 | 19× | emit PPSUpdated( |
| 1382 | 2× | args.strategy, |
| 1383 | 3× | args.pps, |
| 1384 | 4× | args.ppsStdev, |
| 1385 | 4× | args.validatorSet, |
| 1386 | 5× | args.totalValidators, |
| 1387 | 3× | args.timestamp |
| 1388 | ); |
|
| 1389 | } |
|
| 1390 | ||
| 1391 | /// @notice Check if an update authority is exempt from paying upkeep costs |
|
| 1392 | /// @param strategy Address of the strategy being updated |
|
| 1393 | /// @param updateAuthority Address initiating the update |
|
| 1394 | /// @param timestamp Timestamp of the PPS measurement |
|
| 1395 | /// @return isExempt True if the authority is exempt from paying upkeep |
|
| 1396 | function _isExemptFromUpkeep( |
|
| 1397 | address strategy, |
|
| 1398 | address updateAuthority, |
|
| 1399 | uint256 timestamp |
|
| 1400 | ) internal returns (bool) { |
|
| 1401 | // Check if upkeep payments are globally disabled in SuperGovernor |
|
| 1402 | if (!SUPER_GOVERNOR.isUpkeepPaymentsEnabled()) { |
|
| 1403 | return true; |
|
| 1404 | } |
|
| 1405 | ||
| 1406 | // Update is exempt if it is stale |
|
| 1407 | if ( |
|
| 1408 | block.timestamp - timestamp > _strategyData[strategy].maxStaleness |
|
| 1409 | ) { |
|
| 1410 | emit StaleUpdate(strategy, updateAuthority, timestamp); |
|
| 1411 | return true; |
|
| 1412 | } |
|
| 1413 | ||
| 1414 | // If manager is a superform manager, they're exempt from upkeep fees |
|
| 1415 | address manager = _strategyData[strategy].mainManager; |
|
| 1416 | if (SUPER_GOVERNOR.isSuperformManager(manager)) { |
|
| 1417 | return true; |
|
| 1418 | } |
|
| 1419 | ||
| 1420 | // Check if the updateAuthority is in the authorized callers list |
|
| 1421 | // These are manager-designated keepers that should be exempt from fees |
|
| 1422 | // NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller) |
|
| 1423 | if ( |
|
| 1424 | _strategyData[strategy].authorizedCallers.contains(updateAuthority) |
|
| 1425 | ) { |
|
| 1426 | return true; |
|
| 1427 | } |
|
| 1428 | ||
| 1429 | return false; |
|
| 1430 | } |
|
| 1431 | ||
| 1432 | /// @notice Creates a leaf node for Merkle verification from hook address and arguments |
|
| 1433 | /// @param hookAddress The address of the hook contract |
|
| 1434 | /// @param hookArgs The packed-encoded hook arguments (from solidityPack in JS) |
|
| 1435 | /// @return leaf The leaf node hash |
|
| 1436 | 0 | function _createLeaf( |
| 1437 | address hookAddress, |
|
| 1438 | bytes calldata hookArgs |
|
| 1439 | 0 | ) internal pure returns (bytes32) { |
| 1440 | /// @dev The leaf now includes both hook address and args to prevent cross-hook replay attacks |
|
| 1441 | /// @dev Different hooks with identical encoded args will have different authorization leaves |
|
| 1442 | /// @dev This matches StandardMerkleTree's standardLeafHash: keccak256(keccak256(abi.encode(hookAddress, |
|
| 1443 | /// hookArgs))) |
|
| 1444 | /// @dev but uses bytes.concat for explicit concatenation |
|
| 1445 | 0 | return |
| 1446 | 0 | keccak256( |
| 1447 | 0 | bytes.concat(keccak256(abi.encode(hookAddress, hookArgs))) |
| 1448 | ); |
|
| 1449 | } |
|
| 1450 | ||
| 1451 | /** |
|
| 1452 | * @dev Internal function to validate a single hook against either global or strategy root |
|
| 1453 | * @param hookAddress The address of the hook contract |
|
| 1454 | * @param hookArgs Hook arguments |
|
| 1455 | * @param proof Merkle proof for the specified root |
|
| 1456 | * @param isGlobalProof Whether to validate against global root (true) or strategy root (false) |
|
| 1457 | * @param cache Cached hook validation state variables |
|
| 1458 | * @param strategy Address of the strategy (needed to check banned leaves for global proofs) |
|
| 1459 | * @return True if hook is valid, false otherwise |
|
| 1460 | */ |
|
| 1461 | 0 | function _validateSingleHook( |
| 1462 | address hookAddress, |
|
| 1463 | bytes calldata hookArgs, |
|
| 1464 | bytes32[] calldata proof, |
|
| 1465 | bool isGlobalProof, |
|
| 1466 | HookValidationCache memory cache, |
|
| 1467 | address strategy |
|
| 1468 | 0 | ) internal view returns (bool) { |
| 1469 | // Create leaf node from the hook address and arguments |
|
| 1470 | 0 | bytes32 leaf = _createLeaf(hookAddress, hookArgs); |
| 1471 | ||
| 1472 | 0 | if (isGlobalProof) { |
| 1473 | // Validate against global root |
|
| 1474 | 0 | if ( |
| 1475 | 0 | cache.globalHooksRootVetoed || |
| 1476 | 0 | cache.globalHooksRoot == bytes32(0) |
| 1477 | ) { |
|
| 1478 | 0 | return false; |
| 1479 | } |
|
| 1480 | ||
| 1481 | // Check if this leaf is banned by the manager |
|
| 1482 | 0 | if (_strategyData[strategy].bannedLeaves[leaf]) { |
| 1483 | 0 | return false; |
| 1484 | } |
|
| 1485 | ||
| 1486 | // For single-leaf trees, empty proof is valid when root equals leaf |
|
| 1487 | 0 | if (proof.length == 0) { |
| 1488 | 0 | return cache.globalHooksRoot == leaf; |
| 1489 | } |
|
| 1490 | 0 | return MerkleProof.verify(proof, cache.globalHooksRoot, leaf); |
| 1491 | } else { |
|
| 1492 | // Validate against strategy root |
|
| 1493 | 0 | if ( |
| 1494 | 0 | cache.strategyHooksRootVetoed || |
| 1495 | 0 | cache.strategyRoot == bytes32(0) |
| 1496 | ) { |
|
| 1497 | 0 | return false; |
| 1498 | } |
|
| 1499 | // For single-leaf trees, empty proof is valid when root equals leaf |
|
| 1500 | 0 | if (proof.length == 0) { |
| 1501 | 0 | return cache.strategyRoot == leaf; |
| 1502 | } |
|
| 1503 | 0 | return MerkleProof.verify(proof, cache.strategyRoot, leaf); |
| 1504 | } |
|
| 1505 | } |
|
| 1506 | ||
| 1507 | /** |
|
| 1508 | * @dev Internal function to return the `SuperBank` address |
|
| 1509 | * @return superBank The superBank address |
|
| 1510 | */ |
|
| 1511 | 2× | function _getSuperBank() internal view returns (address) { |
| 1512 | 118× | return SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK()); |
| 1513 | } |
|
| 1514 | } |
80%
src/SuperVault/SuperVaultEscrow.sol
Lines covered: 12 / 15 (80%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 5 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 6 | ||
| 7 | /// @title SuperVaultEscrow |
|
| 8 | /// @author Superform Labs |
|
| 9 | /// @notice Escrow contract for SuperVault shares during request/claim process |
|
| 10 | 229× | contract SuperVaultEscrow { |
| 11 | using SafeERC20 for IERC20; |
|
| 12 | ||
| 13 | /*////////////////////////////////////////////////////////////// |
|
| 14 | ERRORS |
|
| 15 | //////////////////////////////////////////////////////////////*/ |
|
| 16 | error ALREADY_INITIALIZED(); |
|
| 17 | error UNAUTHORIZED(); |
|
| 18 | error ZERO_ADDRESS(); |
|
| 19 | ||
| 20 | /*////////////////////////////////////////////////////////////// |
|
| 21 | STATE |
|
| 22 | //////////////////////////////////////////////////////////////*/ |
|
| 23 | 0 | bool public initialized; |
| 24 | 0 | address public vault; |
| 25 | 0 | address public strategy; |
| 26 | ||
| 27 | /*////////////////////////////////////////////////////////////// |
|
| 28 | MODIFIERS |
|
| 29 | //////////////////////////////////////////////////////////////*/ |
|
| 30 | modifier onlyVault() { |
|
| 31 | 68× | if (msg.sender != vault) revert UNAUTHORIZED(); |
| 32 | _; |
|
| 33 | } |
|
| 34 | ||
| 35 | /*////////////////////////////////////////////////////////////// |
|
| 36 | INITIALIZATION |
|
| 37 | //////////////////////////////////////////////////////////////*/ |
|
| 38 | ||
| 39 | /// @notice Initialize the escrow with required parameters |
|
| 40 | /// @param vaultAddress The vault contract address |
|
| 41 | /// @param strategyAddress The strategy contract address |
|
| 42 | 23× | function initialize(address vaultAddress, address strategyAddress) external { |
| 43 | 28× | if (initialized) revert ALREADY_INITIALIZED(); |
| 44 | 14× | if (vaultAddress == address(0) || strategyAddress == address(0)) revert ZERO_ADDRESS(); |
| 45 | ||
| 46 | 5× | initialized = true; |
| 47 | 13× | vault = vaultAddress; |
| 48 | 10× | strategy = strategyAddress; |
| 49 | } |
|
| 50 | ||
| 51 | /*////////////////////////////////////////////////////////////// |
|
| 52 | VAULT FUNCTIONS |
|
| 53 | //////////////////////////////////////////////////////////////*/ |
|
| 54 | ||
| 55 | /// @notice Transfer shares from user to escrow during redeem request |
|
| 56 | /// @param from The address to transfer shares from |
|
| 57 | /// @param amount The amount of shares to transfer |
|
| 58 | 29× | function escrowShares(address from, uint256 amount) external onlyVault { |
| 59 | 14× | IERC20(vault).safeTransferFrom(from, address(this), amount); |
| 60 | } |
|
| 61 | ||
| 62 | /// @notice Return shares from escrow to user during redeem cancellation |
|
| 63 | /// @param to The address to return shares to |
|
| 64 | /// @param amount The amount of shares to return |
|
| 65 | 22× | function returnShares(address to, uint256 amount) external onlyVault { |
| 66 | 12× | IERC20(vault).safeTransfer(to, amount); |
| 67 | } |
|
| 68 | } |
84%
src/SuperVault/SuperVaultStrategy.sol
Lines covered: 557 / 661 (84%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
|
| 6 | import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 7 | import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; |
|
| 8 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 9 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 10 | import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
|
| 11 | import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 12 | import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
|
| 13 | ||
| 14 | // Core Interfaces |
|
| 15 | import {ISuperHook, ISuperHookResult, ISuperHookOutflow, ISuperHookInflowOutflow, ISuperHookResultOutflow, ISuperHookContextAware, ISuperHookInspector} from "@superform-v2-core/src/interfaces/ISuperHook.sol"; |
|
| 16 | import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
|
| 17 | ||
| 18 | // Periphery Interfaces |
|
| 19 | import {ISuperVault} from "../interfaces/SuperVault/ISuperVault.sol"; |
|
| 20 | import {HookDataDecoder} from "@superform-v2-core/src/libraries/HookDataDecoder.sol"; |
|
| 21 | import {ISuperVaultStrategy} from "../interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 22 | import {ISuperGovernor, FeeType} from "../interfaces/ISuperGovernor.sol"; |
|
| 23 | import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 24 | ||
| 25 | /// @title SuperVaultStrategy |
|
| 26 | /// @author Superform Labs |
|
| 27 | /// @notice Strategy implementation for SuperVault that executes strategies |
|
| 28 | 853× | contract SuperVaultStrategy is |
| 29 | ISuperVaultStrategy, |
|
| 30 | Initializable, |
|
| 31 | ReentrancyGuardUpgradeable |
|
| 32 | { |
|
| 33 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 34 | using SafeERC20 for IERC20; |
|
| 35 | using Math for uint256; |
|
| 36 | ||
| 37 | /*////////////////////////////////////////////////////////////// |
|
| 38 | CONSTANTS |
|
| 39 | //////////////////////////////////////////////////////////////*/ |
|
| 40 | 2× | uint256 private constant ONE_WEEK = 7 days; |
| 41 | 17× | uint256 private constant BPS_PRECISION = 10_000; |
| 42 | /// @dev The following is needed because the `processedShares` for some vaults (for example Centrifuge) |
|
| 43 | /// can be lower by 1 or 2 wei than the `totalRequestedAmount`in SuperVault shares |
|
| 44 | 4× | uint256 private constant TOLERANCE_CONSTANT = 10 wei; |
| 45 | ||
| 46 | // Slippage tolerance in BPS (1%) |
|
| 47 | 2× | uint256 private constant SV_SLIPPAGE_TOLERANCE_BPS = 100; |
| 48 | ||
| 49 | 0 | uint256 public PRECISION; |
| 50 | ||
| 51 | /*////////////////////////////////////////////////////////////// |
|
| 52 | STATE |
|
| 53 | //////////////////////////////////////////////////////////////*/ |
|
| 54 | address private _vault; |
|
| 55 | IERC20 private _asset; |
|
| 56 | uint8 private _vaultDecimals; |
|
| 57 | ||
| 58 | // Global configuration |
|
| 59 | uint256 private _maxPPSSlippage; |
|
| 60 | ||
| 61 | // Fee configuration |
|
| 62 | FeeConfig private feeConfig; |
|
| 63 | FeeConfig private proposedFeeConfig; |
|
| 64 | uint256 private feeConfigEffectiveTime; |
|
| 65 | ||
| 66 | // Core contracts |
|
| 67 | 0 | ISuperGovernor public immutable superGovernor; |
| 68 | ||
| 69 | // Emergency withdrawable configuration |
|
| 70 | 0 | bool public emergencyWithdrawable; |
| 71 | 0 | bool public proposedEmergencyWithdrawable; |
| 72 | 0 | uint256 public emergencyWithdrawableEffectiveTime; |
| 73 | ||
| 74 | // Yield source configuration - simplified mapping from source to oracle |
|
| 75 | mapping(address source => address oracle) private yieldSources; |
|
| 76 | EnumerableSet.AddressSet private yieldSourcesList; |
|
| 77 | ||
| 78 | // --- Redeem Request State --- |
|
| 79 | mapping(address controller => SuperVaultState state) |
|
| 80 | private superVaultState; |
|
| 81 | ||
| 82 | 28× | constructor(address superGovernor_) { |
| 83 | 5× | if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
| 84 | ||
| 85 | 6× | superGovernor = ISuperGovernor(superGovernor_); |
| 86 | 7× | emit SuperGovernorSet(superGovernor_); |
| 87 | 4× | _disableInitializers(); |
| 88 | } |
|
| 89 | ||
| 90 | /// @notice Allows the contract to receive native ETH |
|
| 91 | /// @dev Required for hooks that may send ETH back to the strategy |
|
| 92 | receive() external payable {} |
|
| 93 | ||
| 94 | /*////////////////////////////////////////////////////////////// |
|
| 95 | INITIALIZATION |
|
| 96 | //////////////////////////////////////////////////////////////*/ |
|
| 97 | 20× | function initialize( |
| 98 | address vaultAddress, |
|
| 99 | FeeConfig memory feeConfigData |
|
| 100 | ) external initializer { |
|
| 101 | 5× | 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 | 4× | if ( |
| 105 | 14× | (feeConfigData.performanceFeeBps > 0 || |
| 106 | 6× | feeConfigData.managementFeeBps > 0) && |
| 107 | 6× | feeConfigData.recipient == address(0) |
| 108 | 0 | ) revert ZERO_ADDRESS(); |
| 109 | 6× | if (feeConfigData.performanceFeeBps > BPS_PRECISION) |
| 110 | 13× | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 111 | 9× | if (feeConfigData.managementFeeBps > BPS_PRECISION) |
| 112 | 13× | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 113 | ||
| 114 | 4× | __ReentrancyGuard_init(); |
| 115 | ||
| 116 | 13× | _vault = vaultAddress; |
| 117 | 78× | _asset = IERC20(IERC4626(vaultAddress).asset()); |
| 118 | 76× | _vaultDecimals = IERC20Metadata(vaultAddress).decimals(); |
| 119 | 13× | PRECISION = 10 ** _vaultDecimals; |
| 120 | 25× | feeConfig = feeConfigData; |
| 121 | 3× | _maxPPSSlippage = 500; // 5% as a start, configurable later |
| 122 | ||
| 123 | 9× | emit Initialized(_vault); |
| 124 | } |
|
| 125 | ||
| 126 | /*////////////////////////////////////////////////////////////// |
|
| 127 | CORE STRATEGY OPERATIONS |
|
| 128 | //////////////////////////////////////////////////////////////*/ |
|
| 129 | ||
| 130 | /// @inheritdoc ISuperVaultStrategy |
|
| 131 | 119× | function handleOperations4626Deposit( |
| 132 | address controller, |
|
| 133 | uint256 assetsGross |
|
| 134 | 3× | ) external returns (uint256 sharesNet) { |
| 135 | 11× | _requireVault(); |
| 136 | ||
| 137 | 12× | if (assetsGross == 0) revert INVALID_AMOUNT(); |
| 138 | 10× | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 139 | ||
| 140 | // Check if strategy is paused or if global hooks root is vetoed |
|
| 141 | 42× | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 142 | 130× | if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) { |
| 143 | 0 | revert OPERATIONS_BLOCKED_BY_VETO(); |
| 144 | } |
|
| 145 | ||
| 146 | // Fee skim in ASSETS (asset-side entry fee) |
|
| 147 | 6× | uint256 feeBps = feeConfig.managementFeeBps; |
| 148 | 20× | uint256 feeAssets = feeBps == 0 |
| 149 | 2× | ? 0 |
| 150 | 8× | : Math.mulDiv( |
| 151 | 2× | assetsGross, |
| 152 | 2× | feeBps, |
| 153 | BPS_PRECISION, |
|
| 154 | 2× | Math.Rounding.Ceil |
| 155 | ); |
|
| 156 | ||
| 157 | 16× | uint256 assetsNet = assetsGross - feeAssets; |
| 158 | 38× | if (assetsNet == 0) revert INVALID_AMOUNT(); |
| 159 | ||
| 160 | 12× | if (feeAssets != 0) { |
| 161 | 8× | address recipient = feeConfig.recipient; |
| 162 | 6× | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 163 | 20× | _safeTokenTransfer(address(_asset), recipient, feeAssets); |
| 164 | 42× | emit ManagementFeePaid(controller, recipient, feeAssets, feeBps); |
| 165 | } |
|
| 166 | ||
| 167 | // Compute shares on NET using current PPS |
|
| 168 | 14× | uint256 pps = getStoredPPS(); |
| 169 | 38× | if (pps == 0) revert INVALID_PPS(); |
| 170 | 22× | sharesNet = Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
| 171 | 38× | if (sharesNet == 0) revert INVALID_AMOUNT(); |
| 172 | ||
| 173 | // Account on NET |
|
| 174 | 28× | SuperVaultState storage state = superVaultState[controller]; |
| 175 | 42× | state.accumulatorShares += sharesNet; |
| 176 | 34× | state.accumulatorCostBasis += assetsNet; |
| 177 | 30× | emit DepositHandled(controller, assetsNet, sharesNet); |
| 178 | 10× | return sharesNet; |
| 179 | } |
|
| 180 | ||
| 181 | /// @inheritdoc ISuperVaultStrategy |
|
| 182 | 61× | function handleOperations4626Mint( |
| 183 | address controller, |
|
| 184 | uint256 sharesNet, |
|
| 185 | uint256 assetsGross, |
|
| 186 | uint256 assetsNet |
|
| 187 | 4× | ) external { |
| 188 | 11× | _requireVault(); |
| 189 | ||
| 190 | 12× | if (sharesNet == 0) revert INVALID_AMOUNT(); |
| 191 | 10× | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 192 | ||
| 193 | // Check if strategy is paused or if global hooks root is vetoed |
|
| 194 | 42× | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 195 | 130× | if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) { |
| 196 | 0 | revert OPERATIONS_BLOCKED_BY_VETO(); |
| 197 | } |
|
| 198 | ||
| 199 | 4× | uint256 feeBps = feeConfig.managementFeeBps; |
| 200 | // Transfer fee if needed |
|
| 201 | 12× | if (feeBps != 0) { |
| 202 | 16× | uint256 feeAssets = assetsGross - assetsNet; |
| 203 | 12× | if (feeAssets != 0) { |
| 204 | 8× | address recipient = feeConfig.recipient; |
| 205 | 6× | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 206 | 20× | _safeTokenTransfer(address(_asset), recipient, feeAssets); |
| 207 | 34× | emit ManagementFeePaid( |
| 208 | 2× | controller, |
| 209 | 2× | recipient, |
| 210 | 2× | feeAssets, |
| 211 | 2× | feeBps |
| 212 | ); |
|
| 213 | } |
|
| 214 | } |
|
| 215 | ||
| 216 | // Account on NET |
|
| 217 | 28× | SuperVaultState storage state = superVaultState[controller]; |
| 218 | 42× | state.accumulatorShares += sharesNet; |
| 219 | 34× | state.accumulatorCostBasis += assetsNet; |
| 220 | 30× | emit DepositHandled(controller, assetsNet, sharesNet); |
| 221 | } |
|
| 222 | ||
| 223 | /// @inheritdoc ISuperVaultStrategy |
|
| 224 | 60× | function quoteMintAssetsGross( |
| 225 | uint256 shares |
|
| 226 | 4× | ) external view returns (uint256 assetsGross, uint256 assetsNet) { |
| 227 | 14× | uint256 pps = getStoredPPS(); |
| 228 | 38× | if (pps == 0) revert INVALID_PPS(); |
| 229 | 22× | assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
| 230 | 12× | if (assetsNet == 0) revert INVALID_AMOUNT(); |
| 231 | ||
| 232 | 6× | uint256 feeBps = feeConfig.managementFeeBps; |
| 233 | 22× | if (feeBps == 0) return (assetsNet, assetsNet); |
| 234 | 36× | if (feeBps >= BPS_PRECISION) revert INVALID_AMOUNT(); // prevents div-by-zero (100% fee) |
| 235 | 12× | assetsGross = Math.mulDiv( |
| 236 | 2× | assetsNet, |
| 237 | BPS_PRECISION, |
|
| 238 | 10× | (BPS_PRECISION - feeBps), |
| 239 | 2× | Math.Rounding.Ceil |
| 240 | ); |
|
| 241 | 4× | return (assetsGross, assetsNet); |
| 242 | } |
|
| 243 | ||
| 244 | /// @inheritdoc ISuperVaultStrategy |
|
| 245 | 44× | function handleOperations7540( |
| 246 | Operation operation, |
|
| 247 | address controller, |
|
| 248 | address receiver, |
|
| 249 | uint256 amount |
|
| 250 | ) external { |
|
| 251 | 7× | _requireVault(); |
| 252 | 15× | if (operation == Operation.RedeemRequest) { |
| 253 | 6× | _handleRequestRedeem(controller, amount); // amount = shares |
| 254 | 13× | } else if (operation == Operation.CancelRedeem) { |
| 255 | 4× | _handleCancelRedeem(controller); |
| 256 | 15× | } else if (operation == Operation.ClaimRedeem) { |
| 257 | 6× | _handleClaimRedeem(controller, receiver, amount); // amount = assets |
| 258 | } else { |
|
| 259 | 13× | revert ACTION_TYPE_DISALLOWED(); |
| 260 | } |
|
| 261 | } |
|
| 262 | ||
| 263 | /*////////////////////////////////////////////////////////////// |
|
| 264 | MANAGER EXTERNAL ACCESS FUNCTIONS |
|
| 265 | //////////////////////////////////////////////////////////////*/ |
|
| 266 | ||
| 267 | /// @inheritdoc ISuperVaultStrategy |
|
| 268 | 22× | function executeHooks( |
| 269 | ExecuteArgs calldata args |
|
| 270 | 2× | ) external payable nonReentrant { |
| 271 | 10× | _isManager(msg.sender); |
| 272 | ||
| 273 | 22× | uint256 hooksLength = args.hooks.length; |
| 274 | 12× | if (hooksLength == 0) revert ZERO_LENGTH(); |
| 275 | 30× | if (args.hookCalldata.length != hooksLength) |
| 276 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 277 | 30× | if (args.expectedAssetsOrSharesOut.length != hooksLength) |
| 278 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 279 | 30× | if (args.globalProofs.length != hooksLength) |
| 280 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 281 | 30× | if (args.strategyProofs.length != hooksLength) |
| 282 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 283 | ||
| 284 | 2× | address prevHook; |
| 285 | 21× | for (uint256 i; i < hooksLength; ++i) { |
| 286 | 62× | address hook = args.hooks[i]; |
| 287 | 29× | if (!_isRegisteredHook(hook)) revert INVALID_HOOK(); |
| 288 | ||
| 289 | // Check if the hook was validated |
|
| 290 | 6× | if ( |
| 291 | 162× | !_validateHook( |
| 292 | 2× | hook, |
| 293 | 56× | args.hookCalldata[i], |
| 294 | 58× | args.globalProofs[i], |
| 295 | 16× | args.strategyProofs[i] |
| 296 | ) |
|
| 297 | ) { |
|
| 298 | 0 | revert HOOK_VALIDATION_FAILED(); |
| 299 | } |
|
| 300 | ||
| 301 | 90× | prevHook = _processSingleHookExecution( |
| 302 | 2× | hook, |
| 303 | 2× | prevHook, |
| 304 | 56× | args.hookCalldata[i], |
| 305 | 44× | args.expectedAssetsOrSharesOut[i] |
| 306 | ); |
|
| 307 | } |
|
| 308 | 23× | emit HooksExecuted(args.hooks); |
| 309 | } |
|
| 310 | ||
| 311 | /// @inheritdoc ISuperVaultStrategy |
|
| 312 | 54× | function fulfillRedeemRequests( |
| 313 | FulfillArgs calldata args |
|
| 314 | 6× | ) external nonReentrant { |
| 315 | 10× | _isManager(msg.sender); |
| 316 | ||
| 317 | // Check if strategy is paused |
|
| 318 | 42× | if (_isPaused()) revert STRATEGY_PAUSED(); |
| 319 | ||
| 320 | 26× | uint256 hooksLength = args.hooks.length; |
| 321 | 12× | if (hooksLength == 0) revert ZERO_LENGTH(); |
| 322 | 22× | uint256 controllersLength = args.controllers.length; |
| 323 | 12× | if (controllersLength == 0) revert ZERO_LENGTH(); |
| 324 | 30× | if (args.hookCalldata.length != hooksLength) |
| 325 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 326 | 30× | if (args.expectedAssetsOrSharesOut.length != hooksLength) |
| 327 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 328 | 30× | if (args.globalProofs.length != hooksLength) |
| 329 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 330 | 30× | if (args.strategyProofs.length != hooksLength) |
| 331 | 13× | revert INVALID_ARRAY_LENGTH(); |
| 332 | ||
| 333 | 14× | uint256 currentPPS = getStoredPPS(); |
| 334 | 38× | if (currentPPS == 0) revert INVALID_PPS(); |
| 335 | ||
| 336 | // Pre-calculate totals to ensure no overburn of escrowed shares |
|
| 337 | 2× | uint256 totalRequestedShares; |
| 338 | 28× | for (uint256 i; i < controllersLength; ++i) { |
| 339 | 102× | totalRequestedShares += superVaultState[args.controllers[i]] |
| 340 | .pendingRedeemRequest; |
|
| 341 | } |
|
| 342 | 2× | uint256 intendedShares; |
| 343 | 26× | for (uint256 i; i < hooksLength; ++i) { |
| 344 | 66× | address hook = args.hooks[i]; |
| 345 | 29× | if (!_isFulfillRequestsHook(hook)) revert INVALID_HOOK(); |
| 346 | // Check if the hook was validated |
|
| 347 | 6× | if ( |
| 348 | 244× | !_validateHook( |
| 349 | 2× | hook, |
| 350 | 56× | args.hookCalldata[i], |
| 351 | 58× | args.globalProofs[i], |
| 352 | 58× | args.strategyProofs[i] |
| 353 | ) |
|
| 354 | ) { |
|
| 355 | 0 | revert HOOK_VALIDATION_FAILED(); |
| 356 | } |
|
| 357 | 52× | if (args.expectedAssetsOrSharesOut[i] == 0) |
| 358 | 26× | revert ZERO_EXPECTED_VALUE(); |
| 359 | ||
| 360 | 158× | intendedShares += ISuperHookInflowOutflow(hook).decodeAmount( |
| 361 | 56× | args.hookCalldata[i] |
| 362 | ); |
|
| 363 | } |
|
| 364 | ||
| 365 | // Enforce both lower- and upper-bounds on intended shares to prevent escrow overburn |
|
| 366 | 22× | if (intendedShares + TOLERANCE_CONSTANT < totalRequestedShares) { |
| 367 | 26× | revert INVALID_REDEEM_FILL(); |
| 368 | } |
|
| 369 | ||
| 370 | 22× | if (intendedShares > totalRequestedShares + TOLERANCE_CONSTANT) { |
| 371 | 13× | revert INVALID_REDEEM_FILL(); |
| 372 | } |
|
| 373 | ||
| 374 | 2× | uint256 processedShares; |
| 375 | 27× | for (uint256 i; i < hooksLength; ++i) { |
| 376 | 66× | address hook = args.hooks[i]; |
| 377 | 93× | uint256 amountSharesSpent = _processSingleFulfillHookExecution( |
| 378 | hook, |
|
| 379 | 56× | args.hookCalldata[i], |
| 380 | 44× | args.expectedAssetsOrSharesOut[i], |
| 381 | 2× | currentPPS |
| 382 | ); |
|
| 383 | 7× | processedShares += amountSharesSpent; |
| 384 | } |
|
| 385 | ||
| 386 | // Post-condition: processed shares must match intended shares |
|
| 387 | 6× | if (processedShares != intendedShares) revert INVALID_REDEEM_FILL(); |
| 388 | ||
| 389 | 4× | _processRedeemFulfillments( |
| 390 | 6× | args.controllers, |
| 391 | 1× | controllersLength, |
| 392 | 1× | processedShares, |
| 393 | 1× | currentPPS |
| 394 | ); |
|
| 395 | ||
| 396 | 41× | ISuperVault(_vault).burnShares(processedShares); |
| 397 | ||
| 398 | 22× | emit RedeemRequestsFulfilled( |
| 399 | 9× | args.hooks, |
| 400 | 6× | args.controllers, |
| 401 | 1× | processedShares, |
| 402 | 1× | currentPPS |
| 403 | ); |
|
| 404 | } |
|
| 405 | ||
| 406 | /*////////////////////////////////////////////////////////////// |
|
| 407 | YIELD SOURCE MANAGEMENT |
|
| 408 | //////////////////////////////////////////////////////////////*/ |
|
| 409 | ||
| 410 | // @inheritdoc ISuperVaultStrategy |
|
| 411 | 29× | function manageYieldSource( |
| 412 | address source, |
|
| 413 | address oracle, |
|
| 414 | uint8 actionType |
|
| 415 | ) external { |
|
| 416 | 5× | _isPrimaryManager(msg.sender); |
| 417 | 9× | _manageYieldSource(source, oracle, actionType); |
| 418 | } |
|
| 419 | ||
| 420 | // @inheritdoc ISuperVaultStrategy |
|
| 421 | 24× | function manageYieldSources( |
| 422 | address[] calldata sources, |
|
| 423 | address[] calldata oracles, |
|
| 424 | uint8[] calldata actionTypes |
|
| 425 | 1× | ) external { |
| 426 | 5× | _isPrimaryManager(msg.sender); |
| 427 | ||
| 428 | 2× | uint256 length = sources.length; |
| 429 | 6× | if (length == 0) revert ZERO_LENGTH(); |
| 430 | 19× | if (oracles.length != length) revert INVALID_ARRAY_LENGTH(); |
| 431 | 19× | if (actionTypes.length != length) revert INVALID_ARRAY_LENGTH(); |
| 432 | ||
| 433 | 14× | for (uint256 i; i < length; ++i) { |
| 434 | 76× | _manageYieldSource(sources[i], oracles[i], actionTypes[i]); |
| 435 | } |
|
| 436 | } |
|
| 437 | ||
| 438 | // @inheritdoc ISuperVaultStrategy |
|
| 439 | 25× | function proposeVaultFeeConfigUpdate( |
| 440 | uint256 performanceFeeBps, |
|
| 441 | uint256 managementFeeBps, |
|
| 442 | address recipient |
|
| 443 | ) external { |
|
| 444 | 5× | _isPrimaryManager(msg.sender); |
| 445 | ||
| 446 | 6× | if (performanceFeeBps > BPS_PRECISION) |
| 447 | 13× | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 448 | 6× | if (managementFeeBps > BPS_PRECISION) |
| 449 | 13× | revert INVALID_PERFORMANCE_FEE_BPS(); |
| 450 | 18× | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 451 | 41× | proposedFeeConfig = FeeConfig({ |
| 452 | performanceFeeBps: performanceFeeBps, |
|
| 453 | managementFeeBps: managementFeeBps, |
|
| 454 | recipient: recipient |
|
| 455 | }); |
|
| 456 | 9× | feeConfigEffectiveTime = block.timestamp + ONE_WEEK; |
| 457 | 24× | emit VaultFeeConfigProposed( |
| 458 | performanceFeeBps, |
|
| 459 | managementFeeBps, |
|
| 460 | recipient, |
|
| 461 | feeConfigEffectiveTime |
|
| 462 | ); |
|
| 463 | } |
|
| 464 | ||
| 465 | // @inheritdoc ISuperVaultStrategy |
|
| 466 | 13× | function executeVaultFeeConfigUpdate() external { |
| 467 | 8× | if (block.timestamp < feeConfigEffectiveTime) |
| 468 | 13× | revert INVALID_TIMESTAMP(); |
| 469 | 19× | if (proposedFeeConfig.recipient == address(0)) revert ZERO_ADDRESS(); |
| 470 | 31× | feeConfig = proposedFeeConfig; |
| 471 | 12× | delete proposedFeeConfig; |
| 472 | 5× | feeConfigEffectiveTime = 0; |
| 473 | 12× | emit VaultFeeConfigUpdated( |
| 474 | feeConfig.performanceFeeBps, |
|
| 475 | feeConfig.managementFeeBps, |
|
| 476 | feeConfig.recipient |
|
| 477 | ); |
|
| 478 | } |
|
| 479 | ||
| 480 | // @inheritdoc ISuperVaultStrategy |
|
| 481 | 19× | function updateMaxPPSSlippage(uint256 maxSlippageBps) external { |
| 482 | 5× | _isPrimaryManager(msg.sender); |
| 483 | 19× | if (maxSlippageBps > BPS_PRECISION) revert INVALID_MAX_SLIPPAGE_BPS(); |
| 484 | 4× | _maxPPSSlippage = maxSlippageBps; |
| 485 | 11× | emit MaxPPSSlippageUpdated(maxSlippageBps); |
| 486 | } |
|
| 487 | ||
| 488 | // @inheritdoc ISuperVaultStrategy |
|
| 489 | 17× | function manageEmergencyWithdraw( |
| 490 | uint8 action, |
|
| 491 | address recipient, |
|
| 492 | uint256 amount |
|
| 493 | ) external { |
|
| 494 | 8× | if (action == 1) { |
| 495 | 3× | _proposeEmergencyWithdraw(); |
| 496 | 8× | } else if (action == 2) { |
| 497 | 3× | _executeEmergencyWithdrawActivation(); |
| 498 | 8× | } else if (action == 3) { |
| 499 | 5× | _performEmergencyWithdraw(recipient, amount); |
| 500 | 7× | } else if (action == 4) { |
| 501 | 3× | _cancelEmergencyWithdrawProposal(); |
| 502 | } else { |
|
| 503 | revert ACTION_TYPE_DISALLOWED(); |
|
| 504 | } |
|
| 505 | } |
|
| 506 | ||
| 507 | /*////////////////////////////////////////////////////////////// |
|
| 508 | ACCOUNTING MANAGEMENT |
|
| 509 | //////////////////////////////////////////////////////////////*/ |
|
| 510 | /// @inheritdoc ISuperVaultStrategy |
|
| 511 | 81× | function moveAccumulatorOnTransfer( |
| 512 | address from, |
|
| 513 | address to, |
|
| 514 | uint256 shares |
|
| 515 | ) external { |
|
| 516 | 15× | _requireVault(); |
| 517 | 18× | if (shares == 0) return; |
| 518 | ||
| 519 | 42× | SuperVaultState storage fromState = superVaultState[from]; |
| 520 | 27× | SuperVaultState storage toState = superVaultState[to]; |
| 521 | ||
| 522 | // Only move accumulator proportional to what's available |
|
| 523 | // (accumulator shares may be less than ERC20 shares due to pending redeems) |
|
| 524 | // see test_Fix1_AuditAttackScenarioFails |
|
| 525 | 12× | uint256 availableAccumulatorShares = fromState.accumulatorShares; |
| 526 | 25× | if (availableAccumulatorShares == 0) return; // No accumulator to move |
| 527 | ||
| 528 | 12× | uint256 sharesToMove = shares > availableAccumulatorShares |
| 529 | 1× | ? availableAccumulatorShares |
| 530 | 1× | : shares; |
| 531 | ||
| 532 | // Pro-rata move of cost basis (NO PPS here; preserves fee correctness) |
|
| 533 | 12× | uint256 movedCostBasis = sharesToMove == availableAccumulatorShares |
| 534 | 4× | ? fromState.accumulatorCostBasis |
| 535 | 4× | : Math.mulDiv( |
| 536 | 1× | sharesToMove, |
| 537 | 4× | fromState.accumulatorCostBasis, |
| 538 | 1× | availableAccumulatorShares, |
| 539 | 1× | Math.Rounding.Floor |
| 540 | ); /// @audit is there a value st when transfered transfers minimal shares and maximum cost basis |
|
| 541 | ||
| 542 | 21× | fromState.accumulatorShares -= sharesToMove; |
| 543 | 21× | fromState.accumulatorCostBasis -= movedCostBasis; |
| 544 | ||
| 545 | 21× | toState.accumulatorShares += sharesToMove; |
| 546 | 17× | toState.accumulatorCostBasis += movedCostBasis; |
| 547 | ||
| 548 | // Never touch: pendingRedeemRequest, averageRequestPPS, maxWithdraw, averageWithdrawPrice |
|
| 549 | } |
|
| 550 | ||
| 551 | /*////////////////////////////////////////////////////////////// |
|
| 552 | VIEW FUNCTIONS |
|
| 553 | //////////////////////////////////////////////////////////////*/ |
|
| 554 | ||
| 555 | // @inheritdoc ISuperVaultStrategy |
|
| 556 | 0 | function getVaultInfo() |
| 557 | external |
|
| 558 | view |
|
| 559 | returns (address vault, address asset, uint8 vaultDecimals) |
|
| 560 | { |
|
| 561 | 0 | vault = _vault; |
| 562 | 0 | asset = address(_asset); |
| 563 | 0 | vaultDecimals = _vaultDecimals; |
| 564 | } |
|
| 565 | ||
| 566 | // @inheritdoc ISuperVaultStrategy |
|
| 567 | 36× | function getConfigInfo() |
| 568 | external |
|
| 569 | view |
|
| 570 | 4× | returns (FeeConfig memory feeConfig_) |
| 571 | { |
|
| 572 | 58× | feeConfig_ = feeConfig; |
| 573 | } |
|
| 574 | ||
| 575 | // @inheritdoc ISuperVaultStrategy |
|
| 576 | 49× | function getStoredPPS() public view returns (uint256) { |
| 577 | 260× | return _getSuperVaultAggregator().getPPS(address(this)); |
| 578 | } |
|
| 579 | ||
| 580 | // @inheritdoc ISuperVaultStrategy |
|
| 581 | 48× | function getSuperVaultState( |
| 582 | address controller |
|
| 583 | 4× | ) external view returns (SuperVaultState memory state) { |
| 584 | 148× | return superVaultState[controller]; |
| 585 | } |
|
| 586 | ||
| 587 | // @inheritdoc ISuperVaultStrategy |
|
| 588 | 0 | function getYieldSource( |
| 589 | address source |
|
| 590 | ) external view returns (YieldSource memory) { |
|
| 591 | 0 | return YieldSource({oracle: yieldSources[source]}); |
| 592 | } |
|
| 593 | ||
| 594 | // @inheritdoc ISuperVaultStrategy |
|
| 595 | 0 | function getYieldSourcesList() |
| 596 | external |
|
| 597 | view |
|
| 598 | 0 | returns (YieldSourceInfo[] memory) |
| 599 | { |
|
| 600 | 0 | uint256 length = yieldSourcesList.length(); |
| 601 | 0 | YieldSourceInfo[] memory sourcesInfo = new YieldSourceInfo[](length); |
| 602 | ||
| 603 | 0 | for (uint256 i; i < length; ++i) { |
| 604 | 0 | address sourceAddress = yieldSourcesList.at(i); |
| 605 | 0 | address oracle = yieldSources[sourceAddress]; |
| 606 | ||
| 607 | 0 | sourcesInfo[i] = YieldSourceInfo({ |
| 608 | sourceAddress: sourceAddress, |
|
| 609 | oracle: oracle |
|
| 610 | }); |
|
| 611 | } |
|
| 612 | ||
| 613 | 0 | return sourcesInfo; |
| 614 | } |
|
| 615 | ||
| 616 | // @inheritdoc ISuperVaultStrategy |
|
| 617 | 0 | function getYieldSources() external view returns (address[] memory) { |
| 618 | 0 | return yieldSourcesList.values(); |
| 619 | } |
|
| 620 | ||
| 621 | // @inheritdoc ISuperVaultStrategy |
|
| 622 | 0 | function getYieldSourcesCount() external view returns (uint256) { |
| 623 | 0 | return yieldSourcesList.length(); |
| 624 | } |
|
| 625 | ||
| 626 | // @inheritdoc ISuperVaultStrategy |
|
| 627 | 0 | function containsYieldSource(address source) external view returns (bool) { |
| 628 | 0 | return yieldSourcesList.contains(source); |
| 629 | } |
|
| 630 | ||
| 631 | // @inheritdoc ISuperVaultStrategy |
|
| 632 | 60× | function pendingRedeemRequest( |
| 633 | address controller |
|
| 634 | 4× | ) external view returns (uint256 pendingShares) { |
| 635 | 48× | return superVaultState[controller].pendingRedeemRequest; |
| 636 | } |
|
| 637 | ||
| 638 | // @inheritdoc ISuperVaultStrategy |
|
| 639 | 30× | function claimableWithdraw( |
| 640 | address controller |
|
| 641 | 2× | ) external view returns (uint256 claimableAssets) { |
| 642 | 28× | return superVaultState[controller].maxWithdraw; |
| 643 | } |
|
| 644 | ||
| 645 | // @inheritdoc ISuperVaultStrategy |
|
| 646 | 45× | function getAverageWithdrawPrice( |
| 647 | address controller |
|
| 648 | 3× | ) external view returns (uint256 averageWithdrawPrice) { |
| 649 | 42× | return superVaultState[controller].averageWithdrawPrice; |
| 650 | } |
|
| 651 | ||
| 652 | // @inheritdoc ISuperVaultStrategy |
|
| 653 | 0 | function previewPerformanceFee( |
| 654 | address controller, |
|
| 655 | uint256 sharesToRedeem |
|
| 656 | ) |
|
| 657 | external |
|
| 658 | view |
|
| 659 | 0 | returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee) |
| 660 | { |
|
| 661 | 0 | if (sharesToRedeem == 0) return (0, 0, 0); |
| 662 | ||
| 663 | // Get controller's state |
|
| 664 | 0 | SuperVaultState storage state = superVaultState[controller]; |
| 665 | ||
| 666 | // Check if controller has enough shares |
|
| 667 | 0 | if (sharesToRedeem > state.accumulatorShares) return (0, 0, 0); |
| 668 | ||
| 669 | // Get the current price per share |
|
| 670 | 0 | uint256 currentPPS = getStoredPPS(); |
| 671 | ||
| 672 | // Calculate historical assets (cost basis) |
|
| 673 | 0 | uint256 historicalAssets = 0; |
| 674 | 0 | if (state.accumulatorShares > 0) { |
| 675 | 0 | historicalAssets = sharesToRedeem.mulDiv( |
| 676 | 0 | state.accumulatorCostBasis, |
| 677 | 0 | state.accumulatorShares, |
| 678 | 0 | Math.Rounding.Floor |
| 679 | ); |
|
| 680 | } |
|
| 681 | ||
| 682 | // Calculate current value of shares in asset terms |
|
| 683 | 0 | uint256 currentAssetsWithFees = sharesToRedeem.mulDiv( |
| 684 | 0 | currentPPS, |
| 685 | 0 | PRECISION, |
| 686 | Math.Rounding.Floor |
|
| 687 | ); |
|
| 688 | ||
| 689 | // Calculate fee (if any) using same logic as _calculateAndTransferFee |
|
| 690 | 0 | if (currentAssetsWithFees > historicalAssets) { |
| 691 | 0 | uint256 profit = currentAssetsWithFees - historicalAssets; |
| 692 | 0 | uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
| 693 | 0 | totalFee = profit.mulDiv( |
| 694 | performanceFeeBps, |
|
| 695 | BPS_PRECISION, |
|
| 696 | Math.Rounding.Floor |
|
| 697 | ); |
|
| 698 | ||
| 699 | 0 | if (totalFee > 0) { |
| 700 | // Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
|
| 701 | 0 | superformFee = totalFee.mulDiv( |
| 702 | 0 | superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
| 703 | BPS_PRECISION, |
|
| 704 | 0 | Math.Rounding.Floor |
| 705 | ); |
|
| 706 | 0 | recipientFee = totalFee - superformFee; |
| 707 | } |
|
| 708 | } |
|
| 709 | ||
| 710 | 0 | return (totalFee, superformFee, recipientFee); |
| 711 | } |
|
| 712 | ||
| 713 | /*////////////////////////////////////////////////////////////// |
|
| 714 | INTERNAL FUNCTIONS |
|
| 715 | //////////////////////////////////////////////////////////////*/ |
|
| 716 | ||
| 717 | /// @notice Process a single hook execution |
|
| 718 | /// @param hook Hook address |
|
| 719 | /// @param prevHook Previous hook address |
|
| 720 | /// @param hookCalldata Hook calldata |
|
| 721 | /// @param expectedAssetsOrSharesOut Expected assets or shares output |
|
| 722 | /// @return processedHook Processed hook address |
|
| 723 | 4× | function _processSingleHookExecution( |
| 724 | address hook, |
|
| 725 | address prevHook, |
|
| 726 | bytes memory hookCalldata, |
|
| 727 | uint256 expectedAssetsOrSharesOut |
|
| 728 | ) internal returns (address) { |
|
| 729 | ExecutionVars memory vars; |
|
| 730 | 6× | vars.hookContract = ISuperHook(hook); |
| 731 | ||
| 732 | 28× | vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
| 733 | 2× | hookCalldata |
| 734 | ); |
|
| 735 | 30× | if (yieldSources[vars.targetedYieldSource] == address(0)) |
| 736 | 26× | revert YIELD_SOURCE_NOT_FOUND(); |
| 737 | ||
| 738 | 14× | bool usePrevHookAmount = _decodeHookUsePrevHookAmount( |
| 739 | 2× | hook, |
| 740 | 2× | hookCalldata |
| 741 | ); |
|
| 742 | 28× | if (usePrevHookAmount && prevHook != address(0)) { |
| 743 | 0 | vars.outAmount = _getPreviousHookOutAmount(prevHook); |
| 744 | 0 | if (expectedAssetsOrSharesOut == 0) revert ZERO_EXPECTED_VALUE(); |
| 745 | 0 | uint256 minExpectedPrevOut = expectedAssetsOrSharesOut * |
| 746 | 0 | (BPS_PRECISION - _getSlippageTolerance()); |
| 747 | 0 | if (vars.outAmount * BPS_PRECISION < minExpectedPrevOut) { |
| 748 | 0 | revert MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET(); |
| 749 | } |
|
| 750 | } |
|
| 751 | ||
| 752 | 94× | ISuperHook(address(vars.hookContract)).setExecutionContext( |
| 753 | 2× | address(this) |
| 754 | ); |
|
| 755 | 162× | vars.executions = vars.hookContract.build( |
| 756 | 2× | prevHook, |
| 757 | 2× | address(this), |
| 758 | 2× | hookCalldata |
| 759 | ); |
|
| 760 | 34× | for (uint256 j; j < vars.executions.length; ++j) { |
| 761 | 174× | (vars.success, ) = vars.executions[j].target.call{ |
| 762 | 42× | value: vars.executions[j].value |
| 763 | 42× | }(vars.executions[j].callData); |
| 764 | 32× | if (!vars.success) revert OPERATION_FAILED(); |
| 765 | } |
|
| 766 | 86× | ISuperHook(address(vars.hookContract)).resetExecutionState( |
| 767 | 2× | address(this) |
| 768 | ); |
|
| 769 | ||
| 770 | 120× | uint256 actualOutput = ISuperHookResult(hook).getOutAmount( |
| 771 | 2× | address(this) |
| 772 | ); |
|
| 773 | 37× | if (actualOutput == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 774 | ||
| 775 | 15× | uint256 minExpectedOut = (expectedAssetsOrSharesOut * |
| 776 | 4× | (BPS_PRECISION - _getSlippageTolerance())) / BPS_PRECISION; |
| 777 | 7× | if (actualOutput < minExpectedOut) { |
| 778 | 0 | revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 779 | } |
|
| 780 | ||
| 781 | 20× | emit HookExecuted( |
| 782 | 1× | hook, |
| 783 | 1× | prevHook, |
| 784 | 4× | vars.targetedYieldSource, |
| 785 | 1× | usePrevHookAmount, |
| 786 | 1× | hookCalldata |
| 787 | ); |
|
| 788 | ||
| 789 | 2× | 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 | 4× | function _processSingleFulfillHookExecution( |
| 799 | address hook, |
|
| 800 | bytes memory hookCalldata, |
|
| 801 | uint256 expectedAssetOutput, |
|
| 802 | uint256 currentPPS |
|
| 803 | ) internal returns (uint256) { |
|
| 804 | OutflowExecutionVars memory vars; |
|
| 805 | 16× | vars.hookContract = ISuperHook(hook); |
| 806 | 154× | vars.hookType = ISuperHookResult(hook).hookType(); |
| 807 | 32× | if (vars.hookType != ISuperHook.HookType.OUTFLOW) |
| 808 | 0 | revert INVALID_HOOK_TYPE(); |
| 809 | ||
| 810 | 28× | vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
| 811 | 2× | hookCalldata |
| 812 | ); |
|
| 813 | 30× | if (yieldSources[vars.targetedYieldSource] == address(0)) |
| 814 | 26× | revert YIELD_SOURCE_NOT_FOUND(); |
| 815 | ||
| 816 | // we must always encode supervault shares when fulfilling redemptions |
|
| 817 | 134× | vars.superVaultShares = ISuperHookInflowOutflow(hook).decodeAmount( |
| 818 | 4× | hookCalldata |
| 819 | ); |
|
| 820 | ||
| 821 | // Calculate underlying shares and update hook calldata |
|
| 822 | 22× | vars.amountOfAssets = vars.superVaultShares.mulDiv( |
| 823 | 4× | currentPPS, |
| 824 | 8× | PRECISION, |
| 825 | Math.Rounding.Floor |
|
| 826 | ); |
|
| 827 | 24× | vars.svAsset = address(_asset); |
| 828 | 132× | vars.amountConvertedToUnderlyingShares = IYieldSourceOracle( |
| 829 | 46× | yieldSources[vars.targetedYieldSource] |
| 830 | ).getShareOutput( |
|
| 831 | 4× | vars.targetedYieldSource, |
| 832 | 4× | vars.svAsset, |
| 833 | 4× | vars.amountOfAssets |
| 834 | ); |
|
| 835 | 126× | hookCalldata = ISuperHookOutflow(hook).replaceCalldataAmount( |
| 836 | 4× | hookCalldata, |
| 837 | vars.amountConvertedToUnderlyingShares |
|
| 838 | ); |
|
| 839 | ||
| 840 | 26× | vars.balanceAssetBefore = _getTokenBalance(vars.svAsset, address(this)); |
| 841 | ||
| 842 | 94× | ISuperHook(address(vars.hookContract)).setExecutionContext( |
| 843 | 2× | address(this) |
| 844 | ); |
|
| 845 | ||
| 846 | 162× | vars.executions = vars.hookContract.build( |
| 847 | 2× | address(0), |
| 848 | 2× | address(this), |
| 849 | 2× | hookCalldata |
| 850 | ); |
|
| 851 | 33× | for (uint256 j; j < vars.executions.length; ++j) { |
| 852 | 172× | (vars.success, ) = vars.executions[j].target.call( |
| 853 | 42× | vars.executions[j].callData |
| 854 | ); |
|
| 855 | 32× | if (!vars.success) revert OPERATION_FAILED(); |
| 856 | } |
|
| 857 | 47× | ISuperHook(address(vars.hookContract)).resetExecutionState( |
| 858 | 1× | address(this) |
| 859 | ); |
|
| 860 | ||
| 861 | 6× | vars.outAmount = |
| 862 | 15× | _getTokenBalance(vars.svAsset, address(this)) - |
| 863 | 4× | vars.balanceAssetBefore; |
| 864 | ||
| 865 | 18× | if (vars.outAmount == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 866 | 4× | if ( |
| 867 | 11× | vars.outAmount * BPS_PRECISION < |
| 868 | 10× | expectedAssetOutput * (BPS_PRECISION - _getSlippageTolerance()) |
| 869 | ) { |
|
| 870 | 13× | revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 871 | } |
|
| 872 | 24× | emit FulfillHookExecuted(hook, vars.targetedYieldSource, hookCalldata); |
| 873 | ||
| 874 | 4× | 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 | 7× | function _processRedeemFulfillments( |
| 883 | address[] calldata controllers, |
|
| 884 | uint256 controllersLength, |
|
| 885 | uint256 processedShares, |
|
| 886 | uint256 currentPPS |
|
| 887 | ) internal { |
|
| 888 | 20× | for (uint256 i; i < controllersLength; ++i) { |
| 889 | 42× | SuperVaultState storage state = superVaultState[controllers[i]]; |
| 890 | ||
| 891 | 3× | uint256 crtControllerRequestedAmount = state.pendingRedeemRequest; |
| 892 | // Check for PPS slippage if there's a recorded request PPS and max slippage is set |
|
| 893 | 21× | if (state.averageRequestPPS > 0 && _maxPPSSlippage > 0) { |
| 894 | 4× | uint256 averageRequestPPS = state.averageRequestPPS; |
| 895 | // Calculate the percentage decrease from request PPS to current PPS |
|
| 896 | 7× | if (currentPPS < averageRequestPPS) { |
| 897 | 0 | uint256 decrease = ( |
| 898 | 0 | (averageRequestPPS - currentPPS).mulDiv( |
| 899 | BPS_PRECISION, |
|
| 900 | 0 | averageRequestPPS, |
| 901 | Math.Rounding.Floor |
|
| 902 | ) |
|
| 903 | ); |
|
| 904 | // If decrease exceeds maximum allowed slippage, revert |
|
| 905 | 0 | if (decrease > _maxPPSSlippage) revert SLIPPAGE_EXCEEDED(); |
| 906 | } |
|
| 907 | } |
|
| 908 | ||
| 909 | 7× | uint256 currentAssets = _calculateHistoricalAssetsAndProcessFees( |
| 910 | 1× | state, |
| 911 | 1× | crtControllerRequestedAmount, |
| 912 | 1× | currentPPS |
| 913 | ); |
|
| 914 | ||
| 915 | // Update user state, no partial redeems allowed |
|
| 916 | 8× | state.pendingRedeemRequest = 0; |
| 917 | 17× | state.maxWithdraw += currentAssets; |
| 918 | 5× | state.averageRequestPPS = 0; // Reset PPS value after fulfillment |
| 919 | ||
| 920 | // Call vault callback |
|
| 921 | 4× | _onRedeemClaimable( |
| 922 | 24× | controllers[i], |
| 923 | 1× | currentAssets, |
| 924 | 1× | crtControllerRequestedAmount, |
| 925 | 4× | state.averageWithdrawPrice, |
| 926 | 4× | state.accumulatorShares, |
| 927 | 4× | 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 | 7× | function _calculateHistoricalAssetsAndProcessFees( |
| 937 | SuperVaultState storage state, |
|
| 938 | uint256 requestedShares, |
|
| 939 | uint256 currentPricePerShare |
|
| 940 | 1× | ) private returns (uint256 currentAssets) { |
| 941 | // Calculate cost basis based on requested shares |
|
| 942 | 9× | uint256 historicalAssets = _calculateCostBasis(state, requestedShares); |
| 943 | 1× | uint256 currentAssetsWithFees; |
| 944 | // Process fees and get final assets |
|
| 945 | 6× | (currentAssetsWithFees, currentAssets) = _processFees( |
| 946 | 1× | requestedShares, |
| 947 | 1× | currentPricePerShare, |
| 948 | 1× | historicalAssets |
| 949 | ); |
|
| 950 | ||
| 951 | // Update average withdraw price if needed |
|
| 952 | 4× | if (requestedShares > 0) { |
| 953 | 4× | _updateAverageWithdrawPrice( |
| 954 | 1× | state, |
| 955 | 1× | requestedShares, |
| 956 | 1× | currentAssetsWithFees |
| 957 | ); |
|
| 958 | } |
|
| 959 | ||
| 960 | 2× | 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 | 6× | function _calculateCostBasis( |
| 967 | SuperVaultState storage state, |
|
| 968 | uint256 requestedShares |
|
| 969 | 1× | ) private returns (uint256 costBasis) { |
| 970 | 10× | if (requestedShares > state.accumulatorShares) |
| 971 | 0 | revert INSUFFICIENT_SHARES(); |
| 972 | ||
| 973 | // Calculate cost basis proportionally |
|
| 974 | 9× | costBasis = requestedShares.mulDiv( |
| 975 | 4× | state.accumulatorCostBasis, |
| 976 | 4× | state.accumulatorShares, |
| 977 | 1× | Math.Rounding.Floor |
| 978 | ); |
|
| 979 | ||
| 980 | // Update user's accumulator state |
|
| 981 | 21× | state.accumulatorShares -= requestedShares; |
| 982 | 17× | state.accumulatorCostBasis -= costBasis; |
| 983 | ||
| 984 | return costBasis; |
|
| 985 | } |
|
| 986 | ||
| 987 | // --- Fee Processing --- |
|
| 988 | /// @notice Calculate and transfer fees based on profit |
|
| 989 | /// @param requestedShares Shares being redeemed |
|
| 990 | /// @param currentPricePerShare Current price per share |
|
| 991 | /// @param historicalAssets Historical value of shares in assets |
|
| 992 | /// @return currentAssetsWithFees Current value of shares in assets (not net of fees) |
|
| 993 | /// @return currentAssets Current assets after fee deduction |
|
| 994 | 9× | function _processFees( |
| 995 | uint256 requestedShares, |
|
| 996 | uint256 currentPricePerShare, |
|
| 997 | uint256 historicalAssets |
|
| 998 | 4× | ) private returns (uint256 currentAssetsWithFees, uint256 currentAssets) { |
| 999 | // Calculate current value of the shares at current price |
|
| 1000 | 9× | currentAssetsWithFees = requestedShares.mulDiv( |
| 1001 | 2× | currentPricePerShare, |
| 1002 | 2× | PRECISION, |
| 1003 | Math.Rounding.Floor |
|
| 1004 | ); |
|
| 1005 | ||
| 1006 | // Apply fees only on profit |
|
| 1007 | 6× | currentAssets = _calculateAndTransferFee( |
| 1008 | 1× | currentAssetsWithFees, |
| 1009 | 1× | historicalAssets |
| 1010 | ); |
|
| 1011 | ||
| 1012 | // Ensure we don't exceed available balance |
|
| 1013 | 9× | uint256 balanceOfStrategy = _getTokenBalance( |
| 1014 | 3× | address(_asset), |
| 1015 | 1× | address(this) |
| 1016 | ); |
|
| 1017 | 11× | currentAssets = currentAssets > balanceOfStrategy |
| 1018 | 1× | ? balanceOfStrategy |
| 1019 | 1× | : currentAssets; |
| 1020 | ||
| 1021 | 1× | 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 | 1× | function _calculateAndTransferFee( |
| 1029 | uint256 currentAssetsWithFees, |
|
| 1030 | uint256 historicalAssets |
|
| 1031 | ) private returns (uint256 currentAssets) { |
|
| 1032 | 1× | currentAssets = currentAssetsWithFees; |
| 1033 | 6× | if (currentAssetsWithFees > historicalAssets) { |
| 1034 | 0 | uint256 profit = currentAssetsWithFees - historicalAssets; |
| 1035 | 0 | uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
| 1036 | 0 | uint256 totalFee = profit.mulDiv( |
| 1037 | performanceFeeBps, |
|
| 1038 | BPS_PRECISION, |
|
| 1039 | Math.Rounding.Floor |
|
| 1040 | ); |
|
| 1041 | 0 | if (totalFee > 0) { |
| 1042 | // Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
|
| 1043 | 0 | uint256 superformFee = totalFee.mulDiv( |
| 1044 | 0 | superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
| 1045 | BPS_PRECISION, |
|
| 1046 | 0 | Math.Rounding.Floor |
| 1047 | ); |
|
| 1048 | 0 | uint256 recipientFee = totalFee - superformFee; |
| 1049 | ||
| 1050 | // Transfer fees |
|
| 1051 | 0 | if (superformFee > 0) { |
| 1052 | // Get treasury address from SuperGovernor |
|
| 1053 | 0 | address treasury = superGovernor.getAddress( |
| 1054 | 0 | superGovernor.TREASURY() |
| 1055 | ); |
|
| 1056 | 0 | _safeTokenTransfer(address(_asset), treasury, superformFee); |
| 1057 | 0 | emit FeePaid(treasury, superformFee, performanceFeeBps); |
| 1058 | } |
|
| 1059 | ||
| 1060 | 0 | if (recipientFee > 0) { |
| 1061 | 0 | address recipient = feeConfig.recipient; |
| 1062 | 0 | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 1063 | 0 | _safeTokenTransfer( |
| 1064 | 0 | address(_asset), |
| 1065 | 0 | recipient, |
| 1066 | 0 | recipientFee |
| 1067 | ); |
|
| 1068 | 0 | emit FeePaid(recipient, recipientFee, performanceFeeBps); |
| 1069 | } |
|
| 1070 | ||
| 1071 | 0 | currentAssets -= totalFee; |
| 1072 | } |
|
| 1073 | } |
|
| 1074 | return currentAssets; |
|
| 1075 | } |
|
| 1076 | ||
| 1077 | /// @notice Internal function to update the average withdraw price |
|
| 1078 | /// @param state Storage reference to the vault state |
|
| 1079 | /// @param requestedShares Number of shares requested |
|
| 1080 | /// @param currentAssetsWithFees Current assets with fees |
|
| 1081 | 5× | function _updateAverageWithdrawPrice( |
| 1082 | SuperVaultState storage state, |
|
| 1083 | uint256 requestedShares, |
|
| 1084 | uint256 currentAssetsWithFees |
|
| 1085 | 4× | ) private { |
| 1086 | 1× | uint256 existingShares; |
| 1087 | 1× | uint256 existingAssets; |
| 1088 | ||
| 1089 | 15× | if (state.maxWithdraw > 0 && state.averageWithdrawPrice > 0) { |
| 1090 | 0 | existingShares = state.maxWithdraw.mulDiv( |
| 1091 | 0 | PRECISION, |
| 1092 | 0 | state.averageWithdrawPrice, |
| 1093 | 0 | Math.Rounding.Floor |
| 1094 | ); |
|
| 1095 | 0 | existingAssets = state.maxWithdraw; |
| 1096 | } |
|
| 1097 | ||
| 1098 | 8× | uint256 newTotalShares = existingShares + requestedShares; |
| 1099 | 8× | uint256 newTotalAssets = existingAssets + currentAssetsWithFees; |
| 1100 | ||
| 1101 | 4× | if (newTotalShares > 0) { |
| 1102 | 11× | state.averageWithdrawPrice = newTotalAssets.mulDiv( |
| 1103 | 4× | PRECISION, |
| 1104 | 2× | newTotalShares, |
| 1105 | Math.Rounding.Floor |
|
| 1106 | ); |
|
| 1107 | } |
|
| 1108 | } |
|
| 1109 | ||
| 1110 | /// @notice Internal function to get the SuperVaultAggregator |
|
| 1111 | /// @return The SuperVaultAggregator |
|
| 1112 | 4× | function _getSuperVaultAggregator() |
| 1113 | internal |
|
| 1114 | view |
|
| 1115 | 4× | returns (ISuperVaultAggregator) |
| 1116 | { |
|
| 1117 | 244× | address aggregatorAddress = superGovernor.getAddress( |
| 1118 | 232× | superGovernor.SUPER_VAULT_AGGREGATOR() |
| 1119 | ); |
|
| 1120 | ||
| 1121 | return ISuperVaultAggregator(aggregatorAddress); |
|
| 1122 | } |
|
| 1123 | ||
| 1124 | /// @notice Internal function to check if a manager is authorized |
|
| 1125 | /// @param manager_ The manager to check |
|
| 1126 | 2× | function _isManager(address manager_) internal view { |
| 1127 | 132× | if (!_getSuperVaultAggregator().isAnyManager(manager_, address(this))) { |
| 1128 | 26× | revert MANAGER_NOT_AUTHORIZED(); |
| 1129 | } |
|
| 1130 | } |
|
| 1131 | ||
| 1132 | /// @notice Internal function to check if a manager is the primary manager |
|
| 1133 | /// @param manager_ The manager to check |
|
| 1134 | 1× | function _isPrimaryManager(address manager_) internal view { |
| 1135 | if ( |
|
| 1136 | 20× | !_getSuperVaultAggregator().isMainManager(manager_, address(this)) |
| 1137 | ) { |
|
| 1138 | revert MANAGER_NOT_AUTHORIZED(); |
|
| 1139 | } |
|
| 1140 | } |
|
| 1141 | ||
| 1142 | /// @notice Internal function to manage a yield source |
|
| 1143 | /// @param source Address of the yield source |
|
| 1144 | /// @param oracle Address of the oracle |
|
| 1145 | /// @param actionType Type of action: 0=Add, 1=UpdateOracle, 2=Remove |
|
| 1146 | 1× | function _manageYieldSource( |
| 1147 | address source, |
|
| 1148 | address oracle, |
|
| 1149 | uint8 actionType |
|
| 1150 | ) internal { |
|
| 1151 | 8× | if (actionType == 0) { |
| 1152 | 5× | _addYieldSource(source, oracle); |
| 1153 | 8× | } else if (actionType == 1) { |
| 1154 | 5× | _updateYieldSourceOracle(source, oracle); |
| 1155 | 7× | } else if (actionType == 2) { |
| 1156 | 4× | _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 | 4× | function _addYieldSource(address source, address oracle) internal { |
| 1166 | 27× | if (source == address(0) || oracle == address(0)) revert ZERO_ADDRESS(); |
| 1167 | 19× | if (yieldSources[source] != address(0)) |
| 1168 | 13× | revert YIELD_SOURCE_ALREADY_EXISTS(); |
| 1169 | 26× | yieldSources[source] = oracle; |
| 1170 | 8× | if (!yieldSourcesList.add(source)) revert YIELD_SOURCE_ALREADY_EXISTS(); |
| 1171 | ||
| 1172 | 14× | emit YieldSourceAdded(source, oracle); |
| 1173 | } |
|
| 1174 | ||
| 1175 | /// @notice Internal function to update a yield source's oracle |
|
| 1176 | /// @param source Address of the yield source |
|
| 1177 | /// @param oracle Address of the oracle |
|
| 1178 | 5× | function _updateYieldSourceOracle(address source, address oracle) internal { |
| 1179 | 18× | if (oracle == address(0)) revert ZERO_ADDRESS(); |
| 1180 | 16× | address oldOracle = yieldSources[source]; |
| 1181 | 16× | if (oldOracle == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1182 | 29× | yieldSources[source] = oracle; |
| 1183 | ||
| 1184 | 8× | 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 | 3× | function _removeYieldSource(address source) internal { |
| 1190 | 31× | if (yieldSources[source] == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1191 | ||
| 1192 | // Remove from mapping |
|
| 1193 | 18× | delete yieldSources[source]; |
| 1194 | ||
| 1195 | // Remove from EnumerableSet |
|
| 1196 | 8× | if (!yieldSourcesList.remove(source)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1197 | ||
| 1198 | 10× | emit YieldSourceRemoved(source); |
| 1199 | } |
|
| 1200 | ||
| 1201 | /// @notice Internal function to propose an emergency withdraw |
|
| 1202 | 2× | function _proposeEmergencyWithdraw() internal { |
| 1203 | 5× | _isPrimaryManager(msg.sender); |
| 1204 | ||
| 1205 | 8× | proposedEmergencyWithdrawable = true; |
| 1206 | 9× | emergencyWithdrawableEffectiveTime = block.timestamp + ONE_WEEK; |
| 1207 | 13× | emit EmergencyWithdrawableProposed( |
| 1208 | 1× | true, |
| 1209 | emergencyWithdrawableEffectiveTime |
|
| 1210 | ); |
|
| 1211 | } |
|
| 1212 | ||
| 1213 | /// @notice Internal function to execute an emergency withdraw |
|
| 1214 | 1× | function _executeEmergencyWithdrawActivation() internal { |
| 1215 | 20× | if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL(); |
| 1216 | 8× | if (block.timestamp < emergencyWithdrawableEffectiveTime) |
| 1217 | 13× | revert INVALID_TIMESTAMP(); |
| 1218 | 13× | emergencyWithdrawable = proposedEmergencyWithdrawable; |
| 1219 | 11× | proposedEmergencyWithdrawable = false; |
| 1220 | 2× | emergencyWithdrawableEffectiveTime = 0; |
| 1221 | 7× | emit EmergencyWithdrawableUpdated(emergencyWithdrawable); |
| 1222 | } |
|
| 1223 | ||
| 1224 | /// @notice Internal function to cancel an emergency withdraw proposal |
|
| 1225 | 2× | function _cancelEmergencyWithdrawProposal() internal { |
| 1226 | 5× | _isPrimaryManager(msg.sender); |
| 1227 | ||
| 1228 | 20× | if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL(); |
| 1229 | 8× | proposedEmergencyWithdrawable = false; |
| 1230 | 4× | emergencyWithdrawableEffectiveTime = 0; |
| 1231 | 5× | 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 | 1× | function _performEmergencyWithdraw( |
| 1238 | address recipient, |
|
| 1239 | uint256 amount |
|
| 1240 | ) internal { |
|
| 1241 | 5× | _isPrimaryManager(msg.sender); |
| 1242 | ||
| 1243 | 20× | if (!emergencyWithdrawable) revert INVALID_EMERGENCY_WITHDRAWAL(); |
| 1244 | 5× | if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 1245 | 12× | uint256 freeAssets = _getTokenBalance(address(_asset), address(this)); |
| 1246 | 26× | if (amount == 0 || amount > freeAssets) revert INSUFFICIENT_FUNDS(); |
| 1247 | 0 | _safeTokenTransfer(address(_asset), recipient, amount); |
| 1248 | 0 | emit EmergencyWithdrawal(recipient, amount); |
| 1249 | } |
|
| 1250 | ||
| 1251 | /// @notice Internal function to check if a hook is a fulfill requests hook |
|
| 1252 | /// @param hook Address of the hook |
|
| 1253 | /// @return True if the hook is a fulfill requests hook, false otherwise |
|
| 1254 | 6× | function _isFulfillRequestsHook(address hook) private view returns (bool) { |
| 1255 | 116× | return superGovernor.isFulfillRequestsHookRegistered(hook); |
| 1256 | } |
|
| 1257 | ||
| 1258 | /// @notice Internal function to check if a hook is registered |
|
| 1259 | /// @param hook Address of the hook |
|
| 1260 | /// @return True if the hook is registered, false otherwise |
|
| 1261 | 6× | function _isRegisteredHook(address hook) private view returns (bool) { |
| 1262 | 30× | return superGovernor.isHookRegistered(hook); |
| 1263 | } |
|
| 1264 | ||
| 1265 | /// @notice Internal function to decode a hook's use previous hook amount |
|
| 1266 | /// @param hook Address of the hook |
|
| 1267 | /// @param hookCalldata Call data for the hook |
|
| 1268 | /// @return True if the hook should use the previous hook amount, false otherwise |
|
| 1269 | 2× | function _decodeHookUsePrevHookAmount( |
| 1270 | address hook, |
|
| 1271 | bytes memory hookCalldata |
|
| 1272 | 4× | ) private pure returns (bool) { |
| 1273 | 6× | try |
| 1274 | 120× | ISuperHookContextAware(hook).decodeUsePrevHookAmount(hookCalldata) |
| 1275 | returns (bool usePrevHookAmount) { |
|
| 1276 | 6× | return usePrevHookAmount; |
| 1277 | } catch { |
|
| 1278 | 3× | return false; |
| 1279 | } |
|
| 1280 | } |
|
| 1281 | ||
| 1282 | /// @notice Internal function to get the previous hook's output amount |
|
| 1283 | /// @param prevHook Address of the previous hook |
|
| 1284 | /// @return Output amount of the previous hook |
|
| 1285 | 0 | function _getPreviousHookOutAmount( |
| 1286 | address prevHook |
|
| 1287 | 0 | ) private view returns (uint256) { |
| 1288 | 0 | return ISuperHookResultOutflow(prevHook).getOutAmount(address(this)); |
| 1289 | } |
|
| 1290 | ||
| 1291 | /// @notice Internal function to handle a redeem claimable |
|
| 1292 | /// @param controller Address of the controller |
|
| 1293 | /// @param assetsFulfilled Amount of assets fulfilled |
|
| 1294 | /// @param sharesFulfilled Amount of shares fulfilled |
|
| 1295 | /// @param averageWithdrawPrice Average withdraw price |
|
| 1296 | /// @param accumulatorShares Accumulator shares |
|
| 1297 | /// @param accumulatorCostBasis Accumulator cost basis |
|
| 1298 | 8× | function _onRedeemClaimable( |
| 1299 | address controller, |
|
| 1300 | uint256 assetsFulfilled, |
|
| 1301 | uint256 sharesFulfilled, |
|
| 1302 | uint256 averageWithdrawPrice, |
|
| 1303 | uint256 accumulatorShares, |
|
| 1304 | uint256 accumulatorCostBasis |
|
| 1305 | ) private { |
|
| 1306 | 45× | ISuperVault(_vault).onRedeemClaimable( |
| 1307 | controller, |
|
| 1308 | assetsFulfilled, |
|
| 1309 | sharesFulfilled, |
|
| 1310 | averageWithdrawPrice, |
|
| 1311 | accumulatorShares, |
|
| 1312 | accumulatorCostBasis |
|
| 1313 | ); |
|
| 1314 | } |
|
| 1315 | ||
| 1316 | /// @notice Internal function to handle a redeem |
|
| 1317 | /// @param controller Address of the controller |
|
| 1318 | /// @param shares Amount of shares |
|
| 1319 | 6× | function _handleRequestRedeem(address controller, uint256 shares) private { |
| 1320 | 6× | if (shares == 0) revert INVALID_AMOUNT(); |
| 1321 | 5× | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 1322 | 14× | SuperVaultState storage state = superVaultState[controller]; |
| 1323 | ||
| 1324 | // Defense-in-depth: assert controller has accumulator shares |
|
| 1325 | 8× | if (state.accumulatorShares == 0) revert INSUFFICIENT_SHARES(); |
| 1326 | ||
| 1327 | // Get current PPS from aggregator to use as baseline for slippage protection |
|
| 1328 | 7× | uint256 currentPPS = getStoredPPS(); |
| 1329 | 19× | if (currentPPS == 0) revert INVALID_PPS(); |
| 1330 | ||
| 1331 | // Calculate weighted average of PPS if there's an existing request |
|
| 1332 | 9× | if (state.pendingRedeemRequest > 0) { |
| 1333 | // Calculate weighted average of PPS based on share amounts |
|
| 1334 | 4× | uint256 existingSharesInRequest = state.pendingRedeemRequest; |
| 1335 | 7× | uint256 newTotalSharesInRequest = existingSharesInRequest + shares; |
| 1336 | ||
| 1337 | // Use weighted average formula: (existingShares * existingPPS + newShares * currentPPS) / totalShares |
|
| 1338 | 4× | state.averageRequestPPS = |
| 1339 | 22× | ((existingSharesInRequest * state.averageRequestPPS) + |
| 1340 | 6× | (shares * currentPPS)) / |
| 1341 | newTotalSharesInRequest; |
|
| 1342 | ||
| 1343 | // Update total shares |
|
| 1344 | 2× | state.pendingRedeemRequest = newTotalSharesInRequest; |
| 1345 | } else { |
|
| 1346 | // First request for this controller |
|
| 1347 | 3× | state.pendingRedeemRequest = shares; |
| 1348 | 6× | state.averageRequestPPS = currentPPS; |
| 1349 | } |
|
| 1350 | ||
| 1351 | 16× | emit RedeemRequestPlaced(controller, controller, shares); |
| 1352 | } |
|
| 1353 | ||
| 1354 | /// @notice Internal function to handle a redeem cancellation |
|
| 1355 | /// @param controller Address of the controller |
|
| 1356 | 1× | function _handleCancelRedeem(address controller) private { |
| 1357 | 5× | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 1358 | 14× | SuperVaultState storage state = superVaultState[controller]; |
| 1359 | 2× | uint256 pendingShares = state.pendingRedeemRequest; |
| 1360 | 19× | if (pendingShares == 0) revert REQUEST_NOT_FOUND(); |
| 1361 | // Only clear pending request metadata |
|
| 1362 | 4× | state.pendingRedeemRequest = 0; |
| 1363 | 4× | state.averageRequestPPS = 0; |
| 1364 | 8× | 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 | 5× | function _handleClaimRedeem( |
| 1372 | address controller, |
|
| 1373 | address receiver, |
|
| 1374 | uint256 assetsToClaim |
|
| 1375 | 3× | ) private { |
| 1376 | 6× | if (assetsToClaim == 0) revert INVALID_AMOUNT(); |
| 1377 | 5× | if (controller == address(0)) revert ZERO_ADDRESS(); |
| 1378 | 17× | SuperVaultState storage state = superVaultState[controller]; |
| 1379 | ||
| 1380 | // Handle dust collection for rounding errors |
|
| 1381 | 2× | uint256 actualAmountToClaim = assetsToClaim; |
| 1382 | 60× | uint256 remainingAssets = _asset.balanceOf(address(this)); |
| 1383 | ||
| 1384 | // If user is requesting slightly more than available due to rounding errors, |
|
| 1385 | // and the difference is small (dust), give them the remaining balance |
|
| 1386 | 4× | if ( |
| 1387 | 8× | assetsToClaim > remainingAssets && |
| 1388 | 0 | assetsToClaim - remainingAssets <= TOLERANCE_CONSTANT |
| 1389 | ) { |
|
| 1390 | 0 | actualAmountToClaim = remainingAssets; |
| 1391 | } |
|
| 1392 | ||
| 1393 | 10× | if (state.maxWithdraw < actualAmountToClaim) |
| 1394 | 0 | revert INVALID_REDEEM_CLAIM(); |
| 1395 | 17× | state.maxWithdraw -= actualAmountToClaim; |
| 1396 | 10× | _asset.safeTransfer(receiver, actualAmountToClaim); |
| 1397 | 20× | emit RedeemRequestFulfilled( |
| 1398 | receiver, |
|
| 1399 | controller, |
|
| 1400 | actualAmountToClaim, |
|
| 1401 | 1× | 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 | 2× | function _safeTokenTransfer( |
| 1410 | address token, |
|
| 1411 | address recipient, |
|
| 1412 | uint256 amount |
|
| 1413 | ) private { |
|
| 1414 | 22× | if (amount > 0) IERC20(token).safeTransfer(recipient, amount); |
| 1415 | } |
|
| 1416 | ||
| 1417 | /// @notice Internal function to get the token balance of an account |
|
| 1418 | /// @param token Address of the token |
|
| 1419 | /// @param account Address of the account |
|
| 1420 | /// @return Token balance of the account |
|
| 1421 | 2× | function _getTokenBalance( |
| 1422 | address token, |
|
| 1423 | address account |
|
| 1424 | 4× | ) private view returns (uint256) { |
| 1425 | 112× | return IERC20(token).balanceOf(account); |
| 1426 | } |
|
| 1427 | ||
| 1428 | /// @notice Internal function to get the slippage tolerance |
|
| 1429 | /// @return Slippage tolerance |
|
| 1430 | function _getSlippageTolerance() private pure returns (uint256) { |
|
| 1431 | return SV_SLIPPAGE_TOLERANCE_BPS; |
|
| 1432 | } |
|
| 1433 | ||
| 1434 | /// @notice Internal function to check if the caller is the vault |
|
| 1435 | /// @dev This is used to prevent unauthorized access to certain functions |
|
| 1436 | 8× | function _requireVault() internal view { |
| 1437 | 45× | if (msg.sender != _vault) revert ACCESS_DENIED(); |
| 1438 | } |
|
| 1439 | ||
| 1440 | /// @notice Checks if the strategy is currently paused |
|
| 1441 | /// @dev This calls SuperVaultAggregator.isStrategyPaused to determine pause status |
|
| 1442 | /// @return True if the strategy is paused, false otherwise |
|
| 1443 | 8× | function _isPaused() internal view returns (bool) { |
| 1444 | 248× | return _getSuperVaultAggregator().isStrategyPaused(address(this)); |
| 1445 | } |
|
| 1446 | ||
| 1447 | /// @notice Validates a hook using the Merkle root system |
|
| 1448 | /// @param hook Address of the hook to validate |
|
| 1449 | /// @param hookCalldata Calldata to be passed to the hook |
|
| 1450 | /// @param globalProof Merkle proof for the global root |
|
| 1451 | /// @param strategyProof Merkle proof for the strategy-specific root |
|
| 1452 | /// @return isValid True if the hook is valid, false otherwise |
|
| 1453 | 10× | function _validateHook( |
| 1454 | address hook, |
|
| 1455 | bytes memory hookCalldata, |
|
| 1456 | bytes32[] memory globalProof, |
|
| 1457 | bytes32[] memory strategyProof |
|
| 1458 | 2× | ) internal view returns (bool) { |
| 1459 | 4× | return |
| 1460 | 138× | _getSuperVaultAggregator().validateHook( |
| 1461 | 2× | address(this), |
| 1462 | 48× | ISuperVaultAggregator.ValidateHookArgs({ |
| 1463 | 2× | hookAddress: hook, |
| 1464 | 132× | hookArgs: ISuperHookInspector(hook).inspect(hookCalldata), |
| 1465 | 2× | globalProof: globalProof, |
| 1466 | 2× | strategyProof: strategyProof |
| 1467 | }) |
|
| 1468 | ); |
|
| 1469 | } |
|
| 1470 | } |
0%
src/UP/Up.sol
Lines covered: 0 / 22 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
|
| 5 | import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; |
|
| 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
|
| 7 | import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; |
|
| 8 | ||
| 9 | /** |
|
| 10 | * @title Up |
|
| 11 | * @author Superform Foundation |
|
| 12 | */ |
|
| 13 | 0 | contract Up is ERC20, ERC20Permit, Ownable2Step { |
| 14 | 0 | uint256 public constant INITIAL_SUPPLY = 1_000_000_000 * 10 ** 18; |
| 15 | 0 | uint256 public constant MINT_CAP_BPS = 200; // 2% |
| 16 | 0 | uint256 public constant DAYS_PER_YEAR = 365 days; |
| 17 | 0 | uint256 public constant INITIAL_MINT_LOCK = 3 * 365 days; // 3 years |
| 18 | ||
| 19 | 0 | uint256 public lastMintTimestamp; |
| 20 | 0 | uint256 public immutable INITIAL_MINT_TIMESTAMP; |
| 21 | ||
| 22 | event TokensMinted(address indexed to, uint256 amount); |
|
| 23 | ||
| 24 | error MintingTooEarly(); |
|
| 25 | error MintAmountTooHigh(); |
|
| 26 | error InitialLockPeriodNotOver(); |
|
| 27 | ||
| 28 | 0 | constructor(address initialOwner) ERC20("Superform", "UP") ERC20Permit("Superform") Ownable(initialOwner) { |
| 29 | 0 | _mint(initialOwner, INITIAL_SUPPLY); |
| 30 | 0 | lastMintTimestamp = block.timestamp; |
| 31 | 0 | INITIAL_MINT_TIMESTAMP = block.timestamp; |
| 32 | } |
|
| 33 | ||
| 34 | /** |
|
| 35 | * @dev Allows owner to mint new tokens once per year after 3 years, up to 2% of total supply |
|
| 36 | * @param to Address to mint tokens to |
|
| 37 | * @param amount Amount of tokens to mint |
|
| 38 | */ |
|
| 39 | 0 | function mint(address to, uint256 amount) external onlyOwner { |
| 40 | // Check if 3 years have passed since initial mint |
|
| 41 | 0 | if (block.timestamp < INITIAL_MINT_TIMESTAMP + INITIAL_MINT_LOCK) { |
| 42 | 0 | revert InitialLockPeriodNotOver(); |
| 43 | } |
|
| 44 | ||
| 45 | // Check if enough time has passed since last mint |
|
| 46 | 0 | if (block.timestamp < lastMintTimestamp + DAYS_PER_YEAR) { |
| 47 | 0 | revert MintingTooEarly(); |
| 48 | } |
|
| 49 | ||
| 50 | // Calculate maximum mint amount (2% of total supply) |
|
| 51 | 0 | uint256 maxMintAmount = (totalSupply() * MINT_CAP_BPS) / 10_000; |
| 52 | 0 | if (amount > maxMintAmount) { |
| 53 | 0 | revert MintAmountTooHigh(); |
| 54 | } |
|
| 55 | ||
| 56 | 0 | lastMintTimestamp = block.timestamp; |
| 57 | 0 | _mint(to, amount); |
| 58 | 0 | emit TokensMinted(to, amount); |
| 59 | } |
|
| 60 | } |
0%
src/UP/UpDistributor.sol
Lines covered: 0 / 30 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; |
|
| 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 6 | import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; |
|
| 7 | import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; |
|
| 8 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 9 | import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; |
|
| 10 | ||
| 11 | /** |
|
| 12 | * @title UpDistributor |
|
| 13 | * @notice A contract for distributing tokens using a merkle tree for verification |
|
| 14 | * @dev The foundation can reclaim unclaimed tokens |
|
| 15 | */ |
|
| 16 | 0 | contract UpDistributor is Ownable2Step { |
| 17 | using SafeERC20 for IERC20; |
|
| 18 | ||
| 19 | 0 | IERC20 public immutable token; |
| 20 | 0 | bytes32 public merkleRoot; |
| 21 | ||
| 22 | /// @notice Track which addresses have claimed their tokens |
|
| 23 | 0 | mapping(address => bool) public hasClaimed; |
| 24 | ||
| 25 | event MerkleRootSet(bytes32 merkleRoot); |
|
| 26 | event TokensClaimed(address indexed user, uint256 amount); |
|
| 27 | event TokensReclaimed(uint256 amount); |
|
| 28 | ||
| 29 | error ALREADY_CLAIMED(); |
|
| 30 | error NO_TOKENS_TO_RECLAIM(); |
|
| 31 | error INVALID_MERKLE_PROOF(); |
|
| 32 | error INVALID_TOKEN_ADDRESS(); |
|
| 33 | ||
| 34 | 0 | constructor(address _token, address initialOwner) Ownable(initialOwner) { |
| 35 | 0 | if (_token == address(0)) revert INVALID_TOKEN_ADDRESS(); |
| 36 | 0 | token = IERC20(_token); |
| 37 | } |
|
| 38 | ||
| 39 | /** |
|
| 40 | * @notice Set a new merkle root for the distribution |
|
| 41 | * @param _merkleRoot The new merkle root |
|
| 42 | */ |
|
| 43 | 0 | function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { |
| 44 | 0 | merkleRoot = _merkleRoot; |
| 45 | 0 | emit MerkleRootSet(_merkleRoot); |
| 46 | } |
|
| 47 | ||
| 48 | /** |
|
| 49 | * @notice Claim tokens if you are part of the merkle tree |
|
| 50 | * @param amount The amount of tokens to claim |
|
| 51 | * @param merkleProof A proof of inclusion in the merkle tree |
|
| 52 | */ |
|
| 53 | 0 | function claim(uint256 amount, bytes32[] calldata merkleProof) external { |
| 54 | // Verify user hasn't already claimed |
|
| 55 | 0 | if (hasClaimed[msg.sender]) revert ALREADY_CLAIMED(); |
| 56 | ||
| 57 | // Verify the merkle proof |
|
| 58 | 0 | bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amount)))); |
| 59 | 0 | if (!MerkleProof.verify(merkleProof, merkleRoot, leaf)) revert INVALID_MERKLE_PROOF(); |
| 60 | ||
| 61 | // Mark as claimed and transfer tokens |
|
| 62 | 0 | hasClaimed[msg.sender] = true; |
| 63 | 0 | emit TokensClaimed(msg.sender, amount); |
| 64 | 0 | token.safeTransfer(msg.sender, amount); |
| 65 | } |
|
| 66 | ||
| 67 | /** |
|
| 68 | * @notice Claim tokens for a recipient |
|
| 69 | * @dev This function can be called by users with smart accounts |
|
| 70 | * @param recipient The address to claim tokens for |
|
| 71 | * @param amount The amount of tokens to claim |
|
| 72 | * @param merkleProof A proof of inclusion in the merkle tree |
|
| 73 | */ |
|
| 74 | 0 | function claimOnBehalf( |
| 75 | address recipient, |
|
| 76 | uint256 amount, |
|
| 77 | bytes32[] calldata merkleProof |
|
| 78 | ) |
|
| 79 | external |
|
| 80 | 0 | { |
| 81 | // Verify user hasn't already claimed |
|
| 82 | 0 | if (hasClaimed[recipient]) revert ALREADY_CLAIMED(); |
| 83 | ||
| 84 | // Verify the merkle proof |
|
| 85 | 0 | bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(recipient, amount)))); |
| 86 | 0 | if (!MerkleProof.verify(merkleProof, merkleRoot, leaf)) revert INVALID_MERKLE_PROOF(); |
| 87 | ||
| 88 | // Mark as claimed and transfer tokens |
|
| 89 | 0 | hasClaimed[recipient] = true; |
| 90 | 0 | emit TokensClaimed(recipient, amount); |
| 91 | 0 | token.safeTransfer(recipient, amount); |
| 92 | } |
|
| 93 | ||
| 94 | /** |
|
| 95 | * @notice Allow the foundation to reclaim unclaimed tokens |
|
| 96 | * @param amount The amount of tokens to reclaim |
|
| 97 | */ |
|
| 98 | 0 | function reclaimTokens(uint256 amount) external onlyOwner { |
| 99 | 0 | uint256 balance = token.balanceOf(address(this)); |
| 100 | 0 | if (amount > balance) revert NO_TOKENS_TO_RECLAIM(); |
| 101 | ||
| 102 | 0 | emit TokensReclaimed(amount); |
| 103 | 0 | token.safeTransfer(owner(), amount); |
| 104 | } |
|
| 105 | } |
0%
src/VaultBank/VaultBank.sol
Lines covered: 0 / 104 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { BytesLib } from "../vendor/BytesLib.sol"; |
|
| 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 7 | import { ICrossL2ProverV2 } from "../vendor/polymer/ICrossL2ProverV2.sol"; |
|
| 8 | import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; |
|
| 9 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { VaultBankSuperPosition } from "./VaultBankSuperPosition.sol"; |
|
| 13 | import { IVaultBank, IVaultBankSource } from "../interfaces/VaultBank/IVaultBank.sol"; |
|
| 14 | import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol"; |
|
| 15 | import { VaultBankDestination } from "./VaultBankDestination.sol"; |
|
| 16 | import { VaultBankSource } from "./VaultBankSource.sol"; |
|
| 17 | import { Bank } from "../Bank.sol"; |
|
| 18 | ||
| 19 | /// @title VaultBank |
|
| 20 | /// @author Superform Labs |
|
| 21 | /// @notice Locks assets and mints SuperPositions |
|
| 22 | 0 | contract VaultBank is IVaultBank, VaultBankSource, VaultBankDestination, Bank { |
| 23 | using SafeERC20 for IERC20; |
|
| 24 | using BytesLib for bytes; |
|
| 25 | ||
| 26 | /*////////////////////////////////////////////////////////////// |
|
| 27 | STORAGE |
|
| 28 | //////////////////////////////////////////////////////////////*/ |
|
| 29 | 0 | ISuperGovernor public immutable SUPER_GOVERNOR; |
| 30 | ||
| 31 | 0 | mapping(uint64 toChainId => uint256 nonce) public nonces; |
| 32 | 0 | mapping(uint64 fromChainId => mapping(uint256 nonce => bool isUsed)) public noncesUsed; |
| 33 | ||
| 34 | 0 | constructor(address governor_) { |
| 35 | 0 | if (governor_ == address(0)) revert INVALID_VALUE(); |
| 36 | 0 | SUPER_GOVERNOR = ISuperGovernor(governor_); |
| 37 | } |
|
| 38 | ||
| 39 | modifier onlyRelayer() { |
|
| 40 | 0 | if (!SUPER_GOVERNOR.isRelayer(msg.sender)) revert INVALID_RELAYER(); |
| 41 | _; |
|
| 42 | } |
|
| 43 | ||
| 44 | modifier onlyBankManager() { |
|
| 45 | 0 | if (!IAccessControl(address(SUPER_GOVERNOR)).hasRole(SUPER_GOVERNOR.BANK_MANAGER_ROLE(), msg.sender)) { |
| 46 | 0 | revert INVALID_BANK_MANAGER(); |
| 47 | } |
|
| 48 | _; |
|
| 49 | } |
|
| 50 | ||
| 51 | /// @dev to receive ETH rewards |
|
| 52 | receive() external payable { } |
|
| 53 | ||
| 54 | /*////////////////////////////////////////////////////////////// |
|
| 55 | EXTERNAL METHODS |
|
| 56 | //////////////////////////////////////////////////////////////*/ |
|
| 57 | // ------------------ SOURCE VAULTBANK METHODS ------------------ |
|
| 58 | /// @inheritdoc IVaultBank |
|
| 59 | 0 | function lockAsset( |
| 60 | bytes32 yieldSourceOracleId, |
|
| 61 | address account, |
|
| 62 | address token, |
|
| 63 | address hookAddress, |
|
| 64 | uint256 amount, |
|
| 65 | uint64 toChainId |
|
| 66 | ) |
|
| 67 | external |
|
| 68 | 0 | { |
| 69 | 0 | address vaultBank = SUPER_GOVERNOR.getVaultBank(toChainId); |
| 70 | ||
| 71 | 0 | if (vaultBank == address(0)) revert INVALID_VAULT_BANK_ADDRESS(); |
| 72 | 0 | if (!SUPER_GOVERNOR.isHookRegistered(hookAddress)) revert INVALID_HOOK(); |
| 73 | ||
| 74 | 0 | uint256 _nonce = nonces[toChainId]; |
| 75 | 0 | nonces[toChainId]++; |
| 76 | 0 | _lockAssetForChain(yieldSourceOracleId, account, token, amount, toChainId, _nonce); |
| 77 | } |
|
| 78 | ||
| 79 | /// @inheritdoc IVaultBank |
|
| 80 | 0 | function unlockAsset( |
| 81 | address account, |
|
| 82 | address token, |
|
| 83 | uint256 amount, |
|
| 84 | uint64 fromChainId, |
|
| 85 | bytes32 yieldSourceOracleId, |
|
| 86 | bytes calldata proof |
|
| 87 | ) |
|
| 88 | external |
|
| 89 | { |
|
| 90 | // validate and mark `proof.nonce[fromChainId]` as used |
|
| 91 | 0 | _validateUnlockAssetProof(account, yieldSourceOracleId, token, amount, fromChainId, proof); |
| 92 | ||
| 93 | //`toChainId` is current chain |
|
| 94 | 0 | uint256 _nonce = nonces[uint64(_chainId)]; |
| 95 | 0 | nonces[uint64(_chainId)]++; |
| 96 | 0 | _releaseAssetFromChain(yieldSourceOracleId, account, token, amount, fromChainId, _nonce); |
| 97 | } |
|
| 98 | ||
| 99 | /// @inheritdoc IVaultBank |
|
| 100 | 0 | function executeHooks(IVaultBank.HookExecutionData calldata executionData) external onlyBankManager { |
| 101 | 0 | _executeHooks(executionData); |
| 102 | } |
|
| 103 | ||
| 104 | /// @inheritdoc IVaultBank |
|
| 105 | 0 | function batchDistributeRewardsToSuperBank( |
| 106 | address[] memory rewards, |
|
| 107 | uint256[] memory amounts |
|
| 108 | ) |
|
| 109 | external |
|
| 110 | onlyRelayer |
|
| 111 | 0 | { |
| 112 | 0 | uint256 len = rewards.length; |
| 113 | 0 | for (uint256 i; i < len; ++i) { |
| 114 | 0 | _distributeRewardsToSuperBank(rewards[i], amounts[i]); |
| 115 | } |
|
| 116 | 0 | emit BatchDistributeRewardsToSuperBank(rewards, amounts); |
| 117 | } |
|
| 118 | ||
| 119 | // ------------------ DESTINATION VAULTBANK METHODS ------------------ |
|
| 120 | /// @inheritdoc IVaultBank |
|
| 121 | 0 | function distributeSuperPosition( |
| 122 | address account_, |
|
| 123 | uint256 amount_, |
|
| 124 | SourceAssetInfo calldata sourceAsset_, |
|
| 125 | bytes calldata proof_ |
|
| 126 | ) |
|
| 127 | external |
|
| 128 | override |
|
| 129 | onlyRelayer |
|
| 130 | 0 | { |
| 131 | // validate and mark `proof.nonce[sourceAsset_.chainId]` as used |
|
| 132 | 0 | _validateDistributeSPProof(account_, sourceAsset_.yieldSourceOracleId, sourceAsset_.asset, amount_, sourceAsset_.chainId, proof_); |
| 133 | ||
| 134 | 0 | address spAddress = _retrieveSuperPosition( |
| 135 | 0 | sourceAsset_.yieldSourceOracleId, |
| 136 | 0 | sourceAsset_.chainId, |
| 137 | 0 | sourceAsset_.asset, |
| 138 | 0 | sourceAsset_.name, |
| 139 | 0 | sourceAsset_.symbol, |
| 140 | 0 | sourceAsset_.decimals |
| 141 | ); |
|
| 142 | 0 | _mintSP(account_, spAddress, amount_); |
| 143 | ||
| 144 | 0 | nonces[uint64(_chainId)]++; |
| 145 | ||
| 146 | 0 | emit SuperpositionsMinted( |
| 147 | 0 | account_, spAddress, sourceAsset_.asset, amount_, sourceAsset_.chainId, _extractNonce(proof_) |
| 148 | ); |
|
| 149 | } |
|
| 150 | ||
| 151 | /// @inheritdoc IVaultBank |
|
| 152 | 0 | function burnSuperPosition( |
| 153 | uint256 amount_, |
|
| 154 | address spAddress_, |
|
| 155 | uint64 forChainId_, |
|
| 156 | bytes32 yieldSourceOracleId_ |
|
| 157 | ) |
|
| 158 | external |
|
| 159 | override |
|
| 160 | 0 | { |
| 161 | 0 | _burnSP(msg.sender, spAddress_, amount_); |
| 162 | 0 | uint256 _nonce = nonces[forChainId_]; |
| 163 | 0 | nonces[forChainId_]++; |
| 164 | 0 | emit SuperpositionsBurned( |
| 165 | 0 | msg.sender, |
| 166 | spAddress_, |
|
| 167 | 0 | _spAssetsInfo[spAddress_].spToToken[forChainId_][yieldSourceOracleId_], |
| 168 | amount_, |
|
| 169 | forChainId_, |
|
| 170 | _nonce |
|
| 171 | ); |
|
| 172 | } |
|
| 173 | ||
| 174 | 0 | function transferSuperPositionOwnership(address superPos, address newOwner) external onlyBankManager { |
| 175 | 0 | VaultBankSuperPosition(superPos).transferOwnership(newOwner); |
| 176 | } |
|
| 177 | ||
| 178 | /*////////////////////////////////////////////////////////////// |
|
| 179 | INTERNAL METHODS |
|
| 180 | //////////////////////////////////////////////////////////////*/ |
|
| 181 | 0 | function _getMerkleRootForHook(address hookAddress) internal view override returns (bytes32) { |
| 182 | 0 | return SUPER_GOVERNOR.getVaultBankHookMerkleRoot(hookAddress); |
| 183 | } |
|
| 184 | ||
| 185 | 0 | function _distributeRewardsToSuperBank(address token, uint256 amount) internal { |
| 186 | // get SuperBank address |
|
| 187 | 0 | address superBank = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK()); |
| 188 | ||
| 189 | 0 | if (token == address(0)) { |
| 190 | // distribute ETH |
|
| 191 | 0 | (bool success,) = superBank.call{ value: amount }(""); |
| 192 | 0 | if (!success) revert INVALID_VALUE(); |
| 193 | } else { |
|
| 194 | // distribute ERC20 |
|
| 195 | 0 | IERC20(token).safeTransfer(superBank, amount); |
| 196 | } |
|
| 197 | } |
|
| 198 | ||
| 199 | 0 | function _validateDistributeSPProof( |
| 200 | address account, |
|
| 201 | bytes32 yieldSourceOracleId, |
|
| 202 | address token, |
|
| 203 | uint256 amount, |
|
| 204 | uint64 fromChainId, |
|
| 205 | bytes calldata proof |
|
| 206 | ) |
|
| 207 | internal |
|
| 208 | { |
|
| 209 | 0 | (uint32 chainId, address emittingContract, bytes memory topics, bytes memory unindexedData) = |
| 210 | 0 | ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof); |
| 211 | ||
| 212 | 0 | address vaultBank = SUPER_GOVERNOR.getVaultBank(uint64(fromChainId)); |
| 213 | ||
| 214 | 0 | if (emittingContract != vaultBank) revert INVALID_PROOF_EMITTER(); |
| 215 | ||
| 216 | 0 | _validateSPTopics(account, yieldSourceOracleId, token, topics); |
| 217 | 0 | _validateSPData(amount, fromChainId, chainId, unindexedData); |
| 218 | } |
|
| 219 | ||
| 220 | 0 | function _extractNonce(bytes calldata proof_) internal view returns (uint256) { |
| 221 | 0 | (,,, bytes memory unindexedData) = ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof_); |
| 222 | ||
| 223 | 0 | (,,, uint256 eventNonce) = abi.decode(unindexedData, (uint256, uint64, uint64, uint256)); |
| 224 | return eventNonce; |
|
| 225 | } |
|
| 226 | ||
| 227 | 0 | function _validateSPTopics(address account, bytes32 yieldSourceOracleId, address token, bytes memory topics) private pure { |
| 228 | // topics[0] = event signature |
|
| 229 | 0 | if (topics.toBytes32(0) != IVaultBankSource.SharesLocked.selector) revert INVALID_PROOF_EVENT(); |
| 230 | ||
| 231 | // topics[1] = yieldSourceOracleId |
|
| 232 | if (topics.toBytes32(32) != yieldSourceOracleId) revert INVALID_PROOF_YIELD_SOURCE_ORACLE_ID(); |
|
| 233 | ||
| 234 | // topics[2] = account |
|
| 235 | if (topics.toBytes32(64) != bytes32(uint256(uint160(account)))) revert INVALID_PROOF_ACCOUNT(); |
|
| 236 | ||
| 237 | // topics[3] = srcTokenAddress |
|
| 238 | if (topics.toBytes32(96) != keccak256(abi.encodePacked(token))) revert INVALID_PROOF_TOKEN(); |
|
| 239 | } |
|
| 240 | ||
| 241 | 0 | function _validateSPData(uint256 amount, uint64 fromChainId, uint32 chainId, bytes memory unindexedData) private { |
| 242 | 0 | (uint256 eventAmount, uint64 eventSrcChainId, uint64 eventDstChainId, uint256 eventNonce) = |
| 243 | 0 | abi.decode(unindexedData, (uint256, uint64, uint64, uint256)); |
| 244 | ||
| 245 | 0 | if (eventAmount != amount) revert INVALID_PROOF_AMOUNT(); |
| 246 | 0 | if (eventSrcChainId != fromChainId || uint64(chainId) != fromChainId) revert INVALID_PROOF_SOURCE_CHAIN(); |
| 247 | 0 | if (eventDstChainId != _chainId) revert INVALID_PROOF_TARGETED_CHAIN(); |
| 248 | 0 | if (noncesUsed[fromChainId][eventNonce]) revert NONCE_ALREADY_USED(); |
| 249 | 0 | noncesUsed[fromChainId][eventNonce] = true; |
| 250 | } |
|
| 251 | ||
| 252 | 0 | function _validateUnlockAssetProof( |
| 253 | address account, |
|
| 254 | bytes32 yieldSourceOracleId, |
|
| 255 | address token, |
|
| 256 | uint256 amount, |
|
| 257 | uint64 fromChainId, |
|
| 258 | bytes calldata proof |
|
| 259 | ) |
|
| 260 | internal |
|
| 261 | 0 | { |
| 262 | 0 | (uint32 chainId, address emittingContract, bytes memory topics, bytes memory unindexedData) = |
| 263 | 0 | ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof); |
| 264 | ||
| 265 | 0 | if (uint64(chainId) != fromChainId) revert INVALID_PROOF_CHAIN(); |
| 266 | ||
| 267 | 0 | address vaultBank = SUPER_GOVERNOR.getVaultBank(uint64(fromChainId)); |
| 268 | ||
| 269 | 0 | if (emittingContract != vaultBank) revert INVALID_PROOF_EMITTER(); |
| 270 | ||
| 271 | 0 | _validateUnlockTopics(account, yieldSourceOracleId, token, topics); |
| 272 | 0 | _validateUnlockData(amount, fromChainId, unindexedData); |
| 273 | } |
|
| 274 | ||
| 275 | 0 | function _validateUnlockTopics(address account, bytes32 yieldSourceOracleId, address token, bytes memory topics) private pure { |
| 276 | // topics[0] = event signature |
|
| 277 | 0 | if (topics.toBytes32(0) != IVaultBank.SuperpositionsBurned.selector) revert INVALID_PROOF_EVENT(); |
| 278 | ||
| 279 | // topics[1] = yieldSourceOracleId |
|
| 280 | 0 | if (topics.toBytes32(32) != yieldSourceOracleId) revert INVALID_PROOF_YIELD_SOURCE_ORACLE_ID(); |
| 281 | ||
| 282 | // topics[2] = account |
|
| 283 | 0 | if (topics.toBytes32(64) != bytes32(uint256(uint160(account)))) revert INVALID_PROOF_ACCOUNT(); |
| 284 | ||
| 285 | // topics[3] = token |
|
| 286 | 0 | if (topics.toBytes32(96) != keccak256(abi.encodePacked(token))) revert INVALID_PROOF_TOKEN(); |
| 287 | } |
|
| 288 | ||
| 289 | 0 | function _validateUnlockData(uint256 amount, uint64 fromChainId, bytes memory unindexedData) private { |
| 290 | 0 | (uint256 eventAmount, uint64 eventChainId, uint256 eventNonce) = |
| 291 | 0 | abi.decode(unindexedData, (uint256, uint64, uint256)); |
| 292 | ||
| 293 | 0 | if (eventAmount != amount) revert INVALID_PROOF_AMOUNT(); |
| 294 | 0 | if (eventChainId != _chainId) revert INVALID_PROOF_TARGETED_CHAIN(); |
| 295 | 0 | if (noncesUsed[fromChainId][eventNonce]) revert NONCE_ALREADY_USED(); |
| 296 | 0 | noncesUsed[fromChainId][eventNonce] = true; |
| 297 | } |
|
| 298 | } |
0%
src/VaultBank/VaultBankDestination.sol
Lines covered: 0 / 22 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // Superform |
|
| 5 | import { VaultBankSuperPosition } from "./VaultBankSuperPosition.sol"; |
|
| 6 | import { IVaultBankDestination } from "../interfaces/VaultBank/IVaultBank.sol"; |
|
| 7 | ||
| 8 | abstract contract VaultBankDestination is IVaultBankDestination { |
|
| 9 | /*////////////////////////////////////////////////////////////// |
|
| 10 | STORAGE |
|
| 11 | //////////////////////////////////////////////////////////////*/ |
|
| 12 | // synthetic assets |
|
| 13 | mapping(uint64 srcChainId => mapping(bytes32 yieldSourceOracleId => mapping(address srcTokenAddress => address superPositions))) internal |
|
| 14 | _tokenToSuperPosition; |
|
| 15 | mapping(address spToken => SpAsset) internal _spAssetsInfo; |
|
| 16 | ||
| 17 | /*////////////////////////////////////////////////////////////// |
|
| 18 | VIEW METHODS |
|
| 19 | //////////////////////////////////////////////////////////////*/ |
|
| 20 | /// @inheritdoc IVaultBankDestination |
|
| 21 | 0 | function getSuperPositionForAsset(uint64 srcChainId, address srcAsset, bytes32 yieldSourceOracleId) external view returns (address) { |
| 22 | 0 | return _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset]; |
| 23 | } |
|
| 24 | ||
| 25 | /// @inheritdoc IVaultBankDestination |
|
| 26 | 0 | function getAssetForSuperPosition(uint64 srcChainId, address superPosition, bytes32 yieldSourceOracleId) external view returns (address) { |
| 27 | 0 | return _spAssetsInfo[superPosition].spToToken[srcChainId][yieldSourceOracleId]; |
| 28 | } |
|
| 29 | ||
| 30 | /// @inheritdoc IVaultBankDestination |
|
| 31 | 0 | function isSuperPositionCreated(address superPosition) external view returns (bool) { |
| 32 | 0 | return _spAssetsInfo[superPosition].wasCreated; |
| 33 | } |
|
| 34 | ||
| 35 | /*////////////////////////////////////////////////////////////// |
|
| 36 | PRIVATE METHODS |
|
| 37 | //////////////////////////////////////////////////////////////*/ |
|
| 38 | 0 | function _retrieveSuperPosition( |
| 39 | bytes32 yieldSourceOracleId, |
|
| 40 | uint64 srcChainId, |
|
| 41 | address srcAsset, |
|
| 42 | string calldata _srcName, |
|
| 43 | string calldata _srcSymbol, |
|
| 44 | uint8 _srcDecimals |
|
| 45 | ) |
|
| 46 | internal |
|
| 47 | 0 | returns (address) |
| 48 | { |
|
| 49 | 0 | address _created = _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset]; |
| 50 | 0 | if (_created != address(0)) return _created; |
| 51 | ||
| 52 | 0 | _created = address(new VaultBankSuperPosition(_srcName, _srcSymbol, _srcDecimals, yieldSourceOracleId)); |
| 53 | 0 | _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset] = _created; |
| 54 | 0 | _spAssetsInfo[_created].spToToken[srcChainId][yieldSourceOracleId] = srcAsset; |
| 55 | 0 | _spAssetsInfo[_created].wasCreated = true; |
| 56 | 0 | return _created; |
| 57 | } |
|
| 58 | ||
| 59 | 0 | function _mintSP(address account, address superPosition, uint256 amount) internal { |
| 60 | // at this point the asset should exist |
|
| 61 | 0 | if (!_spAssetsInfo[superPosition].wasCreated) revert SUPERPOSITION_ASSET_NOT_FOUND(); |
| 62 | ||
| 63 | // mint the synthetic asset |
|
| 64 | 0 | VaultBankSuperPosition(superPosition).mint(account, amount); |
| 65 | } |
|
| 66 | ||
| 67 | 0 | function _burnSP(address account, address superPosition, uint256 amount) internal { |
| 68 | // at this point the asset should exist |
|
| 69 | 0 | if (!_spAssetsInfo[superPosition].wasCreated) revert SUPERPOSITION_ASSET_NOT_FOUND(); |
| 70 | ||
| 71 | 0 | if (amount > VaultBankSuperPosition(superPosition).balanceOf(account)) revert INVALID_BURN_AMOUNT(); |
| 72 | ||
| 73 | // burn the synthetic asset |
|
| 74 | 0 | VaultBankSuperPosition(superPosition).burn(account, amount); |
| 75 | } |
|
| 76 | } |
0%
src/VaultBank/VaultBankSource.sol
Lines covered: 0 / 30 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 6 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
|
| 7 | import { ExcessivelySafeCall } from "excessivelySafeCall/ExcessivelySafeCall.sol"; |
|
| 8 | import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
|
| 9 | ||
| 10 | // Superform |
|
| 11 | import { IVaultBankSource } from "../interfaces/VaultBank/IVaultBank.sol"; |
|
| 12 | ||
| 13 | abstract contract VaultBankSource is IVaultBankSource { |
|
| 14 | using SafeERC20 for IERC20; |
|
| 15 | using ExcessivelySafeCall for address; |
|
| 16 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 17 | ||
| 18 | /*////////////////////////////////////////////////////////////// |
|
| 19 | STORAGE |
|
| 20 | //////////////////////////////////////////////////////////////*/ |
|
| 21 | // locked assets |
|
| 22 | EnumerableSet.AddressSet internal _lockedAssets; |
|
| 23 | mapping(address token => uint256 amount) internal _lockedAmounts; |
|
| 24 | ||
| 25 | uint64 internal immutable _chainId; |
|
| 26 | ||
| 27 | constructor() { |
|
| 28 | 0 | _chainId = uint64(block.chainid); |
| 29 | } |
|
| 30 | ||
| 31 | /*////////////////////////////////////////////////////////////// |
|
| 32 | VIEW METHODS |
|
| 33 | //////////////////////////////////////////////////////////////*/ |
|
| 34 | /// @inheritdoc IVaultBankSource |
|
| 35 | 0 | function viewTotalLockedAsset(address token) external view returns (uint256) { |
| 36 | 0 | return _lockedAmounts[token]; |
| 37 | } |
|
| 38 | ||
| 39 | /// @inheritdoc IVaultBankSource |
|
| 40 | 0 | function viewAllLockedAssets() external view returns (address[] memory) { |
| 41 | 0 | return _lockedAssets.values(); |
| 42 | } |
|
| 43 | ||
| 44 | /*////////////////////////////////////////////////////////////// |
|
| 45 | INTERNAL METHODS |
|
| 46 | //////////////////////////////////////////////////////////////*/ |
|
| 47 | // ------------------ SYNTHETIC ASSETS ------------------ |
|
| 48 | 0 | function _lockAssetForChain( |
| 49 | bytes32 yieldSourceOracleId, |
|
| 50 | address account, |
|
| 51 | address token, |
|
| 52 | uint256 amount, |
|
| 53 | uint64 toChainId, |
|
| 54 | uint256 nonce |
|
| 55 | ) |
|
| 56 | internal |
|
| 57 | { |
|
| 58 | 0 | if (amount == 0) revert INVALID_AMOUNT(); |
| 59 | 0 | if (token == address(0)) revert INVALID_TOKEN(); |
| 60 | 0 | if (account == address(0)) revert INVALID_ACCOUNT(); |
| 61 | 0 | if (yieldSourceOracleId == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID(); |
| 62 | ||
| 63 | 0 | if (!_lockedAssets.contains(token)) { |
| 64 | 0 | _lockedAssets.add(token); |
| 65 | } |
|
| 66 | 0 | _lockedAmounts[token] += amount; |
| 67 | ||
| 68 | 0 | IERC20(token).safeTransferFrom(account, address(this), amount); |
| 69 | 0 | emit SharesLocked(yieldSourceOracleId, account, token, amount, _chainId, toChainId, nonce); |
| 70 | } |
|
| 71 | ||
| 72 | 0 | function _releaseAssetFromChain( |
| 73 | bytes32 yieldSourceOracleId, |
|
| 74 | address account, |
|
| 75 | address token, |
|
| 76 | uint256 amount, |
|
| 77 | uint64 fromChainId, |
|
| 78 | uint256 nonce |
|
| 79 | ) |
|
| 80 | internal |
|
| 81 | { |
|
| 82 | 0 | if (account == address(0)) revert INVALID_ACCOUNT(); |
| 83 | 0 | if (token == address(0)) revert INVALID_TOKEN(); |
| 84 | 0 | if (amount == 0 || amount > _lockedAmounts[token]) revert INVALID_AMOUNT(); |
| 85 | 0 | if (yieldSourceOracleId == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID(); |
| 86 | ||
| 87 | 0 | _lockedAmounts[token] -= amount; |
| 88 | 0 | if (_lockedAmounts[token] == 0) { |
| 89 | 0 | _lockedAssets.remove(token); |
| 90 | } |
|
| 91 | ||
| 92 | 0 | IERC20(token).safeTransfer(account, amount); |
| 93 | 0 | emit SharesUnlocked(yieldSourceOracleId, account, token, amount, _chainId, fromChainId, nonce); |
| 94 | } |
|
| 95 | // ------------------ MANAGE REWARDS ------------------ |
|
| 96 | ||
| 97 | 0 | function _claimRewards( |
| 98 | address target, |
|
| 99 | uint256 gasLimit, |
|
| 100 | uint256 value, |
|
| 101 | uint16 maxReturnDataCopy, |
|
| 102 | bytes calldata data |
|
| 103 | ) |
|
| 104 | internal |
|
| 105 | 0 | returns (bytes memory) |
| 106 | { |
|
| 107 | 0 | if (target == address(0) || target == address(this)) revert INVALID_CLAIM_TARGET(); |
| 108 | 0 | (bool success, bytes memory result) = target.excessivelySafeCall(gasLimit, value, maxReturnDataCopy, data); |
| 109 | 0 | if (!success) revert CLAIM_FAILED(); |
| 110 | return result; |
|
| 111 | } |
|
| 112 | } |
0%
src/VaultBank/VaultBankSuperPosition.sol
Lines covered: 0 / 14 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; |
|
| 5 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
|
| 6 | import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; |
|
| 7 | import { ISuperPositions } from "../interfaces/VaultBank/ISuperPositions.sol"; |
|
| 8 | ||
| 9 | 0 | contract VaultBankSuperPosition is ERC20, Ownable2Step, ISuperPositions { |
| 10 | /*////////////////////////////////////////////////////////////// |
|
| 11 | STORAGE |
|
| 12 | //////////////////////////////////////////////////////////////*/ |
|
| 13 | 0 | bytes32 public yieldSourceOracleId; |
| 14 | uint8 private _decimals; |
|
| 15 | ||
| 16 | /// @dev `msg.sender` is VaultBank |
|
| 17 | 0 | constructor( |
| 18 | string memory name, |
|
| 19 | string memory symbol, |
|
| 20 | uint8 decimals_, |
|
| 21 | bytes32 yieldSourceOracleId_ |
|
| 22 | 0 | ) ERC20(name, symbol) Ownable(msg.sender) { |
| 23 | 0 | if (decimals_ == 0) revert INVALID_DECIMALS(); |
| 24 | 0 | if (yieldSourceOracleId_ == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID(); |
| 25 | 0 | _decimals = decimals_; |
| 26 | 0 | yieldSourceOracleId = yieldSourceOracleId_; |
| 27 | } |
|
| 28 | ||
| 29 | /*////////////////////////////////////////////////////////////// |
|
| 30 | VIEW METHODS |
|
| 31 | //////////////////////////////////////////////////////////////*/ |
|
| 32 | /// @notice Get the number of decimals for the token |
|
| 33 | 0 | function decimals() public view override returns (uint8) { |
| 34 | 0 | return _decimals; |
| 35 | } |
|
| 36 | /*////////////////////////////////////////////////////////////// |
|
| 37 | EXTERNAL METHODS |
|
| 38 | //////////////////////////////////////////////////////////////*/ |
|
| 39 | /// @notice Mint tokens to the specified address |
|
| 40 | /// @param to_ The address to mint tokens to |
|
| 41 | /// @param amount_ The amount of tokens to mint |
|
| 42 | 0 | function mint(address to_, uint256 amount_) external onlyOwner { |
| 43 | 0 | _mint(to_, amount_); |
| 44 | } |
|
| 45 | ||
| 46 | /// @notice Burn tokens from the specified address |
|
| 47 | /// @param from_ The address to burn tokens from |
|
| 48 | /// @param amount_ The amount of tokens to burn |
|
| 49 | 0 | function burn(address from_, uint256 amount_) external onlyOwner { |
| 50 | 0 | _burn(from_, amount_); |
| 51 | } |
|
| 52 | } |
90%
src/libraries/AssetMetadataLib.sol
Lines covered: 9 / 10 (90%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; |
|
| 5 | ||
| 6 | /// @title AssetMetadataLib |
|
| 7 | /// @author Superform Labs |
|
| 8 | /// @notice Library for handling ERC20 metadata operations |
|
| 9 | 0 | library AssetMetadataLib { |
| 10 | error INVALID_ASSET(); |
|
| 11 | ||
| 12 | /** |
|
| 13 | * @notice Attempts to fetch an asset's decimals |
|
| 14 | * @dev A return value of false indicates that the attempt failed in some way |
|
| 15 | * @param asset_ The address of the token to query |
|
| 16 | * @return ok Boolean indicating if the operation was successful |
|
| 17 | * @return assetDecimals The token's decimals if successful, 0 otherwise |
|
| 18 | */ |
|
| 19 | 12× | function tryGetAssetDecimals(address asset_) internal view returns (bool ok, uint8 assetDecimals) { |
| 20 | 29× | if(asset_.code.length == 0) revert INVALID_ASSET(); |
| 21 | ||
| 22 | 16× | (bool success, bytes memory encodedDecimals) = |
| 23 | 158× | address(asset_).staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); |
| 24 | 31× | if (success && encodedDecimals.length >= 32) { |
| 25 | 34× | uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); |
| 26 | 10× | if (returnedDecimals <= type(uint8).max) { |
| 27 | 8× | return (true, uint8(returnedDecimals)); |
| 28 | } |
|
| 29 | } |
|
| 30 | 4× | return (false, 0); |
| 31 | } |
|
| 32 | } |
0%
src/libraries/SuperAssetPriceLib.sol
Lines covered: 0 / 76 (0%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import { ISuperOracle } from "../interfaces/oracles/ISuperOracle.sol"; |
|
| 5 | import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol"; |
|
| 6 | import { IYieldSourceOracle } from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
|
| 7 | ||
| 8 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 9 | import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 10 | import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
|
| 11 | ||
| 12 | 0 | library SuperAssetPriceLib { |
| 13 | using Math for uint256; |
|
| 14 | ||
| 15 | /// @dev Gets the price of a token with circuit breakers |
|
| 16 | /// @param args The arguments for the price calculation |
|
| 17 | /// @return priceUSD The price of the token in USD |
|
| 18 | /// @return isDepeg Whether the token is depegged |
|
| 19 | /// @return isDispersion Whether the token has price dispersion |
|
| 20 | /// @return isOracleOff Whether the oracle is off |
|
| 21 | 0 | function getPriceWithCircuitBreakers(ISuperAsset.PriceArgs memory args) |
| 22 | external |
|
| 23 | view |
|
| 24 | 0 | returns (uint256 priceUSD, bool isDepeg, bool isDispersion, bool isOracleOff) |
| 25 | { |
|
| 26 | // Get token decimals |
|
| 27 | 0 | uint256 stddev; |
| 28 | 0 | uint256 M; |
| 29 | ||
| 30 | // @dev Passing oneUnit to get the price of a single unit of asset to check if it has depegged |
|
| 31 | 0 | ISuperOracle superOracle = ISuperOracle(args.superOracle); |
| 32 | 0 | ISuperAsset superAsset = ISuperAsset(args.superAsset); |
| 33 | ||
| 34 | 0 | uint256 precision = superAsset.getPrecision(); |
| 35 | ||
| 36 | 0 | (priceUSD, stddev, M) = _getPriceInfo(superOracle, superAsset, args.usd, args.token); |
| 37 | ||
| 38 | // Circuit Breaker for Oracle Off |
|
| 39 | 0 | if (M == 0) { |
| 40 | 0 | isOracleOff = true; |
| 41 | } else { |
|
| 42 | 0 | address primaryAsset = superAsset.getPrimaryAsset(); |
| 43 | 0 | if (primaryAsset == args.usd) { |
| 44 | 0 | return (precision, false, false, false); |
| 45 | } |
|
| 46 | ||
| 47 | // Circuit Breaker for Depeg - price deviates more than ±2% from expected |
|
| 48 | 0 | (isDepeg, isDispersion) = _getDepegAndDispersion(args, precision, priceUSD, stddev); |
| 49 | } |
|
| 50 | 0 | return (priceUSD, isDepeg, isDispersion, isOracleOff); |
| 51 | } |
|
| 52 | ||
| 53 | /// @dev Derives the price of the token from the underlying vault |
|
| 54 | /// @param token The address of the token to derive the price of |
|
| 55 | /// @return priceUSD The price of the token in USD |
|
| 56 | /// @return stddev The standard deviation of the token |
|
| 57 | /// @return M The number of quote providers |
|
| 58 | 0 | function _derivePriceFromUnderlyingVault( |
| 59 | ISuperOracle superOracle, |
|
| 60 | ISuperAsset superAsset, |
|
| 61 | address USD, |
|
| 62 | address token, |
|
| 63 | address oracle |
|
| 64 | ) |
|
| 65 | internal |
|
| 66 | view |
|
| 67 | 0 | returns (uint256 priceUSD, uint256 stddev, uint256 M) |
| 68 | 0 | { |
| 69 | 0 | address vaultAsset = IERC4626(token).asset(); |
| 70 | 0 | uint256 unitVaultAsset = 10 ** IERC20Metadata(vaultAsset).decimals(); |
| 71 | ||
| 72 | 0 | bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
| 73 | ||
| 74 | 0 | try superOracle.getQuoteFromProvider(unitVaultAsset, vaultAsset, USD, AVERAGE_PROVIDER) returns ( |
| 75 | uint256 _priceUSD, uint256 _stddev, uint256, uint256 _m |
|
| 76 | ) { |
|
| 77 | 0 | priceUSD = _priceUSD; |
| 78 | 0 | stddev = _stddev; |
| 79 | 0 | M = _m; |
| 80 | } catch { |
|
| 81 | 0 | priceUSD = superOracle.getEmergencyPrice(vaultAsset); |
| 82 | 0 | stddev = 0; |
| 83 | 0 | M = 0; |
| 84 | } |
|
| 85 | ||
| 86 | 0 | uint256 pricePerShare = IYieldSourceOracle(oracle).getPricePerShare(token); |
| 87 | 0 | if (priceUSD > 0) { |
| 88 | 0 | priceUSD = pricePerShare.mulDiv(priceUSD, superAsset.getPrecision(), Math.Rounding.Floor); |
| 89 | } |
|
| 90 | } |
|
| 91 | ||
| 92 | /// @dev Checks if the standard deviation is greater than the dispersion threshold |
|
| 93 | /// @param stddev The standard deviation |
|
| 94 | /// @param priceUSD The price in USD |
|
| 95 | /// @return isDispersion True if the standard deviation is greater than the dispersion threshold |
|
| 96 | 0 | function _isSTDDevDegged( |
| 97 | address superAsset, |
|
| 98 | uint256 stddev, |
|
| 99 | uint256 priceUSD, |
|
| 100 | uint256 dispersionThreshold |
|
| 101 | ) |
|
| 102 | internal |
|
| 103 | pure |
|
| 104 | 0 | returns (bool) |
| 105 | { |
|
| 106 | // Calculate relative standard deviation |
|
| 107 | 0 | uint256 relativeStdDev = Math.mulDiv(stddev, ISuperAsset(superAsset).getPrecision(), priceUSD); |
| 108 | ||
| 109 | // Circuit Breaker for Dispersion |
|
| 110 | 0 | if (relativeStdDev > dispersionThreshold) { |
| 111 | 0 | return true; |
| 112 | } |
|
| 113 | 0 | return false; |
| 114 | } |
|
| 115 | ||
| 116 | /// @dev Checks if the token is depegged |
|
| 117 | /// @param priceUSD The price of the token in USD |
|
| 118 | /// @param assetPriceUSD The price of the asset in USD |
|
| 119 | /// @return isDepeg True if the token is depegged |
|
| 120 | 0 | function _isTokenDepeg( |
| 121 | uint256 priceUSD, |
|
| 122 | uint256 precision, |
|
| 123 | uint256 assetPriceUSD, |
|
| 124 | uint256 depegLowerThreshold, |
|
| 125 | uint256 depegUpperThreshold |
|
| 126 | ) |
|
| 127 | internal |
|
| 128 | pure |
|
| 129 | 0 | returns (bool isDepeg) |
| 130 | 0 | { |
| 131 | // NOTE: There should be no need to adjust for decimals since |
|
| 132 | // the token specific decimals and |
|
| 133 | // the Oracle Price decimals |
|
| 134 | // can be different |
|
| 135 | // Example, if we send 2 USDC to someone then the transferred amount is 2e6 since USDC has 6d |
|
| 136 | // but the USDC price quoted in USD can have its own decimals, for example |
|
| 137 | // if USDC depegs high and is worth 3 USD then its price quoted in a 18d oracle will be 3e18 |
|
| 138 | 0 | uint256 ratio = Math.mulDiv(priceUSD, precision, assetPriceUSD); |
| 139 | ||
| 140 | 0 | if (ratio < depegLowerThreshold || ratio > depegUpperThreshold) { |
| 141 | 0 | isDepeg = true; |
| 142 | } |
|
| 143 | } |
|
| 144 | ||
| 145 | /// @dev Gets the price information for a token |
|
| 146 | /// @param superOracle The super oracle |
|
| 147 | /// @param superAsset The super asset |
|
| 148 | /// @param USD The USD address |
|
| 149 | /// @param token The token address |
|
| 150 | /// @return priceUSD The price of the token in USD |
|
| 151 | /// @return stddev The standard deviation of the token |
|
| 152 | 0 | function _getPriceInfo( |
| 153 | ISuperOracle superOracle, |
|
| 154 | ISuperAsset superAsset, |
|
| 155 | address USD, |
|
| 156 | address token |
|
| 157 | ) |
|
| 158 | internal |
|
| 159 | view |
|
| 160 | 0 | returns (uint256 priceUSD, uint256 stddev, uint256 M) |
| 161 | 0 | { |
| 162 | 0 | bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
| 163 | 0 | uint256 one = 10 ** IERC20Metadata(token).decimals(); |
| 164 | ||
| 165 | 0 | ISuperAsset.TokenData memory tokenData = superAsset.getTokenData(token); |
| 166 | ||
| 167 | 0 | if (tokenData.isSupportedERC20) { |
| 168 | 0 | try superOracle.getQuoteFromProvider(one, token, USD, AVERAGE_PROVIDER) returns ( |
| 169 | uint256 _priceUSD, uint256 _stddev, uint256, uint256 _m |
|
| 170 | ) { |
|
| 171 | 0 | priceUSD = _priceUSD; |
| 172 | 0 | stddev = _stddev; |
| 173 | 0 | M = _m; |
| 174 | } catch { |
|
| 175 | 0 | priceUSD = superOracle.getEmergencyPrice(token); |
| 176 | 0 | M = 0; |
| 177 | } |
|
| 178 | 0 | } else if (tokenData.isSupportedUnderlyingVault) { |
| 179 | 0 | (priceUSD, stddev, M) = |
| 180 | 0 | _derivePriceFromUnderlyingVault(superOracle, superAsset, USD, token, tokenData.oracle); |
| 181 | } |
|
| 182 | } |
|
| 183 | ||
| 184 | /// @dev Gets the depeg and dispersion status of a token |
|
| 185 | /// @param args The arguments for the price calculation |
|
| 186 | /// @param precision The precision of the token |
|
| 187 | /// @param priceUSD The price of the token in USD |
|
| 188 | /// @param stddev The standard deviation of the token |
|
| 189 | /// @return isDepeg True if the token is depegged |
|
| 190 | /// @return isDispersion True if the token has price dispersion |
|
| 191 | 0 | function _getDepegAndDispersion( |
| 192 | ISuperAsset.PriceArgs memory args, |
|
| 193 | uint256 precision, |
|
| 194 | uint256 priceUSD, |
|
| 195 | uint256 stddev |
|
| 196 | ) |
|
| 197 | internal |
|
| 198 | view |
|
| 199 | 0 | returns (bool isDepeg, bool isDispersion) |
| 200 | 0 | { |
| 201 | 0 | uint256 assetPriceUSD = _getAssetPriceUSD(args.superOracle, args.superAsset, args.usd); |
| 202 | ||
| 203 | 0 | isDepeg = _isTokenDepeg(priceUSD, precision, assetPriceUSD, args.depegLowerThreshold, args.depegUpperThreshold); |
| 204 | ||
| 205 | 0 | isDispersion = _isSTDDevDegged(args.superAsset, stddev, priceUSD, args.dispersionThreshold); |
| 206 | } |
|
| 207 | ||
| 208 | /// @dev Gets the price of the asset in USD |
|
| 209 | /// @param superOracleAddress The address of the super oracle |
|
| 210 | /// @param superAssetAddress The address of the super asset |
|
| 211 | /// @param USD The address of the USD token |
|
| 212 | /// @return assetPriceUSD The price of the asset in USD |
|
| 213 | 0 | function _getAssetPriceUSD( |
| 214 | address superOracleAddress, |
|
| 215 | address superAssetAddress, |
|
| 216 | address USD |
|
| 217 | ) |
|
| 218 | internal |
|
| 219 | view |
|
| 220 | 0 | returns (uint256 assetPriceUSD) |
| 221 | 0 | { |
| 222 | 0 | bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
| 223 | 0 | address primaryAsset = ISuperAsset(superAssetAddress).getPrimaryAsset(); |
| 224 | 0 | uint256 oneUnitAsset = 10 ** IERC20Metadata(primaryAsset).decimals(); |
| 225 | ||
| 226 | 0 | ISuperOracle superOracle = ISuperOracle(superOracleAddress); |
| 227 | ||
| 228 | 0 | try superOracle.getQuoteFromProvider(oneUnitAsset, primaryAsset, USD, AVERAGE_PROVIDER) returns ( |
| 229 | uint256 _priceUSD, uint256, uint256, uint256 |
|
| 230 | ) { |
|
| 231 | 0 | assetPriceUSD = _priceUSD; |
| 232 | } catch { |
|
| 233 | 0 | assetPriceUSD = superOracle.getEmergencyPrice(primaryAsset); |
| 234 | } |
|
| 235 | } |
|
| 236 | } |
0%
src/oracles/ECDSAPPSOracle.sol
Lines covered: 0 / 127 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // External |
|
| 5 | import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; |
|
| 6 | import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; |
|
| 7 | import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; |
|
| 8 | ||
| 9 | // Superform |
|
| 10 | import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol"; |
|
| 11 | import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 12 | import { IECDSAPPSOracle } from "../interfaces/oracles/IECDSAPPSOracle.sol"; |
|
| 13 | ||
| 14 | /// @title ECDSAPPSOracle |
|
| 15 | /// @author Superform Labs |
|
| 16 | /// @notice PPS Oracle that validates price updates using ECDSA signatures |
|
| 17 | /// @dev Implements the IECDSAPPSOracle interface for validating and forwarding PPS updates |
|
| 18 | 0 | contract ECDSAPPSOracle is IECDSAPPSOracle, EIP712 { |
| 19 | using ECDSA for bytes32; |
|
| 20 | using MessageHashUtils for bytes32; |
|
| 21 | ||
| 22 | /*////////////////////////////////////////////////////////////// |
|
| 23 | STORAGE |
|
| 24 | //////////////////////////////////////////////////////////////*/ |
|
| 25 | 0 | mapping(address _strategy => uint256 _nonce) public noncePerStrategy; |
| 26 | ||
| 27 | /// @notice The SuperGovernor contract for validator verification |
|
| 28 | 0 | ISuperGovernor public immutable SUPER_GOVERNOR; |
| 29 | 0 | bytes32 public constant UPDATE_PPS_TYPEHASH = keccak256( |
| 30 | "UpdatePPS(address strategy,uint256 pps,uint256 ppsStdev,uint256 validatorSet,uint256 totalValidators,uint256 timestamp, uint256 strategyNonce)" |
|
| 31 | ); |
|
| 32 | ||
| 33 | 0 | bytes32 private constant SUPER_VAULT_AGGREGATOR = keccak256("SUPER_VAULT_AGGREGATOR"); |
| 34 | ||
| 35 | /*////////////////////////////////////////////////////////////// |
|
| 36 | CONSTRUCTOR |
|
| 37 | //////////////////////////////////////////////////////////////*/ |
|
| 38 | /// @notice Initializes the ECDSAPPSOracle contract |
|
| 39 | /// @param superGovernor_ Address of the SuperGovernor contract |
|
| 40 | 0 | constructor(address superGovernor_, string memory name_, string memory version_) EIP712(name_, version_) { |
| 41 | 0 | if (superGovernor_ == address(0)) revert INVALID_VALIDATOR(); |
| 42 | ||
| 43 | 0 | SUPER_GOVERNOR = ISuperGovernor(superGovernor_); |
| 44 | } |
|
| 45 | ||
| 46 | /*////////////////////////////////////////////////////////////// |
|
| 47 | VIEW FUNCTIONS |
|
| 48 | //////////////////////////////////////////////////////////////*/ |
|
| 49 | /// @inheritdoc IECDSAPPSOracle |
|
| 50 | 0 | function domainSeparator() external view returns (bytes32) { |
| 51 | 0 | return _domainSeparatorV4(); |
| 52 | } |
|
| 53 | ||
| 54 | /*////////////////////////////////////////////////////////////// |
|
| 55 | PPS UPDATE FUNCTIONS |
|
| 56 | //////////////////////////////////////////////////////////////*/ |
|
| 57 | /// @inheritdoc IECDSAPPSOracle |
|
| 58 | 0 | function updatePPS(UpdatePPSArgs calldata args) external { |
| 59 | 0 | uint256 strategiesLength = args.strategies.length; |
| 60 | ||
| 61 | 0 | if (strategiesLength == 0) revert ZERO_LENGTH_ARRAY(); |
| 62 | // Validate input array lengths |
|
| 63 | 0 | if ( strategiesLength != args.proofsArray.length |
| 64 | 0 | || strategiesLength != args.ppss.length |
| 65 | 0 | || strategiesLength != args.ppsStdevs.length || strategiesLength != args.validatorSets.length |
| 66 | 0 | || strategiesLength != args.timestamps.length || strategiesLength != args.totalValidators.length |
| 67 | 0 | ) revert ARRAY_LENGTH_MISMATCH(); |
| 68 | ||
| 69 | ||
| 70 | // Process strategies and collect valid entries |
|
| 71 | 0 | ( |
| 72 | 0 | address[] memory validStrategies, |
| 73 | 0 | uint256[] memory validPpss, |
| 74 | 0 | uint256[] memory validPpsStdevs, |
| 75 | 0 | uint256[] memory validValidatorSets, |
| 76 | 0 | uint256[] memory validTotalValidators, |
| 77 | 0 | uint256[] memory validTimestamps |
| 78 | 0 | ) = _processBatchStrategies(args, strategiesLength); |
| 79 | ||
| 80 | // Forward valid entries if any exist |
|
| 81 | 0 | _forwardValidEntries( |
| 82 | 0 | validStrategies, |
| 83 | 0 | validPpss, |
| 84 | 0 | validPpsStdevs, |
| 85 | 0 | validValidatorSets, |
| 86 | 0 | validTotalValidators, |
| 87 | 0 | validTimestamps |
| 88 | ); |
|
| 89 | } |
|
| 90 | ||
| 91 | ||
| 92 | ||
| 93 | /// @notice Validates an array of proofs for a strategy's PPS update |
|
| 94 | /// @param params Validation parameters |
|
| 95 | /// @dev Reverts immediately if duplicate signers are found or quorum is not met |
|
| 96 | 0 | function validateProofs(IECDSAPPSOracle.ValidationParams memory params) |
| 97 | public |
|
| 98 | view |
|
| 99 | { |
|
| 100 | 0 | _validateProofs(params); |
| 101 | } |
|
| 102 | ||
| 103 | /*////////////////////////////////////////////////////////////// |
|
| 104 | INTERNAL FUNCTIONS |
|
| 105 | //////////////////////////////////////////////////////////////*/ |
|
| 106 | /// @notice Validates an array of proofs for a strategy's PPS update |
|
| 107 | /// @param params Validation parameters |
|
| 108 | /// @dev Reverts immediately if duplicate signers are found or quorum is not met |
|
| 109 | 0 | function _validateProofs(IECDSAPPSOracle.ValidationParams memory params) internal view { |
| 110 | // Check if this oracle is the active PPS Oracle |
|
| 111 | 0 | if (!SUPER_GOVERNOR.isActivePPSOracle(address(this))) revert NOT_ACTIVE_PPS_ORACLE(); |
| 112 | ||
| 113 | // Create message hash with all parameters- If anyare incorrect, the message hash will be different and the |
|
| 114 | // derived signer address will be incorrect- resulting in a revert |
|
| 115 | 0 | bytes32 structHash = keccak256( |
| 116 | 0 | abi.encodePacked( |
| 117 | UPDATE_PPS_TYPEHASH, |
|
| 118 | 0 | params.strategy, |
| 119 | 0 | params.pps, |
| 120 | 0 | params.ppsStdev, |
| 121 | 0 | params.validatorSet, |
| 122 | 0 | params.totalValidators, |
| 123 | 0 | params.timestamp, |
| 124 | 0 | noncePerStrategy[params.strategy] |
| 125 | ) |
|
| 126 | ); |
|
| 127 | 0 | bytes32 digest = _hashTypedDataV4(structHash); |
| 128 | ||
| 129 | 0 | uint256 proofsLength = params.proofs.length; |
| 130 | 0 | if (proofsLength == 0) revert ZERO_LENGTH_ARRAY(); |
| 131 | ||
| 132 | 0 | address lastSigner; |
| 133 | // Process each proof |
|
| 134 | 0 | for (uint256 i; i < proofsLength; i++) { |
| 135 | // Recover the signer from the proof |
|
| 136 | 0 | address signer = ECDSA.recover(digest, params.proofs[i]); |
| 137 | ||
| 138 | // Verify the signer is a registered validator |
|
| 139 | 0 | if (!SUPER_GOVERNOR.isValidator(signer)) revert INVALID_VALIDATOR(); |
| 140 | ||
| 141 | // Check for duplicates or improper ordering - signers must be in ascending order |
|
| 142 | 0 | if (signer <= lastSigner) revert INVALID_PROOF(); |
| 143 | 0 | lastSigner = signer; |
| 144 | } |
|
| 145 | ||
| 146 | // Validate that validatorSet matches actual number of valid signatures |
|
| 147 | 0 | if (params.validatorSet != proofsLength) revert INVALID_VALIDATOR_SET(); |
| 148 | ||
| 149 | // Validate that totalValidators matches actual total number of validators |
|
| 150 | 0 | if (params.totalValidators != SUPER_GOVERNOR.getValidators().length) revert INVALID_TOTAL_VALIDATORS(); |
| 151 | ||
| 152 | // Ensure we have enough valid signatures to meet quorum |
|
| 153 | 0 | if (proofsLength < SUPER_GOVERNOR.getPPSOracleQuorum()) revert QUORUM_NOT_MET(); |
| 154 | } |
|
| 155 | ||
| 156 | /// @notice Processes batch strategies and returns valid entries |
|
| 157 | /// @param args Batch update arguments |
|
| 158 | /// @param strategiesLength Length of strategies array |
|
| 159 | /// @return validStrategies Array of valid strategy addresses |
|
| 160 | /// @return validPpss Array of valid PPS values |
|
| 161 | /// @return validPpsStdevs Array of valid PPS standard deviations |
|
| 162 | /// @return validValidatorSets Array of valid validator sets |
|
| 163 | /// @return validTotalValidators Array of valid total validators |
|
| 164 | /// @return validTimestamps Array of valid timestamps |
|
| 165 | 0 | function _processBatchStrategies( |
| 166 | UpdatePPSArgs calldata args, |
|
| 167 | uint256 strategiesLength |
|
| 168 | ) |
|
| 169 | internal |
|
| 170 | returns ( |
|
| 171 | 0 | address[] memory validStrategies, |
| 172 | 0 | uint256[] memory validPpss, |
| 173 | 0 | uint256[] memory validPpsStdevs, |
| 174 | 0 | uint256[] memory validValidatorSets, |
| 175 | 0 | uint256[] memory validTotalValidators, |
| 176 | 0 | uint256[] memory validTimestamps |
| 177 | ) |
|
| 178 | { |
|
| 179 | // Arrays to collect valid entries |
|
| 180 | 0 | validStrategies = new address[](strategiesLength); |
| 181 | 0 | validPpss = new uint256[](strategiesLength); |
| 182 | 0 | validPpsStdevs = new uint256[](strategiesLength); |
| 183 | 0 | validValidatorSets = new uint256[](strategiesLength); |
| 184 | 0 | validTotalValidators = new uint256[](strategiesLength); |
| 185 | 0 | validTimestamps = new uint256[](strategiesLength); |
| 186 | 0 | uint256 validCount; |
| 187 | ||
| 188 | // Process each strategy update |
|
| 189 | 0 | for (uint256 i; i < strategiesLength; i++) { |
| 190 | 0 | bool isValid = _processIndividualStrategy(args, i); |
| 191 | 0 | if (isValid) { |
| 192 | // Add to valid entries |
|
| 193 | 0 | validStrategies[validCount] = args.strategies[i]; |
| 194 | 0 | validPpss[validCount] = args.ppss[i]; |
| 195 | 0 | validPpsStdevs[validCount] = args.ppsStdevs[i]; |
| 196 | 0 | validValidatorSets[validCount] = args.validatorSets[i]; |
| 197 | 0 | validTotalValidators[validCount] = args.totalValidators[i]; |
| 198 | 0 | validTimestamps[validCount] = args.timestamps[i]; |
| 199 | 0 | validCount++; |
| 200 | } |
|
| 201 | } |
|
| 202 | ||
| 203 | // Resize arrays to actual valid count |
|
| 204 | 0 | assembly { |
| 205 | 0 | mstore(validStrategies, validCount) |
| 206 | 0 | mstore(validPpss, validCount) |
| 207 | 0 | mstore(validPpsStdevs, validCount) |
| 208 | 0 | mstore(validValidatorSets, validCount) |
| 209 | 0 | mstore(validTotalValidators, validCount) |
| 210 | 0 | mstore(validTimestamps, validCount) |
| 211 | } |
|
| 212 | } |
|
| 213 | ||
| 214 | /// @notice Processes an individual strategy in the batch |
|
| 215 | /// @param args Batch update arguments |
|
| 216 | /// @param index Index of the strategy to process |
|
| 217 | /// @return isValid True if the strategy was processed successfully |
|
| 218 | 0 | function _processIndividualStrategy( |
| 219 | UpdatePPSArgs calldata args, |
|
| 220 | uint256 index |
|
| 221 | 0 | ) internal returns (bool isValid) { |
| 222 | 0 | address _strategy = args.strategies[index]; |
| 223 | ||
| 224 | // Validate proofs and check quorum requirement |
|
| 225 | 0 | try IECDSAPPSOracle(address(this)).validateProofs( |
| 226 | 0 | IECDSAPPSOracle.ValidationParams({ |
| 227 | 0 | strategy: _strategy, |
| 228 | 0 | proofs: args.proofsArray[index], |
| 229 | 0 | pps: args.ppss[index], |
| 230 | 0 | ppsStdev: args.ppsStdevs[index], |
| 231 | 0 | validatorSet: args.validatorSets[index], |
| 232 | 0 | totalValidators: args.totalValidators[index], |
| 233 | 0 | timestamp: args.timestamps[index] |
| 234 | }) |
|
| 235 | ) { |
|
| 236 | 0 | emit PPSValidated( |
| 237 | _strategy, |
|
| 238 | 0 | args.ppss[index], |
| 239 | 0 | args.ppsStdevs[index], |
| 240 | 0 | args.validatorSets[index], |
| 241 | 0 | args.totalValidators[index], |
| 242 | 0 | args.timestamps[index], |
| 243 | 0 | msg.sender |
| 244 | ); |
|
| 245 | } catch Error(string memory reason) { |
|
| 246 | 0 | emit ProofValidationFailed(_strategy, reason); |
| 247 | 0 | return false; |
| 248 | } catch (bytes memory lowLevelData) { |
|
| 249 | 0 | emit ProofValidationFailedLowLevel(_strategy, lowLevelData); |
| 250 | return false; |
|
| 251 | } |
|
| 252 | ||
| 253 | 0 | noncePerStrategy[_strategy]++; |
| 254 | 0 | return true; |
| 255 | } |
|
| 256 | ||
| 257 | /// @notice Forwards valid entries to SuperVaultAggregator |
|
| 258 | /// @param validStrategies Array of valid strategy addresses |
|
| 259 | /// @param validPpss Array of valid PPS values |
|
| 260 | /// @param validPpsStdevs Array of valid PPS standard deviations |
|
| 261 | /// @param validValidatorSets Array of valid validator sets |
|
| 262 | /// @param validTotalValidators Array of valid total validators |
|
| 263 | /// @param validTimestamps Array of valid timestamps |
|
| 264 | 0 | function _forwardValidEntries( |
| 265 | address[] memory validStrategies, |
|
| 266 | uint256[] memory validPpss, |
|
| 267 | uint256[] memory validPpsStdevs, |
|
| 268 | uint256[] memory validValidatorSets, |
|
| 269 | uint256[] memory validTotalValidators, |
|
| 270 | uint256[] memory validTimestamps |
|
| 271 | ) internal { |
|
| 272 | // Only forward if there are valid entries |
|
| 273 | 0 | if (validStrategies.length > 0) { |
| 274 | 0 | try ISuperVaultAggregator(SUPER_GOVERNOR.getAddress(SUPER_VAULT_AGGREGATOR)).forwardPPS( |
| 275 | 0 | ISuperVaultAggregator.ForwardPPSArgs({ |
| 276 | 0 | strategies: validStrategies, |
| 277 | 0 | ppss: validPpss, |
| 278 | 0 | ppsStdevs: validPpsStdevs, |
| 279 | 0 | validatorSets: validValidatorSets, |
| 280 | 0 | totalValidators: validTotalValidators, |
| 281 | 0 | timestamps: validTimestamps, |
| 282 | 0 | updateAuthority: msg.sender |
| 283 | }) |
|
| 284 | ) { |
|
| 285 | 0 | } catch Error(string memory reason) { |
| 286 | 0 | emit BatchForwardPPSFailed(reason); |
| 287 | } catch (bytes memory lowLevelData) { |
|
| 288 | 0 | emit BatchForwardPPSFailedLowLevel(lowLevelData); |
| 289 | } |
|
| 290 | } |
|
| 291 | } |
|
| 292 | ||
| 293 | } |
0%
src/oracles/SuperOracle.sol
Lines covered: 0 / 3 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // Superform |
|
| 5 | import { SuperOracleBase } from "./SuperOracleBase.sol"; |
|
| 6 | ||
| 7 | /// @title SuperOracle |
|
| 8 | /// @author Superform Labs |
|
| 9 | /// @notice Oracle for Superform |
|
| 10 | 0 | contract SuperOracle is SuperOracleBase { |
| 11 | 0 | constructor( |
| 12 | address superGovernor_, |
|
| 13 | address[] memory bases, |
|
| 14 | address[] memory quotes, |
|
| 15 | bytes32[] memory providers, |
|
| 16 | address[] memory feeds |
|
| 17 | ) |
|
| 18 | 0 | SuperOracleBase(superGovernor_, bases, quotes, providers, feeds) |
| 19 | { } |
|
| 20 | } |
0%
src/oracles/SuperOracleBase.sol
Lines covered: 0 / 210 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // external |
|
| 5 | import { IOracle } from "../vendor/awesome-oracles/IOracle.sol"; |
|
| 6 | import { AggregatorV3Interface } from "../vendor/chainlink/AggregatorV3Interface.sol"; |
|
| 7 | import { IERC20 } from "forge-std/interfaces/IERC20.sol"; |
|
| 8 | import { BoringERC20 } from "../vendor/BoringSolidity/BoringERC20.sol"; |
|
| 9 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 10 | ||
| 11 | // Superform |
|
| 12 | import { ISuperOracle } from "../interfaces/oracles/ISuperOracle.sol"; |
|
| 13 | ||
| 14 | /// @title SuperOracle |
|
| 15 | /// @author Superform Labs |
|
| 16 | /// @notice Oracle for Superform |
|
| 17 | abstract contract SuperOracleBase is ISuperOracle, IOracle { |
|
| 18 | using BoringERC20 for IERC20; |
|
| 19 | ||
| 20 | /// @notice Mapping of feed to max staleness period |
|
| 21 | 0 | mapping(address feed => uint256 maxStaleness) public feedMaxStaleness; |
| 22 | ||
| 23 | /// @notice Mapping of token to emergency price when oracle is down |
|
| 24 | 0 | mapping(address token => uint256 emergencyPrice) public emergencyPrices; |
| 25 | ||
| 26 | 0 | uint256 public maxDefaultStaleness; |
| 27 | ||
| 28 | /// @notice Pending oracle update |
|
| 29 | 0 | PendingUpdate public pendingUpdate; |
| 30 | ||
| 31 | /// @notice Pending provider removal |
|
| 32 | 0 | PendingRemoval public pendingRemoval; |
| 33 | ||
| 34 | /// @notice Mapping of base asset to array of oracle providers to oracle feed address |
|
| 35 | mapping(address base => mapping(address quote => mapping(bytes32 provider => address feed))) internal oracles; |
|
| 36 | ||
| 37 | /// @notice Array of active provider ids |
|
| 38 | 0 | bytes32[] public activeProviders; |
| 39 | 0 | mapping(bytes32 provider => bool isSet) public isProviderSet; |
| 40 | ||
| 41 | /// @notice Timelock period for oracle updates |
|
| 42 | 0 | uint256 internal constant TIMELOCK_PERIOD = 1 weeks; |
| 43 | uint256 internal constant MAX_SAMPLE_PROVIDERS = 10; |
|
| 44 | 0 | bytes32 internal constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER"); |
| 45 | ||
| 46 | // SuperGovernor address |
|
| 47 | 0 | address public immutable SUPER_GOVERNOR; |
| 48 | ||
| 49 | 0 | constructor( |
| 50 | address superGovernor_, |
|
| 51 | address[] memory bases, |
|
| 52 | address[] memory quotes, |
|
| 53 | bytes32[] memory providers, |
|
| 54 | address[] memory feeds |
|
| 55 | 0 | ) { |
| 56 | 0 | if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
| 57 | 0 | SUPER_GOVERNOR = superGovernor_; |
| 58 | 0 | maxDefaultStaleness = 1 days; |
| 59 | ||
| 60 | // validate oracle inputs |
|
| 61 | 0 | _validateOracleInputs(bases, quotes, providers, feeds); |
| 62 | ||
| 63 | // configure oracles |
|
| 64 | 0 | _configureOracles(bases, quotes, providers, feeds); |
| 65 | ||
| 66 | // set default staleness for feeds |
|
| 67 | 0 | uint256 length = feeds.length; |
| 68 | 0 | for (uint256 i; i < length; ++i) { |
| 69 | 0 | feedMaxStaleness[feeds[i]] = maxDefaultStaleness; |
| 70 | } |
|
| 71 | } |
|
| 72 | ||
| 73 | /*////////////////////////////////////////////////////////////// |
|
| 74 | EXTERNAL FUNCTIONS |
|
| 75 | //////////////////////////////////////////////////////////////*/ |
|
| 76 | /// @inheritdoc ISuperOracle |
|
| 77 | 0 | function setMaxStaleness(uint256 newMaxStaleness) external { |
| 78 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 79 | 0 | maxDefaultStaleness = newMaxStaleness; |
| 80 | 0 | emit MaxStalenessUpdated(newMaxStaleness); |
| 81 | } |
|
| 82 | ||
| 83 | /// @inheritdoc ISuperOracle |
|
| 84 | 0 | function setFeedMaxStaleness(address feed, uint256 newMaxStaleness) external { |
| 85 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 86 | 0 | _setFeedMaxStaleness(feed, newMaxStaleness); |
| 87 | } |
|
| 88 | ||
| 89 | /// @inheritdoc ISuperOracle |
|
| 90 | 0 | function setEmergencyPrice(address token_, uint256 price_) external { |
| 91 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 92 | 0 | _setEmergencyPrice(token_, price_); |
| 93 | } |
|
| 94 | ||
| 95 | /// @inheritdoc ISuperOracle |
|
| 96 | 0 | function batchSetEmergencyPrice(address[] calldata tokens_, uint256[] calldata prices_) external { |
| 97 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 98 | 0 | uint256 length = tokens_.length; |
| 99 | 0 | if (length == 0) revert ZERO_ARRAY_LENGTH(); |
| 100 | 0 | if (length != prices_.length) revert ARRAY_LENGTH_MISMATCH(); |
| 101 | ||
| 102 | 0 | for (uint256 i; i < length; ++i) { |
| 103 | 0 | _setEmergencyPrice(tokens_[i], prices_[i]); |
| 104 | } |
|
| 105 | } |
|
| 106 | ||
| 107 | /// @inheritdoc ISuperOracle |
|
| 108 | 0 | function setFeedMaxStalenessBatch(address[] calldata feeds, uint256[] calldata newMaxStalenessList) external { |
| 109 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 110 | 0 | uint256 length = feeds.length; |
| 111 | 0 | if (length == 0) revert ZERO_ARRAY_LENGTH(); |
| 112 | 0 | if (length != newMaxStalenessList.length) { |
| 113 | 0 | revert ARRAY_LENGTH_MISMATCH(); |
| 114 | } |
|
| 115 | ||
| 116 | 0 | for (uint256 i; i < length; ++i) { |
| 117 | 0 | _setFeedMaxStaleness(feeds[i], newMaxStalenessList[i]); |
| 118 | } |
|
| 119 | } |
|
| 120 | ||
| 121 | /// @inheritdoc ISuperOracle |
|
| 122 | 0 | function queueOracleUpdate( |
| 123 | address[] calldata bases, |
|
| 124 | address[] calldata quotes, |
|
| 125 | bytes32[] calldata providers, |
|
| 126 | address[] calldata feeds |
|
| 127 | ) |
|
| 128 | external |
|
| 129 | 0 | { |
| 130 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 131 | 0 | uint256 length = bases.length; |
| 132 | 0 | if (length != quotes.length || length != providers.length || length != feeds.length) { |
| 133 | 0 | revert ARRAY_LENGTH_MISMATCH(); |
| 134 | } |
|
| 135 | ||
| 136 | 0 | _validateOracleInputs(bases, quotes, providers, feeds); |
| 137 | ||
| 138 | 0 | pendingUpdate = PendingUpdate({ |
| 139 | 0 | bases: bases, |
| 140 | 0 | quotes: quotes, |
| 141 | 0 | providers: providers, |
| 142 | 0 | feeds: feeds, |
| 143 | 0 | timestamp: block.timestamp |
| 144 | }); |
|
| 145 | ||
| 146 | 0 | emit OracleUpdateQueued(bases, quotes, providers, feeds, block.timestamp); |
| 147 | } |
|
| 148 | ||
| 149 | /// @inheritdoc ISuperOracle |
|
| 150 | 0 | function executeOracleUpdate() external { |
| 151 | 0 | if (pendingUpdate.timestamp == 0) revert NO_PENDING_UPDATE(); |
| 152 | 0 | if (block.timestamp < pendingUpdate.timestamp + TIMELOCK_PERIOD) revert TIMELOCK_NOT_ELAPSED(); |
| 153 | ||
| 154 | 0 | _configureOracles(pendingUpdate.bases, pendingUpdate.quotes, pendingUpdate.providers, pendingUpdate.feeds); |
| 155 | ||
| 156 | 0 | emit OracleUpdateExecuted( |
| 157 | 0 | pendingUpdate.bases, pendingUpdate.quotes, pendingUpdate.providers, pendingUpdate.feeds |
| 158 | ); |
|
| 159 | ||
| 160 | 0 | delete pendingUpdate; |
| 161 | } |
|
| 162 | ||
| 163 | /// @inheritdoc ISuperOracle |
|
| 164 | 0 | function getOracleAddress(address base, address quote, bytes32 provider) external view returns (address oracle) { |
| 165 | 0 | oracle = oracles[base][quote][provider]; |
| 166 | 0 | if (oracle == address(0)) revert NO_ORACLES_CONFIGURED(); |
| 167 | 0 | if (!isProviderSet[provider]) return address(0); |
| 168 | } |
|
| 169 | ||
| 170 | /// @inheritdoc ISuperOracle |
|
| 171 | 0 | function queueProviderRemoval(bytes32[] calldata providers) external { |
| 172 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 173 | 0 | if (pendingRemoval.timestamp != 0) revert PENDING_UPDATE_EXISTS(); |
| 174 | ||
| 175 | 0 | uint256 length = providers.length; |
| 176 | 0 | if (length == 0) revert ZERO_ARRAY_LENGTH(); |
| 177 | ||
| 178 | 0 | pendingRemoval = PendingRemoval({ providers: providers, timestamp: block.timestamp }); |
| 179 | ||
| 180 | 0 | emit ProviderRemovalQueued(providers, block.timestamp); |
| 181 | } |
|
| 182 | ||
| 183 | /// @inheritdoc ISuperOracle |
|
| 184 | 0 | function executeProviderRemoval() external { |
| 185 | 0 | if (pendingRemoval.timestamp == 0) revert NO_PENDING_UPDATE(); |
| 186 | 0 | if (block.timestamp < pendingRemoval.timestamp + TIMELOCK_PERIOD) revert TIMELOCK_NOT_ELAPSED(); |
| 187 | ||
| 188 | 0 | bytes32[] memory providersToRemove = pendingRemoval.providers; |
| 189 | ||
| 190 | // Loop through each provider to remove |
|
| 191 | 0 | for (uint256 i; i < providersToRemove.length; i++) { |
| 192 | 0 | bytes32 providerToRemove = providersToRemove[i]; |
| 193 | 0 | isProviderSet[providerToRemove] = false; |
| 194 | ||
| 195 | // Find the provider in activeProviders array |
|
| 196 | 0 | for (uint256 j; j < activeProviders.length; j++) { |
| 197 | 0 | if (activeProviders[j] == providerToRemove) { |
| 198 | // Replace the provider to remove with the last provider in the array |
|
| 199 | 0 | if (j < activeProviders.length - 1) { |
| 200 | 0 | activeProviders[j] = activeProviders[activeProviders.length - 1]; |
| 201 | } |
|
| 202 | ||
| 203 | // Remove the last element |
|
| 204 | 0 | activeProviders.pop(); |
| 205 | 0 | break; |
| 206 | } |
|
| 207 | } |
|
| 208 | } |
|
| 209 | ||
| 210 | 0 | emit ProviderRemovalExecuted(providersToRemove); |
| 211 | ||
| 212 | 0 | delete pendingRemoval; |
| 213 | } |
|
| 214 | ||
| 215 | /// @inheritdoc ISuperOracle |
|
| 216 | 0 | function getActiveProviders() external view returns (bytes32[] memory) { |
| 217 | 0 | return activeProviders; |
| 218 | } |
|
| 219 | ||
| 220 | /*////////////////////////////////////////////////////////////// |
|
| 221 | EXTERNAL VIEW FUNCTIONS |
|
| 222 | //////////////////////////////////////////////////////////////*/ |
|
| 223 | /// @inheritdoc ISuperOracle |
|
| 224 | 0 | function getQuoteFromProvider( |
| 225 | uint256 baseAmount, |
|
| 226 | address base, |
|
| 227 | address quote, |
|
| 228 | bytes32 oracleProvider |
|
| 229 | ) |
|
| 230 | public |
|
| 231 | view |
|
| 232 | virtual |
|
| 233 | 0 | returns (uint256 quoteAmount, uint256 deviation, uint256 totalProviders, uint256 availableProviders) |
| 234 | { |
|
| 235 | // If average, calculate average of all oracles |
|
| 236 | 0 | if (oracleProvider == AVERAGE_PROVIDER) { |
| 237 | 0 | uint256 length = activeProviders.length; |
| 238 | 0 | uint256[] memory validQuotes = new uint256[](length); |
| 239 | 0 | uint256 count; |
| 240 | 0 | (quoteAmount, validQuotes, totalProviders, count) = _getAverageQuote(base, quote, baseAmount, length); |
| 241 | availableProviders = count; |
|
| 242 | 0 | deviation = _calculateStdDev(validQuotes, count); |
| 243 | } else { |
|
| 244 | 0 | quoteAmount = _getQuoteFromOracle(oracles[base][quote][oracleProvider], baseAmount, base, quote, true); |
| 245 | 0 | deviation = 0; |
| 246 | 0 | totalProviders = 1; |
| 247 | 0 | availableProviders = 1; |
| 248 | } |
|
| 249 | } |
|
| 250 | ||
| 251 | /// @inheritdoc ISuperOracle |
|
| 252 | 0 | function getEmergencyPrice(address token) external view returns (uint256) { |
| 253 | 0 | return emergencyPrices[token]; |
| 254 | } |
|
| 255 | ||
| 256 | /// @inheritdoc IOracle |
|
| 257 | 0 | function getQuote( |
| 258 | uint256 baseAmount, |
|
| 259 | address base, |
|
| 260 | address quote |
|
| 261 | ) |
|
| 262 | external |
|
| 263 | view |
|
| 264 | virtual |
|
| 265 | 0 | returns (uint256 quoteAmount) |
| 266 | { |
|
| 267 | // using IOracle interface we always assume average provider |
|
| 268 | 0 | (quoteAmount,,,) = getQuoteFromProvider(baseAmount, base, quote, AVERAGE_PROVIDER); |
| 269 | } |
|
| 270 | ||
| 271 | /*////////////////////////////////////////////////////////////// |
|
| 272 | INTERNAL FUNCTIONS |
|
| 273 | //////////////////////////////////////////////////////////////*/ |
|
| 274 | 0 | function _validateOracleInputs( |
| 275 | address[] memory bases, |
|
| 276 | address[] memory quotes, |
|
| 277 | bytes32[] memory providers, |
|
| 278 | address[] memory feeds |
|
| 279 | ) |
|
| 280 | internal |
|
| 281 | pure |
|
| 282 | 0 | { |
| 283 | 0 | uint256 length = bases.length; |
| 284 | 0 | for (uint256 i; i < length; ++i) { |
| 285 | 0 | address base = bases[i]; |
| 286 | 0 | address quote = quotes[i]; |
| 287 | 0 | bytes32 provider = providers[i]; |
| 288 | 0 | address feed = feeds[i]; |
| 289 | ||
| 290 | 0 | if (provider == bytes32(0)) revert ZERO_PROVIDER(); |
| 291 | 0 | if (provider == AVERAGE_PROVIDER) revert AVERAGE_PROVIDER_NOT_ALLOWED(); |
| 292 | 0 | if (base == address(0) || quote == address(0) || feed == address(0)) revert ZERO_ADDRESS(); |
| 293 | } |
|
| 294 | } |
|
| 295 | ||
| 296 | 0 | function _setFeedMaxStaleness(address feed, uint256 newMaxStaleness) internal { |
| 297 | 0 | if (newMaxStaleness > maxDefaultStaleness) { |
| 298 | 0 | revert MAX_STALENESS_EXCEEDED(); |
| 299 | } |
|
| 300 | 0 | if (newMaxStaleness == 0) { |
| 301 | 0 | newMaxStaleness = maxDefaultStaleness; |
| 302 | } |
|
| 303 | 0 | feedMaxStaleness[feed] = newMaxStaleness; |
| 304 | 0 | emit FeedMaxStalenessUpdated(feed, newMaxStaleness); |
| 305 | } |
|
| 306 | ||
| 307 | 0 | function _getQuoteFromOracle( |
| 308 | address oracle, |
|
| 309 | uint256 baseAmount, |
|
| 310 | address base, |
|
| 311 | address quote, |
|
| 312 | bool revertOnError |
|
| 313 | ) |
|
| 314 | internal |
|
| 315 | view |
|
| 316 | virtual |
|
| 317 | 0 | returns (uint256 quoteAmount) |
| 318 | 0 | { |
| 319 | 0 | int256 answer; |
| 320 | 0 | uint256 updatedAt; |
| 321 | ||
| 322 | // --- Get round data --- |
|
| 323 | 0 | try AggregatorV3Interface(oracle).latestRoundData() returns ( |
| 324 | uint80, int256 _answer, uint256, uint256 _updatedAt, uint80 |
|
| 325 | ) { |
|
| 326 | 0 | answer = _answer; |
| 327 | 0 | updatedAt = _updatedAt; |
| 328 | } catch { |
|
| 329 | 0 | if (revertOnError) revert ORACLE_ROUND_DATA_CALL_FAIL(oracle); |
| 330 | 0 | return 0; |
| 331 | } |
|
| 332 | ||
| 333 | // --- Validate data --- |
|
| 334 | 0 | if (answer <= 0 || block.timestamp - updatedAt > feedMaxStaleness[oracle]) { |
| 335 | 0 | if (revertOnError) revert ORACLE_UNTRUSTED_DATA(); |
| 336 | return 0; |
|
| 337 | } |
|
| 338 | ||
| 339 | // --- Get decimals and compute scaled amount --- |
|
| 340 | 0 | try AggregatorV3Interface(oracle).decimals() returns (uint8 feedDecimals) { |
| 341 | 0 | uint8 baseDecimals = IERC20(base).safeDecimals(); |
| 342 | 0 | uint8 quoteDecimals = IERC20(quote).safeDecimals(); |
| 343 | ||
| 344 | // Calculate quote amount with proper decimal scaling |
|
| 345 | 0 | quoteAmount = Math.mulDiv(baseAmount, uint256(answer), 10 ** feedDecimals); |
| 346 | 0 | quoteAmount = Math.mulDiv(quoteAmount, 10 ** quoteDecimals, 10 ** baseDecimals); |
| 347 | } catch { |
|
| 348 | 0 | if (revertOnError) revert ORACLE_DECIMALS_CALL_FAIL(oracle); |
| 349 | return 0; |
|
| 350 | } |
|
| 351 | } |
|
| 352 | ||
| 353 | ||
| 354 | 0 | function _getAverageQuote( |
| 355 | address base, |
|
| 356 | address quote, |
|
| 357 | uint256 baseAmount, |
|
| 358 | uint256 numberOfProviders |
|
| 359 | ) |
|
| 360 | internal |
|
| 361 | view |
|
| 362 | virtual |
|
| 363 | 0 | returns (uint256 quoteAmount, uint256[] memory validQuotes, uint256 totalCount, uint256 count) |
| 364 | 0 | { |
| 365 | 0 | uint256 total; |
| 366 | 0 | validQuotes = new uint256[](numberOfProviders); |
| 367 | ||
| 368 | // Loop through all active providers |
|
| 369 | // Iterates only until MAX_SAMPLE_PROVIDERS valid quotes are collected to bound gas usage |
|
| 370 | 0 | for (uint256 i; i < numberOfProviders; ++i) { |
| 371 | 0 | bytes32 provider = activeProviders[i]; |
| 372 | 0 | address providerOracle = oracles[base][quote][provider]; |
| 373 | // provider is in active list but has no available oracle address |
|
| 374 | /* |
|
| 375 | base = ETH |
|
| 376 | quote = [USD, EUR] |
|
| 377 | providers = [CHAINLINK, eORACLE] |
|
| 378 | ||
| 379 | Let's say we have |
|
| 380 | ||
| 381 | ETH -> USD -> CHAINLINK -> address(0x1) |
|
| 382 | ETH -> USD - eORACLE -> address(0x2) |
|
| 383 | ETH -> EUR -> CHAINLINK -> address(0x3) |
|
| 384 | ||
| 385 | This would just continue for |
|
| 386 | ||
| 387 | ETH -> EUR -> eOracle, because oracle address is 0 |
|
| 388 | */ |
|
| 389 | 0 | if (providerOracle == address(0)) continue; |
| 390 | ||
| 391 | // We have one more registered oracle for this base asset |
|
| 392 | unchecked { |
|
| 393 | 0 | ++totalCount; |
| 394 | } |
|
| 395 | ||
| 396 | 0 | uint256 quote_ = _getQuoteFromOracle(providerOracle, baseAmount, base, quote, false); |
| 397 | ||
| 398 | /// @dev we don't revert on error, we just skip the oracle value |
|
| 399 | 0 | if (quote_ > 0) { |
| 400 | 0 | total += quote_; |
| 401 | 0 | validQuotes[count] = quote_; |
| 402 | // This oracle is available |
|
| 403 | unchecked { |
|
| 404 | 0 | ++count; |
| 405 | } |
|
| 406 | 0 | if (count == MAX_SAMPLE_PROVIDERS) { |
| 407 | 0 | break; |
| 408 | } |
|
| 409 | } |
|
| 410 | } |
|
| 411 | 0 | if (count == 0) revert NO_VALID_REPORTED_PRICES(); |
| 412 | ||
| 413 | 0 | quoteAmount = total / count; |
| 414 | } |
|
| 415 | ||
| 416 | 0 | function _getOracleDecimals(AggregatorV3Interface oracle_) internal view virtual returns (uint8) { |
| 417 | 0 | return oracle_.decimals(); |
| 418 | } |
|
| 419 | ||
| 420 | 0 | function _calculateStdDev(uint256[] memory values, uint256 length) internal pure virtual returns (uint256 stddev) { |
| 421 | uint256 sum = 0; |
|
| 422 | uint256 count = 0; |
|
| 423 | 0 | for (uint256 i; i < length; ++i) { |
| 424 | 0 | sum += values[i]; |
| 425 | 0 | count++; |
| 426 | } |
|
| 427 | 0 | if (count < 2) return 0; |
| 428 | ||
| 429 | 0 | uint256 mean = sum / count; |
| 430 | 0 | uint256 sumSquaredDiff = 0; |
| 431 | 0 | for (uint256 i; i < length; ++i) { |
| 432 | 0 | uint256 diff; |
| 433 | 0 | if (values[i] >= mean) { |
| 434 | 0 | diff = values[i] - mean; |
| 435 | } else { |
|
| 436 | 0 | diff = mean - values[i]; |
| 437 | } |
|
| 438 | ||
| 439 | 0 | uint256 squaredDiff = Math.mulDiv(diff, diff, 1); |
| 440 | 0 | sumSquaredDiff += squaredDiff; |
| 441 | } |
|
| 442 | ||
| 443 | 0 | uint256 variance = sumSquaredDiff / count; |
| 444 | 0 | return _sqrt(variance); |
| 445 | } |
|
| 446 | ||
| 447 | 0 | function _sqrt(uint256 x) internal pure returns (uint256 y) { |
| 448 | 0 | if (x == 0) return 0; |
| 449 | ||
| 450 | 0 | uint256 z = (x + 1) / 2; |
| 451 | 0 | y = x; |
| 452 | ||
| 453 | 0 | while (z < y) { |
| 454 | 0 | y = z; |
| 455 | 0 | z = (x / z + z) / 2; |
| 456 | } |
|
| 457 | } |
|
| 458 | ||
| 459 | 0 | function _configureOracles( |
| 460 | address[] memory bases, |
|
| 461 | address[] memory quotes, |
|
| 462 | bytes32[] memory providers, |
|
| 463 | address[] memory feeds |
|
| 464 | ) |
|
| 465 | internal |
|
| 466 | { |
|
| 467 | 0 | uint256 length = bases.length; |
| 468 | ||
| 469 | 0 | for (uint256 i; i < length; ++i) { |
| 470 | 0 | address base = bases[i]; |
| 471 | 0 | address quote = quotes[i]; |
| 472 | 0 | bytes32 provider = providers[i]; |
| 473 | 0 | address feed = feeds[i]; |
| 474 | ||
| 475 | 0 | oracles[base][quote][provider] = feed; |
| 476 | ||
| 477 | // Update activeProviders array - add provider if not already present |
|
| 478 | bool providerExists = false; |
|
| 479 | 0 | uint256 activeProvidersLength = activeProviders.length; |
| 480 | 0 | for (uint256 j; j < activeProvidersLength; ++j) { |
| 481 | 0 | if (activeProviders[j] == provider) { |
| 482 | 0 | providerExists = true; |
| 483 | 0 | break; |
| 484 | } |
|
| 485 | } |
|
| 486 | ||
| 487 | 0 | if (!providerExists) { |
| 488 | 0 | activeProviders.push(provider); |
| 489 | 0 | isProviderSet[provider] = true; |
| 490 | } |
|
| 491 | } |
|
| 492 | } |
|
| 493 | ||
| 494 | /// @notice Internal logic to set an emergency price for a token. |
|
| 495 | /// @param token_ The address of the token. |
|
| 496 | /// @param price_ The emergency price to set. |
|
| 497 | 0 | function _setEmergencyPrice(address token_, uint256 price_) internal { |
| 498 | 0 | emergencyPrices[token_] = price_; |
| 499 | 0 | emit EmergencyPriceUpdated(token_, price_); |
| 500 | } |
|
| 501 | } |
0%
src/oracles/SuperOracleL2.sol
Lines covered: 0 / 49 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | // Superform |
|
| 5 | ||
| 6 | import { SuperOracleBase } from "./SuperOracleBase.sol"; |
|
| 7 | import { ISuperOracleL2 } from "../interfaces/oracles/ISuperOracleL2.sol"; |
|
| 8 | ||
| 9 | // external |
|
| 10 | import { IERC20 } from "forge-std/interfaces/IERC20.sol"; |
|
| 11 | import { BoringERC20 } from "../vendor/BoringSolidity/BoringERC20.sol"; |
|
| 12 | import { AggregatorV3Interface } from "../vendor/chainlink/AggregatorV3Interface.sol"; |
|
| 13 | import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 14 | ||
| 15 | /// @title SuperOracleL2 |
|
| 16 | /// @author Superform Labs |
|
| 17 | /// @notice Layer 2 Oracle for Superform |
|
| 18 | 0 | contract SuperOracleL2 is SuperOracleBase, ISuperOracleL2 { |
| 19 | using BoringERC20 for IERC20; |
|
| 20 | ||
| 21 | /*////////////////////////////////////////////////////////////// |
|
| 22 | STORAGE |
|
| 23 | //////////////////////////////////////////////////////////////*/ |
|
| 24 | 0 | mapping(address dataOracle => address uptimeOracle) public uptimeFeeds; |
| 25 | 0 | mapping(address uptimeOracle => uint256 gracePeriod) public gracePeriods; |
| 26 | ||
| 27 | 0 | uint256 private constant DEFAULT_GRACE_PERIOD_TIME = 3600; |
| 28 | ||
| 29 | 0 | constructor( |
| 30 | address superGovernor_, |
|
| 31 | address[] memory bases, |
|
| 32 | address[] memory quotes, |
|
| 33 | bytes32[] memory providers, |
|
| 34 | address[] memory feeds |
|
| 35 | ) |
|
| 36 | 0 | SuperOracleBase(superGovernor_, bases, quotes, providers, feeds) |
| 37 | { } |
|
| 38 | ||
| 39 | /*////////////////////////////////////////////////////////////// |
|
| 40 | EXTERNAL FUNCTIONS |
|
| 41 | //////////////////////////////////////////////////////////////*/ |
|
| 42 | /// @inheritdoc ISuperOracleL2 |
|
| 43 | 0 | function batchSetUptimeFeed( |
| 44 | address[] calldata dataOracles, |
|
| 45 | address[] calldata uptimeOracles, |
|
| 46 | uint256[] calldata gracePeriods_ |
|
| 47 | ) |
|
| 48 | external |
|
| 49 | 0 | { |
| 50 | 0 | if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 51 | ||
| 52 | 0 | uint256 length = dataOracles.length; |
| 53 | 0 | if (length == 0) revert ZERO_ADDRESS(); // Reusing error code |
| 54 | 0 | if (length != uptimeOracles.length || length != gracePeriods_.length) { |
| 55 | 0 | revert ARRAY_LENGTH_MISMATCH(); |
| 56 | } |
|
| 57 | ||
| 58 | 0 | for (uint256 i; i < length; ++i) { |
| 59 | 0 | address dataOracle = dataOracles[i]; |
| 60 | 0 | address uptimeOracle = uptimeOracles[i]; |
| 61 | 0 | uint256 gracePeriod = gracePeriods_[i]; |
| 62 | ||
| 63 | 0 | if (dataOracle == address(0) || uptimeOracle == address(0)) revert ZERO_ADDRESS(); |
| 64 | ||
| 65 | 0 | uptimeFeeds[dataOracle] = uptimeOracle; |
| 66 | 0 | gracePeriods[uptimeOracle] = gracePeriod; |
| 67 | ||
| 68 | 0 | emit UptimeFeedSet(dataOracle, uptimeOracle); |
| 69 | 0 | emit GracePeriodSet(uptimeOracle, gracePeriod); |
| 70 | } |
|
| 71 | } |
|
| 72 | ||
| 73 | /*////////////////////////////////////////////////////////////// |
|
| 74 | INTERNAL FUNCTIONS |
|
| 75 | //////////////////////////////////////////////////////////////*/ |
|
| 76 | 0 | function _getQuoteFromOracle( |
| 77 | address oracle, |
|
| 78 | uint256 baseAmount, |
|
| 79 | address base, |
|
| 80 | address quote, |
|
| 81 | bool revertOnError |
|
| 82 | ) |
|
| 83 | internal |
|
| 84 | view |
|
| 85 | override |
|
| 86 | 0 | returns (uint256 quoteAmount) |
| 87 | 0 | { |
| 88 | 0 | { |
| 89 | 0 | address uptimeOracle = uptimeFeeds[oracle]; |
| 90 | 0 | if (uptimeOracle == address(0)) revert NO_UPTIME_FEED(); |
| 91 | ||
| 92 | 0 | ( |
| 93 | /*uint80 roundID*/ |
|
| 94 | , |
|
| 95 | 0 | int256 uptimeAnswer, |
| 96 | 0 | uint256 startedAt, |
| 97 | /*uint256 updatedAt*/ |
|
| 98 | , |
|
| 99 | /*uint80 answeredInRound*/ |
|
| 100 | 0 | ) = AggregatorV3Interface(uptimeOracle).latestRoundData(); |
| 101 | ||
| 102 | // Answer == 0: Sequencer is up |
|
| 103 | // Answer == 1: Sequencer is down |
|
| 104 | 0 | bool isSequencerUp = uptimeAnswer == 0; |
| 105 | 0 | if (!isSequencerUp) { |
| 106 | 0 | revert SEQUENCER_DOWN(); |
| 107 | } |
|
| 108 | ||
| 109 | // Make sure the grace period has passed after the |
|
| 110 | // sequencer is back up. |
|
| 111 | 0 | uint256 timeSinceUp = block.timestamp - startedAt; |
| 112 | 0 | uint256 gracePeriod = gracePeriods[uptimeOracle]; |
| 113 | 0 | if (gracePeriod == 0) { |
| 114 | gracePeriod = DEFAULT_GRACE_PERIOD_TIME; |
|
| 115 | } |
|
| 116 | 0 | if (timeSinceUp <= gracePeriod) { |
| 117 | 0 | revert GRACE_PERIOD_NOT_OVER(); |
| 118 | } |
|
| 119 | } |
|
| 120 | ||
| 121 | 0 | (, int256 answer,, uint256 updatedAt,) = AggregatorV3Interface(oracle).latestRoundData(); |
| 122 | ||
| 123 | // Validate data |
|
| 124 | 0 | if (answer <= 0 || block.timestamp - updatedAt > feedMaxStaleness[oracle]) { |
| 125 | 0 | if (revertOnError) revert ORACLE_UNTRUSTED_DATA(); |
| 126 | 0 | return 0; |
| 127 | } |
|
| 128 | ||
| 129 | // Get decimals |
|
| 130 | 0 | uint8 feedDecimals = _getOracleDecimals(AggregatorV3Interface(oracle)); |
| 131 | 0 | uint8 baseDecimals = IERC20(base).safeDecimals(); |
| 132 | 0 | uint8 quoteDecimals = IERC20(quote).safeDecimals(); |
| 133 | ||
| 134 | // Calculate quote amount with proper decimal scaling |
|
| 135 | 0 | quoteAmount = Math.mulDiv(baseAmount, uint256(answer), 10 ** feedDecimals); |
| 136 | 0 | quoteAmount = Math.mulDiv(quoteAmount, 10 ** quoteDecimals, 10 ** baseDecimals); |
| 137 | } |
|
| 138 | } |
0%
src/vendor/BoringSolidity/BoringERC20.sol
Lines covered: 0 / 4 (0%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | // With thanks to @BoringCrypto |
|
| 3 | pragma solidity >=0.8.19; |
|
| 4 | ||
| 5 | import { IERC20 } from "forge-std/interfaces/IERC20.sol"; |
|
| 6 | ||
| 7 | // solhint-disable avoid-low-level-calls |
|
| 8 | ||
| 9 | 0 | library BoringERC20 { |
| 10 | bytes4 private constant SIG_DECIMALS = 0x313ce567; // decimals() |
|
| 11 | ||
| 12 | /// @notice Provides a safe ERC20.decimals version which returns '18' as fallback value. |
|
| 13 | /// @param token The address of the ERC-20 token contract. |
|
| 14 | /// @return (uint8) Token decimals. |
|
| 15 | 0 | function safeDecimals(IERC20 token) internal view returns (uint8) { |
| 16 | 0 | (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_DECIMALS)); |
| 17 | 0 | return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; |
| 18 | } |
|
| 19 | } |
0%
src/vendor/BytesLib.sol
Lines covered: 0 / 4 (0%)
| 1 | // SPDX-License-Identifier: Unlicense |
|
| 2 | /* |
|
| 3 | * @title Solidity Bytes Arrays Utils |
|
| 4 | * @author Gonçalo Sá <goncalo.sa@consensys.net> |
|
| 5 | * |
|
| 6 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. |
|
| 7 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. |
|
| 8 | */ |
|
| 9 | pragma solidity 0.8.30; |
|
| 10 | ||
| 11 | ||
| 12 | 0 | library BytesLib { |
| 13 | function concat( |
|
| 14 | bytes memory _preBytes, |
|
| 15 | bytes memory _postBytes |
|
| 16 | ) |
|
| 17 | internal |
|
| 18 | pure |
|
| 19 | returns (bytes memory) |
|
| 20 | { |
|
| 21 | bytes memory tempBytes; |
|
| 22 | ||
| 23 | assembly { |
|
| 24 | // Get a location of some free memory and store it in tempBytes as |
|
| 25 | // Solidity does for memory variables. |
|
| 26 | tempBytes := mload(0x40) |
|
| 27 | ||
| 28 | // Store the length of the first bytes array at the beginning of |
|
| 29 | // the memory for tempBytes. |
|
| 30 | let length := mload(_preBytes) |
|
| 31 | mstore(tempBytes, length) |
|
| 32 | ||
| 33 | // Maintain a memory counter for the current write location in the |
|
| 34 | // temp bytes array by adding the 32 bytes for the array length to |
|
| 35 | // the starting location. |
|
| 36 | let mc := add(tempBytes, 0x20) |
|
| 37 | // Stop copying when the memory counter reaches the length of the |
|
| 38 | // first bytes array. |
|
| 39 | let end := add(mc, length) |
|
| 40 | ||
| 41 | for { |
|
| 42 | // Initialize a copy counter to the start of the _preBytes data, |
|
| 43 | // 32 bytes into its memory. |
|
| 44 | let cc := add(_preBytes, 0x20) |
|
| 45 | } lt(mc, end) { |
|
| 46 | // Increase both counters by 32 bytes each iteration. |
|
| 47 | mc := add(mc, 0x20) |
|
| 48 | cc := add(cc, 0x20) |
|
| 49 | } { |
|
| 50 | // Write the _preBytes data into the tempBytes memory 32 bytes |
|
| 51 | // at a time. |
|
| 52 | mstore(mc, mload(cc)) |
|
| 53 | } |
|
| 54 | ||
| 55 | // Add the length of _postBytes to the current length of tempBytes |
|
| 56 | // and store it as the new length in the first 32 bytes of the |
|
| 57 | // tempBytes memory. |
|
| 58 | length := mload(_postBytes) |
|
| 59 | mstore(tempBytes, add(length, mload(tempBytes))) |
|
| 60 | ||
| 61 | // Move the memory counter back from a multiple of 0x20 to the |
|
| 62 | // actual end of the _preBytes data. |
|
| 63 | mc := end |
|
| 64 | // Stop copying when the memory counter reaches the new combined |
|
| 65 | // length of the arrays. |
|
| 66 | end := add(mc, length) |
|
| 67 | ||
| 68 | for { |
|
| 69 | let cc := add(_postBytes, 0x20) |
|
| 70 | } lt(mc, end) { |
|
| 71 | mc := add(mc, 0x20) |
|
| 72 | cc := add(cc, 0x20) |
|
| 73 | } { |
|
| 74 | mstore(mc, mload(cc)) |
|
| 75 | } |
|
| 76 | ||
| 77 | // Update the free-memory pointer by padding our last write location |
|
| 78 | // to 32 bytes: add 31 bytes to the end of tempBytes to move to the |
|
| 79 | // next 32 byte block, then round down to the nearest multiple of |
|
| 80 | // 32. If the sum of the length of the two arrays is zero then add |
|
| 81 | // one before rounding down to leave a blank 32 bytes (the length block with 0). |
|
| 82 | mstore(0x40, and( |
|
| 83 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), |
|
| 84 | not(31) // Round down to the nearest 32 bytes. |
|
| 85 | )) |
|
| 86 | } |
|
| 87 | ||
| 88 | return tempBytes; |
|
| 89 | } |
|
| 90 | ||
| 91 | function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { |
|
| 92 | assembly { |
|
| 93 | // Read the first 32 bytes of _preBytes storage, which is the length |
|
| 94 | // of the array. (We don't need to use the offset into the slot |
|
| 95 | // because arrays use the entire slot.) |
|
| 96 | let fslot := sload(_preBytes.slot) |
|
| 97 | // Arrays of 31 bytes or less have an even value in their slot, |
|
| 98 | // while longer arrays have an odd value. The actual length is |
|
| 99 | // the slot divided by two for odd values, and the lowest order |
|
| 100 | // byte divided by two for even values. |
|
| 101 | // If the slot is even, bitwise and the slot with 255 and divide by |
|
| 102 | // two to get the length. If the slot is odd, bitwise and the slot |
|
| 103 | // with -1 and divide by two. |
|
| 104 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
|
| 105 | let mlength := mload(_postBytes) |
|
| 106 | let newlength := add(slength, mlength) |
|
| 107 | // slength can contain both the length and contents of the array |
|
| 108 | // if length < 32 bytes so let's prepare for that |
|
| 109 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
|
| 110 | switch add(lt(slength, 32), lt(newlength, 32)) |
|
| 111 | case 2 { |
|
| 112 | // Since the new array still fits in the slot, we just need to |
|
| 113 | // update the contents of the slot. |
|
| 114 | // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length |
|
| 115 | sstore( |
|
| 116 | _preBytes.slot, |
|
| 117 | // all the modifications to the slot are inside this |
|
| 118 | // next block |
|
| 119 | add( |
|
| 120 | // we can just add to the slot contents because the |
|
| 121 | // bytes we want to change are the LSBs |
|
| 122 | fslot, |
|
| 123 | add( |
|
| 124 | mul( |
|
| 125 | div( |
|
| 126 | // load the bytes from memory |
|
| 127 | mload(add(_postBytes, 0x20)), |
|
| 128 | // zero all bytes to the right |
|
| 129 | exp(0x100, sub(32, mlength)) |
|
| 130 | ), |
|
| 131 | // and now shift left the number of bytes to |
|
| 132 | // leave space for the length in the slot |
|
| 133 | exp(0x100, sub(32, newlength)) |
|
| 134 | ), |
|
| 135 | // increase length by the double of the memory |
|
| 136 | // bytes length |
|
| 137 | mul(mlength, 2) |
|
| 138 | ) |
|
| 139 | ) |
|
| 140 | ) |
|
| 141 | } |
|
| 142 | case 1 { |
|
| 143 | // The stored value fits in the slot, but the combined value |
|
| 144 | // will exceed it. |
|
| 145 | // get the keccak hash to get the contents of the array |
|
| 146 | mstore(0x0, _preBytes.slot) |
|
| 147 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
|
| 148 | ||
| 149 | // save new length |
|
| 150 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
|
| 151 | ||
| 152 | // The contents of the _postBytes array start 32 bytes into |
|
| 153 | // the structure. Our first read should obtain the `submod` |
|
| 154 | // bytes that can fit into the unused space in the last word |
|
| 155 | // of the stored array. To get this, we read 32 bytes starting |
|
| 156 | // from `submod`, so the data we read overlaps with the array |
|
| 157 | // contents by `submod` bytes. Masking the lowest-order |
|
| 158 | // `submod` bytes allows us to add that value directly to the |
|
| 159 | // stored value. |
|
| 160 | ||
| 161 | let submod := sub(32, slength) |
|
| 162 | let mc := add(_postBytes, submod) |
|
| 163 | let end := add(_postBytes, mlength) |
|
| 164 | let mask := sub(exp(0x100, submod), 1) |
|
| 165 | ||
| 166 | sstore( |
|
| 167 | sc, |
|
| 168 | add( |
|
| 169 | and( |
|
| 170 | fslot, |
|
| 171 | 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 |
|
| 172 | ), |
|
| 173 | and(mload(mc), mask) |
|
| 174 | ) |
|
| 175 | ) |
|
| 176 | ||
| 177 | for { |
|
| 178 | mc := add(mc, 0x20) |
|
| 179 | sc := add(sc, 1) |
|
| 180 | } lt(mc, end) { |
|
| 181 | sc := add(sc, 1) |
|
| 182 | mc := add(mc, 0x20) |
|
| 183 | } { |
|
| 184 | sstore(sc, mload(mc)) |
|
| 185 | } |
|
| 186 | ||
| 187 | mask := exp(0x100, sub(mc, end)) |
|
| 188 | ||
| 189 | sstore(sc, mul(div(mload(mc), mask), mask)) |
|
| 190 | } |
|
| 191 | default { |
|
| 192 | // get the keccak hash to get the contents of the array |
|
| 193 | mstore(0x0, _preBytes.slot) |
|
| 194 | // Start copying to the last used word of the stored array. |
|
| 195 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
|
| 196 | ||
| 197 | // save new length |
|
| 198 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
|
| 199 | ||
| 200 | // Copy over the first `submod` bytes of the new data as in |
|
| 201 | // case 1 above. |
|
| 202 | let slengthmod := mod(slength, 32) |
|
| 203 | let mlengthmod := mod(mlength, 32) |
|
| 204 | let submod := sub(32, slengthmod) |
|
| 205 | let mc := add(_postBytes, submod) |
|
| 206 | let end := add(_postBytes, mlength) |
|
| 207 | let mask := sub(exp(0x100, submod), 1) |
|
| 208 | ||
| 209 | sstore(sc, add(sload(sc), and(mload(mc), mask))) |
|
| 210 | ||
| 211 | for { |
|
| 212 | sc := add(sc, 1) |
|
| 213 | mc := add(mc, 0x20) |
|
| 214 | } lt(mc, end) { |
|
| 215 | sc := add(sc, 1) |
|
| 216 | mc := add(mc, 0x20) |
|
| 217 | } { |
|
| 218 | sstore(sc, mload(mc)) |
|
| 219 | } |
|
| 220 | ||
| 221 | mask := exp(0x100, sub(mc, end)) |
|
| 222 | ||
| 223 | sstore(sc, mul(div(mload(mc), mask), mask)) |
|
| 224 | } |
|
| 225 | } |
|
| 226 | } |
|
| 227 | ||
| 228 | function slice( |
|
| 229 | bytes memory _bytes, |
|
| 230 | uint256 _start, |
|
| 231 | uint256 _length |
|
| 232 | ) |
|
| 233 | internal |
|
| 234 | pure |
|
| 235 | returns (bytes memory) |
|
| 236 | { |
|
| 237 | // We're using the unchecked block below because otherwise execution ends |
|
| 238 | // with the native overflow error code. |
|
| 239 | unchecked { |
|
| 240 | require(_length + 31 >= _length, "slice_overflow"); |
|
| 241 | } |
|
| 242 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); |
|
| 243 | ||
| 244 | bytes memory tempBytes; |
|
| 245 | ||
| 246 | assembly { |
|
| 247 | switch iszero(_length) |
|
| 248 | case 0 { |
|
| 249 | // Get a location of some free memory and store it in tempBytes as |
|
| 250 | // Solidity does for memory variables. |
|
| 251 | tempBytes := mload(0x40) |
|
| 252 | ||
| 253 | // The first word of the slice result is potentially a partial |
|
| 254 | // word read from the original array. To read it, we calculate |
|
| 255 | // the length of that partial word and start copying that many |
|
| 256 | // bytes into the array. The first word we copy will start with |
|
| 257 | // data we don't care about, but the last `lengthmod` bytes will |
|
| 258 | // land at the beginning of the contents of the new array. When |
|
| 259 | // we're done copying, we overwrite the full first word with |
|
| 260 | // the actual length of the slice. |
|
| 261 | let lengthmod := and(_length, 31) |
|
| 262 | ||
| 263 | // The multiplication in the next line is necessary |
|
| 264 | // because when slicing multiples of 32 bytes (lengthmod == 0) |
|
| 265 | // the following copy loop was copying the origin's length |
|
| 266 | // and then ending prematurely not copying everything it should. |
|
| 267 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) |
|
| 268 | let end := add(mc, _length) |
|
| 269 | ||
| 270 | for { |
|
| 271 | // The multiplication in the next line has the same exact purpose |
|
| 272 | // as the one above. |
|
| 273 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) |
|
| 274 | } lt(mc, end) { |
|
| 275 | mc := add(mc, 0x20) |
|
| 276 | cc := add(cc, 0x20) |
|
| 277 | } { |
|
| 278 | mstore(mc, mload(cc)) |
|
| 279 | } |
|
| 280 | ||
| 281 | mstore(tempBytes, _length) |
|
| 282 | ||
| 283 | //update free-memory pointer |
|
| 284 | //allocating the array padded to 32 bytes like the compiler does now |
|
| 285 | mstore(0x40, and(add(mc, 31), not(31))) |
|
| 286 | } |
|
| 287 | //if we want a zero-length slice let's just return a zero-length array |
|
| 288 | default { |
|
| 289 | tempBytes := mload(0x40) |
|
| 290 | //zero out the 32 bytes slice we are about to return |
|
| 291 | //we need to do it because Solidity does not garbage collect |
|
| 292 | mstore(tempBytes, 0) |
|
| 293 | ||
| 294 | mstore(0x40, add(tempBytes, 0x20)) |
|
| 295 | } |
|
| 296 | } |
|
| 297 | ||
| 298 | return tempBytes; |
|
| 299 | } |
|
| 300 | ||
| 301 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { |
|
| 302 | require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); |
|
| 303 | address tempAddress; |
|
| 304 | ||
| 305 | assembly { |
|
| 306 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) |
|
| 307 | } |
|
| 308 | ||
| 309 | return tempAddress; |
|
| 310 | } |
|
| 311 | ||
| 312 | function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { |
|
| 313 | require(_bytes.length >= _start + 1 , "toUint8_outOfBounds"); |
|
| 314 | uint8 tempUint; |
|
| 315 | ||
| 316 | assembly { |
|
| 317 | tempUint := mload(add(add(_bytes, 0x1), _start)) |
|
| 318 | } |
|
| 319 | ||
| 320 | return tempUint; |
|
| 321 | } |
|
| 322 | ||
| 323 | function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { |
|
| 324 | require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); |
|
| 325 | uint16 tempUint; |
|
| 326 | ||
| 327 | assembly { |
|
| 328 | tempUint := mload(add(add(_bytes, 0x2), _start)) |
|
| 329 | } |
|
| 330 | ||
| 331 | return tempUint; |
|
| 332 | } |
|
| 333 | ||
| 334 | function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { |
|
| 335 | require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); |
|
| 336 | uint32 tempUint; |
|
| 337 | ||
| 338 | assembly { |
|
| 339 | tempUint := mload(add(add(_bytes, 0x4), _start)) |
|
| 340 | } |
|
| 341 | ||
| 342 | return tempUint; |
|
| 343 | } |
|
| 344 | ||
| 345 | function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { |
|
| 346 | require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); |
|
| 347 | uint64 tempUint; |
|
| 348 | ||
| 349 | assembly { |
|
| 350 | tempUint := mload(add(add(_bytes, 0x8), _start)) |
|
| 351 | } |
|
| 352 | ||
| 353 | return tempUint; |
|
| 354 | } |
|
| 355 | ||
| 356 | function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { |
|
| 357 | require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); |
|
| 358 | uint96 tempUint; |
|
| 359 | ||
| 360 | assembly { |
|
| 361 | tempUint := mload(add(add(_bytes, 0xc), _start)) |
|
| 362 | } |
|
| 363 | ||
| 364 | return tempUint; |
|
| 365 | } |
|
| 366 | ||
| 367 | function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { |
|
| 368 | require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); |
|
| 369 | uint128 tempUint; |
|
| 370 | ||
| 371 | assembly { |
|
| 372 | tempUint := mload(add(add(_bytes, 0x10), _start)) |
|
| 373 | } |
|
| 374 | ||
| 375 | return tempUint; |
|
| 376 | } |
|
| 377 | ||
| 378 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { |
|
| 379 | require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); |
|
| 380 | uint256 tempUint; |
|
| 381 | ||
| 382 | assembly { |
|
| 383 | tempUint := mload(add(add(_bytes, 0x20), _start)) |
|
| 384 | } |
|
| 385 | ||
| 386 | return tempUint; |
|
| 387 | } |
|
| 388 | ||
| 389 | 0 | function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { |
| 390 | 0 | require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); |
| 391 | bytes32 tempBytes32; |
|
| 392 | ||
| 393 | assembly { |
|
| 394 | 0 | tempBytes32 := mload(add(add(_bytes, 0x20), _start)) |
| 395 | } |
|
| 396 | ||
| 397 | return tempBytes32; |
|
| 398 | } |
|
| 399 | ||
| 400 | function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { |
|
| 401 | bool success = true; |
|
| 402 | ||
| 403 | assembly { |
|
| 404 | let length := mload(_preBytes) |
|
| 405 | ||
| 406 | // if lengths don't match the arrays are not equal |
|
| 407 | switch eq(length, mload(_postBytes)) |
|
| 408 | case 1 { |
|
| 409 | // cb is a circuit breaker in the for loop since there's |
|
| 410 | // no said feature for inline assembly loops |
|
| 411 | // cb = 1 - don't breaker |
|
| 412 | // cb = 0 - break |
|
| 413 | let cb := 1 |
|
| 414 | ||
| 415 | let mc := add(_preBytes, 0x20) |
|
| 416 | let end := add(mc, length) |
|
| 417 | ||
| 418 | for { |
|
| 419 | let cc := add(_postBytes, 0x20) |
|
| 420 | // the next line is the loop condition: |
|
| 421 | // while(uint256(mc < end) + cb == 2) |
|
| 422 | } eq(add(lt(mc, end), cb), 2) { |
|
| 423 | mc := add(mc, 0x20) |
|
| 424 | cc := add(cc, 0x20) |
|
| 425 | } { |
|
| 426 | // if any of these checks fails then arrays are not equal |
|
| 427 | if iszero(eq(mload(mc), mload(cc))) { |
|
| 428 | // unsuccess: |
|
| 429 | success := 0 |
|
| 430 | cb := 0 |
|
| 431 | } |
|
| 432 | } |
|
| 433 | } |
|
| 434 | default { |
|
| 435 | // unsuccess: |
|
| 436 | success := 0 |
|
| 437 | } |
|
| 438 | } |
|
| 439 | ||
| 440 | return success; |
|
| 441 | } |
|
| 442 | ||
| 443 | function equalStorage( |
|
| 444 | bytes storage _preBytes, |
|
| 445 | bytes memory _postBytes |
|
| 446 | ) |
|
| 447 | internal |
|
| 448 | view |
|
| 449 | returns (bool) |
|
| 450 | { |
|
| 451 | bool success = true; |
|
| 452 | ||
| 453 | assembly { |
|
| 454 | // we know _preBytes_offset is 0 |
|
| 455 | let fslot := sload(_preBytes.slot) |
|
| 456 | // Decode the length of the stored array like in concatStorage(). |
|
| 457 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
|
| 458 | let mlength := mload(_postBytes) |
|
| 459 | ||
| 460 | // if lengths don't match the arrays are not equal |
|
| 461 | switch eq(slength, mlength) |
|
| 462 | case 1 { |
|
| 463 | // slength can contain both the length and contents of the array |
|
| 464 | // if length < 32 bytes so let's prepare for that |
|
| 465 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
|
| 466 | if iszero(iszero(slength)) { |
|
| 467 | switch lt(slength, 32) |
|
| 468 | case 1 { |
|
| 469 | // blank the last byte which is the length |
|
| 470 | fslot := mul(div(fslot, 0x100), 0x100) |
|
| 471 | ||
| 472 | if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { |
|
| 473 | // unsuccess: |
|
| 474 | success := 0 |
|
| 475 | } |
|
| 476 | } |
|
| 477 | default { |
|
| 478 | // cb is a circuit breaker in the for loop since there's |
|
| 479 | // no said feature for inline assembly loops |
|
| 480 | // cb = 1 - don't breaker |
|
| 481 | // cb = 0 - break |
|
| 482 | let cb := 1 |
|
| 483 | ||
| 484 | // get the keccak hash to get the contents of the array |
|
| 485 | mstore(0x0, _preBytes.slot) |
|
| 486 | let sc := keccak256(0x0, 0x20) |
|
| 487 | ||
| 488 | let mc := add(_postBytes, 0x20) |
|
| 489 | let end := add(mc, mlength) |
|
| 490 | ||
| 491 | // the next line is the loop condition: |
|
| 492 | // while(uint256(mc < end) + cb == 2) |
|
| 493 | for {} eq(add(lt(mc, end), cb), 2) { |
|
| 494 | sc := add(sc, 1) |
|
| 495 | mc := add(mc, 0x20) |
|
| 496 | } { |
|
| 497 | if iszero(eq(sload(sc), mload(mc))) { |
|
| 498 | // unsuccess: |
|
| 499 | success := 0 |
|
| 500 | cb := 0 |
|
| 501 | } |
|
| 502 | } |
|
| 503 | } |
|
| 504 | } |
|
| 505 | } |
|
| 506 | default { |
|
| 507 | // unsuccess: |
|
| 508 | success := 0 |
|
| 509 | } |
|
| 510 | } |
|
| 511 | ||
| 512 | return success; |
|
| 513 | } |
|
| 514 | } |
100%
test/recon/BeforeAfter.sol
Lines covered: 86 / 86 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 5 | import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 6 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 7 | ||
| 8 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 9 | ||
| 10 | import {Setup} from "./Setup.sol"; |
|
| 11 | ||
| 12 | enum OpType { |
|
| 13 | DEFAULT, |
|
| 14 | ADD, |
|
| 15 | REMOVE, |
|
| 16 | FULFILL, |
|
| 17 | REQUEST, |
|
| 18 | TRANSFER, |
|
| 19 | CANCEL |
|
| 20 | } |
|
| 21 | ||
| 22 | // ghost variables for tracking state variable values before and after function calls |
|
| 23 | abstract contract BeforeAfter is Setup { |
|
| 24 | struct Vars { |
|
| 25 | mapping(address user => uint256 pendingAsAssets) pendingUserAssets; |
|
| 26 | mapping(address user => uint256 claimableAsAssets) claimableUserAssets; |
|
| 27 | mapping(address user => ISuperVaultStrategy.SuperVaultState) state; |
|
| 28 | mapping(address user => uint256 shares) superVaultShares; |
|
| 29 | uint256 oraclePPS; |
|
| 30 | uint256 naivePPS; |
|
| 31 | uint256 summedTotalShares; |
|
| 32 | uint256 summedAccumulatorShares; |
|
| 33 | uint256 summedAccumulatorCostBasis; |
|
| 34 | uint256 summedTotalAssets; |
|
| 35 | uint256 strategyAssetBalance; |
|
| 36 | uint256 summedPendingRedeem; |
|
| 37 | } |
|
| 38 | ||
| 39 | Vars internal _before; |
|
| 40 | Vars internal _after; |
|
| 41 | OpType internal _currentOp; |
|
| 42 | ||
| 43 | modifier updateGhosts() { |
|
| 44 | 18× | _currentOp = OpType.DEFAULT; |
| 45 | 12× | __before(); |
| 46 | _; |
|
| 47 | 6× | __after(); |
| 48 | } |
|
| 49 | ||
| 50 | modifier updateGhostsWithOpType(OpType op) { |
|
| 51 | 90× | _currentOp = op; |
| 52 | 36× | __before(); |
| 53 | _; |
|
| 54 | 17× | __after(); |
| 55 | } |
|
| 56 | ||
| 57 | 2× | function __before() internal { |
| 58 | 2× | ( |
| 59 | 1× | _before.summedAccumulatorShares, |
| 60 | 1× | _before.summedAccumulatorCostBasis |
| 61 | 4× | ) = _sumSuperVaultValues(); |
| 62 | 6× | _before.naivePPS = _calculateNaivePPS(); |
| 63 | 6× | _before.summedTotalShares = _sumTotalShares(); |
| 64 | 6× | _before.summedTotalAssets = _sumStrategyAssets(); |
| 65 | 6× | _before.summedPendingRedeem = _sumRequestedRedemptions(); |
| 66 | ||
| 67 | 22× | _before.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
| 68 | 27× | _before.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
| 69 | 128× | _before.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
| 70 | 2× | _getActor() |
| 71 | ); |
|
| 72 | 81× | _before.superVaultShares[_getActor()] = superVault.balanceOf( |
| 73 | 2× | _getActor() |
| 74 | ); |
|
| 75 | ||
| 76 | 125× | _before.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
| 77 | 5× | address(superVaultStrategy) |
| 78 | ); |
|
| 79 | 60× | _before.oraclePPS = superVaultAggregator.getPPS( |
| 80 | 5× | address(superVaultStrategy) |
| 81 | ); |
|
| 82 | } |
|
| 83 | ||
| 84 | 4× | function __after() internal { |
| 85 | 4× | ( |
| 86 | 2× | _after.summedAccumulatorShares, |
| 87 | 2× | _after.summedAccumulatorCostBasis |
| 88 | 8× | ) = _sumSuperVaultValues(); |
| 89 | 12× | _after.naivePPS = _calculateNaivePPS(); |
| 90 | 12× | _after.summedTotalShares = _sumTotalShares(); |
| 91 | 12× | _after.summedTotalAssets = _sumStrategyAssets(); |
| 92 | 12× | _after.summedPendingRedeem = _sumRequestedRedemptions(); |
| 93 | ||
| 94 | 44× | _after.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
| 95 | 54× | _after.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
| 96 | 256× | _after.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
| 97 | 4× | _getActor() |
| 98 | ); |
|
| 99 | 162× | _after.superVaultShares[_getActor()] = superVault.balanceOf( |
| 100 | 4× | _getActor() |
| 101 | ); |
|
| 102 | ||
| 103 | 252× | _after.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
| 104 | 10× | address(superVaultStrategy) |
| 105 | ); |
|
| 106 | 118× | _after.oraclePPS = superVaultAggregator.getPPS( |
| 107 | 10× | address(superVaultStrategy) |
| 108 | ); |
|
| 109 | } |
|
| 110 | ||
| 111 | // Helpers |
|
| 112 | ||
| 113 | /// @dev total shares in the system is the sum of shares in the escrow and held by all users |
|
| 114 | 4× | function _sumTotalShares() internal returns (uint256) { |
| 115 | 14× | address[] memory actors = _getActors(); |
| 116 | 4× | uint256 totalShares; |
| 117 | ||
| 118 | 142× | totalShares += superVault.balanceOf(address(superVaultEscrow)); |
| 119 | 26× | for (uint256 i; i < actors.length; i++) { |
| 120 | 182× | totalShares += superVault.balanceOf(actors[i]); |
| 121 | } |
|
| 122 | ||
| 123 | return totalShares; |
|
| 124 | } |
|
| 125 | ||
| 126 | /// @notice Calculates the naive price per share by summing all assets across strategy and yield sources |
|
| 127 | /// @dev inspired by the share price calculation from BaseSuperVaultTest::_updateSuperVaultPPS |
|
| 128 | /// @return naivePPS The calculated price per share (scaled by 1e18) |
|
| 129 | 8× | function _calculateNaivePPS() internal returns (uint256 naivePPS) { |
| 130 | // Get total supply of SuperVault shares |
|
| 131 | 140× | uint256 totalSupply = superVault.totalSupply(); |
| 132 | ||
| 133 | // If no shares exist, PPS is 0 |
|
| 134 | 12× | if (totalSupply == 0) { |
| 135 | 8× | return 0; |
| 136 | } |
|
| 137 | ||
| 138 | // Calculate total assets across all locations |
|
| 139 | 14× | uint256 totalAssets = _sumStrategyAssets(); |
| 140 | ||
| 141 | // Calculate naive PPS: (totalAssets * PRECISION) / totalSupply |
|
| 142 | // Using 1e18 as precision to match the system's PPS_DECIMALS |
|
| 143 | 158× | naivePPS = (totalAssets * superVault.PRECISION()) / totalSupply; |
| 144 | ||
| 145 | return naivePPS; |
|
| 146 | } |
|
| 147 | ||
| 148 | 22× | function _sumStrategyAssets() public view returns (uint256) { |
| 149 | // Get the underlying asset |
|
| 150 | 140× | address asset = superVault.asset(); |
| 151 | ||
| 152 | 4× | uint256 totalAssets; |
| 153 | // 1. Assets held directly in SuperVaultStrategy |
|
| 154 | 140× | totalAssets += IERC20(asset).balanceOf(address(superVaultStrategy)); |
| 155 | ||
| 156 | // 2. Loop through all yield sources and sum underlying asset balances |
|
| 157 | 12× | address[] memory yieldSources = _getYieldSources(); |
| 158 | 28× | for (uint256 i = 0; i < yieldSources.length; i++) { |
| 159 | 44× | if (yieldSources[i] != address(0)) { |
| 160 | // Get the underlying asset balance held in each yield source |
|
| 161 | 170× | totalAssets += IERC20(asset).balanceOf(yieldSources[i]); |
| 162 | } |
|
| 163 | } |
|
| 164 | ||
| 165 | 4× | return totalAssets; |
| 166 | } |
|
| 167 | ||
| 168 | 8× | function _sumSuperVaultValues() |
| 169 | internal |
|
| 170 | view |
|
| 171 | 4× | returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis) |
| 172 | 2× | { |
| 173 | 14× | address[] memory actors = _getActors(); |
| 174 | ||
| 175 | 30× | for (uint256 i; i < actors.length; i++) { |
| 176 | 156× | sumAccumulatorShares += superVaultStrategy |
| 177 | 34× | .getSuperVaultState(actors[i]) |
| 178 | .accumulatorShares; |
|
| 179 | 150× | sumAccumulatorCostBasis += superVaultStrategy |
| 180 | 34× | .getSuperVaultState(actors[i]) |
| 181 | .accumulatorCostBasis; |
|
| 182 | } |
|
| 183 | } |
|
| 184 | ||
| 185 | 4× | function _sumRequestedRedemptions() internal returns (uint256) { |
| 186 | 14× | address[] memory actors = _getActors(); |
| 187 | 2× | uint256 totalRequested; |
| 188 | 26× | for (uint256 i; i < actors.length; i++) { |
| 189 | 192× | totalRequested += superVault.pendingRedeemRequest(0, actors[i]); |
| 190 | } |
|
| 191 | ||
| 192 | return totalRequested; |
|
| 193 | } |
|
| 194 | ||
| 195 | 12× | function _getPendingAsAssets() internal returns (uint256) { |
| 196 | 136× | uint256 pendingRedemptions = superVault.pendingRedeemRequest( |
| 197 | 0, |
|
| 198 | 4× | _getActor() |
| 199 | ); |
|
| 200 | 120× | uint256 pendingRedemptionsAsAssets = superVault.convertToAssets( |
| 201 | pendingRedemptions |
|
| 202 | ); |
|
| 203 | ||
| 204 | return pendingRedemptionsAsAssets; |
|
| 205 | } |
|
| 206 | ||
| 207 | 12× | function _getClaimableAsAssets() internal returns (uint256) { |
| 208 | 8× | uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
| 209 | 0, |
|
| 210 | 2× | _getActor() |
| 211 | ); |
|
| 212 | uint256 claimableRedemptionsAsAssets = superVault.convertToAssets( |
|
| 213 | claimableRedemptions |
|
| 214 | ); |
|
| 215 | ||
| 216 | return claimableRedemptionsAsAssets; |
|
| 217 | } |
|
| 218 | } |
100%
test/recon/CryticTester.sol
Lines covered: 2 / 2 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {CryticAsserts} from "@chimera/CryticAsserts.sol"; |
|
| 5 | ||
| 6 | import {TargetFunctions} from "./TargetFunctions.sol"; |
|
| 7 | ||
| 8 | // echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml --format text --test-limit 1000000 --disable-slither |
|
| 9 | // medusa fuzz |
|
| 10 | 2116× | contract CryticTester is TargetFunctions, CryticAsserts { |
| 11 | constructor() payable { |
|
| 12 | 4× | setup(); |
| 13 | } |
|
| 14 | } |
83%
test/recon/Properties.sol
Lines covered: 290 / 346 (83%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {Asserts} from "@chimera/Asserts.sol"; |
|
| 5 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {ERC7540Properties} from "@properties-7540/ERC7540Properties.sol"; |
|
| 8 | import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; |
|
| 9 | ||
| 10 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 11 | ||
| 12 | import {OpType} from "test/recon/BeforeAfter.sol"; |
|
| 13 | import {BeforeAfter} from "./BeforeAfter.sol"; |
|
| 14 | ||
| 15 | abstract contract Properties is BeforeAfter, Asserts, ERC7540Properties { |
|
| 16 | using Math for uint256; |
|
| 17 | 2× | uint256 internal TOLERANCE = 10; |
| 18 | ||
| 19 | // `!!!`-prefixed reasons are load-bearing for cross-fuzzer bug dedup in the analysis |
|
| 20 | // pipeline. Handler assertion failures surface natively on every fuzzer: as Panic(0x01) |
|
| 21 | // under echidna/medusa/recon and as recorded assertion failures under Foundry |
|
| 22 | // (`assertions_revert = false`, upstream handler-bug reporting). |
|
| 23 | string constant ASSERTION_REDEEM_MAX_REDEEM_SHOULD_NOT_REVERT = |
|
| 24 | "!!! redeeming maxRedeem should not revert"; |
|
| 25 | string constant ASSERTION_WITHDRAW_MAX_WITHDRAW_SHOULD_NOT_REVERT = |
|
| 26 | "!!! withdraw of maxWithdraw should not revert"; |
|
| 27 | string constant ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE = |
|
| 28 | "!!! previewDeposit and deposit equivalence"; |
|
| 29 | string constant ASSERTION_PREVIEW_MINT_EQUIVALENCE = |
|
| 30 | "!!! previewMint and mint equivalence"; |
|
| 31 | string constant ASSERTION_MINT_REDEEM_SYMMETRICAL = |
|
| 32 | "!!! user loses assets in mint/redeem flow"; |
|
| 33 | string constant ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL = |
|
| 34 | "!!! user loses assets in deposit/withdraw flow"; |
|
| 35 | string constant ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION = |
|
| 36 | "!!! maxRedeem should be reset to 0 after full redemption"; |
|
| 37 | string constant ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL = |
|
| 38 | "!!! maxWithdraw should be reset to 0 after full withdrawal"; |
|
| 39 | string constant ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS = |
|
| 40 | "!!! total shares redeemed must not exceed sum of requested shares"; |
|
| 41 | string constant ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE = |
|
| 42 | "!!! Primary manager should always be changeable if not paused"; |
|
| 43 | string constant ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED = |
|
| 44 | "!!! users should always be able to withdraw unless the system is paused"; |
|
| 45 | string constant ASSERTION_CANCEL_REDEEM_PENDING_REQUEST_ZERO = |
|
| 46 | "!!! pendingRedeemRequests should be 0 after cancelling a redemption"; |
|
| 47 | string constant ASSERTION_CANCEL_REDEEM_AVG_REQUEST_PPS_ZERO = |
|
| 48 | "!!! averageRequestPPS should be 0 after cancelling a redemption"; |
|
| 49 | string constant ASSERTION_CANCEL_REDEEM_NO_OVERPAY = |
|
| 50 | "!!! user should not receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem"; |
|
| 51 | string constant ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION = |
|
| 52 | "!!! previewDeposit returns the correct amount compared to executing a deposit"; |
|
| 53 | string constant ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION = |
|
| 54 | "!!! previewMint returns the correct amount compared to executing a mint"; |
|
| 55 | string constant ASSERTION_TRANSFER_SHARES_CONSERVED = |
|
| 56 | "!!! shares are lost on transfer"; |
|
| 57 | string constant ASSERTION_TRANSFER_COST_BASIS_CONSERVED = |
|
| 58 | "!!! cost basis is lost on transfer"; |
|
| 59 | string constant ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM = |
|
| 60 | "!!! Claiming redemptions should never revert with INVALID_REDEEM_CLAIM"; |
|
| 61 | string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER = |
|
| 62 | "!!! _update should never revert in transfer"; |
|
| 63 | string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM = |
|
| 64 | "!!! _update should never revert in transferFrom"; |
|
| 65 | string constant ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT = |
|
| 66 | "!!! strategy incurs loss on fulfillment"; |
|
| 67 | string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES = |
|
| 68 | "!!! previewMint and previewDeposit equivalence (from shares)"; |
|
| 69 | string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS = |
|
| 70 | "!!! previewMint and previewDeposit equivalence (from assets)"; |
|
| 71 | string constant ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS = |
|
| 72 | "!!! previewMint is >= convertToAssets"; |
|
| 73 | string constant ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT = |
|
| 74 | "!!! convertToShares is >= previewDepositShares (equivalent without fees)"; |
|
| 75 | string constant ASSERTION_ERC7540_4_DEPOSIT = |
|
| 76 | "!!! ERC7540-4: deposit with more than max should revert"; |
|
| 77 | string constant ASSERTION_ERC7540_4_MINT = |
|
| 78 | "!!! ERC7540-4: mint with more than max should revert"; |
|
| 79 | string constant ASSERTION_ERC7540_4_WITHDRAW = |
|
| 80 | "!!! ERC7540-4: withdraw with more than max should revert"; |
|
| 81 | string constant ASSERTION_ERC7540_4_REDEEM = |
|
| 82 | "!!! ERC7540-4: redeem with more than max should revert"; |
|
| 83 | string constant ASSERTION_ERC7540_5 = |
|
| 84 | "!!! ERC7540-5: requestRedeem should revert if insufficient share balance"; |
|
| 85 | string constant ASSERTION_ERC7540_7_WITHDRAW = |
|
| 86 | "!!! ERC7540-7: withdraw should not revert when amount <= max"; |
|
| 87 | string constant ASSERTION_ERC7540_7_REDEEM = |
|
| 88 | "!!! ERC7540-7: redeem should not revert when amount <= max"; |
|
| 89 | string constant ASSERTION_CANARY = |
|
| 90 | "!!! canary assertion"; |
|
| 91 | string constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE = |
|
| 92 | "Canary invariant"; |
|
| 93 | ||
| 94 | /// @dev Property: oracle PPS doesn't change on deposit/mint/redeem/withdraw |
|
| 95 | 16× | function invariant_oraclePPSDoesntChangeOnAddOrRemove() public returns (bool) { |
| 96 | 33× | if (_currentOp == OpType.ADD || _currentOp == OpType.REMOVE) { |
| 97 | 20× | eq( |
| 98 | 4× | _before.oraclePPS, |
| 99 | 4× | _after.oraclePPS, |
| 100 | "deposit/withdrawal changes oracle PPS" |
|
| 101 | ); |
|
| 102 | } |
|
| 103 | return true; |
|
| 104 | } |
|
| 105 | ||
| 106 | /// @dev Property: naive PPS doesn't change on deposit/mint |
|
| 107 | // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
|
| 108 | // function property_naivePPSDoesntChangeOnDepositOrMint() public { |
|
| 109 | // if ( |
|
| 110 | // (_currentOp == OpType.ADD) && _before.naivePPS != 0 // price starts as zero when no shares minted |
|
| 111 | // ) { |
|
| 112 | // gte( |
|
| 113 | // _after.naivePPS, |
|
| 114 | // _before.naivePPS, |
|
| 115 | // "deposit/mint cannot decrease naive PPS" |
|
| 116 | // ); |
|
| 117 | // } |
|
| 118 | // } |
|
| 119 | ||
| 120 | /// @dev Property: naive PPS doesn't change on redeem/withdraw |
|
| 121 | // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
|
| 122 | // function property_naivePPSDoesntChangeOnRedeemOrWithdraw() public { |
|
| 123 | // if ( |
|
| 124 | // (_currentOp == OpType.REMOVE) && _before.naivePPS != 0 // price starts as zero when no shares minted |
|
| 125 | // ) { |
|
| 126 | // gte( |
|
| 127 | // _after.naivePPS, |
|
| 128 | // _before.naivePPS, |
|
| 129 | // "redeem/withdraw cannot decrease naive PPS" |
|
| 130 | // ); |
|
| 131 | // } |
|
| 132 | // } |
|
| 133 | ||
| 134 | /// @dev Property: fulfillRedeemRequest doesn't change naive PPS |
|
| 135 | // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
|
| 136 | // function property_naivePPSDoesntChangeOnRedeem() public { |
|
| 137 | // if (_currentOp == OpType.FULFILL) { |
|
| 138 | // eq( |
|
| 139 | // _before.naivePPS, |
|
| 140 | // _after.naivePPS, |
|
| 141 | // "fulfilling redemption changes naive PPS" |
|
| 142 | // ); |
|
| 143 | // } |
|
| 144 | // } |
|
| 145 | ||
| 146 | /// @dev Property: maxRedeem and maxWithdraw should always be equivalent |
|
| 147 | 21× | function invariant_maxRedeemMaxWithdrawSymmetry() public returns (bool) { |
| 148 | 64× | uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
| 149 | // convertToShares uses current price instead of avg from fulfillment |
|
| 150 | 64× | uint256 withdrawPrice = superVaultStrategy.getAverageWithdrawPrice( |
| 151 | 2× | _getActor() |
| 152 | ); |
|
| 153 | 6× | uint256 maxWithdrawAsShares = maxWithdraw.mulDiv( |
| 154 | 67× | superVault.PRECISION(), |
| 155 | 1× | withdrawPrice, |
| 156 | 1× | Math.Rounding.Floor |
| 157 | ); |
|
| 158 | ||
| 159 | 0 | uint256 maxRedeem = superVault.maxRedeem(_getActor()); |
| 160 | ||
| 161 | 1× | eq(maxWithdrawAsShares, maxRedeem, "maxWithdrawAsShares != maxRedeem"); |
| 162 | 7× | return true; |
| 163 | } |
|
| 164 | ||
| 165 | /// @dev Property: requestRedeem should never reduce SuperVault shares |
|
| 166 | 16× | function invariant_totalSharesDontDecreaseOnRedemptionRequest() public returns (bool) { |
| 167 | 15× | if (_currentOp == OpType.REQUEST) { |
| 168 | 20× | eq( |
| 169 | 4× | _before.summedTotalShares, |
| 170 | 4× | _after.summedTotalShares, |
| 171 | "requestRedeem should never reduce SuperVault shares" |
|
| 172 | ); |
|
| 173 | } |
|
| 174 | return true; |
|
| 175 | } |
|
| 176 | ||
| 177 | /// @dev Property: `SuperVault::totalSupply` == SUM(user balances) + balanceOf(escrow) |
|
| 178 | 16× | function invariant_shareSolvency() public returns (bool) { |
| 179 | 7× | uint256 sumOfShares = _sumTotalShares(); |
| 180 | 88× | eq(superVault.totalSupply(), sumOfShares, "vault shares are insolvent"); |
| 181 | return true; |
|
| 182 | } |
|
| 183 | ||
| 184 | /// @dev Property: balanceOf(escrow) >= SUM(controllers.pendingRedeemRequest) |
|
| 185 | 18× | function invariant_escrowBalance() public returns (bool) { |
| 186 | 7× | address[] memory actors = _getActors(); |
| 187 | ||
| 188 | 67× | uint256 escrowBalance = superVault.balanceOf(address(superVaultEscrow)); |
| 189 | 1× | uint256 pendingActorShares; |
| 190 | 15× | for (uint256 i; i < actors.length; i++) { |
| 191 | 96× | pendingActorShares += superVault.pendingRedeemRequest(0, actors[i]); |
| 192 | } |
|
| 193 | ||
| 194 | 21× | gte( |
| 195 | 1× | escrowBalance, |
| 196 | 1× | pendingActorShares, |
| 197 | "balanceOf(escrow) < SUM(controllers.pendingRedeemRequest)" |
|
| 198 | ); |
|
| 199 | 6× | return true; |
| 200 | } |
|
| 201 | ||
| 202 | /// @dev Property: maxMint should be 0 when aggregator is paused |
|
| 203 | 21× | function invariant_maxMintZeroWhenPaused() public returns (bool) { |
| 204 | 59× | bool paused = superVaultAggregator.isStrategyPaused( |
| 205 | 5× | address(superVaultStrategy) |
| 206 | ); |
|
| 207 | 66× | uint256 maxMint = superVault.maxMint(_getActor()); |
| 208 | ||
| 209 | 4× | if (paused) { |
| 210 | 23× | eq(maxMint, 0, "actor has nonzero maxMint when strategy is paused"); |
| 211 | } |
|
| 212 | 5× | return true; |
| 213 | } |
|
| 214 | ||
| 215 | /// @dev Property: maxDeposit should be 0 when strategy is paused |
|
| 216 | 19× | function invariant_maxDepositZeroWhenPaused() public returns (bool) { |
| 217 | 59× | bool paused = superVaultAggregator.isStrategyPaused( |
| 218 | 5× | address(superVaultStrategy) |
| 219 | ); |
|
| 220 | 66× | uint256 maxDeposit = superVault.maxDeposit(_getActor()); |
| 221 | ||
| 222 | 4× | if (paused) { |
| 223 | 20× | eq( |
| 224 | 1× | maxDeposit, |
| 225 | 1× | 0, |
| 226 | "actor has nonzero maxDeposit when strategy is paused" |
|
| 227 | ); |
|
| 228 | } |
|
| 229 | return true; |
|
| 230 | } |
|
| 231 | ||
| 232 | /// @dev Property: SUM(accumulatorShares) doesn't change on SuperVault share transfers |
|
| 233 | 28× | function invariant_accumulatorSharesSolvency() public returns (bool) { |
| 234 | 15× | if (_currentOp == OpType.TRANSFER) { |
| 235 | 21× | eq( |
| 236 | 4× | _before.summedAccumulatorShares, |
| 237 | 4× | _after.summedAccumulatorShares, // TODO: think about way to handle transfer on recipient |
| 238 | "SUM(accumulatorShares) changed on SuperVault share transfers" |
|
| 239 | ); |
|
| 240 | } |
|
| 241 | 2× | return true; |
| 242 | } |
|
| 243 | ||
| 244 | /// @dev Property: SUM(accumulatorCostBasis) doesn't change on SuperVault share transfers |
|
| 245 | 16× | function invariant_accumulatorCostBasisSolvency() public returns (bool) { |
| 246 | 15× | if (_currentOp == OpType.TRANSFER) { |
| 247 | 20× | eq( |
| 248 | 4× | _before.summedAccumulatorCostBasis, |
| 249 | 4× | _after.summedAccumulatorCostBasis, |
| 250 | "SUM(accumulatorCostBasis) changed on SuperVault share transfers" |
|
| 251 | ); |
|
| 252 | } |
|
| 253 | return true; |
|
| 254 | } |
|
| 255 | ||
| 256 | /// @dev Property: cancelRedeem should never alter the supply of SuperVault tokens |
|
| 257 | 16× | function invariant_cancelDoesntChangeTotalSupply() public returns (bool) { |
| 258 | 15× | if (_currentOp == OpType.CANCEL) { |
| 259 | 20× | eq( |
| 260 | 4× | _before.summedTotalShares, |
| 261 | 4× | _after.summedTotalShares, |
| 262 | "cancelRedeem should never alter the supply of SuperVault tokens" |
|
| 263 | ); |
|
| 264 | } |
|
| 265 | return true; |
|
| 266 | } |
|
| 267 | ||
| 268 | /// @dev Property: if totalSupply > 0, then totalAssets > 0 |
|
| 269 | 18× | function invariant_assetBacking() public returns (bool) { |
| 270 | 7× | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 271 | ||
| 272 | 72× | if (superVault.totalSupply() > 0) { |
| 273 | 21× | gt( |
| 274 | 1× | summedTotalAssets, |
| 275 | 1× | 0, |
| 276 | "if totalSupply > 0, then totalAssets > 0" |
|
| 277 | ); |
|
| 278 | } |
|
| 279 | 4× | return true; |
| 280 | } |
|
| 281 | ||
| 282 | /// @dev Property: SUM(shares) * PPS == totalAssets |
|
| 283 | 16× | function invariant_totalAssets() public returns (bool) { |
| 284 | 7× | uint256 totalShares = _sumTotalShares(); |
| 285 | 67× | uint256 pps = superVaultAggregator.getPPS(address(superVaultStrategy)); |
| 286 | 15× | uint256 implicitTotalAssets = (totalShares * pps) / |
| 287 | 67× | superVault.PRECISION(); |
| 288 | ||
| 289 | 70× | uint256 vaultTotalAssets = superVault.totalAssets(); |
| 290 | 20× | eq( |
| 291 | 1× | implicitTotalAssets, |
| 292 | 1× | vaultTotalAssets, |
| 293 | "totalShares * pps != totalAssets" |
|
| 294 | ); |
|
| 295 | return true; |
|
| 296 | } |
|
| 297 | ||
| 298 | /// @dev Property: When a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease |
|
| 299 | 19× | function invariant_avgPPSDoesntDecrease() public returns (bool) { |
| 300 | 2× | uint256 currentPrice = _before.oraclePPS; |
| 301 | 22× | uint256 beforeAvgPPS = _before.state[_getActor()].averageRequestPPS; |
| 302 | 20× | uint256 afterAvgPPS = _after.state[_getActor()].averageRequestPPS; |
| 303 | ||
| 304 | 26× | if (_currentOp == OpType.REQUEST && currentPrice >= beforeAvgPPS) { |
| 305 | 20× | gte( |
| 306 | 1× | afterAvgPPS, |
| 307 | 1× | beforeAvgPPS, |
| 308 | "when a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease" |
|
| 309 | ); |
|
| 310 | } |
|
| 311 | return true; |
|
| 312 | } |
|
| 313 | ||
| 314 | /// @dev Property: previewMint and previewDeposit equivalence (from shares) |
|
| 315 | 20× | function global_previewEquivalenceFromShares_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES(uint256 shares) public { |
| 316 | 68× | uint256 previewMintAssets = superVault.previewMint(shares); |
| 317 | 70× | uint256 previewDepositShares = superVault.previewDeposit( |
| 318 | previewMintAssets |
|
| 319 | ); |
|
| 320 | 69× | uint256 price = superVaultStrategy.getStoredPPS(); |
| 321 | ||
| 322 | 4× | if (price > 0) { |
| 323 | 3× | eq( |
| 324 | 1× | shares, |
| 325 | 1× | previewDepositShares, |
| 326 | 17× | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES |
| 327 | ); |
|
| 328 | } |
|
| 329 | } |
|
| 330 | ||
| 331 | /// @dev Property: previewMint and previewDeposit equivalence (from assets) |
|
| 332 | 20× | function global_previewEquivalenceFromAssets_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS(uint256 assets) public { |
| 333 | 68× | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 334 | 63× | uint256 previewMintAssets_under = superVault.previewMint( |
| 335 | previewDepositShares |
|
| 336 | ); |
|
| 337 | 74× | uint256 previewMintAssets_over = superVault.previewMint( |
| 338 | 5× | previewDepositShares + 1 |
| 339 | ); |
|
| 340 | 69× | uint256 price = superVaultStrategy.getStoredPPS(); |
| 341 | ||
| 342 | 4× | if (price > 0) { |
| 343 | 4× | gte( |
| 344 | 1× | assets, |
| 345 | 1× | previewMintAssets_under, |
| 346 | 17× | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
| 347 | ); |
|
| 348 | ||
| 349 | 3× | lte( |
| 350 | 1× | assets, |
| 351 | 1× | previewMintAssets_over, |
| 352 | 17× | ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
| 353 | ); |
|
| 354 | } |
|
| 355 | } |
|
| 356 | ||
| 357 | /// @dev Property: previewMint is >= convertToAssets |
|
| 358 | 20× | function global_comparePreviewMintAndConvertToAssets_ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS( |
| 359 | uint256 shares |
|
| 360 | ) public { |
|
| 361 | 68× | uint256 previewMintAssets = superVault.previewMint(shares); |
| 362 | 63× | uint256 convertToAssets = superVault.convertToAssets(shares); |
| 363 | 3× | gte( |
| 364 | 1× | previewMintAssets, |
| 365 | 1× | convertToAssets, |
| 366 | 17× | ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS |
| 367 | ); |
|
| 368 | } |
|
| 369 | ||
| 370 | /// @dev Property: convertToShares is >= previewDepositShares (equivalent without fees) |
|
| 371 | 20× | function global_comparePreviewDepositAndConvertToShares_ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT( |
| 372 | uint256 assets |
|
| 373 | ) public { |
|
| 374 | 68× | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 375 | 70× | uint256 convertToShares = superVault.convertToShares(assets); |
| 376 | 3× | gte( |
| 377 | 1× | convertToShares, |
| 378 | 1× | previewDepositShares, |
| 379 | 17× | ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT |
| 380 | ); |
|
| 381 | } |
|
| 382 | ||
| 383 | // NOTE: Withdrawal properties are more nuanced, as we should ensure that no gain can be had and that losses are imputed to the controller receiving the assets |
|
| 384 | ||
| 385 | /// @dev Property: After all redemptions are processed, the sum of all claimable is <= balance available |
|
| 386 | 16× | function invariant_sumOfClaimable() public returns (bool) { |
| 387 | 7× | address[] memory actors = _getActors(); |
| 388 | ||
| 389 | 1× | uint256 sumPending; |
| 390 | 1× | uint256 sumClaimable; |
| 391 | 14× | for (uint256 i; i < actors.length; i++) { |
| 392 | 97× | sumPending += superVault.pendingRedeemRequest(0, actors[i]); |
| 393 | 89× | sumClaimable += superVault.maxWithdraw(actors[i]); |
| 394 | } |
|
| 395 | ||
| 396 | 124× | uint256 strategyBalance = MockERC20(superVault.asset()).balanceOf( |
| 397 | 5× | address(superVaultStrategy) |
| 398 | ); |
|
| 399 | ||
| 400 | // precondition: all pending has been fulfilled |
|
| 401 | 5× | if (sumPending == 0) { |
| 402 | 20× | lte( |
| 403 | 1× | sumClaimable, |
| 404 | 1× | strategyBalance, |
| 405 | "sum of all claimable is > balance available" |
|
| 406 | ); |
|
| 407 | } |
|
| 408 | return true; |
|
| 409 | } |
|
| 410 | ||
| 411 | /// @dev Property: If the sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0 |
|
| 412 | 16× | function invariant_sumOfAssetsMaxWithdrawable() public returns (bool) { |
| 413 | 7× | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 414 | ||
| 415 | 5× | if (summedTotalAssets == 0) { |
| 416 | 66× | uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
| 417 | 20× | eq( |
| 418 | 1× | maxWithdraw, |
| 419 | 1× | 0, |
| 420 | "if sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0" |
|
| 421 | ); |
|
| 422 | } |
|
| 423 | return true; |
|
| 424 | } |
|
| 425 | ||
| 426 | /// @dev Property: averageWithdrawPrice should never decrease when new redemptions are fulfilled at a higher PPS |
|
| 427 | 16× | function invariant_avgPPSMonotonicity() public returns (bool) { |
| 428 | 3× | if ( |
| 429 | 23× | _currentOp == OpType.FULFILL && |
| 430 | 0 | _before.oraclePPS > _before.state[_getActor()].averageRequestPPS && // fulfilled at a higher price |
| 431 | 0 | _after.state[_getActor()].pendingRedeemRequest != 0 // redemptions have all been fulfilled/cancelled; avg gets reset to 0 in this case |
| 432 | ) { |
|
| 433 | 0 | gte( |
| 434 | 0 | _after.state[_getActor()].averageRequestPPS, |
| 435 | 0 | _before.state[_getActor()].averageRequestPPS, |
| 436 | "averageWithdrawPrice should not decrease when fulfilled at a higher PPS" |
|
| 437 | ); |
|
| 438 | } |
|
| 439 | return true; |
|
| 440 | } |
|
| 441 | ||
| 442 | /// @dev Property: state.accumulatorShares >= superVaultState[controllers[i]].pendingRedeemRequest for each user |
|
| 443 | 18× | function invariant_accumulatorSharesGtPendingRequests() public returns (bool) { |
| 444 | 7× | address[] memory actors = _getActors(); |
| 445 | ||
| 446 | 15× | for (uint256 i; i < actors.length; i++) { |
| 447 | 4× | ISuperVaultStrategy.SuperVaultState |
| 448 | 82× | memory state = superVaultStrategy.getSuperVaultState(actors[i]); |
| 449 | 21× | gte( |
| 450 | 4× | state.accumulatorShares, |
| 451 | 4× | state.pendingRedeemRequest, |
| 452 | "state.accumulatorShares < state.pendingRedeemRequest" |
|
| 453 | ); |
|
| 454 | } |
|
| 455 | 4× | return true; |
| 456 | } |
|
| 457 | ||
| 458 | /// @dev Property: accumulatorShares is always accurately increased |
|
| 459 | 17× | function invariant_accumulatorSharesIncrease() public returns (bool) { |
| 460 | 19× | if (_currentOp == OpType.ADD) { |
| 461 | 22× | uint256 accumulatorSharesBefore = _before |
| 462 | 2× | .state[_getActor()] |
| 463 | .accumulatorShares; |
|
| 464 | 20× | uint256 accumulatorSharesAfter = _after |
| 465 | 2× | .state[_getActor()] |
| 466 | .accumulatorShares; |
|
| 467 | 20× | uint256 actorSharesBefore = _before.superVaultShares[_getActor()]; |
| 468 | 18× | uint256 actorSharesAfter = _after.superVaultShares[_getActor()]; |
| 469 | 21× | eq( |
| 470 | 6× | accumulatorSharesAfter - accumulatorSharesBefore, |
| 471 | 6× | actorSharesAfter - actorSharesBefore, |
| 472 | "accumulatorShares is always accurately updated" |
|
| 473 | ); |
|
| 474 | } |
|
| 475 | 2× | return true; |
| 476 | } |
|
| 477 | ||
| 478 | /// @dev Property: accumulatorCostBasis is always accurately increased |
|
| 479 | 16× | function invariant_accumulatorCostBasisIncrease() public returns (bool) { |
| 480 | 15× | if (_currentOp == OpType.ADD) { |
| 481 | 22× | uint256 accumulatorCostBasisBefore = _before |
| 482 | 2× | .state[_getActor()] |
| 483 | .accumulatorCostBasis; |
|
| 484 | 19× | uint256 accumulatorCostBasisAfter = _after |
| 485 | 2× | .state[_getActor()] |
| 486 | .accumulatorCostBasis; |
|
| 487 | 1× | eq( |
| 488 | 5× | accumulatorCostBasisAfter - accumulatorCostBasisBefore, |
| 489 | 9× | _after.strategyAssetBalance - _before.strategyAssetBalance, |
| 490 | "accumulatorShares is always accurately updated" |
|
| 491 | ); |
|
| 492 | } |
|
| 493 | return true; |
|
| 494 | } |
|
| 495 | ||
| 496 | /// @dev Property: redemptions only burn the requested amount of shares (within tolerance range) |
|
| 497 | 16× | function invariant_fulfillOnlyBurnsRequestedAmount() public returns (bool) { |
| 498 | 1× | uint256 TOLERANCE_CONSTANT = 10 wei; // taken from SuperVaultStrategy |
| 499 | ||
| 500 | 15× | if (_currentOp == OpType.FULFILL) { |
| 501 | 0 | uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
| 502 | 0 | _after.summedPendingRedeem; |
| 503 | 0 | uint256 totalSupplyDelta = _before.summedTotalShares - |
| 504 | 0 | _after.summedTotalShares; |
| 505 | ||
| 506 | // Check that burned amount is within tolerance of requested amount |
|
| 507 | 0 | if (totalSupplyDelta < pendingRedeemDelta) { |
| 508 | // Burned less than requested - check within tolerance |
|
| 509 | 0 | gte( |
| 510 | 0 | totalSupplyDelta, |
| 511 | 0 | pendingRedeemDelta - TOLERANCE_CONSTANT, |
| 512 | "burned less than requested beyond tolerance" |
|
| 513 | ); |
|
| 514 | } else { |
|
| 515 | // Burned more than requested - check within tolerance |
|
| 516 | 0 | lte( |
| 517 | 0 | totalSupplyDelta, |
| 518 | 0 | pendingRedeemDelta + TOLERANCE_CONSTANT, |
| 519 | "burned more than requested beyond tolerance" |
|
| 520 | ); |
|
| 521 | } |
|
| 522 | } |
|
| 523 | 0 | return true; |
| 524 | } |
|
| 525 | ||
| 526 | /// @dev Property: accumulatorShares decreases by the exact amounts requested when fulfilling redemptions |
|
| 527 | 17× | function invariant_accumulatorSharesDecreaseOnFulfill_exact() public returns (bool) { |
| 528 | 17× | if (_currentOp == OpType.FULFILL) { |
| 529 | 0 | uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
| 530 | 0 | _after.summedAccumulatorShares; |
| 531 | 0 | uint256 totalSharesDelta = _before.summedTotalShares - |
| 532 | 0 | _after.summedTotalShares; |
| 533 | 1× | eq( |
| 534 | 0 | accumulatorSharesDelta, |
| 535 | 0 | totalSharesDelta, |
| 536 | "accumulatorShares decreases by the exact amounts requested when fulfilling redemptions" |
|
| 537 | ); |
|
| 538 | } |
|
| 539 | 2× | return true; |
| 540 | } |
|
| 541 | ||
| 542 | 15× | function invariant_accumulatorSharesDecreaseOnFulfill_with_tolerance() |
| 543 | 1× | public returns (bool) |
| 544 | { |
|
| 545 | 15× | if (_currentOp == OpType.FULFILL) { |
| 546 | 0 | uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
| 547 | 0 | _after.summedAccumulatorShares; |
| 548 | 0 | uint256 totalSharesDelta = _before.summedTotalShares - |
| 549 | 0 | _after.summedTotalShares; |
| 550 | 0 | if (accumulatorSharesDelta >= totalSharesDelta) { |
| 551 | 0 | lte( |
| 552 | 0 | accumulatorSharesDelta - totalSharesDelta, |
| 553 | 0 | TOLERANCE, |
| 554 | "accumulatorShares decreases by more than TOLERANCE when fulfilling redemptions" |
|
| 555 | ); |
|
| 556 | } else { |
|
| 557 | 0 | lte( |
| 558 | 0 | totalSharesDelta - accumulatorSharesDelta, |
| 559 | 0 | TOLERANCE, |
| 560 | "accumulatorShares decreases by less than TOLERANCE when fulfilling redemptions" |
|
| 561 | ); |
|
| 562 | } |
|
| 563 | } |
|
| 564 | return true; |
|
| 565 | } |
|
| 566 | ||
| 567 | /// Optimization Setters |
|
| 568 | ||
| 569 | 21× | function setpreviewAssetsGreater(uint256 shares) public { |
| 570 | 68× | uint256 previewMintAssets = superVault.previewMint(shares); |
| 571 | 70× | uint256 previewDepositAssets = superVault.previewDeposit( |
| 572 | previewMintAssets |
|
| 573 | ); |
|
| 574 | ||
| 575 | 7× | if (previewMintAssets > previewDepositAssets) { |
| 576 | 2× | previewMintAssetsGreater = int256(previewMintAssets); |
| 577 | } else { |
|
| 578 | 2× | previewDepositAssetsGreater = int256(previewDepositAssets); |
| 579 | } |
|
| 580 | } |
|
| 581 | ||
| 582 | 24× | function setPreviewSharesGreater(uint256 assets) public { |
| 583 | 68× | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 584 | 63× | uint256 previewMintShares = superVault.previewMint( |
| 585 | previewDepositShares |
|
| 586 | ); |
|
| 587 | ||
| 588 | 7× | if (previewDepositShares > previewMintShares) { |
| 589 | 2× | previewDepositSharesGreater = |
| 590 | 5× | int256(previewDepositShares) - |
| 591 | 1× | int256(previewMintShares); |
| 592 | } else { |
|
| 593 | 2× | previewMintSharesGreater = |
| 594 | 5× | int256(previewMintShares) - |
| 595 | 1× | int256(previewDepositShares); |
| 596 | } |
|
| 597 | } |
|
| 598 | ||
| 599 | 15× | function setFulfilledDifference() public { |
| 600 | 15× | if (_currentOp == OpType.FULFILL) { |
| 601 | 0 | uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
| 602 | 0 | _after.summedPendingRedeem; |
| 603 | 0 | uint256 totalSupplyDelta = _before.summedTotalShares - |
| 604 | 0 | _after.summedTotalShares; |
| 605 | ||
| 606 | // Check that burned amount is within tolerance of requested amount |
|
| 607 | 0 | if (totalSupplyDelta < pendingRedeemDelta) { |
| 608 | // Burned less than requested |
|
| 609 | 0 | int256 burnedLessThanRequested = int256(pendingRedeemDelta) - |
| 610 | 0 | int256(totalSupplyDelta); |
| 611 | } else { |
|
| 612 | // Burned more than requested |
|
| 613 | 0 | int256 burnedMoreThanRequested = int256(totalSupplyDelta) - |
| 614 | 0 | int256(pendingRedeemDelta); |
| 615 | } |
|
| 616 | } |
|
| 617 | } |
|
| 618 | ||
| 619 | /// Optimization Tests |
|
| 620 | ||
| 621 | /// @dev Optimize the difference between the amount of assets in the system and claimable assets |
|
| 622 | 16× | function optimize_maxDustAccumulation() public view returns (int256) { |
| 623 | 7× | address[] memory actors = _getActors(); |
| 624 | ||
| 625 | 1× | uint256 summedClaimableRedemptionsAsAssets; |
| 626 | 15× | for (uint256 i; i < actors.length; i++) { |
| 627 | 74× | uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
| 628 | 0, |
|
| 629 | 17× | actors[i] |
| 630 | ); |
|
| 631 | 64× | summedClaimableRedemptionsAsAssets += superVault.convertToAssets( |
| 632 | claimableRedemptions |
|
| 633 | ); |
|
| 634 | } |
|
| 635 | ||
| 636 | 7× | uint256 totalAssets = _sumStrategyAssets(); |
| 637 | ||
| 638 | 4× | return int256(totalAssets) - int256(summedClaimableRedemptionsAsAssets); |
| 639 | } |
|
| 640 | ||
| 641 | /// @dev Optimize the difference between the amount of claimable assets and assets in the system |
|
| 642 | 19× | function optimize_moreClaimableThanHeldDifference() |
| 643 | public |
|
| 644 | view |
|
| 645 | 1× | returns (int256) |
| 646 | 3× | { |
| 647 | 7× | address[] memory actors = _getActors(); |
| 648 | ||
| 649 | 1× | uint256 summedClaimableRedemptionsAsAssets; |
| 650 | 15× | for (uint256 i; i < actors.length; i++) { |
| 651 | 74× | summedClaimableRedemptionsAsAssets += superVault.maxWithdraw( |
| 652 | 17× | actors[i] |
| 653 | ); |
|
| 654 | } |
|
| 655 | ||
| 656 | 7× | uint256 totalAssets = _sumStrategyAssets(); |
| 657 | ||
| 658 | 7× | if (summedClaimableRedemptionsAsAssets > totalAssets) { |
| 659 | 5× | return |
| 660 | 1× | int256(summedClaimableRedemptionsAsAssets) - |
| 661 | 0 | int256(totalAssets); |
| 662 | } |
|
| 663 | } |
|
| 664 | ||
| 665 | 12× | function optimize_burnMoreThanRequestedInRedemption() |
| 666 | public |
|
| 667 | view |
|
| 668 | returns (int256) |
|
| 669 | { |
|
| 670 | 2× | return burnedMoreThanRequested; |
| 671 | } |
|
| 672 | ||
| 673 | 14× | function optimize_burnLessThanRequestedInRedemption() |
| 674 | public |
|
| 675 | view |
|
| 676 | returns (int256) |
|
| 677 | { |
|
| 678 | 2× | return burnedLessThanRequested; |
| 679 | } |
|
| 680 | ||
| 681 | // function optimize_previewMintSharesGreater() public view returns (int256) { |
|
| 682 | // return previewMintSharesGreater; |
|
| 683 | // } |
|
| 684 | ||
| 685 | // function optimize_previewDepositSharesGreater() |
|
| 686 | // public |
|
| 687 | // view |
|
| 688 | // returns (int256) |
|
| 689 | // { |
|
| 690 | // return previewDepositSharesGreater; |
|
| 691 | // } |
|
| 692 | ||
| 693 | // function optimize_previewMintAssetsGreater() public view returns (int256) { |
|
| 694 | // return previewMintAssetsGreater; |
|
| 695 | // } |
|
| 696 | ||
| 697 | // function optimize_previewDepositAssetsGreater() |
|
| 698 | // public |
|
| 699 | // view |
|
| 700 | // returns (int256) |
|
| 701 | // { |
|
| 702 | // return previewDepositAssetsGreater; |
|
| 703 | // } |
|
| 704 | ||
| 705 | 20× | function optimize_assetBackingDifference() public view returns (int256) { |
| 706 | 7× | uint256 summedTotalAssets = _sumStrategyAssets(); |
| 707 | 69× | uint256 totalSupply = superVault.totalSupply(); |
| 708 | ||
| 709 | 15× | if (summedTotalAssets == 0 && totalSupply > 0) { |
| 710 | 0 | return int256(totalSupply); |
| 711 | } |
|
| 712 | } |
|
| 713 | ||
| 714 | // Canaries |
|
| 715 | ||
| 716 | /// @dev Canary assertion helper. A failing input is expected to be discovered during fuzzing. |
|
| 717 | 20× | function assert_canary_ASSERTION_CANARY(uint256 entropy) public { |
| 718 | 22× | t(entropy > 0, ASSERTION_CANARY); |
| 719 | } |
|
| 720 | ||
| 721 | /// @dev Canary global invariant expected to fail immediately. |
|
| 722 | 16× | function invariant_canary() public returns (bool) { |
| 723 | 20× | t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE); |
| 724 | return true; |
|
| 725 | } |
|
| 726 | ||
| 727 | // ERC7540 Properties from erc7540-reusable-properties |
|
| 728 | ||
| 729 | /// @dev Property 7540-1: convertToAssets(totalSupply) == totalAssets unless price is 0.0 |
|
| 730 | 16× | function invariant_erc7540_1() public returns (bool) { |
| 731 | 12× | actor = _getActor(); |
| 732 | 21× | t( |
| 733 | 8× | erc7540_1(address(superVault)), |
| 734 | "ERC7540-1: convertToAssets(totalSupply) == totalAssets failed" |
|
| 735 | ); |
|
| 736 | return true; |
|
| 737 | } |
|
| 738 | ||
| 739 | /// @dev Property 7540-2: convertToShares(totalAssets) == totalSupply unless price is 0.0 |
|
| 740 | 16× | function invariant_erc7540_2() public returns (bool) { |
| 741 | 12× | actor = _getActor(); |
| 742 | 21× | t( |
| 743 | 8× | erc7540_2(address(superVault)), |
| 744 | "ERC7540-2: convertToShares(totalAssets) == totalSupply failed" |
|
| 745 | ); |
|
| 746 | return true; |
|
| 747 | } |
|
| 748 | ||
| 749 | /// @dev Property 7540-3: max* never reverts |
|
| 750 | 16× | function invariant_erc7540_3() public returns (bool) { |
| 751 | 12× | actor = _getActor(); |
| 752 | 21× | t( |
| 753 | 8× | erc7540_3(address(superVault)), |
| 754 | "ERC7540-3: max* functions should never revert" |
|
| 755 | ); |
|
| 756 | return true; |
|
| 757 | } |
|
| 758 | ||
| 759 | /// @dev Property 7540-4: claiming more than max always reverts |
|
| 760 | 20× | function global_erc7540_4_deposit_ASSERTION_ERC7540_4_DEPOSIT(uint256 amt) public stateless { |
| 761 | 14× | actor = _getActor(); |
| 762 | 4× | t( |
| 763 | 9× | erc7540_4_deposit(address(superVault), amt), |
| 764 | 17× | ASSERTION_ERC7540_4_DEPOSIT |
| 765 | ); |
|
| 766 | } |
|
| 767 | ||
| 768 | 20× | function global_erc7540_4_mint_ASSERTION_ERC7540_4_MINT(uint256 amt) public stateless { |
| 769 | 14× | actor = _getActor(); |
| 770 | 4× | t( |
| 771 | 9× | erc7540_4_mint(address(superVault), amt), |
| 772 | 17× | ASSERTION_ERC7540_4_MINT |
| 773 | ); |
|
| 774 | } |
|
| 775 | ||
| 776 | 20× | function global_erc7540_4_withdraw_ASSERTION_ERC7540_4_WITHDRAW(uint256 amt) public stateless { |
| 777 | 14× | actor = _getActor(); |
| 778 | 4× | t( |
| 779 | 9× | erc7540_4_withdraw(address(superVault), amt), |
| 780 | 17× | ASSERTION_ERC7540_4_WITHDRAW |
| 781 | ); |
|
| 782 | } |
|
| 783 | ||
| 784 | 20× | function global_erc7540_4_redeem_ASSERTION_ERC7540_4_REDEEM(uint256 amt) public stateless { |
| 785 | 14× | actor = _getActor(); |
| 786 | 4× | t( |
| 787 | 9× | erc7540_4_redeem(address(superVault), amt), |
| 788 | 17× | ASSERTION_ERC7540_4_REDEEM |
| 789 | ); |
|
| 790 | } |
|
| 791 | ||
| 792 | /// @dev Property 7540-5: requestRedeem reverts if the share balance is less than amount |
|
| 793 | 20× | function global_erc7540_5_ASSERTION_ERC7540_5(uint256 shares) public stateless { |
| 794 | 14× | actor = _getActor(); |
| 795 | 4× | t( |
| 796 | 10× | erc7540_5(address(superVault), address(superVault), shares), |
| 797 | 17× | ASSERTION_ERC7540_5 |
| 798 | ); |
|
| 799 | } |
|
| 800 | ||
| 801 | /// @dev Property 7540-6: preview* always reverts |
|
| 802 | // NOTE: previewMint and previewDeposit don't revert because deposits are sync |
|
| 803 | // function crytic_erc7540_6() public { |
|
| 804 | // actor = _getActor(); |
|
| 805 | // t( |
|
| 806 | // erc7540_6(address(superVault)), |
|
| 807 | // "ERC7540-6: preview* functions should always revert" |
|
| 808 | // ); |
|
| 809 | // } |
|
| 810 | ||
| 811 | /// @dev Property 7540-7: if max[method] > 0, then [method] (max) should not revert |
|
| 812 | // NOTE: maxDeposit always returns type(uint256).max |
|
| 813 | // function crytic_erc7540_7_deposit(uint256 amt) public { |
|
| 814 | // actor = _getActor(); |
|
| 815 | // t( |
|
| 816 | // erc7540_7_deposit(address(superVault), amt), |
|
| 817 | // "ERC7540-7: deposit should not revert when amount <= max" |
|
| 818 | // ); |
|
| 819 | // } |
|
| 820 | ||
| 821 | // NOTE: maxMint always returns type(uint256).max |
|
| 822 | // function crytic_erc7540_7_mint(uint256 amt) public stateless { |
|
| 823 | // actor = _getActor(); |
|
| 824 | // t( |
|
| 825 | // erc7540_7_mint(address(superVault), amt), |
|
| 826 | // "ERC7540-7: mint should not revert when amount <= max" |
|
| 827 | // ); |
|
| 828 | // } |
|
| 829 | ||
| 830 | 20× | function global_erc7540_7_withdraw_ASSERTION_ERC7540_7_WITHDRAW(uint256 amt) public stateless { |
| 831 | 14× | actor = _getActor(); |
| 832 | 4× | t( |
| 833 | 9× | erc7540_7_withdraw(address(superVault), amt), |
| 834 | 17× | ASSERTION_ERC7540_7_WITHDRAW |
| 835 | ); |
|
| 836 | } |
|
| 837 | ||
| 838 | // NOTE: this implements the check from ERC7540Properties directly because SuperVault logic implementation allows amt to be nonzero but round down to 0 when assets passed into redeem are calculated |
|
| 839 | 23× | function global_erc7540_7_redeem_ASSERTION_ERC7540_7_REDEEM(uint256 amt) public stateless { |
| 840 | 20× | actor = _getActor(); |
| 841 | ||
| 842 | 68× | uint256 maxRedeem = superVault.maxRedeem(actor); |
| 843 | 9× | amt = between(amt, 0, maxRedeem); |
| 844 | ||
| 845 | 5× | if (amt == 0) { |
| 846 | 3× | return; // Skip |
| 847 | } |
|
| 848 | ||
| 849 | 0 | uint256 averageWithdrawPrice = superVaultStrategy |
| 850 | 0 | .getAverageWithdrawPrice(actor); |
| 851 | ||
| 852 | // calculates assets in the same way as redeem to confirm that a nonzero amount is being requested |
|
| 853 | 0 | uint256 assets = amt.mulDiv( |
| 854 | 0 | averageWithdrawPrice, |
| 855 | 0 | superVault.PRECISION(), |
| 856 | 0 | Math.Rounding.Floor |
| 857 | ); |
|
| 858 | ||
| 859 | 3× | try superVault.redeem(amt, actor, actor) {} catch { |
| 860 | 0 | if (amt > 0 && assets > 0) { |
| 861 | 0 | t( |
| 862 | 0 | false, |
| 863 | 0 | ASSERTION_ERC7540_7_REDEEM |
| 864 | ); |
|
| 865 | } |
|
| 866 | } |
|
| 867 | } |
|
| 868 | } |
99%
test/recon/Setup.sol
Lines covered: 158 / 159 (99%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // External dependencies |
|
| 5 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {ActorManager} from "@recon/ActorManager.sol"; |
|
| 8 | import {AssetManager} from "@recon/AssetManager.sol"; |
|
| 9 | import {Utils} from "@recon/Utils.sol"; |
|
| 10 | // ERC4626 Hooks |
|
| 11 | import {Deposit4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol"; |
|
| 12 | import {ApproveAndDeposit4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol"; |
|
| 13 | import {Redeem4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol"; |
|
| 14 | ||
| 15 | // ERC5115 Hooks |
|
| 16 | import {Deposit5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol"; |
|
| 17 | import {ApproveAndDeposit5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol"; |
|
| 18 | import {Redeem5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol"; |
|
| 19 | ||
| 20 | // ERC7540 Hooks |
|
| 21 | import {Deposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol"; |
|
| 22 | import {Redeem7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol"; |
|
| 23 | import {RequestDeposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol"; |
|
| 24 | import {RequestRedeem7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol"; |
|
| 25 | import {ApproveAndRequestDeposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol"; |
|
| 26 | import {CancelDepositRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol"; |
|
| 27 | import {CancelRedeemRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol"; |
|
| 28 | import {ClaimCancelDepositRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol"; |
|
| 29 | import {ClaimCancelRedeemRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol"; |
|
| 30 | import {Withdraw7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol"; |
|
| 31 | ||
| 32 | // Super Vault Hooks |
|
| 33 | import {CancelRedeemHook} from "lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol"; |
|
| 34 | import {Withdraw7540VaultHook as SuperVaultWithdraw7540VaultHook} from "lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol"; |
|
| 35 | ||
| 36 | // Source dependencies |
|
| 37 | import "src/SuperVault/SuperVault.sol"; |
|
| 38 | import "src/SuperVault/SuperVaultAggregator.sol"; |
|
| 39 | import "src/SuperVault/SuperVaultEscrow.sol"; |
|
| 40 | import "src/SuperVault/SuperVaultStrategy.sol"; |
|
| 41 | import "src/SuperGovernor.sol"; |
|
| 42 | import {ISuperVaultAggregator} from "src/interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 43 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 44 | import {MockYieldSourceOracle} from "test/mocks/MockYieldSourceOracle.sol"; |
|
| 45 | ||
| 46 | // Test suite dependencies |
|
| 47 | import {YieldManager, YieldSourceType} from "test/recon/managers/YieldManager.sol"; |
|
| 48 | import {MerkleTestHelper} from "test/recon/helpers/MerkleTestHelper.sol"; |
|
| 49 | import {UnsafeSuperVaultAggregator} from "test/recon/helpers/UnsafeSuperVaultAggregator.sol"; |
|
| 50 | import {MockERC4626YieldSourceOracle} from "test/recon/mocks/MockERC4626YieldSourceOracle.sol"; |
|
| 51 | import {MockERC5115YieldSourceOracle} from "test/recon/mocks/MockERC5115YieldSourceOracle.sol"; |
|
| 52 | import {MockERC7540YieldSourceOracle} from "test/recon/mocks/MockERC7540YieldSourceOracle.sol"; |
|
| 53 | import {MockECDSAPPSOracle} from "test/recon/mocks/MockECDSAPPSOracle.sol"; |
|
| 54 | ||
| 55 | abstract contract Setup is |
|
| 56 | BaseSetup, |
|
| 57 | ActorManager, |
|
| 58 | AssetManager, |
|
| 59 | YieldManager, |
|
| 60 | Utils |
|
| 61 | { |
|
| 62 | // Configuration constants |
|
| 63 | 2× | uint8 internal constant DECIMALS = 18; |
| 64 | address asset; |
|
| 65 | 9× | address feeRecipient = address(0xbeef); |
| 66 | ||
| 67 | // Core contracts |
|
| 68 | SuperGovernor superGovernor; |
|
| 69 | SuperVault superVault; |
|
| 70 | UnsafeSuperVaultAggregator superVaultAggregator; |
|
| 71 | SuperVaultEscrow superVaultEscrow; |
|
| 72 | SuperVaultStrategy superVaultStrategy; |
|
| 73 | ||
| 74 | // Implementation contracts for aggregator |
|
| 75 | SuperVault vaultImpl; |
|
| 76 | SuperVaultStrategy strategyImpl; |
|
| 77 | SuperVaultEscrow escrowImpl; |
|
| 78 | ||
| 79 | // Helpers |
|
| 80 | MerkleTestHelper merkleHelper; |
|
| 81 | ||
| 82 | // ERC4626 Hooks |
|
| 83 | ApproveAndDeposit4626VaultHook approveAndDeposit4626Hook; |
|
| 84 | Deposit4626VaultHook deposit4626Hook; |
|
| 85 | Redeem4626VaultHook redeem4626Hook; |
|
| 86 | ||
| 87 | // ERC5115 Hooks |
|
| 88 | ApproveAndDeposit5115VaultHook approveAndDeposit5115Hook; |
|
| 89 | Deposit5115VaultHook deposit5115Hook; |
|
| 90 | Redeem5115VaultHook redeem5115Hook; |
|
| 91 | ||
| 92 | // ERC7540 Hooks |
|
| 93 | Deposit7540VaultHook deposit7540Hook; |
|
| 94 | Redeem7540VaultHook redeem7540Hook; |
|
| 95 | RequestDeposit7540VaultHook requestDeposit7540Hook; |
|
| 96 | RequestRedeem7540VaultHook requestRedeem7540Hook; |
|
| 97 | ApproveAndRequestDeposit7540VaultHook approveAndRequestDeposit7540Hook; |
|
| 98 | CancelDepositRequest7540Hook cancelDepositRequest7540Hook; |
|
| 99 | CancelRedeemRequest7540Hook cancelRedeemRequest7540Hook; |
|
| 100 | ClaimCancelDepositRequest7540Hook claimCancelDepositRequest7540Hook; |
|
| 101 | ClaimCancelRedeemRequest7540Hook claimCancelRedeemRequest7540Hook; |
|
| 102 | Withdraw7540VaultHook withdraw7540Hook; |
|
| 103 | ||
| 104 | // Super Vault Hooks |
|
| 105 | CancelRedeemHook cancelRedeemHook; |
|
| 106 | SuperVaultWithdraw7540VaultHook superVaultWithdraw7540Hook; |
|
| 107 | ||
| 108 | // Mocks |
|
| 109 | MockERC4626YieldSourceOracle erc4626YieldSourceOracle; |
|
| 110 | MockERC5115YieldSourceOracle erc5115YieldSourceOracle; |
|
| 111 | MockERC7540YieldSourceOracle erc7540YieldSourceOracle; |
|
| 112 | MockECDSAPPSOracle ECDSAPPSOracle; |
|
| 113 | ||
| 114 | // Yield sources for different standards |
|
| 115 | address erc4626YieldSource; |
|
| 116 | address erc5115YieldSource; |
|
| 117 | address erc7540YieldSource; |
|
| 118 | ||
| 119 | // Ghosts |
|
| 120 | bool hasUpdatedPPS; |
|
| 121 | int256 burnedMoreThanRequested; |
|
| 122 | int256 burnedLessThanRequested; |
|
| 123 | int256 previewMintSharesGreater; // setPreviewSharesGreater |
|
| 124 | int256 previewDepositSharesGreater; // setPreviewSharesGreater |
|
| 125 | int256 previewMintAssetsGreater; // setpreviewAssetsGreater |
|
| 126 | int256 previewDepositAssetsGreater; // setpreviewAssetsGreater |
|
| 127 | ||
| 128 | // Canaries |
|
| 129 | bool executeHooksClampedSuccess; |
|
| 130 | bool executeHooksSuccess; |
|
| 131 | bool fulfillRedeemRequestsSuccess; |
|
| 132 | bool hasDeployedNewVault; |
|
| 133 | ||
| 134 | /// === MODIFIERS === /// |
|
| 135 | /// Prank admin and actor |
|
| 136 | ||
| 137 | modifier asAdmin() { |
|
| 138 | 632× | vm.prank(address(this)); |
| 139 | _; |
|
| 140 | } |
|
| 141 | ||
| 142 | modifier asActor() { |
|
| 143 | 2519× | vm.prank(address(_getActor())); |
| 144 | _; |
|
| 145 | } |
|
| 146 | ||
| 147 | /// Makes a handler have no side effects |
|
| 148 | /// The fuzzer will call this anyway, and because it reverts it will be removed from shrinking |
|
| 149 | /// Replace the "withGhosts" with "stateless" to make the code clean |
|
| 150 | /// |
|
| 151 | /// Under Foundry with `assertions_revert = false` a failing t()/vm.assert* only records a |
|
| 152 | /// journaled write to the global fail slot, which the trailing revert would erase — making |
|
| 153 | /// every stateless assertion property invisible to the Foundry leg. Re-raise a failure |
|
| 154 | /// recorded during this call as Panic(0x01), which Foundry classifies as an assertion |
|
| 155 | /// failure; under echidna/medusa/recon a failing t() already reverted with Panic before |
|
| 156 | /// reaching this point, so the extra check never fires there. |
|
| 157 | modifier stateless() { |
|
| 158 | 117× | bool preFailed = _foundryFailSlotSet(); |
| 159 | _; |
|
| 160 | 55× | if (!preFailed && _foundryFailSlotSet()) assert(false); |
| 161 | 15× | revert("stateless"); |
| 162 | } |
|
| 163 | ||
| 164 | // bytes32("failed") — Foundry's GLOBAL_FAIL_SLOT on the cheatcode address, written |
|
| 165 | // (journaled sstore of 1) by non-reverting vm.assert* failures. |
|
| 166 | bytes32 internal constant _FOUNDRY_FAIL_SLOT = |
|
| 167 | 0x6661696c65640000000000000000000000000000000000000000000000000000; |
|
| 168 | ||
| 169 | /// Probes the Foundry fail slot with a raw staticcall so fuzzers without the `load` |
|
| 170 | /// cheatcode simply report "not set" instead of reverting. |
|
| 171 | 8× | function _foundryFailSlotSet() internal view returns (bool) { |
| 172 | 79× | (bool ok, bytes memory ret) = address(vm).staticcall( |
| 173 | 70× | abi.encodeWithSignature("load(address,bytes32)", address(vm), _FOUNDRY_FAIL_SLOT) |
| 174 | ); |
|
| 175 | 47× | return ok && ret.length >= 32 && abi.decode(ret, (bytes32)) != bytes32(0); |
| 176 | } |
|
| 177 | ||
| 178 | /// === Setup === /// |
|
| 179 | /// This contains all calls to be performed in the tester constructor, both for Echidna and Foundry |
|
| 180 | 8× | function setup() internal virtual override { |
| 181 | // 1. Add additional actors |
|
| 182 | 5× | _addActor(address(0x100)); // Actor 1 |
| 183 | 5× | _addActor(address(0x200)); // Actor 2 |
| 184 | ||
| 185 | // 2. Create assets using AssetManager |
|
| 186 | 4× | _newAsset(DECIMALS); // Deploy token with 18 decimals |
| 187 | 4× | _newAsset(DECIMALS); // UP token |
| 188 | ||
| 189 | 5× | _switchAsset(0); |
| 190 | ||
| 191 | // 3. Deploy all three types of yield sources using YieldManager |
|
| 192 | // Deploy ERC4626 yield source (default) |
|
| 193 | 18× | erc4626YieldSource = _newYieldSource( |
| 194 | 4× | _getAsset(), |
| 195 | 1× | YieldSourceType.ERC4626 |
| 196 | ); |
|
| 197 | ||
| 198 | // Deploy ERC5115 yield source |
|
| 199 | 18× | erc5115YieldSource = _newYieldSource( |
| 200 | 4× | _getAsset(), |
| 201 | 1× | YieldSourceType.ERC5115 |
| 202 | ); |
|
| 203 | ||
| 204 | // Deploy ERC7540 yield source |
|
| 205 | 18× | erc7540YieldSource = _newYieldSource( |
| 206 | 4× | _getAsset(), |
| 207 | 1× | YieldSourceType.ERC7540 |
| 208 | ); |
|
| 209 | ||
| 210 | // Set ERC4626 as the default active yield source |
|
| 211 | 4× | _switchYieldSource(0); // Switch to first yield source in the array (ERC4626) |
| 212 | ||
| 213 | // 4. Deploy SuperGovernor first (required by other contracts) |
|
| 214 | 35× | superGovernor = new SuperGovernor( |
| 215 | 1× | address(this), // superGovernor role |
| 216 | 1× | address(this), // governor role |
| 217 | 1× | address(this), // bankManager role |
| 218 | 1× | address(this), // gasManager role |
| 219 | 10× | feeRecipient, // treasury |
| 220 | 1× | address(this) // prover |
| 221 | ); |
|
| 222 | ||
| 223 | // 5. Deploy implementation contracts for the aggregator |
|
| 224 | 32× | vaultImpl = new SuperVault(address(superGovernor)); |
| 225 | 41× | strategyImpl = new SuperVaultStrategy(address(superGovernor)); |
| 226 | 35× | escrowImpl = new SuperVaultEscrow(); |
| 227 | ||
| 228 | // 6. Deploy SuperVaultAggregator with implementation contracts |
|
| 229 | 32× | superVaultAggregator = new UnsafeSuperVaultAggregator( |
| 230 | 6× | address(superGovernor), |
| 231 | 6× | address(vaultImpl), |
| 232 | 5× | address(strategyImpl), |
| 233 | address(escrowImpl) |
|
| 234 | ); |
|
| 235 | ||
| 236 | // 7. Register the SuperVaultAggregator and UpToken address with SuperGovernor |
|
| 237 | 54× | superGovernor.setAddress( |
| 238 | 59× | superGovernor.SUPER_VAULT_AGGREGATOR(), |
| 239 | 3× | address(superVaultAggregator) |
| 240 | ); |
|
| 241 | ||
| 242 | 11× | address[] memory assets = _getAssets(); |
| 243 | 128× | superGovernor.setAddress(superGovernor.UP(), assets[1]); // the second deployed token in the AssetManager is the UPToken |
| 244 | 114× | superGovernor.setAddress(superGovernor.SUPER_BANK(), address(this)); |
| 245 | ||
| 246 | // 8. Deploy Mocks and Oracles |
|
| 247 | ||
| 248 | // Deploy specific oracles for each yield source type |
|
| 249 | 36× | erc4626YieldSourceOracle = new MockERC4626YieldSourceOracle(); |
| 250 | 36× | erc5115YieldSourceOracle = new MockERC5115YieldSourceOracle(); |
| 251 | 36× | erc7540YieldSourceOracle = new MockERC7540YieldSourceOracle(); |
| 252 | 35× | ECDSAPPSOracle = new MockECDSAPPSOracle(); |
| 253 | ||
| 254 | // ECDSAPPSOracle setup |
|
| 255 | 41× | superGovernor.setActivePPSOracle(address(ECDSAPPSOracle)); |
| 256 | 49× | ECDSAPPSOracle.setSUPER_GOVERNORReturn(address(superVaultAggregator)); |
| 257 | ||
| 258 | // Set valid assets for all oracles |
|
| 259 | 21× | asset = _getAsset(); |
| 260 | 41× | erc4626YieldSourceOracle.setValidAsset(asset, true); |
| 261 | 46× | erc5115YieldSourceOracle.setValidAsset(asset, true); |
| 262 | 50× | erc7540YieldSourceOracle.setValidAsset(asset, true); |
| 263 | ||
| 264 | // 9. Create a vault trio using the aggregator |
|
| 265 | 5× | ISuperVaultAggregator.VaultCreationParams |
| 266 | 85× | memory params = ISuperVaultAggregator.VaultCreationParams({ |
| 267 | 8× | asset: _getAsset(), // Use the token created by AssetManager |
| 268 | name: "SuperVault", |
|
| 269 | symbol: "SV", |
|
| 270 | 1× | mainManager: address(this), // CONFIGURABLE: This parameter can be modified via target functions |
| 271 | 27× | secondaryManagers: new address[](0), // CONFIGURABLE: This parameter can be modified via target functions |
| 272 | 1× | minUpdateInterval: 5, // CONFIGURABLE: This parameter can be modified via target functions |
| 273 | 1× | maxStaleness: 300, // CONFIGURABLE: This parameter can be modified via target functions |
| 274 | 20× | feeConfig: ISuperVaultStrategy.FeeConfig({ |
| 275 | 1× | performanceFeeBps: 1000, // 10% performance fee |
| 276 | 1× | managementFeeBps: 100, // 1% management fee |
| 277 | 5× | recipient: feeRecipient |
| 278 | }) |
|
| 279 | }); |
|
| 280 | ||
| 281 | 5× | ( |
| 282 | address vaultAddr, |
|
| 283 | address strategyAddr, |
|
| 284 | address escrowAddr |
|
| 285 | 62× | ) = superVaultAggregator.createVault(params); |
| 286 | ||
| 287 | // 10. Store the deployed contracts |
|
| 288 | 13× | superVault = SuperVault(vaultAddr); |
| 289 | 12× | superVaultStrategy = SuperVaultStrategy(payable(strategyAddr)); |
| 290 | 16× | superVaultEscrow = SuperVaultEscrow(escrowAddr); |
| 291 | ||
| 292 | /// 11. Deploy all hook contracts and helper |
|
| 293 | 36× | merkleHelper = new MerkleTestHelper(); |
| 294 | ||
| 295 | // Deploy ERC4626 Hooks |
|
| 296 | 36× | approveAndDeposit4626Hook = new ApproveAndDeposit4626VaultHook(); |
| 297 | 36× | deposit4626Hook = new Deposit4626VaultHook(); |
| 298 | 36× | redeem4626Hook = new Redeem4626VaultHook(); |
| 299 | ||
| 300 | // Deploy ERC5115 Hooks |
|
| 301 | 36× | approveAndDeposit5115Hook = new ApproveAndDeposit5115VaultHook(); |
| 302 | 36× | deposit5115Hook = new Deposit5115VaultHook(); |
| 303 | 36× | redeem5115Hook = new Redeem5115VaultHook(); |
| 304 | ||
| 305 | // Deploy ERC7540 Hooks |
|
| 306 | 36× | deposit7540Hook = new Deposit7540VaultHook(); |
| 307 | 36× | redeem7540Hook = new Redeem7540VaultHook(); |
| 308 | 36× | requestDeposit7540Hook = new RequestDeposit7540VaultHook(); |
| 309 | 36× | requestRedeem7540Hook = new RequestRedeem7540VaultHook(); |
| 310 | 36× | approveAndRequestDeposit7540Hook = new ApproveAndRequestDeposit7540VaultHook(); |
| 311 | 36× | cancelDepositRequest7540Hook = new CancelDepositRequest7540Hook(); |
| 312 | 36× | cancelRedeemRequest7540Hook = new CancelRedeemRequest7540Hook(); |
| 313 | 36× | claimCancelDepositRequest7540Hook = new ClaimCancelDepositRequest7540Hook(); |
| 314 | 36× | claimCancelRedeemRequest7540Hook = new ClaimCancelRedeemRequest7540Hook(); |
| 315 | 36× | withdraw7540Hook = new Withdraw7540VaultHook(); |
| 316 | ||
| 317 | // Deploy Super Vault Hooks |
|
| 318 | 36× | cancelRedeemHook = new CancelRedeemHook(); |
| 319 | 32× | superVaultWithdraw7540Hook = new SuperVaultWithdraw7540VaultHook(); |
| 320 | ||
| 321 | // Register all hooks with SuperGovernor |
|
| 322 | // ERC4626 Hooks (deposit hooks are regular hooks, redeem hooks are fulfill request hooks) |
|
| 323 | 45× | superGovernor.registerHook(address(approveAndDeposit4626Hook), false); |
| 324 | 46× | superGovernor.registerHook(address(deposit4626Hook), false); |
| 325 | 46× | superGovernor.registerHook(address(redeem4626Hook), true); // fulfill request hook |
| 326 | ||
| 327 | // ERC5115 Hooks |
|
| 328 | 46× | superGovernor.registerHook(address(approveAndDeposit5115Hook), false); |
| 329 | 46× | superGovernor.registerHook(address(deposit5115Hook), false); |
| 330 | 46× | superGovernor.registerHook(address(redeem5115Hook), true); // fulfill request hook |
| 331 | ||
| 332 | // ERC7540 Hooks (deposit/request hooks are regular hooks, redeem/withdraw hooks are fulfill request hooks) |
|
| 333 | 46× | superGovernor.registerHook(address(deposit7540Hook), false); |
| 334 | 46× | superGovernor.registerHook(address(redeem7540Hook), true); // fulfill request hook |
| 335 | 46× | superGovernor.registerHook(address(requestDeposit7540Hook), false); |
| 336 | 46× | superGovernor.registerHook(address(requestRedeem7540Hook), false); |
| 337 | 41× | superGovernor.registerHook( |
| 338 | 5× | address(approveAndRequestDeposit7540Hook), |
| 339 | false |
|
| 340 | ); |
|
| 341 | 41× | superGovernor.registerHook( |
| 342 | 5× | address(cancelDepositRequest7540Hook), |
| 343 | false |
|
| 344 | ); |
|
| 345 | 46× | superGovernor.registerHook(address(cancelRedeemRequest7540Hook), false); |
| 346 | 41× | superGovernor.registerHook( |
| 347 | 5× | address(claimCancelDepositRequest7540Hook), |
| 348 | false |
|
| 349 | ); |
|
| 350 | 41× | superGovernor.registerHook( |
| 351 | 5× | address(claimCancelRedeemRequest7540Hook), |
| 352 | false |
|
| 353 | ); |
|
| 354 | 46× | superGovernor.registerHook(address(withdraw7540Hook), true); // fulfill request hook |
| 355 | ||
| 356 | // Super Vault Hooks |
|
| 357 | 48× | superGovernor.registerHook(address(cancelRedeemHook), false); |
| 358 | 46× | superGovernor.registerHook(address(superVaultWithdraw7540Hook), true); // fulfill request hook |
| 359 | ||
| 360 | // 12. Set up approval array for contracts that need token access |
|
| 361 | 44× | address[] memory approvalArray = new address[](6); |
| 362 | 24× | approvalArray[0] = address(superVault); |
| 363 | 29× | approvalArray[1] = address(superVaultStrategy); |
| 364 | 29× | approvalArray[2] = address(superVaultAggregator); |
| 365 | 29× | approvalArray[3] = erc4626YieldSource; |
| 366 | 29× | approvalArray[4] = erc5115YieldSource; |
| 367 | 31× | approvalArray[5] = erc7540YieldSource; |
| 368 | ||
| 369 | // 13. Finalize asset deployment (mints to actors and sets approvals) |
|
| 370 | 9× | _finalizeAssetDeployment(_getActors(), approvalArray, type(uint88).max); |
| 371 | } |
|
| 372 | ||
| 373 | /// Get hook addresses for different yield source types |
|
| 374 | ||
| 375 | 4× | function _getApproveAndDepositHookForType( |
| 376 | YieldSourceType sourceType |
|
| 377 | 2× | ) internal view returns (address) { |
| 378 | 12× | if (sourceType == YieldSourceType.ERC4626) { |
| 379 | 4× | return address(approveAndDeposit4626Hook); |
| 380 | 13× | } else if (sourceType == YieldSourceType.ERC5115) { |
| 381 | 4× | return address(approveAndDeposit5115Hook); |
| 382 | 12× | } else if (sourceType == YieldSourceType.ERC7540) { |
| 383 | 4× | return address(approveAndRequestDeposit7540Hook); |
| 384 | } |
|
| 385 | return address(0); |
|
| 386 | } |
|
| 387 | ||
| 388 | 8× | function _getRedeemHookForType( |
| 389 | YieldSourceType sourceType |
|
| 390 | 4× | ) internal view returns (address) { |
| 391 | 24× | if (sourceType == YieldSourceType.ERC4626) { |
| 392 | 8× | return address(redeem4626Hook); |
| 393 | 26× | } else if (sourceType == YieldSourceType.ERC5115) { |
| 394 | 8× | return address(redeem5115Hook); |
| 395 | 24× | } else if (sourceType == YieldSourceType.ERC7540) { |
| 396 | 8× | return address(redeem7540Hook); |
| 397 | } |
|
| 398 | 0 | return address(0); |
| 399 | } |
|
| 400 | ||
| 401 | /// Get oracle addresses for different yield source types |
|
| 402 | ||
| 403 | 8× | function _getYieldSourceOracleForType( |
| 404 | YieldSourceType sourceType |
|
| 405 | 2× | ) internal view returns (address) { |
| 406 | 12× | if (sourceType == YieldSourceType.ERC4626) { |
| 407 | 4× | return address(erc4626YieldSourceOracle); |
| 408 | 13× | } else if (sourceType == YieldSourceType.ERC5115) { |
| 409 | 4× | return address(erc5115YieldSourceOracle); |
| 410 | 13× | } else if (sourceType == YieldSourceType.ERC7540) { |
| 411 | 4× | return address(erc7540YieldSourceOracle); |
| 412 | } |
|
| 413 | } |
|
| 414 | ||
| 415 | /// @dev Helper function to determine yield source type from address |
|
| 416 | 18× | function _getYieldSourceTypeFromAddress( |
| 417 | address yieldSource |
|
| 418 | 2× | ) internal view returns (YieldSourceType) { |
| 419 | // Get all available yield sources from YieldManager |
|
| 420 | 12× | address[] memory yieldSources = _getYieldSources(); |
| 421 | ||
| 422 | // Check which index matches the current yield source |
|
| 423 | 28× | for (uint256 i = 0; i < yieldSources.length; i++) { |
| 424 | 42× | if (yieldSources[i] == yieldSource) { |
| 425 | // Return the corresponding type based on creation order: |
|
| 426 | // Index 0: ERC4626, Index 1: ERC5115, Index 2: ERC7540 |
|
| 427 | 16× | if (i == 0) return YieldSourceType.ERC4626; |
| 428 | 16× | if (i == 1) return YieldSourceType.ERC5115; |
| 429 | 16× | if (i == 2) return YieldSourceType.ERC7540; |
| 430 | } |
|
| 431 | } |
|
| 432 | ||
| 433 | // Default to ERC4626 if not found |
|
| 434 | 4× | return YieldSourceType.ERC4626; |
| 435 | } |
|
| 436 | ||
| 437 | function _getERC4626YieldSourceOracle() internal view returns (address) { |
|
| 438 | return address(erc4626YieldSourceOracle); |
|
| 439 | } |
|
| 440 | ||
| 441 | function _getERC5115YieldSourceOracle() internal view returns (address) { |
|
| 442 | return address(erc5115YieldSourceOracle); |
|
| 443 | } |
|
| 444 | ||
| 445 | function _getERC7540YieldSourceOracle() internal view returns (address) { |
|
| 446 | return address(erc7540YieldSourceOracle); |
|
| 447 | } |
|
| 448 | ||
| 449 | // Helpers |
|
| 450 | 35× | function _getRandomActor(uint256 entropy) public view returns (address) { |
| 451 | 14× | address[] memory actors = _getActors(); |
| 452 | ||
| 453 | 52× | return actors[entropy % actors.length]; |
| 454 | } |
|
| 455 | } |
73%
test/recon/helpers/MerkleTestHelper.sol
Lines covered: 25 / 34 (73%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; |
|
| 5 | ||
| 6 | /// @title MerkleTestHelper |
|
| 7 | /// @notice Helper contract for generating test Merkle roots and proofs for hook validation |
|
| 8 | 123× | contract MerkleTestHelper { |
| 9 | /// @notice Generate a test Merkle root for ERC4626 deposit and redeem hooks |
|
| 10 | /// @param depositHook Address of the Deposit4626VaultHook or ApproveAndDeposit4626VaultHook contract |
|
| 11 | /// @param redeemHook Address of the Redeem4626VaultHook contract |
|
| 12 | /// @param mockVault Address of the mock ERC4626 vault |
|
| 13 | /// @param mockToken Address of the mock token (unused but kept for compatibility) |
|
| 14 | /// @return root The Merkle root for the tree |
|
| 15 | /// @return proofs Array of proofs for each leaf [depositProof, redeemProof] |
|
| 16 | 28× | function generateTestHooksRoot( |
| 17 | address depositHook, |
|
| 18 | address redeemHook, |
|
| 19 | address mockVault, |
|
| 20 | address mockToken |
|
| 21 | 6× | ) public pure returns (bytes32 root, bytes32[][] memory proofs) { |
| 22 | // Create leaves for the two hooks following the exact format from SuperVaultAggregator._createLeaf() |
|
| 23 | 25× | bytes32[] memory leaves = new bytes32[](2); |
| 24 | ||
| 25 | // Leaf 1: Deposit hook with yield source only (what inspect() returns) |
|
| 26 | // The inspect() function only returns abi.encodePacked(yieldSource) |
|
| 27 | 21× | bytes memory depositArgs = abi.encodePacked(mockVault); // Only yield source address |
| 28 | 68× | leaves[0] = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs)))); |
| 29 | ||
| 30 | // Leaf 2: Redeem hook with yield source only (what inspect() returns) |
|
| 31 | // The inspect() function only returns abi.encodePacked(yieldSource) |
|
| 32 | 24× | bytes memory redeemArgs = abi.encodePacked(mockVault); // Only yield source address |
| 33 | 72× | leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
| 34 | ||
| 35 | // Sort leaves to match standard Merkle tree ordering |
|
| 36 | 35× | if (leaves[0] > leaves[1]) { |
| 37 | 67× | (leaves[0], leaves[1]) = (leaves[1], leaves[0]); |
| 38 | } |
|
| 39 | ||
| 40 | // For a 2-leaf tree, root is hash of the sorted leaves |
|
| 41 | 59× | root = keccak256(abi.encodePacked(leaves[0], leaves[1])); |
| 42 | ||
| 43 | // Store original leaf hashes for proof assignment |
|
| 44 | 56× | bytes32 depositLeaf = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs)))); |
| 45 | 56× | bytes32 redeemLeaf = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
| 46 | ||
| 47 | // Generate proofs for each leaf |
|
| 48 | 29× | proofs = new bytes32[][](2); |
| 49 | 44× | proofs[0] = new bytes32[](1); // Proof for deposit hook |
| 50 | 45× | proofs[1] = new bytes32[](1); // Proof for redeem hook |
| 51 | ||
| 52 | // In a 2-leaf tree, each leaf's proof is just its sibling |
|
| 53 | // Find which position each leaf ended up in after sorting |
|
| 54 | 23× | if (leaves[0] == depositLeaf) { |
| 55 | // depositHook is first leaf after sorting |
|
| 56 | 48× | proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
| 57 | 48× | proofs[1][0] = leaves[0]; // Sibling of redeem leaf |
| 58 | } else { |
|
| 59 | // redeemHook is first leaf after sorting |
|
| 60 | 48× | proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
| 61 | 48× | proofs[1][0] = leaves[0]; // Sibling of redeem leaf |
| 62 | } |
|
| 63 | ||
| 64 | 5× | return (root, proofs); |
| 65 | } |
|
| 66 | ||
| 67 | /// @notice Generate encoded hook arguments for Deposit4626VaultHook |
|
| 68 | /// @param yieldSource Address of the yield source vault |
|
| 69 | /// @param amount Amount to deposit |
|
| 70 | /// @param usePrevHookAmount Whether to use previous hook amount |
|
| 71 | /// @return Encoded hook arguments |
|
| 72 | 0 | function encodeDepositHookArgs( |
| 73 | address yieldSource, |
|
| 74 | uint256 amount, |
|
| 75 | bool usePrevHookAmount |
|
| 76 | 0 | ) public pure returns (bytes memory) { |
| 77 | 0 | return abi.encodePacked( |
| 78 | 0 | bytes32(0), // yieldSourceOracleId placeholder |
| 79 | yieldSource, |
|
| 80 | amount, |
|
| 81 | usePrevHookAmount |
|
| 82 | ); |
|
| 83 | } |
|
| 84 | ||
| 85 | /// @notice Generate encoded hook arguments for Redeem4626VaultHook |
|
| 86 | /// @param yieldSource Address of the yield source vault |
|
| 87 | /// @param owner Address of the owner |
|
| 88 | /// @param shares Number of shares to redeem |
|
| 89 | /// @param usePrevHookAmount Whether to use previous hook amount |
|
| 90 | /// @return Encoded hook arguments |
|
| 91 | 8× | function encodeRedeemHookArgs( |
| 92 | address yieldSource, |
|
| 93 | address owner, |
|
| 94 | uint256 shares, |
|
| 95 | bool usePrevHookAmount |
|
| 96 | 0 | ) public pure returns (bytes memory) { |
| 97 | 0 | return abi.encodePacked( |
| 98 | 0 | bytes32(0), // yieldSourceOracleId placeholder |
| 99 | yieldSource, |
|
| 100 | owner, |
|
| 101 | shares, |
|
| 102 | usePrevHookAmount |
|
| 103 | ); |
|
| 104 | } |
|
| 105 | ||
| 106 | /// @notice Generate encoded hook arguments for ApproveAndDeposit4626VaultHook |
|
| 107 | /// @param yieldSource Address of the yield source vault |
|
| 108 | /// @param token Address of the token to approve and deposit |
|
| 109 | /// @param amount Amount to deposit |
|
| 110 | /// @param usePrevHookAmount Whether to use previous hook amount |
|
| 111 | /// @return Encoded hook arguments |
|
| 112 | function encodeApproveAndDepositHookArgs( |
|
| 113 | address yieldSource, |
|
| 114 | address token, |
|
| 115 | uint256 amount, |
|
| 116 | bool usePrevHookAmount |
|
| 117 | ) public pure returns (bytes memory) { |
|
| 118 | return abi.encodePacked( |
|
| 119 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 120 | yieldSource, |
|
| 121 | token, |
|
| 122 | amount, |
|
| 123 | usePrevHookAmount |
|
| 124 | ); |
|
| 125 | } |
|
| 126 | ||
| 127 | /// @notice Create a leaf hash for a specific hook and arguments |
|
| 128 | /// @param hookAddress Address of the hook contract |
|
| 129 | /// @param hookArgs Encoded hook arguments |
|
| 130 | /// @return leaf The leaf hash |
|
| 131 | 0 | function createLeaf(address hookAddress, bytes memory hookArgs) public pure returns (bytes32 leaf) { |
| 132 | 0 | return keccak256(bytes.concat(keccak256(abi.encode(hookAddress, hookArgs)))); |
| 133 | } |
|
| 134 | ||
| 135 | /// @notice Verify a Merkle proof against a root |
|
| 136 | /// @param proof Array of proof elements |
|
| 137 | /// @param root Merkle root |
|
| 138 | /// @param leaf Leaf to verify |
|
| 139 | /// @return True if proof is valid |
|
| 140 | 2× | function verifyProof(bytes32[] memory proof, bytes32 root, bytes32 leaf) public pure returns (bool) { |
| 141 | 2× | return MerkleProof.verify(proof, root, leaf); |
| 142 | } |
|
| 143 | } |
100%
test/recon/helpers/UnsafeSuperVaultAggregator.sol
Lines covered: 8 / 8 (100%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity 0.8.30; |
|
| 3 | ||
| 4 | import {SuperVaultAggregator} from "src/SuperVault/SuperVaultAggregator.sol"; |
|
| 5 | ||
| 6 | /// @dev inherits from aggregator to override hook validation |
|
| 7 | 1173× | contract UnsafeSuperVaultAggregator is SuperVaultAggregator { |
| 8 | 27× | constructor( |
| 9 | address superGovernor_, |
|
| 10 | address vaultImpl_, |
|
| 11 | address strategyImpl_, |
|
| 12 | address escrowImpl_ |
|
| 13 | ) |
|
| 14 | SuperVaultAggregator( |
|
| 15 | 1× | superGovernor_, |
| 16 | 1× | vaultImpl_, |
| 17 | 1× | strategyImpl_, |
| 18 | 1× | escrowImpl_ |
| 19 | ) |
|
| 20 | {} |
|
| 21 | ||
| 22 | 26× | function validateHook( |
| 23 | address strategy, |
|
| 24 | ValidateHookArgs calldata args |
|
| 25 | ) external view override returns (bool isValid) { |
|
| 26 | 2× | return true; |
| 27 | } |
|
| 28 | } |
89%
test/recon/managers/YieldManager.sol
Lines covered: 26 / 29 (89%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseSetup} from "@chimera/BaseSetup.sol"; |
|
| 5 | import {vm} from "@chimera/Hevm.sol"; |
|
| 6 | ||
| 7 | import {EnumerableSet} from "@recon/EnumerableSet.sol"; |
|
| 8 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 9 | ||
| 10 | import {MockERC4626Tester} from "../mocks/MockERC4626Tester.sol"; |
|
| 11 | import {MockERC5115Tester} from "../mocks/MockERC5115Tester.sol"; |
|
| 12 | import {MockERC7540Tester} from "../mocks/MockERC7540Tester.sol"; |
|
| 13 | ||
| 14 | /// @dev Source of truth for the yield sources being used in the test |
|
| 15 | /// @notice No yield sources should be used in the test suite without being added from here first |
|
| 16 | /// @notice Enum to specify the type of yield source to deploy |
|
| 17 | enum YieldSourceType { |
|
| 18 | ERC4626, |
|
| 19 | ERC5115, |
|
| 20 | ERC7540 |
|
| 21 | } |
|
| 22 | ||
| 23 | abstract contract YieldManager { |
|
| 24 | using EnumerableSet for EnumerableSet.AddressSet; |
|
| 25 | ||
| 26 | /// @notice The current target for this set of variables |
|
| 27 | address private __yieldSource; |
|
| 28 | ||
| 29 | /// @notice The current yield source type |
|
| 30 | YieldSourceType private __currentYieldSourceType; |
|
| 31 | ||
| 32 | /// @notice The list of all yield sources being used |
|
| 33 | EnumerableSet.AddressSet private _yieldSources; |
|
| 34 | ||
| 35 | // If the current target is address(0) then it has not been setup yet and should revert |
|
| 36 | error YieldSourceNotSetup(); |
|
| 37 | // Do not allow duplicates |
|
| 38 | error YieldSourceExists(); |
|
| 39 | // Enable only added yield sources |
|
| 40 | error YieldSourceNotAdded(); |
|
| 41 | // Invalid yield source type |
|
| 42 | error InvalidYieldSourceType(); |
|
| 43 | ||
| 44 | /// @notice Returns the current active yield source |
|
| 45 | 8× | function _getYieldSource() internal view returns (address) { |
| 46 | 12× | if (__yieldSource == address(0)) { |
| 47 | 0 | revert YieldSourceNotSetup(); |
| 48 | } |
|
| 49 | ||
| 50 | 8× | return __yieldSource; |
| 51 | } |
|
| 52 | ||
| 53 | /// @notice Returns all yield sources being used |
|
| 54 | 4× | function _getYieldSources() internal view returns (address[] memory) { |
| 55 | 8× | return _yieldSources.values(); |
| 56 | } |
|
| 57 | ||
| 58 | /// @notice Returns the current yield source type |
|
| 59 | 21× | function _getCurrentYieldSourceType() internal view returns (YieldSourceType) { |
| 60 | 180× | return __currentYieldSourceType; |
| 61 | } |
|
| 62 | ||
| 63 | /// @notice Creates a new yield source and adds it to the list of yield sources |
|
| 64 | /// @param asset The asset to create the yield source for |
|
| 65 | /// @param yieldSourceType The type of yield source to deploy |
|
| 66 | /// @return The address of the new yield source |
|
| 67 | 12× | function _newYieldSource(address asset, YieldSourceType yieldSourceType) internal returns (address) { |
| 68 | address yieldSource_; |
|
| 69 | ||
| 70 | 27× | if (yieldSourceType == YieldSourceType.ERC4626) { |
| 71 | 52× | yieldSource_ = address(new MockERC4626Tester(asset)); |
| 72 | 13× | } else if (yieldSourceType == YieldSourceType.ERC5115) { |
| 73 | 7× | yieldSource_ = address(new MockERC5115Tester(asset)); |
| 74 | 14× | } else if (yieldSourceType == YieldSourceType.ERC7540) { |
| 75 | 7× | yieldSource_ = address(new MockERC7540Tester(asset)); |
| 76 | } else { |
|
| 77 | 0 | revert InvalidYieldSourceType(); |
| 78 | } |
|
| 79 | ||
| 80 | 10× | _addYieldSource(yieldSource_); |
| 81 | 24× | __yieldSource = yieldSource_; // sets the yield source as the current yield source |
| 82 | 32× | __currentYieldSourceType = yieldSourceType; |
| 83 | 2× | 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 | 2× | function _newVault(address asset) internal returns (address) { |
| 97 | 5× | 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 | 2× | function _addYieldSource(address target) internal { |
| 103 | 20× | if (_yieldSources.contains(target)) { |
| 104 | 0 | revert YieldSourceExists(); |
| 105 | } |
|
| 106 | ||
| 107 | 10× | _yieldSources.add(target); |
| 108 | } |
|
| 109 | ||
| 110 | /// @notice Legacy function name for backward compatibility |
|
| 111 | /// @param target The address of the yield source to add |
|
| 112 | function _addVault(address target) internal { |
|
| 113 | _addYieldSource(target); |
|
| 114 | } |
|
| 115 | ||
| 116 | /// @notice Removes a yield source from the list of yield sources |
|
| 117 | /// @param target The address of the yield source to remove |
|
| 118 | function _removeYieldSource(address target) internal { |
|
| 119 | if (!_yieldSources.contains(target)) { |
|
| 120 | revert YieldSourceNotAdded(); |
|
| 121 | } |
|
| 122 | ||
| 123 | _yieldSources.remove(target); |
|
| 124 | } |
|
| 125 | ||
| 126 | /// @notice Legacy function name for backward compatibility |
|
| 127 | /// @param target The address of the yield source to remove |
|
| 128 | function _removeVault(address target) internal { |
|
| 129 | _removeYieldSource(target); |
|
| 130 | } |
|
| 131 | ||
| 132 | /// @notice Switches the current yield source based on the entropy |
|
| 133 | /// @param entropy The entropy to choose a random yield source in the array for switching |
|
| 134 | 4× | function _switchYieldSource(uint256 entropy) internal { |
| 135 | 36× | address target = _yieldSources.at(entropy % _yieldSources.length()); |
| 136 | 28× | __yieldSource = target; |
| 137 | } |
|
| 138 | ||
| 139 | /// @notice Legacy function name for backward compatibility |
|
| 140 | /// @param entropy The entropy to choose a random yield source in the array for switching |
|
| 141 | function _switchVault(uint256 entropy) internal { |
|
| 142 | _switchYieldSource(entropy); |
|
| 143 | } |
|
| 144 | ||
| 145 | /// @notice Switches to a different yield source type by deploying a new yield source |
|
| 146 | /// @param asset The asset to create the new yield source for |
|
| 147 | /// @param newYieldSourceType The new yield source type to switch to |
|
| 148 | /// @return The address of the new yield source |
|
| 149 | function _switchYieldSourceType(address asset, YieldSourceType newYieldSourceType) internal returns (address) { |
|
| 150 | return _newYieldSource(asset, newYieldSourceType); |
|
| 151 | } |
|
| 152 | } |
30%
test/recon/mocks/MockECDSAPPSOracle.sol
Lines covered: 13 / 42 (30%)
| 1 | // SPDX-License-Identifier: UNLICENSED |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {ISuperVaultAggregator} from "src/interfaces/SuperVault/ISuperVaultAggregator.sol"; |
|
| 5 | import {IECDSAPPSOracle} from "src/interfaces/oracles/IECDSAPPSOracle.sol"; |
|
| 6 | ||
| 7 | 177× | contract MockECDSAPPSOracle { |
| 8 | //<>=============================================================<> |
|
| 9 | //|| || |
|
| 10 | //|| NON-VIEW FUNCTIONS || |
|
| 11 | //|| || |
|
| 12 | //<>=============================================================<> |
|
| 13 | // Mock implementation of updatePPS |
|
| 14 | 14× | function updatePPS(IECDSAPPSOracle.UpdatePPSArgs memory args) public { |
| 15 | 1× | ISuperVaultAggregator.ForwardPPSArgs |
| 16 | 47× | memory forwardArgs = ISuperVaultAggregator.ForwardPPSArgs({ |
| 17 | 2× | strategies: args.strategies, |
| 18 | 4× | ppss: args.ppss, |
| 19 | 5× | ppsStdevs: args.ppsStdevs, |
| 20 | 5× | validatorSets: args.validatorSets, |
| 21 | 5× | totalValidators: args.totalValidators, |
| 22 | 5× | timestamps: args.timestamps, |
| 23 | 1× | updateAuthority: msg.sender |
| 24 | }); |
|
| 25 | ||
| 26 | 53× | ISuperVaultAggregator(_SUPER_GOVERNORReturn_0).forwardPPS(forwardArgs); |
| 27 | } |
|
| 28 | ||
| 29 | //<>=============================================================<> |
|
| 30 | //|| || |
|
| 31 | //|| SETTER FUNCTIONS || |
|
| 32 | //|| || |
|
| 33 | //<>=============================================================<> |
|
| 34 | // Function to set return values for SUPER_GOVERNOR |
|
| 35 | 0 | function setSUPER_GOVERNORReturn(address _value0) public { |
| 36 | 0 | _SUPER_GOVERNORReturn_0 = _value0; |
| 37 | } |
|
| 38 | ||
| 39 | // Function to set return values for UPDATE_PPS_TYPEHASH |
|
| 40 | 0 | function setUPDATE_PPS_TYPEHASHReturn(bytes32 _value0) public { |
| 41 | 0 | _UPDATE_PPS_TYPEHASHReturn_0 = _value0; |
| 42 | } |
|
| 43 | ||
| 44 | // Function to set return values for domainSeparator |
|
| 45 | 2× | function setDomainSeparatorReturn(bytes32 _value0) public { |
| 46 | 0 | _domainSeparatorReturn_0 = _value0; |
| 47 | } |
|
| 48 | ||
| 49 | /******************************************************************* |
|
| 50 | * ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ * |
|
| 51 | *-----------------------------------------------------------------* |
|
| 52 | * Generally you only need to modify the sections above. * |
|
| 53 | * The code below handles system operations. * |
|
| 54 | *******************************************************************/ |
|
| 55 | ||
| 56 | //<>=============================================================<> |
|
| 57 | //|| || |
|
| 58 | //|| ⚠️ STRUCT DEFINITIONS - DO NOT MODIFY ⚠️ || |
|
| 59 | //|| || |
|
| 60 | //<>=============================================================<> |
|
| 61 | // Struct definition removed - using IECDSAPPSOracle.UpdatePPSArgs from interface |
|
| 62 | ||
| 63 | //<>=============================================================<> |
|
| 64 | //|| || |
|
| 65 | //|| ⚠️ EVENTS DEFINITIONS - DO NOT MODIFY ⚠️ || |
|
| 66 | //|| || |
|
| 67 | //<>=============================================================<> |
|
| 68 | event EIP712DomainChanged(); |
|
| 69 | event PPSValidated( |
|
| 70 | address strategy, |
|
| 71 | uint256 pps, |
|
| 72 | uint256 ppsStdev, |
|
| 73 | uint256 validatorSet, |
|
| 74 | uint256 totalValidators, |
|
| 75 | uint256 timestamp, |
|
| 76 | address sender |
|
| 77 | ); |
|
| 78 | ||
| 79 | //<>=============================================================<> |
|
| 80 | //|| || |
|
| 81 | //|| ⚠️ INTERNAL STORAGE - DO NOT MODIFY ⚠️ || |
|
| 82 | //|| || |
|
| 83 | //<>=============================================================<> |
|
| 84 | address private _SUPER_GOVERNORReturn_0; |
|
| 85 | bytes32 private _UPDATE_PPS_TYPEHASHReturn_0; |
|
| 86 | bytes32 private _domainSeparatorReturn_0; |
|
| 87 | bytes1 private _eip712DomainReturn_0; |
|
| 88 | string private _eip712DomainReturn_1; |
|
| 89 | string private _eip712DomainReturn_2; |
|
| 90 | uint256 private _eip712DomainReturn_3; |
|
| 91 | address private _eip712DomainReturn_4; |
|
| 92 | bytes32 private _eip712DomainReturn_5; |
|
| 93 | uint256[] private _eip712DomainReturn_6; |
|
| 94 | uint256 private _nonceReturn_0; |
|
| 95 | ||
| 96 | //<>=============================================================<> |
|
| 97 | //|| || |
|
| 98 | //|| ⚠️ VIEW FUNCTIONS - DO NOT MODIFY ⚠️ || |
|
| 99 | //|| || |
|
| 100 | //<>=============================================================<> |
|
| 101 | // Mock implementation of SUPER_GOVERNOR |
|
| 102 | 0 | function SUPER_GOVERNOR() public view returns (address) { |
| 103 | 0 | return _SUPER_GOVERNORReturn_0; |
| 104 | } |
|
| 105 | ||
| 106 | // Mock implementation of UPDATE_PPS_TYPEHASH |
|
| 107 | 0 | function UPDATE_PPS_TYPEHASH() public view returns (bytes32) { |
| 108 | 0 | return _UPDATE_PPS_TYPEHASHReturn_0; |
| 109 | } |
|
| 110 | ||
| 111 | // Mock implementation of domainSeparator |
|
| 112 | 0 | function domainSeparator() public view returns (bytes32) { |
| 113 | 0 | return _domainSeparatorReturn_0; |
| 114 | } |
|
| 115 | ||
| 116 | // Mock implementation of eip712Domain |
|
| 117 | 0 | function eip712Domain() |
| 118 | public |
|
| 119 | view |
|
| 120 | returns ( |
|
| 121 | 0 | bytes1, |
| 122 | 0 | string memory, |
| 123 | 0 | string memory, |
| 124 | 0 | uint256, |
| 125 | 0 | address, |
| 126 | 0 | bytes32, |
| 127 | 0 | uint256[] memory |
| 128 | ) |
|
| 129 | { |
|
| 130 | 0 | return ( |
| 131 | 0 | _eip712DomainReturn_0, |
| 132 | 0 | _eip712DomainReturn_1, |
| 133 | 0 | _eip712DomainReturn_2, |
| 134 | 0 | _eip712DomainReturn_3, |
| 135 | 0 | _eip712DomainReturn_4, |
| 136 | 0 | _eip712DomainReturn_5, |
| 137 | 0 | _eip712DomainReturn_6 |
| 138 | ); |
|
| 139 | } |
|
| 140 | ||
| 141 | // Mock implementation of nonce |
|
| 142 | 0 | function nonce() public view returns (uint256) { |
| 143 | 0 | return _nonceReturn_0; |
| 144 | } |
|
| 145 | } |
91%
test/recon/mocks/MockERC4626Tester.sol
Lines covered: 107 / 117 (91%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // Heavily inspired by https://github.com/liquity/V2-gov/blob/9632de9a988522775336d9b60cdf2542efc600db/test/mocks/MaliciousInitiative.sol |
|
| 3 | pragma solidity ^0.8.0; |
|
| 4 | ||
| 5 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 6 | ||
| 7 | abstract contract ERC4626 is MockERC20 { |
|
| 8 | 18× | MockERC20 public immutable asset; |
| 9 | ||
| 10 | event Deposit( |
|
| 11 | address indexed caller, |
|
| 12 | address indexed owner, |
|
| 13 | uint256 assets, |
|
| 14 | uint256 shares |
|
| 15 | ); |
|
| 16 | event Withdraw( |
|
| 17 | address indexed caller, |
|
| 18 | address indexed receiver, |
|
| 19 | address indexed owner, |
|
| 20 | uint256 assets, |
|
| 21 | uint256 shares |
|
| 22 | ); |
|
| 23 | ||
| 24 | 1× | constructor(MockERC20 _asset) MockERC20("MockERC4626Tester", "MCT", 18) { |
| 25 | 5× | asset = _asset; |
| 26 | } |
|
| 27 | ||
| 28 | 3× | function deposit( |
| 29 | uint256 assets, |
|
| 30 | address receiver |
|
| 31 | 3× | ) public virtual returns (uint256) { |
| 32 | 24× | uint256 shares = previewDeposit(assets); |
| 33 | 21× | _deposit(msg.sender, receiver, assets, shares); |
| 34 | return shares; |
|
| 35 | } |
|
| 36 | ||
| 37 | 2× | function mint( |
| 38 | uint256 shares, |
|
| 39 | address receiver |
|
| 40 | 2× | ) public virtual returns (uint256) { |
| 41 | 16× | uint256 assets = previewMint(shares); |
| 42 | 14× | _deposit(msg.sender, receiver, assets, shares); |
| 43 | return assets; |
|
| 44 | } |
|
| 45 | ||
| 46 | function withdraw( |
|
| 47 | uint256 assets, |
|
| 48 | address receiver, |
|
| 49 | address owner |
|
| 50 | ) public virtual returns (uint256) { |
|
| 51 | uint256 shares = previewWithdraw(assets); |
|
| 52 | _withdraw(msg.sender, receiver, owner, assets, shares); |
|
| 53 | return shares; |
|
| 54 | } |
|
| 55 | ||
| 56 | function redeem( |
|
| 57 | uint256 shares, |
|
| 58 | address receiver, |
|
| 59 | address owner |
|
| 60 | ) public virtual returns (uint256) { |
|
| 61 | uint256 assets = previewRedeem(shares); |
|
| 62 | _withdraw(msg.sender, receiver, owner, assets, shares); |
|
| 63 | return assets; |
|
| 64 | } |
|
| 65 | ||
| 66 | 80× | function totalAssets() public view virtual returns (uint256) { |
| 67 | 295× | return asset.balanceOf(address(this)); |
| 68 | } |
|
| 69 | ||
| 70 | 25× | function convertToShares( |
| 71 | uint256 assets |
|
| 72 | 10× | ) public view virtual returns (uint256) { |
| 73 | 10× | uint256 supply = totalSupply; |
| 74 | 65× | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
| 75 | } |
|
| 76 | ||
| 77 | 14× | function convertToAssets( |
| 78 | uint256 shares |
|
| 79 | 8× | ) public view virtual returns (uint256) { |
| 80 | 8× | uint256 supply = totalSupply; |
| 81 | 121× | return supply == 0 ? shares : (shares * totalAssets()) / supply; |
| 82 | } |
|
| 83 | ||
| 84 | 5× | function previewDeposit( |
| 85 | uint256 assets |
|
| 86 | 5× | ) public view virtual returns (uint256) { |
| 87 | 20× | return convertToShares(assets); |
| 88 | } |
|
| 89 | ||
| 90 | function previewMint(uint256 shares) public view virtual returns (uint256) { |
|
| 91 | uint256 supply = totalSupply; |
|
| 92 | return supply == 0 ? shares : (shares * totalAssets()) / supply; |
|
| 93 | } |
|
| 94 | ||
| 95 | function previewWithdraw( |
|
| 96 | uint256 assets |
|
| 97 | ) public view virtual returns (uint256) { |
|
| 98 | uint256 supply = totalSupply; |
|
| 99 | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
|
| 100 | } |
|
| 101 | ||
| 102 | 4× | function previewRedeem( |
| 103 | uint256 shares |
|
| 104 | 4× | ) public view virtual returns (uint256) { |
| 105 | 16× | return convertToAssets(shares); |
| 106 | } |
|
| 107 | ||
| 108 | 9× | function maxDeposit(address) public view virtual returns (uint256) { |
| 109 | 1× | return type(uint256).max; |
| 110 | } |
|
| 111 | ||
| 112 | function maxMint(address) public view virtual returns (uint256) { |
|
| 113 | return type(uint256).max; |
|
| 114 | } |
|
| 115 | ||
| 116 | 12× | function maxWithdraw(address owner) public view virtual returns (uint256) { |
| 117 | 16× | return convertToAssets(balanceOf[owner]); |
| 118 | } |
|
| 119 | ||
| 120 | 10× | function maxRedeem(address owner) public view virtual returns (uint256) { |
| 121 | 12× | return balanceOf[owner]; |
| 122 | } |
|
| 123 | ||
| 124 | 18× | function _deposit( |
| 125 | address caller, |
|
| 126 | address receiver, |
|
| 127 | uint256 assets, |
|
| 128 | uint256 shares |
|
| 129 | ) internal virtual { |
|
| 130 | 191× | asset.transferFrom(caller, address(this), assets); |
| 131 | 18× | _mint(receiver, shares); |
| 132 | 63× | emit Deposit(caller, receiver, assets, shares); |
| 133 | } |
|
| 134 | ||
| 135 | 22× | function _withdraw( |
| 136 | address caller, |
|
| 137 | address receiver, |
|
| 138 | address owner, |
|
| 139 | uint256 assets, |
|
| 140 | uint256 shares |
|
| 141 | ) internal virtual { |
|
| 142 | 32× | if (caller != owner) { |
| 143 | 80× | allowance[owner][caller] -= shares; |
| 144 | } |
|
| 145 | 23× | _burn(owner, shares); |
| 146 | 188× | asset.transfer(receiver, assets); |
| 147 | 69× | emit Withdraw(caller, receiver, owner, assets, shares); |
| 148 | } |
|
| 149 | } |
|
| 150 | ||
| 151 | enum FunctionType { |
|
| 152 | NONE, |
|
| 153 | DEPOSIT, |
|
| 154 | MINT, |
|
| 155 | WITHDRAW, |
|
| 156 | REDEEM |
|
| 157 | } |
|
| 158 | ||
| 159 | enum RevertType { |
|
| 160 | NONE, |
|
| 161 | THROW, |
|
| 162 | OOG, |
|
| 163 | RETURN_BOMB, |
|
| 164 | REVERT_BOMB |
|
| 165 | } |
|
| 166 | ||
| 167 | /// @dev This will use the simplest possible implementation initially to allow getting coverage and will be expanded on as necessary for testing potentially more interesting behaviors |
|
| 168 | /// @dev Note that blindspots not testable with this current implementation are covered in the ERC4626-integrations.md file |
|
| 169 | 1098× | contract MockERC4626Tester is ERC4626 { |
| 170 | 0 | mapping(FunctionType => RevertType) public revertBehaviours; |
| 171 | ||
| 172 | 0 | uint8 public decimalsOffset; |
| 173 | /// @dev Track total losses |
|
| 174 | 0 | uint256 public totalLosses; |
| 175 | 0 | uint256 public totalGains; |
| 176 | 0 | uint256 public lossOnWithdraw; |
| 177 | 3× | uint256 public MAX_BPS = 10_000; |
| 178 | ||
| 179 | 28× | constructor(address _asset) ERC4626(MockERC20(_asset)) {} |
| 180 | ||
| 181 | /// Standard ERC4626 functions /// |
|
| 182 | ||
| 183 | /// @dev Deposit assets, reverts as specified |
|
| 184 | 33× | function deposit( |
| 185 | uint256 assets, |
|
| 186 | address receiver |
|
| 187 | 6× | ) public override returns (uint256) { |
| 188 | 18× | _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
| 189 | 15× | return super.deposit(assets, receiver); |
| 190 | } |
|
| 191 | ||
| 192 | /// @dev Mint shares, reverts as specified |
|
| 193 | 22× | function mint( |
| 194 | uint256 shares, |
|
| 195 | address receiver |
|
| 196 | 4× | ) public override returns (uint256) { |
| 197 | 12× | _performRevertBehaviour(revertBehaviours[FunctionType.MINT]); |
| 198 | 10× | return super.mint(shares, receiver); |
| 199 | } |
|
| 200 | ||
| 201 | /// @dev Withdraw assets, reverts as specified |
|
| 202 | 26× | function withdraw( |
| 203 | uint256 assets, |
|
| 204 | address receiver, |
|
| 205 | address owner |
|
| 206 | 4× | ) public override returns (uint256) { |
| 207 | 12× | _performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]); |
| 208 | ||
| 209 | 16× | uint256 shares = previewWithdraw(assets); |
| 210 | 52× | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 211 | 18× | _withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 212 | ||
| 213 | 2× | return shares; |
| 214 | } |
|
| 215 | ||
| 216 | /// @dev Redeem shares, reverts as specified |
|
| 217 | 50× | function redeem( |
| 218 | uint256 shares, |
|
| 219 | address receiver, |
|
| 220 | address owner |
|
| 221 | 8× | ) public override returns (uint256) { |
| 222 | 24× | _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
| 223 | ||
| 224 | 32× | uint256 assets = previewRedeem(shares); |
| 225 | 104× | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 226 | 35× | _withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 227 | ||
| 228 | 3× | return lossyAssets; |
| 229 | } |
|
| 230 | ||
| 231 | /// @dev Preview deposit, reverts as specified |
|
| 232 | 35× | function previewDeposit( |
| 233 | uint256 assets |
|
| 234 | 10× | ) public view override returns (uint256) { |
| 235 | 30× | _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
| 236 | 20× | return super.previewDeposit(assets); |
| 237 | } |
|
| 238 | ||
| 239 | /// @dev Preview mint, reverts as specified |
|
| 240 | 12× | function previewMint( |
| 241 | uint256 shares |
|
| 242 | 4× | ) public view override returns (uint256) { |
| 243 | 12× | _performRevertBehaviour(revertBehaviours[FunctionType.MINT]); |
| 244 | 8× | return super.previewMint(shares); |
| 245 | } |
|
| 246 | ||
| 247 | /// @dev Preview withdraw, reverts as specified |
|
| 248 | 2× | function previewWithdraw( |
| 249 | uint256 assets |
|
| 250 | 4× | ) public view override returns (uint256) { |
| 251 | 133× | _performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]); |
| 252 | 8× | return super.previewWithdraw(assets); |
| 253 | } |
|
| 254 | ||
| 255 | /// @dev Preview redeem, reverts as specified |
|
| 256 | 4× | function previewRedeem( |
| 257 | uint256 shares |
|
| 258 | 8× | ) public view override returns (uint256) { |
| 259 | 24× | _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
| 260 | 16× | return super.previewRedeem(shares); |
| 261 | } |
|
| 262 | ||
| 263 | /// @dev Revert in different ways to test the revert behaviour |
|
| 264 | 15× | function _performRevertBehaviour(RevertType action) internal pure { |
| 265 | 65× | if (action == RevertType.THROW) { |
| 266 | 16× | revert("A normal Revert"); |
| 267 | } |
|
| 268 | ||
| 269 | // 3 gas per iteration, consider changing to storage changes if traces are cluttered |
|
| 270 | 65× | if (action == RevertType.OOG) { |
| 271 | 0 | uint256 i; |
| 272 | 0 | while (true) { |
| 273 | 0 | ++i; |
| 274 | } |
|
| 275 | } |
|
| 276 | ||
| 277 | 65× | if (action == RevertType.RETURN_BOMB) { |
| 278 | 0 | uint256 _bytes = 2_000_000; |
| 279 | assembly { |
|
| 280 | 0 | return(0, _bytes) |
| 281 | } |
|
| 282 | } |
|
| 283 | ||
| 284 | 65× | if (action == RevertType.REVERT_BOMB) { |
| 285 | 6× | uint256 _bytes = 2_000_000; |
| 286 | assembly { |
|
| 287 | 2× | revert(0, _bytes) |
| 288 | } |
|
| 289 | } |
|
| 290 | ||
| 291 | return; // NONE |
|
| 292 | } |
|
| 293 | ||
| 294 | /// Custom functions for testing vault behavior /// |
|
| 295 | ||
| 296 | /// @dev Specify the revert behavior on each function |
|
| 297 | 14× | function setRevertBehavior(FunctionType ft, RevertType rt) public { |
| 298 | 47× | revertBehaviours[ft] = rt; |
| 299 | } |
|
| 300 | ||
| 301 | /// @dev Simulate a loss on the vault's assets |
|
| 302 | 11× | function simulateLoss(uint256 lossAmount) external { |
| 303 | 66× | MockERC20(asset).transfer(address(0xbeef), lossAmount); |
| 304 | 11× | totalLosses += lossAmount; |
| 305 | } |
|
| 306 | ||
| 307 | /// @dev Simulate a gain on the vault's assets (similar to Yearn's profit taking) |
|
| 308 | 12× | function simulateGain(uint256 gainAmount) external { |
| 309 | 67× | MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
| 310 | 15× | totalGains += gainAmount; |
| 311 | } |
|
| 312 | ||
| 313 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
|
| 314 | 16× | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 315 | 14× | _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
| 316 | 2× | lossOnWithdraw = _lossOnWithdraw; |
| 317 | } |
|
| 318 | ||
| 319 | /// @dev Set the decimal offset. Only possible with no supply. |
|
| 320 | 12× | function setDecimalsOffset(uint8 targetDecimalsOffset) external { |
| 321 | 6× | if (totalSupply != 0) { |
| 322 | 8× | revert("Supply is not zero"); |
| 323 | } |
|
| 324 | 15× | decimalsOffset = targetDecimalsOffset; |
| 325 | } |
|
| 326 | } |
10%
test/recon/mocks/MockERC4626YieldSourceOracle.sol
Lines covered: 6 / 57 (10%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.30; |
|
| 3 | ||
| 4 | import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 5 | import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
|
| 6 | ||
| 7 | /// @title MockERC4626YieldSourceOracle |
|
| 8 | /// @notice Mock oracle for ERC4626 vaults in testing |
|
| 9 | 278× | contract MockERC4626YieldSourceOracle is IYieldSourceOracle { |
| 10 | 0 | mapping(address => bool) public validAssetMap; |
| 11 | ||
| 12 | 11× | function setValidAsset(address asset, bool isValid) external { |
| 13 | 27× | validAssetMap[asset] = isValid; |
| 14 | } |
|
| 15 | ||
| 16 | 0 | function decimals(address yieldSourceAddress) external view returns (uint8) { |
| 17 | 0 | return IERC4626(yieldSourceAddress).decimals(); |
| 18 | } |
|
| 19 | ||
| 20 | 48× | function getShareOutput( |
| 21 | address yieldSourceAddress, |
|
| 22 | address, |
|
| 23 | uint256 assetsIn |
|
| 24 | 4× | ) external view returns (uint256) { |
| 25 | 130× | return IERC4626(yieldSourceAddress).previewDeposit(assetsIn); |
| 26 | } |
|
| 27 | ||
| 28 | 0 | function getAssetOutput( |
| 29 | address yieldSourceAddress, |
|
| 30 | address, |
|
| 31 | uint256 sharesIn |
|
| 32 | 0 | ) public view returns (uint256) { |
| 33 | 0 | return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
| 34 | } |
|
| 35 | ||
| 36 | 0 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 37 | 0 | IERC4626 yieldSource = IERC4626(yieldSourceAddress); |
| 38 | 0 | uint256 _decimals = yieldSource.decimals(); |
| 39 | 0 | return yieldSource.convertToAssets(10 ** _decimals); |
| 40 | } |
|
| 41 | ||
| 42 | 0 | function getBalanceOfOwner( |
| 43 | address yieldSourceAddress, |
|
| 44 | address ownerOfShares |
|
| 45 | 0 | ) external view returns (uint256) { |
| 46 | 0 | return IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
| 47 | } |
|
| 48 | ||
| 49 | 0 | function getTVLByOwnerOfShares( |
| 50 | address yieldSourceAddress, |
|
| 51 | address ownerOfShares |
|
| 52 | 0 | ) external view returns (uint256) { |
| 53 | 0 | uint256 shares = IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
| 54 | 0 | return IERC4626(yieldSourceAddress).convertToAssets(shares); |
| 55 | } |
|
| 56 | ||
| 57 | 0 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 58 | 0 | return IERC4626(yieldSourceAddress).totalAssets(); |
| 59 | } |
|
| 60 | ||
| 61 | 0 | function getPricePerShareMultiple( |
| 62 | address[] memory yieldSourceAddresses |
|
| 63 | 0 | ) external view returns (uint256[] memory) { |
| 64 | 0 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 65 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 66 | 0 | IERC4626 yieldSource = IERC4626(yieldSourceAddresses[i]); |
| 67 | 0 | uint256 _decimals = yieldSource.decimals(); |
| 68 | 0 | prices[i] = yieldSource.convertToAssets(10 ** _decimals); |
| 69 | } |
|
| 70 | 0 | return prices; |
| 71 | } |
|
| 72 | ||
| 73 | 0 | function getTVLByOwnerOfSharesMultiple( |
| 74 | address[] memory yieldSourceAddresses, |
|
| 75 | address[][] memory ownersOfShares |
|
| 76 | 0 | ) external view returns (uint256[][] memory) { |
| 77 | 0 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 78 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 79 | 0 | result[i] = new uint256[](ownersOfShares[i].length); |
| 80 | 0 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 81 | 0 | uint256 shares = IERC4626(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
| 82 | 0 | result[i][j] = IERC4626(yieldSourceAddresses[i]).convertToAssets(shares); |
| 83 | } |
|
| 84 | } |
|
| 85 | 0 | return result; |
| 86 | } |
|
| 87 | ||
| 88 | 0 | function getTVLMultiple( |
| 89 | address[] memory yieldSourceAddresses |
|
| 90 | 0 | ) external view returns (uint256[] memory) { |
| 91 | 0 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 92 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 93 | 0 | tvls[i] = IERC4626(yieldSourceAddresses[i]).totalAssets(); |
| 94 | } |
|
| 95 | return tvls; |
|
| 96 | } |
|
| 97 | ||
| 98 | 0 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 99 | 0 | return validAssetMap[asset]; |
| 100 | } |
|
| 101 | ||
| 102 | 0 | function isValidUnderlyingAssets( |
| 103 | address[] memory, |
|
| 104 | address[] memory assets |
|
| 105 | 0 | ) external view returns (bool[] memory) { |
| 106 | 0 | bool[] memory validities = new bool[](assets.length); |
| 107 | 0 | for (uint256 i = 0; i < assets.length; i++) { |
| 108 | 0 | validities[i] = validAssetMap[assets[i]]; |
| 109 | } |
|
| 110 | return validities; |
|
| 111 | } |
|
| 112 | ||
| 113 | 0 | function getAssetOutputWithFees( |
| 114 | bytes32, |
|
| 115 | address yieldSourceAddress, |
|
| 116 | address, |
|
| 117 | address, |
|
| 118 | uint256 sharesIn |
|
| 119 | 0 | ) external view returns (uint256) { |
| 120 | 0 | return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
| 121 | } |
|
| 122 | } |
25%
test/recon/mocks/MockERC5115Tester.sol
Lines covered: 26 / 104 (25%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 5 | ||
| 6 | abstract contract ERC5115 is MockERC20 { |
|
| 7 | 0 | MockERC20 public immutable yieldToken; |
| 8 | 0 | address[] public tokensIn; |
| 9 | 0 | address[] public tokensOut; |
| 10 | ||
| 11 | event Deposit( |
|
| 12 | address indexed caller, |
|
| 13 | address indexed receiver, |
|
| 14 | address indexed tokenIn, |
|
| 15 | uint256 amountDeposited, |
|
| 16 | uint256 amountSyOut |
|
| 17 | ); |
|
| 18 | ||
| 19 | event Redeem( |
|
| 20 | address indexed caller, |
|
| 21 | address indexed receiver, |
|
| 22 | address indexed tokenOut, |
|
| 23 | uint256 amountSyToRedeem, |
|
| 24 | uint256 amountTokenOut |
|
| 25 | ); |
|
| 26 | ||
| 27 | constructor( |
|
| 28 | MockERC20 _yieldToken |
|
| 29 | 1× | ) MockERC20("MockERC5115Tester", "SY5115", 18) { |
| 30 | 7× | yieldToken = _yieldToken; |
| 31 | 22× | tokensIn.push(address(_yieldToken)); |
| 32 | 23× | tokensOut.push(address(_yieldToken)); |
| 33 | } |
|
| 34 | ||
| 35 | 0 | function deposit( |
| 36 | address receiver, |
|
| 37 | address tokenIn, |
|
| 38 | uint256 amountTokenToDeposit, |
|
| 39 | uint256 minSharesOut, |
|
| 40 | bool depositFromInternalBalance |
|
| 41 | 0 | ) public virtual returns (uint256 amountSharesOut) { |
| 42 | 0 | amountSharesOut = previewDeposit(tokenIn, amountTokenToDeposit); |
| 43 | ||
| 44 | 0 | MockERC20(tokenIn).transferFrom( |
| 45 | 0 | msg.sender, |
| 46 | 0 | address(this), |
| 47 | amountTokenToDeposit |
|
| 48 | ); |
|
| 49 | 0 | _mint(receiver, amountSharesOut); |
| 50 | ||
| 51 | 0 | emit Deposit( |
| 52 | 0 | msg.sender, |
| 53 | receiver, |
|
| 54 | tokenIn, |
|
| 55 | amountTokenToDeposit, |
|
| 56 | amountSharesOut |
|
| 57 | ); |
|
| 58 | } |
|
| 59 | ||
| 60 | 0 | function exchangeRate() public view virtual returns (uint256) { |
| 61 | 0 | uint256 supply = totalSupply; |
| 62 | 0 | if (supply == 0) return 1e18; |
| 63 | 0 | return (yieldToken.balanceOf(address(this)) * 1e18) / supply; |
| 64 | } |
|
| 65 | ||
| 66 | 0 | function getTokensIn() public view virtual returns (address[] memory) { |
| 67 | 0 | return tokensIn; |
| 68 | } |
|
| 69 | ||
| 70 | 0 | function getTokensOut() public view virtual returns (address[] memory) { |
| 71 | 0 | return tokensOut; |
| 72 | } |
|
| 73 | ||
| 74 | 22× | function previewDeposit( |
| 75 | address tokenIn, |
|
| 76 | uint256 amountTokenToDeposit |
|
| 77 | 2× | ) public view virtual returns (uint256 amountSharesOut) { |
| 78 | 24× | require(tokenIn == address(yieldToken), "Invalid token"); |
| 79 | 6× | uint256 supply = totalSupply; |
| 80 | 10× | if (supply == 0) { |
| 81 | 12× | return amountTokenToDeposit; |
| 82 | } |
|
| 83 | 0 | return |
| 84 | 0 | (amountTokenToDeposit * supply) / |
| 85 | 0 | yieldToken.balanceOf(address(this)); |
| 86 | } |
|
| 87 | ||
| 88 | 0 | function previewRedeem( |
| 89 | address tokenOut, |
|
| 90 | uint256 amountSharesToRedeem |
|
| 91 | 0 | ) public view virtual returns (uint256 amountTokenOut) { |
| 92 | 0 | require(tokenOut == address(yieldToken), "Invalid token"); |
| 93 | 0 | uint256 supply = totalSupply; |
| 94 | 0 | if (supply == 0) { |
| 95 | 0 | return amountSharesToRedeem; |
| 96 | } |
|
| 97 | return |
|
| 98 | 0 | (amountSharesToRedeem * yieldToken.balanceOf(address(this))) / |
| 99 | 0 | supply; |
| 100 | } |
|
| 101 | } |
|
| 102 | ||
| 103 | enum RevertType { |
|
| 104 | NONE, |
|
| 105 | THROW, |
|
| 106 | OOG, |
|
| 107 | RETURN_BOMB, |
|
| 108 | REVERT_BOMB |
|
| 109 | } |
|
| 110 | ||
| 111 | 1061× | contract MockERC5115Tester is ERC5115 { |
| 112 | 0 | RevertType public revertBehaviour; |
| 113 | 0 | uint256 public totalLosses; |
| 114 | 0 | uint256 public totalGains; |
| 115 | 0 | uint256 public lossOnWithdraw; |
| 116 | 3× | uint256 public MAX_BPS = 10_000; |
| 117 | ||
| 118 | 28× | constructor(address _yieldToken) ERC5115(MockERC20(_yieldToken)) {} |
| 119 | ||
| 120 | 0 | function deposit( |
| 121 | address receiver, |
|
| 122 | address tokenIn, |
|
| 123 | uint256 amountTokenToDeposit, |
|
| 124 | uint256 minSharesOut, |
|
| 125 | bool depositFromInternalBalance |
|
| 126 | 0 | ) public override returns (uint256 amountSharesOut) { |
| 127 | 0 | _performRevertBehaviour(revertBehaviour); |
| 128 | 0 | return |
| 129 | 0 | super.deposit( |
| 130 | 0 | receiver, |
| 131 | 0 | tokenIn, |
| 132 | 0 | amountTokenToDeposit, |
| 133 | 0 | minSharesOut, |
| 134 | 0 | depositFromInternalBalance |
| 135 | ); |
|
| 136 | } |
|
| 137 | ||
| 138 | 0 | function redeem( |
| 139 | address receiver, |
|
| 140 | uint256 amountSharesToRedeem, |
|
| 141 | address tokenOut, |
|
| 142 | uint256 minTokenOut, |
|
| 143 | bool burnFromInternalBalance |
|
| 144 | 0 | ) public virtual returns (uint256 amountTokenOut) { |
| 145 | 0 | amountTokenOut = previewRedeem(tokenOut, amountSharesToRedeem); |
| 146 | ||
| 147 | 0 | _burn(msg.sender, amountSharesToRedeem); |
| 148 | 0 | uint256 lossyAmountTokenOut = amountTokenOut - |
| 149 | 0 | ((amountTokenOut * lossOnWithdraw) / MAX_BPS); |
| 150 | 0 | MockERC20(tokenOut).transfer(receiver, lossyAmountTokenOut); |
| 151 | ||
| 152 | 0 | emit Redeem( |
| 153 | 0 | msg.sender, |
| 154 | receiver, |
|
| 155 | tokenOut, |
|
| 156 | amountSharesToRedeem, |
|
| 157 | amountTokenOut |
|
| 158 | ); |
|
| 159 | } |
|
| 160 | ||
| 161 | 2× | function _performRevertBehaviour(RevertType action) internal pure { |
| 162 | 0 | if (action == RevertType.THROW) { |
| 163 | 0 | revert("A normal Revert"); |
| 164 | } |
|
| 165 | ||
| 166 | 0 | if (action == RevertType.OOG) { |
| 167 | 0 | uint256 i; |
| 168 | 0 | while (true) { |
| 169 | 0 | ++i; |
| 170 | } |
|
| 171 | } |
|
| 172 | ||
| 173 | 0 | if (action == RevertType.RETURN_BOMB) { |
| 174 | 0 | uint256 _bytes = 2_000_000; |
| 175 | assembly { |
|
| 176 | 0 | return(0, _bytes) |
| 177 | } |
|
| 178 | } |
|
| 179 | ||
| 180 | 1× | if (action == RevertType.REVERT_BOMB) { |
| 181 | 0 | uint256 _bytes = 2_000_000; |
| 182 | assembly { |
|
| 183 | 0 | revert(0, _bytes) |
| 184 | } |
|
| 185 | } |
|
| 186 | ||
| 187 | return; // NONE |
|
| 188 | } |
|
| 189 | ||
| 190 | 0 | function setRevertBehavior(RevertType rt) public { |
| 191 | 0 | revertBehaviour = rt; |
| 192 | } |
|
| 193 | ||
| 194 | 11× | function simulateLoss(uint256 lossAmount) external { |
| 195 | 66× | MockERC20(yieldToken).transfer(address(0xbeef), lossAmount); |
| 196 | 11× | totalLosses += lossAmount; |
| 197 | } |
|
| 198 | ||
| 199 | 12× | function simulateGain(uint256 gainAmount) external { |
| 200 | 65× | MockERC20(yieldToken).transferFrom( |
| 201 | 1× | msg.sender, |
| 202 | 1× | address(this), |
| 203 | gainAmount |
|
| 204 | ); |
|
| 205 | 15× | totalGains += gainAmount; |
| 206 | } |
|
| 207 | ||
| 208 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
|
| 209 | 16× | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 210 | 14× | _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
| 211 | 2× | lossOnWithdraw = _lossOnWithdraw; |
| 212 | } |
|
| 213 | ||
| 214 | 0 | function increaseYield(uint256 increasePercentageFP4) public { |
| 215 | 0 | require(increasePercentageFP4 <= 10000, "Invalid percentage"); |
| 216 | 0 | uint256 amount = (yieldToken.balanceOf(address(this)) * |
| 217 | 0 | increasePercentageFP4) / 10000; |
| 218 | 0 | MockERC20(yieldToken).transferFrom(msg.sender, address(this), amount); |
| 219 | } |
|
| 220 | ||
| 221 | 0 | function decreaseYield(uint256 decreasePercentageFP4) public { |
| 222 | 0 | require(decreasePercentageFP4 <= 10000, "Invalid percentage"); |
| 223 | 0 | uint256 amount = (yieldToken.balanceOf(address(this)) * |
| 224 | 0 | decreasePercentageFP4) / 10000; |
| 225 | 0 | MockERC20(yieldToken).transfer(address(0xbeef), amount); |
| 226 | 0 | totalLosses += amount; |
| 227 | } |
|
| 228 | } |
10%
test/recon/mocks/MockERC5115YieldSourceOracle.sol
Lines covered: 6 / 59 (10%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.30; |
|
| 3 | ||
| 4 | import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
|
| 5 | import {MockERC5115Tester} from "./MockERC5115Tester.sol"; |
|
| 6 | ||
| 7 | /// @title MockERC5115YieldSourceOracle |
|
| 8 | /// @notice Mock oracle for ERC5115 (Standardized Yield) vaults in testing |
|
| 9 | 278× | contract MockERC5115YieldSourceOracle is IYieldSourceOracle { |
| 10 | 0 | mapping(address => bool) public validAssetMap; |
| 11 | ||
| 12 | 11× | function setValidAsset(address asset, bool isValid) external { |
| 13 | 27× | validAssetMap[asset] = isValid; |
| 14 | } |
|
| 15 | ||
| 16 | 0 | function decimals(address) external pure returns (uint8) { |
| 17 | // ERC5115 always uses 18 decimals |
|
| 18 | 0 | return 18; |
| 19 | } |
|
| 20 | ||
| 21 | 48× | function getShareOutput( |
| 22 | address yieldSourceAddress, |
|
| 23 | address assetIn, |
|
| 24 | uint256 assetsIn |
|
| 25 | 4× | ) external view returns (uint256) { |
| 26 | 132× | return MockERC5115Tester(yieldSourceAddress).previewDeposit(assetIn, assetsIn); |
| 27 | } |
|
| 28 | ||
| 29 | 0 | function getAssetOutput( |
| 30 | address yieldSourceAddress, |
|
| 31 | address assetOut, |
|
| 32 | uint256 sharesIn |
|
| 33 | 0 | ) public view returns (uint256) { |
| 34 | 0 | return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
| 35 | } |
|
| 36 | ||
| 37 | 0 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 38 | 0 | return MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 39 | } |
|
| 40 | ||
| 41 | 0 | function getBalanceOfOwner( |
| 42 | address yieldSourceAddress, |
|
| 43 | address ownerOfShares |
|
| 44 | 0 | ) external view returns (uint256) { |
| 45 | 0 | return MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
| 46 | } |
|
| 47 | ||
| 48 | 0 | function getTVLByOwnerOfShares( |
| 49 | address yieldSourceAddress, |
|
| 50 | address ownerOfShares |
|
| 51 | 0 | ) external view returns (uint256) { |
| 52 | 0 | uint256 shares = MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
| 53 | 0 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 54 | 0 | return (shares * exchangeRate) / 1e18; |
| 55 | } |
|
| 56 | ||
| 57 | 0 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 58 | 0 | uint256 totalShares = MockERC5115Tester(yieldSourceAddress).totalSupply(); |
| 59 | 0 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
| 60 | 0 | return (totalShares * exchangeRate) / 1e18; |
| 61 | } |
|
| 62 | ||
| 63 | 0 | function getPricePerShareMultiple( |
| 64 | address[] memory yieldSourceAddresses |
|
| 65 | 0 | ) external view returns (uint256[] memory) { |
| 66 | 0 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 67 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 68 | 0 | prices[i] = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 69 | } |
|
| 70 | 0 | return prices; |
| 71 | } |
|
| 72 | ||
| 73 | 0 | function getTVLByOwnerOfSharesMultiple( |
| 74 | address[] memory yieldSourceAddresses, |
|
| 75 | address[][] memory ownersOfShares |
|
| 76 | 0 | ) external view returns (uint256[][] memory) { |
| 77 | 0 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 78 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 79 | 0 | result[i] = new uint256[](ownersOfShares[i].length); |
| 80 | 0 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 81 | 0 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 82 | 0 | uint256 shares = MockERC5115Tester(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
| 83 | 0 | result[i][j] = (shares * exchangeRate) / 1e18; |
| 84 | } |
|
| 85 | } |
|
| 86 | 0 | return result; |
| 87 | } |
|
| 88 | ||
| 89 | 0 | function getTVLMultiple( |
| 90 | address[] memory yieldSourceAddresses |
|
| 91 | 0 | ) external view returns (uint256[] memory) { |
| 92 | 0 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 93 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 94 | 0 | uint256 totalShares = MockERC5115Tester(yieldSourceAddresses[i]).totalSupply(); |
| 95 | 0 | uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
| 96 | 0 | tvls[i] = (totalShares * exchangeRate) / 1e18; |
| 97 | } |
|
| 98 | return tvls; |
|
| 99 | } |
|
| 100 | ||
| 101 | 0 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 102 | 0 | return validAssetMap[asset]; |
| 103 | } |
|
| 104 | ||
| 105 | 0 | function isValidUnderlyingAssets( |
| 106 | address[] memory, |
|
| 107 | address[] memory assets |
|
| 108 | 0 | ) external view returns (bool[] memory) { |
| 109 | 0 | bool[] memory validities = new bool[](assets.length); |
| 110 | 0 | for (uint256 i = 0; i < assets.length; i++) { |
| 111 | 0 | validities[i] = validAssetMap[assets[i]]; |
| 112 | } |
|
| 113 | return validities; |
|
| 114 | } |
|
| 115 | ||
| 116 | 0 | function getAssetOutputWithFees( |
| 117 | bytes32, |
|
| 118 | address yieldSourceAddress, |
|
| 119 | address assetOut, |
|
| 120 | address, |
|
| 121 | uint256 sharesIn |
|
| 122 | 0 | ) external view returns (uint256) { |
| 123 | 0 | return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
| 124 | } |
|
| 125 | } |
77%
test/recon/mocks/MockERC7540Tester.sol
Lines covered: 137 / 177 (77%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 5 | import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; |
|
| 6 | ||
| 7 | abstract contract ERC7575 is MockERC20 { |
|
| 8 | 22× | MockERC20 public immutable asset; |
| 9 | ||
| 10 | event Deposit( |
|
| 11 | address indexed sender, |
|
| 12 | address indexed owner, |
|
| 13 | uint256 assets, |
|
| 14 | uint256 shares |
|
| 15 | ); |
|
| 16 | event Withdraw( |
|
| 17 | address indexed sender, |
|
| 18 | address indexed receiver, |
|
| 19 | address indexed owner, |
|
| 20 | uint256 assets, |
|
| 21 | uint256 shares |
|
| 22 | ); |
|
| 23 | ||
| 24 | 1× | constructor(MockERC20 _asset) MockERC20("MockERC7540Tester", "M7540", 18) { |
| 25 | 5× | asset = _asset; |
| 26 | } |
|
| 27 | ||
| 28 | 9× | function share() external view returns (address shareTokenAddress) { |
| 29 | 3× | return address(this); |
| 30 | } |
|
| 31 | ||
| 32 | 29× | function totalAssets() public view virtual returns (uint256) { |
| 33 | 295× | return asset.balanceOf(address(this)); |
| 34 | } |
|
| 35 | ||
| 36 | function convertToShares( |
|
| 37 | uint256 assets |
|
| 38 | ) public view virtual returns (uint256) { |
|
| 39 | uint256 supply = totalSupply; |
|
| 40 | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
|
| 41 | } |
|
| 42 | ||
| 43 | 24× | function convertToAssets( |
| 44 | uint256 shares |
|
| 45 | 8× | ) public view virtual returns (uint256) { |
| 46 | 8× | uint256 supply = totalSupply; |
| 47 | 121× | return supply == 0 ? shares : (shares * totalAssets()) / supply; |
| 48 | } |
|
| 49 | ||
| 50 | 9× | function maxDeposit(address) public pure virtual returns (uint256) { |
| 51 | 1× | return type(uint256).max; |
| 52 | } |
|
| 53 | ||
| 54 | function maxMint(address) public pure virtual returns (uint256) { |
|
| 55 | return type(uint256).max; |
|
| 56 | } |
|
| 57 | ||
| 58 | 12× | function maxWithdraw(address owner) public view virtual returns (uint256) { |
| 59 | 16× | return convertToAssets(balanceOf[owner]); |
| 60 | } |
|
| 61 | ||
| 62 | 10× | function maxRedeem(address owner) public view virtual returns (uint256) { |
| 63 | 12× | return balanceOf[owner]; |
| 64 | } |
|
| 65 | ||
| 66 | 34× | function previewDeposit( |
| 67 | uint256 assets |
|
| 68 | 4× | ) public view virtual returns (uint256) { |
| 69 | 16× | return convertToShares(assets); |
| 70 | } |
|
| 71 | ||
| 72 | function previewMint(uint256 shares) public view virtual returns (uint256) { |
|
| 73 | uint256 supply = totalSupply; |
|
| 74 | return supply == 0 ? shares : (shares * totalAssets()) / supply; |
|
| 75 | } |
|
| 76 | ||
| 77 | 34× | function previewWithdraw( |
| 78 | uint256 assets |
|
| 79 | 8× | ) public view virtual returns (uint256) { |
| 80 | 8× | uint256 supply = totalSupply; |
| 81 | 52× | return supply == 0 ? assets : (assets * supply) / totalAssets(); |
| 82 | } |
|
| 83 | ||
| 84 | 4× | function previewRedeem( |
| 85 | uint256 shares |
|
| 86 | 4× | ) public view virtual returns (uint256) { |
| 87 | 16× | return convertToAssets(shares); |
| 88 | } |
|
| 89 | ||
| 90 | 32× | function deposit( |
| 91 | uint256 assets, |
|
| 92 | address receiver |
|
| 93 | 2× | ) public virtual returns (uint256 shares) { |
| 94 | 14× | shares = previewDeposit(assets); |
| 95 | 150× | asset.transferFrom(msg.sender, address(this), assets); |
| 96 | 12× | _mint(receiver, shares); |
| 97 | 36× | emit Deposit(msg.sender, receiver, assets, shares); |
| 98 | } |
|
| 99 | ||
| 100 | 22× | function mint( |
| 101 | uint256 shares, |
|
| 102 | address receiver |
|
| 103 | 2× | ) public virtual returns (uint256 assets) { |
| 104 | 18× | assets = previewMint(shares); |
| 105 | 146× | asset.transferFrom(msg.sender, address(this), assets); |
| 106 | 12× | _mint(receiver, shares); |
| 107 | 22× | emit Deposit(msg.sender, receiver, assets, shares); |
| 108 | } |
|
| 109 | } |
|
| 110 | ||
| 111 | 1165× | contract MockERC7540Tester is ERC7575, IERC165 { |
| 112 | event DepositRequest( |
|
| 113 | address indexed controller, |
|
| 114 | address indexed owner, |
|
| 115 | uint256 indexed requestId, |
|
| 116 | address sender, |
|
| 117 | uint256 assets |
|
| 118 | ); |
|
| 119 | event RedeemRequest( |
|
| 120 | address indexed controller, |
|
| 121 | address indexed owner, |
|
| 122 | uint256 indexed requestId, |
|
| 123 | address sender, |
|
| 124 | uint256 shares |
|
| 125 | ); |
|
| 126 | event OperatorSet( |
|
| 127 | address indexed controller, |
|
| 128 | address indexed operator, |
|
| 129 | bool approved |
|
| 130 | ); |
|
| 131 | ||
| 132 | struct DepositRequestStruct { |
|
| 133 | uint256 assets; |
|
| 134 | address controller; |
|
| 135 | address owner; |
|
| 136 | bool fulfilled; |
|
| 137 | bool canceled; |
|
| 138 | } |
|
| 139 | ||
| 140 | struct RedeemRequestStruct { |
|
| 141 | uint256 shares; |
|
| 142 | address controller; |
|
| 143 | address owner; |
|
| 144 | bool fulfilled; |
|
| 145 | bool canceled; |
|
| 146 | } |
|
| 147 | ||
| 148 | 3× | uint256 private _nextRequestId = 1; |
| 149 | ||
| 150 | 0 | mapping(uint256 => DepositRequestStruct) public depositRequests; |
| 151 | 0 | mapping(uint256 => RedeemRequestStruct) public redeemRequests; |
| 152 | 0 | mapping(address => mapping(address => bool)) public operators; |
| 153 | 0 | mapping(uint256 => bool) public pendingCancelDeposit; |
| 154 | 0 | mapping(uint256 => bool) public pendingCancelRedeem; |
| 155 | ||
| 156 | 3× | uint256 public yieldMultiplier = 10000; // 100% in basis points |
| 157 | 5× | uint256 private constant MAX_BPS = 10000; |
| 158 | 0 | uint256 public totalLosses; |
| 159 | 0 | uint256 public totalGains; |
| 160 | 0 | uint256 public lossOnWithdraw; |
| 161 | ||
| 162 | 28× | constructor(address _asset) ERC7575(MockERC20(_asset)) {} |
| 163 | ||
| 164 | // Operator Management |
|
| 165 | 11× | function setOperator( |
| 166 | address operator, |
|
| 167 | bool approved |
|
| 168 | 3× | ) external returns (bool) { |
| 169 | 37× | operators[msg.sender][operator] = approved; |
| 170 | 5× | emit OperatorSet(msg.sender, operator, approved); |
| 171 | return true; |
|
| 172 | } |
|
| 173 | ||
| 174 | 0 | function isOperator( |
| 175 | address controller, |
|
| 176 | address operator |
|
| 177 | 0 | ) external view returns (bool) { |
| 178 | 0 | return operators[controller][operator]; |
| 179 | } |
|
| 180 | ||
| 181 | // Async Deposit Flow |
|
| 182 | 11× | function requestDeposit( |
| 183 | uint256 assets, |
|
| 184 | address controller, |
|
| 185 | address owner |
|
| 186 | 3× | ) external returns (uint256 requestId) { |
| 187 | 13× | requestId = _nextRequestId++; |
| 188 | 116× | depositRequests[requestId] = DepositRequestStruct({ |
| 189 | 2× | assets: assets, |
| 190 | controller: controller, |
|
| 191 | owner: owner, |
|
| 192 | fulfilled: false, |
|
| 193 | canceled: false |
|
| 194 | }); |
|
| 195 | ||
| 196 | 72× | asset.transferFrom(msg.sender, address(this), assets); |
| 197 | 17× | emit DepositRequest(controller, owner, requestId, msg.sender, assets); |
| 198 | } |
|
| 199 | ||
| 200 | 0 | function pendingDepositRequest( |
| 201 | uint256 requestId, |
|
| 202 | address controller |
|
| 203 | 0 | ) external view returns (uint256) { |
| 204 | 0 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 205 | 6× | if ( |
| 206 | 10× | request.controller != controller || |
| 207 | 0 | request.fulfilled || |
| 208 | 0 | request.canceled |
| 209 | ) { |
|
| 210 | 12× | return 0; |
| 211 | } |
|
| 212 | 0 | return request.assets; |
| 213 | } |
|
| 214 | ||
| 215 | function claimableDepositRequest( |
|
| 216 | uint256 requestId, |
|
| 217 | address controller |
|
| 218 | ) external view returns (uint256) { |
|
| 219 | DepositRequestStruct storage request = depositRequests[requestId]; |
|
| 220 | if ( |
|
| 221 | request.controller != controller || |
|
| 222 | request.fulfilled || |
|
| 223 | request.canceled |
|
| 224 | ) { |
|
| 225 | return 0; |
|
| 226 | } |
|
| 227 | return request.assets; |
|
| 228 | } |
|
| 229 | ||
| 230 | 0 | function pendingCancelDepositRequest( |
| 231 | uint256 requestId, |
|
| 232 | address controller |
|
| 233 | 0 | ) external view returns (bool) { |
| 234 | 0 | DepositRequestStruct storage request = depositRequests[requestId]; |
| 235 | return |
|
| 236 | 0 | request.controller == controller && pendingCancelDeposit[requestId]; |
| 237 | } |
|
| 238 | ||
| 239 | // Override deposit to handle async requests |
|
| 240 | 13× | function deposit( |
| 241 | uint256 assets, |
|
| 242 | address receiver, |
|
| 243 | address controller |
|
| 244 | 1× | ) public returns (uint256 shares) { |
| 245 | 11× | require( |
| 246 | 34× | msg.sender == controller || operators[controller][msg.sender], |
| 247 | "Not authorized" |
|
| 248 | ); |
|
| 249 | ||
| 250 | // Find and fulfill a matching deposit request |
|
| 251 | 15× | for (uint256 i = 1; i < _nextRequestId; i++) { |
| 252 | 10× | DepositRequestStruct storage request = depositRequests[i]; |
| 253 | 4× | if ( |
| 254 | 25× | request.controller == controller && |
| 255 | 9× | !request.fulfilled && |
| 256 | 9× | !request.canceled && |
| 257 | 4× | request.assets >= assets |
| 258 | ) { |
|
| 259 | 7× | shares = previewDeposit(assets); |
| 260 | 9× | request.fulfilled = true; |
| 261 | 5× | _mint(receiver, shares); |
| 262 | ||
| 263 | // Refund excess assets if any |
|
| 264 | 7× | if (request.assets > assets) { |
| 265 | 80× | asset.transfer(controller, request.assets - assets); |
| 266 | } |
|
| 267 | ||
| 268 | 17× | emit Deposit(msg.sender, receiver, assets, shares); |
| 269 | 4× | return shares; |
| 270 | } |
|
| 271 | } |
|
| 272 | ||
| 273 | // Fallback to synchronous deposit if no matching request |
|
| 274 | 7× | return super.deposit(assets, receiver); |
| 275 | } |
|
| 276 | ||
| 277 | 40× | function withdraw( |
| 278 | uint256 assets, |
|
| 279 | address receiver, |
|
| 280 | address owner |
|
| 281 | 5× | ) public returns (uint256 shares) { |
| 282 | 12× | shares = previewWithdraw(assets); |
| 283 | 14× | if (msg.sender != owner) { |
| 284 | 72× | allowance[owner][msg.sender] -= shares; |
| 285 | } |
|
| 286 | 12× | _burn(owner, shares); |
| 287 | 48× | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 288 | ||
| 289 | 118× | asset.transfer(receiver, lossyAssets); |
| 290 | 54× | emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 291 | } |
|
| 292 | ||
| 293 | 44× | function redeem( |
| 294 | uint256 shares, |
|
| 295 | address receiver, |
|
| 296 | address owner |
|
| 297 | 4× | ) public returns (uint256 assets) { |
| 298 | 24× | assets = previewRedeem(shares); |
| 299 | 28× | if (msg.sender != owner) { |
| 300 | 72× | allowance[owner][msg.sender] -= shares; |
| 301 | } |
|
| 302 | 22× | _burn(owner, shares); |
| 303 | 48× | uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
| 304 | ||
| 305 | 118× | asset.transfer(receiver, lossyAssets); |
| 306 | 32× | emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
| 307 | } |
|
| 308 | ||
| 309 | // Async Redeem Flow |
|
| 310 | 17× | function requestRedeem( |
| 311 | uint256 shares, |
|
| 312 | address controller, |
|
| 313 | address owner |
|
| 314 | 3× | ) external returns (uint256 requestId) { |
| 315 | 11× | requestId = _nextRequestId++; |
| 316 | 128× | redeemRequests[requestId] = RedeemRequestStruct({ |
| 317 | shares: shares, |
|
| 318 | controller: controller, |
|
| 319 | owner: owner, |
|
| 320 | fulfilled: false, |
|
| 321 | canceled: false |
|
| 322 | }); |
|
| 323 | ||
| 324 | 13× | emit RedeemRequest(controller, owner, requestId, msg.sender, shares); |
| 325 | } |
|
| 326 | ||
| 327 | function pendingRedeemRequest( |
|
| 328 | uint256 requestId, |
|
| 329 | address controller |
|
| 330 | ) external view returns (uint256) { |
|
| 331 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
|
| 332 | if ( |
|
| 333 | request.controller != controller || |
|
| 334 | request.fulfilled || |
|
| 335 | request.canceled |
|
| 336 | ) { |
|
| 337 | return 0; |
|
| 338 | } |
|
| 339 | return request.shares; |
|
| 340 | } |
|
| 341 | ||
| 342 | 22× | function claimableRedeemRequest( |
| 343 | uint256 requestId, |
|
| 344 | address controller |
|
| 345 | 2× | ) external view returns (uint256) { |
| 346 | 18× | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 347 | 0 | if ( |
| 348 | 28× | request.controller != controller || |
| 349 | 0 | request.fulfilled || |
| 350 | 0 | request.canceled |
| 351 | ) { |
|
| 352 | 0 | return 0; |
| 353 | } |
|
| 354 | return request.shares; |
|
| 355 | } |
|
| 356 | ||
| 357 | 0 | function pendingCancelRedeemRequest( |
| 358 | uint256 requestId, |
|
| 359 | address controller |
|
| 360 | 0 | ) external view returns (bool) { |
| 361 | 0 | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 362 | return |
|
| 363 | 0 | request.controller == controller && pendingCancelRedeem[requestId]; |
| 364 | } |
|
| 365 | ||
| 366 | // Cancel Operations |
|
| 367 | 22× | function cancelDepositRequest( |
| 368 | uint256 requestId, |
|
| 369 | address controller |
|
| 370 | ) external { |
|
| 371 | 14× | require( |
| 372 | 42× | msg.sender == controller || operators[controller][msg.sender], |
| 373 | "Not authorized" |
|
| 374 | ); |
|
| 375 | 20× | DepositRequestStruct storage request = depositRequests[requestId]; |
| 376 | 20× | require( |
| 377 | 30× | request.controller == controller && !request.fulfilled, |
| 378 | "Invalid request" |
|
| 379 | ); |
|
| 380 | ||
| 381 | 0 | pendingCancelDeposit[requestId] = true; |
| 382 | } |
|
| 383 | ||
| 384 | 34× | function claimCancelDepositRequest( |
| 385 | uint256 requestId, |
|
| 386 | address receiver, |
|
| 387 | address controller |
|
| 388 | 4× | ) external returns (uint256 assets) { |
| 389 | 26× | DepositRequestStruct storage request = depositRequests[requestId]; |
| 390 | ||
| 391 | 8× | assets = request.assets; |
| 392 | 18× | request.canceled = true; |
| 393 | 26× | pendingCancelDeposit[requestId] = false; |
| 394 | ||
| 395 | 121× | asset.transfer(receiver, assets); |
| 396 | } |
|
| 397 | ||
| 398 | 24× | function cancelRedeemRequest( |
| 399 | uint256 requestId, |
|
| 400 | address controller |
|
| 401 | ) external { |
|
| 402 | 12× | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 403 | ||
| 404 | 22× | pendingCancelRedeem[requestId] = true; |
| 405 | } |
|
| 406 | ||
| 407 | 89× | function claimCancelRedeemRequest( |
| 408 | uint256 requestId, |
|
| 409 | address receiver, |
|
| 410 | address controller |
|
| 411 | 4× | ) external returns (uint256 shares) { |
| 412 | 24× | RedeemRequestStruct storage request = redeemRequests[requestId]; |
| 413 | ||
| 414 | 8× | shares = request.shares; |
| 415 | 18× | request.canceled = true; |
| 416 | 22× | pendingCancelRedeem[requestId] = false; |
| 417 | ||
| 418 | 10× | _mint(receiver, shares); |
| 419 | } |
|
| 420 | ||
| 421 | // Placeholder functions for Centrifuge compatibility |
|
| 422 | 0 | function poolId() external pure returns (uint64) { |
| 423 | 0 | return 1; |
| 424 | } |
|
| 425 | ||
| 426 | 0 | function trancheId() external pure returns (bytes16) { |
| 427 | return bytes16(uint128(1)); |
|
| 428 | } |
|
| 429 | ||
| 430 | // Yield simulation functions (similar to MockERC4626Tester) |
|
| 431 | // Primary way that 7540 vaults receive losses is on rounding in redemptions so we just simulate a loss that reduces total asset balance |
|
| 432 | 0 | function increaseYield(uint256 increasePercentageFP4) external { |
| 433 | 0 | uint256 amount = (totalAssets() * increasePercentageFP4) / MAX_BPS; |
| 434 | 0 | MockERC20(asset).transferFrom(msg.sender, address(this), amount); |
| 435 | } |
|
| 436 | ||
| 437 | 0 | function decreaseYield(uint256 decreasePercentageFP4) external { |
| 438 | 0 | uint256 amount = (totalAssets() * decreasePercentageFP4) / MAX_BPS; |
| 439 | 0 | MockERC20(asset).transfer(address(0xbeef), amount); |
| 440 | } |
|
| 441 | ||
| 442 | 12× | function simulateGain(uint256 gainAmount) external { |
| 443 | 75× | MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
| 444 | 15× | totalGains += gainAmount; |
| 445 | } |
|
| 446 | ||
| 447 | 11× | function simulateLoss(uint256 lossAmount) external { |
| 448 | 66× | MockERC20(asset).transfer(address(0xbeef), lossAmount); |
| 449 | 11× | totalLosses += lossAmount; |
| 450 | } |
|
| 451 | ||
| 452 | /// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
|
| 453 | 12× | function setLossOnWithdraw(uint256 _lossOnWithdraw) public { |
| 454 | 11× | _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
| 455 | 2× | lossOnWithdraw = _lossOnWithdraw; |
| 456 | } |
|
| 457 | ||
| 458 | /// @notice ERC165 interface detection |
|
| 459 | 4× | function supportsInterface( |
| 460 | bytes4 interfaceId |
|
| 461 | ) external pure override returns (bool) { |
|
| 462 | 0 | return interfaceId == type(IERC165).interfaceId; |
| 463 | } |
|
| 464 | } |
10%
test/recon/mocks/MockERC7540YieldSourceOracle.sol
Lines covered: 6 / 60 (10%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | pragma solidity ^0.8.30; |
|
| 3 | ||
| 4 | import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; |
|
| 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 6 | import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol"; |
|
| 7 | import {MockERC7540Tester} from "./MockERC7540Tester.sol"; |
|
| 8 | ||
| 9 | /// @title MockERC7540YieldSourceOracle |
|
| 10 | /// @notice Mock oracle for ERC7540 (Asynchronous Tokenized Vaults) in testing |
|
| 11 | 278× | contract MockERC7540YieldSourceOracle is IYieldSourceOracle { |
| 12 | 0 | mapping(address => bool) public validAssetMap; |
| 13 | ||
| 14 | 11× | function setValidAsset(address asset, bool isValid) external { |
| 15 | 27× | validAssetMap[asset] = isValid; |
| 16 | } |
|
| 17 | ||
| 18 | 0 | function decimals(address yieldSourceAddress) external view returns (uint8) { |
| 19 | 0 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 20 | 0 | return IERC20Metadata(share).decimals(); |
| 21 | } |
|
| 22 | ||
| 23 | 48× | function getShareOutput( |
| 24 | address yieldSourceAddress, |
|
| 25 | address, |
|
| 26 | uint256 assetsIn |
|
| 27 | 4× | ) external view returns (uint256) { |
| 28 | 130× | return MockERC7540Tester(yieldSourceAddress).convertToShares(assetsIn); |
| 29 | } |
|
| 30 | ||
| 31 | 0 | function getAssetOutput( |
| 32 | address yieldSourceAddress, |
|
| 33 | address, |
|
| 34 | uint256 sharesIn |
|
| 35 | 0 | ) public view returns (uint256) { |
| 36 | 0 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
| 37 | } |
|
| 38 | ||
| 39 | 0 | function getPricePerShare(address yieldSourceAddress) external view returns (uint256) { |
| 40 | 0 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 41 | 0 | uint256 _decimals = IERC20Metadata(share).decimals(); |
| 42 | 0 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(10 ** _decimals); |
| 43 | } |
|
| 44 | ||
| 45 | 0 | function getBalanceOfOwner( |
| 46 | address yieldSourceAddress, |
|
| 47 | address ownerOfShares |
|
| 48 | 0 | ) external view returns (uint256) { |
| 49 | 0 | return IERC20(MockERC7540Tester(yieldSourceAddress).share()).balanceOf(ownerOfShares); |
| 50 | } |
|
| 51 | ||
| 52 | 0 | function getTVLByOwnerOfShares( |
| 53 | address yieldSourceAddress, |
|
| 54 | address ownerOfShares |
|
| 55 | 0 | ) external view returns (uint256) { |
| 56 | 0 | address share = MockERC7540Tester(yieldSourceAddress).share(); |
| 57 | 0 | uint256 shares = IERC20(share).balanceOf(ownerOfShares); |
| 58 | 0 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(shares); |
| 59 | } |
|
| 60 | ||
| 61 | 0 | function getTVL(address yieldSourceAddress) external view returns (uint256) { |
| 62 | 0 | return MockERC7540Tester(yieldSourceAddress).totalAssets(); |
| 63 | } |
|
| 64 | ||
| 65 | 0 | function getPricePerShareMultiple( |
| 66 | address[] memory yieldSourceAddresses |
|
| 67 | 0 | ) external view returns (uint256[] memory) { |
| 68 | 0 | uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
| 69 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 70 | 0 | address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
| 71 | 0 | uint256 _decimals = IERC20Metadata(share).decimals(); |
| 72 | 0 | prices[i] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(10 ** _decimals); |
| 73 | } |
|
| 74 | 0 | return prices; |
| 75 | } |
|
| 76 | ||
| 77 | 0 | function getTVLByOwnerOfSharesMultiple( |
| 78 | address[] memory yieldSourceAddresses, |
|
| 79 | address[][] memory ownersOfShares |
|
| 80 | 0 | ) external view returns (uint256[][] memory) { |
| 81 | 0 | uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
| 82 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 83 | 0 | result[i] = new uint256[](ownersOfShares[i].length); |
| 84 | 0 | address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
| 85 | 0 | for (uint256 j = 0; j < ownersOfShares[i].length; j++) { |
| 86 | 0 | uint256 shares = IERC20(share).balanceOf(ownersOfShares[i][j]); |
| 87 | 0 | result[i][j] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(shares); |
| 88 | } |
|
| 89 | } |
|
| 90 | 0 | return result; |
| 91 | } |
|
| 92 | ||
| 93 | 0 | function getTVLMultiple( |
| 94 | address[] memory yieldSourceAddresses |
|
| 95 | 0 | ) external view returns (uint256[] memory) { |
| 96 | 0 | uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
| 97 | 0 | for (uint256 i = 0; i < yieldSourceAddresses.length; i++) { |
| 98 | 0 | tvls[i] = MockERC7540Tester(yieldSourceAddresses[i]).totalAssets(); |
| 99 | } |
|
| 100 | return tvls; |
|
| 101 | } |
|
| 102 | ||
| 103 | 0 | function isValidUnderlyingAsset(address, address asset) external view returns (bool) { |
| 104 | 0 | return validAssetMap[asset]; |
| 105 | } |
|
| 106 | ||
| 107 | 0 | function isValidUnderlyingAssets( |
| 108 | address[] memory, |
|
| 109 | address[] memory assets |
|
| 110 | 0 | ) external view returns (bool[] memory) { |
| 111 | 0 | bool[] memory validities = new bool[](assets.length); |
| 112 | 0 | for (uint256 i = 0; i < assets.length; i++) { |
| 113 | 0 | validities[i] = validAssetMap[assets[i]]; |
| 114 | } |
|
| 115 | return validities; |
|
| 116 | } |
|
| 117 | ||
| 118 | 0 | function getAssetOutputWithFees( |
| 119 | bytes32, |
|
| 120 | address yieldSourceAddress, |
|
| 121 | address, |
|
| 122 | address, |
|
| 123 | uint256 sharesIn |
|
| 124 | 0 | ) external view returns (uint256) { |
| 125 | 0 | return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
| 126 | } |
|
| 127 | } |
92%
test/recon/targets/AdminTargets.sol
Lines covered: 187 / 203 (92%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // External dependencies |
|
| 5 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {Panic} from "@recon/Panic.sol"; |
|
| 8 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 9 | ||
| 10 | // System dependencies |
|
| 11 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 12 | import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; |
|
| 13 | ||
| 14 | // Test dependencies |
|
| 15 | import {YieldSourceType} from "test/recon/managers/YieldManager.sol"; |
|
| 16 | import {BeforeAfter, OpType} from "../BeforeAfter.sol"; |
|
| 17 | import {Properties} from "../Properties.sol"; |
|
| 18 | ||
| 19 | abstract contract AdminTargets is BaseTargetFunctions, Properties { |
|
| 20 | enum HookType { |
|
| 21 | // ERC4626 Hooks |
|
| 22 | ApproveAndDeposit4626, |
|
| 23 | Deposit4626, |
|
| 24 | Redeem4626, |
|
| 25 | // ERC5115 Hooks |
|
| 26 | ApproveAndDeposit5115, |
|
| 27 | Deposit5115, |
|
| 28 | Redeem5115, |
|
| 29 | // ERC7540 Hooks |
|
| 30 | Deposit7540, |
|
| 31 | Redeem7540, |
|
| 32 | RequestDeposit7540, |
|
| 33 | RequestRedeem7540, |
|
| 34 | ApproveAndRequestDeposit7540, |
|
| 35 | CancelDepositRequest7540, |
|
| 36 | CancelRedeemRequest7540, |
|
| 37 | ClaimCancelDepositRequest7540, |
|
| 38 | ClaimCancelRedeemRequest7540, |
|
| 39 | Withdraw7540, |
|
| 40 | // Super Vault Hooks |
|
| 41 | CancelRedeem, |
|
| 42 | SuperVaultWithdraw7540 |
|
| 43 | } |
|
| 44 | ||
| 45 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 46 | 15× | function superVaultStrategy_executeHooks_clamped( |
| 47 | uint256[] memory hookTypeInts, |
|
| 48 | uint256[] memory amountsToInvest, |
|
| 49 | bool[] memory usePrevHookAmounts |
|
| 50 | 0 | ) public payable { |
| 51 | // Limit the number of hooks to 10 maximum |
|
| 52 | 2× | uint256 numHooks = hookTypeInts.length; |
| 53 | 7× | if (numHooks > 10) { |
| 54 | 1× | numHooks = 10; |
| 55 | } |
|
| 56 | ||
| 57 | // Ensure all arrays have the same length |
|
| 58 | 8× | if (amountsToInvest.length < numHooks) { |
| 59 | 2× | numHooks = amountsToInvest.length; |
| 60 | } |
|
| 61 | 8× | if (usePrevHookAmounts.length < numHooks) { |
| 62 | 2× | numHooks = usePrevHookAmounts.length; |
| 63 | } |
|
| 64 | ||
| 65 | // Return early if no hooks to execute |
|
| 66 | 6× | if (numHooks == 0) { |
| 67 | 0 | return; |
| 68 | } |
|
| 69 | ||
| 70 | // Create ExecuteArgs for the hooks |
|
| 71 | 28× | ISuperVaultStrategy.ExecuteArgs memory executeArgs = ISuperVaultStrategy |
| 72 | .ExecuteArgs({ |
|
| 73 | 40× | hooks: new address[](numHooks), |
| 74 | 47× | hookCalldata: new bytes[](numHooks), |
| 75 | 40× | expectedAssetsOrSharesOut: new uint256[](numHooks), |
| 76 | 47× | globalProofs: new bytes32[][](numHooks), |
| 77 | 46× | strategyProofs: new bytes32[][](numHooks) |
| 78 | }); |
|
| 79 | ||
| 80 | // Process each hook |
|
| 81 | 2× | uint256 totalAmountToDeposit; |
| 82 | 17× | for (uint256 i = 0; i < numHooks; i++) { |
| 83 | // Convert integer to enum (will wrap around if > max enum value) |
|
| 84 | 32× | HookType hookType = HookType(hookTypeInts[i] % 17); // 17 is the total number of hooks |
| 85 | ||
| 86 | // Clamp to the strategy's asset balance (not SuperVault's balance) |
|
| 87 | 24× | uint256 clampedAmount = amountsToInvest[i] % |
| 88 | 128× | (MockERC20(superVault.asset()).balanceOf( |
| 89 | 5× | address(superVaultStrategy) |
| 90 | 1× | ) + 1); |
| 91 | ||
| 92 | // Get the hook address and calldata |
|
| 93 | 4× | ( |
| 94 | 1× | address hookAddress, |
| 95 | 1× | bytes memory hookCalldata |
| 96 | 4× | ) = _getHookAddressAndCalldata( |
| 97 | 1× | hookType, |
| 98 | 1× | clampedAmount, |
| 99 | 15× | usePrevHookAmounts[i] |
| 100 | ); |
|
| 101 | ||
| 102 | 27× | executeArgs.hooks[i] = hookAddress; |
| 103 | 22× | executeArgs.hookCalldata[i] = hookCalldata; |
| 104 | 23× | executeArgs.expectedAssetsOrSharesOut[i] = clampedAmount; |
| 105 | 49× | executeArgs.globalProofs[i] = new bytes32[](1); |
| 106 | 49× | executeArgs.strategyProofs[i] = new bytes32[](1); |
| 107 | ||
| 108 | 7× | totalAmountToDeposit += clampedAmount; |
| 109 | } |
|
| 110 | ||
| 111 | // Check that amount to be invested is less than the claimable assets so that it doesn't reinvest and prevent users from claiming |
|
| 112 | 12× | if (_claimableMoreThanInvested(totalAmountToDeposit)) return; |
| 113 | ||
| 114 | // Execute all hooks |
|
| 115 | 51× | this.superVaultStrategy_executeHooks{value: msg.value}(executeArgs); |
| 116 | } |
|
| 117 | ||
| 118 | 8× | function _getHookAddressAndCalldata( |
| 119 | HookType hookType, |
|
| 120 | uint256 amountToInvest, |
|
| 121 | bool usePrevHookAmount |
|
| 122 | 3× | ) internal view returns (address hookAddress, bytes memory hookCalldata) { |
| 123 | 14× | if (hookType == HookType.ApproveAndDeposit4626) { |
| 124 | 5× | hookAddress = address(approveAndDeposit4626Hook); |
| 125 | 28× | hookCalldata = abi.encodePacked( |
| 126 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 127 | 4× | _getYieldSource(), // Address of the yield source |
| 128 | 67× | superVault.asset(), // Address of the token to approve and deposit |
| 129 | 1× | amountToInvest, // Amount to deposit |
| 130 | 1× | usePrevHookAmount |
| 131 | ); |
|
| 132 | 13× | } else if (hookType == HookType.Deposit4626) { |
| 133 | 5× | hookAddress = address(deposit4626Hook); |
| 134 | 12× | hookCalldata = abi.encodePacked( |
| 135 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 136 | 4× | _getYieldSource(), // Address of the yield source |
| 137 | 1× | amountToInvest, // Amount to deposit |
| 138 | 1× | usePrevHookAmount |
| 139 | ); |
|
| 140 | 13× | } else if (hookType == HookType.Redeem4626) { |
| 141 | 5× | hookAddress = address(redeem4626Hook); |
| 142 | 7× | hookCalldata = abi.encodePacked( |
| 143 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 144 | 4× | _getYieldSource(), // Address of the yield source |
| 145 | amountToInvest, // Amount to redeem |
|
| 146 | 2× | address(superVaultStrategy), // Receiver |
| 147 | address(superVaultStrategy) // Owner |
|
| 148 | ); |
|
| 149 | 13× | } else if (hookType == HookType.ApproveAndDeposit5115) { |
| 150 | 5× | hookAddress = address(approveAndDeposit5115Hook); |
| 151 | 7× | hookCalldata = abi.encodePacked( |
| 152 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 153 | 4× | _getYieldSource(), // Address of the yield source |
| 154 | 67× | superVault.asset(), // Address of the token to approve and deposit |
| 155 | 1× | bytes32(0), // tokenId (for ERC5115) |
| 156 | amountToInvest, // Amount to deposit |
|
| 157 | usePrevHookAmount |
|
| 158 | ); |
|
| 159 | 13× | } else if (hookType == HookType.Deposit5115) { |
| 160 | 5× | hookAddress = address(deposit5115Hook); |
| 161 | 7× | hookCalldata = abi.encodePacked( |
| 162 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 163 | 4× | _getYieldSource(), // Address of the yield source |
| 164 | bytes32(0), // tokenId (for ERC5115) |
|
| 165 | amountToInvest, // Amount to deposit |
|
| 166 | usePrevHookAmount |
|
| 167 | ); |
|
| 168 | 13× | } else if (hookType == HookType.Redeem5115) { |
| 169 | 5× | hookAddress = address(redeem5115Hook); |
| 170 | 7× | hookCalldata = abi.encodePacked( |
| 171 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 172 | 4× | _getYieldSource(), // Address of the yield source |
| 173 | 1× | bytes32(0), // tokenId (for ERC5115) |
| 174 | amountToInvest, // Amount to redeem |
|
| 175 | 2× | address(superVaultStrategy), // Receiver |
| 176 | address(superVaultStrategy) // Owner |
|
| 177 | ); |
|
| 178 | 13× | } else if (hookType == HookType.Deposit7540) { |
| 179 | 5× | hookAddress = address(deposit7540Hook); |
| 180 | hookCalldata = abi.encodePacked( |
|
| 181 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 182 | 3× | _getYieldSource(), // Address of the yield source |
| 183 | amountToInvest, // Amount to deposit |
|
| 184 | address(superVaultStrategy), // Receiver |
|
| 185 | address(superVaultStrategy) // Controller |
|
| 186 | ); |
|
| 187 | 13× | } else if (hookType == HookType.Redeem7540) { |
| 188 | 5× | hookAddress = address(redeem7540Hook); |
| 189 | 10× | hookCalldata = abi.encodePacked( |
| 190 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 191 | 4× | _getYieldSource(), // Address of the yield source |
| 192 | 2× | amountToInvest, // Amount to redeem |
| 193 | 10× | address(superVaultStrategy), // Receiver |
| 194 | address(superVaultStrategy), // Owner |
|
| 195 | address(superVaultStrategy) // Controller |
|
| 196 | ); |
|
| 197 | 13× | } else if (hookType == HookType.RequestDeposit7540) { |
| 198 | 5× | hookAddress = address(requestDeposit7540Hook); |
| 199 | hookCalldata = abi.encodePacked( |
|
| 200 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 201 | 3× | _getYieldSource(), // Address of the yield source |
| 202 | amountToInvest, // Amount to request deposit |
|
| 203 | address(superVaultStrategy), // Owner |
|
| 204 | address(superVaultStrategy) // Controller |
|
| 205 | ); |
|
| 206 | 13× | } else if (hookType == HookType.RequestRedeem7540) { |
| 207 | 5× | hookAddress = address(requestRedeem7540Hook); |
| 208 | hookCalldata = abi.encodePacked( |
|
| 209 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 210 | 3× | _getYieldSource(), // Address of the yield source |
| 211 | amountToInvest, // Amount to request redeem |
|
| 212 | address(superVaultStrategy), // Owner |
|
| 213 | address(superVaultStrategy) // Controller |
|
| 214 | ); |
|
| 215 | 13× | } else if (hookType == HookType.ApproveAndRequestDeposit7540) { |
| 216 | 5× | hookAddress = address(approveAndRequestDeposit7540Hook); |
| 217 | 7× | hookCalldata = abi.encodePacked( |
| 218 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 219 | 4× | _getYieldSource(), // Address of the yield source |
| 220 | 67× | superVault.asset(), // Address of the token to approve |
| 221 | amountToInvest, // Amount to request deposit |
|
| 222 | 2× | address(superVaultStrategy), // Owner |
| 223 | address(superVaultStrategy) // Controller |
|
| 224 | ); |
|
| 225 | 13× | } else if (hookType == HookType.CancelDepositRequest7540) { |
| 226 | 5× | hookAddress = address(cancelDepositRequest7540Hook); |
| 227 | 8× | hookCalldata = abi.encodePacked( |
| 228 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 229 | 4× | _getYieldSource(), // Address of the yield source |
| 230 | 4× | address(superVaultStrategy) // Controller |
| 231 | ); |
|
| 232 | 13× | } else if (hookType == HookType.CancelRedeemRequest7540) { |
| 233 | 5× | hookAddress = address(cancelRedeemRequest7540Hook); |
| 234 | hookCalldata = abi.encodePacked( |
|
| 235 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 236 | 3× | _getYieldSource(), // Address of the yield source |
| 237 | address(superVaultStrategy) // Controller |
|
| 238 | ); |
|
| 239 | 13× | } else if (hookType == HookType.ClaimCancelDepositRequest7540) { |
| 240 | 5× | hookAddress = address(claimCancelDepositRequest7540Hook); |
| 241 | 7× | hookCalldata = abi.encodePacked( |
| 242 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 243 | 4× | _getYieldSource(), // Address of the yield source |
| 244 | 2× | address(superVaultStrategy), // Receiver |
| 245 | address(superVaultStrategy) // Controller |
|
| 246 | ); |
|
| 247 | 13× | } else if (hookType == HookType.ClaimCancelRedeemRequest7540) { |
| 248 | 5× | hookAddress = address(claimCancelRedeemRequest7540Hook); |
| 249 | hookCalldata = abi.encodePacked( |
|
| 250 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 251 | 3× | _getYieldSource(), // Address of the yield source |
| 252 | address(superVaultStrategy), // Receiver |
|
| 253 | address(superVaultStrategy) // Controller |
|
| 254 | ); |
|
| 255 | 13× | } else if (hookType == HookType.Withdraw7540) { |
| 256 | 5× | hookAddress = address(withdraw7540Hook); |
| 257 | hookCalldata = abi.encodePacked( |
|
| 258 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 259 | 3× | _getYieldSource(), // Address of the yield source |
| 260 | amountToInvest, // Amount to withdraw |
|
| 261 | address(superVaultStrategy), // Receiver |
|
| 262 | address(superVaultStrategy), // Owner |
|
| 263 | address(superVaultStrategy) // Controller |
|
| 264 | ); |
|
| 265 | 12× | } else if (hookType == HookType.CancelRedeem) { |
| 266 | 5× | hookAddress = address(cancelRedeemHook); |
| 267 | hookCalldata = abi.encodePacked( |
|
| 268 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 269 | 3× | _getYieldSource(), // Address of the yield source |
| 270 | address(superVaultStrategy) // Controller |
|
| 271 | ); |
|
| 272 | 1× | } else if (hookType == HookType.SuperVaultWithdraw7540) { |
| 273 | 0 | hookAddress = address(superVaultWithdraw7540Hook); |
| 274 | 0 | hookCalldata = abi.encodePacked( |
| 275 | bytes32(0), // yieldSourceOracleId placeholder |
|
| 276 | 0 | _getYieldSource(), // Address of the yield source |
| 277 | 0 | amountToInvest, // Amount to withdraw |
| 278 | 0 | address(superVaultStrategy), // Receiver |
| 279 | address(superVaultStrategy), // Owner |
|
| 280 | address(superVaultStrategy) // Controller |
|
| 281 | ); |
|
| 282 | } |
|
| 283 | } |
|
| 284 | ||
| 285 | 20× | function superVaultStrategy_fulfillRedeemRequests_clamped( |
| 286 | uint256 redeemAmount |
|
| 287 | 0 | ) public { |
| 288 | // Find a controller that has pending redeem requests |
|
| 289 | 3× | address selectedController = _getActor(); |
| 290 | 63× | uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
| 291 | selectedController |
|
| 292 | ); |
|
| 293 | ||
| 294 | // Clamp using the actor's pending amount |
|
| 295 | 14× | uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
| 296 | ||
| 297 | 30× | address[] memory controllers = new address[](1); |
| 298 | 24× | controllers[0] = selectedController; |
| 299 | ||
| 300 | // Determine yield source type from currently active yield source |
|
| 301 | 9× | YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress( |
| 302 | 5× | _getYieldSource() |
| 303 | ); |
|
| 304 | 7× | address redeemHook = _getRedeemHookForType(activeYieldSourceType); |
| 305 | ||
| 306 | // Create realistic hook calldata for redeem operation |
|
| 307 | 1× | bytes memory redeemHookCalldata; |
| 308 | ||
| 309 | 7× | if ( |
| 310 | 14× | activeYieldSourceType == YieldSourceType.ERC4626 || |
| 311 | 10× | activeYieldSourceType == YieldSourceType.ERC5115 |
| 312 | ) { |
|
| 313 | // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
|
| 314 | 25× | redeemHookCalldata = abi.encodePacked( |
| 315 | 1× | bytes32(0), // yieldSourceOracleId placeholder |
| 316 | 4× | _getYieldSource(), // Current active yield source |
| 317 | 6× | address(superVaultStrategy), // Owner (strategy owns the yield source shares) |
| 318 | 2× | actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
| 319 | false // Don't use previous hook amount |
|
| 320 | ); |
|
| 321 | } else { |
|
| 322 | // ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
|
| 323 | 27× | redeemHookCalldata = abi.encodePacked( |
| 324 | 1× | bytes32(0), // yieldSourceOracleId placeholder |
| 325 | 4× | _getYieldSource(), // Current active yield source |
| 326 | 1× | actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
| 327 | 1× | false // Don't use previous hook amount |
| 328 | ); |
|
| 329 | } |
|
| 330 | ||
| 331 | // Create arrays for FulfillArgs |
|
| 332 | 29× | address[] memory hooks = new address[](1); |
| 333 | 26× | hooks[0] = redeemHook; |
| 334 | ||
| 335 | 37× | bytes[] memory hookCalldata = new bytes[](1); |
| 336 | 20× | hookCalldata[0] = redeemHookCalldata; |
| 337 | ||
| 338 | 30× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 339 | 20× | expectedAssetsOrSharesOut[0] = 1; // @audit Allow max losse amount matching the actual redeem |
| 340 | ||
| 341 | 34× | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 342 | 33× | globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
| 343 | ||
| 344 | 34× | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 345 | 32× | strategyProofs[0] = new bytes32[](0); // Empty proof |
| 346 | ||
| 347 | // Create the FulfillArgs struct |
|
| 348 | 34× | ISuperVaultStrategy.FulfillArgs memory fulfillArgs = ISuperVaultStrategy |
| 349 | .FulfillArgs({ |
|
| 350 | 1× | controllers: controllers, |
| 351 | 1× | hooks: hooks, |
| 352 | 1× | hookCalldata: hookCalldata, |
| 353 | 1× | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
| 354 | 1× | globalProofs: globalProofs, |
| 355 | 1× | strategyProofs: strategyProofs |
| 356 | }); |
|
| 357 | ||
| 358 | // Execute the function |
|
| 359 | 4× | superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT(fulfillArgs); |
| 360 | } |
|
| 361 | ||
| 362 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 363 | ||
| 364 | 22× | function superVaultStrategy_executeHooks( |
| 365 | ISuperVaultStrategy.ExecuteArgs memory args |
|
| 366 | ) public payable asAdmin { |
|
| 367 | 110× | superVaultStrategy.executeHooks{value: msg.value}(args); |
| 368 | ||
| 369 | 0 | executeHooksSuccess = true; |
| 370 | } |
|
| 371 | ||
| 372 | /// @dev Property: superVaultStrategy does not incur loss on fulfillment |
|
| 373 | 20× | function superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT( |
| 374 | ISuperVaultStrategy.FulfillArgs memory args |
|
| 375 | 1× | ) public updateGhostsWithOpType(OpType.FULFILL) { |
| 376 | 1× | uint256 summedExpectedAssets; |
| 377 | 17× | for (uint256 i; i < args.expectedAssetsOrSharesOut.length; i++) { |
| 378 | 26× | summedExpectedAssets += args.expectedAssetsOrSharesOut[i]; |
| 379 | } |
|
| 380 | ||
| 381 | // no need to prank because called as admin address(this) |
|
| 382 | 53× | superVaultStrategy.fulfillRedeemRequests(args); |
| 383 | ||
| 384 | 0 | uint256 assetBalanceAfter = MockERC20(superVault.asset()).balanceOf( |
| 385 | 0 | address(superVaultStrategy) |
| 386 | ); |
|
| 387 | ||
| 388 | 0 | gte( |
| 389 | 0 | assetBalanceAfter, |
| 390 | 0 | summedExpectedAssets, |
| 391 | 0 | ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT |
| 392 | ); |
|
| 393 | } |
|
| 394 | ||
| 395 | // Functions that require SuperGovernor access |
|
| 396 | /// @dev removed because we're bypassing hook validation |
|
| 397 | // function superVaultAggregator_setHooksRootUpdateTimelock( |
|
| 398 | // uint256 newTimelock |
|
| 399 | // ) public asAdmin { |
|
| 400 | // superVaultAggregator.setHooksRootUpdateTimelock(newTimelock); |
|
| 401 | // } |
|
| 402 | ||
| 403 | /// @dev removed because we're bypassing hook validation |
|
| 404 | // function superVaultAggregator_proposeGlobalHooksRoot( |
|
| 405 | // bytes32 newRoot |
|
| 406 | // ) public asAdmin { |
|
| 407 | // superVaultAggregator.proposeGlobalHooksRoot(newRoot); |
|
| 408 | // } |
|
| 409 | ||
| 410 | /// @dev removed because we're bypassing hook validation |
|
| 411 | // function superVaultAggregator_executeGlobalHooksRootUpdate() |
|
| 412 | // public |
|
| 413 | // asAdmin |
|
| 414 | // { |
|
| 415 | // superVaultAggregator.executeGlobalHooksRootUpdate(); |
|
| 416 | // } |
|
| 417 | ||
| 418 | /// @dev removed because we're bypassing hook validation |
|
| 419 | // function superVaultAggregator_setGlobalHooksRootVetoStatus( |
|
| 420 | // bool vetoed |
|
| 421 | // ) public asAdmin { |
|
| 422 | // superVaultAggregator.setGlobalHooksRootVetoStatus(vetoed); |
|
| 423 | // } |
|
| 424 | ||
| 425 | /// @dev removed because we're bypassing hook validation |
|
| 426 | // function superVaultAggregator_setStrategyHooksRootVetoStatus( |
|
| 427 | // address strategy, |
|
| 428 | // bool vetoed |
|
| 429 | // ) public asAdmin { |
|
| 430 | // superVaultAggregator.setStrategyHooksRootVetoStatus(strategy, vetoed); |
|
| 431 | // } |
|
| 432 | ||
| 433 | 20× | function superVaultAggregator_changePrimaryManager( |
| 434 | address strategy, |
|
| 435 | address newManager |
|
| 436 | ) public asAdmin { |
|
| 437 | 22× | superVaultAggregator.changePrimaryManager(strategy, newManager); |
| 438 | } |
|
| 439 | ||
| 440 | /// @dev won't achieve coverage until issue outlined here is resolved: https://github.com/Recon-Fuzz/superform-review/issues/5 |
|
| 441 | 20× | function superVaultAggregator_slashStake( |
| 442 | address manager, |
|
| 443 | uint256 amount |
|
| 444 | ) public asAdmin { |
|
| 445 | 22× | superVaultAggregator.slashStake(manager, amount); |
| 446 | } |
|
| 447 | ||
| 448 | /// Helpers |
|
| 449 | ||
| 450 | function _requestedSharesForControllers( |
|
| 451 | address[] memory controllers |
|
| 452 | ) internal returns (uint256) { |
|
| 453 | uint256 totalRequested; |
|
| 454 | for (uint256 i; i < controllers.length; i++) { |
|
| 455 | totalRequested += superVault.pendingRedeemRequest( |
|
| 456 | 0, |
|
| 457 | controllers[i] |
|
| 458 | ); |
|
| 459 | } |
|
| 460 | ||
| 461 | return totalRequested; |
|
| 462 | } |
|
| 463 | ||
| 464 | function _sumSuperVaultValsForControllers( |
|
| 465 | address[] memory controllers |
|
| 466 | ) |
|
| 467 | internal |
|
| 468 | view |
|
| 469 | returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis) |
|
| 470 | { |
|
| 471 | for (uint256 i; i < controllers.length; i++) { |
|
| 472 | sumAccumulatorShares += superVaultStrategy |
|
| 473 | .getSuperVaultState(controllers[i]) |
|
| 474 | .accumulatorShares; |
|
| 475 | sumAccumulatorCostBasis += superVaultStrategy |
|
| 476 | .getSuperVaultState(controllers[i]) |
|
| 477 | .accumulatorCostBasis; |
|
| 478 | } |
|
| 479 | } |
|
| 480 | ||
| 481 | 5× | function _claimableMoreThanInvested( |
| 482 | uint256 totalAmountToDeposit |
|
| 483 | 1× | ) internal returns (bool) { |
| 484 | 7× | address[] memory actors = _getActors(); |
| 485 | 1× | uint256 totalClaimable; |
| 486 | 14× | for (uint256 i; i < actors.length; i++) { |
| 487 | 74× | uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
| 488 | 0, |
|
| 489 | 17× | actors[i] |
| 490 | ); |
|
| 491 | 63× | uint256 claimableRedemptionsAsAssets = superVault.convertToAssets( |
| 492 | claimableRedemptions |
|
| 493 | ); |
|
| 494 | 6× | totalClaimable += claimableRedemptionsAsAssets; |
| 495 | } |
|
| 496 | ||
| 497 | 124× | uint256 currentStrategyBalance = MockERC20(superVault.asset()) |
| 498 | 5× | .balanceOf(address(superVaultStrategy)); |
| 499 | ||
| 500 | // Don't allow investing more than the claimable amount |
|
| 501 | 7× | if (totalAmountToDeposit > totalClaimable) { |
| 502 | 2× | return true; |
| 503 | } |
|
| 504 | ||
| 505 | // Ensure strategy has sufficient assets remaining after investment to cover claimable amounts |
|
| 506 | 8× | uint256 remainingStrategyBalance = currentStrategyBalance - |
| 507 | 1× | totalAmountToDeposit; |
| 508 | 7× | if (remainingStrategyBalance < totalClaimable) { |
| 509 | 0 | return true; |
| 510 | } |
|
| 511 | ||
| 512 | 2× | return false; |
| 513 | } |
|
| 514 | } |
88%
test/recon/targets/DoomsdayTargets.sol
Lines covered: 331 / 375 (88%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | import {vm} from "@chimera/Hevm.sol"; |
|
| 6 | import {Panic} from "@recon/Panic.sol"; |
|
| 7 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 8 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
|
| 9 | ||
| 10 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 11 | import {Properties} from "../Properties.sol"; |
|
| 12 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 13 | import {YieldSourceType} from "test/recon/managers/YieldManager.sol"; |
|
| 14 | import {MockERC4626Tester} from "test/recon/mocks/MockERC4626Tester.sol"; |
|
| 15 | import {MockERC5115Tester} from "test/recon/mocks/MockERC5115Tester.sol"; |
|
| 16 | import {MockERC7540Tester} from "test/recon/mocks/MockERC7540Tester.sol"; |
|
| 17 | ||
| 18 | abstract contract DoomsdayTargets is BaseTargetFunctions, Properties { |
|
| 19 | /// @dev Property: previewDeposit and deposit equivalence |
|
| 20 | 20× | function doomsday_previewDepositEquivalence_ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE( |
| 21 | uint256 assets |
|
| 22 | ) public stateless { |
|
| 23 | 69× | uint256 previewDepositShares = superVault.previewDeposit(assets); |
| 24 | ||
| 25 | 41× | vm.prank(_getActor()); |
| 26 | 82× | uint256 sharesActualDeposit = superVault.deposit(assets, _getActor()); |
| 27 | ||
| 28 | 3× | eq( |
| 29 | 1× | previewDepositShares, |
| 30 | 1× | sharesActualDeposit, |
| 31 | 17× | ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE |
| 32 | ); |
|
| 33 | } |
|
| 34 | ||
| 35 | /// @dev Property: previewMint and mint equivalence |
|
| 36 | 22× | function doomsday_previewMintEquivalence_ASSERTION_PREVIEW_MINT_EQUIVALENCE(uint256 shares) public stateless { |
| 37 | 69× | uint256 previewMintAssets = superVault.previewMint(shares); |
| 38 | ||
| 39 | 41× | vm.prank(_getActor()); |
| 40 | 82× | uint256 assetsActualMint = superVault.mint(shares, _getActor()); |
| 41 | ||
| 42 | 4× | eq( |
| 43 | 1× | previewMintAssets, |
| 44 | 1× | assetsActualMint, |
| 45 | 17× | ASSERTION_PREVIEW_MINT_EQUIVALENCE |
| 46 | ); |
|
| 47 | } |
|
| 48 | ||
| 49 | /// @dev Property: mint/redeem doesn't cause loss to user |
|
| 50 | 20× | function doomsday_mintRedeemSymmetrical_ASSERTION_MINT_REDEEM_SYMMETRICAL( |
| 51 | uint256 sharesToMint |
|
| 52 | 14× | ) public stateless { |
| 53 | // skip if there's been any gain because it complicates the assertion checking |
|
| 54 | // NOTE: removed because was previously checking that user doesn't gain only from minting/redeeming |
|
| 55 | // if (MockERC4626Tester(_getYieldSource()).totalGains() > 0) { |
|
| 56 | // return; |
|
| 57 | // } |
|
| 58 | 70× | address asset = superVault.asset(); |
| 59 | 64× | uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
| 60 | 60× | uint256 feeRecipientBalanceBefore = MockERC20(asset).balanceOf( |
| 61 | 5× | feeRecipient |
| 62 | ); |
|
| 63 | ||
| 64 | // 1. Mint |
|
| 65 | 41× | vm.prank(_getActor()); |
| 66 | 82× | uint256 assetsUsed = superVault.mint(sharesToMint, _getActor()); |
| 67 | ||
| 68 | // 2. Deposit assets into yield strategy via executeHooks |
|
| 69 | // This is needed because the user's assets are currently in the strategy contract |
|
| 70 | // but not yet deposited into the underlying yield source |
|
| 71 | 60× | uint256 strategyAssetBalance = MockERC20(asset).balanceOf( |
| 72 | 5× | address(superVaultStrategy) |
| 73 | ); |
|
| 74 | 6× | if (strategyAssetBalance > 0) { |
| 75 | 5× | ISuperVaultStrategy.ExecuteArgs |
| 76 | 4× | memory depositArgs = _createExecuteDepositArgs( |
| 77 | 1× | strategyAssetBalance |
| 78 | ); |
|
| 79 | ||
| 80 | 54× | superVaultStrategy.executeHooks(depositArgs); |
| 81 | } |
|
| 82 | ||
| 83 | // 3. Request Redemption |
|
| 84 | 66× | uint256 userShares = superVault.balanceOf(_getActor()); |
| 85 | // get shares of strategy in the deposited yield source |
|
| 86 | 6× | uint256 shares = _getSuperVaultStrategyShares(); |
| 87 | ||
| 88 | 41× | vm.prank(_getActor()); |
| 89 | 80× | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 90 | ||
| 91 | 124× | uint256 feeBalanceBefore = MockERC20(superVault.asset()).balanceOf( |
| 92 | 5× | 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 | 5× | ISuperVaultStrategy.FulfillArgs |
| 98 | 5× | memory fulfillArgs = _createFulfillRedeemFromStrategyArgs(shares); |
| 99 | ||
| 100 | // called by admin address(this) |
|
| 101 | 50× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 102 | ||
| 103 | // 5. Claim Redemption |
|
| 104 | 66× | uint256 sharesToRedeem = superVault.maxRedeem(_getActor()); |
| 105 | ||
| 106 | 41× | vm.prank(_getActor()); |
| 107 | 74× | superVault.redeem(sharesToRedeem, _getActor(), _getActor()); |
| 108 | ||
| 109 | 64× | uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor()); |
| 110 | ||
| 111 | 4× | uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost |
| 112 | ||
| 113 | 122× | uint256 feeRecipientBalanceAfter = MockERC20(superVault.asset()) |
| 114 | 5× | .balanceOf(feeRecipient); |
| 115 | 8× | uint256 feeDelta = feeRecipientBalanceAfter - feeRecipientBalanceBefore; |
| 116 | ||
| 117 | // 6. Check that user didn't lose assets |
|
| 118 | 4× | gte( |
| 119 | 12× | balanceAfter + TOLERANCE + feeDelta, |
| 120 | 1× | balanceBefore, |
| 121 | 17× | ASSERTION_MINT_REDEEM_SYMMETRICAL |
| 122 | ); |
|
| 123 | } |
|
| 124 | ||
| 125 | /// @dev Property: deposit/withdraw doesn't cause loss to user |
|
| 126 | 20× | function doomsday_depositWithdrawSymmetrical_ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL( |
| 127 | uint256 assetsToDeposit |
|
| 128 | 2× | ) public stateless returns (uint256, uint256) { |
| 129 | // skip if there's been any gain because it complicates the assertion checking |
|
| 130 | // NOTE: removed because was previously checking that user doesn't gain only from minting/redeeming |
|
| 131 | // if (MockERC4626Tester(_getYieldSource()).totalGains() > 0) { |
|
| 132 | // return; |
|
| 133 | // } |
|
| 134 | 70× | address asset = superVault.asset(); |
| 135 | 63× | uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
| 136 | ||
| 137 | // 1. Deposit |
|
| 138 | 41× | vm.prank(_getActor()); |
| 139 | 79× | superVault.deposit(assetsToDeposit, _getActor()); |
| 140 | ||
| 141 | // 2. Request Withdrawal (through redemption in ERC7540) |
|
| 142 | 65× | uint256 shares = superVault.balanceOf(_getActor()); |
| 143 | 41× | vm.prank(_getActor()); |
| 144 | 74× | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 145 | ||
| 146 | // 3. Fulfill Withdrawal |
|
| 147 | 5× | ISuperVaultStrategy.FulfillArgs |
| 148 | 5× | memory fulfillArgs = _createFulfillRedeemArgs(shares); |
| 149 | // fulfills as admin (address(this)) |
|
| 150 | 50× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 151 | ||
| 152 | // 4. Claim Withdrawal |
|
| 153 | 66× | uint256 withdrawableAssets = superVault.maxWithdraw(_getActor()); |
| 154 | 41× | vm.prank(_getActor()); |
| 155 | 74× | superVault.withdraw(withdrawableAssets, _getActor(), _getActor()); |
| 156 | ||
| 157 | 64× | uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor()); |
| 158 | ||
| 159 | 2× | uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost |
| 160 | // 5. Check that user didn't lose assets |
|
| 161 | 4× | gte( |
| 162 | 4× | balanceAfter + TOLERANCE, |
| 163 | 1× | balanceBefore, |
| 164 | 17× | ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL |
| 165 | ); |
|
| 166 | ||
| 167 | 3× | return (balanceAfter, balanceBefore); |
| 168 | } |
|
| 169 | ||
| 170 | /// @dev Property: maxRedeem is reset to 0 after full redemption |
|
| 171 | /// @dev Property: redeeming maxRedeem shouldn't revert |
|
| 172 | 20× | function doomsday_maxRedeemResetsAfterFullRedemption_ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION( |
| 173 | uint256 sharesToMint |
|
| 174 | ) public stateless { |
|
| 175 | // 1. Deposit to get shares |
|
| 176 | 41× | vm.prank(_getActor()); |
| 177 | 79× | superVault.mint(sharesToMint, _getActor()); |
| 178 | ||
| 179 | // redeem all user shares |
|
| 180 | 65× | uint256 shares = superVault.balanceOf(_getActor()); |
| 181 | ||
| 182 | // 2. Request full redemption |
|
| 183 | 41× | vm.prank(_getActor()); |
| 184 | 74× | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 185 | ||
| 186 | // 3. Fulfill the redemption request |
|
| 187 | 5× | ISuperVaultStrategy.FulfillArgs |
| 188 | 5× | memory fulfillArgs = _createFulfillRedeemArgs(shares); |
| 189 | // fulfill as address(this) |
|
| 190 | 50× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 191 | ||
| 192 | // 4. Check maxRedeem before claiming |
|
| 193 | 66× | uint256 maxRedeemBeforeClaim = superVault.maxRedeem(_getActor()); |
| 194 | ||
| 195 | // 5. Claim the full redemption |
|
| 196 | 41× | vm.prank(_getActor()); |
| 197 | 74× | try superVault.redeem(maxRedeemBeforeClaim, _getActor(), _getActor()) { |
| 198 | // 6. Check maxRedeem is reset to 0 after full redemption |
|
| 199 | 66× | uint256 maxRedeemAfterClaim = superVault.maxRedeem(_getActor()); |
| 200 | 3× | eq( |
| 201 | 1× | maxRedeemAfterClaim, |
| 202 | 1× | 0, |
| 203 | 17× | ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION |
| 204 | ); |
|
| 205 | } catch { |
|
| 206 | 0 | if (maxRedeemBeforeClaim > 0) { |
| 207 | 0 | t(false, ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION); |
| 208 | } |
|
| 209 | } |
|
| 210 | } |
|
| 211 | ||
| 212 | /// @dev Property: maxWithdraw is reset to 0 after full withdrawal |
|
| 213 | 20× | function doomsday_maxWithdrawResetsAfterFullWithdrawal_ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL( |
| 214 | uint256 assetsToDeposit |
|
| 215 | ) public stateless { |
|
| 216 | // 1. Deposit to get shares |
|
| 217 | 41× | vm.prank(_getActor()); |
| 218 | 79× | superVault.deposit(assetsToDeposit, _getActor()); |
| 219 | ||
| 220 | 65× | uint256 shares = superVault.balanceOf(_getActor()); |
| 221 | ||
| 222 | // 2. Request redemption of all shares |
|
| 223 | 41× | vm.prank(_getActor()); |
| 224 | 74× | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 225 | ||
| 226 | // 3. Fulfill the redemption request |
|
| 227 | 5× | ISuperVaultStrategy.FulfillArgs |
| 228 | 5× | memory fulfillArgs = _createFulfillRedeemArgs(shares); |
| 229 | // called as admin address(this) |
|
| 230 | 50× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 231 | ||
| 232 | // 4. Check maxWithdraw after fulfillment and use that value |
|
| 233 | 66× | uint256 maxWithdrawBefore = superVault.maxWithdraw(_getActor()); |
| 234 | ||
| 235 | // 5. Withdraw the exact amount returned by maxWithdraw |
|
| 236 | 41× | vm.prank(_getActor()); |
| 237 | 74× | try superVault.withdraw(maxWithdrawBefore, _getActor(), _getActor()) { |
| 238 | // 6. Check maxWithdraw is reset to 0 after full withdrawal |
|
| 239 | 66× | uint256 maxWithdrawAfter = superVault.maxWithdraw(_getActor()); |
| 240 | 3× | eq( |
| 241 | 1× | maxWithdrawAfter, |
| 242 | 1× | 0, |
| 243 | 17× | ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL |
| 244 | ); |
|
| 245 | } catch { |
|
| 246 | 0 | t(false, ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL); |
| 247 | } |
|
| 248 | } |
|
| 249 | ||
| 250 | /// @dev Property: fulfillRedeemRequests doesn't redeem more than requested for multiple actors |
|
| 251 | 20× | function doomsday_fulfillDoesntOverRedeemMultipleActors_ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS( |
| 252 | uint256[3] memory sharesToMint, |
|
| 253 | uint256[3] memory actorIndexes |
|
| 254 | 0 | ) public stateless { |
| 255 | 7× | address[] memory actors = _getActors(); |
| 256 | 8× | if (actors.length < 3) return; // Need at least 3 actors for this test |
| 257 | ||
| 258 | // Arrays to track actors and their requests |
|
| 259 | 25× | address[] memory testActors = new address[](3); |
| 260 | 26× | uint256[] memory requestedShares = new uint256[](3); |
| 261 | 27× | uint256[] memory sharesBefore = new uint256[](3); |
| 262 | ||
| 263 | // 1. Setup: Each actor deposits and requests redemption |
|
| 264 | 2× | uint256 totalRequestedShares; |
| 265 | 13× | for (uint256 i = 0; i < 3; i++) { |
| 266 | // Get unique actor |
|
| 267 | 57× | testActors[i] = actors[actorIndexes[i] % actors.length]; |
| 268 | ||
| 269 | // Mint shares for this actor |
|
| 270 | 18× | if (sharesToMint[i] > 0) { |
| 271 | 58× | vm.prank(testActors[i]); |
| 272 | 104× | superVault.mint(sharesToMint[i], testActors[i]); |
| 273 | } |
|
| 274 | ||
| 275 | // Get actual share balance |
|
| 276 | 103× | sharesBefore[i] = superVault.maxRedeem(testActors[i]); |
| 277 | 34× | requestedShares[i] = sharesBefore[i]; |
| 278 | ||
| 279 | // Request redemption of all shares |
|
| 280 | 21× | if (requestedShares[i] > 0) { |
| 281 | 0 | vm.prank(testActors[i]); |
| 282 | 0 | superVault.requestRedeem( |
| 283 | 0 | requestedShares[i], |
| 284 | 0 | testActors[i], |
| 285 | 0 | testActors[i] |
| 286 | ); |
|
| 287 | } |
|
| 288 | 23× | totalRequestedShares += requestedShares[i]; |
| 289 | } |
|
| 290 | ||
| 291 | // 2. Create multi-actor FulfillArgs |
|
| 292 | 2× | ISuperVaultStrategy.FulfillArgs |
| 293 | 4× | memory fulfillArgs = _createMultiActorFulfillArgs( |
| 294 | 1× | testActors, |
| 295 | 1× | requestedShares |
| 296 | ); |
|
| 297 | ||
| 298 | // 3. Calculate total pending before |
|
| 299 | 2× | uint256 totalPendingBefore; |
| 300 | 12× | for (uint256 i = 0; i < 3; i++) { |
| 301 | 79× | totalPendingBefore += superVault.pendingRedeemRequest( |
| 302 | 0, |
|
| 303 | 17× | testActors[i] |
| 304 | ); |
|
| 305 | } |
|
| 306 | ||
| 307 | // 4. Fulfill all redemption requests at once |
|
| 308 | // fulfill as address(this) |
|
| 309 | 53× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 310 | ||
| 311 | // 5. Calculate total pending after |
|
| 312 | 0 | uint256 totalPendingAfter; |
| 313 | 0 | for (uint256 i = 0; i < 3; i++) { |
| 314 | 0 | totalPendingAfter += superVault.pendingRedeemRequest( |
| 315 | 0, |
|
| 316 | 0 | testActors[i] |
| 317 | ); |
|
| 318 | } |
|
| 319 | ||
| 320 | 0 | lte( |
| 321 | 0 | totalPendingBefore - totalPendingAfter, |
| 322 | 0 | totalRequestedShares, |
| 323 | 0 | ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS |
| 324 | ); |
|
| 325 | } |
|
| 326 | ||
| 327 | /// @dev Property: primary manager can always be replaced by governance via `changePrimaryManager` |
|
| 328 | 15× | function doomsday_primaryManagerAlwaysChangeable_ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE() public stateless { |
| 329 | 4× | address strategy = address(superVaultStrategy); |
| 330 | 4× | address newManager = _getActor(); |
| 331 | ||
| 332 | // Since address(this) has SUPER_GOVERNOR_ROLE, this should always succeed |
|
| 333 | 36× | vm.prank(address(this)); |
| 334 | 56× | try superGovernor.changePrimaryManager(strategy, newManager) { |
| 335 | // Call succeeded - this is expected behavior |
|
| 336 | 0 | } catch (bytes memory err) { |
| 337 | 0 | bool expectedError; |
| 338 | 0 | expectedError = checkError(err, "MANAGER_TAKEOVERS_FROZEN()"); // custom error |
| 339 | 0 | t( |
| 340 | 0 | !expectedError, |
| 341 | 0 | ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE |
| 342 | ); |
|
| 343 | } |
|
| 344 | } |
|
| 345 | ||
| 346 | /// @dev Property: all users can withdraw (solvency) |
|
| 347 | // NOTE: if withdrawing from a given strategy via fulfillRedeemRequests fails, it can be expected that one of the YieldSourceTargets would be called to switch the yield source |
|
| 348 | // this should allow fulfillments to eventually succeed so we don't need to sort through all yield sources that have currently been deposited into before fulfilling |
|
| 349 | 15× | function doomsday_allUsersCanWithdraw_ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED() public stateless { |
| 350 | 7× | address[] memory actors = _getActors(); |
| 351 | 62× | bool paused = superVaultAggregator.isStrategyPaused( |
| 352 | 5× | address(superVaultStrategy) |
| 353 | ); |
|
| 354 | ||
| 355 | // request redemption for all actors |
|
| 356 | 15× | for (uint256 i; i < actors.length; i++) { |
| 357 | 86× | uint256 redeemableShares = superVault.balanceOf(actors[i]); |
| 358 | ||
| 359 | 58× | vm.prank(actors[i]); |
| 360 | 113× | superVault.requestRedeem(redeemableShares, actors[i], actors[i]); |
| 361 | } |
|
| 362 | ||
| 363 | // fulfill redemption for all actors |
|
| 364 | 9× | for (uint256 i; i < actors.length; i++) { |
| 365 | 74× | uint256 redeemableShares = superVault.pendingRedeemRequest( |
| 366 | 0, |
|
| 367 | 17× | actors[i] |
| 368 | ); |
|
| 369 | ||
| 370 | // switch the actor |
|
| 371 | 6× | _switchActor(i); |
| 372 | ||
| 373 | 5× | ISuperVaultStrategy.FulfillArgs |
| 374 | 4× | memory fulfillArgs = _fulfillRedeemRequestsArgs( |
| 375 | 1× | redeemableShares |
| 376 | ); |
|
| 377 | 49× | superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
| 378 | } |
|
| 379 | ||
| 380 | // try to withdraw max possible for all actors |
|
| 381 | 0 | for (uint256 i; i < actors.length; i++) { |
| 382 | 0 | uint256 withdrawable = superVault.maxWithdraw(actors[i]); |
| 383 | ||
| 384 | 0 | if (withdrawable > 0 && !paused) { |
| 385 | 0 | vm.prank(actors[i]); |
| 386 | 0 | try |
| 387 | 0 | superVault.withdraw(withdrawable, actors[i], actors[i]) |
| 388 | {} catch { |
|
| 389 | // if user can't maxWithdraw there's most likely an insolvency issue related to the TOLERANCE_CONSTANT |
|
| 390 | 0 | t( |
| 391 | 0 | false, |
| 392 | 0 | ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED |
| 393 | ); |
|
| 394 | } |
|
| 395 | } |
|
| 396 | } |
|
| 397 | } |
|
| 398 | ||
| 399 | /// @dev Property: Claiming redemptions should never revert with INVALID_REDEEM_CLAIM |
|
| 400 | 20× | function doomsday_redemptionsNeverReverts_ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM( |
| 401 | uint256 shares |
|
| 402 | ) public asActor stateless { |
|
| 403 | 85× | try superVault.redeem(shares, _getActor(), _getActor()) {} catch ( |
| 404 | bytes memory err |
|
| 405 | 1× | ) { |
| 406 | 24× | bool unexpectedError = checkError(err, "INVALID_REDEEM_CLAIM()"); |
| 407 | 4× | t( |
| 408 | 2× | !unexpectedError, |
| 409 | 17× | ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM |
| 410 | ); |
|
| 411 | } |
|
| 412 | } |
|
| 413 | ||
| 414 | /// @dev Get shares for SuperVaultStrategy in the current yield source |
|
| 415 | 2× | function _getSuperVaultStrategyShares() internal view returns (uint256) { |
| 416 | 7× | address yieldSource = _getYieldSource(); |
| 417 | 6× | YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
| 418 | 1× | yieldSource |
| 419 | ); |
|
| 420 | ||
| 421 | 12× | if (sourceType == YieldSourceType.ERC4626) { |
| 422 | return |
|
| 423 | 57× | MockERC4626Tester(yieldSource).balanceOf( |
| 424 | 5× | address(superVaultStrategy) |
| 425 | ); |
|
| 426 | 0 | } else if (sourceType == YieldSourceType.ERC5115) { |
| 427 | return |
|
| 428 | 0 | MockERC5115Tester(yieldSource).balanceOf( |
| 429 | 0 | address(superVaultStrategy) |
| 430 | ); |
|
| 431 | 0 | } else if (sourceType == YieldSourceType.ERC7540) { |
| 432 | return |
|
| 433 | 0 | MockERC7540Tester(yieldSource).balanceOf( |
| 434 | 0 | address(superVaultStrategy) |
| 435 | ); |
|
| 436 | } else { |
|
| 437 | 0 | revert("Invalid yield source type"); |
| 438 | } |
|
| 439 | } |
|
| 440 | ||
| 441 | // Helpers |
|
| 442 | ||
| 443 | /// @dev Helper function to clamp the values for the function call |
|
| 444 | 33× | function _fulfillRedeemRequestsArgs( |
| 445 | uint256 redeemAmount |
|
| 446 | 8× | ) public returns (ISuperVaultStrategy.FulfillArgs memory fulfillArgs) { |
| 447 | // Find a controller that has pending redeem requests |
|
| 448 | 6× | address selectedController = _getActor(); |
| 449 | 126× | uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
| 450 | selectedController |
|
| 451 | ); |
|
| 452 | ||
| 453 | // Clamp using the actor's pending amount |
|
| 454 | 28× | uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
| 455 | ||
| 456 | 60× | address[] memory controllers = new address[](1); |
| 457 | 48× | controllers[0] = selectedController; |
| 458 | ||
| 459 | // Determine yield source type from currently active yield source |
|
| 460 | 10× | YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress( |
| 461 | 6× | _getYieldSource() |
| 462 | ); |
|
| 463 | 14× | address redeemHook = _getRedeemHookForType(activeYieldSourceType); |
| 464 | ||
| 465 | // Create realistic hook calldata for redeem operation |
|
| 466 | 2× | bytes memory redeemHookCalldata; |
| 467 | ||
| 468 | 14× | if ( |
| 469 | 28× | activeYieldSourceType == YieldSourceType.ERC4626 || |
| 470 | 20× | activeYieldSourceType == YieldSourceType.ERC5115 |
| 471 | ) { |
|
| 472 | // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
|
| 473 | 50× | redeemHookCalldata = abi.encodePacked( |
| 474 | 2× | bytes32(0), // yieldSourceOracleId placeholder |
| 475 | 8× | _getYieldSource(), // Current active yield source |
| 476 | 12× | address(superVaultStrategy), // Owner (strategy owns the yield source shares) |
| 477 | 4× | actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
| 478 | false // Don't use previous hook amount |
|
| 479 | ); |
|
| 480 | } else { |
|
| 481 | // ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
|
| 482 | 54× | redeemHookCalldata = abi.encodePacked( |
| 483 | 2× | bytes32(0), // yieldSourceOracleId placeholder |
| 484 | 8× | _getYieldSource(), // Current active yield source |
| 485 | 2× | actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
| 486 | 2× | false // Don't use previous hook amount |
| 487 | ); |
|
| 488 | } |
|
| 489 | ||
| 490 | // Create arrays for FulfillArgs |
|
| 491 | 58× | address[] memory hooks = new address[](1); |
| 492 | 52× | hooks[0] = redeemHook; |
| 493 | ||
| 494 | 74× | bytes[] memory hookCalldata = new bytes[](1); |
| 495 | 40× | hookCalldata[0] = redeemHookCalldata; |
| 496 | ||
| 497 | 60× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 498 | 40× | expectedAssetsOrSharesOut[0] = actualRedeemAmount; // Expect amount matching the actual redeem |
| 499 | ||
| 500 | 68× | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 501 | 66× | globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
| 502 | ||
| 503 | 68× | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 504 | 74× | strategyProofs[0] = new bytes32[](0); // Empty proof |
| 505 | ||
| 506 | // Create the FulfillArgs struct |
|
| 507 | 78× | fulfillArgs = ISuperVaultStrategy.FulfillArgs({ |
| 508 | controllers: controllers, |
|
| 509 | hooks: hooks, |
|
| 510 | hookCalldata: hookCalldata, |
|
| 511 | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 512 | globalProofs: globalProofs, |
|
| 513 | strategyProofs: strategyProofs |
|
| 514 | }); |
|
| 515 | ||
| 516 | return fulfillArgs; |
|
| 517 | } |
|
| 518 | ||
| 519 | /// @dev Helper function to create FulfillArgs for multiple actors |
|
| 520 | 3× | function _createMultiActorFulfillArgs( |
| 521 | address[] memory controllers, |
|
| 522 | uint256[] memory amounts |
|
| 523 | 4× | ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) { |
| 524 | 4× | uint256 numActors = controllers.length; |
| 525 | ||
| 526 | 41× | address[] memory hooks = new address[](numActors); |
| 527 | 50× | bytes[] memory hookCalldata = new bytes[](numActors); |
| 528 | 43× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](numActors); |
| 529 | 50× | bytes32[][] memory globalProofs = new bytes32[][](numActors); |
| 530 | 48× | bytes32[][] memory strategyProofs = new bytes32[][](numActors); |
| 531 | ||
| 532 | 13× | for (uint256 i = 0; i < numActors; i++) { |
| 533 | 26× | hooks[i] = _getRedeemHookForType( |
| 534 | 4× | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 535 | ); |
|
| 536 | ||
| 537 | 6× | if ( |
| 538 | 13× | _getYieldSourceTypeFromAddress(_getYieldSource()) == |
| 539 | 1× | YieldSourceType.ERC4626 |
| 540 | ) { |
|
| 541 | 44× | hookCalldata[i] = abi.encodePacked( |
| 542 | 1× | bytes32(0), |
| 543 | 4× | _getYieldSource(), |
| 544 | 6× | address(superVaultStrategy), |
| 545 | 17× | amounts[i], |
| 546 | 1× | false |
| 547 | ); |
|
| 548 | } else { |
|
| 549 | 43× | hookCalldata[i] = abi.encodePacked( |
| 550 | 1× | bytes32(0), |
| 551 | 4× | _getYieldSource(), |
| 552 | 15× | amounts[i], |
| 553 | 1× | false |
| 554 | ); |
|
| 555 | } |
|
| 556 | ||
| 557 | 34× | expectedAssetsOrSharesOut[i] = amounts[i]; |
| 558 | 41× | globalProofs[i] = new bytes32[](0); |
| 559 | 41× | strategyProofs[i] = new bytes32[](0); |
| 560 | } |
|
| 561 | ||
| 562 | return |
|
| 563 | 39× | ISuperVaultStrategy.FulfillArgs({ |
| 564 | controllers: controllers, |
|
| 565 | hooks: hooks, |
|
| 566 | hookCalldata: hookCalldata, |
|
| 567 | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 568 | globalProofs: globalProofs, |
|
| 569 | strategyProofs: strategyProofs |
|
| 570 | }); |
|
| 571 | } |
|
| 572 | ||
| 573 | /// @dev Helper function to create FulfillArgs for redeem requests |
|
| 574 | 3× | function _createFulfillRedeemArgs( |
| 575 | uint256 amount |
|
| 576 | 4× | ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) { |
| 577 | 26× | address[] memory controllers = new address[](1); |
| 578 | 25× | controllers[0] = _getActor(); |
| 579 | ||
| 580 | 30× | address[] memory hooks = new address[](1); |
| 581 | 29× | hooks[0] = _getRedeemHookForType( |
| 582 | 5× | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 583 | ); |
|
| 584 | ||
| 585 | 35× | bytes[] memory hookCalldata = new bytes[](1); |
| 586 | 6× | if ( |
| 587 | 13× | _getYieldSourceTypeFromAddress(_getYieldSource()) == |
| 588 | 1× | YieldSourceType.ERC4626 |
| 589 | ) { |
|
| 590 | 41× | hookCalldata[0] = abi.encodePacked( |
| 591 | 1× | bytes32(0), |
| 592 | 4× | _getYieldSource(), |
| 593 | 6× | address(superVaultStrategy), |
| 594 | 2× | amount, |
| 595 | false |
|
| 596 | ); |
|
| 597 | } else { |
|
| 598 | 43× | hookCalldata[0] = abi.encodePacked( |
| 599 | 1× | bytes32(0), |
| 600 | 4× | _getYieldSource(), |
| 601 | 1× | amount, |
| 602 | 1× | false |
| 603 | ); |
|
| 604 | } |
|
| 605 | ||
| 606 | 29× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 607 | 20× | expectedAssetsOrSharesOut[0] = amount; |
| 608 | ||
| 609 | 34× | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 610 | 33× | globalProofs[0] = new bytes32[](0); |
| 611 | ||
| 612 | 34× | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 613 | 37× | strategyProofs[0] = new bytes32[](0); |
| 614 | ||
| 615 | return |
|
| 616 | 38× | ISuperVaultStrategy.FulfillArgs({ |
| 617 | controllers: controllers, |
|
| 618 | hooks: hooks, |
|
| 619 | hookCalldata: hookCalldata, |
|
| 620 | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 621 | globalProofs: globalProofs, |
|
| 622 | strategyProofs: strategyProofs |
|
| 623 | }); |
|
| 624 | } |
|
| 625 | ||
| 626 | /// @dev Helper function to create ExecuteArgs for depositing assets into yield strategy |
|
| 627 | 5× | function _createExecuteDepositArgs( |
| 628 | uint256 amount |
|
| 629 | 2× | ) internal view returns (ISuperVaultStrategy.ExecuteArgs memory) { |
| 630 | 29× | address[] memory hooks = new address[](1); |
| 631 | 29× | hooks[0] = _getApproveAndDepositHookForType( |
| 632 | 5× | _getYieldSourceTypeFromAddress(_getYieldSource()) |
| 633 | ); |
|
| 634 | ||
| 635 | 37× | bytes[] memory hookCalldata = new bytes[](1); |
| 636 | 4× | YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
| 637 | 3× | _getYieldSource() |
| 638 | ); |
|
| 639 | ||
| 640 | 15× | if (sourceType == YieldSourceType.ERC4626) { |
| 641 | // ERC4626 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, bool usePrevAmount |
|
| 642 | 44× | hookCalldata[0] = abi.encodePacked( |
| 643 | 1× | bytes32(0), // oracle ID placeholder |
| 644 | 4× | _getYieldSource(), |
| 645 | 67× | superVault.asset(), // token address |
| 646 | 1× | amount, |
| 647 | 1× | false // don't use previous amount |
| 648 | ); |
|
| 649 | 13× | } else if (sourceType == YieldSourceType.ERC5115) { |
| 650 | // ERC5115 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address receiver, uint256 id, bool usePrevAmount |
|
| 651 | 7× | hookCalldata[0] = abi.encodePacked( |
| 652 | 1× | bytes32(0), |
| 653 | 4× | _getYieldSource(), |
| 654 | 67× | superVault.asset(), // token address |
| 655 | amount, |
|
| 656 | 3× | address(superVaultStrategy), |
| 657 | uint256(0), // sy ID for ERC5115 |
|
| 658 | false |
|
| 659 | ); |
|
| 660 | 13× | } else if (sourceType == YieldSourceType.ERC7540) { |
| 661 | // ERC7540 requestDeposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address controller, bool usePrevAmount |
|
| 662 | 36× | hookCalldata[0] = abi.encodePacked( |
| 663 | 1× | bytes32(0), |
| 664 | 4× | _getYieldSource(), |
| 665 | 67× | superVault.asset(), // token address |
| 666 | amount, |
|
| 667 | 3× | address(superVaultStrategy), |
| 668 | false |
|
| 669 | ); |
|
| 670 | } |
|
| 671 | ||
| 672 | 29× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 673 | // For deposit hooks, we expect shares back. Calculate expected shares based on yield source conversion |
|
| 674 | // Use a very small amount (1) as minimum since we just want the deposit to succeed |
|
| 675 | 20× | expectedAssetsOrSharesOut[0] = 1; // Minimum expected shares from deposit |
| 676 | ||
| 677 | 34× | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 678 | 33× | globalProofs[0] = new bytes32[](0); |
| 679 | ||
| 680 | 34× | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 681 | 32× | strategyProofs[0] = new bytes32[](0); |
| 682 | ||
| 683 | 8× | return |
| 684 | 27× | ISuperVaultStrategy.ExecuteArgs({ |
| 685 | 1× | hooks: hooks, |
| 686 | 1× | hookCalldata: hookCalldata, |
| 687 | 1× | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
| 688 | 1× | globalProofs: globalProofs, |
| 689 | 1× | strategyProofs: strategyProofs |
| 690 | }); |
|
| 691 | } |
|
| 692 | ||
| 693 | /// @dev Helper function to create FulfillArgs for redeeming from yield strategy |
|
| 694 | 3× | function _createFulfillRedeemFromStrategyArgs( |
| 695 | uint256 sharesToRedeem |
|
| 696 | 4× | ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) { |
| 697 | 26× | address[] memory controllers = new address[](1); |
| 698 | 23× | controllers[0] = _getActor(); |
| 699 | ||
| 700 | // Get the actual share balance that the SuperVaultStrategy holds in the yield source |
|
| 701 | 7× | address yieldSource = _getYieldSource(); |
| 702 | 61× | uint256 strategyYieldSourceShares = IERC20(yieldSource).balanceOf( |
| 703 | 5× | address(superVaultStrategy) |
| 704 | ); |
|
| 705 | ||
| 706 | // Use the pending shares for the hook calldata to match what's expected by fulfillRedeemRequests |
|
| 707 | 73× | uint256 pendingShares = superVault.pendingRedeemRequest(0, _getActor()); |
| 708 | ||
| 709 | // For debugging: ensure we don't try to redeem more than the strategy has |
|
| 710 | 12× | uint256 actualSharesToRedeem = strategyYieldSourceShares < pendingShares |
| 711 | 1× | ? strategyYieldSourceShares |
| 712 | 1× | : pendingShares; |
| 713 | ||
| 714 | 30× | address[] memory hooks = new address[](1); |
| 715 | 27× | hooks[0] = _getRedeemHookForType( |
| 716 | 4× | _getYieldSourceTypeFromAddress(yieldSource) |
| 717 | ); |
|
| 718 | ||
| 719 | 37× | bytes[] memory hookCalldata = new bytes[](1); |
| 720 | 6× | YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
| 721 | 1× | yieldSource |
| 722 | ); |
|
| 723 | ||
| 724 | 6× | if ( |
| 725 | 14× | sourceType == YieldSourceType.ERC4626 || |
| 726 | 0 | sourceType == YieldSourceType.ERC5115 |
| 727 | ) { |
|
| 728 | // For ERC4626/5115: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
|
| 729 | // Use the calculated shares to redeem (limited by what strategy actually has) |
|
| 730 | 39× | hookCalldata[0] = abi.encodePacked( |
| 731 | 4× | bytes32(0), |
| 732 | 2× | yieldSource, |
| 733 | 4× | address(superVaultStrategy), // owner is the strategy |
| 734 | 2× | actualSharesToRedeem, // redeem calculated amount |
| 735 | false |
|
| 736 | ); |
|
| 737 | } else { |
|
| 738 | // For ERC7540: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
|
| 739 | 0 | hookCalldata[0] = abi.encodePacked( |
| 740 | 0 | bytes32(0), |
| 741 | 0 | yieldSource, |
| 742 | 0 | actualSharesToRedeem, // redeem calculated amount |
| 743 | false |
|
| 744 | ); |
|
| 745 | } |
|
| 746 | ||
| 747 | // For fulfillment, we expect to get assets back from redeeming the shares |
|
| 748 | // Use a conservative estimate for expected assets out |
|
| 749 | 29× | uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
| 750 | 29× | expectedAssetsOrSharesOut[0] = actualSharesToRedeem > 0 ? 1 : 0; // Minimum 1 asset expected if redeeming anything |
| 751 | ||
| 752 | 34× | bytes32[][] memory globalProofs = new bytes32[][](1); |
| 753 | 33× | globalProofs[0] = new bytes32[](0); |
| 754 | ||
| 755 | 34× | bytes32[][] memory strategyProofs = new bytes32[][](1); |
| 756 | 37× | strategyProofs[0] = new bytes32[](0); |
| 757 | ||
| 758 | return |
|
| 759 | 39× | ISuperVaultStrategy.FulfillArgs({ |
| 760 | controllers: controllers, |
|
| 761 | hooks: hooks, |
|
| 762 | hookCalldata: hookCalldata, |
|
| 763 | expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 764 | globalProofs: globalProofs, |
|
| 765 | strategyProofs: strategyProofs |
|
| 766 | }); |
|
| 767 | } |
|
| 768 | } |
100%
test/recon/targets/ManagersTargets.sol
Lines covered: 14 / 14 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 6 | import {Properties} from "../Properties.sol"; |
|
| 7 | import {vm} from "@chimera/Hevm.sol"; |
|
| 8 | ||
| 9 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 10 | ||
| 11 | // Target functions that are effectively inherited from the Actor and AssetManagers |
|
| 12 | // Once properly standardized, managers will expose these by default |
|
| 13 | // Keeping them out makes your project more custom |
|
| 14 | abstract contract ManagersTargets is BaseTargetFunctions, Properties { |
|
| 15 | // == ACTOR HANDLERS == // |
|
| 16 | ||
| 17 | /// @dev Start acting as another actor |
|
| 18 | /// @dev Update ghosts here to make global property checks not fail falsely |
|
| 19 | 20× | function switchActor(uint256 entropy) public updateGhosts { |
| 20 | 6× | _switchActor(entropy); |
| 21 | } |
|
| 22 | ||
| 23 | /// @dev Starts using a new asset |
|
| 24 | 20× | function switch_asset(uint256 entropy) public { |
| 25 | 4× | _switchAsset(entropy); |
| 26 | } |
|
| 27 | ||
| 28 | /// @dev Deploy a new token and add it to the list of assets, then set it as the current asset |
|
| 29 | 25× | function add_new_asset(uint8 decimals) public returns (address) { |
| 30 | 9× | address newAsset = _newAsset(decimals); |
| 31 | return newAsset; |
|
| 32 | } |
|
| 33 | ||
| 34 | /// @dev Switches the current vault based on the entropy |
|
| 35 | /// @param entropy The entropy to choose a random vault in the array for switching |
|
| 36 | 20× | function switch_vault(uint256 entropy) public { |
| 37 | 4× | _switchVault(entropy); |
| 38 | } |
|
| 39 | ||
| 40 | /// @dev Deploy a new vault using the current asset and add it to the list of vaults, |
|
| 41 | /// then set it as the current vault |
|
| 42 | 15× | function add_new_vault() public { |
| 43 | 68× | _newVault(superVault.asset()); |
| 44 | } |
|
| 45 | ||
| 46 | /// === GHOST UPDATING HANDLERS ===/// |
|
| 47 | /// We `updateGhosts` cause you never know (e.g. donations) |
|
| 48 | /// If you don't want to track donations, remove the `updateGhosts` |
|
| 49 | ||
| 50 | /// @dev Approve to arbitrary address, uses Actor by default |
|
| 51 | /// NOTE: You're almost always better off setting approvals in `Setup` |
|
| 52 | 20× | function asset_approve( |
| 53 | address to, |
|
| 54 | uint128 amt |
|
| 55 | ) public updateGhosts asActor { |
|
| 56 | 132× | MockERC20(superVault.asset()).approve(to, amt); |
| 57 | } |
|
| 58 | ||
| 59 | /// @dev Mint to arbitrary address, uses owner by default, even though MockERC20 doesn't check |
|
| 60 | 20× | function asset_mint(address to, uint128 amt) public updateGhosts asAdmin { |
| 61 | 118× | MockERC20(superVault.asset()).mint(to, amt); |
| 62 | } |
|
| 63 | } |
92%
test/recon/targets/OracleTargets.sol
Lines covered: 36 / 39 (92%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // External dependencies |
|
| 5 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {Panic} from "@recon/Panic.sol"; |
|
| 8 | ||
| 9 | // Source dependencies |
|
| 10 | import {IECDSAPPSOracle} from "src/interfaces/oracles/IECDSAPPSOracle.sol"; |
|
| 11 | import "test/mocks/MockYieldSourceOracle.sol"; |
|
| 12 | import "../mocks/MockERC4626YieldSourceOracle.sol"; |
|
| 13 | import "../mocks/MockERC5115YieldSourceOracle.sol"; |
|
| 14 | import "../mocks/MockERC7540YieldSourceOracle.sol"; |
|
| 15 | ||
| 16 | // Test suite dependencies |
|
| 17 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 18 | import {Properties} from "../Properties.sol"; |
|
| 19 | ||
| 20 | abstract contract OracleTargets is BaseTargetFunctions, Properties { |
|
| 21 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 22 | 15× | function yieldSourceOracle_setValidAsset_clamped() public { |
| 23 | 69× | mockERC4626YieldSourceOracle_setValidAsset(superVault.asset(), true); |
| 24 | } |
|
| 25 | ||
| 26 | 20× | function mockERC4626YieldSourceOracle_setValidAsset( |
| 27 | address asset, |
|
| 28 | bool isValid |
|
| 29 | ) public asActor { |
|
| 30 | 16× | MockERC4626YieldSourceOracle(address(erc4626YieldSourceOracle)) |
| 31 | 0 | .setValidAsset(asset, isValid); |
| 32 | } |
|
| 33 | ||
| 34 | 20× | function mockERC5115YieldSourceOracle_setValidAsset( |
| 35 | address asset, |
|
| 36 | bool isValid |
|
| 37 | ) public asActor { |
|
| 38 | 16× | MockERC5115YieldSourceOracle(address(erc5115YieldSourceOracle)) |
| 39 | 0 | .setValidAsset(asset, isValid); |
| 40 | } |
|
| 41 | ||
| 42 | 20× | function mockERC7540YieldSourceOracle_setValidAsset( |
| 43 | address asset, |
|
| 44 | bool isValid |
|
| 45 | ) public asActor { |
|
| 46 | 16× | MockERC7540YieldSourceOracle(address(erc7540YieldSourceOracle)) |
| 47 | 0 | .setValidAsset(asset, isValid); |
| 48 | } |
|
| 49 | ||
| 50 | 30× | function ECDSAPPSOracle_updatePPS_clamped(uint256 pps) public { |
| 51 | 8× | pps %= (100_000_000 * 1e18) + 1; // clamp to a reasonable max price |
| 52 | ||
| 53 | 25× | address[] memory strategies = new address[](1); |
| 54 | 31× | strategies[0] = address(superVaultStrategy); |
| 55 | ||
| 56 | 34× | bytes[][] memory proofsArray = new bytes[][](1); |
| 57 | 36× | proofsArray[0] = new bytes[](0); |
| 58 | ||
| 59 | 30× | uint256[] memory ppss = new uint256[](1); |
| 60 | 20× | ppss[0] = pps; |
| 61 | ||
| 62 | 30× | uint256[] memory ppsStdevs = new uint256[](1); |
| 63 | 20× | ppsStdevs[0] = 0; |
| 64 | ||
| 65 | 30× | uint256[] memory validatorSets = new uint256[](1); |
| 66 | 20× | validatorSets[0] = 0; |
| 67 | ||
| 68 | 30× | uint256[] memory totalValidators = new uint256[](1); |
| 69 | 20× | totalValidators[0] = 0; |
| 70 | ||
| 71 | 30× | uint256[] memory timestamps = new uint256[](1); |
| 72 | 20× | timestamps[0] = block.timestamp; |
| 73 | ||
| 74 | 38× | IECDSAPPSOracle.UpdatePPSArgs memory args = IECDSAPPSOracle |
| 75 | .UpdatePPSArgs({ |
|
| 76 | 1× | strategies: strategies, |
| 77 | 1× | proofsArray: proofsArray, |
| 78 | 1× | ppss: ppss, |
| 79 | 1× | ppsStdevs: ppsStdevs, |
| 80 | 1× | validatorSets: validatorSets, |
| 81 | 1× | totalValidators: totalValidators, |
| 82 | 1× | timestamps: timestamps |
| 83 | }); |
|
| 84 | ||
| 85 | 5× | ECDSAPPSOracle_updatePPS(args); |
| 86 | } |
|
| 87 | ||
| 88 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 89 | 21× | function ECDSAPPSOracle_updatePPS( |
| 90 | IECDSAPPSOracle.UpdatePPSArgs memory args |
|
| 91 | ) public asActor { |
|
| 92 | 54× | ECDSAPPSOracle.updatePPS(args); |
| 93 | ||
| 94 | 7× | hasUpdatedPPS = true; |
| 95 | } |
|
| 96 | } |
100%
test/recon/targets/SuperGovernorTargets.sol
Lines covered: 37 / 37 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 6 | import {Properties} from "../Properties.sol"; |
|
| 7 | // Chimera deps |
|
| 8 | import {vm} from "@chimera/Hevm.sol"; |
|
| 9 | ||
| 10 | // Helpers |
|
| 11 | import {Panic} from "@recon/Panic.sol"; |
|
| 12 | ||
| 13 | import "src/SuperGovernor.sol"; |
|
| 14 | ||
| 15 | abstract contract SuperGovernorTargets is BaseTargetFunctions, Properties { |
|
| 16 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 17 | 20× | function superGovernor_proposeGlobalHooksRoot_clamped( |
| 18 | bytes32 newRoot |
|
| 19 | ) public { |
|
| 20 | 73× | (bytes32 testRoot, bytes32[][] memory testProofs) = merkleHelper |
| 21 | .generateTestHooksRoot( |
|
| 22 | 6× | address(approveAndDeposit4626Hook), |
| 23 | 3× | address(redeem4626Hook), |
| 24 | 4× | _getYieldSource(), |
| 25 | 67× | superVault.asset() |
| 26 | ); |
|
| 27 | ||
| 28 | 4× | superGovernor_proposeGlobalHooksRoot(testRoot); |
| 29 | } |
|
| 30 | ||
| 31 | 15× | function superGovernor_proposeUpkeepPaymentsChange_clamped() public { |
| 32 | 4× | superGovernor_proposeUpkeepPaymentsChange(true); |
| 33 | } |
|
| 34 | ||
| 35 | 20× | function superGovernor_proposeFee_clamped( |
| 36 | uint256 feeTypeAsUint, |
|
| 37 | uint256 value |
|
| 38 | ) public { |
|
| 39 | 8× | feeTypeAsUint %= 3; |
| 40 | 12× | superGovernor_proposeFee(FeeType(feeTypeAsUint), value); |
| 41 | } |
|
| 42 | ||
| 43 | 26× | function superGovernor_executeFeeUpdate_clamped( |
| 44 | uint256 feeTypeAsUint |
|
| 45 | ) public { |
|
| 46 | 8× | feeTypeAsUint %= 3; |
| 47 | 11× | superGovernor_executeFeeUpdate(FeeType(feeTypeAsUint)); |
| 48 | } |
|
| 49 | ||
| 50 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 51 | ||
| 52 | 23× | function superGovernor_proposeFee( |
| 53 | FeeType feeType, |
|
| 54 | uint256 value |
|
| 55 | ) public asAdmin { |
|
| 56 | 60× | superGovernor.proposeFee(feeType, value); |
| 57 | } |
|
| 58 | ||
| 59 | 20× | function superGovernor_executeFeeUpdate(FeeType feeType) public asAdmin { |
| 60 | 20× | superGovernor.executeFeeUpdate(feeType); |
| 61 | } |
|
| 62 | ||
| 63 | 20× | function superGovernor_proposeMinStaleness( |
| 64 | uint256 newMinStaleness |
|
| 65 | ) public asAdmin { |
|
| 66 | 16× | superGovernor.proposeMinStaleness(newMinStaleness); |
| 67 | } |
|
| 68 | ||
| 69 | 15× | function superGovernor_executeMinStalenesChange() public asAdmin { |
| 70 | 39× | superGovernor.executeMinStalenesChange(); |
| 71 | } |
|
| 72 | ||
| 73 | 15× | function superGovernor_executeRemoveIncentiveTokens() public asAdmin { |
| 74 | 39× | superGovernor.executeRemoveIncentiveTokens(); |
| 75 | } |
|
| 76 | ||
| 77 | 24× | function superGovernor_executeUpkeepClaim(uint256 amount) public asAdmin { |
| 78 | 58× | superGovernor.executeUpkeepClaim(amount); |
| 79 | } |
|
| 80 | ||
| 81 | 20× | function superGovernor_proposeUpkeepPaymentsChange( |
| 82 | bool enabled |
|
| 83 | ) public asAdmin { |
|
| 84 | 16× | superGovernor.proposeUpkeepPaymentsChange(enabled); |
| 85 | } |
|
| 86 | ||
| 87 | 15× | function superGovernor_executeUpkeepPaymentsChange() public asAdmin { |
| 88 | 39× | superGovernor.executeUpkeepPaymentsChange(); |
| 89 | } |
|
| 90 | ||
| 91 | 20× | function superGovernor_proposeAddIncentiveTokens( |
| 92 | address[] memory tokens |
|
| 93 | ) public asAdmin { |
|
| 94 | 20× | superGovernor.proposeAddIncentiveTokens(tokens); |
| 95 | } |
|
| 96 | ||
| 97 | 15× | function superGovernor_executeAddIncentiveTokens() public asAdmin { |
| 98 | 39× | superGovernor.executeAddIncentiveTokens(); |
| 99 | } |
|
| 100 | ||
| 101 | 20× | function superGovernor_proposeGlobalHooksRoot( |
| 102 | bytes32 newRoot |
|
| 103 | ) public asAdmin { |
|
| 104 | 16× | superGovernor.proposeGlobalHooksRoot(newRoot); |
| 105 | } |
|
| 106 | } |
100%
test/recon/targets/SuperVaultAggregatorTargets.sol
Lines covered: 56 / 56 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | import {vm} from "@chimera/Hevm.sol"; |
|
| 6 | import {Panic} from "@recon/Panic.sol"; |
|
| 7 | ||
| 8 | import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol"; |
|
| 9 | import "src/SuperVault/SuperVaultAggregator.sol"; |
|
| 10 | ||
| 11 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 12 | import {Properties} from "../Properties.sol"; |
|
| 13 | ||
| 14 | abstract contract SuperVaultAggregatorTargets is |
|
| 15 | BaseTargetFunctions, |
|
| 16 | Properties |
|
| 17 | { |
|
| 18 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 19 | 15× | function superVaultAggregator_proposeChangePrimaryManager_clamped() public { |
| 20 | 2× | superVaultAggregator_proposeChangePrimaryManager( |
| 21 | 3× | address(superVaultStrategy), |
| 22 | 1× | _getActor() |
| 23 | ); |
|
| 24 | } |
|
| 25 | ||
| 26 | 15× | function superVaultAggregator_executeChangePrimaryManager_clamped() public { |
| 27 | 4× | superVaultAggregator_executeChangePrimaryManager( |
| 28 | 3× | address(superVaultStrategy) |
| 29 | ); |
|
| 30 | } |
|
| 31 | ||
| 32 | 20× | function superVaultAggregator_createVault_clamped( |
| 33 | uint256 minUpdateInterval, |
|
| 34 | uint256 maxStaleness, |
|
| 35 | uint256 performanceFeeBps, |
|
| 36 | uint256 managementFeeBps |
|
| 37 | ) public { |
|
| 38 | // Clamp values to reasonable ranges |
|
| 39 | 7× | minUpdateInterval = minUpdateInterval % 3601; // Max 1 hour |
| 40 | 13× | maxStaleness = (maxStaleness % 86400) + 301; // Between 5 minutes and 1 day |
| 41 | 7× | performanceFeeBps = performanceFeeBps % 9001; // Max 90% |
| 42 | 8× | managementFeeBps = managementFeeBps % 5001; // Max 50% |
| 43 | ||
| 44 | // Create secondary managers array |
|
| 45 | 30× | address[] memory secondaryManagers = new address[](1); |
| 46 | ||
| 47 | 3× | ISuperVaultAggregator.VaultCreationParams |
| 48 | 73× | memory params = ISuperVaultAggregator.VaultCreationParams({ |
| 49 | 4× | asset: _getAsset(), |
| 50 | name: "SuperVault", |
|
| 51 | symbol: "SV", |
|
| 52 | 1× | mainManager: address(this), |
| 53 | 1× | secondaryManagers: secondaryManagers, |
| 54 | 1× | minUpdateInterval: minUpdateInterval, |
| 55 | 1× | maxStaleness: maxStaleness, |
| 56 | 20× | feeConfig: ISuperVaultStrategy.FeeConfig({ |
| 57 | 1× | performanceFeeBps: performanceFeeBps, |
| 58 | 1× | managementFeeBps: managementFeeBps, |
| 59 | 1× | recipient: address(this) |
| 60 | }) |
|
| 61 | }); |
|
| 62 | ||
| 63 | 4× | superVaultAggregator_createVault(params); |
| 64 | } |
|
| 65 | ||
| 66 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 67 | ||
| 68 | 20× | function superVaultAggregator_addAuthorizedCaller( |
| 69 | address strategy, |
|
| 70 | address caller |
|
| 71 | ) public asAdmin { |
|
| 72 | 22× | superVaultAggregator.addAuthorizedCaller(strategy, caller); |
| 73 | } |
|
| 74 | ||
| 75 | 20× | function superVaultAggregator_addSecondaryManager( |
| 76 | address strategy, |
|
| 77 | address manager |
|
| 78 | ) public asActor { |
|
| 79 | 22× | superVaultAggregator.addSecondaryManager(strategy, manager); |
| 80 | } |
|
| 81 | ||
| 82 | /// @dev removed because only callable by oracle |
|
| 83 | // function superVaultAggregator_batchForwardPPS( |
|
| 84 | // ISuperVaultAggregator.BatchForwardPPSArgs memory args |
|
| 85 | // ) public asActor { |
|
| 86 | // superVaultAggregator.batchForwardPPS(args); |
|
| 87 | // } |
|
| 88 | ||
| 89 | /// @dev irrelevant for testing because we're bypassing hook validation |
|
| 90 | // function superVaultAggregator_changeGlobalLeavesStatus( |
|
| 91 | // bytes32[] memory leaves, |
|
| 92 | // bool[] memory statuses, |
|
| 93 | // address strategy |
|
| 94 | // ) public asActor { |
|
| 95 | // superVaultAggregator.changeGlobalLeavesStatus( |
|
| 96 | // leaves, |
|
| 97 | // statuses, |
|
| 98 | // strategy |
|
| 99 | // ); |
|
| 100 | // } |
|
| 101 | ||
| 102 | 20× | function superVaultAggregator_claimUpkeep(uint256 amount) public asActor { |
| 103 | 16× | superVaultAggregator.claimUpkeep(amount); |
| 104 | } |
|
| 105 | ||
| 106 | 21× | function superVaultAggregator_createVault( |
| 107 | ISuperVaultAggregator.VaultCreationParams memory params |
|
| 108 | ) public asActor { |
|
| 109 | ( |
|
| 110 | 6× | address _superVault, |
| 111 | address _strategy, |
|
| 112 | address _escrow |
|
| 113 | 71× | ) = superVaultAggregator.createVault(params); |
| 114 | ||
| 115 | 13× | superVault = SuperVault(_superVault); |
| 116 | 16× | superVaultStrategy = SuperVaultStrategy(payable(_strategy)); |
| 117 | 12× | superVaultEscrow = SuperVaultEscrow(_escrow); |
| 118 | ||
| 119 | 8× | hasDeployedNewVault = true; |
| 120 | } |
|
| 121 | ||
| 122 | 20× | function superVaultAggregator_depositStake( |
| 123 | address manager, |
|
| 124 | uint256 amount |
|
| 125 | ) public asActor { |
|
| 126 | 22× | superVaultAggregator.depositStake(manager, amount); |
| 127 | } |
|
| 128 | ||
| 129 | 20× | function superVaultAggregator_depositUpkeep(uint256 amount) public asActor { |
| 130 | 26× | superVaultAggregator.depositUpkeep(_getActor(), amount); |
| 131 | } |
|
| 132 | ||
| 133 | 20× | function superVaultAggregator_executeChangePrimaryManager( |
| 134 | address strategy |
|
| 135 | ) public asActor { |
|
| 136 | 16× | superVaultAggregator.executeChangePrimaryManager(strategy); |
| 137 | } |
|
| 138 | ||
| 139 | /// @dev removed because we're bypassing hook validation |
|
| 140 | // function superVaultAggregator_executeStrategyHooksRootUpdate( |
|
| 141 | // address strategy |
|
| 142 | // ) public asActor { |
|
| 143 | // superVaultAggregator.executeStrategyHooksRootUpdate(strategy); |
|
| 144 | // } |
|
| 145 | ||
| 146 | /// @dev removed because only callable by oracle |
|
| 147 | // function superVaultAggregator_forwardPPS( |
|
| 148 | // address updateAuthority, |
|
| 149 | // ISuperVaultAggregator.ForwardPPSArgs memory args |
|
| 150 | // ) public asActor { |
|
| 151 | // superVaultAggregator.forwardPPS(updateAuthority, args); |
|
| 152 | // } |
|
| 153 | ||
| 154 | 20× | function superVaultAggregator_proposeChangePrimaryManager( |
| 155 | address strategy, |
|
| 156 | address newManager |
|
| 157 | ) public asActor { |
|
| 158 | 22× | superVaultAggregator.proposeChangePrimaryManager(strategy, newManager); |
| 159 | } |
|
| 160 | ||
| 161 | /// @dev removed because we're bypassing hook validation |
|
| 162 | // function superVaultAggregator_proposeStrategyHooksRoot( |
|
| 163 | // address strategy, |
|
| 164 | // bytes32 newRoot |
|
| 165 | // ) public asActor { |
|
| 166 | // superVaultAggregator.proposeStrategyHooksRoot(strategy, newRoot); |
|
| 167 | // } |
|
| 168 | ||
| 169 | 20× | function superVaultAggregator_removeAuthorizedCaller( |
| 170 | address strategy, |
|
| 171 | address caller |
|
| 172 | ) public asActor { |
|
| 173 | 22× | superVaultAggregator.removeAuthorizedCaller(strategy, caller); |
| 174 | } |
|
| 175 | ||
| 176 | 20× | function superVaultAggregator_removeSecondaryManager( |
| 177 | address strategy, |
|
| 178 | address manager |
|
| 179 | ) public asActor { |
|
| 180 | 22× | superVaultAggregator.removeSecondaryManager(strategy, manager); |
| 181 | } |
|
| 182 | ||
| 183 | 20× | function superVaultAggregator_updatePPSVerificationThresholds( |
| 184 | address strategy, |
|
| 185 | uint256 dispersionThreshold_, |
|
| 186 | uint256 deviationThreshold_, |
|
| 187 | uint256 mnThreshold_ |
|
| 188 | ) public asActor { |
|
| 189 | 16× | superVaultAggregator.updatePPSVerificationThresholds( |
| 190 | strategy, |
|
| 191 | dispersionThreshold_, |
|
| 192 | deviationThreshold_, |
|
| 193 | mnThreshold_ |
|
| 194 | ); |
|
| 195 | } |
|
| 196 | ||
| 197 | 20× | function superVaultAggregator_withdrawStake(uint256 amount) public asActor { |
| 198 | 16× | superVaultAggregator.withdrawStake(amount); |
| 199 | } |
|
| 200 | ||
| 201 | 20× | function superVaultAggregator_withdrawUpkeep( |
| 202 | uint256 amount |
|
| 203 | ) public asActor { |
|
| 204 | 16× | superVaultAggregator.withdrawUpkeep(amount); |
| 205 | } |
|
| 206 | } |
100%
test/recon/targets/SuperVaultEscrowTargets.sol
Lines covered: 6 / 6 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | import {BeforeAfter} from "../BeforeAfter.sol"; |
|
| 6 | import {Properties} from "../Properties.sol"; |
|
| 7 | // Chimera deps |
|
| 8 | import {vm} from "@chimera/Hevm.sol"; |
|
| 9 | ||
| 10 | // Helpers |
|
| 11 | import {Panic} from "@recon/Panic.sol"; |
|
| 12 | ||
| 13 | import "src/SuperVault/SuperVaultEscrow.sol"; |
|
| 14 | ||
| 15 | abstract contract SuperVaultEscrowTargets is |
|
| 16 | BaseTargetFunctions, |
|
| 17 | Properties |
|
| 18 | { |
|
| 19 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 20 | ||
| 21 | ||
| 22 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 23 | ||
| 24 | 20× | function superVaultEscrow_escrowShares(address from, uint256 amount) public asActor { |
| 25 | 22× | superVaultEscrow.escrowShares(from, amount); |
| 26 | } |
|
| 27 | ||
| 28 | 20× | function superVaultEscrow_initialize(address vaultAddress, address strategyAddress) public asActor { |
| 29 | 22× | superVaultEscrow.initialize(vaultAddress, strategyAddress); |
| 30 | } |
|
| 31 | ||
| 32 | 20× | function superVaultEscrow_returnShares(address to, uint256 amount) public asActor { |
| 33 | 22× | superVaultEscrow.returnShares(to, amount); |
| 34 | } |
|
| 35 | } |
100%
test/recon/targets/SuperVaultStrategyTargets.sol
Lines covered: 36 / 36 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 5 | ||
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {Panic} from "@recon/Panic.sol"; |
|
| 8 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 9 | ||
| 10 | import "src/SuperVault/SuperVaultStrategy.sol"; |
|
| 11 | ||
| 12 | import {YieldSourceType} from "test/recon/managers/YieldManager.sol"; |
|
| 13 | import {BeforeAfter, OpType} from "../BeforeAfter.sol"; |
|
| 14 | import {Properties} from "../Properties.sol"; |
|
| 15 | ||
| 16 | abstract contract SuperVaultStrategyTargets is BaseTargetFunctions, Properties { |
|
| 17 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 18 | ||
| 19 | /// @dev Clamps the action type to 0 to add a vault as a yield source |
|
| 20 | 20× | function superVaultStrategy_manageYieldSource_clamped( |
| 21 | uint256 sourceType |
|
| 22 | ) public { |
|
| 23 | 16× | YieldSourceType clampedType = YieldSourceType(sourceType % 3); // 0=ERC4626, 1=ERC5115, 2=ERC7540 |
| 24 | 8× | address yieldSourceOracle = _getYieldSourceOracleForType(clampedType); |
| 25 | ||
| 26 | 3× | superVaultStrategy_manageYieldSource( |
| 27 | 4× | _getYieldSource(), |
| 28 | 1× | yieldSourceOracle, |
| 29 | 1× | 0 |
| 30 | ); |
|
| 31 | } |
|
| 32 | ||
| 33 | 20× | function superVaultStrategy_handleOperations7540_clamped( |
| 34 | uint256 operation, |
|
| 35 | uint256 amount |
|
| 36 | ) public { |
|
| 37 | 8× | operation %= 7; // clamp by the possible operation types in the enum |
| 38 | 3× | superVaultStrategy_handleOperations7540( |
| 39 | 11× | ISuperVaultStrategy.Operation(operation), |
| 40 | _getActor(), |
|
| 41 | _getActor(), |
|
| 42 | 1× | amount |
| 43 | ); |
|
| 44 | } |
|
| 45 | ||
| 46 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 47 | ||
| 48 | 17× | function superVaultStrategy_executeVaultFeeConfigUpdate() public asActor { |
| 49 | 65× | superVaultStrategy.executeVaultFeeConfigUpdate(); |
| 50 | } |
|
| 51 | ||
| 52 | 20× | function superVaultStrategy_handleOperations4626Deposit( |
| 53 | address controller, |
|
| 54 | uint256 assetsGross |
|
| 55 | ) public asActor { |
|
| 56 | 46× | superVaultStrategy.handleOperations4626Deposit(controller, assetsGross); |
| 57 | } |
|
| 58 | ||
| 59 | 29× | function superVaultStrategy_handleOperations4626Mint( |
| 60 | address controller, |
|
| 61 | uint256 sharesNet, |
|
| 62 | uint256 assetsGross, |
|
| 63 | uint256 assetsNet |
|
| 64 | ) public asActor { |
|
| 65 | 53× | superVaultStrategy.handleOperations4626Mint( |
| 66 | controller, |
|
| 67 | sharesNet, |
|
| 68 | assetsGross, |
|
| 69 | assetsNet |
|
| 70 | ); |
|
| 71 | } |
|
| 72 | ||
| 73 | 20× | function superVaultStrategy_handleOperations7540( |
| 74 | ISuperVaultStrategy.Operation operation, |
|
| 75 | address controller, |
|
| 76 | address receiver, |
|
| 77 | uint256 amount |
|
| 78 | ) public asActor { |
|
| 79 | 18× | superVaultStrategy.handleOperations7540( |
| 80 | 2× | operation, |
| 81 | 2× | controller, |
| 82 | 2× | receiver, |
| 83 | 2× | amount |
| 84 | ); |
|
| 85 | } |
|
| 86 | ||
| 87 | 20× | function superVaultStrategy_manageEmergencyWithdraw( |
| 88 | uint8 action, |
|
| 89 | address recipient, |
|
| 90 | uint256 amount |
|
| 91 | ) public asActor { |
|
| 92 | 16× | superVaultStrategy.manageEmergencyWithdraw(action, recipient, amount); |
| 93 | } |
|
| 94 | ||
| 95 | 28× | function superVaultStrategy_manageYieldSource( |
| 96 | address source, |
|
| 97 | address oracle, |
|
| 98 | uint8 actionType |
|
| 99 | ) public asActor { |
|
| 100 | 91× | superVaultStrategy.manageYieldSource(source, oracle, actionType); |
| 101 | } |
|
| 102 | ||
| 103 | 20× | function superVaultStrategy_manageYieldSources( |
| 104 | address[] memory sources, |
|
| 105 | address[] memory oracles, |
|
| 106 | uint8[] memory actionTypes |
|
| 107 | ) public asActor { |
|
| 108 | 24× | superVaultStrategy.manageYieldSources(sources, oracles, actionTypes); |
| 109 | } |
|
| 110 | ||
| 111 | 20× | function superVaultStrategy_moveAccumulatorOnTransfer( |
| 112 | address from, |
|
| 113 | address to, |
|
| 114 | uint256 shares |
|
| 115 | ) public asActor { |
|
| 116 | 24× | superVaultStrategy.moveAccumulatorOnTransfer(from, to, shares); |
| 117 | } |
|
| 118 | ||
| 119 | 20× | function superVaultStrategy_proposeVaultFeeConfigUpdate( |
| 120 | uint256 performanceFeeBps, |
|
| 121 | uint256 managementFeeBps, |
|
| 122 | address recipient |
|
| 123 | ) public asActor { |
|
| 124 | 16× | superVaultStrategy.proposeVaultFeeConfigUpdate( |
| 125 | performanceFeeBps, |
|
| 126 | managementFeeBps, |
|
| 127 | recipient |
|
| 128 | ); |
|
| 129 | } |
|
| 130 | ||
| 131 | 20× | function superVaultStrategy_updateMaxPPSSlippage( |
| 132 | uint256 maxSlippageBps |
|
| 133 | ) public asActor { |
|
| 134 | 16× | superVaultStrategy.updateMaxPPSSlippage(maxSlippageBps); |
| 135 | } |
|
| 136 | } |
100%
test/recon/targets/SuperVaultTargets.sol
Lines covered: 123 / 123 (100%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // Recon deps |
|
| 5 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | import {Panic} from "@recon/Panic.sol"; |
|
| 8 | import {MockERC20} from "@recon/MockERC20.sol"; |
|
| 9 | ||
| 10 | import "src/SuperVault/SuperVault.sol"; |
|
| 11 | ||
| 12 | import {BeforeAfter, OpType} from "test/recon/BeforeAfter.sol"; |
|
| 13 | import {Properties} from "../Properties.sol"; |
|
| 14 | ||
| 15 | /// @dev All receivers are inherently clamped to actors to make checking properties easier |
|
| 16 | abstract contract SuperVaultTargets is BaseTargetFunctions, Properties { |
|
| 17 | /// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
|
| 18 | 20× | function superVault_requestRedeem_clamped(uint256 shares) public { |
| 19 | 76× | shares %= superVault.balanceOf(_getActor()) + 1; |
| 20 | ||
| 21 | 4× | superVault_requestRedeem(shares); |
| 22 | } |
|
| 23 | ||
| 24 | 20× | function superVault_redeem_clamped(uint256 shares) public { |
| 25 | 66× | uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
| 26 | 63× | uint256 claimableShares = superVault.convertToShares(claimableAssets); |
| 27 | ||
| 28 | 13× | shares %= claimableShares + 1; |
| 29 | ||
| 30 | 4× | superVault_redeem(shares); |
| 31 | } |
|
| 32 | ||
| 33 | 20× | function superVault_withdraw_clamped(uint256 assets) public { |
| 34 | 66× | uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
| 35 | 13× | assets %= claimableAssets + 1; |
| 36 | ||
| 37 | 2× | superVault_withdraw(assets); |
| 38 | } |
|
| 39 | ||
| 40 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 41 | ||
| 42 | 20× | function superVault_approve(address spender, uint256 value) public asActor { |
| 43 | 74× | superVault.approve(spender, value); |
| 44 | } |
|
| 45 | ||
| 46 | 20× | function superVault_burnShares(uint256 amount) public asActor { |
| 47 | 16× | superVault.burnShares(amount); |
| 48 | } |
|
| 49 | ||
| 50 | /// @dev Property: pendingRedeemRequest should be 0 after a user calls cancelRedeem |
|
| 51 | /// @dev Property: averageRequestPPS should be 0 after a user calls cancelRedeem |
|
| 52 | /// @dev Property: user shouldn't receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem |
|
| 53 | 15× | function superVault_cancelRedeem_ASSERTION_CANCEL_REDEEM_NO_OVERPAY() |
| 54 | public |
|
| 55 | 1× | updateGhostsWithOpType(OpType.CANCEL) |
| 56 | 6× | { |
| 57 | 71× | uint256 pendingRedeemRequestsBefore = superVault.pendingRedeemRequest( |
| 58 | 0, |
|
| 59 | 2× | _getActor() |
| 60 | ); |
|
| 61 | 63× | uint256 pendingRedeemRequestsAsAssets = superVault.convertToAssets( |
| 62 | pendingRedeemRequestsBefore |
|
| 63 | ); |
|
| 64 | 127× | uint256 balanceBefore = MockERC20(superVault.asset()).balanceOf( |
| 65 | 2× | _getActor() |
| 66 | ); |
|
| 67 | ||
| 68 | 41× | vm.prank(_getActor()); |
| 69 | 53× | superVault.cancelRedeem(_getActor()); |
| 70 | ||
| 71 | 72× | uint256 pendingRedeemRequestsAfter = superVault.pendingRedeemRequest( |
| 72 | 0, |
|
| 73 | 2× | _getActor() |
| 74 | ); |
|
| 75 | 67× | uint256 averageRequestPPS = superVaultStrategy |
| 76 | 2× | .getSuperVaultState(_getActor()) |
| 77 | .averageRequestPPS; |
|
| 78 | 128× | uint256 balanceAfter = MockERC20(superVault.asset()).balanceOf( |
| 79 | 2× | _getActor() |
| 80 | ); |
|
| 81 | ||
| 82 | // Checks |
|
| 83 | 4× | eq( |
| 84 | 1× | pendingRedeemRequestsAfter, |
| 85 | 1× | 0, |
| 86 | 17× | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 87 | ); |
|
| 88 | 4× | eq( |
| 89 | 1× | averageRequestPPS, |
| 90 | 1× | 0, |
| 91 | 17× | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 92 | ); |
|
| 93 | 4× | lte( |
| 94 | 6× | balanceAfter - balanceBefore, |
| 95 | 1× | pendingRedeemRequestsAsAssets, |
| 96 | 17× | ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
| 97 | ); |
|
| 98 | } |
|
| 99 | ||
| 100 | /// @dev Property: previewDeposit returns the correct amounts compared to executing a deposit |
|
| 101 | 20× | function superVault_deposit_ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION( |
| 102 | uint256 assets |
|
| 103 | 3× | ) public updateGhostsWithOpType(OpType.ADD) { |
| 104 | 67× | uint256 previewShares = superVault.previewDeposit(assets); |
| 105 | ||
| 106 | 41× | vm.prank(_getActor()); |
| 107 | 82× | uint256 shares = superVault.deposit(assets, _getActor()); |
| 108 | ||
| 109 | 4× | eq( |
| 110 | 1× | previewShares, |
| 111 | 1× | shares, |
| 112 | 17× | ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION |
| 113 | ); |
|
| 114 | } |
|
| 115 | ||
| 116 | /// @dev Property: previewMint returns the correct amounts compared to executing a mint |
|
| 117 | 20× | function superVault_mint_ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION( |
| 118 | uint256 shares |
|
| 119 | 1× | ) public updateGhostsWithOpType(OpType.ADD) { |
| 120 | 67× | uint256 previewMint = superVault.previewMint(shares); |
| 121 | ||
| 122 | 41× | vm.prank(_getActor()); |
| 123 | 82× | uint256 assets = superVault.mint(shares, _getActor()); |
| 124 | ||
| 125 | 3× | eq( |
| 126 | 1× | assets, |
| 127 | 1× | previewMint, |
| 128 | 17× | ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION |
| 129 | ); |
|
| 130 | } |
|
| 131 | ||
| 132 | 20× | function superVault_invalidateNonce(bytes32 nonce) public asActor { |
| 133 | 16× | superVault.invalidateNonce(nonce); |
| 134 | } |
|
| 135 | ||
| 136 | 20× | function superVault_redeem( |
| 137 | uint256 shares |
|
| 138 | 1× | ) public updateGhostsWithOpType(OpType.REMOVE) asActor { |
| 139 | 8× | superVault.redeem(shares, _getActor(), _getActor()); |
| 140 | } |
|
| 141 | ||
| 142 | 20× | function superVault_withdraw( |
| 143 | uint256 assets |
|
| 144 | 1× | ) public updateGhostsWithOpType(OpType.REMOVE) asActor { |
| 145 | 8× | superVault.withdraw(assets, _getActor(), _getActor()); |
| 146 | } |
|
| 147 | ||
| 148 | 26× | function superVault_requestRedeem( |
| 149 | uint256 shares |
|
| 150 | 1× | ) public updateGhostsWithOpType(OpType.REQUEST) asActor { |
| 151 | 81× | superVault.requestRedeem(shares, _getActor(), _getActor()); |
| 152 | } |
|
| 153 | ||
| 154 | 20× | function superVault_setOperator( |
| 155 | uint256 entropy, |
|
| 156 | bool approved |
|
| 157 | ) public asActor { |
|
| 158 | 8× | address operator = _getRandomActor(entropy); |
| 159 | 65× | superVault.setOperator(operator, approved); |
| 160 | } |
|
| 161 | ||
| 162 | /// @dev Propery: _update should never revert |
|
| 163 | /// @dev Property: Transfers of shares should transfer the exact amount of accumulatorShares to the recipient |
|
| 164 | /// @dev Property: Transfers of shares should transfer the exact amount of accumulatorCostBasis to the recipient |
|
| 165 | // NOTE: _update only gets called on transfer of Vault shares |
|
| 166 | 20× | function superVault_transfer_ASSERTION_TRANSFER_SHARES_CONSERVED( |
| 167 | uint256 entropy, |
|
| 168 | uint256 value |
|
| 169 | 4× | ) public updateGhostsWithOpType(OpType.TRANSFER) { |
| 170 | 8× | address to = _getRandomActor(entropy); |
| 171 | 4× | ISuperVaultStrategy.SuperVaultState |
| 172 | 60× | memory stateSenderBefore = superVaultStrategy.getSuperVaultState( |
| 173 | 2× | _getActor() |
| 174 | ); |
|
| 175 | 3× | ISuperVaultStrategy.SuperVaultState |
| 176 | 59× | memory stateRecipientBefore = superVaultStrategy.getSuperVaultState( |
| 177 | to |
|
| 178 | ); |
|
| 179 | ||
| 180 | 41× | vm.prank(_getActor()); |
| 181 | 107× | try superVault.transfer(to, value) { |
| 182 | 4× | ISuperVaultStrategy.SuperVaultState |
| 183 | 60× | memory stateSenderAfter = superVaultStrategy.getSuperVaultState( |
| 184 | 2× | _getActor() |
| 185 | ); |
|
| 186 | 4× | ISuperVaultStrategy.SuperVaultState |
| 187 | 59× | memory stateRecipientAfter = superVaultStrategy |
| 188 | .getSuperVaultState(to); |
|
| 189 | ||
| 190 | 4× | eq( |
| 191 | 10× | stateSenderBefore.accumulatorShares - |
| 192 | 4× | stateSenderAfter.accumulatorShares, |
| 193 | 10× | stateRecipientAfter.accumulatorShares - |
| 194 | 4× | stateRecipientBefore.accumulatorShares, |
| 195 | 17× | ASSERTION_TRANSFER_SHARES_CONSERVED |
| 196 | ); |
|
| 197 | 2× | eq( |
| 198 | 10× | stateSenderBefore.accumulatorCostBasis - |
| 199 | 4× | stateSenderAfter.accumulatorCostBasis, |
| 200 | 9× | stateRecipientAfter.accumulatorCostBasis - |
| 201 | 4× | stateRecipientBefore.accumulatorCostBasis, |
| 202 | ASSERTION_TRANSFER_SHARES_CONSERVED |
|
| 203 | ); |
|
| 204 | 2× | } catch (bytes memory err) { |
| 205 | 1× | bool expectedError; |
| 206 | 23× | expectedError = checkError( |
| 207 | 1× | err, |
| 208 | "ERC20InsufficientBalance(address,uint256,uint256)" |
|
| 209 | ); |
|
| 210 | 22× | t(expectedError, ASSERTION_TRANSFER_SHARES_CONSERVED); |
| 211 | } |
|
| 212 | } |
|
| 213 | ||
| 214 | /// @dev Propery: _update should never revert |
|
| 215 | // NOTE: _update only gets called on transfer of Vault shares |
|
| 216 | 20× | function superVault_transferFrom_ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM( |
| 217 | uint256 entropyFrom, |
|
| 218 | uint256 entropyTo, |
|
| 219 | uint256 value |
|
| 220 | 3× | ) public updateGhostsWithOpType(OpType.TRANSFER) { |
| 221 | 8× | address from = _getRandomActor(entropyFrom); |
| 222 | 7× | address to = _getRandomActor(entropyTo); |
| 223 | ||
| 224 | 41× | vm.prank(_getActor()); |
| 225 | 109× | try superVault.transferFrom(from, to, value) {} catch ( |
| 226 | bytes memory err |
|
| 227 | 1× | ) { |
| 228 | 1× | bool expectedError; |
| 229 | 2× | expectedError = |
| 230 | 25× | checkError( |
| 231 | 1× | err, |
| 232 | "ERC20InsufficientBalance(address,uint256,uint256)" |
|
| 233 | ) || |
|
| 234 | 21× | checkError( |
| 235 | 1× | err, |
| 236 | "ERC20InsufficientAllowance(address,uint256,uint256)" |
|
| 237 | ); |
|
| 238 | 4× | t( |
| 239 | 1× | expectedError, |
| 240 | 17× | ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM |
| 241 | ); |
|
| 242 | } |
|
| 243 | } |
|
| 244 | ||
| 245 | /// @dev removed because signature components not fuzzable |
|
| 246 | // function superVault_authorizeOperator( |
|
| 247 | // address controller, |
|
| 248 | // address operator, |
|
| 249 | // bool approved, |
|
| 250 | // bytes32 nonce, |
|
| 251 | // uint256 deadline, |
|
| 252 | // bytes memory signature |
|
| 253 | // ) public asActor { |
|
| 254 | // superVault.authorizeOperator( |
|
| 255 | // controller, |
|
| 256 | // operator, |
|
| 257 | // approved, |
|
| 258 | // nonce, |
|
| 259 | // deadline, |
|
| 260 | // signature |
|
| 261 | // ); |
|
| 262 | // } |
|
| 263 | } |
94%
test/recon/targets/YieldSourceTargets.sol
Lines covered: 166 / 176 (94%)
| 1 | // SPDX-License-Identifier: GPL-2.0 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | // Chimera deps |
|
| 5 | import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol"; |
|
| 6 | import {vm} from "@chimera/Hevm.sol"; |
|
| 7 | ||
| 8 | // Helpers |
|
| 9 | import {Panic} from "@recon/Panic.sol"; |
|
| 10 | ||
| 11 | import {Properties} from "../Properties.sol"; |
|
| 12 | import {MockERC4626Tester, FunctionType, RevertType} from "../mocks/MockERC4626Tester.sol"; |
|
| 13 | import {MockERC5115Tester, RevertType as RevertType5115} from "../mocks/MockERC5115Tester.sol"; |
|
| 14 | import {MockERC7540Tester} from "../mocks/MockERC7540Tester.sol"; |
|
| 15 | import {YieldSourceType} from "../managers/YieldManager.sol"; |
|
| 16 | ||
| 17 | /// @dev Target functions for yield source testers which are used as yield sources in SuperVaultStrategy |
|
| 18 | abstract contract YieldSourceTargets is BaseTargetFunctions, Properties { |
|
| 19 | /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
|
| 20 | ||
| 21 | /// ERC20 functions (available across all types as they inherit from MockERC20) /// |
|
| 22 | 20× | function yieldSource_approve( |
| 23 | address spender, |
|
| 24 | uint256 value |
|
| 25 | ) public asActor { |
|
| 26 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 27 | 6× | address yieldSource = _getYieldSource(); |
| 28 | ||
| 29 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 30 | 19× | MockERC4626Tester(yieldSource).approve(spender, value); |
| 31 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 32 | 0 | MockERC5115Tester(yieldSource).approve(spender, value); |
| 33 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 34 | 19× | MockERC7540Tester(yieldSource).approve(spender, value); |
| 35 | } |
|
| 36 | } |
|
| 37 | ||
| 38 | 27× | function yieldSource_setDecimalsOffset( |
| 39 | uint8 targetDecimalsOffset |
|
| 40 | 6× | ) public asActor { |
| 41 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 42 | 6× | address yieldSource = _getYieldSource(); |
| 43 | ||
| 44 | 14× | if (currentType == YieldSourceType.ERC4626) { |
| 45 | 13× | MockERC4626Tester(yieldSource).setDecimalsOffset( |
| 46 | targetDecimalsOffset |
|
| 47 | ); |
|
| 48 | } |
|
| 49 | // Note: ERC5115 and ERC7540 don't have setDecimalsOffset function |
|
| 50 | } |
|
| 51 | ||
| 52 | 20× | function yieldSource_transfer(address to, uint256 value) public asActor { |
| 53 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 54 | 6× | address yieldSource = _getYieldSource(); |
| 55 | ||
| 56 | 15× | if (currentType == YieldSourceType.ERC4626) { |
| 57 | 73× | MockERC4626Tester(yieldSource).transfer(to, value); |
| 58 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 59 | 0 | MockERC5115Tester(yieldSource).transfer(to, value); |
| 60 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 61 | 71× | MockERC7540Tester(yieldSource).transfer(to, value); |
| 62 | } |
|
| 63 | } |
|
| 64 | ||
| 65 | 20× | function yieldSource_transferFrom( |
| 66 | address from, |
|
| 67 | address to, |
|
| 68 | uint256 value |
|
| 69 | ) public asActor { |
|
| 70 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 71 | 6× | address yieldSource = _getYieldSource(); |
| 72 | ||
| 73 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 74 | 73× | MockERC4626Tester(yieldSource).transferFrom(from, to, value); |
| 75 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 76 | 0 | MockERC5115Tester(yieldSource).transferFrom(from, to, value); |
| 77 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 78 | 73× | MockERC7540Tester(yieldSource).transferFrom(from, to, value); |
| 79 | } |
|
| 80 | } |
|
| 81 | ||
| 82 | /// Core Vault Functions (ERC4626/ERC4626-like) /// |
|
| 83 | 20× | function yieldSource_deposit( |
| 84 | uint256 assets, |
|
| 85 | address receiver |
|
| 86 | ) public asActor { |
|
| 87 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 88 | 6× | address yieldSource = _getYieldSource(); |
| 89 | ||
| 90 | 12× | if (currentType == YieldSourceType.ERC4626) { |
| 91 | 19× | MockERC4626Tester(yieldSource).deposit(assets, receiver); |
| 92 | } |
|
| 93 | // Note: ERC5115 has different deposit signature, handled separately |
|
| 94 | // Note: ERC7540 has different deposit signature, handled separately |
|
| 95 | } |
|
| 96 | ||
| 97 | 20× | function yieldSource_mint(uint256 shares, address receiver) public asActor { |
| 98 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 99 | 6× | address yieldSource = _getYieldSource(); |
| 100 | ||
| 101 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 102 | 71× | MockERC4626Tester(yieldSource).mint(shares, receiver); |
| 103 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 104 | 71× | MockERC7540Tester(yieldSource).mint(shares, receiver); |
| 105 | } |
|
| 106 | // Note: ERC5115 doesn't have mint function |
|
| 107 | } |
|
| 108 | ||
| 109 | 20× | function yieldSource_withdraw( |
| 110 | uint256 assets, |
|
| 111 | address receiver, |
|
| 112 | address owner |
|
| 113 | ) public asActor { |
|
| 114 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 115 | 6× | address yieldSource = _getYieldSource(); |
| 116 | ||
| 117 | 15× | if (currentType == YieldSourceType.ERC4626) { |
| 118 | 75× | MockERC4626Tester(yieldSource).withdraw(assets, receiver, owner); |
| 119 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 120 | 21× | MockERC7540Tester(yieldSource).withdraw(assets, receiver, owner); |
| 121 | } |
|
| 122 | // Note: ERC5115 doesn't have withdraw function |
|
| 123 | } |
|
| 124 | ||
| 125 | 20× | function yieldSource_redeem( |
| 126 | uint256 shares, |
|
| 127 | address receiver, |
|
| 128 | address owner |
|
| 129 | ) public asActor { |
|
| 130 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 131 | 6× | address yieldSource = _getYieldSource(); |
| 132 | ||
| 133 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 134 | 21× | MockERC4626Tester(yieldSource).redeem(shares, receiver, owner); |
| 135 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 136 | 21× | MockERC7540Tester(yieldSource).redeem(shares, receiver, owner); |
| 137 | } |
|
| 138 | // Note: ERC5115 has different redeem signature, handled separately |
|
| 139 | } |
|
| 140 | ||
| 141 | /// ERC5115-specific functions /// |
|
| 142 | 20× | function yieldSource_deposit5115( |
| 143 | address receiver, |
|
| 144 | address tokenIn, |
|
| 145 | uint256 amountTokenToDeposit, |
|
| 146 | uint256 minSharesOut, |
|
| 147 | bool depositFromInternalBalance |
|
| 148 | ) public asActor { |
|
| 149 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 150 | 6× | address yieldSource = _getYieldSource(); |
| 151 | ||
| 152 | 12× | if (currentType == YieldSourceType.ERC5115) { |
| 153 | 0 | MockERC5115Tester(yieldSource).deposit( |
| 154 | receiver, |
|
| 155 | tokenIn, |
|
| 156 | amountTokenToDeposit, |
|
| 157 | minSharesOut, |
|
| 158 | depositFromInternalBalance |
|
| 159 | ); |
|
| 160 | } |
|
| 161 | } |
|
| 162 | ||
| 163 | 20× | function yieldSource_redeem5115( |
| 164 | address receiver, |
|
| 165 | uint256 amountSharesToRedeem, |
|
| 166 | address tokenOut, |
|
| 167 | uint256 minTokenOut, |
|
| 168 | bool burnFromInternalBalance |
|
| 169 | ) public asActor { |
|
| 170 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 171 | 6× | address yieldSource = _getYieldSource(); |
| 172 | ||
| 173 | 12× | if (currentType == YieldSourceType.ERC5115) { |
| 174 | 0 | MockERC5115Tester(yieldSource).redeem( |
| 175 | receiver, |
|
| 176 | amountSharesToRedeem, |
|
| 177 | tokenOut, |
|
| 178 | minTokenOut, |
|
| 179 | burnFromInternalBalance |
|
| 180 | ); |
|
| 181 | } |
|
| 182 | } |
|
| 183 | ||
| 184 | /// ERC7540-specific functions /// |
|
| 185 | 20× | function yieldSource_setOperator( |
| 186 | address operator, |
|
| 187 | bool approved |
|
| 188 | ) public asActor { |
|
| 189 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 190 | 6× | address yieldSource = _getYieldSource(); |
| 191 | ||
| 192 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 193 | 13× | MockERC7540Tester(yieldSource).setOperator(operator, approved); |
| 194 | } |
|
| 195 | } |
|
| 196 | ||
| 197 | 20× | function yieldSource_requestDeposit( |
| 198 | uint256 assets, |
|
| 199 | address controller, |
|
| 200 | address owner |
|
| 201 | ) public asActor { |
|
| 202 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 203 | 6× | address yieldSource = _getYieldSource(); |
| 204 | ||
| 205 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 206 | 15× | MockERC7540Tester(yieldSource).requestDeposit( |
| 207 | 2× | assets, |
| 208 | 2× | controller, |
| 209 | 2× | owner |
| 210 | ); |
|
| 211 | } |
|
| 212 | } |
|
| 213 | ||
| 214 | 20× | function yieldSource_requestRedeem( |
| 215 | uint256 shares, |
|
| 216 | address controller, |
|
| 217 | address owner |
|
| 218 | ) public asActor { |
|
| 219 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 220 | 6× | address yieldSource = _getYieldSource(); |
| 221 | ||
| 222 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 223 | 15× | MockERC7540Tester(yieldSource).requestRedeem( |
| 224 | 2× | shares, |
| 225 | 2× | controller, |
| 226 | 2× | owner |
| 227 | ); |
|
| 228 | } |
|
| 229 | } |
|
| 230 | ||
| 231 | 20× | function yieldSource_cancelDepositRequest( |
| 232 | uint256 requestId, |
|
| 233 | address controller |
|
| 234 | ) public asActor { |
|
| 235 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 236 | 6× | address yieldSource = _getYieldSource(); |
| 237 | ||
| 238 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 239 | 15× | MockERC7540Tester(yieldSource).cancelDepositRequest( |
| 240 | 2× | requestId, |
| 241 | 2× | controller |
| 242 | ); |
|
| 243 | } |
|
| 244 | } |
|
| 245 | ||
| 246 | 20× | function yieldSource_cancelRedeemRequest( |
| 247 | uint256 requestId, |
|
| 248 | address controller |
|
| 249 | ) public asActor { |
|
| 250 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 251 | 6× | address yieldSource = _getYieldSource(); |
| 252 | ||
| 253 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 254 | 15× | MockERC7540Tester(yieldSource).cancelRedeemRequest( |
| 255 | 2× | requestId, |
| 256 | 2× | controller |
| 257 | ); |
|
| 258 | } |
|
| 259 | } |
|
| 260 | ||
| 261 | 20× | function yieldSource_claimCancelDepositRequest( |
| 262 | uint256 requestId, |
|
| 263 | address receiver, |
|
| 264 | address controller |
|
| 265 | ) public asActor { |
|
| 266 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 267 | 6× | address yieldSource = _getYieldSource(); |
| 268 | ||
| 269 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 270 | 15× | MockERC7540Tester(yieldSource).claimCancelDepositRequest( |
| 271 | 2× | requestId, |
| 272 | 2× | receiver, |
| 273 | 2× | controller |
| 274 | ); |
|
| 275 | } |
|
| 276 | } |
|
| 277 | ||
| 278 | 20× | function yieldSource_claimCancelRedeemRequest( |
| 279 | uint256 requestId, |
|
| 280 | address receiver, |
|
| 281 | address controller |
|
| 282 | ) public asActor { |
|
| 283 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 284 | 6× | address yieldSource = _getYieldSource(); |
| 285 | ||
| 286 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 287 | 67× | MockERC7540Tester(yieldSource).claimCancelRedeemRequest( |
| 288 | 2× | requestId, |
| 289 | 2× | receiver, |
| 290 | 2× | controller |
| 291 | ); |
|
| 292 | } |
|
| 293 | } |
|
| 294 | ||
| 295 | 20× | function yieldSource_deposit7540( |
| 296 | uint256 assets, |
|
| 297 | address receiver, |
|
| 298 | address controller |
|
| 299 | ) public asActor { |
|
| 300 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 301 | 6× | address yieldSource = _getYieldSource(); |
| 302 | ||
| 303 | 12× | if (currentType == YieldSourceType.ERC7540) { |
| 304 | 15× | MockERC7540Tester(yieldSource).deposit( |
| 305 | 2× | assets, |
| 306 | 2× | receiver, |
| 307 | 2× | controller |
| 308 | ); |
|
| 309 | } |
|
| 310 | } |
|
| 311 | ||
| 312 | /// Testing-specific functions (ERC4626 only) /// |
|
| 313 | 20× | function yieldSource_setRevertBehavior4626( |
| 314 | uint8 functionType, |
|
| 315 | uint8 revertType |
|
| 316 | ) public asActor { |
|
| 317 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 318 | 6× | address yieldSource = _getYieldSource(); |
| 319 | ||
| 320 | 12× | if (currentType == YieldSourceType.ERC4626) { |
| 321 | 20× | MockERC4626Tester(yieldSource).setRevertBehavior( |
| 322 | 13× | FunctionType(functionType), |
| 323 | 13× | RevertType(revertType) |
| 324 | ); |
|
| 325 | } |
|
| 326 | } |
|
| 327 | ||
| 328 | /// Testing-specific functions (ERC5115 only) /// |
|
| 329 | 20× | function yieldSource_setRevertBehavior5115( |
| 330 | uint8 revertType |
|
| 331 | ) public asActor { |
|
| 332 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 333 | 6× | address yieldSource = _getYieldSource(); |
| 334 | ||
| 335 | 12× | if (currentType == YieldSourceType.ERC5115) { |
| 336 | 0 | MockERC5115Tester(yieldSource).setRevertBehavior( |
| 337 | 0 | RevertType5115(revertType) |
| 338 | ); |
|
| 339 | } |
|
| 340 | } |
|
| 341 | ||
| 342 | /// Common yield manipulation functions /// |
|
| 343 | ||
| 344 | 20× | function yieldSource_simulateLoss(uint256 lossAmount) public { |
| 345 | 1× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 346 | 5× | address yieldSource = _getYieldSource(); |
| 347 | ||
| 348 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 349 | 13× | MockERC4626Tester(yieldSource).simulateLoss(lossAmount); |
| 350 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 351 | 0 | MockERC5115Tester(yieldSource).simulateLoss(lossAmount); |
| 352 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 353 | 13× | MockERC7540Tester(yieldSource).simulateLoss(lossAmount); |
| 354 | } |
|
| 355 | } |
|
| 356 | ||
| 357 | 20× | function yieldSource_simulateGain(uint256 gainAmount) public { |
| 358 | 1× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 359 | 5× | address yieldSource = _getYieldSource(); |
| 360 | ||
| 361 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 362 | 13× | MockERC4626Tester(yieldSource).simulateGain(gainAmount); |
| 363 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 364 | 0 | MockERC5115Tester(yieldSource).simulateGain(gainAmount); |
| 365 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 366 | 13× | MockERC7540Tester(yieldSource).simulateGain(gainAmount); |
| 367 | } |
|
| 368 | } |
|
| 369 | ||
| 370 | 20× | function yieldSource_setLossOnWithdraw( |
| 371 | uint256 lossOnWithdraw |
|
| 372 | ) public asActor { |
|
| 373 | 5× | YieldSourceType currentType = _getCurrentYieldSourceType(); |
| 374 | 6× | address yieldSource = _getYieldSource(); |
| 375 | ||
| 376 | 13× | if (currentType == YieldSourceType.ERC4626) { |
| 377 | 13× | MockERC4626Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 378 | 13× | } else if (currentType == YieldSourceType.ERC5115) { |
| 379 | 0 | MockERC5115Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 380 | 12× | } else if (currentType == YieldSourceType.ERC7540) { |
| 381 | 13× | MockERC7540Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
| 382 | } |
|
| 383 | } |
|
| 384 | ||
| 385 | /// Yield source management functions /// |
|
| 386 | 17× | function yieldSource_switchToERC4626() public { |
| 387 | 6× | _switchYieldSource(0); // Switch to first yield source (ERC4626) |
| 388 | } |
|
| 389 | ||
| 390 | 15× | function yieldSource_switchToERC5115() public { |
| 391 | 4× | _switchYieldSource(1); // Switch to second yield source (ERC5115) |
| 392 | } |
|
| 393 | ||
| 394 | 15× | function yieldSource_switchToERC7540() public { |
| 395 | 4× | _switchYieldSource(2); // Switch to third yield source (ERC7540) |
| 396 | } |
|
| 397 | ||
| 398 | 20× | function yieldSource_switchRandom(uint256 entropy) public { |
| 399 | // Randomly switch between the three deployed yield sources |
|
| 400 | 4× | _switchYieldSource(entropy); |
| 401 | } |
|
| 402 | } |