Files
128
Total Lines
28014
Coverage
67.4%
3373 / 5006 lines
Actions
90.0%
lib/erc7540-reusable-properties/src/ERC7540Properties.sol
Lines covered: 99 / 110 (90.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity ^0.8.21; |
||
| 3 | |||
| 4 |
import {Asserts} from "@chimera/Asserts.sol";
|
||
| 5 | |||
| 6 |
interface IShareLike {
|
||
| 7 |
function balanceOf(address) external view returns (uint256); |
||
| 8 |
function totalSupply() external view returns (uint256); |
||
| 9 |
function decimals() external view returns (uint8); |
||
| 10 |
} |
||
| 11 | |||
| 12 |
interface IERC7540Like {
|
||
| 13 |
function share() external view returns (address shareTokenAddress); |
||
| 14 |
function convertToShares( |
||
| 15 |
uint256 assets |
||
| 16 |
) external view returns (uint256 shares); |
||
| 17 |
function convertToAssets( |
||
| 18 |
uint256 shares |
||
| 19 |
) external view returns (uint256 assets); |
||
| 20 |
function totalAssets() external view returns (uint256 totalManagedAssets); |
||
| 21 |
function maxDeposit( |
||
| 22 |
address receiver |
||
| 23 |
) external view returns (uint256 maxAssets); |
||
| 24 |
function previewDeposit( |
||
| 25 |
uint256 assets |
||
| 26 |
) external view returns (uint256 shares); |
||
| 27 |
function deposit( |
||
| 28 |
uint256 assets, |
||
| 29 |
address receiver |
||
| 30 |
) external returns (uint256 shares); |
||
| 31 |
function maxMint( |
||
| 32 |
address receiver |
||
| 33 |
) external view returns (uint256 maxShares); |
||
| 34 |
function previewMint(uint256 shares) external view returns (uint256 assets); |
||
| 35 |
function mint( |
||
| 36 |
uint256 shares, |
||
| 37 |
address receiver |
||
| 38 |
) external returns (uint256 assets); |
||
| 39 |
function maxWithdraw( |
||
| 40 |
address owner |
||
| 41 |
) external view returns (uint256 maxAssets); |
||
| 42 |
function previewWithdraw( |
||
| 43 |
uint256 assets |
||
| 44 |
) external view returns (uint256 shares); |
||
| 45 |
function withdraw( |
||
| 46 |
uint256 assets, |
||
| 47 |
address receiver, |
||
| 48 |
address owner |
||
| 49 |
) external returns (uint256 shares); |
||
| 50 |
function maxRedeem(address owner) external view returns (uint256 maxShares); |
||
| 51 |
function previewRedeem( |
||
| 52 |
uint256 shares |
||
| 53 |
) external view returns (uint256 assets); |
||
| 54 |
function redeem( |
||
| 55 |
uint256 shares, |
||
| 56 |
address receiver, |
||
| 57 |
address owner |
||
| 58 |
) external returns (uint256 assets); |
||
| 59 | |||
| 60 |
function requestRedeem( |
||
| 61 |
uint256 shares, |
||
| 62 |
address receiver, |
||
| 63 |
address owner, |
||
| 64 |
bytes calldata data |
||
| 65 |
) external returns (uint256 requestId); |
||
| 66 |
} |
||
| 67 | |||
| 68 |
/// @dev ERC-7540 Properties |
||
| 69 |
/// @author The Recon Team |
||
| 70 |
/// @notice A set of reuseable tests for your ERC7540 Vaults |
||
| 71 |
/// To get started, extend from this contract and make sure to add a way to set the actor (the current user) |
||
| 72 |
/// For more info: https://getrecon.xyz/ |
||
| 73 |
abstract contract ERC7540Properties is Asserts {
|
||
| 74 |
✓ 10.6K
|
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 |
✓ 141.9K
|
function erc7540_1(address erc7540Target) public virtual returns (bool) {
|
|
| 82 |
// Doesn't hold on zero price |
||
| 83 |
if ( |
||
| 84 |
✓ 141.9K
|
IERC7540Like(erc7540Target).convertToAssets( |
|
| 85 |
✓ 141.9K
|
10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
|
| 86 |
✓ 2.5K
|
) == 0 |
|
| 87 |
✓ 447
|
) return true; |
|
| 88 | |||
| 89 |
return |
||
| 90 |
✓ 2.0K
|
IERC7540Like(erc7540Target).convertToAssets( |
|
| 91 |
✓ 2.0K
|
IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
|
| 92 |
✓ 2.0K
|
) == IERC7540Like(erc7540Target).totalAssets(); |
|
| 93 |
} |
||
| 94 | |||
| 95 |
/// @dev 7540-2 convertToShares(totalAssets) == totalSupply unless price is 0.0 |
||
| 96 |
✓ 181.0K
|
function erc7540_2(address erc7540Target) public virtual returns (bool) {
|
|
| 97 |
if ( |
||
| 98 |
✓ 181.0K
|
IERC7540Like(erc7540Target).convertToAssets( |
|
| 99 |
✓ 181.0K
|
10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals() |
|
| 100 |
✓ 10.7K
|
) == 0 |
|
| 101 |
✓ 79
|
) return true; |
|
| 102 | |||
| 103 |
// convertToShares(totalAssets) == totalSupply |
||
| 104 |
return |
||
| 105 |
✓ 10.6K
|
_diff( |
|
| 106 |
✓ 10.6K
|
IERC7540Like(erc7540Target).convertToShares( |
|
| 107 |
✓ 10.6K
|
IERC7540Like(erc7540Target).totalAssets() |
|
| 108 |
), |
||
| 109 |
✓ 10.6K
|
IShareLike(IERC7540Like(erc7540Target).share()).totalSupply() |
|
| 110 |
) <= MAX_ROUNDING_ERROR; |
||
| 111 |
} |
||
| 112 | |||
| 113 |
✓ 10.6K
|
function _diff(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
| 114 |
✓ 10.6K
|
return a > b ? a - b : b - a; |
|
| 115 |
} |
||
| 116 | |||
| 117 |
/// @dev 7540-3 max* never reverts |
||
| 118 |
function erc7540_3(address erc7540Target) public virtual returns (bool) {
|
||
| 119 |
// max* never reverts |
||
| 120 |
✓ 133.5K
|
try IERC7540Like(erc7540Target).maxDeposit(actor) {} catch {
|
|
| 121 |
✓ 97.0K
|
return false; |
|
| 122 |
} |
||
| 123 |
✓ 6.6K
|
try IERC7540Like(erc7540Target).maxMint(actor) {} catch {
|
|
| 124 |
return false; |
||
| 125 |
} |
||
| 126 |
✓ 6.6K
|
try IERC7540Like(erc7540Target).maxRedeem(actor) {} catch {
|
|
| 127 |
✓ 2.8K
|
return false; |
|
| 128 |
} |
||
| 129 |
✓ 3.8K
|
try IERC7540Like(erc7540Target).maxWithdraw(actor) {} catch {
|
|
| 130 |
return false; |
||
| 131 |
} |
||
| 132 | |||
| 133 |
✓ 3.8K
|
return true; |
|
| 134 |
} |
||
| 135 | |||
| 136 |
/// == erc7540_4 == // |
||
| 137 | |||
| 138 |
/// @dev 7540-4 claiming more than max always reverts |
||
| 139 |
function erc7540_4_deposit( |
||
| 140 |
address erc7540Target, |
||
| 141 |
uint256 amt |
||
| 142 |
✓ 328.7K
|
) public virtual returns (bool) {
|
|
| 143 |
// Skip 0 |
||
| 144 |
✓ 328.7K
|
if (amt == 0) {
|
|
| 145 |
✓ 4.3K
|
return true; // Skip |
|
| 146 |
} |
||
| 147 | |||
| 148 |
✓ 324.4K
|
uint256 maxDep = IERC7540Like(erc7540Target).maxDeposit(actor); |
|
| 149 | |||
| 150 |
/// @audit No Revert is proven by erc7540_5 |
||
| 151 | |||
| 152 |
✓ 198.6K
|
uint256 sum = maxDep + amt; |
|
| 153 |
✓ 5.2K
|
if (sum == 0) {
|
|
| 154 |
return true; // Needs to be greater than 0, skip |
||
| 155 |
} |
||
| 156 | |||
| 157 |
✓ 5.2K
|
try IERC7540Like(erc7540Target).deposit(maxDep + amt, actor) {
|
|
| 158 |
return false; |
||
| 159 |
} catch {
|
||
| 160 |
// We want this to be hit |
||
| 161 |
return true; // So we explicitly return here, as a means to ensure that this is the code path |
||
| 162 |
} |
||
| 163 | |||
| 164 |
// NOTE: This code path is never hit per the above |
||
| 165 |
} |
||
| 166 | |||
| 167 |
function erc7540_4_mint( |
||
| 168 |
address erc7540Target, |
||
| 169 |
uint256 amt |
||
| 170 |
✓ 255.5K
|
) public virtual returns (bool) {
|
|
| 171 |
// Skip 0 |
||
| 172 |
✓ 255.5K
|
if (amt == 0) {
|
|
| 173 |
✓ 4.8K
|
return true; |
|
| 174 |
} |
||
| 175 | |||
| 176 |
✓ 250.7K
|
uint256 maxDep = IERC7540Like(erc7540Target).maxMint(actor); |
|
| 177 | |||
| 178 |
✓ 138.4K
|
uint256 sum = maxDep + amt; |
|
| 179 |
✓ 1.2K
|
if (sum == 0) {
|
|
| 180 |
return true; // Needs to be greater than 0, skip |
||
| 181 |
} |
||
| 182 | |||
| 183 |
✓ 6.4K
|
try IERC7540Like(erc7540Target).mint(maxDep + amt, actor) {
|
|
| 184 |
return false; |
||
| 185 |
} catch {
|
||
| 186 |
// We want this to be hit |
||
| 187 |
return true; // So we explicitly return here, as a means to ensure that this is the code path |
||
| 188 |
} |
||
| 189 | |||
| 190 |
// NOTE: This code path is never hit per the above |
||
| 191 |
} |
||
| 192 | |||
| 193 |
function erc7540_4_withdraw( |
||
| 194 |
address erc7540Target, |
||
| 195 |
uint256 amt |
||
| 196 |
✓ 261.2K
|
) public virtual returns (bool) {
|
|
| 197 |
// Skip 0 |
||
| 198 |
✓ 261.2K
|
if (amt == 0) {
|
|
| 199 |
✓ 3.9K
|
return true; |
|
| 200 |
} |
||
| 201 | |||
| 202 |
✓ 257.4K
|
uint256 maxDep = IERC7540Like(erc7540Target).maxWithdraw(actor); |
|
| 203 | |||
| 204 |
✓ 131.5K
|
uint256 sum = maxDep + amt; |
|
| 205 |
✓ 131.5K
|
if (sum == 0) {
|
|
| 206 |
return true; // Needs to be greater than 0 |
||
| 207 |
} |
||
| 208 | |||
| 209 |
✓ 131.5K
|
try IERC7540Like(erc7540Target).withdraw(maxDep + amt, actor, actor) {
|
|
| 210 |
return false; |
||
| 211 |
} catch {
|
||
| 212 |
// We want this to be hit |
||
| 213 |
return true; // So we explicitly return here, as a means to ensure that this is the code path |
||
| 214 |
} |
||
| 215 | |||
| 216 |
// NOTE: This code path is never hit per the above |
||
| 217 |
} |
||
| 218 | |||
| 219 |
function erc7540_4_redeem( |
||
| 220 |
address erc7540Target, |
||
| 221 |
uint256 amt |
||
| 222 |
✓ 289.3K
|
) public virtual returns (bool) {
|
|
| 223 |
// Skip 0 |
||
| 224 |
✓ 289.3K
|
if (amt == 0) {
|
|
| 225 |
✓ 3.4K
|
return true; |
|
| 226 |
} |
||
| 227 | |||
| 228 |
✓ 285.9K
|
uint256 maxDep = IERC7540Like(erc7540Target).maxRedeem(actor); |
|
| 229 | |||
| 230 |
✓ 130.7K
|
uint256 sum = maxDep + amt; |
|
| 231 |
✓ 130.7K
|
if (sum == 0) {
|
|
| 232 |
return true; // Needs to be greater than 0 |
||
| 233 |
} |
||
| 234 | |||
| 235 |
✓ 262.2K
|
try IERC7540Like(erc7540Target).redeem(maxDep + amt, actor, actor) {
|
|
| 236 |
return false; |
||
| 237 |
} catch {
|
||
| 238 |
// We want this to be hit |
||
| 239 |
✓ 268.6K
|
return true; // So we explicitly return here, as a means to ensure that this is the code path |
|
| 240 |
} |
||
| 241 | |||
| 242 |
// NOTE: This code path is never hit per the above |
||
| 243 |
} |
||
| 244 | |||
| 245 |
/// == END erc7540_4 == // |
||
| 246 | |||
| 247 |
/// @dev 7540-5 requestRedeem reverts if the share balance is less than amount |
||
| 248 |
function erc7540_5( |
||
| 249 |
address erc7540Target, |
||
| 250 |
address shareToken, |
||
| 251 |
uint256 shares |
||
| 252 |
✓ 285.3K
|
) public virtual returns (bool) {
|
|
| 253 |
✓ 285.3K
|
if (shares == 0) {
|
|
| 254 |
✓ 15.5K
|
return true; // Skip |
|
| 255 |
} |
||
| 256 | |||
| 257 |
✓ 269.8K
|
uint256 actualBal = IShareLike(shareToken).balanceOf(actor); |
|
| 258 |
✓ 137.2K
|
uint256 balWeWillUse = actualBal + shares; |
|
| 259 | |||
| 260 |
✓ 135.8K
|
if (balWeWillUse == 0) {
|
|
| 261 |
return true; // Skip |
||
| 262 |
} |
||
| 263 | |||
| 264 |
try |
||
| 265 |
✓ 135.8K
|
IERC7540Like(erc7540Target).requestRedeem( |
|
| 266 |
balWeWillUse, |
||
| 267 |
✓ 135.8K
|
actor, |
|
| 268 |
actor, |
||
| 269 |
"" |
||
| 270 |
) |
||
| 271 |
{
|
||
| 272 |
return false; |
||
| 273 |
} catch {
|
||
| 274 |
✓ 134.2K
|
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 |
✓ 124.9K
|
function erc7540_6(address erc7540Target) public virtual returns (bool) {
|
|
| 282 |
// preview* always reverts |
||
| 283 |
✓ 124.9K
|
try IERC7540Like(erc7540Target).previewDeposit(0) {
|
|
| 284 |
✓ 3.7K
|
return false; |
|
| 285 |
} catch {}
|
||
| 286 |
✓ 92.9K
|
try IERC7540Like(erc7540Target).previewMint(0) {
|
|
| 287 |
✓ 3.1K
|
return false; |
|
| 288 |
} catch {}
|
||
| 289 |
✓ 89.8K
|
try IERC7540Like(erc7540Target).previewRedeem(0) {
|
|
| 290 |
return false; |
||
| 291 |
} catch {}
|
||
| 292 |
✓ 89.8K
|
try IERC7540Like(erc7540Target).previewWithdraw(0) {
|
|
| 293 |
return false; |
||
| 294 |
} catch {}
|
||
| 295 | |||
| 296 |
✓ 89.8K
|
return true; |
|
| 297 |
} |
||
| 298 | |||
| 299 |
/// == erc7540_7 == // |
||
| 300 | |||
| 301 |
/// @dev 7540-7 if max[method] > 0, then [method] (max) should not revert |
||
| 302 |
function erc7540_7_deposit( |
||
| 303 |
address erc7540Target, |
||
| 304 |
uint256 amt |
||
| 305 |
✓ 110.8K
|
) public virtual returns (bool) {
|
|
| 306 |
// Per erc7540_4 |
||
| 307 |
✓ 110.8K
|
uint256 maxDeposit = IERC7540Like(erc7540Target).maxDeposit(actor); |
|
| 308 |
✓ 6.6K
|
amt = between(amt, 0, maxDeposit); |
|
| 309 | |||
| 310 |
✓ 6.6K
|
if (amt == 0) {
|
|
| 311 |
✓ 1.8K
|
return true; // Skip |
|
| 312 |
} |
||
| 313 | |||
| 314 |
✓ 4.8K
|
try IERC7540Like(erc7540Target).deposit(amt, actor) {
|
|
| 315 |
// Success here |
||
| 316 |
return true; |
||
| 317 |
} catch {
|
||
| 318 |
return false; |
||
| 319 |
} |
||
| 320 | |||
| 321 |
// NOTE: This code path is never hit per the above |
||
| 322 |
} |
||
| 323 | |||
| 324 |
function erc7540_7_mint( |
||
| 325 |
address erc7540Target, |
||
| 326 |
uint256 amt |
||
| 327 |
✓ 129.7K
|
) public virtual returns (bool) {
|
|
| 328 |
✓ 129.7K
|
uint256 maxMint = IERC7540Like(erc7540Target).maxMint(actor); |
|
| 329 |
✓ 7.8K
|
amt = between(amt, 0, maxMint); |
|
| 330 | |||
| 331 |
✓ 7.8K
|
if (amt == 0) {
|
|
| 332 |
✓ 1.7K
|
return true; // Skip |
|
| 333 |
} |
||
| 334 | |||
| 335 |
✓ 6.2K
|
try IERC7540Like(erc7540Target).mint(amt, actor) {
|
|
| 336 |
// Success here |
||
| 337 |
return true; |
||
| 338 |
} catch {
|
||
| 339 |
return false; |
||
| 340 |
} |
||
| 341 | |||
| 342 |
// NOTE: This code path is never hit per the above |
||
| 343 |
} |
||
| 344 | |||
| 345 |
function erc7540_7_withdraw( |
||
| 346 |
address erc7540Target, |
||
| 347 |
uint256 amt |
||
| 348 |
✓ 296.9K
|
) public virtual returns (bool) {
|
|
| 349 |
✓ 296.9K
|
uint256 maxWithdraw = IERC7540Like(erc7540Target).maxWithdraw(actor); |
|
| 350 |
✓ 151.8K
|
amt = between(amt, 0, maxWithdraw); |
|
| 351 | |||
| 352 |
✓ 151.8K
|
if (amt == 0) {
|
|
| 353 |
✓ 151.2K
|
return true; // Skip |
|
| 354 |
} |
||
| 355 | |||
| 356 |
✓ 688
|
try IERC7540Like(erc7540Target).withdraw(amt, actor, actor) {
|
|
| 357 |
// Success here |
||
| 358 |
✓ 1.9K
|
return true; |
|
| 359 |
} catch {
|
||
| 360 |
✓ 10.0K
|
return false; |
|
| 361 |
} |
||
| 362 | |||
| 363 |
// NOTE: This code path is never hit per the above |
||
| 364 |
} |
||
| 365 | |||
| 366 |
function erc7540_7_redeem( |
||
| 367 |
address erc7540Target, |
||
| 368 |
uint256 amt |
||
| 369 |
✓ 147.1K
|
) public virtual returns (bool) {
|
|
| 370 |
// Per erc7540_4 |
||
| 371 |
✓ 147.1K
|
uint256 maxRedeem = IERC7540Like(erc7540Target).maxRedeem(actor); |
|
| 372 |
✓ 5.3K
|
amt = between(amt, 0, maxRedeem); |
|
| 373 | |||
| 374 |
✓ 5.3K
|
if (amt == 0) {
|
|
| 375 |
✓ 5.1K
|
return true; // Skip |
|
| 376 |
} |
||
| 377 | |||
| 378 |
✓ 195
|
try IERC7540Like(erc7540Target).redeem(amt, actor, actor) {
|
|
| 379 |
return true; |
||
| 380 |
} catch {
|
||
| 381 |
return false; |
||
| 382 |
} |
||
| 383 | |||
| 384 |
// NOTE: This code path is never hit per the above |
||
| 385 |
} |
||
| 386 | |||
| 387 |
/// == END erc7540_7 == // |
||
| 388 |
} |
||
| 389 |
88.5%
lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
Lines covered: 23 / 26 (88.5%)
| 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 |
✓ 1.6M
|
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; |
|
| 78 | |||
| 79 |
/** |
||
| 80 |
* @dev The contract is already initialized. |
||
| 81 |
*/ |
||
| 82 |
error InvalidInitialization(); |
||
| 83 | |||
| 84 |
/** |
||
| 85 |
* @dev The contract is not initializing. |
||
| 86 |
*/ |
||
| 87 |
error NotInitializing(); |
||
| 88 | |||
| 89 |
/** |
||
| 90 |
* @dev Triggered when the contract has been initialized or reinitialized. |
||
| 91 |
*/ |
||
| 92 |
event Initialized(uint64 version); |
||
| 93 | |||
| 94 |
/** |
||
| 95 |
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, |
||
| 96 |
* `onlyInitializing` functions can be used to initialize parent contracts. |
||
| 97 |
* |
||
| 98 |
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any |
||
| 99 |
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in |
||
| 100 |
* production. |
||
| 101 |
* |
||
| 102 |
* Emits an {Initialized} event.
|
||
| 103 |
*/ |
||
| 104 |
modifier initializer() {
|
||
| 105 |
// solhint-disable-next-line var-name-mixedcase |
||
| 106 |
✓ 228.0K
|
InitializableStorage storage $ = _getInitializableStorage(); |
|
| 107 | |||
| 108 |
// Cache values to avoid duplicated sloads |
||
| 109 |
✓ 228.0K
|
bool isTopLevelCall = !$._initializing; |
|
| 110 |
✓ 228.0K
|
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 |
✓ 228.0K
|
bool initialSetup = initialized == 0 && isTopLevelCall; |
|
| 118 |
✓ 228.0K
|
bool construction = initialized == 1 && address(this).code.length == 0; |
|
| 119 | |||
| 120 |
✓ 228.0K
|
if (!initialSetup && !construction) {
|
|
| 121 |
revert InvalidInitialization(); |
||
| 122 |
} |
||
| 123 |
✓ 228.0K
|
$._initialized = 1; |
|
| 124 |
if (isTopLevelCall) {
|
||
| 125 |
✓ 228.0K
|
$._initializing = true; |
|
| 126 |
} |
||
| 127 |
_; |
||
| 128 |
✓ 112.6K
|
if (isTopLevelCall) {
|
|
| 129 |
✓ 122.2K
|
$._initializing = false; |
|
| 130 |
✓ 122.2K
|
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 |
✓ 228.0K
|
_checkInitializing(); |
|
| 172 |
_; |
||
| 173 |
} |
||
| 174 | |||
| 175 |
/** |
||
| 176 |
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
|
||
| 177 |
*/ |
||
| 178 |
function _checkInitializing() internal view virtual {
|
||
| 179 |
✓ 1.4M
|
if (!_isInitializing()) {
|
|
| 180 |
revert NotInitializing(); |
||
| 181 |
} |
||
| 182 |
} |
||
| 183 | |||
| 184 |
/** |
||
| 185 |
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. |
||
| 186 |
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized |
||
| 187 |
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called |
||
| 188 |
* through proxies. |
||
| 189 |
* |
||
| 190 |
* Emits an {Initialized} event the first time it is successfully executed.
|
||
| 191 |
*/ |
||
| 192 |
function _disableInitializers() internal virtual {
|
||
| 193 |
// solhint-disable-next-line var-name-mixedcase |
||
| 194 |
InitializableStorage storage $ = _getInitializableStorage(); |
||
| 195 | |||
| 196 |
✓ 1
|
if ($._initializing) {
|
|
| 197 |
revert InvalidInitialization(); |
||
| 198 |
} |
||
| 199 |
✓ 1
|
if ($._initialized != type(uint64).max) {
|
|
| 200 |
✓ 1
|
$._initialized = type(uint64).max; |
|
| 201 |
✓ 1
|
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 |
✓ 1.4M
|
function _isInitializing() internal view returns (bool) {
|
|
| 216 |
✓ 1.4M
|
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 |
✓ 1.6M
|
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 |
✓ 1.6M
|
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
|
|
| 233 |
✓ 1.6M
|
bytes32 slot = _initializableStorageSlot(); |
|
| 234 |
assembly {
|
||
| 235 |
$.slot := slot |
||
| 236 |
} |
||
| 237 |
} |
||
| 238 |
} |
||
| 239 |
80.4%
lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
Lines covered: 45 / 56 (80.4%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 7 |
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||
| 8 |
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
|
||
| 9 |
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
|
||
| 10 |
import {Initializable} from "../../proxy/utils/Initializable.sol";
|
||
| 11 | |||
| 12 |
/** |
||
| 13 |
* @dev Implementation of the {IERC20} interface.
|
||
| 14 |
* |
||
| 15 |
* This implementation is agnostic to the way tokens are created. This means |
||
| 16 |
* that a supply mechanism has to be added in a derived contract using {_mint}.
|
||
| 17 |
* |
||
| 18 |
* TIP: For a detailed writeup see our guide |
||
| 19 |
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How |
||
| 20 |
* to implement supply mechanisms]. |
||
| 21 |
* |
||
| 22 |
* The default value of {decimals} is 18. To change this, you should override
|
||
| 23 |
* this function so it returns a different value. |
||
| 24 |
* |
||
| 25 |
* We have followed general OpenZeppelin Contracts guidelines: functions revert |
||
| 26 |
* instead returning `false` on failure. This behavior is nonetheless |
||
| 27 |
* conventional and does not conflict with the expectations of ERC-20 |
||
| 28 |
* applications. |
||
| 29 |
*/ |
||
| 30 |
abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
|
||
| 31 |
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20 |
||
| 32 |
struct ERC20Storage {
|
||
| 33 |
mapping(address account => uint256) _balances; |
||
| 34 | |||
| 35 |
mapping(address account => mapping(address spender => uint256)) _allowances; |
||
| 36 | |||
| 37 |
uint256 _totalSupply; |
||
| 38 | |||
| 39 |
string _name; |
||
| 40 |
string _symbol; |
||
| 41 |
} |
||
| 42 | |||
| 43 |
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
|
||
| 44 |
bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00; |
||
| 45 | |||
| 46 |
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
|
||
| 47 |
assembly {
|
||
| 48 |
$.slot := ERC20StorageLocation |
||
| 49 |
} |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Sets the values for {name} and {symbol}.
|
||
| 54 |
* |
||
| 55 |
* Both values are immutable: they can only be set once during construction. |
||
| 56 |
*/ |
||
| 57 |
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
|
||
| 58 |
✓ 228.0K
|
__ERC20_init_unchained(name_, symbol_); |
|
| 59 |
} |
||
| 60 | |||
| 61 |
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
|
||
| 62 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 63 |
✓ 228.0K
|
$._name = name_; |
|
| 64 |
✓ 228.0K
|
$._symbol = symbol_; |
|
| 65 |
} |
||
| 66 | |||
| 67 |
/** |
||
| 68 |
* @dev Returns the name of the token. |
||
| 69 |
*/ |
||
| 70 |
function name() public view virtual returns (string memory) {
|
||
| 71 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 72 |
return $._name; |
||
| 73 |
} |
||
| 74 | |||
| 75 |
/** |
||
| 76 |
* @dev Returns the symbol of the token, usually a shorter version of the |
||
| 77 |
* name. |
||
| 78 |
*/ |
||
| 79 |
function symbol() public view virtual returns (string memory) {
|
||
| 80 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 81 |
return $._symbol; |
||
| 82 |
} |
||
| 83 | |||
| 84 |
/** |
||
| 85 |
* @dev Returns the number of decimals used to get its user representation. |
||
| 86 |
* For example, if `decimals` equals `2`, a balance of `505` tokens should |
||
| 87 |
* be displayed to a user as `5.05` (`505 / 10 ** 2`). |
||
| 88 |
* |
||
| 89 |
* Tokens usually opt for a value of 18, imitating the relationship between |
||
| 90 |
* Ether and Wei. This is the default value returned by this function, unless |
||
| 91 |
* it's overridden. |
||
| 92 |
* |
||
| 93 |
* NOTE: This information is only used for _display_ purposes: it in |
||
| 94 |
* no way affects any of the arithmetic of the contract, including |
||
| 95 |
* {IERC20-balanceOf} and {IERC20-transfer}.
|
||
| 96 |
*/ |
||
| 97 |
function decimals() public view virtual returns (uint8) {
|
||
| 98 |
return 18; |
||
| 99 |
} |
||
| 100 | |||
| 101 |
/// @inheritdoc IERC20 |
||
| 102 |
function totalSupply() public view virtual returns (uint256) {
|
||
| 103 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 104 |
✓ 3.8M
|
return $._totalSupply; |
|
| 105 |
} |
||
| 106 | |||
| 107 |
/// @inheritdoc IERC20 |
||
| 108 |
✓ 20.5M
|
function balanceOf(address account) public view virtual returns (uint256) {
|
|
| 109 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 110 |
✓ 20.5M
|
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 |
✓ 146.0K
|
function transfer(address to, uint256 value) public virtual returns (bool) {
|
|
| 122 |
address owner = _msgSender(); |
||
| 123 |
✓ 146.0K
|
_transfer(owner, to, value); |
|
| 124 |
return true; |
||
| 125 |
} |
||
| 126 | |||
| 127 |
/// @inheritdoc IERC20 |
||
| 128 |
✓ 442.2K
|
function allowance(address owner, address spender) public view virtual returns (uint256) {
|
|
| 129 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 130 |
✓ 442.2K
|
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 |
✓ 148.6K
|
function approve(address spender, uint256 value) public virtual returns (bool) {
|
|
| 144 |
address owner = _msgSender(); |
||
| 145 |
✓ 148.6K
|
_approve(owner, spender, value); |
|
| 146 |
✓ 150.9K
|
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 |
✓ 442.2K
|
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
|
|
| 166 |
address spender = _msgSender(); |
||
| 167 |
✓ 442.2K
|
_spendAllowance(from, spender, value); |
|
| 168 |
✓ 270.4K
|
_transfer(from, to, value); |
|
| 169 |
✓ 270.0K
|
return true; |
|
| 170 |
} |
||
| 171 | |||
| 172 |
/** |
||
| 173 |
* @dev Moves a `value` amount of tokens from `from` to `to`. |
||
| 174 |
* |
||
| 175 |
* This internal function is equivalent to {transfer}, and can be used to
|
||
| 176 |
* e.g. implement automatic token fees, slashing mechanisms, etc. |
||
| 177 |
* |
||
| 178 |
* Emits a {Transfer} event.
|
||
| 179 |
* |
||
| 180 |
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||
| 181 |
*/ |
||
| 182 |
function _transfer(address from, address to, uint256 value) internal {
|
||
| 183 |
✓ 416.3K
|
if (from == address(0)) {
|
|
| 184 |
revert ERC20InvalidSender(address(0)); |
||
| 185 |
} |
||
| 186 |
✓ 416.3K
|
if (to == address(0)) {
|
|
| 187 |
revert ERC20InvalidReceiver(address(0)); |
||
| 188 |
} |
||
| 189 |
✓ 416.3K
|
_update(from, to, value); |
|
| 190 |
} |
||
| 191 | |||
| 192 |
/** |
||
| 193 |
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` |
||
| 194 |
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding |
||
| 195 |
* this function. |
||
| 196 |
* |
||
| 197 |
* Emits a {Transfer} event.
|
||
| 198 |
*/ |
||
| 199 |
function _update(address from, address to, uint256 value) internal virtual {
|
||
| 200 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 201 |
✓ 892.4K
|
if (from == address(0)) {
|
|
| 202 |
// Overflow check required: The rest of the code assumes that totalSupply never overflows |
||
| 203 |
✓ 353.4K
|
$._totalSupply += value; |
|
| 204 |
} else {
|
||
| 205 |
✓ 538.9K
|
uint256 fromBalance = $._balances[from]; |
|
| 206 |
✓ 538.9K
|
if (fromBalance < value) {
|
|
| 207 |
✓ 137.1K
|
revert ERC20InsufficientBalance(from, fromBalance, value); |
|
| 208 |
} |
||
| 209 |
unchecked {
|
||
| 210 |
// Overflow not possible: value <= fromBalance <= totalSupply. |
||
| 211 |
✓ 401.9K
|
$._balances[from] = fromBalance - value; |
|
| 212 |
} |
||
| 213 |
} |
||
| 214 | |||
| 215 |
✓ 755.3K
|
if (to == address(0)) {
|
|
| 216 |
unchecked {
|
||
| 217 |
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. |
||
| 218 |
✓ 124.4K
|
$._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 |
✓ 630.9K
|
$._balances[to] += value; |
|
| 224 |
} |
||
| 225 |
} |
||
| 226 | |||
| 227 |
✓ 755.3K
|
emit Transfer(from, to, value); |
|
| 228 |
} |
||
| 229 | |||
| 230 |
/** |
||
| 231 |
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). |
||
| 232 |
* Relies on the `_update` mechanism |
||
| 233 |
* |
||
| 234 |
* Emits a {Transfer} event with `from` set to the zero address.
|
||
| 235 |
* |
||
| 236 |
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||
| 237 |
*/ |
||
| 238 |
function _mint(address account, uint256 value) internal {
|
||
| 239 |
✓ 353.4K
|
if (account == address(0)) {
|
|
| 240 |
revert ERC20InvalidReceiver(address(0)); |
||
| 241 |
} |
||
| 242 |
✓ 353.4K
|
_update(address(0), account, value); |
|
| 243 |
} |
||
| 244 | |||
| 245 |
/** |
||
| 246 |
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. |
||
| 247 |
* Relies on the `_update` mechanism. |
||
| 248 |
* |
||
| 249 |
* Emits a {Transfer} event with `to` set to the zero address.
|
||
| 250 |
* |
||
| 251 |
* NOTE: This function is not virtual, {_update} should be overridden instead
|
||
| 252 |
*/ |
||
| 253 |
function _burn(address account, uint256 value) internal {
|
||
| 254 |
✓ 124.4K
|
if (account == address(0)) {
|
|
| 255 |
revert ERC20InvalidSender(address(0)); |
||
| 256 |
} |
||
| 257 |
✓ 124.4K
|
_update(account, address(0), value); |
|
| 258 |
} |
||
| 259 | |||
| 260 |
/** |
||
| 261 |
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens. |
||
| 262 |
* |
||
| 263 |
* This internal function is equivalent to `approve`, and can be used to |
||
| 264 |
* e.g. set automatic allowances for certain subsystems, etc. |
||
| 265 |
* |
||
| 266 |
* Emits an {Approval} event.
|
||
| 267 |
* |
||
| 268 |
* Requirements: |
||
| 269 |
* |
||
| 270 |
* - `owner` cannot be the zero address. |
||
| 271 |
* - `spender` cannot be the zero address. |
||
| 272 |
* |
||
| 273 |
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. |
||
| 274 |
*/ |
||
| 275 |
function _approve(address owner, address spender, uint256 value) internal {
|
||
| 276 |
✓ 417.0K
|
_approve(owner, spender, value, true); |
|
| 277 |
} |
||
| 278 | |||
| 279 |
/** |
||
| 280 |
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
|
||
| 281 |
* |
||
| 282 |
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
|
||
| 283 |
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any |
||
| 284 |
* `Approval` event during `transferFrom` operations. |
||
| 285 |
* |
||
| 286 |
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to |
||
| 287 |
* true using the following override: |
||
| 288 |
* |
||
| 289 |
* ```solidity |
||
| 290 |
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
|
||
| 291 |
* super._approve(owner, spender, value, true); |
||
| 292 |
* } |
||
| 293 |
* ``` |
||
| 294 |
* |
||
| 295 |
* Requirements are the same as {_approve}.
|
||
| 296 |
*/ |
||
| 297 |
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
|
||
| 298 |
ERC20Storage storage $ = _getERC20Storage(); |
||
| 299 |
✓ 687.3K
|
if (owner == address(0)) {
|
|
| 300 |
revert ERC20InvalidApprover(address(0)); |
||
| 301 |
} |
||
| 302 |
✓ 687.3K
|
if (spender == address(0)) {
|
|
| 303 |
✓ 5.2K
|
revert ERC20InvalidSpender(address(0)); |
|
| 304 |
} |
||
| 305 |
✓ 682.1K
|
$._allowances[owner][spender] = value; |
|
| 306 |
if (emitEvent) {
|
||
| 307 |
✓ 411.8K
|
emit Approval(owner, spender, value); |
|
| 308 |
} |
||
| 309 |
} |
||
| 310 | |||
| 311 |
/** |
||
| 312 |
* @dev Updates `owner`'s allowance for `spender` based on spent `value`. |
||
| 313 |
* |
||
| 314 |
* Does not update the allowance value in case of infinite allowance. |
||
| 315 |
* Revert if not enough allowance is available. |
||
| 316 |
* |
||
| 317 |
* Does not emit an {Approval} event.
|
||
| 318 |
*/ |
||
| 319 |
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
|
||
| 320 |
✓ 442.2K
|
uint256 currentAllowance = allowance(owner, spender); |
|
| 321 |
✓ 442.2K
|
if (currentAllowance < type(uint256).max) {
|
|
| 322 |
✓ 442.2K
|
if (currentAllowance < value) {
|
|
| 323 |
✓ 171.8K
|
revert ERC20InsufficientAllowance(spender, currentAllowance, value); |
|
| 324 |
} |
||
| 325 |
unchecked {
|
||
| 326 |
✓ 270.4K
|
_approve(owner, spender, currentAllowance - value, false); |
|
| 327 |
} |
||
| 328 |
} |
||
| 329 |
} |
||
| 330 |
} |
||
| 331 |
100.0%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 |
import {Initializable} from "../proxy/utils/Initializable.sol";
|
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @dev Provides information about the current execution context, including the |
||
| 9 |
* sender of the transaction and its data. While these are generally available |
||
| 10 |
* via msg.sender and msg.data, they should not be accessed in such a direct |
||
| 11 |
* manner, since when dealing with meta-transactions the account sending and |
||
| 12 |
* paying for execution may not be the actual sender (as far as an application |
||
| 13 |
* is concerned). |
||
| 14 |
* |
||
| 15 |
* This contract is only required for intermediate, library-like contracts. |
||
| 16 |
*/ |
||
| 17 |
abstract contract ContextUpgradeable is Initializable {
|
||
| 18 |
function __Context_init() internal onlyInitializing {
|
||
| 19 |
} |
||
| 20 | |||
| 21 |
function __Context_init_unchained() internal onlyInitializing {
|
||
| 22 |
} |
||
| 23 |
function _msgSender() internal view virtual returns (address) {
|
||
| 24 |
✓ 442.2K
|
return msg.sender; |
|
| 25 |
} |
||
| 26 | |||
| 27 |
function _msgData() internal view virtual returns (bytes calldata) {
|
||
| 28 |
return msg.data; |
||
| 29 |
} |
||
| 30 | |||
| 31 |
function _contextSuffixLength() internal view virtual returns (uint256) {
|
||
| 32 |
return 0; |
||
| 33 |
} |
||
| 34 |
} |
||
| 35 |
90.0%
lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
Lines covered: 9 / 10 (90.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 |
import {Initializable} from "../proxy/utils/Initializable.sol";
|
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @dev Contract module that helps prevent reentrant calls to a function. |
||
| 9 |
* |
||
| 10 |
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
|
||
| 11 |
* available, which can be applied to functions to make sure there are no nested |
||
| 12 |
* (reentrant) calls to them. |
||
| 13 |
* |
||
| 14 |
* Note that because there is a single `nonReentrant` guard, functions marked as |
||
| 15 |
* `nonReentrant` may not call one another. This can be worked around by making |
||
| 16 |
* those functions `private`, and then adding `external` `nonReentrant` entry |
||
| 17 |
* points to them. |
||
| 18 |
* |
||
| 19 |
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at, |
||
| 20 |
* consider using {ReentrancyGuardTransient} instead.
|
||
| 21 |
* |
||
| 22 |
* TIP: If you would like to learn more about reentrancy and alternative ways |
||
| 23 |
* to protect against it, check out our blog post |
||
| 24 |
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. |
||
| 25 |
*/ |
||
| 26 |
abstract contract ReentrancyGuardUpgradeable is Initializable {
|
||
| 27 |
// Booleans are more expensive than uint256 or any type that takes up a full |
||
| 28 |
// word because each write operation emits an extra SLOAD to first read the |
||
| 29 |
// slot's contents, replace the bits taken up by the boolean, and then write |
||
| 30 |
// back. This is the compiler's defense against contract upgrades and |
||
| 31 |
// pointer aliasing, and it cannot be disabled. |
||
| 32 | |||
| 33 |
// The values being non-zero value makes deployment a bit more expensive, |
||
| 34 |
// but in exchange the refund on every call to nonReentrant will be lower in |
||
| 35 |
// amount. Since refunds are capped to a percentage of the total |
||
| 36 |
// transaction's gas, it is best to keep them low in cases like this one, to |
||
| 37 |
// increase the likelihood of the full refund coming into effect. |
||
| 38 |
✓ 270.7K
|
uint256 private constant NOT_ENTERED = 1; |
|
| 39 |
✓ 2.4M
|
uint256 private constant ENTERED = 2; |
|
| 40 | |||
| 41 |
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard |
||
| 42 |
struct ReentrancyGuardStorage {
|
||
| 43 |
uint256 _status; |
||
| 44 |
} |
||
| 45 | |||
| 46 |
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
|
||
| 47 |
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00; |
||
| 48 | |||
| 49 |
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
|
||
| 50 |
assembly {
|
||
| 51 |
$.slot := ReentrancyGuardStorageLocation |
||
| 52 |
} |
||
| 53 |
} |
||
| 54 | |||
| 55 |
/** |
||
| 56 |
* @dev Unauthorized reentrant call. |
||
| 57 |
*/ |
||
| 58 |
error ReentrancyGuardReentrantCall(); |
||
| 59 | |||
| 60 |
function __ReentrancyGuard_init() internal onlyInitializing {
|
||
| 61 |
✓ 228.0K
|
__ReentrancyGuard_init_unchained(); |
|
| 62 |
} |
||
| 63 | |||
| 64 |
✓ 228.0K
|
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 |
✓ 776.8K
|
_nonReentrantBefore(); |
|
| 78 |
_; |
||
| 79 |
✓ 73.0M
|
_nonReentrantAfter(); |
|
| 80 |
} |
||
| 81 | |||
| 82 |
function _nonReentrantBefore() private {
|
||
| 83 |
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
||
| 84 |
// On the first call to nonReentrant, _status will be NOT_ENTERED |
||
| 85 |
✓ 2.4M
|
if ($._status == ENTERED) {
|
|
| 86 |
revert ReentrancyGuardReentrantCall(); |
||
| 87 |
} |
||
| 88 | |||
| 89 |
// Any calls to nonReentrant after this point will fail |
||
| 90 |
✓ 2.4M
|
$._status = ENTERED; |
|
| 91 |
} |
||
| 92 | |||
| 93 |
function _nonReentrantAfter() private {
|
||
| 94 |
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
||
| 95 |
// By storing the original value once again, a refund is triggered (see |
||
| 96 |
// https://eips.ethereum.org/EIPS/eip-2200) |
||
| 97 |
✓ 270.7K
|
$._status = NOT_ENTERED; |
|
| 98 |
} |
||
| 99 | |||
| 100 |
/** |
||
| 101 |
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a |
||
| 102 |
* `nonReentrant` function in the call stack. |
||
| 103 |
*/ |
||
| 104 |
function _reentrancyGuardEntered() internal view returns (bool) {
|
||
| 105 |
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); |
||
| 106 |
return $._status == ENTERED; |
||
| 107 |
} |
||
| 108 |
} |
||
| 109 |
11.9%
lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol
Lines covered: 5 / 42 (11.9%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||
| 7 |
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
|
||
| 8 |
import {Initializable} from "../../proxy/utils/Initializable.sol";
|
||
| 9 | |||
| 10 |
/** |
||
| 11 |
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data. |
||
| 12 |
* |
||
| 13 |
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose |
||
| 14 |
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract |
||
| 15 |
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to |
||
| 16 |
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. |
||
| 17 |
* |
||
| 18 |
* This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
|
||
| 19 |
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA |
||
| 20 |
* ({_hashTypedDataV4}).
|
||
| 21 |
* |
||
| 22 |
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating |
||
| 23 |
* the chain id to protect against replay attacks on an eventual fork of the chain. |
||
| 24 |
* |
||
| 25 |
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method |
||
| 26 |
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. |
||
| 27 |
* |
||
| 28 |
* NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator |
||
| 29 |
* each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage.
|
||
| 30 |
*/ |
||
| 31 |
abstract contract EIP712Upgradeable is Initializable, IERC5267 {
|
||
| 32 |
bytes32 private constant TYPE_HASH = |
||
| 33 |
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
||
| 34 | |||
| 35 |
/// @custom:storage-location erc7201:openzeppelin.storage.EIP712 |
||
| 36 |
struct EIP712Storage {
|
||
| 37 |
/// @custom:oz-renamed-from _HASHED_NAME |
||
| 38 |
bytes32 _hashedName; |
||
| 39 |
/// @custom:oz-renamed-from _HASHED_VERSION |
||
| 40 |
bytes32 _hashedVersion; |
||
| 41 | |||
| 42 |
string _name; |
||
| 43 |
string _version; |
||
| 44 |
} |
||
| 45 | |||
| 46 |
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
|
||
| 47 |
bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100; |
||
| 48 | |||
| 49 |
function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
|
||
| 50 |
assembly {
|
||
| 51 |
$.slot := EIP712StorageLocation |
||
| 52 |
} |
||
| 53 |
} |
||
| 54 | |||
| 55 |
/** |
||
| 56 |
* @dev Initializes the domain separator and parameter caches. |
||
| 57 |
* |
||
| 58 |
* The meaning of `name` and `version` is specified in |
||
| 59 |
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]: |
||
| 60 |
* |
||
| 61 |
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. |
||
| 62 |
* - `version`: the current major version of the signing domain. |
||
| 63 |
* |
||
| 64 |
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart |
||
| 65 |
* contract upgrade]. |
||
| 66 |
*/ |
||
| 67 |
function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
|
||
| 68 |
✓ 228.0K
|
__EIP712_init_unchained(name, version); |
|
| 69 |
} |
||
| 70 | |||
| 71 |
function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
|
||
| 72 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 73 |
✓ 228.0K
|
$._name = name; |
|
| 74 |
✓ 228.0K
|
$._version = version; |
|
| 75 | |||
| 76 |
// Reset prior values in storage if upgrading |
||
| 77 |
✓ 228.0K
|
$._hashedName = 0; |
|
| 78 |
✓ 228.0K
|
$._hashedVersion = 0; |
|
| 79 |
} |
||
| 80 | |||
| 81 |
/** |
||
| 82 |
* @dev Returns the domain separator for the current chain. |
||
| 83 |
*/ |
||
| 84 |
function _domainSeparatorV4() internal view returns (bytes32) {
|
||
| 85 |
return _buildDomainSeparator(); |
||
| 86 |
} |
||
| 87 | |||
| 88 |
function _buildDomainSeparator() private view returns (bytes32) {
|
||
| 89 |
return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this))); |
||
| 90 |
} |
||
| 91 | |||
| 92 |
/** |
||
| 93 |
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this |
||
| 94 |
* function returns the hash of the fully encoded EIP712 message for this domain. |
||
| 95 |
* |
||
| 96 |
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
|
||
| 97 |
* |
||
| 98 |
* ```solidity |
||
| 99 |
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( |
||
| 100 |
* keccak256("Mail(address to,string contents)"),
|
||
| 101 |
* mailTo, |
||
| 102 |
* keccak256(bytes(mailContents)) |
||
| 103 |
* ))); |
||
| 104 |
* address signer = ECDSA.recover(digest, signature); |
||
| 105 |
* ``` |
||
| 106 |
*/ |
||
| 107 |
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
|
||
| 108 |
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); |
||
| 109 |
} |
||
| 110 | |||
| 111 |
/// @inheritdoc IERC5267 |
||
| 112 |
function eip712Domain() |
||
| 113 |
public |
||
| 114 |
view |
||
| 115 |
virtual |
||
| 116 |
returns ( |
||
| 117 |
bytes1 fields, |
||
| 118 |
string memory name, |
||
| 119 |
string memory version, |
||
| 120 |
uint256 chainId, |
||
| 121 |
address verifyingContract, |
||
| 122 |
bytes32 salt, |
||
| 123 |
uint256[] memory extensions |
||
| 124 |
) |
||
| 125 |
{
|
||
| 126 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 127 |
// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized |
||
| 128 |
// and the EIP712 domain is not reliable, as it will be missing name and version. |
||
| 129 |
require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized"); |
||
| 130 | |||
| 131 |
return ( |
||
| 132 |
hex"0f", // 01111 |
||
| 133 |
_EIP712Name(), |
||
| 134 |
_EIP712Version(), |
||
| 135 |
block.chainid, |
||
| 136 |
address(this), |
||
| 137 |
bytes32(0), |
||
| 138 |
new uint256[](0) |
||
| 139 |
); |
||
| 140 |
} |
||
| 141 | |||
| 142 |
/** |
||
| 143 |
* @dev The name parameter for the EIP712 domain. |
||
| 144 |
* |
||
| 145 |
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs |
||
| 146 |
* are a concern. |
||
| 147 |
*/ |
||
| 148 |
function _EIP712Name() internal view virtual returns (string memory) {
|
||
| 149 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 150 |
return $._name; |
||
| 151 |
} |
||
| 152 | |||
| 153 |
/** |
||
| 154 |
* @dev The version parameter for the EIP712 domain. |
||
| 155 |
* |
||
| 156 |
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs |
||
| 157 |
* are a concern. |
||
| 158 |
*/ |
||
| 159 |
function _EIP712Version() internal view virtual returns (string memory) {
|
||
| 160 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 161 |
return $._version; |
||
| 162 |
} |
||
| 163 | |||
| 164 |
/** |
||
| 165 |
* @dev The hash of the name parameter for the EIP712 domain. |
||
| 166 |
* |
||
| 167 |
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead. |
||
| 168 |
*/ |
||
| 169 |
function _EIP712NameHash() internal view returns (bytes32) {
|
||
| 170 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 171 |
string memory name = _EIP712Name(); |
||
| 172 |
if (bytes(name).length > 0) {
|
||
| 173 |
return keccak256(bytes(name)); |
||
| 174 |
} else {
|
||
| 175 |
// If the name is empty, the contract may have been upgraded without initializing the new storage. |
||
| 176 |
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design. |
||
| 177 |
bytes32 hashedName = $._hashedName; |
||
| 178 |
if (hashedName != 0) {
|
||
| 179 |
return hashedName; |
||
| 180 |
} else {
|
||
| 181 |
return keccak256("");
|
||
| 182 |
} |
||
| 183 |
} |
||
| 184 |
} |
||
| 185 | |||
| 186 |
/** |
||
| 187 |
* @dev The hash of the version parameter for the EIP712 domain. |
||
| 188 |
* |
||
| 189 |
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. |
||
| 190 |
*/ |
||
| 191 |
function _EIP712VersionHash() internal view returns (bytes32) {
|
||
| 192 |
EIP712Storage storage $ = _getEIP712Storage(); |
||
| 193 |
string memory version = _EIP712Version(); |
||
| 194 |
if (bytes(version).length > 0) {
|
||
| 195 |
return keccak256(bytes(version)); |
||
| 196 |
} else {
|
||
| 197 |
// If the version is empty, the contract may have been upgraded without initializing the new storage. |
||
| 198 |
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design. |
||
| 199 |
bytes32 hashedVersion = $._hashedVersion; |
||
| 200 |
if (hashedVersion != 0) {
|
||
| 201 |
return hashedVersion; |
||
| 202 |
} else {
|
||
| 203 |
return keccak256("");
|
||
| 204 |
} |
||
| 205 |
} |
||
| 206 |
} |
||
| 207 |
} |
||
| 208 |
0.0%
lib/setup-helpers/lib/chimera/src/Asserts.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
abstract contract Asserts {
|
||
| 5 |
function gt(uint256 a, uint256 b, string memory reason) internal virtual; |
||
| 6 | |||
| 7 |
function gte(uint256 a, uint256 b, string memory reason) internal virtual; |
||
| 8 | |||
| 9 |
function lt(uint256 a, uint256 b, string memory reason) internal virtual; |
||
| 10 | |||
| 11 |
function lte(uint256 a, uint256 b, string memory reason) internal virtual; |
||
| 12 | |||
| 13 |
function eq(uint256 a, uint256 b, string memory reason) internal virtual; |
||
| 14 | |||
| 15 |
function t(bool b, string memory reason) internal virtual; |
||
| 16 | |||
| 17 |
function between(uint256 value, uint256 low, uint256 high) internal virtual returns (uint256); |
||
| 18 | |||
| 19 |
function between(int256 value, int256 low, int256 high) internal virtual returns (int256); |
||
| 20 | |||
| 21 |
function precondition(bool p) internal virtual; |
||
| 22 |
} |
||
| 23 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseProperties.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseSetup} from "./BaseSetup.sol";
|
||
| 5 | |||
| 6 |
abstract contract BaseProperties is BaseSetup {}
|
||
| 7 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseSetup.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
abstract contract BaseSetup {
|
||
| 5 |
function setup() internal virtual; |
||
| 6 |
} |
||
| 7 |
0.0%
lib/setup-helpers/lib/chimera/src/BaseTargetFunctions.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseProperties} from "./BaseProperties.sol";
|
||
| 5 |
import {Asserts} from "./Asserts.sol";
|
||
| 6 | |||
| 7 |
abstract contract BaseTargetFunctions is BaseProperties, Asserts {}
|
||
| 8 |
83.3%
lib/setup-helpers/lib/chimera/src/CryticAsserts.sol
Lines covered: 15 / 18 (83.3%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {Asserts} from "./Asserts.sol";
|
||
| 5 | |||
| 6 |
contract CryticAsserts is Asserts {
|
||
| 7 |
event Log(string); |
||
| 8 | |||
| 9 |
function gt(uint256 a, uint256 b, string memory reason) internal virtual override {
|
||
| 10 |
if (!(a > b)) {
|
||
| 11 |
emit Log(reason); |
||
| 12 |
assert(false); |
||
| 13 |
} |
||
| 14 |
} |
||
| 15 | |||
| 16 |
function gte(uint256 a, uint256 b, string memory reason) internal virtual override {
|
||
| 17 |
✓ 562.5K
|
if (!(a >= b)) {
|
|
| 18 |
✓ 16.0K
|
emit Log(reason); |
|
| 19 |
assert(false); |
||
| 20 |
} |
||
| 21 |
} |
||
| 22 | |||
| 23 |
function lt(uint256 a, uint256 b, string memory reason) internal virtual override {
|
||
| 24 |
if (!(a < b)) {
|
||
| 25 |
emit Log(reason); |
||
| 26 |
assert(false); |
||
| 27 |
} |
||
| 28 |
} |
||
| 29 | |||
| 30 |
function lte(uint256 a, uint256 b, string memory reason) internal virtual override {
|
||
| 31 |
✓ 198.7K
|
if (!(a <= b)) {
|
|
| 32 |
✓ 32.1K
|
emit Log(reason); |
|
| 33 |
assert(false); |
||
| 34 |
} |
||
| 35 |
} |
||
| 36 | |||
| 37 |
function eq(uint256 a, uint256 b, string memory reason) internal virtual override {
|
||
| 38 |
✓ 346.0K
|
if (!(a == b)) {
|
|
| 39 |
✓ 11.5K
|
emit Log(reason); |
|
| 40 |
✓ 59.6K
|
assert(false); |
|
| 41 |
} |
||
| 42 |
} |
||
| 43 | |||
| 44 |
function t(bool b, string memory reason) internal virtual override {
|
||
| 45 |
✓ 1.1M
|
if (!b) {
|
|
| 46 |
✓ 10.4K
|
emit Log(reason); |
|
| 47 |
✓ 10.4K
|
assert(false); |
|
| 48 |
} |
||
| 49 |
} |
||
| 50 | |||
| 51 |
✓ 307.7K
|
function between(uint256 value, uint256 low, uint256 high) internal virtual override returns (uint256) {
|
|
| 52 |
✓ 307.7K
|
if (value < low || value > high) {
|
|
| 53 |
✓ 293.0K
|
uint256 ans = low + (value % (high - low + 1)); |
|
| 54 |
✓ 293.0K
|
return ans; |
|
| 55 |
} |
||
| 56 |
✓ 14.7K
|
return value; |
|
| 57 |
} |
||
| 58 | |||
| 59 |
function between(int256 value, int256 low, int256 high) internal virtual override returns (int256) {
|
||
| 60 |
if (value < low || value > high) {
|
||
| 61 |
int256 range = high - low + 1; |
||
| 62 |
int256 clamped = (value - low) % (range); |
||
| 63 |
if (clamped < 0) clamped += range; |
||
| 64 |
int256 ans = low + clamped; |
||
| 65 |
return ans; |
||
| 66 |
} |
||
| 67 |
return value; |
||
| 68 |
} |
||
| 69 | |||
| 70 |
function precondition(bool p) internal virtual override {
|
||
| 71 |
require(p); |
||
| 72 |
} |
||
| 73 |
} |
||
| 74 |
100.0%
lib/setup-helpers/lib/chimera/src/Hevm.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// slither-disable-start shadowing-local |
||
| 5 | |||
| 6 |
interface IHevm {
|
||
| 7 |
// Set block.timestamp to newTimestamp |
||
| 8 |
function warp(uint256 newTimestamp) external; |
||
| 9 | |||
| 10 |
// Set block.number to newNumber |
||
| 11 |
function roll(uint256 newNumber) external; |
||
| 12 | |||
| 13 |
// Add the condition b to the assumption base for the current branch |
||
| 14 |
// This function is almost identical to require |
||
| 15 |
function assume(bool b) external; |
||
| 16 | |||
| 17 |
// Sets the eth balance of usr to amt |
||
| 18 |
function deal(address usr, uint256 amt) external; |
||
| 19 | |||
| 20 |
// Loads a storage slot from an address |
||
| 21 |
function load(address where, bytes32 slot) external returns (bytes32); |
||
| 22 | |||
| 23 |
// Stores a value to an address' storage slot |
||
| 24 |
function store(address where, bytes32 slot, bytes32 value) external; |
||
| 25 | |||
| 26 |
// Signs data (privateKey, digest) => (v, r, s) |
||
| 27 |
function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); |
||
| 28 | |||
| 29 |
// Gets address for a given private key |
||
| 30 |
function addr(uint256 privateKey) external returns (address addr); |
||
| 31 | |||
| 32 |
// Performs a foreign function call via terminal |
||
| 33 |
function ffi(string[] calldata inputs) external returns (bytes memory result); |
||
| 34 | |||
| 35 |
// Performs the next smart contract call with specified `msg.sender` |
||
| 36 |
function prank(address newSender) external; |
||
| 37 | |||
| 38 |
// Creates a new fork with the given endpoint and the latest block and returns the identifier of the fork |
||
| 39 |
function createFork(string calldata urlOrAlias) external returns (uint256); |
||
| 40 | |||
| 41 |
// Takes a fork identifier created by createFork and sets the corresponding forked state as active |
||
| 42 |
function selectFork(uint256 forkId) external; |
||
| 43 | |||
| 44 |
// Returns the identifier of the current fork |
||
| 45 |
function activeFork() external returns (uint256); |
||
| 46 | |||
| 47 |
// Labels the address in traces |
||
| 48 |
function label(address addr, string calldata label) external; |
||
| 49 | |||
| 50 |
/// Sets an address' code. |
||
| 51 |
function etch(address target, bytes calldata newRuntimeBytecode) external; |
||
| 52 |
} |
||
| 53 | |||
| 54 |
✓ 274.4K
|
IHevm constant vm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); |
|
| 55 | |||
| 56 |
// slither-disable-end shadowing-local |
||
| 57 |
85.7%
lib/setup-helpers/src/ActorManager.sol
Lines covered: 12 / 14 (85.7%)
| 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 |
✓ 1
|
_actors.add(address(this)); |
|
| 31 |
✓ 1
|
_actor = address(this); |
|
| 32 |
} |
||
| 33 | |||
| 34 |
/// @notice Returns the current active actor |
||
| 35 |
✓ 3.8M
|
function _getActor() internal view returns (address) {
|
|
| 36 |
✓ 3.8M
|
return _actor; |
|
| 37 |
} |
||
| 38 | |||
| 39 |
/// @notice Returns all actors being used |
||
| 40 |
✓ 12.2M
|
function _getActors() internal view returns (address[] memory) {
|
|
| 41 |
✓ 12.2M
|
return _actors.values(); |
|
| 42 |
} |
||
| 43 | |||
| 44 |
/// @notice Adds an actor to the list of actors |
||
| 45 |
function _addActor(address target) internal {
|
||
| 46 |
✓ 2
|
if (_actors.contains(target)) {
|
|
| 47 |
revert ActorExists(); |
||
| 48 |
} |
||
| 49 | |||
| 50 |
✓ 2
|
if (target == address(this)) {
|
|
| 51 |
revert DefaultActor(); |
||
| 52 |
} |
||
| 53 | |||
| 54 |
✓ 2
|
_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 |
✓ 138.2K
|
function _switchActor(uint256 entropy) internal returns (address target) {
|
|
| 77 |
✓ 138.2K
|
target = _actors.at(entropy); |
|
| 78 |
✓ 14.4K
|
_actor = target; |
|
| 79 |
} |
||
| 80 |
} |
||
| 81 |
92.9%
lib/setup-helpers/src/AssetManager.sol
Lines covered: 26 / 28 (92.9%)
| 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 |
✓ 112.0K
|
function _getAsset() internal view returns (address) {
|
|
| 30 |
✓ 112.0K
|
if (__asset == address(0)) {
|
|
| 31 |
revert NotSetup(); |
||
| 32 |
} |
||
| 33 | |||
| 34 |
✓ 112.0K
|
return __asset; |
|
| 35 |
} |
||
| 36 | |||
| 37 |
/// @notice Returns all assets being used |
||
| 38 |
✓ 8
|
function _getAssets() internal view returns (address[] memory) {
|
|
| 39 |
✓ 8
|
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 |
✓ 229.9K
|
function _newAsset(uint8 decimals) internal returns (address) {
|
|
| 46 |
✓ 229.9K
|
address asset_ = address(new MockERC20("Test Token", "TST", decimals)); // If names get confusing, concatenate the decimals to the name
|
|
| 47 |
✓ 229.9K
|
_addAsset(asset_); |
|
| 48 |
✓ 229.9K
|
__asset = asset_; // sets the asset as the current asset |
|
| 49 |
return asset_; |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/// @notice Adds an asset to the list of assets |
||
| 53 |
/// @param target The address of the asset to add |
||
| 54 |
function _addAsset(address target) internal {
|
||
| 55 |
✓ 229.9K
|
if (_assets.contains(target)) {
|
|
| 56 |
revert Exists(); |
||
| 57 |
} |
||
| 58 | |||
| 59 |
✓ 229.9K
|
_assets.add(target); |
|
| 60 |
} |
||
| 61 | |||
| 62 |
/// @notice Removes an asset from the list of assets |
||
| 63 |
/// @param target The address of the asset to remove |
||
| 64 |
function _removeAsset(address target) internal {
|
||
| 65 |
if (!_assets.contains(target)) {
|
||
| 66 |
revert NotAdded(); |
||
| 67 |
} |
||
| 68 | |||
| 69 |
_assets.remove(target); |
||
| 70 |
} |
||
| 71 | |||
| 72 |
/// @notice Switches the current asset based on the entropy |
||
| 73 |
/// NOTE: We revert if the entropy is greater than the number of actors, for Halmos compatibility |
||
| 74 |
/// @param entropy The entropy to choose a random asset in the array for switching |
||
| 75 |
function _switchAsset(uint256 entropy) internal {
|
||
| 76 |
✓ 123.0K
|
address target = _assets.at(entropy); |
|
| 77 |
✓ 2.8K
|
__asset = target; |
|
| 78 |
} |
||
| 79 | |||
| 80 |
/// === Approve & Mint Asset === /// |
||
| 81 | |||
| 82 |
/// @notice Mint initial balance and approve allowances for the active asset |
||
| 83 |
/// @param actorsArray The array of actors to mint the asset to |
||
| 84 |
/// @param approvalArray The array of addresses to approve the asset to |
||
| 85 |
/// @param amount The amount of the asset to mint |
||
| 86 |
function _finalizeAssetDeployment(address[] memory actorsArray, address[] memory approvalArray, uint256 amount) |
||
| 87 |
internal |
||
| 88 |
{
|
||
| 89 |
✓ 1
|
_mintAssetToAllActors(actorsArray, amount); |
|
| 90 |
✓ 7
|
for (uint256 i; i < approvalArray.length; i++) {
|
|
| 91 |
✓ 6
|
_approveAssetToAddressForAllActors(actorsArray, approvalArray[i]); |
|
| 92 |
} |
||
| 93 |
} |
||
| 94 | |||
| 95 |
/// @notice Mint the asset to all actors |
||
| 96 |
/// @param actorsArray The array of actors to mint the asset to |
||
| 97 |
/// @param amount The amount of the asset to mint |
||
| 98 |
function _mintAssetToAllActors(address[] memory actorsArray, uint256 amount) private {
|
||
| 99 |
// mint all actors |
||
| 100 |
✓ 1
|
address[] memory assets = _getAssets(); |
|
| 101 |
✓ 3
|
for (uint256 i; i < assets.length; i++) {
|
|
| 102 |
✓ 8
|
for (uint256 j; j < actorsArray.length; j++) {
|
|
| 103 |
✓ 6
|
vm.prank(actorsArray[j]); |
|
| 104 |
✓ 6
|
MockERC20(assets[i]).mint(actorsArray[j], amount); |
|
| 105 |
} |
||
| 106 |
} |
||
| 107 |
} |
||
| 108 | |||
| 109 |
/// @notice Approve the asset to all actors |
||
| 110 |
/// @param actorsArray The array of actors to approve the asset from |
||
| 111 |
/// @param addressToApprove The address to approve the asset to |
||
| 112 |
function _approveAssetToAddressForAllActors(address[] memory actorsArray, address addressToApprove) private {
|
||
| 113 |
// approve to all actors |
||
| 114 |
✓ 6
|
address[] memory assets = _getAssets(); |
|
| 115 |
✓ 18
|
for (uint256 i; i < assets.length; i++) {
|
|
| 116 |
✓ 48
|
for (uint256 j; j < actorsArray.length; j++) {
|
|
| 117 |
✓ 36
|
vm.prank(actorsArray[j]); |
|
| 118 |
✓ 36
|
MockERC20(assets[i]).approve(addressToApprove, type(uint256).max); |
|
| 119 |
} |
||
| 120 |
} |
||
| 121 |
} |
||
| 122 |
} |
||
| 123 |
95.5%
lib/setup-helpers/src/EnumerableSet.sol
Lines covered: 21 / 22 (95.5%)
| 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 |
✓ 23
|
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 |
✓ 376.6K
|
function _add(Set storage set, bytes32 value) private returns (bool) {
|
|
| 66 |
if (!_contains(set, value)) {
|
||
| 67 |
✓ 376.6K
|
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 |
✓ 376.6K
|
set._indexes[value] = set._values.length; |
|
| 71 |
✓ 376.6K
|
return true; |
|
| 72 |
} else {
|
||
| 73 |
return false; |
||
| 74 |
} |
||
| 75 |
} |
||
| 76 | |||
| 77 |
/** |
||
| 78 |
* @dev Removes a value from a set. O(1). |
||
| 79 |
* |
||
| 80 |
* Returns true if the value was removed from the set, that is if it was |
||
| 81 |
* present. |
||
| 82 |
*/ |
||
| 83 |
function _remove(Set storage set, bytes32 value) private returns (bool) {
|
||
| 84 |
// We read and store the value's index to prevent multiple reads from the same storage slot |
||
| 85 |
uint256 valueIndex = set._indexes[value]; |
||
| 86 | |||
| 87 |
if (valueIndex != 0) {
|
||
| 88 |
// Equivalent to contains(set, value) |
||
| 89 |
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
||
| 90 |
// the array, and then remove the last element (sometimes called as 'swap and pop'). |
||
| 91 |
// This modifies the order of the array, as noted in {at}.
|
||
| 92 | |||
| 93 |
uint256 toDeleteIndex = valueIndex - 1; |
||
| 94 |
uint256 lastIndex = set._values.length - 1; |
||
| 95 | |||
| 96 |
if (lastIndex != toDeleteIndex) {
|
||
| 97 |
bytes32 lastValue = set._values[lastIndex]; |
||
| 98 | |||
| 99 |
// Move the last value to the index where the value to delete is |
||
| 100 |
set._values[toDeleteIndex] = lastValue; |
||
| 101 |
// Update the index for the moved value |
||
| 102 |
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex |
||
| 103 |
} |
||
| 104 | |||
| 105 |
// Delete the slot where the moved value was stored |
||
| 106 |
set._values.pop(); |
||
| 107 | |||
| 108 |
// Delete the index for the deleted slot |
||
| 109 |
delete set._indexes[value]; |
||
| 110 | |||
| 111 |
return true; |
||
| 112 |
} else {
|
||
| 113 |
return false; |
||
| 114 |
} |
||
| 115 |
} |
||
| 116 | |||
| 117 |
/** |
||
| 118 |
* @dev Returns true if the value is in the set. O(1). |
||
| 119 |
*/ |
||
| 120 |
function _contains(Set storage set, bytes32 value) private view returns (bool) {
|
||
| 121 |
✓ 376.6K
|
return set._indexes[value] != 0; |
|
| 122 |
} |
||
| 123 | |||
| 124 |
/** |
||
| 125 |
* @dev Returns the number of values on the set. O(1). |
||
| 126 |
*/ |
||
| 127 |
function _length(Set storage set) private view returns (uint256) {
|
||
| 128 |
✓ 668.1K
|
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 |
✓ 929.2K
|
function _at(Set storage set, uint256 index) private view returns (bytes32) {
|
|
| 142 |
✓ 929.2K
|
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 |
✓ 17.7M
|
function _values(Set storage set) private view returns (bytes32[] memory) {
|
|
| 154 |
✓ 17.7M
|
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 |
✓ 376.6K
|
function add(AddressSet storage set, address value) internal returns (bool) {
|
|
| 244 |
✓ 376.6K
|
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 |
✓ 376.6K
|
function contains(AddressSet storage set, address value) internal view returns (bool) {
|
|
| 261 |
✓ 376.6K
|
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 |
✓ 668.1K
|
function length(AddressSet storage set) internal view returns (uint256) {
|
|
| 268 |
✓ 668.1K
|
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 |
✓ 929.2K
|
function at(AddressSet storage set, uint256 index) internal view returns (address) {
|
|
| 282 |
✓ 929.2K
|
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 |
✓ 17.7M
|
function values(AddressSet storage set) internal view returns (address[] memory) {
|
|
| 294 |
✓ 17.7M
|
bytes32[] memory store = _values(set._inner); |
|
| 295 |
address[] memory result; |
||
| 296 | |||
| 297 |
/// @solidity memory-safe-assembly |
||
| 298 |
assembly {
|
||
| 299 |
result := store |
||
| 300 |
} |
||
| 301 | |||
| 302 |
return result; |
||
| 303 |
} |
||
| 304 | |||
| 305 |
// UintSet |
||
| 306 | |||
| 307 |
struct UintSet {
|
||
| 308 |
Set _inner; |
||
| 309 |
} |
||
| 310 | |||
| 311 |
/** |
||
| 312 |
* @dev Add a value to a set. O(1). |
||
| 313 |
* |
||
| 314 |
* Returns true if the value was added to the set, that is if it was not |
||
| 315 |
* already present. |
||
| 316 |
*/ |
||
| 317 |
function add(UintSet storage set, uint256 value) internal returns (bool) {
|
||
| 318 |
return _add(set._inner, bytes32(value)); |
||
| 319 |
} |
||
| 320 | |||
| 321 |
/** |
||
| 322 |
* @dev Removes a value from a set. O(1). |
||
| 323 |
* |
||
| 324 |
* Returns true if the value was removed from the set, that is if it was |
||
| 325 |
* present. |
||
| 326 |
*/ |
||
| 327 |
function remove(UintSet storage set, uint256 value) internal returns (bool) {
|
||
| 328 |
return _remove(set._inner, bytes32(value)); |
||
| 329 |
} |
||
| 330 | |||
| 331 |
/** |
||
| 332 |
* @dev Returns true if the value is in the set. O(1). |
||
| 333 |
*/ |
||
| 334 |
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
|
||
| 335 |
return _contains(set._inner, bytes32(value)); |
||
| 336 |
} |
||
| 337 | |||
| 338 |
/** |
||
| 339 |
* @dev Returns the number of values in the set. O(1). |
||
| 340 |
*/ |
||
| 341 |
function length(UintSet storage set) internal view returns (uint256) {
|
||
| 342 |
return _length(set._inner); |
||
| 343 |
} |
||
| 344 | |||
| 345 |
/** |
||
| 346 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 347 |
* |
||
| 348 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 349 |
* array, and it may change when more values are added or removed. |
||
| 350 |
* |
||
| 351 |
* Requirements: |
||
| 352 |
* |
||
| 353 |
* - `index` must be strictly less than {length}.
|
||
| 354 |
*/ |
||
| 355 |
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
|
||
| 356 |
return uint256(_at(set._inner, index)); |
||
| 357 |
} |
||
| 358 | |||
| 359 |
/** |
||
| 360 |
* @dev Return the entire set in an array |
||
| 361 |
* |
||
| 362 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 363 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 364 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 365 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 366 |
*/ |
||
| 367 |
function values(UintSet storage set) internal view returns (uint256[] memory) {
|
||
| 368 |
bytes32[] memory store = _values(set._inner); |
||
| 369 |
uint256[] memory result; |
||
| 370 | |||
| 371 |
/// @solidity memory-safe-assembly |
||
| 372 |
assembly {
|
||
| 373 |
result := store |
||
| 374 |
} |
||
| 375 | |||
| 376 |
return result; |
||
| 377 |
} |
||
| 378 |
} |
||
| 379 |
77.8%
lib/setup-helpers/src/MockERC20.sol
Lines covered: 49 / 63 (77.8%)
| 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 |
✓ 27.4M
|
string public name; |
|
| 35 | |||
| 36 |
string public symbol; |
||
| 37 | |||
| 38 |
✓ 208.4K
|
uint8 public immutable decimals; |
|
| 39 | |||
| 40 |
/*////////////////////////////////////////////////////////////// |
||
| 41 |
ERC20 STORAGE |
||
| 42 |
//////////////////////////////////////////////////////////////*/ |
||
| 43 | |||
| 44 |
✓ 25.8M
|
uint256 public totalSupply; |
|
| 45 | |||
| 46 |
✓ 25.8M
|
mapping(address => uint256) public balanceOf; |
|
| 47 | |||
| 48 |
mapping(address => mapping(address => uint256)) public allowance; |
||
| 49 | |||
| 50 |
/*////////////////////////////////////////////////////////////// |
||
| 51 |
EIP-2612 STORAGE |
||
| 52 |
//////////////////////////////////////////////////////////////*/ |
||
| 53 | |||
| 54 |
uint256 internal immutable INITIAL_CHAIN_ID; |
||
| 55 | |||
| 56 |
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; |
||
| 57 | |||
| 58 |
mapping(address => uint256) public nonces; |
||
| 59 | |||
| 60 |
/*////////////////////////////////////////////////////////////// |
||
| 61 |
CONSTRUCTOR |
||
| 62 |
//////////////////////////////////////////////////////////////*/ |
||
| 63 | |||
| 64 |
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
|
||
| 65 |
✓ 229.9K
|
name = _name; |
|
| 66 |
✓ 229.9K
|
symbol = _symbol; |
|
| 67 |
✓ 229.9K
|
decimals = _decimals; |
|
| 68 | |||
| 69 |
✓ 229.9K
|
INITIAL_CHAIN_ID = block.chainid; |
|
| 70 |
✓ 229.9K
|
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); |
|
| 71 |
} |
||
| 72 | |||
| 73 |
/*////////////////////////////////////////////////////////////// |
||
| 74 |
ERC20 LOGIC |
||
| 75 |
//////////////////////////////////////////////////////////////*/ |
||
| 76 | |||
| 77 |
✓ 173.7K
|
function approve(address spender, uint256 amount) public virtual returns (bool) {
|
|
| 78 |
✓ 173.7K
|
allowance[msg.sender][spender] = amount; |
|
| 79 | |||
| 80 |
✓ 173.7K
|
emit Approval(msg.sender, spender, amount); |
|
| 81 | |||
| 82 |
✓ 173.7K
|
return true; |
|
| 83 |
} |
||
| 84 | |||
| 85 |
✓ 671.3K
|
function transfer(address to, uint256 amount) public virtual returns (bool) {
|
|
| 86 |
✓ 671.3K
|
uint256 fromBalance = balanceOf[msg.sender]; |
|
| 87 |
✓ 671.3K
|
if (fromBalance < amount) revert InsufficientBalance(msg.sender, fromBalance, amount); |
|
| 88 | |||
| 89 |
✓ 539.8K
|
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 |
✓ 539.8K
|
balanceOf[to] += amount; |
|
| 95 |
} |
||
| 96 | |||
| 97 |
✓ 539.8K
|
emit Transfer(msg.sender, to, amount); |
|
| 98 | |||
| 99 |
✓ 539.8K
|
return true; |
|
| 100 |
} |
||
| 101 | |||
| 102 |
✓ 1.9M
|
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
|
|
| 103 |
✓ 1.9M
|
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. |
|
| 104 |
✓ 1.9M
|
uint256 fromBalance = balanceOf[from]; |
|
| 105 | |||
| 106 |
✓ 1.9M
|
if (allowed != type(uint256).max) {
|
|
| 107 |
✓ 257.0K
|
if (allowed < amount) revert InsufficientAllowance(from, msg.sender, allowed, amount); |
|
| 108 |
✓ 17.7K
|
allowance[from][msg.sender] = allowed - amount; |
|
| 109 |
} |
||
| 110 | |||
| 111 |
✓ 1.6M
|
if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
|
| 112 |
✓ 679.1K
|
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 |
✓ 679.1K
|
balanceOf[to] += amount; |
|
| 118 |
} |
||
| 119 | |||
| 120 |
✓ 679.1K
|
emit Transfer(from, to, amount); |
|
| 121 | |||
| 122 |
✓ 679.1K
|
return true; |
|
| 123 |
} |
||
| 124 | |||
| 125 |
/*////////////////////////////////////////////////////////////// |
||
| 126 |
EIP-2612 LOGIC |
||
| 127 |
//////////////////////////////////////////////////////////////*/ |
||
| 128 | |||
| 129 |
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) |
||
| 130 |
public |
||
| 131 |
virtual |
||
| 132 |
{
|
||
| 133 |
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); |
||
| 134 | |||
| 135 |
// Unchecked because the only math done is incrementing |
||
| 136 |
// the owner's nonce which cannot realistically overflow. |
||
| 137 |
unchecked {
|
||
| 138 |
address recoveredAddress = ecrecover( |
||
| 139 |
keccak256( |
||
| 140 |
abi.encodePacked( |
||
| 141 |
"\x19\x01", |
||
| 142 |
DOMAIN_SEPARATOR(), |
||
| 143 |
keccak256( |
||
| 144 |
abi.encode( |
||
| 145 |
keccak256( |
||
| 146 |
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" |
||
| 147 |
), |
||
| 148 |
owner, |
||
| 149 |
spender, |
||
| 150 |
value, |
||
| 151 |
nonces[owner]++, |
||
| 152 |
deadline |
||
| 153 |
) |
||
| 154 |
) |
||
| 155 |
) |
||
| 156 |
), |
||
| 157 |
v, |
||
| 158 |
r, |
||
| 159 |
s |
||
| 160 |
); |
||
| 161 | |||
| 162 |
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); |
||
| 163 | |||
| 164 |
allowance[recoveredAddress][spender] = value; |
||
| 165 |
} |
||
| 166 | |||
| 167 |
emit Approval(owner, spender, value); |
||
| 168 |
} |
||
| 169 | |||
| 170 |
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
|
||
| 171 |
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); |
||
| 172 |
} |
||
| 173 | |||
| 174 |
✓ 229.9K
|
function computeDomainSeparator() internal view virtual returns (bytes32) {
|
|
| 175 |
return keccak256( |
||
| 176 |
abi.encode( |
||
| 177 |
✓ 229.9K
|
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
|
|
| 178 |
✓ 229.9K
|
keccak256(bytes(name)), |
|
| 179 |
✓ 229.9K
|
keccak256("1"),
|
|
| 180 |
✓ 229.9K
|
block.chainid, |
|
| 181 |
✓ 229.9K
|
address(this) |
|
| 182 |
) |
||
| 183 |
); |
||
| 184 |
} |
||
| 185 | |||
| 186 |
/*////////////////////////////////////////////////////////////// |
||
| 187 |
INTERNAL MINT/BURN LOGIC |
||
| 188 |
//////////////////////////////////////////////////////////////*/ |
||
| 189 | |||
| 190 |
function _mint(address to, uint256 amount) internal virtual {
|
||
| 191 |
✓ 200.7K
|
uint256 newTotalSupply = totalSupply + amount; |
|
| 192 |
✓ 199.1K
|
if (newTotalSupply < totalSupply) revert MintOverflow(totalSupply, amount); |
|
| 193 |
✓ 199.1K
|
totalSupply = newTotalSupply; |
|
| 194 | |||
| 195 |
// Cannot overflow because the sum of all user |
||
| 196 |
// balances can't exceed the max uint256 value. |
||
| 197 |
unchecked {
|
||
| 198 |
✓ 199.1K
|
balanceOf[to] += amount; |
|
| 199 |
} |
||
| 200 | |||
| 201 |
✓ 328.4K
|
emit Transfer(address(0), to, amount); |
|
| 202 |
} |
||
| 203 | |||
| 204 |
function _burn(address from, uint256 amount) internal virtual {
|
||
| 205 |
✓ 138.8K
|
uint256 fromBalance = balanceOf[from]; |
|
| 206 |
✓ 138.8K
|
if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount); |
|
| 207 | |||
| 208 |
✓ 129.3K
|
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 |
✓ 129.3K
|
totalSupply -= amount; |
|
| 214 |
} |
||
| 215 | |||
| 216 |
✓ 129.3K
|
emit Transfer(from, address(0), amount); |
|
| 217 |
} |
||
| 218 |
} |
||
| 219 | |||
| 220 |
contract MockERC20 is ERC20 {
|
||
| 221 |
✓ 229.9K
|
constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {}
|
|
| 222 | |||
| 223 |
function mint(address to, uint256 value) public virtual {
|
||
| 224 |
✓ 141.6K
|
_mint(to, value); |
|
| 225 |
} |
||
| 226 | |||
| 227 |
function burn(address from, uint256 value) public virtual {
|
||
| 228 |
_burn(from, value); |
||
| 229 |
} |
||
| 230 |
} |
||
| 231 |
100.0%
lib/setup-helpers/src/Panic.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
✓ 23
|
library Panic {
|
|
| 5 |
// compiler panics |
||
| 6 |
string constant assertionPanic = "Panic(1)"; |
||
| 7 |
string constant arithmeticPanic = "Panic(17)"; |
||
| 8 |
string constant divisionPanic = "Panic(18)"; |
||
| 9 |
string constant enumPanic = "Panic(33)"; |
||
| 10 |
string constant arrayPanic = "Panic(34)"; |
||
| 11 |
string constant emptyArrayPanic = "Panic(49)"; |
||
| 12 |
string constant outOfBoundsPanic = "Panic(50)"; |
||
| 13 |
string constant memoryPanic = "Panic(65)"; |
||
| 14 |
string constant functionPanic = "Panic(81)"; |
||
| 15 |
} |
||
| 16 |
30.2%
lib/setup-helpers/src/Utils.sol
Lines covered: 16 / 53 (30.2%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {Panic} from "./Panic.sol";
|
||
| 5 | |||
| 6 |
contract Utils {
|
||
| 7 |
/// @dev check if the error returned from a call is the same as the expected error |
||
| 8 |
/// @param err the error returned from a call |
||
| 9 |
/// @param expected the expected error |
||
| 10 |
/// @return true if the error is the same as the expected error, false otherwise |
||
| 11 |
✓ 571.1K
|
function checkError(bytes memory err, string memory expected) internal pure returns (bool) {
|
|
| 12 |
✓ 571.1K
|
(string memory revertMsg, bool customError) = _getRevertMsg(err); |
|
| 13 | |||
| 14 |
✓ 571.1K
|
bytes32 errorBytes; |
|
| 15 |
✓ 571.1K
|
bytes32 expectedBytes; |
|
| 16 | |||
| 17 |
✓ 571.1K
|
if (customError) {
|
|
| 18 |
// Custom error returns the keccak256 hash of the error, so don't need to hash it again |
||
| 19 |
✓ 571.1K
|
errorBytes = bytes32(abi.encodePacked(revertMsg, bytes28(0))); |
|
| 20 |
✓ 571.1K
|
expectedBytes = bytes4(keccak256(abi.encodePacked(expected))); |
|
| 21 |
} else {
|
||
| 22 |
errorBytes = keccak256(abi.encodePacked(revertMsg)); |
||
| 23 |
expectedBytes = keccak256(abi.encodePacked(expected)); |
||
| 24 |
} |
||
| 25 | |||
| 26 |
// Check if error contains expected string |
||
| 27 |
✓ 571.1K
|
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 |
✓ 571.1K
|
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 |
✓ 571.1K
|
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 |
✓ 571.1K
|
if (returnData.length == 4 + 32) {
|
|
| 41 |
// Check that the data starts with the Panic signature |
||
| 42 |
bool panic = _checkIfPanic(returnData); |
||
| 43 | |||
| 44 |
if (panic) {
|
||
| 45 |
return _getPanicCode(returnData); |
||
| 46 |
} |
||
| 47 |
} |
||
| 48 | |||
| 49 |
// Get the error selector from returnData |
||
| 50 |
✓ 571.1K
|
bytes4 errorSelector = _getErrorSelector(returnData); |
|
| 51 | |||
| 52 |
// 2. Error(string) - If it's a standard revert string |
||
| 53 |
✓ 571.1K
|
bytes4 errorStringSelector = bytes4(keccak256("Error(string)")); // Get the standard Error(string) selector
|
|
| 54 | |||
| 55 |
✓ 571.1K
|
if (errorSelector == errorStringSelector) {
|
|
| 56 |
assembly {
|
||
| 57 |
// slice the sighash of the error so we can decode the string |
||
| 58 |
returnData := add(returnData, 0x04) |
||
| 59 |
} |
||
| 60 |
return (abi.decode(returnData, (string)), false); |
||
| 61 |
} |
||
| 62 | |||
| 63 |
// 3. Custom error - Return the custom error selector as a string |
||
| 64 |
✓ 571.1K
|
return (string(abi.encodePacked(errorSelector)), true); |
|
| 65 |
} |
||
| 66 | |||
| 67 |
function _checkIfPanic(bytes memory returnData) internal pure returns (bool) {
|
||
| 68 |
bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)")));
|
||
| 69 | |||
| 70 |
for (uint256 i = 0; i < 4; i++) {
|
||
| 71 |
if (returnData[i] != panicSignature[i]) {
|
||
| 72 |
return false; |
||
| 73 |
} |
||
| 74 |
} |
||
| 75 | |||
| 76 |
return true; |
||
| 77 |
} |
||
| 78 | |||
| 79 |
function _getPanicCode(bytes memory returnData) internal pure returns (string memory, bool) {
|
||
| 80 |
uint256 panicCode; |
||
| 81 |
for (uint256 i = 4; i < 36; i++) {
|
||
| 82 |
panicCode = panicCode << 8; |
||
| 83 |
panicCode |= uint8(returnData[i]); |
||
| 84 |
} |
||
| 85 | |||
| 86 |
// Convert the panic code into its string representation |
||
| 87 |
if (panicCode == 1) {
|
||
| 88 |
// call assert with an argument that evaluates to false |
||
| 89 |
return (Panic.assertionPanic, false); |
||
| 90 |
} else if (panicCode == 17) {
|
||
| 91 |
// arithmetic operation results in underflow or overflow |
||
| 92 |
return (Panic.arithmeticPanic, false); |
||
| 93 |
} else if (panicCode == 18) {
|
||
| 94 |
// division or modulo by zero |
||
| 95 |
return (Panic.divisionPanic, false); |
||
| 96 |
} else if (panicCode == 33) {
|
||
| 97 |
// converting a value that's too big or negative into an enum type |
||
| 98 |
return (Panic.enumPanic, false); |
||
| 99 |
} else if (panicCode == 34) {
|
||
| 100 |
// access a storage byte array that is incorrectly encoded |
||
| 101 |
return (Panic.arrayPanic, false); |
||
| 102 |
} else if (panicCode == 49) {
|
||
| 103 |
// call .pop() on an empty array |
||
| 104 |
return (Panic.emptyArrayPanic, false); |
||
| 105 |
} else if (panicCode == 50) {
|
||
| 106 |
// array access out of bounds |
||
| 107 |
return (Panic.outOfBoundsPanic, false); |
||
| 108 |
} else if (panicCode == 65) {
|
||
| 109 |
// allocate too much memory or create an array that is too large |
||
| 110 |
return (Panic.memoryPanic, false); |
||
| 111 |
} else if (panicCode == 81) {
|
||
| 112 |
// call a zero-initialized variable of internal function type |
||
| 113 |
return (Panic.functionPanic, false); |
||
| 114 |
} |
||
| 115 | |||
| 116 |
return ("Undefined panic code", false);
|
||
| 117 |
} |
||
| 118 | |||
| 119 |
function _getErrorSelector(bytes memory returnData) internal pure returns (bytes4 errorSelector) {
|
||
| 120 |
assembly {
|
||
| 121 |
✓ 571.1K
|
errorSelector := mload(add(returnData, 0x20)) |
|
| 122 |
} |
||
| 123 |
return errorSelector; |
||
| 124 |
} |
||
| 125 |
} |
||
| 126 |
0.0%
lib/v2-core/lib/modulekit/src/accounts/common/interfaces/IERC7579Account.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity >=0.8.23 <0.9.0; |
||
| 3 | |||
| 4 |
/* solhint-disable no-unused-import */ |
||
| 5 | |||
| 6 |
// Types |
||
| 7 |
import { CallType, ExecType, ModeCode } from "../lib/ModeLib.sol";
|
||
| 8 | |||
| 9 |
// Structs |
||
| 10 |
struct Execution {
|
||
| 11 |
address target; |
||
| 12 |
uint256 value; |
||
| 13 |
bytes callData; |
||
| 14 |
} |
||
| 15 | |||
| 16 |
interface IERC7579Account {
|
||
| 17 |
event ModuleInstalled(uint256 moduleTypeId, address module); |
||
| 18 |
event ModuleUninstalled(uint256 moduleTypeId, address module); |
||
| 19 | |||
| 20 |
/** |
||
| 21 |
* @dev Executes a transaction on behalf of the account. |
||
| 22 |
* This function is intended to be called by ERC-4337 EntryPoint.sol |
||
| 23 |
* @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf |
||
| 24 |
* |
||
| 25 |
* @dev MSA MUST implement this function signature. |
||
| 26 |
* If a mode is requested that is not supported by the Account, it MUST revert |
||
| 27 |
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details |
||
| 28 |
* @param executionCalldata The encoded execution call data |
||
| 29 |
*/ |
||
| 30 |
function execute(ModeCode mode, bytes calldata executionCalldata) external payable; |
||
| 31 | |||
| 32 |
/** |
||
| 33 |
* @dev Executes a transaction on behalf of the account. |
||
| 34 |
* This function is intended to be called by Executor Modules |
||
| 35 |
* @dev Ensure adequate authorization control: i.e. onlyExecutorModule |
||
| 36 |
* |
||
| 37 |
* @dev MSA MUST implement this function signature. |
||
| 38 |
* If a mode is requested that is not supported by the Account, it MUST revert |
||
| 39 |
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details |
||
| 40 |
* @param executionCalldata The encoded execution call data |
||
| 41 |
*/ |
||
| 42 |
function executeFromExecutor( |
||
| 43 |
ModeCode mode, |
||
| 44 |
bytes calldata executionCalldata |
||
| 45 |
) |
||
| 46 |
external |
||
| 47 |
payable |
||
| 48 |
returns (bytes[] memory returnData); |
||
| 49 | |||
| 50 |
/** |
||
| 51 |
* @dev ERC-1271 isValidSignature |
||
| 52 |
* This function is intended to be used to validate a smart account signature |
||
| 53 |
* and may forward the call to a validator module |
||
| 54 |
* |
||
| 55 |
* @param hash The hash of the data that is signed |
||
| 56 |
* @param data The data that is signed |
||
| 57 |
*/ |
||
| 58 |
function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); |
||
| 59 | |||
| 60 |
/** |
||
| 61 |
* @dev installs a Module of a certain type on the smart account |
||
| 62 |
* @dev Implement Authorization control of your chosing |
||
| 63 |
* @param moduleTypeId the module type ID according the ERC-7579 spec |
||
| 64 |
* @param module the module address |
||
| 65 |
* @param initData arbitrary data that may be required on the module during `onInstall` |
||
| 66 |
* initialization. |
||
| 67 |
*/ |
||
| 68 |
function installModule( |
||
| 69 |
uint256 moduleTypeId, |
||
| 70 |
address module, |
||
| 71 |
bytes calldata initData |
||
| 72 |
) |
||
| 73 |
external |
||
| 74 |
payable; |
||
| 75 | |||
| 76 |
/** |
||
| 77 |
* @dev uninstalls a Module of a certain type on the smart account |
||
| 78 |
* @dev Implement Authorization control of your chosing |
||
| 79 |
* @param moduleTypeId the module type ID according the ERC-7579 spec |
||
| 80 |
* @param module the module address |
||
| 81 |
* @param deInitData arbitrary data that may be required on the module during `onUninstall` |
||
| 82 |
* de-initialization. |
||
| 83 |
*/ |
||
| 84 |
function uninstallModule( |
||
| 85 |
uint256 moduleTypeId, |
||
| 86 |
address module, |
||
| 87 |
bytes calldata deInitData |
||
| 88 |
) |
||
| 89 |
external |
||
| 90 |
payable; |
||
| 91 | |||
| 92 |
/** |
||
| 93 |
* Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol) |
||
| 94 |
* @param encodedMode the encoded mode |
||
| 95 |
*/ |
||
| 96 |
function supportsExecutionMode(ModeCode encodedMode) external view returns (bool); |
||
| 97 | |||
| 98 |
/** |
||
| 99 |
* Function to check if the account supports installation of a certain module type Id |
||
| 100 |
* @param moduleTypeId the module type ID according the ERC-7579 spec |
||
| 101 |
*/ |
||
| 102 |
function supportsModule(uint256 moduleTypeId) external view returns (bool); |
||
| 103 | |||
| 104 |
/** |
||
| 105 |
* Function to check if the account has a certain module installed |
||
| 106 |
* @param moduleTypeId the module type ID according the ERC-7579 spec |
||
| 107 |
* Note: keep in mind that some contracts can be multiple module types at the same time. It |
||
| 108 |
* thus may be necessary to query multiple module types |
||
| 109 |
* @param module the module address |
||
| 110 |
* @param additionalContext additional context data that the smart account may interpret to |
||
| 111 |
* identifiy conditions under which the module is installed. |
||
| 112 |
* usually this is not necessary, but for some special hooks that |
||
| 113 |
* are stored in mappings, this param might be needed |
||
| 114 |
*/ |
||
| 115 |
function isModuleInstalled( |
||
| 116 |
uint256 moduleTypeId, |
||
| 117 |
address module, |
||
| 118 |
bytes calldata additionalContext |
||
| 119 |
) |
||
| 120 |
external |
||
| 121 |
view |
||
| 122 |
returns (bool); |
||
| 123 | |||
| 124 |
/** |
||
| 125 |
* @dev Returns the account id of the smart account |
||
| 126 |
* @return accountImplementationId the account id of the smart account |
||
| 127 |
* the accountId should be structured like so: |
||
| 128 |
* "vendorname.accountname.semver" |
||
| 129 |
*/ |
||
| 130 |
function accountId() external view returns (string memory accountImplementationId); |
||
| 131 |
} |
||
| 132 |
100.0%
lib/v2-core/lib/modulekit/src/accounts/common/lib/ModeLib.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-3.0 |
||
| 2 |
pragma solidity >=0.8.0 <0.9.0; |
||
| 3 | |||
| 4 |
/** |
||
| 5 |
* @title ModeLib |
||
| 6 |
* @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) |
||
| 7 |
* To allow smart accounts to be very simple, but allow for more complex execution, A custom mode |
||
| 8 |
* encoding is used. |
||
| 9 |
* Function Signature of execute function: |
||
| 10 |
* function execute(ModeCode mode, bytes calldata executionCalldata) external payable; |
||
| 11 |
* This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and |
||
| 12 |
* context. |
||
| 13 |
* NOTE: Simple Account implementations only have to scope for the most significant byte. Account that |
||
| 14 |
* implement |
||
| 15 |
* more complex execution modes may use the entire bytes32. |
||
| 16 |
* |
||
| 17 |
* |--------------------------------------------------------------------| |
||
| 18 |
* | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | |
||
| 19 |
* |--------------------------------------------------------------------| |
||
| 20 |
* | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | |
||
| 21 |
* |--------------------------------------------------------------------| |
||
| 22 |
* |
||
| 23 |
* CALLTYPE: 1 byte |
||
| 24 |
* CallType is used to determine how the executeCalldata paramter of the execute function has to be |
||
| 25 |
* decoded. |
||
| 26 |
* It can be either single, batch or delegatecall. In the future different calls could be added. |
||
| 27 |
* CALLTYPE can be used by a validation module to determine how to decode <userOp.callData[36:]>. |
||
| 28 |
* |
||
| 29 |
* EXECTYPE: 1 byte |
||
| 30 |
* ExecType is used to determine how the account should handle the execution. |
||
| 31 |
* It can indicate if the execution should revert on failure or continue execution. |
||
| 32 |
* In the future more execution modes may be added. |
||
| 33 |
* Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in |
||
| 34 |
* a batch fails, the entire batch is reverted |
||
| 35 |
* |
||
| 36 |
* UNUSED: 4 bytes |
||
| 37 |
* Unused bytes are reserved for future use. |
||
| 38 |
* |
||
| 39 |
* ModeSelector: bytes4 |
||
| 40 |
* The "optional" mode selector can be used by account vendors, to implement custom behavior in |
||
| 41 |
* their accounts. |
||
| 42 |
* the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename"))
|
||
| 43 |
* this is to prevent collisions between different vendors, while allowing innovation and the |
||
| 44 |
* development of new features without coordination between ERC-7579 implementing accounts |
||
| 45 |
* |
||
| 46 |
* ModePayload: 22 bytes |
||
| 47 |
* Mode payload is used to pass additional data to the smart account execution, this may be |
||
| 48 |
* interpreted depending on the ModeSelector |
||
| 49 |
* |
||
| 50 |
* ExecutionCallData: n bytes |
||
| 51 |
* single, delegatecall or batch exec abi.encoded as bytes |
||
| 52 |
*/ |
||
| 53 | |||
| 54 |
// Custom type for improved developer experience |
||
| 55 |
type ModeCode is bytes32; |
||
| 56 | |||
| 57 |
type CallType is bytes1; |
||
| 58 | |||
| 59 |
type ExecType is bytes1; |
||
| 60 | |||
| 61 |
type ModeSelector is bytes4; |
||
| 62 | |||
| 63 |
type ModePayload is bytes22; |
||
| 64 | |||
| 65 |
// Default CallType |
||
| 66 |
CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); |
||
| 67 |
// Batched CallType |
||
| 68 |
CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); |
||
| 69 |
CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); |
||
| 70 |
// @dev Implementing delegatecall is OPTIONAL! |
||
| 71 |
// implement delegatecall with extreme care. |
||
| 72 |
CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); |
||
| 73 | |||
| 74 |
// @dev default behavior is to revert on failure |
||
| 75 |
// To allow very simple accounts to use mode encoding, the default behavior is to revert on failure |
||
| 76 |
// Since this is value 0x00, no additional encoding is required for simple accounts |
||
| 77 |
ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); |
||
| 78 |
// @dev account may elect to change execution behavior. For example "try exec" / "allow fail" |
||
| 79 |
ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); |
||
| 80 | |||
| 81 |
ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); |
||
| 82 |
// Example declaration of a custom mode selector |
||
| 83 |
ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset")));
|
||
| 84 | |||
| 85 |
/** |
||
| 86 |
* @dev ModeLib is a helper library to encode/decode ModeCodes |
||
| 87 |
*/ |
||
| 88 |
✓ 16.1K
|
⟲ 16.1K
|
library ModeLib {
|
| 89 |
function decode(ModeCode mode) |
||
| 90 |
internal |
||
| 91 |
pure |
||
| 92 |
returns ( |
||
| 93 |
CallType _calltype, |
||
| 94 |
ExecType _execType, |
||
| 95 |
ModeSelector _modeSelector, |
||
| 96 |
ModePayload _modePayload |
||
| 97 |
) |
||
| 98 |
{
|
||
| 99 |
// solhint-disable-next-line no-inline-assembly |
||
| 100 |
assembly {
|
||
| 101 |
_calltype := mode |
||
| 102 |
_execType := shl(8, mode) |
||
| 103 |
_modeSelector := shl(48, mode) |
||
| 104 |
_modePayload := shl(80, mode) |
||
| 105 |
} |
||
| 106 |
} |
||
| 107 | |||
| 108 |
function encode( |
||
| 109 |
CallType callType, |
||
| 110 |
ExecType execType, |
||
| 111 |
ModeSelector mode, |
||
| 112 |
ModePayload payload |
||
| 113 |
) |
||
| 114 |
internal |
||
| 115 |
pure |
||
| 116 |
returns (ModeCode) |
||
| 117 |
{
|
||
| 118 |
return ModeCode.wrap( |
||
| 119 |
bytes32( |
||
| 120 |
abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload) |
||
| 121 |
) |
||
| 122 |
); |
||
| 123 |
} |
||
| 124 | |||
| 125 |
function encodeSimpleBatch() internal pure returns (ModeCode mode) {
|
||
| 126 |
mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); |
||
| 127 |
} |
||
| 128 | |||
| 129 |
function encodeSimpleSingle() internal pure returns (ModeCode mode) {
|
||
| 130 |
mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); |
||
| 131 |
} |
||
| 132 | |||
| 133 |
function getCallType(ModeCode mode) internal pure returns (CallType calltype) {
|
||
| 134 |
// solhint-disable-next-line no-inline-assembly |
||
| 135 |
assembly {
|
||
| 136 |
calltype := mode |
||
| 137 |
} |
||
| 138 |
} |
||
| 139 |
} |
||
| 140 | |||
| 141 |
using { eqModeSelector as == } for ModeSelector global;
|
||
| 142 |
using { eqCallType as == } for CallType global;
|
||
| 143 |
using { neqCallType as != } for CallType global;
|
||
| 144 |
using { eqExecType as == } for ExecType global;
|
||
| 145 | |||
| 146 |
function eqCallType(CallType a, CallType b) pure returns (bool) {
|
||
| 147 |
return CallType.unwrap(a) == CallType.unwrap(b); |
||
| 148 |
} |
||
| 149 | |||
| 150 |
function neqCallType(CallType a, CallType b) pure returns (bool) {
|
||
| 151 |
return CallType.unwrap(a) == CallType.unwrap(b); |
||
| 152 |
} |
||
| 153 | |||
| 154 |
function eqExecType(ExecType a, ExecType b) pure returns (bool) {
|
||
| 155 |
return ExecType.unwrap(a) == ExecType.unwrap(b); |
||
| 156 |
} |
||
| 157 | |||
| 158 |
function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) {
|
||
| 159 |
return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); |
||
| 160 |
} |
||
| 161 |
100.0%
lib/v2-core/lib/modulekit/src/accounts/erc7579/lib/ExecutionLib.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity >=0.8.23 <0.9.0; |
||
| 3 | |||
| 4 |
// Types |
||
| 5 |
import { Execution } from "../../common/interfaces/IERC7579Account.sol";
|
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* Helper Library for decoding Execution calldata |
||
| 9 |
* malloc for memory allocation is bad for gas. use this assembly instead |
||
| 10 |
*/ |
||
| 11 |
✓ 15.9K
|
⟲ 15.9K
|
library ExecutionLib {
|
| 12 |
error ERC7579DecodingError(); |
||
| 13 | |||
| 14 |
/** |
||
| 15 |
* @notice Decode a batch of `Execution` executionBatch from a `bytes` calldata. |
||
| 16 |
* @dev code is copied from solady's LibERC7579.sol |
||
| 17 |
* https://github.com/Vectorized/solady/blob/740812cedc9a1fc11e17cb3d4569744367dedf19/src/accounts/LibERC7579.sol#L146 |
||
| 18 |
* Credits to Vectorized and the Solady Team |
||
| 19 |
*/ |
||
| 20 |
function decodeBatch(bytes calldata executionCalldata) |
||
| 21 |
internal |
||
| 22 |
pure |
||
| 23 |
returns (Execution[] calldata executionBatch) |
||
| 24 |
{
|
||
| 25 |
/// @solidity memory-safe-assembly |
||
| 26 |
assembly {
|
||
| 27 |
let u := calldataload(executionCalldata.offset) |
||
| 28 |
let s := add(executionCalldata.offset, u) |
||
| 29 |
let e := sub(add(executionCalldata.offset, executionCalldata.length), 0x20) |
||
| 30 |
executionBatch.offset := add(s, 0x20) |
||
| 31 |
executionBatch.length := calldataload(s) |
||
| 32 |
if or(shr(64, u), gt(add(s, shl(5, executionBatch.length)), e)) {
|
||
| 33 |
mstore(0x00, 0xba597e7e) // `DecodingError()`. |
||
| 34 |
revert(0x1c, 0x04) |
||
| 35 |
} |
||
| 36 |
if executionBatch.length {
|
||
| 37 |
// Perform bounds checks on the decoded `executionBatch`. |
||
| 38 |
// Loop runs out-of-gas if `executionBatch.length` is big enough to cause overflows. |
||
| 39 |
for { let i := executionBatch.length } 1 { } {
|
||
| 40 |
i := sub(i, 1) |
||
| 41 |
let p := calldataload(add(executionBatch.offset, shl(5, i))) |
||
| 42 |
let c := add(executionBatch.offset, p) |
||
| 43 |
let q := calldataload(add(c, 0x40)) |
||
| 44 |
let o := add(c, q) |
||
| 45 |
// forgefmt: disable-next-item |
||
| 46 |
if or(shr(64, or(calldataload(o), or(p, q))), |
||
| 47 |
or(gt(add(c, 0x40), e), gt(add(o, calldataload(o)), e))) {
|
||
| 48 |
mstore(0x00, 0xba597e7e) // `DecodingError()`. |
||
| 49 |
revert(0x1c, 0x04) |
||
| 50 |
} |
||
| 51 |
if iszero(i) { break }
|
||
| 52 |
} |
||
| 53 |
} |
||
| 54 |
} |
||
| 55 |
} |
||
| 56 | |||
| 57 |
function encodeBatch(Execution[] memory executions) |
||
| 58 |
internal |
||
| 59 |
pure |
||
| 60 |
returns (bytes memory callData) |
||
| 61 |
{
|
||
| 62 |
callData = abi.encode(executions); |
||
| 63 |
} |
||
| 64 | |||
| 65 |
function decodeSingle(bytes calldata executionCalldata) |
||
| 66 |
internal |
||
| 67 |
pure |
||
| 68 |
returns (address target, uint256 value, bytes calldata callData) |
||
| 69 |
{
|
||
| 70 |
target = address(bytes20(executionCalldata[0:20])); |
||
| 71 |
value = uint256(bytes32(executionCalldata[20:52])); |
||
| 72 |
callData = executionCalldata[52:]; |
||
| 73 |
} |
||
| 74 | |||
| 75 |
function encodeSingle( |
||
| 76 |
address target, |
||
| 77 |
uint256 value, |
||
| 78 |
bytes memory callData |
||
| 79 |
) |
||
| 80 |
internal |
||
| 81 |
pure |
||
| 82 |
returns (bytes memory userOpCalldata) |
||
| 83 |
{
|
||
| 84 |
userOpCalldata = abi.encodePacked(target, value, callData); |
||
| 85 |
} |
||
| 86 |
} |
||
| 87 |
53.3%
lib/v2-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol
Lines covered: 16 / 30 (53.3%)
| 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 |
✓ 1
|
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 |
✓ 274.4K
|
_checkRole(role); |
|
| 65 |
_; |
||
| 66 |
} |
||
| 67 | |||
| 68 |
/// @inheritdoc IERC165 |
||
| 69 |
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
||
| 70 |
✓ 24.1M
|
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); |
|
| 71 |
} |
||
| 72 | |||
| 73 |
/** |
||
| 74 |
* @dev Returns `true` if `account` has been granted `role`. |
||
| 75 |
*/ |
||
| 76 |
✓ 1.2M
|
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
|
|
| 77 |
✓ 1.2M
|
return _roles[role].hasRole[account]; |
|
| 78 |
} |
||
| 79 | |||
| 80 |
/** |
||
| 81 |
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
|
||
| 82 |
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
|
||
| 83 |
*/ |
||
| 84 |
function _checkRole(bytes32 role) internal view virtual {
|
||
| 85 |
✓ 1.2M
|
_checkRole(role, _msgSender()); |
|
| 86 |
} |
||
| 87 | |||
| 88 |
/** |
||
| 89 |
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
|
||
| 90 |
* is missing `role`. |
||
| 91 |
*/ |
||
| 92 |
function _checkRole(bytes32 role, address account) internal view virtual {
|
||
| 93 |
✓ 1.2M
|
if (!hasRole(role, account)) {
|
|
| 94 |
revert AccessControlUnauthorizedAccount(account, role); |
||
| 95 |
} |
||
| 96 |
} |
||
| 97 | |||
| 98 |
/** |
||
| 99 |
* @dev Returns the admin role that controls `role`. See {grantRole} and
|
||
| 100 |
* {revokeRole}.
|
||
| 101 |
* |
||
| 102 |
* To change a role's admin, use {_setRoleAdmin}.
|
||
| 103 |
*/ |
||
| 104 |
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
|
||
| 105 |
✓ 5
|
return _roles[role].adminRole; |
|
| 106 |
} |
||
| 107 | |||
| 108 |
/** |
||
| 109 |
* @dev Grants `role` to `account`. |
||
| 110 |
* |
||
| 111 |
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||
| 112 |
* event. |
||
| 113 |
* |
||
| 114 |
* Requirements: |
||
| 115 |
* |
||
| 116 |
* - the caller must have ``role``'s admin role. |
||
| 117 |
* |
||
| 118 |
* May emit a {RoleGranted} event.
|
||
| 119 |
*/ |
||
| 120 |
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
|
||
| 121 |
_grantRole(role, account); |
||
| 122 |
} |
||
| 123 | |||
| 124 |
/** |
||
| 125 |
* @dev Revokes `role` from `account`. |
||
| 126 |
* |
||
| 127 |
* If `account` had been granted `role`, emits a {RoleRevoked} event.
|
||
| 128 |
* |
||
| 129 |
* Requirements: |
||
| 130 |
* |
||
| 131 |
* - the caller must have ``role``'s admin role. |
||
| 132 |
* |
||
| 133 |
* May emit a {RoleRevoked} event.
|
||
| 134 |
*/ |
||
| 135 |
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
|
||
| 136 |
_revokeRole(role, account); |
||
| 137 |
} |
||
| 138 | |||
| 139 |
/** |
||
| 140 |
* @dev Revokes `role` from the calling account. |
||
| 141 |
* |
||
| 142 |
* Roles are often managed via {grantRole} and {revokeRole}: this function's
|
||
| 143 |
* purpose is to provide a mechanism for accounts to lose their privileges |
||
| 144 |
* if they are compromised (such as when a trusted device is misplaced). |
||
| 145 |
* |
||
| 146 |
* If the calling account had been revoked `role`, emits a {RoleRevoked}
|
||
| 147 |
* event. |
||
| 148 |
* |
||
| 149 |
* Requirements: |
||
| 150 |
* |
||
| 151 |
* - the caller must be `callerConfirmation`. |
||
| 152 |
* |
||
| 153 |
* May emit a {RoleRevoked} event.
|
||
| 154 |
*/ |
||
| 155 |
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
|
||
| 156 |
if (callerConfirmation != _msgSender()) {
|
||
| 157 |
revert AccessControlBadConfirmation(); |
||
| 158 |
} |
||
| 159 | |||
| 160 |
_revokeRole(role, callerConfirmation); |
||
| 161 |
} |
||
| 162 | |||
| 163 |
/** |
||
| 164 |
* @dev Sets `adminRole` as ``role``'s admin role. |
||
| 165 |
* |
||
| 166 |
* Emits a {RoleAdminChanged} event.
|
||
| 167 |
*/ |
||
| 168 |
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
|
||
| 169 |
✓ 5
|
bytes32 previousAdminRole = getRoleAdmin(role); |
|
| 170 |
✓ 5
|
_roles[role].adminRole = adminRole; |
|
| 171 |
✓ 5
|
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 |
✓ 5
|
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
|
|
| 182 |
if (!hasRole(role, account)) {
|
||
| 183 |
✓ 5
|
_roles[role].hasRole[account] = true; |
|
| 184 |
✓ 5
|
emit RoleGranted(role, account, _msgSender()); |
|
| 185 |
✓ 5
|
return true; |
|
| 186 |
} else {
|
||
| 187 |
✓ 1.2M
|
return false; |
|
| 188 |
} |
||
| 189 |
} |
||
| 190 | |||
| 191 |
/** |
||
| 192 |
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked. |
||
| 193 |
* |
||
| 194 |
* Internal function without access restriction. |
||
| 195 |
* |
||
| 196 |
* May emit a {RoleRevoked} event.
|
||
| 197 |
*/ |
||
| 198 |
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
|
||
| 199 |
if (hasRole(role, account)) {
|
||
| 200 |
_roles[role].hasRole[account] = false; |
||
| 201 |
emit RoleRevoked(role, account, _msgSender()); |
||
| 202 |
return true; |
||
| 203 |
} else {
|
||
| 204 |
return false; |
||
| 205 |
} |
||
| 206 |
} |
||
| 207 |
} |
||
| 208 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.8.4; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev External interface of AccessControl declared to support ERC-165 detection. |
||
| 8 |
*/ |
||
| 9 |
interface IAccessControl {
|
||
| 10 |
/** |
||
| 11 |
* @dev The `account` is missing a role. |
||
| 12 |
*/ |
||
| 13 |
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); |
||
| 14 | |||
| 15 |
/** |
||
| 16 |
* @dev The caller of a function is not the expected one. |
||
| 17 |
* |
||
| 18 |
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
|
||
| 19 |
*/ |
||
| 20 |
error AccessControlBadConfirmation(); |
||
| 21 | |||
| 22 |
/** |
||
| 23 |
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` |
||
| 24 |
* |
||
| 25 |
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite |
||
| 26 |
* {RoleAdminChanged} not being emitted to signal this.
|
||
| 27 |
*/ |
||
| 28 |
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev Emitted when `account` is granted `role`. |
||
| 32 |
* |
||
| 33 |
* `sender` is the account that originated the contract call. This account bears the admin role (for the granted role). |
||
| 34 |
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
|
||
| 35 |
*/ |
||
| 36 |
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); |
||
| 37 | |||
| 38 |
/** |
||
| 39 |
* @dev Emitted when `account` is revoked `role`. |
||
| 40 |
* |
||
| 41 |
* `sender` is the account that originated the contract call: |
||
| 42 |
* - if using `revokeRole`, it is the admin role bearer |
||
| 43 |
* - if using `renounceRole`, it is the role bearer (i.e. `account`) |
||
| 44 |
*/ |
||
| 45 |
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); |
||
| 46 | |||
| 47 |
/** |
||
| 48 |
* @dev Returns `true` if `account` has been granted `role`. |
||
| 49 |
*/ |
||
| 50 |
function hasRole(bytes32 role, address account) external view returns (bool); |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Returns the admin role that controls `role`. See {grantRole} and
|
||
| 54 |
* {revokeRole}.
|
||
| 55 |
* |
||
| 56 |
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
|
||
| 57 |
*/ |
||
| 58 |
function getRoleAdmin(bytes32 role) external view returns (bytes32); |
||
| 59 | |||
| 60 |
/** |
||
| 61 |
* @dev Grants `role` to `account`. |
||
| 62 |
* |
||
| 63 |
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||
| 64 |
* event. |
||
| 65 |
* |
||
| 66 |
* Requirements: |
||
| 67 |
* |
||
| 68 |
* - the caller must have ``role``'s admin role. |
||
| 69 |
*/ |
||
| 70 |
function grantRole(bytes32 role, address account) external; |
||
| 71 | |||
| 72 |
/** |
||
| 73 |
* @dev Revokes `role` from `account`. |
||
| 74 |
* |
||
| 75 |
* If `account` had been granted `role`, emits a {RoleRevoked} event.
|
||
| 76 |
* |
||
| 77 |
* Requirements: |
||
| 78 |
* |
||
| 79 |
* - the caller must have ``role``'s admin role. |
||
| 80 |
*/ |
||
| 81 |
function revokeRole(bytes32 role, address account) external; |
||
| 82 | |||
| 83 |
/** |
||
| 84 |
* @dev Revokes `role` from the calling account. |
||
| 85 |
* |
||
| 86 |
* Roles are often managed via {grantRole} and {revokeRole}: this function's
|
||
| 87 |
* purpose is to provide a mechanism for accounts to lose their privileges |
||
| 88 |
* if they are compromised (such as when a trusted device is misplaced). |
||
| 89 |
* |
||
| 90 |
* If the calling account had been granted `role`, emits a {RoleRevoked}
|
||
| 91 |
* event. |
||
| 92 |
* |
||
| 93 |
* Requirements: |
||
| 94 |
* |
||
| 95 |
* - the caller must be `callerConfirmation`. |
||
| 96 |
*/ |
||
| 97 |
function renounceRole(bytes32 role, address callerConfirmation) external; |
||
| 98 |
} |
||
| 99 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.6.2; |
||
| 5 | |||
| 6 |
import {IERC20} from "./IERC20.sol";
|
||
| 7 |
import {IERC165} from "./IERC165.sol";
|
||
| 8 | |||
| 9 |
/** |
||
| 10 |
* @title IERC1363 |
||
| 11 |
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363]. |
||
| 12 |
* |
||
| 13 |
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract |
||
| 14 |
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction. |
||
| 15 |
*/ |
||
| 16 |
interface IERC1363 is IERC20, IERC165 {
|
||
| 17 |
/* |
||
| 18 |
* Note: the ERC-165 identifier for this interface is 0xb0202a11. |
||
| 19 |
* 0xb0202a11 === |
||
| 20 |
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
|
||
| 21 |
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
|
||
| 22 |
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
|
||
| 23 |
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
|
||
| 24 |
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
|
||
| 25 |
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
|
||
| 26 |
*/ |
||
| 27 | |||
| 28 |
/** |
||
| 29 |
* @dev Moves a `value` amount of tokens from the caller's account to `to` |
||
| 30 |
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||
| 31 |
* @param to The address which you want to transfer to. |
||
| 32 |
* @param value The amount of tokens to be transferred. |
||
| 33 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 34 |
*/ |
||
| 35 |
function transferAndCall(address to, uint256 value) external returns (bool); |
||
| 36 | |||
| 37 |
/** |
||
| 38 |
* @dev Moves a `value` amount of tokens from the caller's account to `to` |
||
| 39 |
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||
| 40 |
* @param to The address which you want to transfer to. |
||
| 41 |
* @param value The amount of tokens to be transferred. |
||
| 42 |
* @param data Additional data with no specified format, sent in call to `to`. |
||
| 43 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 44 |
*/ |
||
| 45 |
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool); |
||
| 46 | |||
| 47 |
/** |
||
| 48 |
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism |
||
| 49 |
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||
| 50 |
* @param from The address which you want to send tokens from. |
||
| 51 |
* @param to The address which you want to transfer to. |
||
| 52 |
* @param value The amount of tokens to be transferred. |
||
| 53 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 54 |
*/ |
||
| 55 |
function transferFromAndCall(address from, address to, uint256 value) external returns (bool); |
||
| 56 | |||
| 57 |
/** |
||
| 58 |
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism |
||
| 59 |
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||
| 60 |
* @param from The address which you want to send tokens from. |
||
| 61 |
* @param to The address which you want to transfer to. |
||
| 62 |
* @param value The amount of tokens to be transferred. |
||
| 63 |
* @param data Additional data with no specified format, sent in call to `to`. |
||
| 64 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 65 |
*/ |
||
| 66 |
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool); |
||
| 67 | |||
| 68 |
/** |
||
| 69 |
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
||
| 70 |
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
|
||
| 71 |
* @param spender The address which will spend the funds. |
||
| 72 |
* @param value The amount of tokens to be spent. |
||
| 73 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 74 |
*/ |
||
| 75 |
function approveAndCall(address spender, uint256 value) external returns (bool); |
||
| 76 | |||
| 77 |
/** |
||
| 78 |
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
||
| 79 |
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
|
||
| 80 |
* @param spender The address which will spend the funds. |
||
| 81 |
* @param value The amount of tokens to be spent. |
||
| 82 |
* @param data Additional data with no specified format, sent in call to `spender`. |
||
| 83 |
* @return A boolean value indicating whether the operation succeeded unless throwing. |
||
| 84 |
*/ |
||
| 85 |
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool); |
||
| 86 |
} |
||
| 87 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.4.16; |
||
| 5 | |||
| 6 |
import {IERC165} from "../utils/introspection/IERC165.sol";
|
||
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.4.16; |
||
| 5 | |||
| 6 |
import {IERC20} from "../token/ERC20/IERC20.sol";
|
||
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20Metadata.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.6.2; |
||
| 5 | |||
| 6 |
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
|
||
| 7 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.6.2; |
||
| 5 | |||
| 6 |
import {IERC20} from "../token/ERC20/IERC20.sol";
|
||
| 7 |
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
|
||
| 8 | |||
| 9 |
/** |
||
| 10 |
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in |
||
| 11 |
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. |
||
| 12 |
*/ |
||
| 13 |
interface IERC4626 is IERC20, IERC20Metadata {
|
||
| 14 |
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); |
||
| 15 | |||
| 16 |
event Withdraw( |
||
| 17 |
address indexed sender, |
||
| 18 |
address indexed receiver, |
||
| 19 |
address indexed owner, |
||
| 20 |
uint256 assets, |
||
| 21 |
uint256 shares |
||
| 22 |
); |
||
| 23 | |||
| 24 |
/** |
||
| 25 |
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
||
| 26 |
* |
||
| 27 |
* - MUST be an ERC-20 token contract. |
||
| 28 |
* - MUST NOT revert. |
||
| 29 |
*/ |
||
| 30 |
function asset() external view returns (address assetTokenAddress); |
||
| 31 | |||
| 32 |
/** |
||
| 33 |
* @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
||
| 34 |
* |
||
| 35 |
* - SHOULD include any compounding that occurs from yield. |
||
| 36 |
* - MUST be inclusive of any fees that are charged against assets in the Vault. |
||
| 37 |
* - MUST NOT revert. |
||
| 38 |
*/ |
||
| 39 |
function totalAssets() external view returns (uint256 totalManagedAssets); |
||
| 40 | |||
| 41 |
/** |
||
| 42 |
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
||
| 43 |
* scenario where all the conditions are met. |
||
| 44 |
* |
||
| 45 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 46 |
* - MUST NOT show any variations depending on the caller. |
||
| 47 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 48 |
* - MUST NOT revert. |
||
| 49 |
* |
||
| 50 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 51 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 52 |
* from. |
||
| 53 |
*/ |
||
| 54 |
function convertToShares(uint256 assets) external view returns (uint256 shares); |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
||
| 58 |
* scenario where all the conditions are met. |
||
| 59 |
* |
||
| 60 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 61 |
* - MUST NOT show any variations depending on the caller. |
||
| 62 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 63 |
* - MUST NOT revert. |
||
| 64 |
* |
||
| 65 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 66 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 67 |
* from. |
||
| 68 |
*/ |
||
| 69 |
function convertToAssets(uint256 shares) external view returns (uint256 assets); |
||
| 70 | |||
| 71 |
/** |
||
| 72 |
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
||
| 73 |
* through a deposit call. |
||
| 74 |
* |
||
| 75 |
* - MUST return a limited value if receiver is subject to some deposit limit. |
||
| 76 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
||
| 77 |
* - MUST NOT revert. |
||
| 78 |
*/ |
||
| 79 |
function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
||
| 80 | |||
| 81 |
/** |
||
| 82 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
||
| 83 |
* current on-chain conditions. |
||
| 84 |
* |
||
| 85 |
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
||
| 86 |
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
||
| 87 |
* in the same transaction. |
||
| 88 |
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
||
| 89 |
* deposit would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 90 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 91 |
* - MUST NOT revert. |
||
| 92 |
* |
||
| 93 |
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
||
| 94 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 95 |
*/ |
||
| 96 |
function previewDeposit(uint256 assets) external view returns (uint256 shares); |
||
| 97 | |||
| 98 |
/** |
||
| 99 |
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
||
| 100 |
* |
||
| 101 |
* - MUST emit the Deposit event. |
||
| 102 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 103 |
* deposit execution, and are accounted for during deposit. |
||
| 104 |
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
||
| 105 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 106 |
* |
||
| 107 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 108 |
*/ |
||
| 109 |
function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
||
| 110 | |||
| 111 |
/** |
||
| 112 |
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
||
| 113 |
* - MUST return a limited value if receiver is subject to some mint limit. |
||
| 114 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
||
| 115 |
* - MUST NOT revert. |
||
| 116 |
*/ |
||
| 117 |
function maxMint(address receiver) external view returns (uint256 maxShares); |
||
| 118 | |||
| 119 |
/** |
||
| 120 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
||
| 121 |
* current on-chain conditions. |
||
| 122 |
* |
||
| 123 |
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
||
| 124 |
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
||
| 125 |
* same transaction. |
||
| 126 |
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
||
| 127 |
* would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 128 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 129 |
* - MUST NOT revert. |
||
| 130 |
* |
||
| 131 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
||
| 132 |
* share price or some other type of condition, meaning the depositor will lose assets by minting. |
||
| 133 |
*/ |
||
| 134 |
function previewMint(uint256 shares) external view returns (uint256 assets); |
||
| 135 | |||
| 136 |
/** |
||
| 137 |
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
||
| 138 |
* |
||
| 139 |
* - MUST emit the Deposit event. |
||
| 140 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
||
| 141 |
* execution, and are accounted for during mint. |
||
| 142 |
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
||
| 143 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 144 |
* |
||
| 145 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 146 |
*/ |
||
| 147 |
function mint(uint256 shares, address receiver) external returns (uint256 assets); |
||
| 148 | |||
| 149 |
/** |
||
| 150 |
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
||
| 151 |
* Vault, through a withdraw call. |
||
| 152 |
* |
||
| 153 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 154 |
* - MUST NOT revert. |
||
| 155 |
*/ |
||
| 156 |
function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
||
| 157 | |||
| 158 |
/** |
||
| 159 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
||
| 160 |
* given current on-chain conditions. |
||
| 161 |
* |
||
| 162 |
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
||
| 163 |
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
||
| 164 |
* called |
||
| 165 |
* in the same transaction. |
||
| 166 |
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
||
| 167 |
* the withdrawal would be accepted, regardless if the user has enough shares, etc. |
||
| 168 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 169 |
* - MUST NOT revert. |
||
| 170 |
* |
||
| 171 |
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
||
| 172 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 173 |
*/ |
||
| 174 |
function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
||
| 175 | |||
| 176 |
/** |
||
| 177 |
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
||
| 178 |
* |
||
| 179 |
* - MUST emit the Withdraw event. |
||
| 180 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 181 |
* withdraw execution, and are accounted for during withdraw. |
||
| 182 |
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
||
| 183 |
* not having enough shares, etc). |
||
| 184 |
* |
||
| 185 |
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 186 |
* Those methods should be performed separately. |
||
| 187 |
*/ |
||
| 188 |
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
||
| 189 | |||
| 190 |
/** |
||
| 191 |
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
||
| 192 |
* through a redeem call. |
||
| 193 |
* |
||
| 194 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 195 |
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
||
| 196 |
* - MUST NOT revert. |
||
| 197 |
*/ |
||
| 198 |
function maxRedeem(address owner) external view returns (uint256 maxShares); |
||
| 199 | |||
| 200 |
/** |
||
| 201 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, |
||
| 202 |
* given current on-chain conditions. |
||
| 203 |
* |
||
| 204 |
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
||
| 205 |
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
||
| 206 |
* same transaction. |
||
| 207 |
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
||
| 208 |
* redemption would be accepted, regardless if the user has enough shares, etc. |
||
| 209 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 210 |
* - MUST NOT revert. |
||
| 211 |
* |
||
| 212 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
||
| 213 |
* share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
||
| 214 |
*/ |
||
| 215 |
function previewRedeem(uint256 shares) external view returns (uint256 assets); |
||
| 216 | |||
| 217 |
/** |
||
| 218 |
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
||
| 219 |
* |
||
| 220 |
* - MUST emit the Withdraw event. |
||
| 221 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 222 |
* redeem execution, and are accounted for during redeem. |
||
| 223 |
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
||
| 224 |
* not having enough shares, etc). |
||
| 225 |
* |
||
| 226 |
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 227 |
* Those methods should be performed separately. |
||
| 228 |
*/ |
||
| 229 |
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
||
| 230 |
} |
||
| 231 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.4.16; |
||
| 5 | |||
| 6 |
interface IERC5267 {
|
||
| 7 |
/** |
||
| 8 |
* @dev MAY be emitted to signal that the domain could have changed. |
||
| 9 |
*/ |
||
| 10 |
event EIP712DomainChanged(); |
||
| 11 | |||
| 12 |
/** |
||
| 13 |
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 |
||
| 14 |
* signature. |
||
| 15 |
*/ |
||
| 16 |
function eip712Domain() |
||
| 17 |
external |
||
| 18 |
view |
||
| 19 |
returns ( |
||
| 20 |
bytes1 fields, |
||
| 21 |
string memory name, |
||
| 22 |
string memory version, |
||
| 23 |
uint256 chainId, |
||
| 24 |
address verifyingContract, |
||
| 25 |
bytes32 salt, |
||
| 26 |
uint256[] memory extensions |
||
| 27 |
); |
||
| 28 |
} |
||
| 29 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol) |
||
| 3 |
pragma solidity >=0.8.4; |
||
| 4 | |||
| 5 |
/** |
||
| 6 |
* @dev Standard ERC-20 Errors |
||
| 7 |
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens. |
||
| 8 |
*/ |
||
| 9 |
interface IERC20Errors {
|
||
| 10 |
/** |
||
| 11 |
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. |
||
| 12 |
* @param sender Address whose tokens are being transferred. |
||
| 13 |
* @param balance Current balance for the interacting account. |
||
| 14 |
* @param needed Minimum amount required to perform a transfer. |
||
| 15 |
*/ |
||
| 16 |
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); |
||
| 17 | |||
| 18 |
/** |
||
| 19 |
* @dev Indicates a failure with the token `sender`. Used in transfers. |
||
| 20 |
* @param sender Address whose tokens are being transferred. |
||
| 21 |
*/ |
||
| 22 |
error ERC20InvalidSender(address sender); |
||
| 23 | |||
| 24 |
/** |
||
| 25 |
* @dev Indicates a failure with the token `receiver`. Used in transfers. |
||
| 26 |
* @param receiver Address to which tokens are being transferred. |
||
| 27 |
*/ |
||
| 28 |
error ERC20InvalidReceiver(address receiver); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. |
||
| 32 |
* @param spender Address that may be allowed to operate on tokens without being their owner. |
||
| 33 |
* @param allowance Amount of tokens a `spender` is allowed to operate with. |
||
| 34 |
* @param needed Minimum amount required to perform a transfer. |
||
| 35 |
*/ |
||
| 36 |
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); |
||
| 37 | |||
| 38 |
/** |
||
| 39 |
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
||
| 40 |
* @param approver Address initiating an approval operation. |
||
| 41 |
*/ |
||
| 42 |
error ERC20InvalidApprover(address approver); |
||
| 43 | |||
| 44 |
/** |
||
| 45 |
* @dev Indicates a failure with the `spender` to be approved. Used in approvals. |
||
| 46 |
* @param spender Address that may be allowed to operate on tokens without being their owner. |
||
| 47 |
*/ |
||
| 48 |
error ERC20InvalidSpender(address spender); |
||
| 49 |
} |
||
| 50 | |||
| 51 |
/** |
||
| 52 |
* @dev Standard ERC-721 Errors |
||
| 53 |
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens. |
||
| 54 |
*/ |
||
| 55 |
interface IERC721Errors {
|
||
| 56 |
/** |
||
| 57 |
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20. |
||
| 58 |
* Used in balance queries. |
||
| 59 |
* @param owner Address of the current owner of a token. |
||
| 60 |
*/ |
||
| 61 |
error ERC721InvalidOwner(address owner); |
||
| 62 | |||
| 63 |
/** |
||
| 64 |
* @dev Indicates a `tokenId` whose `owner` is the zero address. |
||
| 65 |
* @param tokenId Identifier number of a token. |
||
| 66 |
*/ |
||
| 67 |
error ERC721NonexistentToken(uint256 tokenId); |
||
| 68 | |||
| 69 |
/** |
||
| 70 |
* @dev Indicates an error related to the ownership over a particular token. Used in transfers. |
||
| 71 |
* @param sender Address whose tokens are being transferred. |
||
| 72 |
* @param tokenId Identifier number of a token. |
||
| 73 |
* @param owner Address of the current owner of a token. |
||
| 74 |
*/ |
||
| 75 |
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); |
||
| 76 | |||
| 77 |
/** |
||
| 78 |
* @dev Indicates a failure with the token `sender`. Used in transfers. |
||
| 79 |
* @param sender Address whose tokens are being transferred. |
||
| 80 |
*/ |
||
| 81 |
error ERC721InvalidSender(address sender); |
||
| 82 | |||
| 83 |
/** |
||
| 84 |
* @dev Indicates a failure with the token `receiver`. Used in transfers. |
||
| 85 |
* @param receiver Address to which tokens are being transferred. |
||
| 86 |
*/ |
||
| 87 |
error ERC721InvalidReceiver(address receiver); |
||
| 88 | |||
| 89 |
/** |
||
| 90 |
* @dev Indicates a failure with the `operator`’s approval. Used in transfers. |
||
| 91 |
* @param operator Address that may be allowed to operate on tokens without being their owner. |
||
| 92 |
* @param tokenId Identifier number of a token. |
||
| 93 |
*/ |
||
| 94 |
error ERC721InsufficientApproval(address operator, uint256 tokenId); |
||
| 95 | |||
| 96 |
/** |
||
| 97 |
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
||
| 98 |
* @param approver Address initiating an approval operation. |
||
| 99 |
*/ |
||
| 100 |
error ERC721InvalidApprover(address approver); |
||
| 101 | |||
| 102 |
/** |
||
| 103 |
* @dev Indicates a failure with the `operator` to be approved. Used in approvals. |
||
| 104 |
* @param operator Address that may be allowed to operate on tokens without being their owner. |
||
| 105 |
*/ |
||
| 106 |
error ERC721InvalidOperator(address operator); |
||
| 107 |
} |
||
| 108 | |||
| 109 |
/** |
||
| 110 |
* @dev Standard ERC-1155 Errors |
||
| 111 |
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens. |
||
| 112 |
*/ |
||
| 113 |
interface IERC1155Errors {
|
||
| 114 |
/** |
||
| 115 |
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. |
||
| 116 |
* @param sender Address whose tokens are being transferred. |
||
| 117 |
* @param balance Current balance for the interacting account. |
||
| 118 |
* @param needed Minimum amount required to perform a transfer. |
||
| 119 |
* @param tokenId Identifier number of a token. |
||
| 120 |
*/ |
||
| 121 |
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); |
||
| 122 | |||
| 123 |
/** |
||
| 124 |
* @dev Indicates a failure with the token `sender`. Used in transfers. |
||
| 125 |
* @param sender Address whose tokens are being transferred. |
||
| 126 |
*/ |
||
| 127 |
error ERC1155InvalidSender(address sender); |
||
| 128 | |||
| 129 |
/** |
||
| 130 |
* @dev Indicates a failure with the token `receiver`. Used in transfers. |
||
| 131 |
* @param receiver Address to which tokens are being transferred. |
||
| 132 |
*/ |
||
| 133 |
error ERC1155InvalidReceiver(address receiver); |
||
| 134 | |||
| 135 |
/** |
||
| 136 |
* @dev Indicates a failure with the `operator`’s approval. Used in transfers. |
||
| 137 |
* @param operator Address that may be allowed to operate on tokens without being their owner. |
||
| 138 |
* @param owner Address of the current owner of a token. |
||
| 139 |
*/ |
||
| 140 |
error ERC1155MissingApprovalForAll(address operator, address owner); |
||
| 141 | |||
| 142 |
/** |
||
| 143 |
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. |
||
| 144 |
* @param approver Address initiating an approval operation. |
||
| 145 |
*/ |
||
| 146 |
error ERC1155InvalidApprover(address approver); |
||
| 147 | |||
| 148 |
/** |
||
| 149 |
* @dev Indicates a failure with the `operator` to be approved. Used in approvals. |
||
| 150 |
* @param operator Address that may be allowed to operate on tokens without being their owner. |
||
| 151 |
*/ |
||
| 152 |
error ERC1155InvalidOperator(address operator); |
||
| 153 | |||
| 154 |
/** |
||
| 155 |
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. |
||
| 156 |
* Used in batch transfers. |
||
| 157 |
* @param idsLength Length of the array of token identifiers |
||
| 158 |
* @param valuesLength Length of the array of token amounts |
||
| 159 |
*/ |
||
| 160 |
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); |
||
| 161 |
} |
||
| 162 |
81.8%
lib/v2-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol
Lines covered: 9 / 11 (81.8%)
| 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 |
✓ 22.1K
|
⟲ 22.1K
|
library Clones {
|
| 21 |
error CloneArgumentsTooLong(); |
||
| 22 | |||
| 23 |
/** |
||
| 24 |
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`. |
||
| 25 |
* |
||
| 26 |
* This function uses the create opcode, which should never revert. |
||
| 27 |
*/ |
||
| 28 |
function clone(address implementation) internal returns (address instance) {
|
||
| 29 |
return clone(implementation, 0); |
||
| 30 |
} |
||
| 31 | |||
| 32 |
/** |
||
| 33 |
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
|
||
| 34 |
* to the new contract. |
||
| 35 |
* |
||
| 36 |
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
||
| 37 |
* to always have enough balance for new deployments. Consider exposing this function under a payable method. |
||
| 38 |
*/ |
||
| 39 |
function clone(address implementation, uint256 value) internal returns (address instance) {
|
||
| 40 |
if (address(this).balance < value) {
|
||
| 41 |
revert Errors.InsufficientBalance(address(this).balance, value); |
||
| 42 |
} |
||
| 43 |
assembly ("memory-safe") {
|
||
| 44 |
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes |
||
| 45 |
// of the `implementation` address with the bytecode before the address. |
||
| 46 |
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) |
||
| 47 |
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address. |
||
| 48 |
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) |
||
| 49 |
instance := create(value, 0x09, 0x37) |
||
| 50 |
} |
||
| 51 |
if (instance == address(0)) {
|
||
| 52 |
revert Errors.FailedDeployment(); |
||
| 53 |
} |
||
| 54 |
} |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`. |
||
| 58 |
* |
||
| 59 |
* This function uses the create2 opcode and a `salt` to deterministically deploy |
||
| 60 |
* the clone. Using the same `implementation` and `salt` multiple times will revert, since |
||
| 61 |
* the clones cannot be deployed twice at the same address. |
||
| 62 |
*/ |
||
| 63 |
✓ 684.0K
|
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
|
|
| 64 |
✓ 684.0K
|
return cloneDeterministic(implementation, salt, 0); |
|
| 65 |
} |
||
| 66 | |||
| 67 |
/** |
||
| 68 |
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
|
||
| 69 |
* a `value` parameter to send native currency to the new contract. |
||
| 70 |
* |
||
| 71 |
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
||
| 72 |
* to always have enough balance for new deployments. Consider exposing this function under a payable method. |
||
| 73 |
*/ |
||
| 74 |
function cloneDeterministic( |
||
| 75 |
address implementation, |
||
| 76 |
bytes32 salt, |
||
| 77 |
uint256 value |
||
| 78 |
✓ 684.0K
|
) internal returns (address instance) {
|
|
| 79 |
✓ 684.0K
|
if (address(this).balance < value) {
|
|
| 80 |
revert Errors.InsufficientBalance(address(this).balance, value); |
||
| 81 |
} |
||
| 82 |
assembly ("memory-safe") {
|
||
| 83 |
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes |
||
| 84 |
// of the `implementation` address with the bytecode before the address. |
||
| 85 |
✓ 684.0K
|
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) |
|
| 86 |
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address. |
||
| 87 |
✓ 684.0K
|
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) |
|
| 88 |
✓ 684.0K
|
instance := create2(value, 0x09, 0x37, salt) |
|
| 89 |
} |
||
| 90 |
✓ 684.0K
|
if (instance == address(0)) {
|
|
| 91 |
revert Errors.FailedDeployment(); |
||
| 92 |
} |
||
| 93 |
} |
||
| 94 | |||
| 95 |
/** |
||
| 96 |
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
|
||
| 97 |
*/ |
||
| 98 |
function predictDeterministicAddress( |
||
| 99 |
address implementation, |
||
| 100 |
bytes32 salt, |
||
| 101 |
address deployer |
||
| 102 |
) internal pure returns (address predicted) {
|
||
| 103 |
assembly ("memory-safe") {
|
||
| 104 |
let ptr := mload(0x40) |
||
| 105 |
mstore(add(ptr, 0x38), deployer) |
||
| 106 |
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) |
||
| 107 |
mstore(add(ptr, 0x14), implementation) |
||
| 108 |
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) |
||
| 109 |
mstore(add(ptr, 0x58), salt) |
||
| 110 |
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) |
||
| 111 |
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff) |
||
| 112 |
} |
||
| 113 |
} |
||
| 114 | |||
| 115 |
/** |
||
| 116 |
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
|
||
| 117 |
*/ |
||
| 118 |
function predictDeterministicAddress( |
||
| 119 |
address implementation, |
||
| 120 |
bytes32 salt |
||
| 121 |
) internal view returns (address predicted) {
|
||
| 122 |
return predictDeterministicAddress(implementation, salt, address(this)); |
||
| 123 |
} |
||
| 124 | |||
| 125 |
/** |
||
| 126 |
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom |
||
| 127 |
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To |
||
| 128 |
* access the arguments within the implementation, use {fetchCloneArgs}.
|
||
| 129 |
* |
||
| 130 |
* This function uses the create opcode, which should never revert. |
||
| 131 |
*/ |
||
| 132 |
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
|
||
| 133 |
return cloneWithImmutableArgs(implementation, args, 0); |
||
| 134 |
} |
||
| 135 | |||
| 136 |
/** |
||
| 137 |
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
|
||
| 138 |
* parameter to send native currency to the new contract. |
||
| 139 |
* |
||
| 140 |
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
||
| 141 |
* to always have enough balance for new deployments. Consider exposing this function under a payable method. |
||
| 142 |
*/ |
||
| 143 |
function cloneWithImmutableArgs( |
||
| 144 |
address implementation, |
||
| 145 |
bytes memory args, |
||
| 146 |
uint256 value |
||
| 147 |
) internal returns (address instance) {
|
||
| 148 |
if (address(this).balance < value) {
|
||
| 149 |
revert Errors.InsufficientBalance(address(this).balance, value); |
||
| 150 |
} |
||
| 151 |
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
||
| 152 |
assembly ("memory-safe") {
|
||
| 153 |
instance := create(value, add(bytecode, 0x20), mload(bytecode)) |
||
| 154 |
} |
||
| 155 |
if (instance == address(0)) {
|
||
| 156 |
revert Errors.FailedDeployment(); |
||
| 157 |
} |
||
| 158 |
} |
||
| 159 | |||
| 160 |
/** |
||
| 161 |
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom |
||
| 162 |
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To |
||
| 163 |
* access the arguments within the implementation, use {fetchCloneArgs}.
|
||
| 164 |
* |
||
| 165 |
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same |
||
| 166 |
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice |
||
| 167 |
* at the same address. |
||
| 168 |
*/ |
||
| 169 |
function cloneDeterministicWithImmutableArgs( |
||
| 170 |
address implementation, |
||
| 171 |
bytes memory args, |
||
| 172 |
bytes32 salt |
||
| 173 |
) internal returns (address instance) {
|
||
| 174 |
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0); |
||
| 175 |
} |
||
| 176 | |||
| 177 |
/** |
||
| 178 |
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
|
||
| 179 |
* but with a `value` parameter to send native currency to the new contract. |
||
| 180 |
* |
||
| 181 |
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) |
||
| 182 |
* to always have enough balance for new deployments. Consider exposing this function under a payable method. |
||
| 183 |
*/ |
||
| 184 |
function cloneDeterministicWithImmutableArgs( |
||
| 185 |
address implementation, |
||
| 186 |
bytes memory args, |
||
| 187 |
bytes32 salt, |
||
| 188 |
uint256 value |
||
| 189 |
) internal returns (address instance) {
|
||
| 190 |
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
||
| 191 |
return Create2.deploy(value, salt, bytecode); |
||
| 192 |
} |
||
| 193 | |||
| 194 |
/** |
||
| 195 |
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
|
||
| 196 |
*/ |
||
| 197 |
function predictDeterministicAddressWithImmutableArgs( |
||
| 198 |
address implementation, |
||
| 199 |
bytes memory args, |
||
| 200 |
bytes32 salt, |
||
| 201 |
address deployer |
||
| 202 |
) internal pure returns (address predicted) {
|
||
| 203 |
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); |
||
| 204 |
return Create2.computeAddress(salt, keccak256(bytecode), deployer); |
||
| 205 |
} |
||
| 206 | |||
| 207 |
/** |
||
| 208 |
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
|
||
| 209 |
*/ |
||
| 210 |
function predictDeterministicAddressWithImmutableArgs( |
||
| 211 |
address implementation, |
||
| 212 |
bytes memory args, |
||
| 213 |
bytes32 salt |
||
| 214 |
) internal view returns (address predicted) {
|
||
| 215 |
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this)); |
||
| 216 |
} |
||
| 217 | |||
| 218 |
/** |
||
| 219 |
* @dev Get the immutable args attached to a clone. |
||
| 220 |
* |
||
| 221 |
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this |
||
| 222 |
* function will return an empty array. |
||
| 223 |
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or |
||
| 224 |
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at |
||
| 225 |
* creation. |
||
| 226 |
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This |
||
| 227 |
* function should only be used to check addresses that are known to be clones. |
||
| 228 |
*/ |
||
| 229 |
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
|
||
| 230 |
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short |
||
| 231 |
assembly ("memory-safe") {
|
||
| 232 |
extcodecopy(instance, add(result, 32), 45, mload(result)) |
||
| 233 |
} |
||
| 234 |
return result; |
||
| 235 |
} |
||
| 236 | |||
| 237 |
/** |
||
| 238 |
* @dev Helper that prepares the initcode of the proxy with immutable args. |
||
| 239 |
* |
||
| 240 |
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using |
||
| 241 |
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using |
||
| 242 |
* abi.encodePacked is more expensive but also more portable and easier to review. |
||
| 243 |
* |
||
| 244 |
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes. |
||
| 245 |
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes. |
||
| 246 |
*/ |
||
| 247 |
function _cloneCodeWithImmutableArgs( |
||
| 248 |
address implementation, |
||
| 249 |
bytes memory args |
||
| 250 |
) private pure returns (bytes memory) {
|
||
| 251 |
if (args.length > 24531) revert CloneArgumentsTooLong(); |
||
| 252 |
return |
||
| 253 |
abi.encodePacked( |
||
| 254 |
hex"61", |
||
| 255 |
uint16(args.length + 45), |
||
| 256 |
hex"3d81600a3d39f3363d3d373d3d3d363d73", |
||
| 257 |
implementation, |
||
| 258 |
hex"5af43d82803e903d91602b57fd5bf3", |
||
| 259 |
args |
||
| 260 |
); |
||
| 261 |
} |
||
| 262 |
} |
||
| 263 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.4.16; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Interface of the ERC-20 standard as defined in the ERC. |
||
| 8 |
*/ |
||
| 9 |
interface IERC20 {
|
||
| 10 |
/** |
||
| 11 |
* @dev Emitted when `value` tokens are moved from one account (`from`) to |
||
| 12 |
* another (`to`). |
||
| 13 |
* |
||
| 14 |
* Note that `value` may be zero. |
||
| 15 |
*/ |
||
| 16 |
event Transfer(address indexed from, address indexed to, uint256 value); |
||
| 17 | |||
| 18 |
/** |
||
| 19 |
* @dev Emitted when the allowance of a `spender` for an `owner` is set by |
||
| 20 |
* a call to {approve}. `value` is the new allowance.
|
||
| 21 |
*/ |
||
| 22 |
event Approval(address indexed owner, address indexed spender, uint256 value); |
||
| 23 | |||
| 24 |
/** |
||
| 25 |
* @dev Returns the value of tokens in existence. |
||
| 26 |
*/ |
||
| 27 |
function totalSupply() external view returns (uint256); |
||
| 28 | |||
| 29 |
/** |
||
| 30 |
* @dev Returns the value of tokens owned by `account`. |
||
| 31 |
*/ |
||
| 32 |
function balanceOf(address account) external view returns (uint256); |
||
| 33 | |||
| 34 |
/** |
||
| 35 |
* @dev Moves a `value` amount of tokens from the caller's account to `to`. |
||
| 36 |
* |
||
| 37 |
* Returns a boolean value indicating whether the operation succeeded. |
||
| 38 |
* |
||
| 39 |
* Emits a {Transfer} event.
|
||
| 40 |
*/ |
||
| 41 |
function transfer(address to, uint256 value) external returns (bool); |
||
| 42 | |||
| 43 |
/** |
||
| 44 |
* @dev Returns the remaining number of tokens that `spender` will be |
||
| 45 |
* allowed to spend on behalf of `owner` through {transferFrom}. This is
|
||
| 46 |
* zero by default. |
||
| 47 |
* |
||
| 48 |
* This value changes when {approve} or {transferFrom} are called.
|
||
| 49 |
*/ |
||
| 50 |
function allowance(address owner, address spender) external view returns (uint256); |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the |
||
| 54 |
* caller's tokens. |
||
| 55 |
* |
||
| 56 |
* Returns a boolean value indicating whether the operation succeeded. |
||
| 57 |
* |
||
| 58 |
* IMPORTANT: Beware that changing an allowance with this method brings the risk |
||
| 59 |
* that someone may use both the old and the new allowance by unfortunate |
||
| 60 |
* transaction ordering. One possible solution to mitigate this race |
||
| 61 |
* condition is to first reduce the spender's allowance to 0 and set the |
||
| 62 |
* desired value afterwards: |
||
| 63 |
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 |
||
| 64 |
* |
||
| 65 |
* Emits an {Approval} event.
|
||
| 66 |
*/ |
||
| 67 |
function approve(address spender, uint256 value) external returns (bool); |
||
| 68 | |||
| 69 |
/** |
||
| 70 |
* @dev Moves a `value` amount of tokens from `from` to `to` using the |
||
| 71 |
* allowance mechanism. `value` is then deducted from the caller's |
||
| 72 |
* allowance. |
||
| 73 |
* |
||
| 74 |
* Returns a boolean value indicating whether the operation succeeded. |
||
| 75 |
* |
||
| 76 |
* Emits a {Transfer} event.
|
||
| 77 |
*/ |
||
| 78 |
function transferFrom(address from, address to, uint256 value) external returns (bool); |
||
| 79 |
} |
||
| 80 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.6.2; |
||
| 5 | |||
| 6 |
import {IERC20} from "../IERC20.sol";
|
||
| 7 | |||
| 8 |
/** |
||
| 9 |
* @dev Interface for the optional metadata functions from the ERC-20 standard. |
||
| 10 |
*/ |
||
| 11 |
interface IERC20Metadata is IERC20 {
|
||
| 12 |
/** |
||
| 13 |
* @dev Returns the name of the token. |
||
| 14 |
*/ |
||
| 15 |
function name() external view returns (string memory); |
||
| 16 | |||
| 17 |
/** |
||
| 18 |
* @dev Returns the symbol of the token. |
||
| 19 |
*/ |
||
| 20 |
function symbol() external view returns (string memory); |
||
| 21 | |||
| 22 |
/** |
||
| 23 |
* @dev Returns the decimals places of the token. |
||
| 24 |
*/ |
||
| 25 |
function decimals() external view returns (uint8); |
||
| 26 |
} |
||
| 27 |
92.9%
lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
Lines covered: 13 / 14 (92.9%)
| 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 |
✓ 21.5K
|
⟲ 21.5K
|
library SafeERC20 {
|
| 19 |
/** |
||
| 20 |
* @dev An operation with an ERC-20 token failed. |
||
| 21 |
*/ |
||
| 22 |
error SafeERC20FailedOperation(address token); |
||
| 23 | |||
| 24 |
/** |
||
| 25 |
* @dev Indicates a failed `decreaseAllowance` request. |
||
| 26 |
*/ |
||
| 27 |
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); |
||
| 28 | |||
| 29 |
/** |
||
| 30 |
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, |
||
| 31 |
* non-reverting calls are assumed to be successful. |
||
| 32 |
*/ |
||
| 33 |
function safeTransfer(IERC20 token, address to, uint256 value) internal {
|
||
| 34 |
✓ 387.4K
|
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); |
|
| 35 |
} |
||
| 36 | |||
| 37 |
/** |
||
| 38 |
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the |
||
| 39 |
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. |
||
| 40 |
*/ |
||
| 41 |
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
|
||
| 42 |
✓ 1.2M
|
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); |
|
| 43 |
} |
||
| 44 | |||
| 45 |
/** |
||
| 46 |
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
|
||
| 47 |
*/ |
||
| 48 |
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
|
||
| 49 |
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value))); |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
|
||
| 54 |
*/ |
||
| 55 |
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
|
||
| 56 |
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value))); |
||
| 57 |
} |
||
| 58 | |||
| 59 |
/** |
||
| 60 |
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, |
||
| 61 |
* non-reverting calls are assumed to be successful. |
||
| 62 |
* |
||
| 63 |
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" |
||
| 64 |
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using |
||
| 65 |
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
|
||
| 66 |
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. |
||
| 67 |
*/ |
||
| 68 |
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
|
||
| 69 |
uint256 oldAllowance = token.allowance(address(this), spender); |
||
| 70 |
forceApprove(token, spender, oldAllowance + value); |
||
| 71 |
} |
||
| 72 | |||
| 73 |
/** |
||
| 74 |
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no |
||
| 75 |
* value, non-reverting calls are assumed to be successful. |
||
| 76 |
* |
||
| 77 |
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" |
||
| 78 |
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using |
||
| 79 |
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
|
||
| 80 |
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. |
||
| 81 |
*/ |
||
| 82 |
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
|
||
| 83 |
unchecked {
|
||
| 84 |
uint256 currentAllowance = token.allowance(address(this), spender); |
||
| 85 |
if (currentAllowance < requestedDecrease) {
|
||
| 86 |
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); |
||
| 87 |
} |
||
| 88 |
forceApprove(token, spender, currentAllowance - requestedDecrease); |
||
| 89 |
} |
||
| 90 |
} |
||
| 91 | |||
| 92 |
/** |
||
| 93 |
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, |
||
| 94 |
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval |
||
| 95 |
* to be set to zero before setting it to a non-zero value, such as USDT. |
||
| 96 |
* |
||
| 97 |
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function |
||
| 98 |
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being |
||
| 99 |
* set here. |
||
| 100 |
*/ |
||
| 101 |
function forceApprove(IERC20 token, address spender, uint256 value) internal {
|
||
| 102 |
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); |
||
| 103 | |||
| 104 |
if (!_callOptionalReturnBool(token, approvalCall)) {
|
||
| 105 |
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); |
||
| 106 |
_callOptionalReturn(token, approvalCall); |
||
| 107 |
} |
||
| 108 |
} |
||
| 109 | |||
| 110 |
/** |
||
| 111 |
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
|
||
| 112 |
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||
| 113 |
* targeting contracts. |
||
| 114 |
* |
||
| 115 |
* Reverts if the returned value is other than `true`. |
||
| 116 |
*/ |
||
| 117 |
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
|
||
| 118 |
if (to.code.length == 0) {
|
||
| 119 |
safeTransfer(token, to, value); |
||
| 120 |
} else if (!token.transferAndCall(to, value, data)) {
|
||
| 121 |
revert SafeERC20FailedOperation(address(token)); |
||
| 122 |
} |
||
| 123 |
} |
||
| 124 | |||
| 125 |
/** |
||
| 126 |
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
|
||
| 127 |
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||
| 128 |
* targeting contracts. |
||
| 129 |
* |
||
| 130 |
* Reverts if the returned value is other than `true`. |
||
| 131 |
*/ |
||
| 132 |
function transferFromAndCallRelaxed( |
||
| 133 |
IERC1363 token, |
||
| 134 |
address from, |
||
| 135 |
address to, |
||
| 136 |
uint256 value, |
||
| 137 |
bytes memory data |
||
| 138 |
) internal {
|
||
| 139 |
if (to.code.length == 0) {
|
||
| 140 |
safeTransferFrom(token, from, to, value); |
||
| 141 |
} else if (!token.transferFromAndCall(from, to, value, data)) {
|
||
| 142 |
revert SafeERC20FailedOperation(address(token)); |
||
| 143 |
} |
||
| 144 |
} |
||
| 145 | |||
| 146 |
/** |
||
| 147 |
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
|
||
| 148 |
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||
| 149 |
* targeting contracts. |
||
| 150 |
* |
||
| 151 |
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
|
||
| 152 |
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
|
||
| 153 |
* once without retrying, and relies on the returned value to be true. |
||
| 154 |
* |
||
| 155 |
* Reverts if the returned value is other than `true`. |
||
| 156 |
*/ |
||
| 157 |
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
|
||
| 158 |
if (to.code.length == 0) {
|
||
| 159 |
forceApprove(token, to, value); |
||
| 160 |
} else if (!token.approveAndCall(to, value, data)) {
|
||
| 161 |
revert SafeERC20FailedOperation(address(token)); |
||
| 162 |
} |
||
| 163 |
} |
||
| 164 | |||
| 165 |
/** |
||
| 166 |
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement |
||
| 167 |
* on the return value: the return value is optional (but if data is returned, it must not be false). |
||
| 168 |
* @param token The token targeted by the call. |
||
| 169 |
* @param data The call data (encoded using abi.encode or one of its variants). |
||
| 170 |
* |
||
| 171 |
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
|
||
| 172 |
*/ |
||
| 173 |
function _callOptionalReturn(IERC20 token, bytes memory data) private {
|
||
| 174 |
✓ 1.2M
|
uint256 returnSize; |
|
| 175 |
✓ 1.2M
|
uint256 returnValue; |
|
| 176 |
assembly ("memory-safe") {
|
||
| 177 |
✓ 1.2M
|
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) |
|
| 178 |
// bubble errors |
||
| 179 |
✓ 1.2M
|
if iszero(success) {
|
|
| 180 |
✓ 819.9K
|
let ptr := mload(0x40) |
|
| 181 |
✓ 819.9K
|
returndatacopy(ptr, 0, returndatasize()) |
|
| 182 |
✓ 819.9K
|
revert(ptr, returndatasize()) |
|
| 183 |
} |
||
| 184 |
✓ 387.4K
|
returnSize := returndatasize() |
|
| 185 |
✓ 387.4K
|
returnValue := mload(0) |
|
| 186 |
} |
||
| 187 | |||
| 188 |
✓ 387.4K
|
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
|
|
| 189 |
revert SafeERC20FailedOperation(address(token)); |
||
| 190 |
} |
||
| 191 |
} |
||
| 192 | |||
| 193 |
/** |
||
| 194 |
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement |
||
| 195 |
* on the return value: the return value is optional (but if data is returned, it must not be false). |
||
| 196 |
* @param token The token targeted by the call. |
||
| 197 |
* @param data The call data (encoded using abi.encode or one of its variants). |
||
| 198 |
* |
||
| 199 |
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
|
||
| 200 |
*/ |
||
| 201 |
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
|
||
| 202 |
bool success; |
||
| 203 |
uint256 returnSize; |
||
| 204 |
uint256 returnValue; |
||
| 205 |
assembly ("memory-safe") {
|
||
| 206 |
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) |
||
| 207 |
returnSize := returndatasize() |
||
| 208 |
returnValue := mload(0) |
||
| 209 |
} |
||
| 210 |
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1); |
||
| 211 |
} |
||
| 212 |
} |
||
| 213 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Arrays.sol) |
||
| 3 |
// This file was procedurally generated from scripts/generate/templates/Arrays.js. |
||
| 4 | |||
| 5 |
pragma solidity ^0.8.20; |
||
| 6 | |||
| 7 |
import {Comparators} from "./Comparators.sol";
|
||
| 8 |
import {SlotDerivation} from "./SlotDerivation.sol";
|
||
| 9 |
import {StorageSlot} from "./StorageSlot.sol";
|
||
| 10 |
import {Math} from "./math/Math.sol";
|
||
| 11 | |||
| 12 |
/** |
||
| 13 |
* @dev Collection of functions related to array types. |
||
| 14 |
*/ |
||
| 15 |
✓ 19.5K
|
⟲ 19.5K
|
library Arrays {
|
| 16 |
using SlotDerivation for bytes32; |
||
| 17 |
using StorageSlot for bytes32; |
||
| 18 | |||
| 19 |
/** |
||
| 20 |
* @dev Sort an array of uint256 (in memory) following the provided comparator function. |
||
| 21 |
* |
||
| 22 |
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
||
| 23 |
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
||
| 24 |
* |
||
| 25 |
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
||
| 26 |
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
||
| 27 |
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
||
| 28 |
* consume more gas than is available in a block, leading to potential DoS. |
||
| 29 |
* |
||
| 30 |
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
||
| 31 |
*/ |
||
| 32 |
function sort( |
||
| 33 |
uint256[] memory array, |
||
| 34 |
function(uint256, uint256) pure returns (bool) comp |
||
| 35 |
) internal pure returns (uint256[] memory) {
|
||
| 36 |
_quickSort(_begin(array), _end(array), comp); |
||
| 37 |
return array; |
||
| 38 |
} |
||
| 39 | |||
| 40 |
/** |
||
| 41 |
* @dev Variant of {sort} that sorts an array of uint256 in increasing order.
|
||
| 42 |
*/ |
||
| 43 |
function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
|
||
| 44 |
sort(array, Comparators.lt); |
||
| 45 |
return array; |
||
| 46 |
} |
||
| 47 | |||
| 48 |
/** |
||
| 49 |
* @dev Sort an array of address (in memory) following the provided comparator function. |
||
| 50 |
* |
||
| 51 |
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
||
| 52 |
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
||
| 53 |
* |
||
| 54 |
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
||
| 55 |
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
||
| 56 |
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
||
| 57 |
* consume more gas than is available in a block, leading to potential DoS. |
||
| 58 |
* |
||
| 59 |
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
||
| 60 |
*/ |
||
| 61 |
function sort( |
||
| 62 |
address[] memory array, |
||
| 63 |
function(address, address) pure returns (bool) comp |
||
| 64 |
) internal pure returns (address[] memory) {
|
||
| 65 |
sort(_castToUint256Array(array), _castToUint256Comp(comp)); |
||
| 66 |
return array; |
||
| 67 |
} |
||
| 68 | |||
| 69 |
/** |
||
| 70 |
* @dev Variant of {sort} that sorts an array of address in increasing order.
|
||
| 71 |
*/ |
||
| 72 |
function sort(address[] memory array) internal pure returns (address[] memory) {
|
||
| 73 |
sort(_castToUint256Array(array), Comparators.lt); |
||
| 74 |
return array; |
||
| 75 |
} |
||
| 76 | |||
| 77 |
/** |
||
| 78 |
* @dev Sort an array of bytes32 (in memory) following the provided comparator function. |
||
| 79 |
* |
||
| 80 |
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for |
||
| 81 |
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. |
||
| 82 |
* |
||
| 83 |
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the |
||
| 84 |
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful |
||
| 85 |
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may |
||
| 86 |
* consume more gas than is available in a block, leading to potential DoS. |
||
| 87 |
* |
||
| 88 |
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. |
||
| 89 |
*/ |
||
| 90 |
function sort( |
||
| 91 |
bytes32[] memory array, |
||
| 92 |
function(bytes32, bytes32) pure returns (bool) comp |
||
| 93 |
) internal pure returns (bytes32[] memory) {
|
||
| 94 |
sort(_castToUint256Array(array), _castToUint256Comp(comp)); |
||
| 95 |
return array; |
||
| 96 |
} |
||
| 97 | |||
| 98 |
/** |
||
| 99 |
* @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
|
||
| 100 |
*/ |
||
| 101 |
function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
|
||
| 102 |
sort(_castToUint256Array(array), Comparators.lt); |
||
| 103 |
return array; |
||
| 104 |
} |
||
| 105 | |||
| 106 |
/** |
||
| 107 |
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops |
||
| 108 |
* at end (exclusive). Sorting follows the `comp` comparator. |
||
| 109 |
* |
||
| 110 |
* Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls.
|
||
| 111 |
* |
||
| 112 |
* IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should |
||
| 113 |
* be used only if the limits are within a memory array. |
||
| 114 |
*/ |
||
| 115 |
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
|
||
| 116 |
unchecked {
|
||
| 117 |
if (end - begin < 0x40) return; |
||
| 118 | |||
| 119 |
// Use first element as pivot |
||
| 120 |
uint256 pivot = _mload(begin); |
||
| 121 |
// Position where the pivot should be at the end of the loop |
||
| 122 |
uint256 pos = begin; |
||
| 123 | |||
| 124 |
for (uint256 it = begin + 0x20; it < end; it += 0x20) {
|
||
| 125 |
if (comp(_mload(it), pivot)) {
|
||
| 126 |
// If the value stored at the iterator's position comes before the pivot, we increment the |
||
| 127 |
// position of the pivot and move the value there. |
||
| 128 |
pos += 0x20; |
||
| 129 |
_swap(pos, it); |
||
| 130 |
} |
||
| 131 |
} |
||
| 132 | |||
| 133 |
_swap(begin, pos); // Swap pivot into place |
||
| 134 |
_quickSort(begin, pos, comp); // Sort the left side of the pivot |
||
| 135 |
_quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot |
||
| 136 |
} |
||
| 137 |
} |
||
| 138 | |||
| 139 |
/** |
||
| 140 |
* @dev Pointer to the memory location of the first element of `array`. |
||
| 141 |
*/ |
||
| 142 |
function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
|
||
| 143 |
assembly ("memory-safe") {
|
||
| 144 |
ptr := add(array, 0x20) |
||
| 145 |
} |
||
| 146 |
} |
||
| 147 | |||
| 148 |
/** |
||
| 149 |
* @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word |
||
| 150 |
* that comes just after the last element of the array. |
||
| 151 |
*/ |
||
| 152 |
function _end(uint256[] memory array) private pure returns (uint256 ptr) {
|
||
| 153 |
unchecked {
|
||
| 154 |
return _begin(array) + array.length * 0x20; |
||
| 155 |
} |
||
| 156 |
} |
||
| 157 | |||
| 158 |
/** |
||
| 159 |
* @dev Load memory word (as a uint256) at location `ptr`. |
||
| 160 |
*/ |
||
| 161 |
function _mload(uint256 ptr) private pure returns (uint256 value) {
|
||
| 162 |
assembly {
|
||
| 163 |
value := mload(ptr) |
||
| 164 |
} |
||
| 165 |
} |
||
| 166 | |||
| 167 |
/** |
||
| 168 |
* @dev Swaps the elements memory location `ptr1` and `ptr2`. |
||
| 169 |
*/ |
||
| 170 |
function _swap(uint256 ptr1, uint256 ptr2) private pure {
|
||
| 171 |
assembly {
|
||
| 172 |
let value1 := mload(ptr1) |
||
| 173 |
let value2 := mload(ptr2) |
||
| 174 |
mstore(ptr1, value2) |
||
| 175 |
mstore(ptr2, value1) |
||
| 176 |
} |
||
| 177 |
} |
||
| 178 | |||
| 179 |
/// @dev Helper: low level cast address memory array to uint256 memory array |
||
| 180 |
function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) {
|
||
| 181 |
assembly {
|
||
| 182 |
output := input |
||
| 183 |
} |
||
| 184 |
} |
||
| 185 | |||
| 186 |
/// @dev Helper: low level cast bytes32 memory array to uint256 memory array |
||
| 187 |
function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) {
|
||
| 188 |
assembly {
|
||
| 189 |
output := input |
||
| 190 |
} |
||
| 191 |
} |
||
| 192 | |||
| 193 |
/// @dev Helper: low level cast address comp function to uint256 comp function |
||
| 194 |
function _castToUint256Comp( |
||
| 195 |
function(address, address) pure returns (bool) input |
||
| 196 |
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
|
||
| 197 |
assembly {
|
||
| 198 |
output := input |
||
| 199 |
} |
||
| 200 |
} |
||
| 201 | |||
| 202 |
/// @dev Helper: low level cast bytes32 comp function to uint256 comp function |
||
| 203 |
function _castToUint256Comp( |
||
| 204 |
function(bytes32, bytes32) pure returns (bool) input |
||
| 205 |
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
|
||
| 206 |
assembly {
|
||
| 207 |
output := input |
||
| 208 |
} |
||
| 209 |
} |
||
| 210 | |||
| 211 |
/** |
||
| 212 |
* @dev Searches a sorted `array` and returns the first index that contains |
||
| 213 |
* a value greater or equal to `element`. If no such index exists (i.e. all |
||
| 214 |
* values in the array are strictly less than `element`), the array length is |
||
| 215 |
* returned. Time complexity O(log n). |
||
| 216 |
* |
||
| 217 |
* NOTE: The `array` is expected to be sorted in ascending order, and to |
||
| 218 |
* contain no repeated elements. |
||
| 219 |
* |
||
| 220 |
* IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
|
||
| 221 |
* support for repeated elements in the array. The {lowerBound} function should
|
||
| 222 |
* be used instead. |
||
| 223 |
*/ |
||
| 224 |
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
|
||
| 225 |
uint256 low = 0; |
||
| 226 |
uint256 high = array.length; |
||
| 227 | |||
| 228 |
if (high == 0) {
|
||
| 229 |
return 0; |
||
| 230 |
} |
||
| 231 | |||
| 232 |
while (low < high) {
|
||
| 233 |
uint256 mid = Math.average(low, high); |
||
| 234 | |||
| 235 |
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
||
| 236 |
// because Math.average rounds towards zero (it does integer division with truncation). |
||
| 237 |
if (unsafeAccess(array, mid).value > element) {
|
||
| 238 |
high = mid; |
||
| 239 |
} else {
|
||
| 240 |
low = mid + 1; |
||
| 241 |
} |
||
| 242 |
} |
||
| 243 | |||
| 244 |
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. |
||
| 245 |
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
|
||
| 246 |
return low - 1; |
||
| 247 |
} else {
|
||
| 248 |
return low; |
||
| 249 |
} |
||
| 250 |
} |
||
| 251 | |||
| 252 |
/** |
||
| 253 |
* @dev Searches an `array` sorted in ascending order and returns the first |
||
| 254 |
* index that contains a value greater or equal than `element`. If no such index |
||
| 255 |
* exists (i.e. all values in the array are strictly less than `element`), the array |
||
| 256 |
* length is returned. Time complexity O(log n). |
||
| 257 |
* |
||
| 258 |
* See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound]. |
||
| 259 |
*/ |
||
| 260 |
function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
|
||
| 261 |
uint256 low = 0; |
||
| 262 |
uint256 high = array.length; |
||
| 263 | |||
| 264 |
if (high == 0) {
|
||
| 265 |
return 0; |
||
| 266 |
} |
||
| 267 | |||
| 268 |
while (low < high) {
|
||
| 269 |
uint256 mid = Math.average(low, high); |
||
| 270 | |||
| 271 |
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
||
| 272 |
// because Math.average rounds towards zero (it does integer division with truncation). |
||
| 273 |
if (unsafeAccess(array, mid).value < element) {
|
||
| 274 |
// this cannot overflow because mid < high |
||
| 275 |
unchecked {
|
||
| 276 |
low = mid + 1; |
||
| 277 |
} |
||
| 278 |
} else {
|
||
| 279 |
high = mid; |
||
| 280 |
} |
||
| 281 |
} |
||
| 282 | |||
| 283 |
return low; |
||
| 284 |
} |
||
| 285 | |||
| 286 |
/** |
||
| 287 |
* @dev Searches an `array` sorted in ascending order and returns the first |
||
| 288 |
* index that contains a value strictly greater than `element`. If no such index |
||
| 289 |
* exists (i.e. all values in the array are strictly less than `element`), the array |
||
| 290 |
* length is returned. Time complexity O(log n). |
||
| 291 |
* |
||
| 292 |
* See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound]. |
||
| 293 |
*/ |
||
| 294 |
function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
|
||
| 295 |
uint256 low = 0; |
||
| 296 |
uint256 high = array.length; |
||
| 297 | |||
| 298 |
if (high == 0) {
|
||
| 299 |
return 0; |
||
| 300 |
} |
||
| 301 | |||
| 302 |
while (low < high) {
|
||
| 303 |
uint256 mid = Math.average(low, high); |
||
| 304 | |||
| 305 |
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
||
| 306 |
// because Math.average rounds towards zero (it does integer division with truncation). |
||
| 307 |
if (unsafeAccess(array, mid).value > element) {
|
||
| 308 |
high = mid; |
||
| 309 |
} else {
|
||
| 310 |
// this cannot overflow because mid < high |
||
| 311 |
unchecked {
|
||
| 312 |
low = mid + 1; |
||
| 313 |
} |
||
| 314 |
} |
||
| 315 |
} |
||
| 316 | |||
| 317 |
return low; |
||
| 318 |
} |
||
| 319 | |||
| 320 |
/** |
||
| 321 |
* @dev Same as {lowerBound}, but with an array in memory.
|
||
| 322 |
*/ |
||
| 323 |
function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
|
||
| 324 |
uint256 low = 0; |
||
| 325 |
uint256 high = array.length; |
||
| 326 | |||
| 327 |
if (high == 0) {
|
||
| 328 |
return 0; |
||
| 329 |
} |
||
| 330 | |||
| 331 |
while (low < high) {
|
||
| 332 |
uint256 mid = Math.average(low, high); |
||
| 333 | |||
| 334 |
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
||
| 335 |
// because Math.average rounds towards zero (it does integer division with truncation). |
||
| 336 |
if (unsafeMemoryAccess(array, mid) < element) {
|
||
| 337 |
// this cannot overflow because mid < high |
||
| 338 |
unchecked {
|
||
| 339 |
low = mid + 1; |
||
| 340 |
} |
||
| 341 |
} else {
|
||
| 342 |
high = mid; |
||
| 343 |
} |
||
| 344 |
} |
||
| 345 | |||
| 346 |
return low; |
||
| 347 |
} |
||
| 348 | |||
| 349 |
/** |
||
| 350 |
* @dev Same as {upperBound}, but with an array in memory.
|
||
| 351 |
*/ |
||
| 352 |
function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
|
||
| 353 |
uint256 low = 0; |
||
| 354 |
uint256 high = array.length; |
||
| 355 | |||
| 356 |
if (high == 0) {
|
||
| 357 |
return 0; |
||
| 358 |
} |
||
| 359 | |||
| 360 |
while (low < high) {
|
||
| 361 |
uint256 mid = Math.average(low, high); |
||
| 362 | |||
| 363 |
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) |
||
| 364 |
// because Math.average rounds towards zero (it does integer division with truncation). |
||
| 365 |
if (unsafeMemoryAccess(array, mid) > element) {
|
||
| 366 |
high = mid; |
||
| 367 |
} else {
|
||
| 368 |
// this cannot overflow because mid < high |
||
| 369 |
unchecked {
|
||
| 370 |
low = mid + 1; |
||
| 371 |
} |
||
| 372 |
} |
||
| 373 |
} |
||
| 374 | |||
| 375 |
return low; |
||
| 376 |
} |
||
| 377 | |||
| 378 |
/** |
||
| 379 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 380 |
* |
||
| 381 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 382 |
*/ |
||
| 383 |
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
|
||
| 384 |
bytes32 slot; |
||
| 385 |
assembly ("memory-safe") {
|
||
| 386 |
slot := arr.slot |
||
| 387 |
} |
||
| 388 |
return slot.deriveArray().offset(pos).getAddressSlot(); |
||
| 389 |
} |
||
| 390 | |||
| 391 |
/** |
||
| 392 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 393 |
* |
||
| 394 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 395 |
*/ |
||
| 396 |
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
|
||
| 397 |
bytes32 slot; |
||
| 398 |
assembly ("memory-safe") {
|
||
| 399 |
slot := arr.slot |
||
| 400 |
} |
||
| 401 |
return slot.deriveArray().offset(pos).getBytes32Slot(); |
||
| 402 |
} |
||
| 403 | |||
| 404 |
/** |
||
| 405 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 406 |
* |
||
| 407 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 408 |
*/ |
||
| 409 |
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
|
||
| 410 |
bytes32 slot; |
||
| 411 |
assembly ("memory-safe") {
|
||
| 412 |
slot := arr.slot |
||
| 413 |
} |
||
| 414 |
return slot.deriveArray().offset(pos).getUint256Slot(); |
||
| 415 |
} |
||
| 416 | |||
| 417 |
/** |
||
| 418 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 419 |
* |
||
| 420 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 421 |
*/ |
||
| 422 |
function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) {
|
||
| 423 |
bytes32 slot; |
||
| 424 |
assembly ("memory-safe") {
|
||
| 425 |
slot := arr.slot |
||
| 426 |
} |
||
| 427 |
return slot.deriveArray().offset(pos).getBytesSlot(); |
||
| 428 |
} |
||
| 429 | |||
| 430 |
/** |
||
| 431 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 432 |
* |
||
| 433 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 434 |
*/ |
||
| 435 |
function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) {
|
||
| 436 |
bytes32 slot; |
||
| 437 |
assembly ("memory-safe") {
|
||
| 438 |
slot := arr.slot |
||
| 439 |
} |
||
| 440 |
return slot.deriveArray().offset(pos).getStringSlot(); |
||
| 441 |
} |
||
| 442 | |||
| 443 |
/** |
||
| 444 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 445 |
* |
||
| 446 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 447 |
*/ |
||
| 448 |
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
|
||
| 449 |
assembly {
|
||
| 450 |
res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
||
| 451 |
} |
||
| 452 |
} |
||
| 453 | |||
| 454 |
/** |
||
| 455 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 456 |
* |
||
| 457 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 458 |
*/ |
||
| 459 |
function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) {
|
||
| 460 |
assembly {
|
||
| 461 |
res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
||
| 462 |
} |
||
| 463 |
} |
||
| 464 | |||
| 465 |
/** |
||
| 466 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 467 |
* |
||
| 468 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 469 |
*/ |
||
| 470 |
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
|
||
| 471 |
assembly {
|
||
| 472 |
res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
||
| 473 |
} |
||
| 474 |
} |
||
| 475 | |||
| 476 |
/** |
||
| 477 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 478 |
* |
||
| 479 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 480 |
*/ |
||
| 481 |
function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) {
|
||
| 482 |
assembly {
|
||
| 483 |
res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
||
| 484 |
} |
||
| 485 |
} |
||
| 486 | |||
| 487 |
/** |
||
| 488 |
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. |
||
| 489 |
* |
||
| 490 |
* WARNING: Only use if you are certain `pos` is lower than the array length. |
||
| 491 |
*/ |
||
| 492 |
function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) {
|
||
| 493 |
assembly {
|
||
| 494 |
res := mload(add(add(arr, 0x20), mul(pos, 0x20))) |
||
| 495 |
} |
||
| 496 |
} |
||
| 497 | |||
| 498 |
/** |
||
| 499 |
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
||
| 500 |
* |
||
| 501 |
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
||
| 502 |
*/ |
||
| 503 |
function unsafeSetLength(address[] storage array, uint256 len) internal {
|
||
| 504 |
assembly ("memory-safe") {
|
||
| 505 |
sstore(array.slot, len) |
||
| 506 |
} |
||
| 507 |
} |
||
| 508 | |||
| 509 |
/** |
||
| 510 |
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
||
| 511 |
* |
||
| 512 |
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
||
| 513 |
*/ |
||
| 514 |
function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
|
||
| 515 |
assembly ("memory-safe") {
|
||
| 516 |
sstore(array.slot, len) |
||
| 517 |
} |
||
| 518 |
} |
||
| 519 | |||
| 520 |
/** |
||
| 521 |
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
||
| 522 |
* |
||
| 523 |
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
||
| 524 |
*/ |
||
| 525 |
function unsafeSetLength(uint256[] storage array, uint256 len) internal {
|
||
| 526 |
assembly ("memory-safe") {
|
||
| 527 |
sstore(array.slot, len) |
||
| 528 |
} |
||
| 529 |
} |
||
| 530 | |||
| 531 |
/** |
||
| 532 |
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
||
| 533 |
* |
||
| 534 |
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
||
| 535 |
*/ |
||
| 536 |
function unsafeSetLength(bytes[] storage array, uint256 len) internal {
|
||
| 537 |
assembly ("memory-safe") {
|
||
| 538 |
sstore(array.slot, len) |
||
| 539 |
} |
||
| 540 |
} |
||
| 541 | |||
| 542 |
/** |
||
| 543 |
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. |
||
| 544 |
* |
||
| 545 |
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. |
||
| 546 |
*/ |
||
| 547 |
function unsafeSetLength(string[] storage array, uint256 len) internal {
|
||
| 548 |
assembly ("memory-safe") {
|
||
| 549 |
sstore(array.slot, len) |
||
| 550 |
} |
||
| 551 |
} |
||
| 552 |
} |
||
| 553 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Comparators.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Provides a set of functions to compare values. |
||
| 8 |
* |
||
| 9 |
* _Available since v5.1._ |
||
| 10 |
*/ |
||
| 11 |
✓ 18.2K
|
⟲ 18.2K
|
library Comparators {
|
| 12 |
function lt(uint256 a, uint256 b) internal pure returns (bool) {
|
||
| 13 |
return a < b; |
||
| 14 |
} |
||
| 15 | |||
| 16 |
function gt(uint256 a, uint256 b) internal pure returns (bool) {
|
||
| 17 |
return a > b; |
||
| 18 |
} |
||
| 19 |
} |
||
| 20 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Context.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Provides information about the current execution context, including the |
||
| 8 |
* sender of the transaction and its data. While these are generally available |
||
| 9 |
* via msg.sender and msg.data, they should not be accessed in such a direct |
||
| 10 |
* manner, since when dealing with meta-transactions the account sending and |
||
| 11 |
* paying for execution may not be the actual sender (as far as an application |
||
| 12 |
* is concerned). |
||
| 13 |
* |
||
| 14 |
* This contract is only required for intermediate, library-like contracts. |
||
| 15 |
*/ |
||
| 16 |
abstract contract Context {
|
||
| 17 |
function _msgSender() internal view virtual returns (address) {
|
||
| 18 |
✓ 1.2M
|
return msg.sender; |
|
| 19 |
} |
||
| 20 | |||
| 21 |
function _msgData() internal view virtual returns (bytes calldata) {
|
||
| 22 |
return msg.data; |
||
| 23 |
} |
||
| 24 | |||
| 25 |
function _contextSuffixLength() internal view virtual returns (uint256) {
|
||
| 26 |
return 0; |
||
| 27 |
} |
||
| 28 |
} |
||
| 29 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {Errors} from "./Errors.sol";
|
||
| 7 | |||
| 8 |
/** |
||
| 9 |
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. |
||
| 10 |
* `CREATE2` can be used to compute in advance the address where a smart |
||
| 11 |
* contract will be deployed, which allows for interesting new mechanisms known |
||
| 12 |
* as 'counterfactual interactions'. |
||
| 13 |
* |
||
| 14 |
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more |
||
| 15 |
* information. |
||
| 16 |
*/ |
||
| 17 |
✓ 33.8K
|
⟲ 33.8K
|
library Create2 {
|
| 18 |
/** |
||
| 19 |
* @dev There's no code to deploy. |
||
| 20 |
*/ |
||
| 21 |
error Create2EmptyBytecode(); |
||
| 22 | |||
| 23 |
/** |
||
| 24 |
* @dev Deploys a contract using `CREATE2`. The address where the contract |
||
| 25 |
* will be deployed can be known in advance via {computeAddress}.
|
||
| 26 |
* |
||
| 27 |
* The bytecode for a contract can be obtained from Solidity with |
||
| 28 |
* `type(contractName).creationCode`. |
||
| 29 |
* |
||
| 30 |
* Requirements: |
||
| 31 |
* |
||
| 32 |
* - `bytecode` must not be empty. |
||
| 33 |
* - `salt` must have not been used for `bytecode` already. |
||
| 34 |
* - the factory must have a balance of at least `amount`. |
||
| 35 |
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor. |
||
| 36 |
*/ |
||
| 37 |
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
|
||
| 38 |
if (address(this).balance < amount) {
|
||
| 39 |
revert Errors.InsufficientBalance(address(this).balance, amount); |
||
| 40 |
} |
||
| 41 |
if (bytecode.length == 0) {
|
||
| 42 |
revert Create2EmptyBytecode(); |
||
| 43 |
} |
||
| 44 |
assembly ("memory-safe") {
|
||
| 45 |
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) |
||
| 46 |
// if no address was created, and returndata is not empty, bubble revert |
||
| 47 |
if and(iszero(addr), not(iszero(returndatasize()))) {
|
||
| 48 |
let p := mload(0x40) |
||
| 49 |
returndatacopy(p, 0, returndatasize()) |
||
| 50 |
revert(p, returndatasize()) |
||
| 51 |
} |
||
| 52 |
} |
||
| 53 |
if (addr == address(0)) {
|
||
| 54 |
revert Errors.FailedDeployment(); |
||
| 55 |
} |
||
| 56 |
} |
||
| 57 | |||
| 58 |
/** |
||
| 59 |
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
|
||
| 60 |
* `bytecodeHash` or `salt` will result in a new destination address. |
||
| 61 |
*/ |
||
| 62 |
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
|
||
| 63 |
return computeAddress(salt, bytecodeHash, address(this)); |
||
| 64 |
} |
||
| 65 | |||
| 66 |
/** |
||
| 67 |
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
|
||
| 68 |
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
|
||
| 69 |
*/ |
||
| 70 |
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
|
||
| 71 |
assembly ("memory-safe") {
|
||
| 72 |
let ptr := mload(0x40) // Get free memory pointer |
||
| 73 | |||
| 74 |
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | |
||
| 75 |
// |-------------------|---------------------------------------------------------------------------| |
||
| 76 |
// | bytecodeHash | CCCCCCCCCCCCC...CC | |
||
| 77 |
// | salt | BBBBBBBBBBBBB...BB | |
||
| 78 |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA | |
||
| 79 |
// | 0xFF | FF | |
||
| 80 |
// |-------------------|---------------------------------------------------------------------------| |
||
| 81 |
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC | |
||
| 82 |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ | |
||
| 83 | |||
| 84 |
mstore(add(ptr, 0x40), bytecodeHash) |
||
| 85 |
mstore(add(ptr, 0x20), salt) |
||
| 86 |
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes |
||
| 87 |
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff |
||
| 88 |
mstore8(start, 0xff) |
||
| 89 |
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff) |
||
| 90 |
} |
||
| 91 |
} |
||
| 92 |
} |
||
| 93 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Errors.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Collection of common custom errors used in multiple contracts |
||
| 8 |
* |
||
| 9 |
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. |
||
| 10 |
* It is recommended to avoid relying on the error API for critical functionality. |
||
| 11 |
* |
||
| 12 |
* _Available since v5.1._ |
||
| 13 |
*/ |
||
| 14 |
✓ 25.8K
|
⟲ 25.8K
|
library Errors {
|
| 15 |
/** |
||
| 16 |
* @dev The ETH balance of the account is not enough to perform the operation. |
||
| 17 |
*/ |
||
| 18 |
error InsufficientBalance(uint256 balance, uint256 needed); |
||
| 19 | |||
| 20 |
/** |
||
| 21 |
* @dev A call to an address target failed. The target may have reverted. |
||
| 22 |
*/ |
||
| 23 |
error FailedCall(); |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev The deployment failed. |
||
| 27 |
*/ |
||
| 28 |
error FailedDeployment(); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev A necessary precompile is missing. |
||
| 32 |
*/ |
||
| 33 |
error MissingPrecompile(address); |
||
| 34 |
} |
||
| 35 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Panic.sol
Lines covered: 5 / 5 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Helper library for emitting standardized panic codes. |
||
| 8 |
* |
||
| 9 |
* ```solidity |
||
| 10 |
* contract Example {
|
||
| 11 |
* using Panic for uint256; |
||
| 12 |
* |
||
| 13 |
* // Use any of the declared internal constants |
||
| 14 |
* function foo() { Panic.GENERIC.panic(); }
|
||
| 15 |
* |
||
| 16 |
* // Alternatively |
||
| 17 |
* function foo() { Panic.panic(Panic.GENERIC); }
|
||
| 18 |
* } |
||
| 19 |
* ``` |
||
| 20 |
* |
||
| 21 |
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. |
||
| 22 |
* |
||
| 23 |
* _Available since v5.1._ |
||
| 24 |
*/ |
||
| 25 |
// slither-disable-next-line unused-state |
||
| 26 |
✓ 16.9K
|
⟲ 16.9K
|
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 |
✓ 206.6K
|
uint256 internal constant UNDER_OVERFLOW = 0x11; |
|
| 33 |
/// @dev division or modulo by zero |
||
| 34 |
uint256 internal constant DIVISION_BY_ZERO = 0x12; |
||
| 35 |
/// @dev enum conversion error |
||
| 36 |
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21; |
||
| 37 |
/// @dev invalid encoding in storage |
||
| 38 |
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22; |
||
| 39 |
/// @dev empty array pop |
||
| 40 |
uint256 internal constant EMPTY_ARRAY_POP = 0x31; |
||
| 41 |
/// @dev array out of bounds access |
||
| 42 |
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32; |
||
| 43 |
/// @dev resource error (too large allocation or too large array) |
||
| 44 |
uint256 internal constant RESOURCE_ERROR = 0x41; |
||
| 45 |
/// @dev calling invalid internal function |
||
| 46 |
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51; |
||
| 47 | |||
| 48 |
/// @dev Reverts with a panic code. Recommended to use with |
||
| 49 |
/// the internal constants with predefined codes. |
||
| 50 |
function panic(uint256 code) internal pure {
|
||
| 51 |
assembly ("memory-safe") {
|
||
| 52 |
✓ 206.6K
|
mstore(0x00, 0x4e487b71) |
|
| 53 |
✓ 206.6K
|
mstore(0x20, code) |
|
| 54 |
✓ 206.6K
|
revert(0x1c, 0x24) |
|
| 55 |
} |
||
| 56 |
} |
||
| 57 |
} |
||
| 58 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/SlotDerivation.sol) |
||
| 3 |
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js. |
||
| 4 | |||
| 5 |
pragma solidity ^0.8.20; |
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots |
||
| 9 |
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by |
||
| 10 |
* the solidity language / compiler. |
||
| 11 |
* |
||
| 12 |
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.]. |
||
| 13 |
* |
||
| 14 |
* Example usage: |
||
| 15 |
* ```solidity |
||
| 16 |
* contract Example {
|
||
| 17 |
* // Add the library methods |
||
| 18 |
* using StorageSlot for bytes32; |
||
| 19 |
* using SlotDerivation for bytes32; |
||
| 20 |
* |
||
| 21 |
* // Declare a namespace |
||
| 22 |
* string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot |
||
| 23 |
* |
||
| 24 |
* function setValueInNamespace(uint256 key, address newValue) internal {
|
||
| 25 |
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue; |
||
| 26 |
* } |
||
| 27 |
* |
||
| 28 |
* function getValueInNamespace(uint256 key) internal view returns (address) {
|
||
| 29 |
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value; |
||
| 30 |
* } |
||
| 31 |
* } |
||
| 32 |
* ``` |
||
| 33 |
* |
||
| 34 |
* TIP: Consider using this library along with {StorageSlot}.
|
||
| 35 |
* |
||
| 36 |
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking |
||
| 37 |
* upgrade safety will ignore the slots accessed through this library. |
||
| 38 |
* |
||
| 39 |
* _Available since v5.1._ |
||
| 40 |
*/ |
||
| 41 |
✓ 27.5K
|
⟲ 27.5K
|
library SlotDerivation {
|
| 42 |
/** |
||
| 43 |
* @dev Derive an ERC-7201 slot from a string (namespace). |
||
| 44 |
*/ |
||
| 45 |
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
|
||
| 46 |
assembly ("memory-safe") {
|
||
| 47 |
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)) |
||
| 48 |
slot := and(keccak256(0x00, 0x20), not(0xff)) |
||
| 49 |
} |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Add an offset to a slot to get the n-th element of a structure or an array. |
||
| 54 |
*/ |
||
| 55 |
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
|
||
| 56 |
unchecked {
|
||
| 57 |
return bytes32(uint256(slot) + pos); |
||
| 58 |
} |
||
| 59 |
} |
||
| 60 | |||
| 61 |
/** |
||
| 62 |
* @dev Derive the location of the first element in an array from the slot where the length is stored. |
||
| 63 |
*/ |
||
| 64 |
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
|
||
| 65 |
assembly ("memory-safe") {
|
||
| 66 |
mstore(0x00, slot) |
||
| 67 |
result := keccak256(0x00, 0x20) |
||
| 68 |
} |
||
| 69 |
} |
||
| 70 | |||
| 71 |
/** |
||
| 72 |
* @dev Derive the location of a mapping element from the key. |
||
| 73 |
*/ |
||
| 74 |
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
|
||
| 75 |
assembly ("memory-safe") {
|
||
| 76 |
mstore(0x00, and(key, shr(96, not(0)))) |
||
| 77 |
mstore(0x20, slot) |
||
| 78 |
result := keccak256(0x00, 0x40) |
||
| 79 |
} |
||
| 80 |
} |
||
| 81 | |||
| 82 |
/** |
||
| 83 |
* @dev Derive the location of a mapping element from the key. |
||
| 84 |
*/ |
||
| 85 |
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
|
||
| 86 |
assembly ("memory-safe") {
|
||
| 87 |
mstore(0x00, iszero(iszero(key))) |
||
| 88 |
mstore(0x20, slot) |
||
| 89 |
result := keccak256(0x00, 0x40) |
||
| 90 |
} |
||
| 91 |
} |
||
| 92 | |||
| 93 |
/** |
||
| 94 |
* @dev Derive the location of a mapping element from the key. |
||
| 95 |
*/ |
||
| 96 |
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
|
||
| 97 |
assembly ("memory-safe") {
|
||
| 98 |
mstore(0x00, key) |
||
| 99 |
mstore(0x20, slot) |
||
| 100 |
result := keccak256(0x00, 0x40) |
||
| 101 |
} |
||
| 102 |
} |
||
| 103 | |||
| 104 |
/** |
||
| 105 |
* @dev Derive the location of a mapping element from the key. |
||
| 106 |
*/ |
||
| 107 |
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
|
||
| 108 |
assembly ("memory-safe") {
|
||
| 109 |
mstore(0x00, key) |
||
| 110 |
mstore(0x20, slot) |
||
| 111 |
result := keccak256(0x00, 0x40) |
||
| 112 |
} |
||
| 113 |
} |
||
| 114 | |||
| 115 |
/** |
||
| 116 |
* @dev Derive the location of a mapping element from the key. |
||
| 117 |
*/ |
||
| 118 |
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
|
||
| 119 |
assembly ("memory-safe") {
|
||
| 120 |
mstore(0x00, key) |
||
| 121 |
mstore(0x20, slot) |
||
| 122 |
result := keccak256(0x00, 0x40) |
||
| 123 |
} |
||
| 124 |
} |
||
| 125 | |||
| 126 |
/** |
||
| 127 |
* @dev Derive the location of a mapping element from the key. |
||
| 128 |
*/ |
||
| 129 |
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
|
||
| 130 |
assembly ("memory-safe") {
|
||
| 131 |
let length := mload(key) |
||
| 132 |
let begin := add(key, 0x20) |
||
| 133 |
let end := add(begin, length) |
||
| 134 |
let cache := mload(end) |
||
| 135 |
mstore(end, slot) |
||
| 136 |
result := keccak256(begin, add(length, 0x20)) |
||
| 137 |
mstore(end, cache) |
||
| 138 |
} |
||
| 139 |
} |
||
| 140 | |||
| 141 |
/** |
||
| 142 |
* @dev Derive the location of a mapping element from the key. |
||
| 143 |
*/ |
||
| 144 |
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
|
||
| 145 |
assembly ("memory-safe") {
|
||
| 146 |
let length := mload(key) |
||
| 147 |
let begin := add(key, 0x20) |
||
| 148 |
let end := add(begin, length) |
||
| 149 |
let cache := mload(end) |
||
| 150 |
mstore(end, slot) |
||
| 151 |
result := keccak256(begin, add(length, 0x20)) |
||
| 152 |
mstore(end, cache) |
||
| 153 |
} |
||
| 154 |
} |
||
| 155 |
} |
||
| 156 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol) |
||
| 3 |
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js. |
||
| 4 | |||
| 5 |
pragma solidity ^0.8.20; |
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @dev Library for reading and writing primitive types to specific storage slots. |
||
| 9 |
* |
||
| 10 |
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. |
||
| 11 |
* This library helps with reading and writing to such slots without the need for inline assembly. |
||
| 12 |
* |
||
| 13 |
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write. |
||
| 14 |
* |
||
| 15 |
* Example usage to set ERC-1967 implementation slot: |
||
| 16 |
* ```solidity |
||
| 17 |
* contract ERC1967 {
|
||
| 18 |
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. |
||
| 19 |
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; |
||
| 20 |
* |
||
| 21 |
* function _getImplementation() internal view returns (address) {
|
||
| 22 |
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; |
||
| 23 |
* } |
||
| 24 |
* |
||
| 25 |
* function _setImplementation(address newImplementation) internal {
|
||
| 26 |
* require(newImplementation.code.length > 0); |
||
| 27 |
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; |
||
| 28 |
* } |
||
| 29 |
* } |
||
| 30 |
* ``` |
||
| 31 |
* |
||
| 32 |
* TIP: Consider using this library along with {SlotDerivation}.
|
||
| 33 |
*/ |
||
| 34 |
✓ 18.6K
|
⟲ 18.6K
|
library StorageSlot {
|
| 35 |
struct AddressSlot {
|
||
| 36 |
address value; |
||
| 37 |
} |
||
| 38 | |||
| 39 |
struct BooleanSlot {
|
||
| 40 |
bool value; |
||
| 41 |
} |
||
| 42 | |||
| 43 |
struct Bytes32Slot {
|
||
| 44 |
bytes32 value; |
||
| 45 |
} |
||
| 46 | |||
| 47 |
struct Uint256Slot {
|
||
| 48 |
uint256 value; |
||
| 49 |
} |
||
| 50 | |||
| 51 |
struct Int256Slot {
|
||
| 52 |
int256 value; |
||
| 53 |
} |
||
| 54 | |||
| 55 |
struct StringSlot {
|
||
| 56 |
string value; |
||
| 57 |
} |
||
| 58 | |||
| 59 |
struct BytesSlot {
|
||
| 60 |
bytes value; |
||
| 61 |
} |
||
| 62 | |||
| 63 |
/** |
||
| 64 |
* @dev Returns an `AddressSlot` with member `value` located at `slot`. |
||
| 65 |
*/ |
||
| 66 |
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
|
||
| 67 |
assembly ("memory-safe") {
|
||
| 68 |
r.slot := slot |
||
| 69 |
} |
||
| 70 |
} |
||
| 71 | |||
| 72 |
/** |
||
| 73 |
* @dev Returns a `BooleanSlot` with member `value` located at `slot`. |
||
| 74 |
*/ |
||
| 75 |
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
|
||
| 76 |
assembly ("memory-safe") {
|
||
| 77 |
r.slot := slot |
||
| 78 |
} |
||
| 79 |
} |
||
| 80 | |||
| 81 |
/** |
||
| 82 |
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`. |
||
| 83 |
*/ |
||
| 84 |
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
|
||
| 85 |
assembly ("memory-safe") {
|
||
| 86 |
r.slot := slot |
||
| 87 |
} |
||
| 88 |
} |
||
| 89 | |||
| 90 |
/** |
||
| 91 |
* @dev Returns a `Uint256Slot` with member `value` located at `slot`. |
||
| 92 |
*/ |
||
| 93 |
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
|
||
| 94 |
assembly ("memory-safe") {
|
||
| 95 |
r.slot := slot |
||
| 96 |
} |
||
| 97 |
} |
||
| 98 | |||
| 99 |
/** |
||
| 100 |
* @dev Returns a `Int256Slot` with member `value` located at `slot`. |
||
| 101 |
*/ |
||
| 102 |
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
|
||
| 103 |
assembly ("memory-safe") {
|
||
| 104 |
r.slot := slot |
||
| 105 |
} |
||
| 106 |
} |
||
| 107 | |||
| 108 |
/** |
||
| 109 |
* @dev Returns a `StringSlot` with member `value` located at `slot`. |
||
| 110 |
*/ |
||
| 111 |
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
|
||
| 112 |
assembly ("memory-safe") {
|
||
| 113 |
r.slot := slot |
||
| 114 |
} |
||
| 115 |
} |
||
| 116 | |||
| 117 |
/** |
||
| 118 |
* @dev Returns an `StringSlot` representation of the string storage pointer `store`. |
||
| 119 |
*/ |
||
| 120 |
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
|
||
| 121 |
assembly ("memory-safe") {
|
||
| 122 |
r.slot := store.slot |
||
| 123 |
} |
||
| 124 |
} |
||
| 125 | |||
| 126 |
/** |
||
| 127 |
* @dev Returns a `BytesSlot` with member `value` located at `slot`. |
||
| 128 |
*/ |
||
| 129 |
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
|
||
| 130 |
assembly ("memory-safe") {
|
||
| 131 |
r.slot := slot |
||
| 132 |
} |
||
| 133 |
} |
||
| 134 | |||
| 135 |
/** |
||
| 136 |
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. |
||
| 137 |
*/ |
||
| 138 |
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
|
||
| 139 |
assembly ("memory-safe") {
|
||
| 140 |
r.slot := store.slot |
||
| 141 |
} |
||
| 142 |
} |
||
| 143 |
} |
||
| 144 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {Math} from "./math/Math.sol";
|
||
| 7 |
import {SafeCast} from "./math/SafeCast.sol";
|
||
| 8 |
import {SignedMath} from "./math/SignedMath.sol";
|
||
| 9 | |||
| 10 |
/** |
||
| 11 |
* @dev String operations. |
||
| 12 |
*/ |
||
| 13 |
✓ 26.3K
|
⟲ 26.3K
|
library Strings {
|
| 14 |
using SafeCast for *; |
||
| 15 | |||
| 16 |
bytes16 private constant HEX_DIGITS = "0123456789abcdef"; |
||
| 17 |
uint8 private constant ADDRESS_LENGTH = 20; |
||
| 18 |
uint256 private constant SPECIAL_CHARS_LOOKUP = |
||
| 19 |
(1 << 0x08) | // backspace |
||
| 20 |
(1 << 0x09) | // tab |
||
| 21 |
(1 << 0x0a) | // newline |
||
| 22 |
(1 << 0x0c) | // form feed |
||
| 23 |
(1 << 0x0d) | // carriage return |
||
| 24 |
(1 << 0x22) | // double quote |
||
| 25 |
(1 << 0x5c); // backslash |
||
| 26 | |||
| 27 |
/** |
||
| 28 |
* @dev The `value` string doesn't fit in the specified `length`. |
||
| 29 |
*/ |
||
| 30 |
error StringsInsufficientHexLength(uint256 value, uint256 length); |
||
| 31 | |||
| 32 |
/** |
||
| 33 |
* @dev The string being parsed contains characters that are not in scope of the given base. |
||
| 34 |
*/ |
||
| 35 |
error StringsInvalidChar(); |
||
| 36 | |||
| 37 |
/** |
||
| 38 |
* @dev The string being parsed is not a properly formatted address. |
||
| 39 |
*/ |
||
| 40 |
error StringsInvalidAddressFormat(); |
||
| 41 | |||
| 42 |
/** |
||
| 43 |
* @dev Converts a `uint256` to its ASCII `string` decimal representation. |
||
| 44 |
*/ |
||
| 45 |
function toString(uint256 value) internal pure returns (string memory) {
|
||
| 46 |
unchecked {
|
||
| 47 |
uint256 length = Math.log10(value) + 1; |
||
| 48 |
string memory buffer = new string(length); |
||
| 49 |
uint256 ptr; |
||
| 50 |
assembly ("memory-safe") {
|
||
| 51 |
ptr := add(add(buffer, 0x20), length) |
||
| 52 |
} |
||
| 53 |
while (true) {
|
||
| 54 |
ptr--; |
||
| 55 |
assembly ("memory-safe") {
|
||
| 56 |
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) |
||
| 57 |
} |
||
| 58 |
value /= 10; |
||
| 59 |
if (value == 0) break; |
||
| 60 |
} |
||
| 61 |
return buffer; |
||
| 62 |
} |
||
| 63 |
} |
||
| 64 | |||
| 65 |
/** |
||
| 66 |
* @dev Converts a `int256` to its ASCII `string` decimal representation. |
||
| 67 |
*/ |
||
| 68 |
function toStringSigned(int256 value) internal pure returns (string memory) {
|
||
| 69 |
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); |
||
| 70 |
} |
||
| 71 | |||
| 72 |
/** |
||
| 73 |
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. |
||
| 74 |
*/ |
||
| 75 |
function toHexString(uint256 value) internal pure returns (string memory) {
|
||
| 76 |
unchecked {
|
||
| 77 |
return toHexString(value, Math.log256(value) + 1); |
||
| 78 |
} |
||
| 79 |
} |
||
| 80 | |||
| 81 |
/** |
||
| 82 |
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. |
||
| 83 |
*/ |
||
| 84 |
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
|
||
| 85 |
uint256 localValue = value; |
||
| 86 |
bytes memory buffer = new bytes(2 * length + 2); |
||
| 87 |
buffer[0] = "0"; |
||
| 88 |
buffer[1] = "x"; |
||
| 89 |
for (uint256 i = 2 * length + 1; i > 1; --i) {
|
||
| 90 |
buffer[i] = HEX_DIGITS[localValue & 0xf]; |
||
| 91 |
localValue >>= 4; |
||
| 92 |
} |
||
| 93 |
if (localValue != 0) {
|
||
| 94 |
revert StringsInsufficientHexLength(value, length); |
||
| 95 |
} |
||
| 96 |
return string(buffer); |
||
| 97 |
} |
||
| 98 | |||
| 99 |
/** |
||
| 100 |
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal |
||
| 101 |
* representation. |
||
| 102 |
*/ |
||
| 103 |
function toHexString(address addr) internal pure returns (string memory) {
|
||
| 104 |
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); |
||
| 105 |
} |
||
| 106 | |||
| 107 |
/** |
||
| 108 |
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal |
||
| 109 |
* representation, according to EIP-55. |
||
| 110 |
*/ |
||
| 111 |
function toChecksumHexString(address addr) internal pure returns (string memory) {
|
||
| 112 |
bytes memory buffer = bytes(toHexString(addr)); |
||
| 113 | |||
| 114 |
// hash the hex part of buffer (skip length + 2 bytes, length 40) |
||
| 115 |
uint256 hashValue; |
||
| 116 |
assembly ("memory-safe") {
|
||
| 117 |
hashValue := shr(96, keccak256(add(buffer, 0x22), 40)) |
||
| 118 |
} |
||
| 119 | |||
| 120 |
for (uint256 i = 41; i > 1; --i) {
|
||
| 121 |
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f) |
||
| 122 |
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
|
||
| 123 |
// case shift by xoring with 0x20 |
||
| 124 |
buffer[i] ^= 0x20; |
||
| 125 |
} |
||
| 126 |
hashValue >>= 4; |
||
| 127 |
} |
||
| 128 |
return string(buffer); |
||
| 129 |
} |
||
| 130 | |||
| 131 |
/** |
||
| 132 |
* @dev Converts a `bytes` buffer to its ASCII `string` hexadecimal representation. |
||
| 133 |
*/ |
||
| 134 |
function toHexString(bytes memory input) internal pure returns (string memory) {
|
||
| 135 |
unchecked {
|
||
| 136 |
bytes memory buffer = new bytes(2 * input.length + 2); |
||
| 137 |
buffer[0] = "0"; |
||
| 138 |
buffer[1] = "x"; |
||
| 139 |
for (uint256 i = 0; i < input.length; ++i) {
|
||
| 140 |
uint8 v = uint8(input[i]); |
||
| 141 |
buffer[2 * i + 2] = HEX_DIGITS[v >> 4]; |
||
| 142 |
buffer[2 * i + 3] = HEX_DIGITS[v & 0xf]; |
||
| 143 |
} |
||
| 144 |
return string(buffer); |
||
| 145 |
} |
||
| 146 |
} |
||
| 147 | |||
| 148 |
/** |
||
| 149 |
* @dev Returns true if the two strings are equal. |
||
| 150 |
*/ |
||
| 151 |
function equal(string memory a, string memory b) internal pure returns (bool) {
|
||
| 152 |
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); |
||
| 153 |
} |
||
| 154 | |||
| 155 |
/** |
||
| 156 |
* @dev Parse a decimal string and returns the value as a `uint256`. |
||
| 157 |
* |
||
| 158 |
* Requirements: |
||
| 159 |
* - The string must be formatted as `[0-9]*` |
||
| 160 |
* - The result must fit into an `uint256` type |
||
| 161 |
*/ |
||
| 162 |
function parseUint(string memory input) internal pure returns (uint256) {
|
||
| 163 |
return parseUint(input, 0, bytes(input).length); |
||
| 164 |
} |
||
| 165 | |||
| 166 |
/** |
||
| 167 |
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
|
||
| 168 |
* `end` (excluded). |
||
| 169 |
* |
||
| 170 |
* Requirements: |
||
| 171 |
* - The substring must be formatted as `[0-9]*` |
||
| 172 |
* - The result must fit into an `uint256` type |
||
| 173 |
*/ |
||
| 174 |
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
|
||
| 175 |
(bool success, uint256 value) = tryParseUint(input, begin, end); |
||
| 176 |
if (!success) revert StringsInvalidChar(); |
||
| 177 |
return value; |
||
| 178 |
} |
||
| 179 | |||
| 180 |
/** |
||
| 181 |
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
|
||
| 182 |
* |
||
| 183 |
* NOTE: This function will revert if the result does not fit in a `uint256`. |
||
| 184 |
*/ |
||
| 185 |
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
|
||
| 186 |
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length); |
||
| 187 |
} |
||
| 188 | |||
| 189 |
/** |
||
| 190 |
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
|
||
| 191 |
* character. |
||
| 192 |
* |
||
| 193 |
* NOTE: This function will revert if the result does not fit in a `uint256`. |
||
| 194 |
*/ |
||
| 195 |
function tryParseUint( |
||
| 196 |
string memory input, |
||
| 197 |
uint256 begin, |
||
| 198 |
uint256 end |
||
| 199 |
) internal pure returns (bool success, uint256 value) {
|
||
| 200 |
if (end > bytes(input).length || begin > end) return (false, 0); |
||
| 201 |
return _tryParseUintUncheckedBounds(input, begin, end); |
||
| 202 |
} |
||
| 203 | |||
| 204 |
/** |
||
| 205 |
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
|
||
| 206 |
* `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
||
| 207 |
*/ |
||
| 208 |
function _tryParseUintUncheckedBounds( |
||
| 209 |
string memory input, |
||
| 210 |
uint256 begin, |
||
| 211 |
uint256 end |
||
| 212 |
) private pure returns (bool success, uint256 value) {
|
||
| 213 |
bytes memory buffer = bytes(input); |
||
| 214 | |||
| 215 |
uint256 result = 0; |
||
| 216 |
for (uint256 i = begin; i < end; ++i) {
|
||
| 217 |
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); |
||
| 218 |
if (chr > 9) return (false, 0); |
||
| 219 |
result *= 10; |
||
| 220 |
result += chr; |
||
| 221 |
} |
||
| 222 |
return (true, result); |
||
| 223 |
} |
||
| 224 | |||
| 225 |
/** |
||
| 226 |
* @dev Parse a decimal string and returns the value as a `int256`. |
||
| 227 |
* |
||
| 228 |
* Requirements: |
||
| 229 |
* - The string must be formatted as `[-+]?[0-9]*` |
||
| 230 |
* - The result must fit in an `int256` type. |
||
| 231 |
*/ |
||
| 232 |
function parseInt(string memory input) internal pure returns (int256) {
|
||
| 233 |
return parseInt(input, 0, bytes(input).length); |
||
| 234 |
} |
||
| 235 | |||
| 236 |
/** |
||
| 237 |
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
|
||
| 238 |
* `end` (excluded). |
||
| 239 |
* |
||
| 240 |
* Requirements: |
||
| 241 |
* - The substring must be formatted as `[-+]?[0-9]*` |
||
| 242 |
* - The result must fit in an `int256` type. |
||
| 243 |
*/ |
||
| 244 |
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
|
||
| 245 |
(bool success, int256 value) = tryParseInt(input, begin, end); |
||
| 246 |
if (!success) revert StringsInvalidChar(); |
||
| 247 |
return value; |
||
| 248 |
} |
||
| 249 | |||
| 250 |
/** |
||
| 251 |
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
|
||
| 252 |
* the result does not fit in a `int256`. |
||
| 253 |
* |
||
| 254 |
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. |
||
| 255 |
*/ |
||
| 256 |
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
|
||
| 257 |
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length); |
||
| 258 |
} |
||
| 259 | |||
| 260 |
uint256 private constant ABS_MIN_INT256 = 2 ** 255; |
||
| 261 | |||
| 262 |
/** |
||
| 263 |
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
|
||
| 264 |
* character or if the result does not fit in a `int256`. |
||
| 265 |
* |
||
| 266 |
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. |
||
| 267 |
*/ |
||
| 268 |
function tryParseInt( |
||
| 269 |
string memory input, |
||
| 270 |
uint256 begin, |
||
| 271 |
uint256 end |
||
| 272 |
) internal pure returns (bool success, int256 value) {
|
||
| 273 |
if (end > bytes(input).length || begin > end) return (false, 0); |
||
| 274 |
return _tryParseIntUncheckedBounds(input, begin, end); |
||
| 275 |
} |
||
| 276 | |||
| 277 |
/** |
||
| 278 |
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
|
||
| 279 |
* `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
||
| 280 |
*/ |
||
| 281 |
function _tryParseIntUncheckedBounds( |
||
| 282 |
string memory input, |
||
| 283 |
uint256 begin, |
||
| 284 |
uint256 end |
||
| 285 |
) private pure returns (bool success, int256 value) {
|
||
| 286 |
bytes memory buffer = bytes(input); |
||
| 287 | |||
| 288 |
// Check presence of a negative sign. |
||
| 289 |
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty |
||
| 290 |
bool positiveSign = sign == bytes1("+");
|
||
| 291 |
bool negativeSign = sign == bytes1("-");
|
||
| 292 |
uint256 offset = (positiveSign || negativeSign).toUint(); |
||
| 293 | |||
| 294 |
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end); |
||
| 295 | |||
| 296 |
if (absSuccess && absValue < ABS_MIN_INT256) {
|
||
| 297 |
return (true, negativeSign ? -int256(absValue) : int256(absValue)); |
||
| 298 |
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
|
||
| 299 |
return (true, type(int256).min); |
||
| 300 |
} else return (false, 0); |
||
| 301 |
} |
||
| 302 | |||
| 303 |
/** |
||
| 304 |
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. |
||
| 305 |
* |
||
| 306 |
* Requirements: |
||
| 307 |
* - The string must be formatted as `(0x)?[0-9a-fA-F]*` |
||
| 308 |
* - The result must fit in an `uint256` type. |
||
| 309 |
*/ |
||
| 310 |
function parseHexUint(string memory input) internal pure returns (uint256) {
|
||
| 311 |
return parseHexUint(input, 0, bytes(input).length); |
||
| 312 |
} |
||
| 313 | |||
| 314 |
/** |
||
| 315 |
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
|
||
| 316 |
* `end` (excluded). |
||
| 317 |
* |
||
| 318 |
* Requirements: |
||
| 319 |
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*` |
||
| 320 |
* - The result must fit in an `uint256` type. |
||
| 321 |
*/ |
||
| 322 |
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
|
||
| 323 |
(bool success, uint256 value) = tryParseHexUint(input, begin, end); |
||
| 324 |
if (!success) revert StringsInvalidChar(); |
||
| 325 |
return value; |
||
| 326 |
} |
||
| 327 | |||
| 328 |
/** |
||
| 329 |
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
|
||
| 330 |
* |
||
| 331 |
* NOTE: This function will revert if the result does not fit in a `uint256`. |
||
| 332 |
*/ |
||
| 333 |
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
|
||
| 334 |
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length); |
||
| 335 |
} |
||
| 336 | |||
| 337 |
/** |
||
| 338 |
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
|
||
| 339 |
* invalid character. |
||
| 340 |
* |
||
| 341 |
* NOTE: This function will revert if the result does not fit in a `uint256`. |
||
| 342 |
*/ |
||
| 343 |
function tryParseHexUint( |
||
| 344 |
string memory input, |
||
| 345 |
uint256 begin, |
||
| 346 |
uint256 end |
||
| 347 |
) internal pure returns (bool success, uint256 value) {
|
||
| 348 |
if (end > bytes(input).length || begin > end) return (false, 0); |
||
| 349 |
return _tryParseHexUintUncheckedBounds(input, begin, end); |
||
| 350 |
} |
||
| 351 | |||
| 352 |
/** |
||
| 353 |
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
|
||
| 354 |
* `begin <= end <= input.length`. Other inputs would result in undefined behavior. |
||
| 355 |
*/ |
||
| 356 |
function _tryParseHexUintUncheckedBounds( |
||
| 357 |
string memory input, |
||
| 358 |
uint256 begin, |
||
| 359 |
uint256 end |
||
| 360 |
) private pure returns (bool success, uint256 value) {
|
||
| 361 |
bytes memory buffer = bytes(input); |
||
| 362 | |||
| 363 |
// skip 0x prefix if present |
||
| 364 |
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
|
||
| 365 |
uint256 offset = hasPrefix.toUint() * 2; |
||
| 366 | |||
| 367 |
uint256 result = 0; |
||
| 368 |
for (uint256 i = begin + offset; i < end; ++i) {
|
||
| 369 |
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); |
||
| 370 |
if (chr > 15) return (false, 0); |
||
| 371 |
result *= 16; |
||
| 372 |
unchecked {
|
||
| 373 |
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check). |
||
| 374 |
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked. |
||
| 375 |
result += chr; |
||
| 376 |
} |
||
| 377 |
} |
||
| 378 |
return (true, result); |
||
| 379 |
} |
||
| 380 | |||
| 381 |
/** |
||
| 382 |
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. |
||
| 383 |
* |
||
| 384 |
* Requirements: |
||
| 385 |
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
|
||
| 386 |
*/ |
||
| 387 |
function parseAddress(string memory input) internal pure returns (address) {
|
||
| 388 |
return parseAddress(input, 0, bytes(input).length); |
||
| 389 |
} |
||
| 390 | |||
| 391 |
/** |
||
| 392 |
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
|
||
| 393 |
* `end` (excluded). |
||
| 394 |
* |
||
| 395 |
* Requirements: |
||
| 396 |
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
|
||
| 397 |
*/ |
||
| 398 |
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
|
||
| 399 |
(bool success, address value) = tryParseAddress(input, begin, end); |
||
| 400 |
if (!success) revert StringsInvalidAddressFormat(); |
||
| 401 |
return value; |
||
| 402 |
} |
||
| 403 | |||
| 404 |
/** |
||
| 405 |
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
|
||
| 406 |
* formatted address. See {parseAddress-string} requirements.
|
||
| 407 |
*/ |
||
| 408 |
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
|
||
| 409 |
return tryParseAddress(input, 0, bytes(input).length); |
||
| 410 |
} |
||
| 411 | |||
| 412 |
/** |
||
| 413 |
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
|
||
| 414 |
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
|
||
| 415 |
*/ |
||
| 416 |
function tryParseAddress( |
||
| 417 |
string memory input, |
||
| 418 |
uint256 begin, |
||
| 419 |
uint256 end |
||
| 420 |
) internal pure returns (bool success, address value) {
|
||
| 421 |
if (end > bytes(input).length || begin > end) return (false, address(0)); |
||
| 422 | |||
| 423 |
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
|
||
| 424 |
uint256 expectedLength = 40 + hasPrefix.toUint() * 2; |
||
| 425 | |||
| 426 |
// check that input is the correct length |
||
| 427 |
if (end - begin == expectedLength) {
|
||
| 428 |
// length guarantees that this does not overflow, and value is at most type(uint160).max |
||
| 429 |
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end); |
||
| 430 |
return (s, address(uint160(v))); |
||
| 431 |
} else {
|
||
| 432 |
return (false, address(0)); |
||
| 433 |
} |
||
| 434 |
} |
||
| 435 | |||
| 436 |
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
|
||
| 437 |
uint8 value = uint8(chr); |
||
| 438 | |||
| 439 |
// Try to parse `chr`: |
||
| 440 |
// - Case 1: [0-9] |
||
| 441 |
// - Case 2: [a-f] |
||
| 442 |
// - Case 3: [A-F] |
||
| 443 |
// - otherwise not supported |
||
| 444 |
unchecked {
|
||
| 445 |
if (value > 47 && value < 58) value -= 48; |
||
| 446 |
else if (value > 96 && value < 103) value -= 87; |
||
| 447 |
else if (value > 64 && value < 71) value -= 55; |
||
| 448 |
else return type(uint8).max; |
||
| 449 |
} |
||
| 450 | |||
| 451 |
return value; |
||
| 452 |
} |
||
| 453 | |||
| 454 |
/** |
||
| 455 |
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata. |
||
| 456 |
* |
||
| 457 |
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped. |
||
| 458 |
* |
||
| 459 |
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of |
||
| 460 |
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode |
||
| 461 |
* characters that are not in this range, but other tooling may provide different results. |
||
| 462 |
*/ |
||
| 463 |
function escapeJSON(string memory input) internal pure returns (string memory) {
|
||
| 464 |
bytes memory buffer = bytes(input); |
||
| 465 |
bytes memory output = new bytes(2 * buffer.length); // worst case scenario |
||
| 466 |
uint256 outputLength = 0; |
||
| 467 | |||
| 468 |
for (uint256 i; i < buffer.length; ++i) {
|
||
| 469 |
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i)); |
||
| 470 |
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
|
||
| 471 |
output[outputLength++] = "\\"; |
||
| 472 |
if (char == 0x08) output[outputLength++] = "b"; |
||
| 473 |
else if (char == 0x09) output[outputLength++] = "t"; |
||
| 474 |
else if (char == 0x0a) output[outputLength++] = "n"; |
||
| 475 |
else if (char == 0x0c) output[outputLength++] = "f"; |
||
| 476 |
else if (char == 0x0d) output[outputLength++] = "r"; |
||
| 477 |
else if (char == 0x5c) output[outputLength++] = "\\"; |
||
| 478 |
else if (char == 0x22) {
|
||
| 479 |
// solhint-disable-next-line quotes |
||
| 480 |
output[outputLength++] = '"'; |
||
| 481 |
} |
||
| 482 |
} else {
|
||
| 483 |
output[outputLength++] = char; |
||
| 484 |
} |
||
| 485 |
} |
||
| 486 |
// write the actual length and deallocate unused memory |
||
| 487 |
assembly ("memory-safe") {
|
||
| 488 |
mstore(output, outputLength) |
||
| 489 |
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63))))) |
||
| 490 |
} |
||
| 491 | |||
| 492 |
return string(output); |
||
| 493 |
} |
||
| 494 | |||
| 495 |
/** |
||
| 496 |
* @dev Reads a bytes32 from a bytes array without bounds checking. |
||
| 497 |
* |
||
| 498 |
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the |
||
| 499 |
* assembly block as such would prevent some optimizations. |
||
| 500 |
*/ |
||
| 501 |
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
|
||
| 502 |
// This is not memory safe in the general case, but all calls to this private function are within bounds. |
||
| 503 |
assembly ("memory-safe") {
|
||
| 504 |
value := mload(add(add(buffer, 0x20), offset)) |
||
| 505 |
} |
||
| 506 |
} |
||
| 507 |
} |
||
| 508 |
3.7%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol
Lines covered: 1 / 27 (3.7%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. |
||
| 8 |
* |
||
| 9 |
* These functions can be used to verify that a message was signed by the holder |
||
| 10 |
* of the private keys of a given address. |
||
| 11 |
*/ |
||
| 12 |
✓ 32.2K
|
⟲ 32.2K
|
library ECDSA {
|
| 13 |
enum RecoverError {
|
||
| 14 |
NoError, |
||
| 15 |
InvalidSignature, |
||
| 16 |
InvalidSignatureLength, |
||
| 17 |
InvalidSignatureS |
||
| 18 |
} |
||
| 19 | |||
| 20 |
/** |
||
| 21 |
* @dev The signature derives the `address(0)`. |
||
| 22 |
*/ |
||
| 23 |
error ECDSAInvalidSignature(); |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev The signature has an invalid length. |
||
| 27 |
*/ |
||
| 28 |
error ECDSAInvalidSignatureLength(uint256 length); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev The signature has an S value that is in the upper half order. |
||
| 32 |
*/ |
||
| 33 |
error ECDSAInvalidSignatureS(bytes32 s); |
||
| 34 | |||
| 35 |
/** |
||
| 36 |
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not |
||
| 37 |
* return address(0) without also returning an error description. Errors are documented using an enum (error type) |
||
| 38 |
* and a bytes32 providing additional information about the error. |
||
| 39 |
* |
||
| 40 |
* If no error is returned, then the address can be used for verification purposes. |
||
| 41 |
* |
||
| 42 |
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: |
||
| 43 |
* this function rejects them by requiring the `s` value to be in the lower |
||
| 44 |
* half order, and the `v` value to be either 27 or 28. |
||
| 45 |
* |
||
| 46 |
* IMPORTANT: `hash` _must_ be the result of a hash operation for the |
||
| 47 |
* verification to be secure: it is possible to craft signatures that |
||
| 48 |
* recover to arbitrary addresses for non-hashed data. A safe way to ensure |
||
| 49 |
* this is by receiving a hash of the original message (which may otherwise |
||
| 50 |
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
|
||
| 51 |
* |
||
| 52 |
* Documentation for signature generation: |
||
| 53 |
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] |
||
| 54 |
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] |
||
| 55 |
*/ |
||
| 56 |
function tryRecover( |
||
| 57 |
bytes32 hash, |
||
| 58 |
bytes memory signature |
||
| 59 |
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
|
||
| 60 |
if (signature.length == 65) {
|
||
| 61 |
bytes32 r; |
||
| 62 |
bytes32 s; |
||
| 63 |
uint8 v; |
||
| 64 |
// ecrecover takes the signature parameters, and the only way to get them |
||
| 65 |
// currently is to use assembly. |
||
| 66 |
assembly ("memory-safe") {
|
||
| 67 |
r := mload(add(signature, 0x20)) |
||
| 68 |
s := mload(add(signature, 0x40)) |
||
| 69 |
v := byte(0, mload(add(signature, 0x60))) |
||
| 70 |
} |
||
| 71 |
return tryRecover(hash, v, r, s); |
||
| 72 |
} else {
|
||
| 73 |
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); |
||
| 74 |
} |
||
| 75 |
} |
||
| 76 | |||
| 77 |
/** |
||
| 78 |
* @dev Returns the address that signed a hashed message (`hash`) with |
||
| 79 |
* `signature`. This address can then be used for verification purposes. |
||
| 80 |
* |
||
| 81 |
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: |
||
| 82 |
* this function rejects them by requiring the `s` value to be in the lower |
||
| 83 |
* half order, and the `v` value to be either 27 or 28. |
||
| 84 |
* |
||
| 85 |
* IMPORTANT: `hash` _must_ be the result of a hash operation for the |
||
| 86 |
* verification to be secure: it is possible to craft signatures that |
||
| 87 |
* recover to arbitrary addresses for non-hashed data. A safe way to ensure |
||
| 88 |
* this is by receiving a hash of the original message (which may otherwise |
||
| 89 |
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
|
||
| 90 |
*/ |
||
| 91 |
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
|
||
| 92 |
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); |
||
| 93 |
_throwError(error, errorArg); |
||
| 94 |
return recovered; |
||
| 95 |
} |
||
| 96 | |||
| 97 |
/** |
||
| 98 |
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
|
||
| 99 |
* |
||
| 100 |
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] |
||
| 101 |
*/ |
||
| 102 |
function tryRecover( |
||
| 103 |
bytes32 hash, |
||
| 104 |
bytes32 r, |
||
| 105 |
bytes32 vs |
||
| 106 |
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
|
||
| 107 |
unchecked {
|
||
| 108 |
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); |
||
| 109 |
// We do not check for an overflow here since the shift operation results in 0 or 1. |
||
| 110 |
uint8 v = uint8((uint256(vs) >> 255) + 27); |
||
| 111 |
return tryRecover(hash, v, r, s); |
||
| 112 |
} |
||
| 113 |
} |
||
| 114 | |||
| 115 |
/** |
||
| 116 |
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
|
||
| 117 |
*/ |
||
| 118 |
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
|
||
| 119 |
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); |
||
| 120 |
_throwError(error, errorArg); |
||
| 121 |
return recovered; |
||
| 122 |
} |
||
| 123 | |||
| 124 |
/** |
||
| 125 |
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
|
||
| 126 |
* `r` and `s` signature fields separately. |
||
| 127 |
*/ |
||
| 128 |
function tryRecover( |
||
| 129 |
bytes32 hash, |
||
| 130 |
uint8 v, |
||
| 131 |
bytes32 r, |
||
| 132 |
bytes32 s |
||
| 133 |
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
|
||
| 134 |
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature |
||
| 135 |
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines |
||
| 136 |
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
|
||
| 137 |
// signatures from current libraries generate a unique signature with an s-value in the lower half order. |
||
| 138 |
// |
||
| 139 |
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value |
||
| 140 |
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or |
||
| 141 |
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept |
||
| 142 |
// these malleable signatures as well. |
||
| 143 |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
|
||
| 144 |
return (address(0), RecoverError.InvalidSignatureS, s); |
||
| 145 |
} |
||
| 146 | |||
| 147 |
// If the signature is valid (and not malleable), return the signer address |
||
| 148 |
address signer = ecrecover(hash, v, r, s); |
||
| 149 |
if (signer == address(0)) {
|
||
| 150 |
return (address(0), RecoverError.InvalidSignature, bytes32(0)); |
||
| 151 |
} |
||
| 152 | |||
| 153 |
return (signer, RecoverError.NoError, bytes32(0)); |
||
| 154 |
} |
||
| 155 | |||
| 156 |
/** |
||
| 157 |
* @dev Overload of {ECDSA-recover} that receives the `v`,
|
||
| 158 |
* `r` and `s` signature fields separately. |
||
| 159 |
*/ |
||
| 160 |
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
|
||
| 161 |
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); |
||
| 162 |
_throwError(error, errorArg); |
||
| 163 |
return recovered; |
||
| 164 |
} |
||
| 165 | |||
| 166 |
/** |
||
| 167 |
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. |
||
| 168 |
*/ |
||
| 169 |
function _throwError(RecoverError error, bytes32 errorArg) private pure {
|
||
| 170 |
if (error == RecoverError.NoError) {
|
||
| 171 |
return; // no error: do nothing |
||
| 172 |
} else if (error == RecoverError.InvalidSignature) {
|
||
| 173 |
revert ECDSAInvalidSignature(); |
||
| 174 |
} else if (error == RecoverError.InvalidSignatureLength) {
|
||
| 175 |
revert ECDSAInvalidSignatureLength(uint256(errorArg)); |
||
| 176 |
} else if (error == RecoverError.InvalidSignatureS) {
|
||
| 177 |
revert ECDSAInvalidSignatureS(errorArg); |
||
| 178 |
} |
||
| 179 |
} |
||
| 180 |
} |
||
| 181 |
14.3%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/Hashes.sol
Lines covered: 1 / 7 (14.3%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Library of standard hash functions. |
||
| 8 |
* |
||
| 9 |
* _Available since v5.1._ |
||
| 10 |
*/ |
||
| 11 |
✓ 20.0K
|
⟲ 20.0K
|
library Hashes {
|
| 12 |
/** |
||
| 13 |
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs. |
||
| 14 |
* |
||
| 15 |
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. |
||
| 16 |
*/ |
||
| 17 |
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
|
||
| 18 |
return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a); |
||
| 19 |
} |
||
| 20 | |||
| 21 |
/** |
||
| 22 |
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. |
||
| 23 |
*/ |
||
| 24 |
function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) {
|
||
| 25 |
assembly ("memory-safe") {
|
||
| 26 |
mstore(0x00, a) |
||
| 27 |
mstore(0x20, b) |
||
| 28 |
value := keccak256(0x00, 0x40) |
||
| 29 |
} |
||
| 30 |
} |
||
| 31 |
} |
||
| 32 |
12.5%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol
Lines covered: 1 / 8 (12.5%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol) |
||
| 3 |
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js. |
||
| 4 | |||
| 5 |
pragma solidity ^0.8.20; |
||
| 6 | |||
| 7 |
import {Hashes} from "./Hashes.sol";
|
||
| 8 | |||
| 9 |
/** |
||
| 10 |
* @dev These functions deal with verification of Merkle Tree proofs. |
||
| 11 |
* |
||
| 12 |
* The tree and the proofs can be generated using our |
||
| 13 |
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. |
||
| 14 |
* You will find a quickstart guide in the readme. |
||
| 15 |
* |
||
| 16 |
* WARNING: You should avoid using leaf values that are 64 bytes long prior to |
||
| 17 |
* hashing, or use a hash function other than keccak256 for hashing leaves. |
||
| 18 |
* This is because the concatenation of a sorted pair of internal nodes in |
||
| 19 |
* the Merkle tree could be reinterpreted as a leaf value. |
||
| 20 |
* OpenZeppelin's JavaScript library generates Merkle trees that are safe |
||
| 21 |
* against this attack out of the box. |
||
| 22 |
* |
||
| 23 |
* IMPORTANT: Consider memory side-effects when using custom hashing functions |
||
| 24 |
* that access memory in an unsafe way. |
||
| 25 |
* |
||
| 26 |
* NOTE: This library supports proof verification for merkle trees built using |
||
| 27 |
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving |
||
| 28 |
* leaf inclusion in trees built using non-commutative hashing functions requires |
||
| 29 |
* additional logic that is not supported by this library. |
||
| 30 |
*/ |
||
| 31 |
✓ 27.6K
|
⟲ 27.6K
|
library MerkleProof {
|
| 32 |
/** |
||
| 33 |
*@dev The multiproof provided is not valid. |
||
| 34 |
*/ |
||
| 35 |
error MerkleProofInvalidMultiproof(); |
||
| 36 | |||
| 37 |
/** |
||
| 38 |
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
||
| 39 |
* defined by `root`. For this, a `proof` must be provided, containing |
||
| 40 |
* sibling hashes on the branch from the leaf to the root of the tree. Each |
||
| 41 |
* pair of leaves and each pair of pre-images are assumed to be sorted. |
||
| 42 |
* |
||
| 43 |
* This version handles proofs in memory with the default hashing function. |
||
| 44 |
*/ |
||
| 45 |
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
|
||
| 46 |
return processProof(proof, leaf) == root; |
||
| 47 |
} |
||
| 48 | |||
| 49 |
/** |
||
| 50 |
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
||
| 51 |
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
||
| 52 |
* hash matches the root of the tree. When processing the proof, the pairs |
||
| 53 |
* of leaves & pre-images are assumed to be sorted. |
||
| 54 |
* |
||
| 55 |
* This version handles proofs in memory with the default hashing function. |
||
| 56 |
*/ |
||
| 57 |
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
|
||
| 58 |
bytes32 computedHash = leaf; |
||
| 59 |
for (uint256 i = 0; i < proof.length; i++) {
|
||
| 60 |
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); |
||
| 61 |
} |
||
| 62 |
return computedHash; |
||
| 63 |
} |
||
| 64 | |||
| 65 |
/** |
||
| 66 |
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
||
| 67 |
* defined by `root`. For this, a `proof` must be provided, containing |
||
| 68 |
* sibling hashes on the branch from the leaf to the root of the tree. Each |
||
| 69 |
* pair of leaves and each pair of pre-images are assumed to be sorted. |
||
| 70 |
* |
||
| 71 |
* This version handles proofs in memory with a custom hashing function. |
||
| 72 |
*/ |
||
| 73 |
function verify( |
||
| 74 |
bytes32[] memory proof, |
||
| 75 |
bytes32 root, |
||
| 76 |
bytes32 leaf, |
||
| 77 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 78 |
) internal view returns (bool) {
|
||
| 79 |
return processProof(proof, leaf, hasher) == root; |
||
| 80 |
} |
||
| 81 | |||
| 82 |
/** |
||
| 83 |
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
||
| 84 |
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
||
| 85 |
* hash matches the root of the tree. When processing the proof, the pairs |
||
| 86 |
* of leaves & pre-images are assumed to be sorted. |
||
| 87 |
* |
||
| 88 |
* This version handles proofs in memory with a custom hashing function. |
||
| 89 |
*/ |
||
| 90 |
function processProof( |
||
| 91 |
bytes32[] memory proof, |
||
| 92 |
bytes32 leaf, |
||
| 93 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 94 |
) internal view returns (bytes32) {
|
||
| 95 |
bytes32 computedHash = leaf; |
||
| 96 |
for (uint256 i = 0; i < proof.length; i++) {
|
||
| 97 |
computedHash = hasher(computedHash, proof[i]); |
||
| 98 |
} |
||
| 99 |
return computedHash; |
||
| 100 |
} |
||
| 101 | |||
| 102 |
/** |
||
| 103 |
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
||
| 104 |
* defined by `root`. For this, a `proof` must be provided, containing |
||
| 105 |
* sibling hashes on the branch from the leaf to the root of the tree. Each |
||
| 106 |
* pair of leaves and each pair of pre-images are assumed to be sorted. |
||
| 107 |
* |
||
| 108 |
* This version handles proofs in calldata with the default hashing function. |
||
| 109 |
*/ |
||
| 110 |
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
|
||
| 111 |
return processProofCalldata(proof, leaf) == root; |
||
| 112 |
} |
||
| 113 | |||
| 114 |
/** |
||
| 115 |
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
||
| 116 |
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
||
| 117 |
* hash matches the root of the tree. When processing the proof, the pairs |
||
| 118 |
* of leaves & pre-images are assumed to be sorted. |
||
| 119 |
* |
||
| 120 |
* This version handles proofs in calldata with the default hashing function. |
||
| 121 |
*/ |
||
| 122 |
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
|
||
| 123 |
bytes32 computedHash = leaf; |
||
| 124 |
for (uint256 i = 0; i < proof.length; i++) {
|
||
| 125 |
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); |
||
| 126 |
} |
||
| 127 |
return computedHash; |
||
| 128 |
} |
||
| 129 | |||
| 130 |
/** |
||
| 131 |
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree |
||
| 132 |
* defined by `root`. For this, a `proof` must be provided, containing |
||
| 133 |
* sibling hashes on the branch from the leaf to the root of the tree. Each |
||
| 134 |
* pair of leaves and each pair of pre-images are assumed to be sorted. |
||
| 135 |
* |
||
| 136 |
* This version handles proofs in calldata with a custom hashing function. |
||
| 137 |
*/ |
||
| 138 |
function verifyCalldata( |
||
| 139 |
bytes32[] calldata proof, |
||
| 140 |
bytes32 root, |
||
| 141 |
bytes32 leaf, |
||
| 142 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 143 |
) internal view returns (bool) {
|
||
| 144 |
return processProofCalldata(proof, leaf, hasher) == root; |
||
| 145 |
} |
||
| 146 | |||
| 147 |
/** |
||
| 148 |
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up |
||
| 149 |
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt |
||
| 150 |
* hash matches the root of the tree. When processing the proof, the pairs |
||
| 151 |
* of leaves & pre-images are assumed to be sorted. |
||
| 152 |
* |
||
| 153 |
* This version handles proofs in calldata with a custom hashing function. |
||
| 154 |
*/ |
||
| 155 |
function processProofCalldata( |
||
| 156 |
bytes32[] calldata proof, |
||
| 157 |
bytes32 leaf, |
||
| 158 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 159 |
) internal view returns (bytes32) {
|
||
| 160 |
bytes32 computedHash = leaf; |
||
| 161 |
for (uint256 i = 0; i < proof.length; i++) {
|
||
| 162 |
computedHash = hasher(computedHash, proof[i]); |
||
| 163 |
} |
||
| 164 |
return computedHash; |
||
| 165 |
} |
||
| 166 | |||
| 167 |
/** |
||
| 168 |
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
||
| 169 |
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
|
||
| 170 |
* |
||
| 171 |
* This version handles multiproofs in memory with the default hashing function. |
||
| 172 |
* |
||
| 173 |
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
|
||
| 174 |
* |
||
| 175 |
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
||
| 176 |
* The `leaves` must be validated independently. See {processMultiProof}.
|
||
| 177 |
*/ |
||
| 178 |
function multiProofVerify( |
||
| 179 |
bytes32[] memory proof, |
||
| 180 |
bool[] memory proofFlags, |
||
| 181 |
bytes32 root, |
||
| 182 |
bytes32[] memory leaves |
||
| 183 |
) internal pure returns (bool) {
|
||
| 184 |
return processMultiProof(proof, proofFlags, leaves) == root; |
||
| 185 |
} |
||
| 186 | |||
| 187 |
/** |
||
| 188 |
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
||
| 189 |
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
||
| 190 |
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
||
| 191 |
* respectively. |
||
| 192 |
* |
||
| 193 |
* This version handles multiproofs in memory with the default hashing function. |
||
| 194 |
* |
||
| 195 |
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
||
| 196 |
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
||
| 197 |
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
||
| 198 |
* |
||
| 199 |
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
||
| 200 |
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
||
| 201 |
* validating the leaves elsewhere. |
||
| 202 |
*/ |
||
| 203 |
function processMultiProof( |
||
| 204 |
bytes32[] memory proof, |
||
| 205 |
bool[] memory proofFlags, |
||
| 206 |
bytes32[] memory leaves |
||
| 207 |
) internal pure returns (bytes32 merkleRoot) {
|
||
| 208 |
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
||
| 209 |
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
||
| 210 |
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
||
| 211 |
// the Merkle tree. |
||
| 212 |
uint256 leavesLen = leaves.length; |
||
| 213 |
uint256 proofFlagsLen = proofFlags.length; |
||
| 214 | |||
| 215 |
// Check proof validity. |
||
| 216 |
if (leavesLen + proof.length != proofFlagsLen + 1) {
|
||
| 217 |
revert MerkleProofInvalidMultiproof(); |
||
| 218 |
} |
||
| 219 | |||
| 220 |
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
||
| 221 |
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
||
| 222 |
bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
||
| 223 |
uint256 leafPos = 0; |
||
| 224 |
uint256 hashPos = 0; |
||
| 225 |
uint256 proofPos = 0; |
||
| 226 |
// At each step, we compute the next hash using two values: |
||
| 227 |
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
||
| 228 |
// get the next hash. |
||
| 229 |
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
||
| 230 |
// `proof` array. |
||
| 231 |
for (uint256 i = 0; i < proofFlagsLen; i++) {
|
||
| 232 |
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
||
| 233 |
bytes32 b = proofFlags[i] |
||
| 234 |
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
||
| 235 |
: proof[proofPos++]; |
||
| 236 |
hashes[i] = Hashes.commutativeKeccak256(a, b); |
||
| 237 |
} |
||
| 238 | |||
| 239 |
if (proofFlagsLen > 0) {
|
||
| 240 |
if (proofPos != proof.length) {
|
||
| 241 |
revert MerkleProofInvalidMultiproof(); |
||
| 242 |
} |
||
| 243 |
unchecked {
|
||
| 244 |
return hashes[proofFlagsLen - 1]; |
||
| 245 |
} |
||
| 246 |
} else if (leavesLen > 0) {
|
||
| 247 |
return leaves[0]; |
||
| 248 |
} else {
|
||
| 249 |
return proof[0]; |
||
| 250 |
} |
||
| 251 |
} |
||
| 252 | |||
| 253 |
/** |
||
| 254 |
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
||
| 255 |
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
|
||
| 256 |
* |
||
| 257 |
* This version handles multiproofs in memory with a custom hashing function. |
||
| 258 |
* |
||
| 259 |
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
|
||
| 260 |
* |
||
| 261 |
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
||
| 262 |
* The `leaves` must be validated independently. See {processMultiProof}.
|
||
| 263 |
*/ |
||
| 264 |
function multiProofVerify( |
||
| 265 |
bytes32[] memory proof, |
||
| 266 |
bool[] memory proofFlags, |
||
| 267 |
bytes32 root, |
||
| 268 |
bytes32[] memory leaves, |
||
| 269 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 270 |
) internal view returns (bool) {
|
||
| 271 |
return processMultiProof(proof, proofFlags, leaves, hasher) == root; |
||
| 272 |
} |
||
| 273 | |||
| 274 |
/** |
||
| 275 |
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
||
| 276 |
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
||
| 277 |
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
||
| 278 |
* respectively. |
||
| 279 |
* |
||
| 280 |
* This version handles multiproofs in memory with a custom hashing function. |
||
| 281 |
* |
||
| 282 |
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
||
| 283 |
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
||
| 284 |
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
||
| 285 |
* |
||
| 286 |
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
||
| 287 |
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
||
| 288 |
* validating the leaves elsewhere. |
||
| 289 |
*/ |
||
| 290 |
function processMultiProof( |
||
| 291 |
bytes32[] memory proof, |
||
| 292 |
bool[] memory proofFlags, |
||
| 293 |
bytes32[] memory leaves, |
||
| 294 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 295 |
) internal view returns (bytes32 merkleRoot) {
|
||
| 296 |
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
||
| 297 |
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
||
| 298 |
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
||
| 299 |
// the Merkle tree. |
||
| 300 |
uint256 leavesLen = leaves.length; |
||
| 301 |
uint256 proofFlagsLen = proofFlags.length; |
||
| 302 | |||
| 303 |
// Check proof validity. |
||
| 304 |
if (leavesLen + proof.length != proofFlagsLen + 1) {
|
||
| 305 |
revert MerkleProofInvalidMultiproof(); |
||
| 306 |
} |
||
| 307 | |||
| 308 |
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
||
| 309 |
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
||
| 310 |
bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
||
| 311 |
uint256 leafPos = 0; |
||
| 312 |
uint256 hashPos = 0; |
||
| 313 |
uint256 proofPos = 0; |
||
| 314 |
// At each step, we compute the next hash using two values: |
||
| 315 |
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
||
| 316 |
// get the next hash. |
||
| 317 |
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
||
| 318 |
// `proof` array. |
||
| 319 |
for (uint256 i = 0; i < proofFlagsLen; i++) {
|
||
| 320 |
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
||
| 321 |
bytes32 b = proofFlags[i] |
||
| 322 |
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
||
| 323 |
: proof[proofPos++]; |
||
| 324 |
hashes[i] = hasher(a, b); |
||
| 325 |
} |
||
| 326 | |||
| 327 |
if (proofFlagsLen > 0) {
|
||
| 328 |
if (proofPos != proof.length) {
|
||
| 329 |
revert MerkleProofInvalidMultiproof(); |
||
| 330 |
} |
||
| 331 |
unchecked {
|
||
| 332 |
return hashes[proofFlagsLen - 1]; |
||
| 333 |
} |
||
| 334 |
} else if (leavesLen > 0) {
|
||
| 335 |
return leaves[0]; |
||
| 336 |
} else {
|
||
| 337 |
return proof[0]; |
||
| 338 |
} |
||
| 339 |
} |
||
| 340 | |||
| 341 |
/** |
||
| 342 |
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
||
| 343 |
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
|
||
| 344 |
* |
||
| 345 |
* This version handles multiproofs in calldata with the default hashing function. |
||
| 346 |
* |
||
| 347 |
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
|
||
| 348 |
* |
||
| 349 |
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
||
| 350 |
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
|
||
| 351 |
*/ |
||
| 352 |
function multiProofVerifyCalldata( |
||
| 353 |
bytes32[] calldata proof, |
||
| 354 |
bool[] calldata proofFlags, |
||
| 355 |
bytes32 root, |
||
| 356 |
bytes32[] memory leaves |
||
| 357 |
) internal pure returns (bool) {
|
||
| 358 |
return processMultiProofCalldata(proof, proofFlags, leaves) == root; |
||
| 359 |
} |
||
| 360 | |||
| 361 |
/** |
||
| 362 |
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
||
| 363 |
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
||
| 364 |
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
||
| 365 |
* respectively. |
||
| 366 |
* |
||
| 367 |
* This version handles multiproofs in calldata with the default hashing function. |
||
| 368 |
* |
||
| 369 |
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
||
| 370 |
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
||
| 371 |
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
||
| 372 |
* |
||
| 373 |
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
||
| 374 |
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
||
| 375 |
* validating the leaves elsewhere. |
||
| 376 |
*/ |
||
| 377 |
function processMultiProofCalldata( |
||
| 378 |
bytes32[] calldata proof, |
||
| 379 |
bool[] calldata proofFlags, |
||
| 380 |
bytes32[] memory leaves |
||
| 381 |
) internal pure returns (bytes32 merkleRoot) {
|
||
| 382 |
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
||
| 383 |
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
||
| 384 |
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
||
| 385 |
// the Merkle tree. |
||
| 386 |
uint256 leavesLen = leaves.length; |
||
| 387 |
uint256 proofFlagsLen = proofFlags.length; |
||
| 388 | |||
| 389 |
// Check proof validity. |
||
| 390 |
if (leavesLen + proof.length != proofFlagsLen + 1) {
|
||
| 391 |
revert MerkleProofInvalidMultiproof(); |
||
| 392 |
} |
||
| 393 | |||
| 394 |
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
||
| 395 |
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
||
| 396 |
bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
||
| 397 |
uint256 leafPos = 0; |
||
| 398 |
uint256 hashPos = 0; |
||
| 399 |
uint256 proofPos = 0; |
||
| 400 |
// At each step, we compute the next hash using two values: |
||
| 401 |
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
||
| 402 |
// get the next hash. |
||
| 403 |
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
||
| 404 |
// `proof` array. |
||
| 405 |
for (uint256 i = 0; i < proofFlagsLen; i++) {
|
||
| 406 |
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
||
| 407 |
bytes32 b = proofFlags[i] |
||
| 408 |
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
||
| 409 |
: proof[proofPos++]; |
||
| 410 |
hashes[i] = Hashes.commutativeKeccak256(a, b); |
||
| 411 |
} |
||
| 412 | |||
| 413 |
if (proofFlagsLen > 0) {
|
||
| 414 |
if (proofPos != proof.length) {
|
||
| 415 |
revert MerkleProofInvalidMultiproof(); |
||
| 416 |
} |
||
| 417 |
unchecked {
|
||
| 418 |
return hashes[proofFlagsLen - 1]; |
||
| 419 |
} |
||
| 420 |
} else if (leavesLen > 0) {
|
||
| 421 |
return leaves[0]; |
||
| 422 |
} else {
|
||
| 423 |
return proof[0]; |
||
| 424 |
} |
||
| 425 |
} |
||
| 426 | |||
| 427 |
/** |
||
| 428 |
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by |
||
| 429 |
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
|
||
| 430 |
* |
||
| 431 |
* This version handles multiproofs in calldata with a custom hashing function. |
||
| 432 |
* |
||
| 433 |
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
|
||
| 434 |
* |
||
| 435 |
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. |
||
| 436 |
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
|
||
| 437 |
*/ |
||
| 438 |
function multiProofVerifyCalldata( |
||
| 439 |
bytes32[] calldata proof, |
||
| 440 |
bool[] calldata proofFlags, |
||
| 441 |
bytes32 root, |
||
| 442 |
bytes32[] memory leaves, |
||
| 443 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 444 |
) internal view returns (bool) {
|
||
| 445 |
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root; |
||
| 446 |
} |
||
| 447 | |||
| 448 |
/** |
||
| 449 |
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction |
||
| 450 |
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another |
||
| 451 |
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false |
||
| 452 |
* respectively. |
||
| 453 |
* |
||
| 454 |
* This version handles multiproofs in calldata with a custom hashing function. |
||
| 455 |
* |
||
| 456 |
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree |
||
| 457 |
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the |
||
| 458 |
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
||
| 459 |
* |
||
| 460 |
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, |
||
| 461 |
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not |
||
| 462 |
* validating the leaves elsewhere. |
||
| 463 |
*/ |
||
| 464 |
function processMultiProofCalldata( |
||
| 465 |
bytes32[] calldata proof, |
||
| 466 |
bool[] calldata proofFlags, |
||
| 467 |
bytes32[] memory leaves, |
||
| 468 |
function(bytes32, bytes32) view returns (bytes32) hasher |
||
| 469 |
) internal view returns (bytes32 merkleRoot) {
|
||
| 470 |
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by |
||
| 471 |
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the |
||
| 472 |
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of |
||
| 473 |
// the Merkle tree. |
||
| 474 |
uint256 leavesLen = leaves.length; |
||
| 475 |
uint256 proofFlagsLen = proofFlags.length; |
||
| 476 | |||
| 477 |
// Check proof validity. |
||
| 478 |
if (leavesLen + proof.length != proofFlagsLen + 1) {
|
||
| 479 |
revert MerkleProofInvalidMultiproof(); |
||
| 480 |
} |
||
| 481 | |||
| 482 |
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using |
||
| 483 |
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". |
||
| 484 |
bytes32[] memory hashes = new bytes32[](proofFlagsLen); |
||
| 485 |
uint256 leafPos = 0; |
||
| 486 |
uint256 hashPos = 0; |
||
| 487 |
uint256 proofPos = 0; |
||
| 488 |
// At each step, we compute the next hash using two values: |
||
| 489 |
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we |
||
| 490 |
// get the next hash. |
||
| 491 |
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the |
||
| 492 |
// `proof` array. |
||
| 493 |
for (uint256 i = 0; i < proofFlagsLen; i++) {
|
||
| 494 |
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; |
||
| 495 |
bytes32 b = proofFlags[i] |
||
| 496 |
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) |
||
| 497 |
: proof[proofPos++]; |
||
| 498 |
hashes[i] = hasher(a, b); |
||
| 499 |
} |
||
| 500 | |||
| 501 |
if (proofFlagsLen > 0) {
|
||
| 502 |
if (proofPos != proof.length) {
|
||
| 503 |
revert MerkleProofInvalidMultiproof(); |
||
| 504 |
} |
||
| 505 |
unchecked {
|
||
| 506 |
return hashes[proofFlagsLen - 1]; |
||
| 507 |
} |
||
| 508 |
} else if (leavesLen > 0) {
|
||
| 509 |
return leaves[0]; |
||
| 510 |
} else {
|
||
| 511 |
return proof[0]; |
||
| 512 |
} |
||
| 513 |
} |
||
| 514 |
} |
||
| 515 |
16.7%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol
Lines covered: 1 / 6 (16.7%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {Strings} from "../Strings.sol";
|
||
| 7 | |||
| 8 |
/** |
||
| 9 |
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
|
||
| 10 |
* |
||
| 11 |
* The library provides methods for generating a hash of a message that conforms to the |
||
| 12 |
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] |
||
| 13 |
* specifications. |
||
| 14 |
*/ |
||
| 15 |
✓ 21.0K
|
⟲ 21.0K
|
library MessageHashUtils {
|
| 16 |
/** |
||
| 17 |
* @dev Returns the keccak256 digest of an ERC-191 signed data with version |
||
| 18 |
* `0x45` (`personal_sign` messages). |
||
| 19 |
* |
||
| 20 |
* The digest is calculated by prefixing a bytes32 `messageHash` with |
||
| 21 |
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the |
||
| 22 |
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method. |
||
| 23 |
* |
||
| 24 |
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with |
||
| 25 |
* keccak256, although any bytes32 value can be safely used because the final digest will |
||
| 26 |
* be re-hashed. |
||
| 27 |
* |
||
| 28 |
* See {ECDSA-recover}.
|
||
| 29 |
*/ |
||
| 30 |
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
|
||
| 31 |
assembly ("memory-safe") {
|
||
| 32 |
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash |
||
| 33 |
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix |
||
| 34 |
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) |
||
| 35 |
} |
||
| 36 |
} |
||
| 37 | |||
| 38 |
/** |
||
| 39 |
* @dev Returns the keccak256 digest of an ERC-191 signed data with version |
||
| 40 |
* `0x45` (`personal_sign` messages). |
||
| 41 |
* |
||
| 42 |
* The digest is calculated by prefixing an arbitrary `message` with |
||
| 43 |
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the |
||
| 44 |
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method. |
||
| 45 |
* |
||
| 46 |
* See {ECDSA-recover}.
|
||
| 47 |
*/ |
||
| 48 |
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
|
||
| 49 |
return |
||
| 50 |
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
|
||
| 51 |
} |
||
| 52 | |||
| 53 |
/** |
||
| 54 |
* @dev Returns the keccak256 digest of an ERC-191 signed data with version |
||
| 55 |
* `0x00` (data with intended validator). |
||
| 56 |
* |
||
| 57 |
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended |
||
| 58 |
* `validator` address. Then hashing the result. |
||
| 59 |
* |
||
| 60 |
* See {ECDSA-recover}.
|
||
| 61 |
*/ |
||
| 62 |
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
|
||
| 63 |
return keccak256(abi.encodePacked(hex"19_00", validator, data)); |
||
| 64 |
} |
||
| 65 | |||
| 66 |
/** |
||
| 67 |
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
|
||
| 68 |
*/ |
||
| 69 |
function toDataWithIntendedValidatorHash( |
||
| 70 |
address validator, |
||
| 71 |
bytes32 messageHash |
||
| 72 |
) internal pure returns (bytes32 digest) {
|
||
| 73 |
assembly ("memory-safe") {
|
||
| 74 |
mstore(0x00, hex"19_00") |
||
| 75 |
mstore(0x02, shl(96, validator)) |
||
| 76 |
mstore(0x16, messageHash) |
||
| 77 |
digest := keccak256(0x00, 0x36) |
||
| 78 |
} |
||
| 79 |
} |
||
| 80 | |||
| 81 |
/** |
||
| 82 |
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). |
||
| 83 |
* |
||
| 84 |
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with |
||
| 85 |
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the |
||
| 86 |
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. |
||
| 87 |
* |
||
| 88 |
* See {ECDSA-recover}.
|
||
| 89 |
*/ |
||
| 90 |
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
|
||
| 91 |
assembly ("memory-safe") {
|
||
| 92 |
let ptr := mload(0x40) |
||
| 93 |
mstore(ptr, hex"19_01") |
||
| 94 |
mstore(add(ptr, 0x02), domainSeparator) |
||
| 95 |
mstore(add(ptr, 0x22), structHash) |
||
| 96 |
digest := keccak256(ptr, 0x42) |
||
| 97 |
} |
||
| 98 |
} |
||
| 99 |
} |
||
| 100 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol
Lines covered: 0 / 1 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {IERC165} from "./IERC165.sol";
|
||
| 7 | |||
| 8 |
/** |
||
| 9 |
* @dev Implementation of the {IERC165} interface.
|
||
| 10 |
* |
||
| 11 |
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
|
||
| 12 |
* for the additional interface id that will be supported. For example: |
||
| 13 |
* |
||
| 14 |
* ```solidity |
||
| 15 |
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
||
| 16 |
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); |
||
| 17 |
* } |
||
| 18 |
* ``` |
||
| 19 |
*/ |
||
| 20 |
abstract contract ERC165 is IERC165 {
|
||
| 21 |
/// @inheritdoc IERC165 |
||
| 22 |
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
|
||
| 23 |
return interfaceId == type(IERC165).interfaceId; |
||
| 24 |
} |
||
| 25 |
} |
||
| 26 |
0.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) |
||
| 3 | |||
| 4 |
pragma solidity >=0.4.16; |
||
| 5 | |||
| 6 |
/** |
||
| 7 |
* @dev Interface of the ERC-165 standard, as defined in the |
||
| 8 |
* https://eips.ethereum.org/EIPS/eip-165[ERC]. |
||
| 9 |
* |
||
| 10 |
* Implementers can declare support of contract interfaces, which can then be |
||
| 11 |
* queried by others ({ERC165Checker}).
|
||
| 12 |
* |
||
| 13 |
* For an implementation, see {ERC165}.
|
||
| 14 |
*/ |
||
| 15 |
interface IERC165 {
|
||
| 16 |
/** |
||
| 17 |
* @dev Returns true if this contract implements the interface defined by |
||
| 18 |
* `interfaceId`. See the corresponding |
||
| 19 |
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] |
||
| 20 |
* to learn more about how these ids are created. |
||
| 21 |
* |
||
| 22 |
* This function call must use less than 30 000 gas. |
||
| 23 |
*/ |
||
| 24 |
function supportsInterface(bytes4 interfaceId) external view returns (bool); |
||
| 25 |
} |
||
| 26 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
Lines covered: 33 / 33 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {Panic} from "../Panic.sol";
|
||
| 7 |
import {SafeCast} from "./SafeCast.sol";
|
||
| 8 | |||
| 9 |
/** |
||
| 10 |
* @dev Standard math utilities missing in the Solidity language. |
||
| 11 |
*/ |
||
| 12 |
✓ 48.7K
|
⟲ 48.7K
|
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 |
✓ 13.0M
|
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 |
✓ 13.0M
|
let mm := mulmod(a, b, not(0)) |
|
| 43 |
✓ 13.0M
|
low := mul(a, b) |
|
| 44 |
✓ 13.0M
|
high := sub(sub(mm, low), lt(mm, low)) |
|
| 45 |
} |
||
| 46 |
} |
||
| 47 | |||
| 48 |
/** |
||
| 49 |
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow). |
||
| 50 |
*/ |
||
| 51 |
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
|
||
| 52 |
unchecked {
|
||
| 53 |
uint256 c = a + b; |
||
| 54 |
success = c >= a; |
||
| 55 |
result = c * SafeCast.toUint(success); |
||
| 56 |
} |
||
| 57 |
} |
||
| 58 | |||
| 59 |
/** |
||
| 60 |
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow). |
||
| 61 |
*/ |
||
| 62 |
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
|
||
| 63 |
unchecked {
|
||
| 64 |
uint256 c = a - b; |
||
| 65 |
success = c <= a; |
||
| 66 |
result = c * SafeCast.toUint(success); |
||
| 67 |
} |
||
| 68 |
} |
||
| 69 | |||
| 70 |
/** |
||
| 71 |
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow). |
||
| 72 |
*/ |
||
| 73 |
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
|
||
| 74 |
unchecked {
|
||
| 75 |
uint256 c = a * b; |
||
| 76 |
assembly ("memory-safe") {
|
||
| 77 |
// Only true when the multiplication doesn't overflow |
||
| 78 |
// (c / a == b) || (a == 0) |
||
| 79 |
success := or(eq(div(c, a), b), iszero(a)) |
||
| 80 |
} |
||
| 81 |
// equivalent to: success ? c : 0 |
||
| 82 |
result = c * SafeCast.toUint(success); |
||
| 83 |
} |
||
| 84 |
} |
||
| 85 | |||
| 86 |
/** |
||
| 87 |
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero). |
||
| 88 |
*/ |
||
| 89 |
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
|
||
| 90 |
unchecked {
|
||
| 91 |
success = b > 0; |
||
| 92 |
assembly ("memory-safe") {
|
||
| 93 |
// The `DIV` opcode returns zero when the denominator is 0. |
||
| 94 |
result := div(a, b) |
||
| 95 |
} |
||
| 96 |
} |
||
| 97 |
} |
||
| 98 | |||
| 99 |
/** |
||
| 100 |
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). |
||
| 101 |
*/ |
||
| 102 |
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
|
||
| 103 |
unchecked {
|
||
| 104 |
success = b > 0; |
||
| 105 |
assembly ("memory-safe") {
|
||
| 106 |
// The `MOD` opcode returns zero when the denominator is 0. |
||
| 107 |
result := mod(a, b) |
||
| 108 |
} |
||
| 109 |
} |
||
| 110 |
} |
||
| 111 | |||
| 112 |
/** |
||
| 113 |
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing. |
||
| 114 |
*/ |
||
| 115 |
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 116 |
(bool success, uint256 result) = tryAdd(a, b); |
||
| 117 |
return ternary(success, result, type(uint256).max); |
||
| 118 |
} |
||
| 119 | |||
| 120 |
/** |
||
| 121 |
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing. |
||
| 122 |
*/ |
||
| 123 |
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 124 |
(, uint256 result) = trySub(a, b); |
||
| 125 |
return result; |
||
| 126 |
} |
||
| 127 | |||
| 128 |
/** |
||
| 129 |
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing. |
||
| 130 |
*/ |
||
| 131 |
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 132 |
(bool success, uint256 result) = tryMul(a, b); |
||
| 133 |
return ternary(success, result, type(uint256).max); |
||
| 134 |
} |
||
| 135 | |||
| 136 |
/** |
||
| 137 |
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. |
||
| 138 |
* |
||
| 139 |
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. |
||
| 140 |
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute |
||
| 141 |
* one branch when needed, making this function more expensive. |
||
| 142 |
*/ |
||
| 143 |
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 144 |
unchecked {
|
||
| 145 |
// branchless ternary works because: |
||
| 146 |
// b ^ (a ^ b) == a |
||
| 147 |
// b ^ 0 == b |
||
| 148 |
✓ 206.6K
|
return b ^ ((a ^ b) * SafeCast.toUint(condition)); |
|
| 149 |
} |
||
| 150 |
} |
||
| 151 | |||
| 152 |
/** |
||
| 153 |
* @dev Returns the largest of two numbers. |
||
| 154 |
*/ |
||
| 155 |
function max(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 156 |
return ternary(a > b, a, b); |
||
| 157 |
} |
||
| 158 | |||
| 159 |
/** |
||
| 160 |
* @dev Returns the smallest of two numbers. |
||
| 161 |
*/ |
||
| 162 |
function min(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 163 |
return ternary(a < b, a, b); |
||
| 164 |
} |
||
| 165 | |||
| 166 |
/** |
||
| 167 |
* @dev Returns the average of two numbers. The result is rounded towards |
||
| 168 |
* zero. |
||
| 169 |
*/ |
||
| 170 |
function average(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 171 |
// (a + b) / 2 can overflow. |
||
| 172 |
return (a & b) + (a ^ b) / 2; |
||
| 173 |
} |
||
| 174 | |||
| 175 |
/** |
||
| 176 |
* @dev Returns the ceiling of the division of two numbers. |
||
| 177 |
* |
||
| 178 |
* This differs from standard division with `/` in that it rounds towards infinity instead |
||
| 179 |
* of rounding towards zero. |
||
| 180 |
*/ |
||
| 181 |
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
| 182 |
if (b == 0) {
|
||
| 183 |
// Guarantee the same behavior as in a regular Solidity division. |
||
| 184 |
Panic.panic(Panic.DIVISION_BY_ZERO); |
||
| 185 |
} |
||
| 186 | |||
| 187 |
// The following calculation ensures accurate ceiling division without overflow. |
||
| 188 |
// Since a is non-zero, (a - 1) / b will not overflow. |
||
| 189 |
// The largest possible result occurs when (a - 1) / b is type(uint256).max, |
||
| 190 |
// but the largest value we can obtain is type(uint256).max - 1, which happens |
||
| 191 |
// when a = type(uint256).max and b = 1. |
||
| 192 |
unchecked {
|
||
| 193 |
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); |
||
| 194 |
} |
||
| 195 |
} |
||
| 196 | |||
| 197 |
/** |
||
| 198 |
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or |
||
| 199 |
* denominator == 0. |
||
| 200 |
* |
||
| 201 |
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by |
||
| 202 |
* Uniswap Labs also under MIT license. |
||
| 203 |
*/ |
||
| 204 |
✓ 13.0M
|
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
|
|
| 205 |
unchecked {
|
||
| 206 |
✓ 13.0M
|
(uint256 high, uint256 low) = mul512(x, y); |
|
| 207 | |||
| 208 |
// Handle non-overflow cases, 256 by 256 division. |
||
| 209 |
✓ 13.0M
|
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 |
✓ 10.0M
|
return low / denominator; |
|
| 214 |
} |
||
| 215 | |||
| 216 |
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. |
||
| 217 |
✓ 3.0M
|
if (denominator <= high) {
|
|
| 218 |
✓ 206.6K
|
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 |
✓ 2.8M
|
uint256 remainder; |
|
| 227 |
assembly ("memory-safe") {
|
||
| 228 |
// Compute remainder using mulmod. |
||
| 229 |
✓ 2.8M
|
remainder := mulmod(x, y, denominator) |
|
| 230 | |||
| 231 |
// Subtract 256 bit number from 512 bit number. |
||
| 232 |
✓ 2.8M
|
high := sub(high, gt(remainder, low)) |
|
| 233 |
✓ 2.8M
|
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 |
✓ 2.8M
|
uint256 twos = denominator & (0 - denominator); |
|
| 240 |
assembly ("memory-safe") {
|
||
| 241 |
// Divide denominator by twos. |
||
| 242 |
✓ 2.8M
|
denominator := div(denominator, twos) |
|
| 243 | |||
| 244 |
// Divide [high low] by twos. |
||
| 245 |
✓ 2.8M
|
low := div(low, twos) |
|
| 246 | |||
| 247 |
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. |
||
| 248 |
✓ 2.8M
|
twos := add(div(sub(0, twos), twos), 1) |
|
| 249 |
} |
||
| 250 | |||
| 251 |
// Shift in bits from high into low. |
||
| 252 |
✓ 2.8M
|
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 |
✓ 2.8M
|
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 |
✓ 2.8M
|
inverse *= 2 - denominator * inverse; // inverse mod 2⁸ |
|
| 262 |
✓ 2.8M
|
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ |
|
| 263 |
✓ 2.8M
|
inverse *= 2 - denominator * inverse; // inverse mod 2³² |
|
| 264 |
✓ 2.8M
|
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ |
|
| 265 |
✓ 2.8M
|
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ |
|
| 266 |
✓ 2.8M
|
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 |
✓ 2.8M
|
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 |
✓ 13.0M
|
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
|
|
| 281 |
✓ 13.0M
|
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0); |
|
| 282 |
} |
||
| 283 | |||
| 284 |
/** |
||
| 285 |
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256. |
||
| 286 |
*/ |
||
| 287 |
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
|
||
| 288 |
unchecked {
|
||
| 289 |
(uint256 high, uint256 low) = mul512(x, y); |
||
| 290 |
if (high >= 1 << n) {
|
||
| 291 |
Panic.panic(Panic.UNDER_OVERFLOW); |
||
| 292 |
} |
||
| 293 |
return (high << (256 - n)) | (low >> n); |
||
| 294 |
} |
||
| 295 |
} |
||
| 296 | |||
| 297 |
/** |
||
| 298 |
* @dev Calculates x * y >> n with full precision, following the selected rounding direction. |
||
| 299 |
*/ |
||
| 300 |
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
|
||
| 301 |
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0); |
||
| 302 |
} |
||
| 303 | |||
| 304 |
/** |
||
| 305 |
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ. |
||
| 306 |
* |
||
| 307 |
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0. |
||
| 308 |
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. |
||
| 309 |
* |
||
| 310 |
* If the input value is not inversible, 0 is returned. |
||
| 311 |
* |
||
| 312 |
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the |
||
| 313 |
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
|
||
| 314 |
*/ |
||
| 315 |
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
|
||
| 316 |
unchecked {
|
||
| 317 |
if (n == 0) return 0; |
||
| 318 | |||
| 319 |
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version) |
||
| 320 |
// Used to compute integers x and y such that: ax + ny = gcd(a, n). |
||
| 321 |
// When the gcd is 1, then the inverse of a modulo n exists and it's x. |
||
| 322 |
// ax + ny = 1 |
||
| 323 |
// ax = 1 + (-y)n |
||
| 324 |
// ax ≡ 1 (mod n) # x is the inverse of a modulo n |
||
| 325 | |||
| 326 |
// If the remainder is 0 the gcd is n right away. |
||
| 327 |
uint256 remainder = a % n; |
||
| 328 |
uint256 gcd = n; |
||
| 329 | |||
| 330 |
// Therefore the initial coefficients are: |
||
| 331 |
// ax + ny = gcd(a, n) = n |
||
| 332 |
// 0a + 1n = n |
||
| 333 |
int256 x = 0; |
||
| 334 |
int256 y = 1; |
||
| 335 | |||
| 336 |
while (remainder != 0) {
|
||
| 337 |
uint256 quotient = gcd / remainder; |
||
| 338 | |||
| 339 |
(gcd, remainder) = ( |
||
| 340 |
// The old remainder is the next gcd to try. |
||
| 341 |
remainder, |
||
| 342 |
// Compute the next remainder. |
||
| 343 |
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd |
||
| 344 |
// where gcd is at most n (capped to type(uint256).max) |
||
| 345 |
gcd - remainder * quotient |
||
| 346 |
); |
||
| 347 | |||
| 348 |
(x, y) = ( |
||
| 349 |
// Increment the coefficient of a. |
||
| 350 |
y, |
||
| 351 |
// Decrement the coefficient of n. |
||
| 352 |
// Can overflow, but the result is casted to uint256 so that the |
||
| 353 |
// next value of y is "wrapped around" to a value between 0 and n - 1. |
||
| 354 |
x - y * int256(quotient) |
||
| 355 |
); |
||
| 356 |
} |
||
| 357 | |||
| 358 |
if (gcd != 1) return 0; // No inverse exists. |
||
| 359 |
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. |
||
| 360 |
} |
||
| 361 |
} |
||
| 362 | |||
| 363 |
/** |
||
| 364 |
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
|
||
| 365 |
* |
||
| 366 |
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is |
||
| 367 |
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that |
||
| 368 |
* `a**(p-2)` is the modular multiplicative inverse of a in Fp. |
||
| 369 |
* |
||
| 370 |
* NOTE: this function does NOT check that `p` is a prime greater than `2`. |
||
| 371 |
*/ |
||
| 372 |
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
|
||
| 373 |
unchecked {
|
||
| 374 |
return Math.modExp(a, p - 2, p); |
||
| 375 |
} |
||
| 376 |
} |
||
| 377 | |||
| 378 |
/** |
||
| 379 |
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) |
||
| 380 |
* |
||
| 381 |
* Requirements: |
||
| 382 |
* - modulus can't be zero |
||
| 383 |
* - underlying staticcall to precompile must succeed |
||
| 384 |
* |
||
| 385 |
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make |
||
| 386 |
* sure the chain you're using it on supports the precompiled contract for modular exponentiation |
||
| 387 |
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, |
||
| 388 |
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly |
||
| 389 |
* interpreted as 0. |
||
| 390 |
*/ |
||
| 391 |
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
|
||
| 392 |
(bool success, uint256 result) = tryModExp(b, e, m); |
||
| 393 |
if (!success) {
|
||
| 394 |
Panic.panic(Panic.DIVISION_BY_ZERO); |
||
| 395 |
} |
||
| 396 |
return result; |
||
| 397 |
} |
||
| 398 | |||
| 399 |
/** |
||
| 400 |
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). |
||
| 401 |
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying |
||
| 402 |
* to operate modulo 0 or if the underlying precompile reverted. |
||
| 403 |
* |
||
| 404 |
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain |
||
| 405 |
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in |
||
| 406 |
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack |
||
| 407 |
* of a revert, but the result may be incorrectly interpreted as 0. |
||
| 408 |
*/ |
||
| 409 |
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
|
||
| 410 |
if (m == 0) return (false, 0); |
||
| 411 |
assembly ("memory-safe") {
|
||
| 412 |
let ptr := mload(0x40) |
||
| 413 |
// | Offset | Content | Content (Hex) | |
||
| 414 |
// |-----------|------------|--------------------------------------------------------------------| |
||
| 415 |
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
||
| 416 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
||
| 417 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 | |
||
| 418 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> | |
||
| 419 |
// | 0x80:0x9f | value of e | 0x<.............................................................e> | |
||
| 420 |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> | |
||
| 421 |
mstore(ptr, 0x20) |
||
| 422 |
mstore(add(ptr, 0x20), 0x20) |
||
| 423 |
mstore(add(ptr, 0x40), 0x20) |
||
| 424 |
mstore(add(ptr, 0x60), b) |
||
| 425 |
mstore(add(ptr, 0x80), e) |
||
| 426 |
mstore(add(ptr, 0xa0), m) |
||
| 427 | |||
| 428 |
// Given the result < m, it's guaranteed to fit in 32 bytes, |
||
| 429 |
// so we can use the memory scratch space located at offset 0. |
||
| 430 |
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20) |
||
| 431 |
result := mload(0x00) |
||
| 432 |
} |
||
| 433 |
} |
||
| 434 | |||
| 435 |
/** |
||
| 436 |
* @dev Variant of {modExp} that supports inputs of arbitrary length.
|
||
| 437 |
*/ |
||
| 438 |
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
|
||
| 439 |
(bool success, bytes memory result) = tryModExp(b, e, m); |
||
| 440 |
if (!success) {
|
||
| 441 |
Panic.panic(Panic.DIVISION_BY_ZERO); |
||
| 442 |
} |
||
| 443 |
return result; |
||
| 444 |
} |
||
| 445 | |||
| 446 |
/** |
||
| 447 |
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
|
||
| 448 |
*/ |
||
| 449 |
function tryModExp( |
||
| 450 |
bytes memory b, |
||
| 451 |
bytes memory e, |
||
| 452 |
bytes memory m |
||
| 453 |
) internal view returns (bool success, bytes memory result) {
|
||
| 454 |
if (_zeroBytes(m)) return (false, new bytes(0)); |
||
| 455 | |||
| 456 |
uint256 mLen = m.length; |
||
| 457 | |||
| 458 |
// Encode call args in result and move the free memory pointer |
||
| 459 |
result = abi.encodePacked(b.length, e.length, mLen, b, e, m); |
||
| 460 | |||
| 461 |
assembly ("memory-safe") {
|
||
| 462 |
let dataPtr := add(result, 0x20) |
||
| 463 |
// Write result on top of args to avoid allocating extra memory. |
||
| 464 |
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) |
||
| 465 |
// Overwrite the length. |
||
| 466 |
// result.length > returndatasize() is guaranteed because returndatasize() == m.length |
||
| 467 |
mstore(result, mLen) |
||
| 468 |
// Set the memory pointer after the returned data. |
||
| 469 |
mstore(0x40, add(dataPtr, mLen)) |
||
| 470 |
} |
||
| 471 |
} |
||
| 472 | |||
| 473 |
/** |
||
| 474 |
* @dev Returns whether the provided byte array is zero. |
||
| 475 |
*/ |
||
| 476 |
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
|
||
| 477 |
for (uint256 i = 0; i < byteArray.length; ++i) {
|
||
| 478 |
if (byteArray[i] != 0) {
|
||
| 479 |
return false; |
||
| 480 |
} |
||
| 481 |
} |
||
| 482 |
return true; |
||
| 483 |
} |
||
| 484 | |||
| 485 |
/** |
||
| 486 |
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded |
||
| 487 |
* towards zero. |
||
| 488 |
* |
||
| 489 |
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only |
||
| 490 |
* using integer operations. |
||
| 491 |
*/ |
||
| 492 |
function sqrt(uint256 a) internal pure returns (uint256) {
|
||
| 493 |
unchecked {
|
||
| 494 |
// Take care of easy edge cases when a == 0 or a == 1 |
||
| 495 |
if (a <= 1) {
|
||
| 496 |
return a; |
||
| 497 |
} |
||
| 498 | |||
| 499 |
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a |
||
| 500 |
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between |
||
| 501 |
// the current value as `ε_n = | x_n - sqrt(a) |`. |
||
| 502 |
// |
||
| 503 |
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root |
||
| 504 |
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is |
||
| 505 |
// bigger than any uint256. |
||
| 506 |
// |
||
| 507 |
// By noticing that |
||
| 508 |
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` |
||
| 509 |
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar |
||
| 510 |
// to the msb function. |
||
| 511 |
uint256 aa = a; |
||
| 512 |
uint256 xn = 1; |
||
| 513 | |||
| 514 |
if (aa >= (1 << 128)) {
|
||
| 515 |
aa >>= 128; |
||
| 516 |
xn <<= 64; |
||
| 517 |
} |
||
| 518 |
if (aa >= (1 << 64)) {
|
||
| 519 |
aa >>= 64; |
||
| 520 |
xn <<= 32; |
||
| 521 |
} |
||
| 522 |
if (aa >= (1 << 32)) {
|
||
| 523 |
aa >>= 32; |
||
| 524 |
xn <<= 16; |
||
| 525 |
} |
||
| 526 |
if (aa >= (1 << 16)) {
|
||
| 527 |
aa >>= 16; |
||
| 528 |
xn <<= 8; |
||
| 529 |
} |
||
| 530 |
if (aa >= (1 << 8)) {
|
||
| 531 |
aa >>= 8; |
||
| 532 |
xn <<= 4; |
||
| 533 |
} |
||
| 534 |
if (aa >= (1 << 4)) {
|
||
| 535 |
aa >>= 4; |
||
| 536 |
xn <<= 2; |
||
| 537 |
} |
||
| 538 |
if (aa >= (1 << 2)) {
|
||
| 539 |
xn <<= 1; |
||
| 540 |
} |
||
| 541 | |||
| 542 |
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). |
||
| 543 |
// |
||
| 544 |
// We can refine our estimation by noticing that the middle of that interval minimizes the error. |
||
| 545 |
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). |
||
| 546 |
// This is going to be our x_0 (and ε_0) |
||
| 547 |
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) |
||
| 548 | |||
| 549 |
// From here, Newton's method give us: |
||
| 550 |
// x_{n+1} = (x_n + a / x_n) / 2
|
||
| 551 |
// |
||
| 552 |
// One should note that: |
||
| 553 |
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
|
||
| 554 |
// = ((x_n² + a) / (2 * x_n))² - a |
||
| 555 |
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a |
||
| 556 |
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) |
||
| 557 |
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) |
||
| 558 |
// = (x_n² - a)² / (2 * x_n)² |
||
| 559 |
// = ((x_n² - a) / (2 * x_n))² |
||
| 560 |
// ≥ 0 |
||
| 561 |
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n |
||
| 562 |
// |
||
| 563 |
// This gives us the proof of quadratic convergence of the sequence: |
||
| 564 |
// ε_{n+1} = | x_{n+1} - sqrt(a) |
|
||
| 565 |
// = | (x_n + a / x_n) / 2 - sqrt(a) | |
||
| 566 |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | |
||
| 567 |
// = | (x_n - sqrt(a))² / (2 * x_n) | |
||
| 568 |
// = | ε_n² / (2 * x_n) | |
||
| 569 |
// = ε_n² / | (2 * x_n) | |
||
| 570 |
// |
||
| 571 |
// For the first iteration, we have a special case where x_0 is known: |
||
| 572 |
// ε_1 = ε_0² / | (2 * x_0) | |
||
| 573 |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) |
||
| 574 |
// ≤ 2**(2*e-4) / (3 * 2**(e-1)) |
||
| 575 |
// ≤ 2**(e-3) / 3 |
||
| 576 |
// ≤ 2**(e-3-log2(3)) |
||
| 577 |
// ≤ 2**(e-4.5) |
||
| 578 |
// |
||
| 579 |
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: |
||
| 580 |
// ε_{n+1} = ε_n² / | (2 * x_n) |
|
||
| 581 |
// ≤ (2**(e-k))² / (2 * 2**(e-1)) |
||
| 582 |
// ≤ 2**(2*e-2*k) / 2**e |
||
| 583 |
// ≤ 2**(e-2*k) |
||
| 584 |
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above |
||
| 585 |
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 |
||
| 586 |
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 |
||
| 587 |
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 |
||
| 588 |
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 |
||
| 589 |
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 |
||
| 590 | |||
| 591 |
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision |
||
| 592 |
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either |
||
| 593 |
// sqrt(a) or sqrt(a) + 1. |
||
| 594 |
return xn - SafeCast.toUint(xn > a / xn); |
||
| 595 |
} |
||
| 596 |
} |
||
| 597 | |||
| 598 |
/** |
||
| 599 |
* @dev Calculates sqrt(a), following the selected rounding direction. |
||
| 600 |
*/ |
||
| 601 |
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
|
||
| 602 |
unchecked {
|
||
| 603 |
uint256 result = sqrt(a); |
||
| 604 |
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a); |
||
| 605 |
} |
||
| 606 |
} |
||
| 607 | |||
| 608 |
/** |
||
| 609 |
* @dev Return the log in base 2 of a positive value rounded towards zero. |
||
| 610 |
* Returns 0 if given 0. |
||
| 611 |
*/ |
||
| 612 |
function log2(uint256 x) internal pure returns (uint256 r) {
|
||
| 613 |
// If value has upper 128 bits set, log2 result is at least 128 |
||
| 614 |
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; |
||
| 615 |
// If upper 64 bits of 128-bit half set, add 64 to result |
||
| 616 |
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; |
||
| 617 |
// If upper 32 bits of 64-bit half set, add 32 to result |
||
| 618 |
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; |
||
| 619 |
// If upper 16 bits of 32-bit half set, add 16 to result |
||
| 620 |
r |= SafeCast.toUint((x >> r) > 0xffff) << 4; |
||
| 621 |
// If upper 8 bits of 16-bit half set, add 8 to result |
||
| 622 |
r |= SafeCast.toUint((x >> r) > 0xff) << 3; |
||
| 623 |
// If upper 4 bits of 8-bit half set, add 4 to result |
||
| 624 |
r |= SafeCast.toUint((x >> r) > 0xf) << 2; |
||
| 625 | |||
| 626 |
// Shifts value right by the current result and use it as an index into this lookup table: |
||
| 627 |
// |
||
| 628 |
// | x (4 bits) | index | table[index] = MSB position | |
||
| 629 |
// |------------|---------|-----------------------------| |
||
| 630 |
// | 0000 | 0 | table[0] = 0 | |
||
| 631 |
// | 0001 | 1 | table[1] = 0 | |
||
| 632 |
// | 0010 | 2 | table[2] = 1 | |
||
| 633 |
// | 0011 | 3 | table[3] = 1 | |
||
| 634 |
// | 0100 | 4 | table[4] = 2 | |
||
| 635 |
// | 0101 | 5 | table[5] = 2 | |
||
| 636 |
// | 0110 | 6 | table[6] = 2 | |
||
| 637 |
// | 0111 | 7 | table[7] = 2 | |
||
| 638 |
// | 1000 | 8 | table[8] = 3 | |
||
| 639 |
// | 1001 | 9 | table[9] = 3 | |
||
| 640 |
// | 1010 | 10 | table[10] = 3 | |
||
| 641 |
// | 1011 | 11 | table[11] = 3 | |
||
| 642 |
// | 1100 | 12 | table[12] = 3 | |
||
| 643 |
// | 1101 | 13 | table[13] = 3 | |
||
| 644 |
// | 1110 | 14 | table[14] = 3 | |
||
| 645 |
// | 1111 | 15 | table[15] = 3 | |
||
| 646 |
// |
||
| 647 |
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes. |
||
| 648 |
assembly ("memory-safe") {
|
||
| 649 |
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000)) |
||
| 650 |
} |
||
| 651 |
} |
||
| 652 | |||
| 653 |
/** |
||
| 654 |
* @dev Return the log in base 2, following the selected rounding direction, of a positive value. |
||
| 655 |
* Returns 0 if given 0. |
||
| 656 |
*/ |
||
| 657 |
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
|
||
| 658 |
unchecked {
|
||
| 659 |
uint256 result = log2(value); |
||
| 660 |
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value); |
||
| 661 |
} |
||
| 662 |
} |
||
| 663 | |||
| 664 |
/** |
||
| 665 |
* @dev Return the log in base 10 of a positive value rounded towards zero. |
||
| 666 |
* Returns 0 if given 0. |
||
| 667 |
*/ |
||
| 668 |
function log10(uint256 value) internal pure returns (uint256) {
|
||
| 669 |
uint256 result = 0; |
||
| 670 |
unchecked {
|
||
| 671 |
if (value >= 10 ** 64) {
|
||
| 672 |
value /= 10 ** 64; |
||
| 673 |
result += 64; |
||
| 674 |
} |
||
| 675 |
if (value >= 10 ** 32) {
|
||
| 676 |
value /= 10 ** 32; |
||
| 677 |
result += 32; |
||
| 678 |
} |
||
| 679 |
if (value >= 10 ** 16) {
|
||
| 680 |
value /= 10 ** 16; |
||
| 681 |
result += 16; |
||
| 682 |
} |
||
| 683 |
if (value >= 10 ** 8) {
|
||
| 684 |
value /= 10 ** 8; |
||
| 685 |
result += 8; |
||
| 686 |
} |
||
| 687 |
if (value >= 10 ** 4) {
|
||
| 688 |
value /= 10 ** 4; |
||
| 689 |
result += 4; |
||
| 690 |
} |
||
| 691 |
if (value >= 10 ** 2) {
|
||
| 692 |
value /= 10 ** 2; |
||
| 693 |
result += 2; |
||
| 694 |
} |
||
| 695 |
if (value >= 10 ** 1) {
|
||
| 696 |
result += 1; |
||
| 697 |
} |
||
| 698 |
} |
||
| 699 |
return result; |
||
| 700 |
} |
||
| 701 | |||
| 702 |
/** |
||
| 703 |
* @dev Return the log in base 10, following the selected rounding direction, of a positive value. |
||
| 704 |
* Returns 0 if given 0. |
||
| 705 |
*/ |
||
| 706 |
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
|
||
| 707 |
unchecked {
|
||
| 708 |
uint256 result = log10(value); |
||
| 709 |
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value); |
||
| 710 |
} |
||
| 711 |
} |
||
| 712 | |||
| 713 |
/** |
||
| 714 |
* @dev Return the log in base 256 of a positive value rounded towards zero. |
||
| 715 |
* Returns 0 if given 0. |
||
| 716 |
* |
||
| 717 |
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. |
||
| 718 |
*/ |
||
| 719 |
function log256(uint256 x) internal pure returns (uint256 r) {
|
||
| 720 |
// If value has upper 128 bits set, log2 result is at least 128 |
||
| 721 |
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; |
||
| 722 |
// If upper 64 bits of 128-bit half set, add 64 to result |
||
| 723 |
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; |
||
| 724 |
// If upper 32 bits of 64-bit half set, add 32 to result |
||
| 725 |
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; |
||
| 726 |
// If upper 16 bits of 32-bit half set, add 16 to result |
||
| 727 |
r |= SafeCast.toUint((x >> r) > 0xffff) << 4; |
||
| 728 |
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8 |
||
| 729 |
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff); |
||
| 730 |
} |
||
| 731 | |||
| 732 |
/** |
||
| 733 |
* @dev Return the log in base 256, following the selected rounding direction, of a positive value. |
||
| 734 |
* Returns 0 if given 0. |
||
| 735 |
*/ |
||
| 736 |
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
|
||
| 737 |
unchecked {
|
||
| 738 |
uint256 result = log256(value); |
||
| 739 |
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value); |
||
| 740 |
} |
||
| 741 |
} |
||
| 742 | |||
| 743 |
/** |
||
| 744 |
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. |
||
| 745 |
*/ |
||
| 746 |
✓ 13.0M
|
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
|
|
| 747 |
✓ 13.0M
|
return uint8(rounding) % 2 == 1; |
|
| 748 |
} |
||
| 749 |
} |
||
| 750 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
Lines covered: 2 / 2 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol) |
||
| 3 |
// This file was procedurally generated from scripts/generate/templates/SafeCast.js. |
||
| 4 | |||
| 5 |
pragma solidity ^0.8.20; |
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow |
||
| 9 |
* checks. |
||
| 10 |
* |
||
| 11 |
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can |
||
| 12 |
* easily result in undesired exploitation or bugs, since developers usually |
||
| 13 |
* assume that overflows raise errors. `SafeCast` restores this intuition by |
||
| 14 |
* reverting the transaction when such an operation overflows. |
||
| 15 |
* |
||
| 16 |
* Using this library instead of the unchecked operations eliminates an entire |
||
| 17 |
* class of bugs, so it's recommended to use it always. |
||
| 18 |
*/ |
||
| 19 |
✓ 37.9K
|
⟲ 37.9K
|
library SafeCast {
|
| 20 |
/** |
||
| 21 |
* @dev Value doesn't fit in an uint of `bits` size. |
||
| 22 |
*/ |
||
| 23 |
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev An int value doesn't fit in an uint of `bits` size. |
||
| 27 |
*/ |
||
| 28 |
error SafeCastOverflowedIntToUint(int256 value); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev Value doesn't fit in an int of `bits` size. |
||
| 32 |
*/ |
||
| 33 |
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); |
||
| 34 | |||
| 35 |
/** |
||
| 36 |
* @dev An uint value doesn't fit in an int of `bits` size. |
||
| 37 |
*/ |
||
| 38 |
error SafeCastOverflowedUintToInt(uint256 value); |
||
| 39 | |||
| 40 |
/** |
||
| 41 |
* @dev Returns the downcasted uint248 from uint256, reverting on |
||
| 42 |
* overflow (when the input is greater than largest uint248). |
||
| 43 |
* |
||
| 44 |
* Counterpart to Solidity's `uint248` operator. |
||
| 45 |
* |
||
| 46 |
* Requirements: |
||
| 47 |
* |
||
| 48 |
* - input must fit into 248 bits |
||
| 49 |
*/ |
||
| 50 |
function toUint248(uint256 value) internal pure returns (uint248) {
|
||
| 51 |
if (value > type(uint248).max) {
|
||
| 52 |
revert SafeCastOverflowedUintDowncast(248, value); |
||
| 53 |
} |
||
| 54 |
return uint248(value); |
||
| 55 |
} |
||
| 56 | |||
| 57 |
/** |
||
| 58 |
* @dev Returns the downcasted uint240 from uint256, reverting on |
||
| 59 |
* overflow (when the input is greater than largest uint240). |
||
| 60 |
* |
||
| 61 |
* Counterpart to Solidity's `uint240` operator. |
||
| 62 |
* |
||
| 63 |
* Requirements: |
||
| 64 |
* |
||
| 65 |
* - input must fit into 240 bits |
||
| 66 |
*/ |
||
| 67 |
function toUint240(uint256 value) internal pure returns (uint240) {
|
||
| 68 |
if (value > type(uint240).max) {
|
||
| 69 |
revert SafeCastOverflowedUintDowncast(240, value); |
||
| 70 |
} |
||
| 71 |
return uint240(value); |
||
| 72 |
} |
||
| 73 | |||
| 74 |
/** |
||
| 75 |
* @dev Returns the downcasted uint232 from uint256, reverting on |
||
| 76 |
* overflow (when the input is greater than largest uint232). |
||
| 77 |
* |
||
| 78 |
* Counterpart to Solidity's `uint232` operator. |
||
| 79 |
* |
||
| 80 |
* Requirements: |
||
| 81 |
* |
||
| 82 |
* - input must fit into 232 bits |
||
| 83 |
*/ |
||
| 84 |
function toUint232(uint256 value) internal pure returns (uint232) {
|
||
| 85 |
if (value > type(uint232).max) {
|
||
| 86 |
revert SafeCastOverflowedUintDowncast(232, value); |
||
| 87 |
} |
||
| 88 |
return uint232(value); |
||
| 89 |
} |
||
| 90 | |||
| 91 |
/** |
||
| 92 |
* @dev Returns the downcasted uint224 from uint256, reverting on |
||
| 93 |
* overflow (when the input is greater than largest uint224). |
||
| 94 |
* |
||
| 95 |
* Counterpart to Solidity's `uint224` operator. |
||
| 96 |
* |
||
| 97 |
* Requirements: |
||
| 98 |
* |
||
| 99 |
* - input must fit into 224 bits |
||
| 100 |
*/ |
||
| 101 |
function toUint224(uint256 value) internal pure returns (uint224) {
|
||
| 102 |
if (value > type(uint224).max) {
|
||
| 103 |
revert SafeCastOverflowedUintDowncast(224, value); |
||
| 104 |
} |
||
| 105 |
return uint224(value); |
||
| 106 |
} |
||
| 107 | |||
| 108 |
/** |
||
| 109 |
* @dev Returns the downcasted uint216 from uint256, reverting on |
||
| 110 |
* overflow (when the input is greater than largest uint216). |
||
| 111 |
* |
||
| 112 |
* Counterpart to Solidity's `uint216` operator. |
||
| 113 |
* |
||
| 114 |
* Requirements: |
||
| 115 |
* |
||
| 116 |
* - input must fit into 216 bits |
||
| 117 |
*/ |
||
| 118 |
function toUint216(uint256 value) internal pure returns (uint216) {
|
||
| 119 |
if (value > type(uint216).max) {
|
||
| 120 |
revert SafeCastOverflowedUintDowncast(216, value); |
||
| 121 |
} |
||
| 122 |
return uint216(value); |
||
| 123 |
} |
||
| 124 | |||
| 125 |
/** |
||
| 126 |
* @dev Returns the downcasted uint208 from uint256, reverting on |
||
| 127 |
* overflow (when the input is greater than largest uint208). |
||
| 128 |
* |
||
| 129 |
* Counterpart to Solidity's `uint208` operator. |
||
| 130 |
* |
||
| 131 |
* Requirements: |
||
| 132 |
* |
||
| 133 |
* - input must fit into 208 bits |
||
| 134 |
*/ |
||
| 135 |
function toUint208(uint256 value) internal pure returns (uint208) {
|
||
| 136 |
if (value > type(uint208).max) {
|
||
| 137 |
revert SafeCastOverflowedUintDowncast(208, value); |
||
| 138 |
} |
||
| 139 |
return uint208(value); |
||
| 140 |
} |
||
| 141 | |||
| 142 |
/** |
||
| 143 |
* @dev Returns the downcasted uint200 from uint256, reverting on |
||
| 144 |
* overflow (when the input is greater than largest uint200). |
||
| 145 |
* |
||
| 146 |
* Counterpart to Solidity's `uint200` operator. |
||
| 147 |
* |
||
| 148 |
* Requirements: |
||
| 149 |
* |
||
| 150 |
* - input must fit into 200 bits |
||
| 151 |
*/ |
||
| 152 |
function toUint200(uint256 value) internal pure returns (uint200) {
|
||
| 153 |
if (value > type(uint200).max) {
|
||
| 154 |
revert SafeCastOverflowedUintDowncast(200, value); |
||
| 155 |
} |
||
| 156 |
return uint200(value); |
||
| 157 |
} |
||
| 158 | |||
| 159 |
/** |
||
| 160 |
* @dev Returns the downcasted uint192 from uint256, reverting on |
||
| 161 |
* overflow (when the input is greater than largest uint192). |
||
| 162 |
* |
||
| 163 |
* Counterpart to Solidity's `uint192` operator. |
||
| 164 |
* |
||
| 165 |
* Requirements: |
||
| 166 |
* |
||
| 167 |
* - input must fit into 192 bits |
||
| 168 |
*/ |
||
| 169 |
function toUint192(uint256 value) internal pure returns (uint192) {
|
||
| 170 |
if (value > type(uint192).max) {
|
||
| 171 |
revert SafeCastOverflowedUintDowncast(192, value); |
||
| 172 |
} |
||
| 173 |
return uint192(value); |
||
| 174 |
} |
||
| 175 | |||
| 176 |
/** |
||
| 177 |
* @dev Returns the downcasted uint184 from uint256, reverting on |
||
| 178 |
* overflow (when the input is greater than largest uint184). |
||
| 179 |
* |
||
| 180 |
* Counterpart to Solidity's `uint184` operator. |
||
| 181 |
* |
||
| 182 |
* Requirements: |
||
| 183 |
* |
||
| 184 |
* - input must fit into 184 bits |
||
| 185 |
*/ |
||
| 186 |
function toUint184(uint256 value) internal pure returns (uint184) {
|
||
| 187 |
if (value > type(uint184).max) {
|
||
| 188 |
revert SafeCastOverflowedUintDowncast(184, value); |
||
| 189 |
} |
||
| 190 |
return uint184(value); |
||
| 191 |
} |
||
| 192 | |||
| 193 |
/** |
||
| 194 |
* @dev Returns the downcasted uint176 from uint256, reverting on |
||
| 195 |
* overflow (when the input is greater than largest uint176). |
||
| 196 |
* |
||
| 197 |
* Counterpart to Solidity's `uint176` operator. |
||
| 198 |
* |
||
| 199 |
* Requirements: |
||
| 200 |
* |
||
| 201 |
* - input must fit into 176 bits |
||
| 202 |
*/ |
||
| 203 |
function toUint176(uint256 value) internal pure returns (uint176) {
|
||
| 204 |
if (value > type(uint176).max) {
|
||
| 205 |
revert SafeCastOverflowedUintDowncast(176, value); |
||
| 206 |
} |
||
| 207 |
return uint176(value); |
||
| 208 |
} |
||
| 209 | |||
| 210 |
/** |
||
| 211 |
* @dev Returns the downcasted uint168 from uint256, reverting on |
||
| 212 |
* overflow (when the input is greater than largest uint168). |
||
| 213 |
* |
||
| 214 |
* Counterpart to Solidity's `uint168` operator. |
||
| 215 |
* |
||
| 216 |
* Requirements: |
||
| 217 |
* |
||
| 218 |
* - input must fit into 168 bits |
||
| 219 |
*/ |
||
| 220 |
function toUint168(uint256 value) internal pure returns (uint168) {
|
||
| 221 |
if (value > type(uint168).max) {
|
||
| 222 |
revert SafeCastOverflowedUintDowncast(168, value); |
||
| 223 |
} |
||
| 224 |
return uint168(value); |
||
| 225 |
} |
||
| 226 | |||
| 227 |
/** |
||
| 228 |
* @dev Returns the downcasted uint160 from uint256, reverting on |
||
| 229 |
* overflow (when the input is greater than largest uint160). |
||
| 230 |
* |
||
| 231 |
* Counterpart to Solidity's `uint160` operator. |
||
| 232 |
* |
||
| 233 |
* Requirements: |
||
| 234 |
* |
||
| 235 |
* - input must fit into 160 bits |
||
| 236 |
*/ |
||
| 237 |
function toUint160(uint256 value) internal pure returns (uint160) {
|
||
| 238 |
if (value > type(uint160).max) {
|
||
| 239 |
revert SafeCastOverflowedUintDowncast(160, value); |
||
| 240 |
} |
||
| 241 |
return uint160(value); |
||
| 242 |
} |
||
| 243 | |||
| 244 |
/** |
||
| 245 |
* @dev Returns the downcasted uint152 from uint256, reverting on |
||
| 246 |
* overflow (when the input is greater than largest uint152). |
||
| 247 |
* |
||
| 248 |
* Counterpart to Solidity's `uint152` operator. |
||
| 249 |
* |
||
| 250 |
* Requirements: |
||
| 251 |
* |
||
| 252 |
* - input must fit into 152 bits |
||
| 253 |
*/ |
||
| 254 |
function toUint152(uint256 value) internal pure returns (uint152) {
|
||
| 255 |
if (value > type(uint152).max) {
|
||
| 256 |
revert SafeCastOverflowedUintDowncast(152, value); |
||
| 257 |
} |
||
| 258 |
return uint152(value); |
||
| 259 |
} |
||
| 260 | |||
| 261 |
/** |
||
| 262 |
* @dev Returns the downcasted uint144 from uint256, reverting on |
||
| 263 |
* overflow (when the input is greater than largest uint144). |
||
| 264 |
* |
||
| 265 |
* Counterpart to Solidity's `uint144` operator. |
||
| 266 |
* |
||
| 267 |
* Requirements: |
||
| 268 |
* |
||
| 269 |
* - input must fit into 144 bits |
||
| 270 |
*/ |
||
| 271 |
function toUint144(uint256 value) internal pure returns (uint144) {
|
||
| 272 |
if (value > type(uint144).max) {
|
||
| 273 |
revert SafeCastOverflowedUintDowncast(144, value); |
||
| 274 |
} |
||
| 275 |
return uint144(value); |
||
| 276 |
} |
||
| 277 | |||
| 278 |
/** |
||
| 279 |
* @dev Returns the downcasted uint136 from uint256, reverting on |
||
| 280 |
* overflow (when the input is greater than largest uint136). |
||
| 281 |
* |
||
| 282 |
* Counterpart to Solidity's `uint136` operator. |
||
| 283 |
* |
||
| 284 |
* Requirements: |
||
| 285 |
* |
||
| 286 |
* - input must fit into 136 bits |
||
| 287 |
*/ |
||
| 288 |
function toUint136(uint256 value) internal pure returns (uint136) {
|
||
| 289 |
if (value > type(uint136).max) {
|
||
| 290 |
revert SafeCastOverflowedUintDowncast(136, value); |
||
| 291 |
} |
||
| 292 |
return uint136(value); |
||
| 293 |
} |
||
| 294 | |||
| 295 |
/** |
||
| 296 |
* @dev Returns the downcasted uint128 from uint256, reverting on |
||
| 297 |
* overflow (when the input is greater than largest uint128). |
||
| 298 |
* |
||
| 299 |
* Counterpart to Solidity's `uint128` operator. |
||
| 300 |
* |
||
| 301 |
* Requirements: |
||
| 302 |
* |
||
| 303 |
* - input must fit into 128 bits |
||
| 304 |
*/ |
||
| 305 |
function toUint128(uint256 value) internal pure returns (uint128) {
|
||
| 306 |
if (value > type(uint128).max) {
|
||
| 307 |
revert SafeCastOverflowedUintDowncast(128, value); |
||
| 308 |
} |
||
| 309 |
return uint128(value); |
||
| 310 |
} |
||
| 311 | |||
| 312 |
/** |
||
| 313 |
* @dev Returns the downcasted uint120 from uint256, reverting on |
||
| 314 |
* overflow (when the input is greater than largest uint120). |
||
| 315 |
* |
||
| 316 |
* Counterpart to Solidity's `uint120` operator. |
||
| 317 |
* |
||
| 318 |
* Requirements: |
||
| 319 |
* |
||
| 320 |
* - input must fit into 120 bits |
||
| 321 |
*/ |
||
| 322 |
function toUint120(uint256 value) internal pure returns (uint120) {
|
||
| 323 |
if (value > type(uint120).max) {
|
||
| 324 |
revert SafeCastOverflowedUintDowncast(120, value); |
||
| 325 |
} |
||
| 326 |
return uint120(value); |
||
| 327 |
} |
||
| 328 | |||
| 329 |
/** |
||
| 330 |
* @dev Returns the downcasted uint112 from uint256, reverting on |
||
| 331 |
* overflow (when the input is greater than largest uint112). |
||
| 332 |
* |
||
| 333 |
* Counterpart to Solidity's `uint112` operator. |
||
| 334 |
* |
||
| 335 |
* Requirements: |
||
| 336 |
* |
||
| 337 |
* - input must fit into 112 bits |
||
| 338 |
*/ |
||
| 339 |
function toUint112(uint256 value) internal pure returns (uint112) {
|
||
| 340 |
if (value > type(uint112).max) {
|
||
| 341 |
revert SafeCastOverflowedUintDowncast(112, value); |
||
| 342 |
} |
||
| 343 |
return uint112(value); |
||
| 344 |
} |
||
| 345 | |||
| 346 |
/** |
||
| 347 |
* @dev Returns the downcasted uint104 from uint256, reverting on |
||
| 348 |
* overflow (when the input is greater than largest uint104). |
||
| 349 |
* |
||
| 350 |
* Counterpart to Solidity's `uint104` operator. |
||
| 351 |
* |
||
| 352 |
* Requirements: |
||
| 353 |
* |
||
| 354 |
* - input must fit into 104 bits |
||
| 355 |
*/ |
||
| 356 |
function toUint104(uint256 value) internal pure returns (uint104) {
|
||
| 357 |
if (value > type(uint104).max) {
|
||
| 358 |
revert SafeCastOverflowedUintDowncast(104, value); |
||
| 359 |
} |
||
| 360 |
return uint104(value); |
||
| 361 |
} |
||
| 362 | |||
| 363 |
/** |
||
| 364 |
* @dev Returns the downcasted uint96 from uint256, reverting on |
||
| 365 |
* overflow (when the input is greater than largest uint96). |
||
| 366 |
* |
||
| 367 |
* Counterpart to Solidity's `uint96` operator. |
||
| 368 |
* |
||
| 369 |
* Requirements: |
||
| 370 |
* |
||
| 371 |
* - input must fit into 96 bits |
||
| 372 |
*/ |
||
| 373 |
function toUint96(uint256 value) internal pure returns (uint96) {
|
||
| 374 |
if (value > type(uint96).max) {
|
||
| 375 |
revert SafeCastOverflowedUintDowncast(96, value); |
||
| 376 |
} |
||
| 377 |
return uint96(value); |
||
| 378 |
} |
||
| 379 | |||
| 380 |
/** |
||
| 381 |
* @dev Returns the downcasted uint88 from uint256, reverting on |
||
| 382 |
* overflow (when the input is greater than largest uint88). |
||
| 383 |
* |
||
| 384 |
* Counterpart to Solidity's `uint88` operator. |
||
| 385 |
* |
||
| 386 |
* Requirements: |
||
| 387 |
* |
||
| 388 |
* - input must fit into 88 bits |
||
| 389 |
*/ |
||
| 390 |
function toUint88(uint256 value) internal pure returns (uint88) {
|
||
| 391 |
if (value > type(uint88).max) {
|
||
| 392 |
revert SafeCastOverflowedUintDowncast(88, value); |
||
| 393 |
} |
||
| 394 |
return uint88(value); |
||
| 395 |
} |
||
| 396 | |||
| 397 |
/** |
||
| 398 |
* @dev Returns the downcasted uint80 from uint256, reverting on |
||
| 399 |
* overflow (when the input is greater than largest uint80). |
||
| 400 |
* |
||
| 401 |
* Counterpart to Solidity's `uint80` operator. |
||
| 402 |
* |
||
| 403 |
* Requirements: |
||
| 404 |
* |
||
| 405 |
* - input must fit into 80 bits |
||
| 406 |
*/ |
||
| 407 |
function toUint80(uint256 value) internal pure returns (uint80) {
|
||
| 408 |
if (value > type(uint80).max) {
|
||
| 409 |
revert SafeCastOverflowedUintDowncast(80, value); |
||
| 410 |
} |
||
| 411 |
return uint80(value); |
||
| 412 |
} |
||
| 413 | |||
| 414 |
/** |
||
| 415 |
* @dev Returns the downcasted uint72 from uint256, reverting on |
||
| 416 |
* overflow (when the input is greater than largest uint72). |
||
| 417 |
* |
||
| 418 |
* Counterpart to Solidity's `uint72` operator. |
||
| 419 |
* |
||
| 420 |
* Requirements: |
||
| 421 |
* |
||
| 422 |
* - input must fit into 72 bits |
||
| 423 |
*/ |
||
| 424 |
function toUint72(uint256 value) internal pure returns (uint72) {
|
||
| 425 |
if (value > type(uint72).max) {
|
||
| 426 |
revert SafeCastOverflowedUintDowncast(72, value); |
||
| 427 |
} |
||
| 428 |
return uint72(value); |
||
| 429 |
} |
||
| 430 | |||
| 431 |
/** |
||
| 432 |
* @dev Returns the downcasted uint64 from uint256, reverting on |
||
| 433 |
* overflow (when the input is greater than largest uint64). |
||
| 434 |
* |
||
| 435 |
* Counterpart to Solidity's `uint64` operator. |
||
| 436 |
* |
||
| 437 |
* Requirements: |
||
| 438 |
* |
||
| 439 |
* - input must fit into 64 bits |
||
| 440 |
*/ |
||
| 441 |
function toUint64(uint256 value) internal pure returns (uint64) {
|
||
| 442 |
if (value > type(uint64).max) {
|
||
| 443 |
revert SafeCastOverflowedUintDowncast(64, value); |
||
| 444 |
} |
||
| 445 |
return uint64(value); |
||
| 446 |
} |
||
| 447 | |||
| 448 |
/** |
||
| 449 |
* @dev Returns the downcasted uint56 from uint256, reverting on |
||
| 450 |
* overflow (when the input is greater than largest uint56). |
||
| 451 |
* |
||
| 452 |
* Counterpart to Solidity's `uint56` operator. |
||
| 453 |
* |
||
| 454 |
* Requirements: |
||
| 455 |
* |
||
| 456 |
* - input must fit into 56 bits |
||
| 457 |
*/ |
||
| 458 |
function toUint56(uint256 value) internal pure returns (uint56) {
|
||
| 459 |
if (value > type(uint56).max) {
|
||
| 460 |
revert SafeCastOverflowedUintDowncast(56, value); |
||
| 461 |
} |
||
| 462 |
return uint56(value); |
||
| 463 |
} |
||
| 464 | |||
| 465 |
/** |
||
| 466 |
* @dev Returns the downcasted uint48 from uint256, reverting on |
||
| 467 |
* overflow (when the input is greater than largest uint48). |
||
| 468 |
* |
||
| 469 |
* Counterpart to Solidity's `uint48` operator. |
||
| 470 |
* |
||
| 471 |
* Requirements: |
||
| 472 |
* |
||
| 473 |
* - input must fit into 48 bits |
||
| 474 |
*/ |
||
| 475 |
function toUint48(uint256 value) internal pure returns (uint48) {
|
||
| 476 |
if (value > type(uint48).max) {
|
||
| 477 |
revert SafeCastOverflowedUintDowncast(48, value); |
||
| 478 |
} |
||
| 479 |
return uint48(value); |
||
| 480 |
} |
||
| 481 | |||
| 482 |
/** |
||
| 483 |
* @dev Returns the downcasted uint40 from uint256, reverting on |
||
| 484 |
* overflow (when the input is greater than largest uint40). |
||
| 485 |
* |
||
| 486 |
* Counterpart to Solidity's `uint40` operator. |
||
| 487 |
* |
||
| 488 |
* Requirements: |
||
| 489 |
* |
||
| 490 |
* - input must fit into 40 bits |
||
| 491 |
*/ |
||
| 492 |
function toUint40(uint256 value) internal pure returns (uint40) {
|
||
| 493 |
if (value > type(uint40).max) {
|
||
| 494 |
revert SafeCastOverflowedUintDowncast(40, value); |
||
| 495 |
} |
||
| 496 |
return uint40(value); |
||
| 497 |
} |
||
| 498 | |||
| 499 |
/** |
||
| 500 |
* @dev Returns the downcasted uint32 from uint256, reverting on |
||
| 501 |
* overflow (when the input is greater than largest uint32). |
||
| 502 |
* |
||
| 503 |
* Counterpart to Solidity's `uint32` operator. |
||
| 504 |
* |
||
| 505 |
* Requirements: |
||
| 506 |
* |
||
| 507 |
* - input must fit into 32 bits |
||
| 508 |
*/ |
||
| 509 |
function toUint32(uint256 value) internal pure returns (uint32) {
|
||
| 510 |
if (value > type(uint32).max) {
|
||
| 511 |
revert SafeCastOverflowedUintDowncast(32, value); |
||
| 512 |
} |
||
| 513 |
return uint32(value); |
||
| 514 |
} |
||
| 515 | |||
| 516 |
/** |
||
| 517 |
* @dev Returns the downcasted uint24 from uint256, reverting on |
||
| 518 |
* overflow (when the input is greater than largest uint24). |
||
| 519 |
* |
||
| 520 |
* Counterpart to Solidity's `uint24` operator. |
||
| 521 |
* |
||
| 522 |
* Requirements: |
||
| 523 |
* |
||
| 524 |
* - input must fit into 24 bits |
||
| 525 |
*/ |
||
| 526 |
function toUint24(uint256 value) internal pure returns (uint24) {
|
||
| 527 |
if (value > type(uint24).max) {
|
||
| 528 |
revert SafeCastOverflowedUintDowncast(24, value); |
||
| 529 |
} |
||
| 530 |
return uint24(value); |
||
| 531 |
} |
||
| 532 | |||
| 533 |
/** |
||
| 534 |
* @dev Returns the downcasted uint16 from uint256, reverting on |
||
| 535 |
* overflow (when the input is greater than largest uint16). |
||
| 536 |
* |
||
| 537 |
* Counterpart to Solidity's `uint16` operator. |
||
| 538 |
* |
||
| 539 |
* Requirements: |
||
| 540 |
* |
||
| 541 |
* - input must fit into 16 bits |
||
| 542 |
*/ |
||
| 543 |
function toUint16(uint256 value) internal pure returns (uint16) {
|
||
| 544 |
if (value > type(uint16).max) {
|
||
| 545 |
revert SafeCastOverflowedUintDowncast(16, value); |
||
| 546 |
} |
||
| 547 |
return uint16(value); |
||
| 548 |
} |
||
| 549 | |||
| 550 |
/** |
||
| 551 |
* @dev Returns the downcasted uint8 from uint256, reverting on |
||
| 552 |
* overflow (when the input is greater than largest uint8). |
||
| 553 |
* |
||
| 554 |
* Counterpart to Solidity's `uint8` operator. |
||
| 555 |
* |
||
| 556 |
* Requirements: |
||
| 557 |
* |
||
| 558 |
* - input must fit into 8 bits |
||
| 559 |
*/ |
||
| 560 |
function toUint8(uint256 value) internal pure returns (uint8) {
|
||
| 561 |
if (value > type(uint8).max) {
|
||
| 562 |
revert SafeCastOverflowedUintDowncast(8, value); |
||
| 563 |
} |
||
| 564 |
return uint8(value); |
||
| 565 |
} |
||
| 566 | |||
| 567 |
/** |
||
| 568 |
* @dev Converts a signed int256 into an unsigned uint256. |
||
| 569 |
* |
||
| 570 |
* Requirements: |
||
| 571 |
* |
||
| 572 |
* - input must be greater than or equal to 0. |
||
| 573 |
*/ |
||
| 574 |
function toUint256(int256 value) internal pure returns (uint256) {
|
||
| 575 |
if (value < 0) {
|
||
| 576 |
revert SafeCastOverflowedIntToUint(value); |
||
| 577 |
} |
||
| 578 |
return uint256(value); |
||
| 579 |
} |
||
| 580 | |||
| 581 |
/** |
||
| 582 |
* @dev Returns the downcasted int248 from int256, reverting on |
||
| 583 |
* overflow (when the input is less than smallest int248 or |
||
| 584 |
* greater than largest int248). |
||
| 585 |
* |
||
| 586 |
* Counterpart to Solidity's `int248` operator. |
||
| 587 |
* |
||
| 588 |
* Requirements: |
||
| 589 |
* |
||
| 590 |
* - input must fit into 248 bits |
||
| 591 |
*/ |
||
| 592 |
function toInt248(int256 value) internal pure returns (int248 downcasted) {
|
||
| 593 |
downcasted = int248(value); |
||
| 594 |
if (downcasted != value) {
|
||
| 595 |
revert SafeCastOverflowedIntDowncast(248, value); |
||
| 596 |
} |
||
| 597 |
} |
||
| 598 | |||
| 599 |
/** |
||
| 600 |
* @dev Returns the downcasted int240 from int256, reverting on |
||
| 601 |
* overflow (when the input is less than smallest int240 or |
||
| 602 |
* greater than largest int240). |
||
| 603 |
* |
||
| 604 |
* Counterpart to Solidity's `int240` operator. |
||
| 605 |
* |
||
| 606 |
* Requirements: |
||
| 607 |
* |
||
| 608 |
* - input must fit into 240 bits |
||
| 609 |
*/ |
||
| 610 |
function toInt240(int256 value) internal pure returns (int240 downcasted) {
|
||
| 611 |
downcasted = int240(value); |
||
| 612 |
if (downcasted != value) {
|
||
| 613 |
revert SafeCastOverflowedIntDowncast(240, value); |
||
| 614 |
} |
||
| 615 |
} |
||
| 616 | |||
| 617 |
/** |
||
| 618 |
* @dev Returns the downcasted int232 from int256, reverting on |
||
| 619 |
* overflow (when the input is less than smallest int232 or |
||
| 620 |
* greater than largest int232). |
||
| 621 |
* |
||
| 622 |
* Counterpart to Solidity's `int232` operator. |
||
| 623 |
* |
||
| 624 |
* Requirements: |
||
| 625 |
* |
||
| 626 |
* - input must fit into 232 bits |
||
| 627 |
*/ |
||
| 628 |
function toInt232(int256 value) internal pure returns (int232 downcasted) {
|
||
| 629 |
downcasted = int232(value); |
||
| 630 |
if (downcasted != value) {
|
||
| 631 |
revert SafeCastOverflowedIntDowncast(232, value); |
||
| 632 |
} |
||
| 633 |
} |
||
| 634 | |||
| 635 |
/** |
||
| 636 |
* @dev Returns the downcasted int224 from int256, reverting on |
||
| 637 |
* overflow (when the input is less than smallest int224 or |
||
| 638 |
* greater than largest int224). |
||
| 639 |
* |
||
| 640 |
* Counterpart to Solidity's `int224` operator. |
||
| 641 |
* |
||
| 642 |
* Requirements: |
||
| 643 |
* |
||
| 644 |
* - input must fit into 224 bits |
||
| 645 |
*/ |
||
| 646 |
function toInt224(int256 value) internal pure returns (int224 downcasted) {
|
||
| 647 |
downcasted = int224(value); |
||
| 648 |
if (downcasted != value) {
|
||
| 649 |
revert SafeCastOverflowedIntDowncast(224, value); |
||
| 650 |
} |
||
| 651 |
} |
||
| 652 | |||
| 653 |
/** |
||
| 654 |
* @dev Returns the downcasted int216 from int256, reverting on |
||
| 655 |
* overflow (when the input is less than smallest int216 or |
||
| 656 |
* greater than largest int216). |
||
| 657 |
* |
||
| 658 |
* Counterpart to Solidity's `int216` operator. |
||
| 659 |
* |
||
| 660 |
* Requirements: |
||
| 661 |
* |
||
| 662 |
* - input must fit into 216 bits |
||
| 663 |
*/ |
||
| 664 |
function toInt216(int256 value) internal pure returns (int216 downcasted) {
|
||
| 665 |
downcasted = int216(value); |
||
| 666 |
if (downcasted != value) {
|
||
| 667 |
revert SafeCastOverflowedIntDowncast(216, value); |
||
| 668 |
} |
||
| 669 |
} |
||
| 670 | |||
| 671 |
/** |
||
| 672 |
* @dev Returns the downcasted int208 from int256, reverting on |
||
| 673 |
* overflow (when the input is less than smallest int208 or |
||
| 674 |
* greater than largest int208). |
||
| 675 |
* |
||
| 676 |
* Counterpart to Solidity's `int208` operator. |
||
| 677 |
* |
||
| 678 |
* Requirements: |
||
| 679 |
* |
||
| 680 |
* - input must fit into 208 bits |
||
| 681 |
*/ |
||
| 682 |
function toInt208(int256 value) internal pure returns (int208 downcasted) {
|
||
| 683 |
downcasted = int208(value); |
||
| 684 |
if (downcasted != value) {
|
||
| 685 |
revert SafeCastOverflowedIntDowncast(208, value); |
||
| 686 |
} |
||
| 687 |
} |
||
| 688 | |||
| 689 |
/** |
||
| 690 |
* @dev Returns the downcasted int200 from int256, reverting on |
||
| 691 |
* overflow (when the input is less than smallest int200 or |
||
| 692 |
* greater than largest int200). |
||
| 693 |
* |
||
| 694 |
* Counterpart to Solidity's `int200` operator. |
||
| 695 |
* |
||
| 696 |
* Requirements: |
||
| 697 |
* |
||
| 698 |
* - input must fit into 200 bits |
||
| 699 |
*/ |
||
| 700 |
function toInt200(int256 value) internal pure returns (int200 downcasted) {
|
||
| 701 |
downcasted = int200(value); |
||
| 702 |
if (downcasted != value) {
|
||
| 703 |
revert SafeCastOverflowedIntDowncast(200, value); |
||
| 704 |
} |
||
| 705 |
} |
||
| 706 | |||
| 707 |
/** |
||
| 708 |
* @dev Returns the downcasted int192 from int256, reverting on |
||
| 709 |
* overflow (when the input is less than smallest int192 or |
||
| 710 |
* greater than largest int192). |
||
| 711 |
* |
||
| 712 |
* Counterpart to Solidity's `int192` operator. |
||
| 713 |
* |
||
| 714 |
* Requirements: |
||
| 715 |
* |
||
| 716 |
* - input must fit into 192 bits |
||
| 717 |
*/ |
||
| 718 |
function toInt192(int256 value) internal pure returns (int192 downcasted) {
|
||
| 719 |
downcasted = int192(value); |
||
| 720 |
if (downcasted != value) {
|
||
| 721 |
revert SafeCastOverflowedIntDowncast(192, value); |
||
| 722 |
} |
||
| 723 |
} |
||
| 724 | |||
| 725 |
/** |
||
| 726 |
* @dev Returns the downcasted int184 from int256, reverting on |
||
| 727 |
* overflow (when the input is less than smallest int184 or |
||
| 728 |
* greater than largest int184). |
||
| 729 |
* |
||
| 730 |
* Counterpart to Solidity's `int184` operator. |
||
| 731 |
* |
||
| 732 |
* Requirements: |
||
| 733 |
* |
||
| 734 |
* - input must fit into 184 bits |
||
| 735 |
*/ |
||
| 736 |
function toInt184(int256 value) internal pure returns (int184 downcasted) {
|
||
| 737 |
downcasted = int184(value); |
||
| 738 |
if (downcasted != value) {
|
||
| 739 |
revert SafeCastOverflowedIntDowncast(184, value); |
||
| 740 |
} |
||
| 741 |
} |
||
| 742 | |||
| 743 |
/** |
||
| 744 |
* @dev Returns the downcasted int176 from int256, reverting on |
||
| 745 |
* overflow (when the input is less than smallest int176 or |
||
| 746 |
* greater than largest int176). |
||
| 747 |
* |
||
| 748 |
* Counterpart to Solidity's `int176` operator. |
||
| 749 |
* |
||
| 750 |
* Requirements: |
||
| 751 |
* |
||
| 752 |
* - input must fit into 176 bits |
||
| 753 |
*/ |
||
| 754 |
function toInt176(int256 value) internal pure returns (int176 downcasted) {
|
||
| 755 |
downcasted = int176(value); |
||
| 756 |
if (downcasted != value) {
|
||
| 757 |
revert SafeCastOverflowedIntDowncast(176, value); |
||
| 758 |
} |
||
| 759 |
} |
||
| 760 | |||
| 761 |
/** |
||
| 762 |
* @dev Returns the downcasted int168 from int256, reverting on |
||
| 763 |
* overflow (when the input is less than smallest int168 or |
||
| 764 |
* greater than largest int168). |
||
| 765 |
* |
||
| 766 |
* Counterpart to Solidity's `int168` operator. |
||
| 767 |
* |
||
| 768 |
* Requirements: |
||
| 769 |
* |
||
| 770 |
* - input must fit into 168 bits |
||
| 771 |
*/ |
||
| 772 |
function toInt168(int256 value) internal pure returns (int168 downcasted) {
|
||
| 773 |
downcasted = int168(value); |
||
| 774 |
if (downcasted != value) {
|
||
| 775 |
revert SafeCastOverflowedIntDowncast(168, value); |
||
| 776 |
} |
||
| 777 |
} |
||
| 778 | |||
| 779 |
/** |
||
| 780 |
* @dev Returns the downcasted int160 from int256, reverting on |
||
| 781 |
* overflow (when the input is less than smallest int160 or |
||
| 782 |
* greater than largest int160). |
||
| 783 |
* |
||
| 784 |
* Counterpart to Solidity's `int160` operator. |
||
| 785 |
* |
||
| 786 |
* Requirements: |
||
| 787 |
* |
||
| 788 |
* - input must fit into 160 bits |
||
| 789 |
*/ |
||
| 790 |
function toInt160(int256 value) internal pure returns (int160 downcasted) {
|
||
| 791 |
downcasted = int160(value); |
||
| 792 |
if (downcasted != value) {
|
||
| 793 |
revert SafeCastOverflowedIntDowncast(160, value); |
||
| 794 |
} |
||
| 795 |
} |
||
| 796 | |||
| 797 |
/** |
||
| 798 |
* @dev Returns the downcasted int152 from int256, reverting on |
||
| 799 |
* overflow (when the input is less than smallest int152 or |
||
| 800 |
* greater than largest int152). |
||
| 801 |
* |
||
| 802 |
* Counterpart to Solidity's `int152` operator. |
||
| 803 |
* |
||
| 804 |
* Requirements: |
||
| 805 |
* |
||
| 806 |
* - input must fit into 152 bits |
||
| 807 |
*/ |
||
| 808 |
function toInt152(int256 value) internal pure returns (int152 downcasted) {
|
||
| 809 |
downcasted = int152(value); |
||
| 810 |
if (downcasted != value) {
|
||
| 811 |
revert SafeCastOverflowedIntDowncast(152, value); |
||
| 812 |
} |
||
| 813 |
} |
||
| 814 | |||
| 815 |
/** |
||
| 816 |
* @dev Returns the downcasted int144 from int256, reverting on |
||
| 817 |
* overflow (when the input is less than smallest int144 or |
||
| 818 |
* greater than largest int144). |
||
| 819 |
* |
||
| 820 |
* Counterpart to Solidity's `int144` operator. |
||
| 821 |
* |
||
| 822 |
* Requirements: |
||
| 823 |
* |
||
| 824 |
* - input must fit into 144 bits |
||
| 825 |
*/ |
||
| 826 |
function toInt144(int256 value) internal pure returns (int144 downcasted) {
|
||
| 827 |
downcasted = int144(value); |
||
| 828 |
if (downcasted != value) {
|
||
| 829 |
revert SafeCastOverflowedIntDowncast(144, value); |
||
| 830 |
} |
||
| 831 |
} |
||
| 832 | |||
| 833 |
/** |
||
| 834 |
* @dev Returns the downcasted int136 from int256, reverting on |
||
| 835 |
* overflow (when the input is less than smallest int136 or |
||
| 836 |
* greater than largest int136). |
||
| 837 |
* |
||
| 838 |
* Counterpart to Solidity's `int136` operator. |
||
| 839 |
* |
||
| 840 |
* Requirements: |
||
| 841 |
* |
||
| 842 |
* - input must fit into 136 bits |
||
| 843 |
*/ |
||
| 844 |
function toInt136(int256 value) internal pure returns (int136 downcasted) {
|
||
| 845 |
downcasted = int136(value); |
||
| 846 |
if (downcasted != value) {
|
||
| 847 |
revert SafeCastOverflowedIntDowncast(136, value); |
||
| 848 |
} |
||
| 849 |
} |
||
| 850 | |||
| 851 |
/** |
||
| 852 |
* @dev Returns the downcasted int128 from int256, reverting on |
||
| 853 |
* overflow (when the input is less than smallest int128 or |
||
| 854 |
* greater than largest int128). |
||
| 855 |
* |
||
| 856 |
* Counterpart to Solidity's `int128` operator. |
||
| 857 |
* |
||
| 858 |
* Requirements: |
||
| 859 |
* |
||
| 860 |
* - input must fit into 128 bits |
||
| 861 |
*/ |
||
| 862 |
function toInt128(int256 value) internal pure returns (int128 downcasted) {
|
||
| 863 |
downcasted = int128(value); |
||
| 864 |
if (downcasted != value) {
|
||
| 865 |
revert SafeCastOverflowedIntDowncast(128, value); |
||
| 866 |
} |
||
| 867 |
} |
||
| 868 | |||
| 869 |
/** |
||
| 870 |
* @dev Returns the downcasted int120 from int256, reverting on |
||
| 871 |
* overflow (when the input is less than smallest int120 or |
||
| 872 |
* greater than largest int120). |
||
| 873 |
* |
||
| 874 |
* Counterpart to Solidity's `int120` operator. |
||
| 875 |
* |
||
| 876 |
* Requirements: |
||
| 877 |
* |
||
| 878 |
* - input must fit into 120 bits |
||
| 879 |
*/ |
||
| 880 |
function toInt120(int256 value) internal pure returns (int120 downcasted) {
|
||
| 881 |
downcasted = int120(value); |
||
| 882 |
if (downcasted != value) {
|
||
| 883 |
revert SafeCastOverflowedIntDowncast(120, value); |
||
| 884 |
} |
||
| 885 |
} |
||
| 886 | |||
| 887 |
/** |
||
| 888 |
* @dev Returns the downcasted int112 from int256, reverting on |
||
| 889 |
* overflow (when the input is less than smallest int112 or |
||
| 890 |
* greater than largest int112). |
||
| 891 |
* |
||
| 892 |
* Counterpart to Solidity's `int112` operator. |
||
| 893 |
* |
||
| 894 |
* Requirements: |
||
| 895 |
* |
||
| 896 |
* - input must fit into 112 bits |
||
| 897 |
*/ |
||
| 898 |
function toInt112(int256 value) internal pure returns (int112 downcasted) {
|
||
| 899 |
downcasted = int112(value); |
||
| 900 |
if (downcasted != value) {
|
||
| 901 |
revert SafeCastOverflowedIntDowncast(112, value); |
||
| 902 |
} |
||
| 903 |
} |
||
| 904 | |||
| 905 |
/** |
||
| 906 |
* @dev Returns the downcasted int104 from int256, reverting on |
||
| 907 |
* overflow (when the input is less than smallest int104 or |
||
| 908 |
* greater than largest int104). |
||
| 909 |
* |
||
| 910 |
* Counterpart to Solidity's `int104` operator. |
||
| 911 |
* |
||
| 912 |
* Requirements: |
||
| 913 |
* |
||
| 914 |
* - input must fit into 104 bits |
||
| 915 |
*/ |
||
| 916 |
function toInt104(int256 value) internal pure returns (int104 downcasted) {
|
||
| 917 |
downcasted = int104(value); |
||
| 918 |
if (downcasted != value) {
|
||
| 919 |
revert SafeCastOverflowedIntDowncast(104, value); |
||
| 920 |
} |
||
| 921 |
} |
||
| 922 | |||
| 923 |
/** |
||
| 924 |
* @dev Returns the downcasted int96 from int256, reverting on |
||
| 925 |
* overflow (when the input is less than smallest int96 or |
||
| 926 |
* greater than largest int96). |
||
| 927 |
* |
||
| 928 |
* Counterpart to Solidity's `int96` operator. |
||
| 929 |
* |
||
| 930 |
* Requirements: |
||
| 931 |
* |
||
| 932 |
* - input must fit into 96 bits |
||
| 933 |
*/ |
||
| 934 |
function toInt96(int256 value) internal pure returns (int96 downcasted) {
|
||
| 935 |
downcasted = int96(value); |
||
| 936 |
if (downcasted != value) {
|
||
| 937 |
revert SafeCastOverflowedIntDowncast(96, value); |
||
| 938 |
} |
||
| 939 |
} |
||
| 940 | |||
| 941 |
/** |
||
| 942 |
* @dev Returns the downcasted int88 from int256, reverting on |
||
| 943 |
* overflow (when the input is less than smallest int88 or |
||
| 944 |
* greater than largest int88). |
||
| 945 |
* |
||
| 946 |
* Counterpart to Solidity's `int88` operator. |
||
| 947 |
* |
||
| 948 |
* Requirements: |
||
| 949 |
* |
||
| 950 |
* - input must fit into 88 bits |
||
| 951 |
*/ |
||
| 952 |
function toInt88(int256 value) internal pure returns (int88 downcasted) {
|
||
| 953 |
downcasted = int88(value); |
||
| 954 |
if (downcasted != value) {
|
||
| 955 |
revert SafeCastOverflowedIntDowncast(88, value); |
||
| 956 |
} |
||
| 957 |
} |
||
| 958 | |||
| 959 |
/** |
||
| 960 |
* @dev Returns the downcasted int80 from int256, reverting on |
||
| 961 |
* overflow (when the input is less than smallest int80 or |
||
| 962 |
* greater than largest int80). |
||
| 963 |
* |
||
| 964 |
* Counterpart to Solidity's `int80` operator. |
||
| 965 |
* |
||
| 966 |
* Requirements: |
||
| 967 |
* |
||
| 968 |
* - input must fit into 80 bits |
||
| 969 |
*/ |
||
| 970 |
function toInt80(int256 value) internal pure returns (int80 downcasted) {
|
||
| 971 |
downcasted = int80(value); |
||
| 972 |
if (downcasted != value) {
|
||
| 973 |
revert SafeCastOverflowedIntDowncast(80, value); |
||
| 974 |
} |
||
| 975 |
} |
||
| 976 | |||
| 977 |
/** |
||
| 978 |
* @dev Returns the downcasted int72 from int256, reverting on |
||
| 979 |
* overflow (when the input is less than smallest int72 or |
||
| 980 |
* greater than largest int72). |
||
| 981 |
* |
||
| 982 |
* Counterpart to Solidity's `int72` operator. |
||
| 983 |
* |
||
| 984 |
* Requirements: |
||
| 985 |
* |
||
| 986 |
* - input must fit into 72 bits |
||
| 987 |
*/ |
||
| 988 |
function toInt72(int256 value) internal pure returns (int72 downcasted) {
|
||
| 989 |
downcasted = int72(value); |
||
| 990 |
if (downcasted != value) {
|
||
| 991 |
revert SafeCastOverflowedIntDowncast(72, value); |
||
| 992 |
} |
||
| 993 |
} |
||
| 994 | |||
| 995 |
/** |
||
| 996 |
* @dev Returns the downcasted int64 from int256, reverting on |
||
| 997 |
* overflow (when the input is less than smallest int64 or |
||
| 998 |
* greater than largest int64). |
||
| 999 |
* |
||
| 1000 |
* Counterpart to Solidity's `int64` operator. |
||
| 1001 |
* |
||
| 1002 |
* Requirements: |
||
| 1003 |
* |
||
| 1004 |
* - input must fit into 64 bits |
||
| 1005 |
*/ |
||
| 1006 |
function toInt64(int256 value) internal pure returns (int64 downcasted) {
|
||
| 1007 |
downcasted = int64(value); |
||
| 1008 |
if (downcasted != value) {
|
||
| 1009 |
revert SafeCastOverflowedIntDowncast(64, value); |
||
| 1010 |
} |
||
| 1011 |
} |
||
| 1012 | |||
| 1013 |
/** |
||
| 1014 |
* @dev Returns the downcasted int56 from int256, reverting on |
||
| 1015 |
* overflow (when the input is less than smallest int56 or |
||
| 1016 |
* greater than largest int56). |
||
| 1017 |
* |
||
| 1018 |
* Counterpart to Solidity's `int56` operator. |
||
| 1019 |
* |
||
| 1020 |
* Requirements: |
||
| 1021 |
* |
||
| 1022 |
* - input must fit into 56 bits |
||
| 1023 |
*/ |
||
| 1024 |
function toInt56(int256 value) internal pure returns (int56 downcasted) {
|
||
| 1025 |
downcasted = int56(value); |
||
| 1026 |
if (downcasted != value) {
|
||
| 1027 |
revert SafeCastOverflowedIntDowncast(56, value); |
||
| 1028 |
} |
||
| 1029 |
} |
||
| 1030 | |||
| 1031 |
/** |
||
| 1032 |
* @dev Returns the downcasted int48 from int256, reverting on |
||
| 1033 |
* overflow (when the input is less than smallest int48 or |
||
| 1034 |
* greater than largest int48). |
||
| 1035 |
* |
||
| 1036 |
* Counterpart to Solidity's `int48` operator. |
||
| 1037 |
* |
||
| 1038 |
* Requirements: |
||
| 1039 |
* |
||
| 1040 |
* - input must fit into 48 bits |
||
| 1041 |
*/ |
||
| 1042 |
function toInt48(int256 value) internal pure returns (int48 downcasted) {
|
||
| 1043 |
downcasted = int48(value); |
||
| 1044 |
if (downcasted != value) {
|
||
| 1045 |
revert SafeCastOverflowedIntDowncast(48, value); |
||
| 1046 |
} |
||
| 1047 |
} |
||
| 1048 | |||
| 1049 |
/** |
||
| 1050 |
* @dev Returns the downcasted int40 from int256, reverting on |
||
| 1051 |
* overflow (when the input is less than smallest int40 or |
||
| 1052 |
* greater than largest int40). |
||
| 1053 |
* |
||
| 1054 |
* Counterpart to Solidity's `int40` operator. |
||
| 1055 |
* |
||
| 1056 |
* Requirements: |
||
| 1057 |
* |
||
| 1058 |
* - input must fit into 40 bits |
||
| 1059 |
*/ |
||
| 1060 |
function toInt40(int256 value) internal pure returns (int40 downcasted) {
|
||
| 1061 |
downcasted = int40(value); |
||
| 1062 |
if (downcasted != value) {
|
||
| 1063 |
revert SafeCastOverflowedIntDowncast(40, value); |
||
| 1064 |
} |
||
| 1065 |
} |
||
| 1066 | |||
| 1067 |
/** |
||
| 1068 |
* @dev Returns the downcasted int32 from int256, reverting on |
||
| 1069 |
* overflow (when the input is less than smallest int32 or |
||
| 1070 |
* greater than largest int32). |
||
| 1071 |
* |
||
| 1072 |
* Counterpart to Solidity's `int32` operator. |
||
| 1073 |
* |
||
| 1074 |
* Requirements: |
||
| 1075 |
* |
||
| 1076 |
* - input must fit into 32 bits |
||
| 1077 |
*/ |
||
| 1078 |
function toInt32(int256 value) internal pure returns (int32 downcasted) {
|
||
| 1079 |
downcasted = int32(value); |
||
| 1080 |
if (downcasted != value) {
|
||
| 1081 |
revert SafeCastOverflowedIntDowncast(32, value); |
||
| 1082 |
} |
||
| 1083 |
} |
||
| 1084 | |||
| 1085 |
/** |
||
| 1086 |
* @dev Returns the downcasted int24 from int256, reverting on |
||
| 1087 |
* overflow (when the input is less than smallest int24 or |
||
| 1088 |
* greater than largest int24). |
||
| 1089 |
* |
||
| 1090 |
* Counterpart to Solidity's `int24` operator. |
||
| 1091 |
* |
||
| 1092 |
* Requirements: |
||
| 1093 |
* |
||
| 1094 |
* - input must fit into 24 bits |
||
| 1095 |
*/ |
||
| 1096 |
function toInt24(int256 value) internal pure returns (int24 downcasted) {
|
||
| 1097 |
downcasted = int24(value); |
||
| 1098 |
if (downcasted != value) {
|
||
| 1099 |
revert SafeCastOverflowedIntDowncast(24, value); |
||
| 1100 |
} |
||
| 1101 |
} |
||
| 1102 | |||
| 1103 |
/** |
||
| 1104 |
* @dev Returns the downcasted int16 from int256, reverting on |
||
| 1105 |
* overflow (when the input is less than smallest int16 or |
||
| 1106 |
* greater than largest int16). |
||
| 1107 |
* |
||
| 1108 |
* Counterpart to Solidity's `int16` operator. |
||
| 1109 |
* |
||
| 1110 |
* Requirements: |
||
| 1111 |
* |
||
| 1112 |
* - input must fit into 16 bits |
||
| 1113 |
*/ |
||
| 1114 |
function toInt16(int256 value) internal pure returns (int16 downcasted) {
|
||
| 1115 |
downcasted = int16(value); |
||
| 1116 |
if (downcasted != value) {
|
||
| 1117 |
revert SafeCastOverflowedIntDowncast(16, value); |
||
| 1118 |
} |
||
| 1119 |
} |
||
| 1120 | |||
| 1121 |
/** |
||
| 1122 |
* @dev Returns the downcasted int8 from int256, reverting on |
||
| 1123 |
* overflow (when the input is less than smallest int8 or |
||
| 1124 |
* greater than largest int8). |
||
| 1125 |
* |
||
| 1126 |
* Counterpart to Solidity's `int8` operator. |
||
| 1127 |
* |
||
| 1128 |
* Requirements: |
||
| 1129 |
* |
||
| 1130 |
* - input must fit into 8 bits |
||
| 1131 |
*/ |
||
| 1132 |
function toInt8(int256 value) internal pure returns (int8 downcasted) {
|
||
| 1133 |
downcasted = int8(value); |
||
| 1134 |
if (downcasted != value) {
|
||
| 1135 |
revert SafeCastOverflowedIntDowncast(8, value); |
||
| 1136 |
} |
||
| 1137 |
} |
||
| 1138 | |||
| 1139 |
/** |
||
| 1140 |
* @dev Converts an unsigned uint256 into a signed int256. |
||
| 1141 |
* |
||
| 1142 |
* Requirements: |
||
| 1143 |
* |
||
| 1144 |
* - input must be less than or equal to maxInt256. |
||
| 1145 |
*/ |
||
| 1146 |
function toInt256(uint256 value) internal pure returns (int256) {
|
||
| 1147 |
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive |
||
| 1148 |
if (value > uint256(type(int256).max)) {
|
||
| 1149 |
revert SafeCastOverflowedUintToInt(value); |
||
| 1150 |
} |
||
| 1151 |
return int256(value); |
||
| 1152 |
} |
||
| 1153 | |||
| 1154 |
/** |
||
| 1155 |
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. |
||
| 1156 |
*/ |
||
| 1157 |
function toUint(bool b) internal pure returns (uint256 u) {
|
||
| 1158 |
assembly ("memory-safe") {
|
||
| 1159 |
✓ 13.0M
|
u := iszero(iszero(b)) |
|
| 1160 |
} |
||
| 1161 |
} |
||
| 1162 |
} |
||
| 1163 |
100.0%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol
Lines covered: 1 / 1 (100.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol) |
||
| 3 | |||
| 4 |
pragma solidity ^0.8.20; |
||
| 5 | |||
| 6 |
import {SafeCast} from "./SafeCast.sol";
|
||
| 7 | |||
| 8 |
/** |
||
| 9 |
* @dev Standard signed math utilities missing in the Solidity language. |
||
| 10 |
*/ |
||
| 11 |
✓ 23.5K
|
⟲ 23.5K
|
library SignedMath {
|
| 12 |
/** |
||
| 13 |
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. |
||
| 14 |
* |
||
| 15 |
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. |
||
| 16 |
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute |
||
| 17 |
* one branch when needed, making this function more expensive. |
||
| 18 |
*/ |
||
| 19 |
function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
|
||
| 20 |
unchecked {
|
||
| 21 |
// branchless ternary works because: |
||
| 22 |
// b ^ (a ^ b) == a |
||
| 23 |
// b ^ 0 == b |
||
| 24 |
return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); |
||
| 25 |
} |
||
| 26 |
} |
||
| 27 | |||
| 28 |
/** |
||
| 29 |
* @dev Returns the largest of two signed numbers. |
||
| 30 |
*/ |
||
| 31 |
function max(int256 a, int256 b) internal pure returns (int256) {
|
||
| 32 |
return ternary(a > b, a, b); |
||
| 33 |
} |
||
| 34 | |||
| 35 |
/** |
||
| 36 |
* @dev Returns the smallest of two signed numbers. |
||
| 37 |
*/ |
||
| 38 |
function min(int256 a, int256 b) internal pure returns (int256) {
|
||
| 39 |
return ternary(a < b, a, b); |
||
| 40 |
} |
||
| 41 | |||
| 42 |
/** |
||
| 43 |
* @dev Returns the average of two signed numbers without overflow. |
||
| 44 |
* The result is rounded towards zero. |
||
| 45 |
*/ |
||
| 46 |
function average(int256 a, int256 b) internal pure returns (int256) {
|
||
| 47 |
// Formula from the book "Hacker's Delight" |
||
| 48 |
int256 x = (a & b) + ((a ^ b) >> 1); |
||
| 49 |
return x + (int256(uint256(x) >> 255) & (a ^ b)); |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/** |
||
| 53 |
* @dev Returns the absolute unsigned value of a signed value. |
||
| 54 |
*/ |
||
| 55 |
function abs(int256 n) internal pure returns (uint256) {
|
||
| 56 |
unchecked {
|
||
| 57 |
// Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson. |
||
| 58 |
// Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, |
||
| 59 |
// taking advantage of the most significant (or "sign" bit) in two's complement representation. |
||
| 60 |
// This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, |
||
| 61 |
// the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative). |
||
| 62 |
int256 mask = n >> 255; |
||
| 63 | |||
| 64 |
// A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. |
||
| 65 |
return uint256((n + mask) ^ mask); |
||
| 66 |
} |
||
| 67 |
} |
||
| 68 |
} |
||
| 69 |
89.2%
lib/v2-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
Lines covered: 33 / 37 (89.2%)
| 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 |
✓ 36.1K
|
⟲ 36.1K
|
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 |
✓ 4.3M
|
function _add(Set storage set, bytes32 value) private returns (bool) {
|
|
| 75 |
if (!_contains(set, value)) {
|
||
| 76 |
✓ 3.1M
|
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 |
✓ 3.1M
|
set._positions[value] = set._values.length; |
|
| 80 |
✓ 3.1M
|
return true; |
|
| 81 |
} else {
|
||
| 82 |
✓ 3.7K
|
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 |
✓ 785.0K
|
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 |
✓ 785.0K
|
uint256 position = set._positions[value]; |
|
| 95 | |||
| 96 |
✓ 785.0K
|
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 |
✓ 785.0K
|
uint256 valueIndex = position - 1; |
|
| 103 |
✓ 785.0K
|
uint256 lastIndex = set._values.length - 1; |
|
| 104 | |||
| 105 |
✓ 785.0K
|
if (valueIndex != lastIndex) {
|
|
| 106 |
✓ 385.2K
|
bytes32 lastValue = set._values[lastIndex]; |
|
| 107 | |||
| 108 |
// Move the lastValue to the index where the value to delete is |
||
| 109 |
✓ 385.2K
|
set._values[valueIndex] = lastValue; |
|
| 110 |
// Update the tracked position of the lastValue (that was just moved) |
||
| 111 |
✓ 385.2K
|
set._positions[lastValue] = position; |
|
| 112 |
} |
||
| 113 | |||
| 114 |
// Delete the slot where the moved value was stored |
||
| 115 |
✓ 785.0K
|
set._values.pop(); |
|
| 116 | |||
| 117 |
// Delete the tracked position for the deleted slot |
||
| 118 |
✓ 785.0K
|
delete set._positions[value]; |
|
| 119 | |||
| 120 |
✓ 785.0K
|
return true; |
|
| 121 |
} else {
|
||
| 122 |
✓ 14.3K
|
return false; |
|
| 123 |
} |
||
| 124 |
} |
||
| 125 | |||
| 126 |
/** |
||
| 127 |
* @dev Removes all the values from a set. O(n). |
||
| 128 |
* |
||
| 129 |
* WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that |
||
| 130 |
* using it may render the function uncallable if the set grows to the point where clearing it consumes too much |
||
| 131 |
* gas to fit in a block. |
||
| 132 |
*/ |
||
| 133 |
function _clear(Set storage set) private {
|
||
| 134 |
uint256 len = _length(set); |
||
| 135 |
for (uint256 i = 0; i < len; ++i) {
|
||
| 136 |
delete set._positions[set._values[i]]; |
||
| 137 |
} |
||
| 138 |
Arrays.unsafeSetLength(set._values, 0); |
||
| 139 |
} |
||
| 140 | |||
| 141 |
/** |
||
| 142 |
* @dev Returns true if the value is in the set. O(1). |
||
| 143 |
*/ |
||
| 144 |
function _contains(Set storage set, bytes32 value) private view returns (bool) {
|
||
| 145 |
✓ 18.1M
|
return set._positions[value] != 0; |
|
| 146 |
} |
||
| 147 | |||
| 148 |
/** |
||
| 149 |
* @dev Returns the number of values on the set. O(1). |
||
| 150 |
*/ |
||
| 151 |
function _length(Set storage set) private view returns (uint256) {
|
||
| 152 |
✓ 112.6K
|
return set._values.length; |
|
| 153 |
} |
||
| 154 | |||
| 155 |
/** |
||
| 156 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 157 |
* |
||
| 158 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 159 |
* array, and it may change when more values are added or removed. |
||
| 160 |
* |
||
| 161 |
* Requirements: |
||
| 162 |
* |
||
| 163 |
* - `index` must be strictly less than {length}.
|
||
| 164 |
*/ |
||
| 165 |
function _at(Set storage set, uint256 index) private view returns (bytes32) {
|
||
| 166 |
return set._values[index]; |
||
| 167 |
} |
||
| 168 | |||
| 169 |
/** |
||
| 170 |
* @dev Return the entire set in an array |
||
| 171 |
* |
||
| 172 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 173 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 174 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 175 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 176 |
*/ |
||
| 177 |
✓ 134.7K
|
function _values(Set storage set) private view returns (bytes32[] memory) {
|
|
| 178 |
✓ 134.7K
|
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 |
✓ 4.3M
|
function add(AddressSet storage set, address value) internal returns (bool) {
|
|
| 318 |
✓ 4.3M
|
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 |
✓ 785.0K
|
function remove(AddressSet storage set, address value) internal returns (bool) {
|
|
| 328 |
✓ 785.0K
|
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 |
✓ 18.1M
|
function contains(AddressSet storage set, address value) internal view returns (bool) {
|
|
| 345 |
✓ 18.1M
|
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 |
✓ 112.6K
|
function length(AddressSet storage set) internal view returns (uint256) {
|
|
| 352 |
✓ 112.6K
|
return _length(set._inner); |
|
| 353 |
} |
||
| 354 | |||
| 355 |
/** |
||
| 356 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 357 |
* |
||
| 358 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 359 |
* array, and it may change when more values are added or removed. |
||
| 360 |
* |
||
| 361 |
* Requirements: |
||
| 362 |
* |
||
| 363 |
* - `index` must be strictly less than {length}.
|
||
| 364 |
*/ |
||
| 365 |
function at(AddressSet storage set, uint256 index) internal view returns (address) {
|
||
| 366 |
return address(uint160(uint256(_at(set._inner, index)))); |
||
| 367 |
} |
||
| 368 | |||
| 369 |
/** |
||
| 370 |
* @dev Return the entire set in an array |
||
| 371 |
* |
||
| 372 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 373 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 374 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 375 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 376 |
*/ |
||
| 377 |
✓ 134.7K
|
function values(AddressSet storage set) internal view returns (address[] memory) {
|
|
| 378 |
✓ 134.7K
|
bytes32[] memory store = _values(set._inner); |
|
| 379 |
address[] memory result; |
||
| 380 | |||
| 381 |
assembly ("memory-safe") {
|
||
| 382 |
result := store |
||
| 383 |
} |
||
| 384 | |||
| 385 |
return result; |
||
| 386 |
} |
||
| 387 | |||
| 388 |
/** |
||
| 389 |
* @dev Return a slice of the set in an array |
||
| 390 |
* |
||
| 391 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 392 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 393 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 394 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 395 |
*/ |
||
| 396 |
function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) {
|
||
| 397 |
bytes32[] memory store = _values(set._inner, start, end); |
||
| 398 |
address[] memory result; |
||
| 399 | |||
| 400 |
assembly ("memory-safe") {
|
||
| 401 |
result := store |
||
| 402 |
} |
||
| 403 | |||
| 404 |
return result; |
||
| 405 |
} |
||
| 406 | |||
| 407 |
// UintSet |
||
| 408 | |||
| 409 |
struct UintSet {
|
||
| 410 |
Set _inner; |
||
| 411 |
} |
||
| 412 | |||
| 413 |
/** |
||
| 414 |
* @dev Add a value to a set. O(1). |
||
| 415 |
* |
||
| 416 |
* Returns true if the value was added to the set, that is if it was not |
||
| 417 |
* already present. |
||
| 418 |
*/ |
||
| 419 |
function add(UintSet storage set, uint256 value) internal returns (bool) {
|
||
| 420 |
return _add(set._inner, bytes32(value)); |
||
| 421 |
} |
||
| 422 | |||
| 423 |
/** |
||
| 424 |
* @dev Removes a value from a set. O(1). |
||
| 425 |
* |
||
| 426 |
* Returns true if the value was removed from the set, that is if it was |
||
| 427 |
* present. |
||
| 428 |
*/ |
||
| 429 |
function remove(UintSet storage set, uint256 value) internal returns (bool) {
|
||
| 430 |
return _remove(set._inner, bytes32(value)); |
||
| 431 |
} |
||
| 432 | |||
| 433 |
/** |
||
| 434 |
* @dev Removes all the values from a set. O(n). |
||
| 435 |
* |
||
| 436 |
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
||
| 437 |
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
||
| 438 |
*/ |
||
| 439 |
function clear(UintSet storage set) internal {
|
||
| 440 |
_clear(set._inner); |
||
| 441 |
} |
||
| 442 | |||
| 443 |
/** |
||
| 444 |
* @dev Returns true if the value is in the set. O(1). |
||
| 445 |
*/ |
||
| 446 |
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
|
||
| 447 |
return _contains(set._inner, bytes32(value)); |
||
| 448 |
} |
||
| 449 | |||
| 450 |
/** |
||
| 451 |
* @dev Returns the number of values in the set. O(1). |
||
| 452 |
*/ |
||
| 453 |
function length(UintSet storage set) internal view returns (uint256) {
|
||
| 454 |
return _length(set._inner); |
||
| 455 |
} |
||
| 456 | |||
| 457 |
/** |
||
| 458 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 459 |
* |
||
| 460 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 461 |
* array, and it may change when more values are added or removed. |
||
| 462 |
* |
||
| 463 |
* Requirements: |
||
| 464 |
* |
||
| 465 |
* - `index` must be strictly less than {length}.
|
||
| 466 |
*/ |
||
| 467 |
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
|
||
| 468 |
return uint256(_at(set._inner, index)); |
||
| 469 |
} |
||
| 470 | |||
| 471 |
/** |
||
| 472 |
* @dev Return the entire set in an array |
||
| 473 |
* |
||
| 474 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 475 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 476 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 477 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 478 |
*/ |
||
| 479 |
function values(UintSet storage set) internal view returns (uint256[] memory) {
|
||
| 480 |
bytes32[] memory store = _values(set._inner); |
||
| 481 |
uint256[] memory result; |
||
| 482 | |||
| 483 |
assembly ("memory-safe") {
|
||
| 484 |
result := store |
||
| 485 |
} |
||
| 486 | |||
| 487 |
return result; |
||
| 488 |
} |
||
| 489 | |||
| 490 |
/** |
||
| 491 |
* @dev Return a slice of the set in an array |
||
| 492 |
* |
||
| 493 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 494 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 495 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 496 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 497 |
*/ |
||
| 498 |
function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) {
|
||
| 499 |
bytes32[] memory store = _values(set._inner, start, end); |
||
| 500 |
uint256[] memory result; |
||
| 501 | |||
| 502 |
assembly ("memory-safe") {
|
||
| 503 |
result := store |
||
| 504 |
} |
||
| 505 | |||
| 506 |
return result; |
||
| 507 |
} |
||
| 508 | |||
| 509 |
struct StringSet {
|
||
| 510 |
// Storage of set values |
||
| 511 |
string[] _values; |
||
| 512 |
// Position is the index of the value in the `values` array plus 1. |
||
| 513 |
// Position 0 is used to mean a value is not in the set. |
||
| 514 |
mapping(string value => uint256) _positions; |
||
| 515 |
} |
||
| 516 | |||
| 517 |
/** |
||
| 518 |
* @dev Add a value to a set. O(1). |
||
| 519 |
* |
||
| 520 |
* Returns true if the value was added to the set, that is if it was not |
||
| 521 |
* already present. |
||
| 522 |
*/ |
||
| 523 |
function add(StringSet storage set, string memory value) internal returns (bool) {
|
||
| 524 |
if (!contains(set, value)) {
|
||
| 525 |
set._values.push(value); |
||
| 526 |
// The value is stored at length-1, but we add 1 to all indexes |
||
| 527 |
// and use 0 as a sentinel value |
||
| 528 |
set._positions[value] = set._values.length; |
||
| 529 |
return true; |
||
| 530 |
} else {
|
||
| 531 |
return false; |
||
| 532 |
} |
||
| 533 |
} |
||
| 534 | |||
| 535 |
/** |
||
| 536 |
* @dev Removes a value from a set. O(1). |
||
| 537 |
* |
||
| 538 |
* Returns true if the value was removed from the set, that is if it was |
||
| 539 |
* present. |
||
| 540 |
*/ |
||
| 541 |
function remove(StringSet storage set, string memory value) internal returns (bool) {
|
||
| 542 |
// We cache the value's position to prevent multiple reads from the same storage slot |
||
| 543 |
uint256 position = set._positions[value]; |
||
| 544 | |||
| 545 |
if (position != 0) {
|
||
| 546 |
// Equivalent to contains(set, value) |
||
| 547 |
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
||
| 548 |
// the array, and then remove the last element (sometimes called as 'swap and pop'). |
||
| 549 |
// This modifies the order of the array, as noted in {at}.
|
||
| 550 | |||
| 551 |
uint256 valueIndex = position - 1; |
||
| 552 |
uint256 lastIndex = set._values.length - 1; |
||
| 553 | |||
| 554 |
if (valueIndex != lastIndex) {
|
||
| 555 |
string memory lastValue = set._values[lastIndex]; |
||
| 556 | |||
| 557 |
// Move the lastValue to the index where the value to delete is |
||
| 558 |
set._values[valueIndex] = lastValue; |
||
| 559 |
// Update the tracked position of the lastValue (that was just moved) |
||
| 560 |
set._positions[lastValue] = position; |
||
| 561 |
} |
||
| 562 | |||
| 563 |
// Delete the slot where the moved value was stored |
||
| 564 |
set._values.pop(); |
||
| 565 | |||
| 566 |
// Delete the tracked position for the deleted slot |
||
| 567 |
delete set._positions[value]; |
||
| 568 | |||
| 569 |
return true; |
||
| 570 |
} else {
|
||
| 571 |
return false; |
||
| 572 |
} |
||
| 573 |
} |
||
| 574 | |||
| 575 |
/** |
||
| 576 |
* @dev Removes all the values from a set. O(n). |
||
| 577 |
* |
||
| 578 |
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
||
| 579 |
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
||
| 580 |
*/ |
||
| 581 |
function clear(StringSet storage set) internal {
|
||
| 582 |
uint256 len = length(set); |
||
| 583 |
for (uint256 i = 0; i < len; ++i) {
|
||
| 584 |
delete set._positions[set._values[i]]; |
||
| 585 |
} |
||
| 586 |
Arrays.unsafeSetLength(set._values, 0); |
||
| 587 |
} |
||
| 588 | |||
| 589 |
/** |
||
| 590 |
* @dev Returns true if the value is in the set. O(1). |
||
| 591 |
*/ |
||
| 592 |
function contains(StringSet storage set, string memory value) internal view returns (bool) {
|
||
| 593 |
return set._positions[value] != 0; |
||
| 594 |
} |
||
| 595 | |||
| 596 |
/** |
||
| 597 |
* @dev Returns the number of values on the set. O(1). |
||
| 598 |
*/ |
||
| 599 |
function length(StringSet storage set) internal view returns (uint256) {
|
||
| 600 |
return set._values.length; |
||
| 601 |
} |
||
| 602 | |||
| 603 |
/** |
||
| 604 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 605 |
* |
||
| 606 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 607 |
* array, and it may change when more values are added or removed. |
||
| 608 |
* |
||
| 609 |
* Requirements: |
||
| 610 |
* |
||
| 611 |
* - `index` must be strictly less than {length}.
|
||
| 612 |
*/ |
||
| 613 |
function at(StringSet storage set, uint256 index) internal view returns (string memory) {
|
||
| 614 |
return set._values[index]; |
||
| 615 |
} |
||
| 616 | |||
| 617 |
/** |
||
| 618 |
* @dev Return the entire set in an array |
||
| 619 |
* |
||
| 620 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 621 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 622 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 623 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 624 |
*/ |
||
| 625 |
function values(StringSet storage set) internal view returns (string[] memory) {
|
||
| 626 |
return set._values; |
||
| 627 |
} |
||
| 628 | |||
| 629 |
/** |
||
| 630 |
* @dev Return a slice of the set in an array |
||
| 631 |
* |
||
| 632 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 633 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 634 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 635 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 636 |
*/ |
||
| 637 |
function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
|
||
| 638 |
unchecked {
|
||
| 639 |
end = Math.min(end, length(set)); |
||
| 640 |
start = Math.min(start, end); |
||
| 641 | |||
| 642 |
uint256 len = end - start; |
||
| 643 |
string[] memory result = new string[](len); |
||
| 644 |
for (uint256 i = 0; i < len; ++i) {
|
||
| 645 |
result[i] = Arrays.unsafeAccess(set._values, start + i).value; |
||
| 646 |
} |
||
| 647 |
return result; |
||
| 648 |
} |
||
| 649 |
} |
||
| 650 | |||
| 651 |
struct BytesSet {
|
||
| 652 |
// Storage of set values |
||
| 653 |
bytes[] _values; |
||
| 654 |
// Position is the index of the value in the `values` array plus 1. |
||
| 655 |
// Position 0 is used to mean a value is not in the set. |
||
| 656 |
mapping(bytes value => uint256) _positions; |
||
| 657 |
} |
||
| 658 | |||
| 659 |
/** |
||
| 660 |
* @dev Add a value to a set. O(1). |
||
| 661 |
* |
||
| 662 |
* Returns true if the value was added to the set, that is if it was not |
||
| 663 |
* already present. |
||
| 664 |
*/ |
||
| 665 |
function add(BytesSet storage set, bytes memory value) internal returns (bool) {
|
||
| 666 |
if (!contains(set, value)) {
|
||
| 667 |
set._values.push(value); |
||
| 668 |
// The value is stored at length-1, but we add 1 to all indexes |
||
| 669 |
// and use 0 as a sentinel value |
||
| 670 |
set._positions[value] = set._values.length; |
||
| 671 |
return true; |
||
| 672 |
} else {
|
||
| 673 |
return false; |
||
| 674 |
} |
||
| 675 |
} |
||
| 676 | |||
| 677 |
/** |
||
| 678 |
* @dev Removes a value from a set. O(1). |
||
| 679 |
* |
||
| 680 |
* Returns true if the value was removed from the set, that is if it was |
||
| 681 |
* present. |
||
| 682 |
*/ |
||
| 683 |
function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
|
||
| 684 |
// We cache the value's position to prevent multiple reads from the same storage slot |
||
| 685 |
uint256 position = set._positions[value]; |
||
| 686 | |||
| 687 |
if (position != 0) {
|
||
| 688 |
// Equivalent to contains(set, value) |
||
| 689 |
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in |
||
| 690 |
// the array, and then remove the last element (sometimes called as 'swap and pop'). |
||
| 691 |
// This modifies the order of the array, as noted in {at}.
|
||
| 692 | |||
| 693 |
uint256 valueIndex = position - 1; |
||
| 694 |
uint256 lastIndex = set._values.length - 1; |
||
| 695 | |||
| 696 |
if (valueIndex != lastIndex) {
|
||
| 697 |
bytes memory lastValue = set._values[lastIndex]; |
||
| 698 | |||
| 699 |
// Move the lastValue to the index where the value to delete is |
||
| 700 |
set._values[valueIndex] = lastValue; |
||
| 701 |
// Update the tracked position of the lastValue (that was just moved) |
||
| 702 |
set._positions[lastValue] = position; |
||
| 703 |
} |
||
| 704 | |||
| 705 |
// Delete the slot where the moved value was stored |
||
| 706 |
set._values.pop(); |
||
| 707 | |||
| 708 |
// Delete the tracked position for the deleted slot |
||
| 709 |
delete set._positions[value]; |
||
| 710 | |||
| 711 |
return true; |
||
| 712 |
} else {
|
||
| 713 |
return false; |
||
| 714 |
} |
||
| 715 |
} |
||
| 716 | |||
| 717 |
/** |
||
| 718 |
* @dev Removes all the values from a set. O(n). |
||
| 719 |
* |
||
| 720 |
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the |
||
| 721 |
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. |
||
| 722 |
*/ |
||
| 723 |
function clear(BytesSet storage set) internal {
|
||
| 724 |
uint256 len = length(set); |
||
| 725 |
for (uint256 i = 0; i < len; ++i) {
|
||
| 726 |
delete set._positions[set._values[i]]; |
||
| 727 |
} |
||
| 728 |
Arrays.unsafeSetLength(set._values, 0); |
||
| 729 |
} |
||
| 730 | |||
| 731 |
/** |
||
| 732 |
* @dev Returns true if the value is in the set. O(1). |
||
| 733 |
*/ |
||
| 734 |
function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
|
||
| 735 |
return set._positions[value] != 0; |
||
| 736 |
} |
||
| 737 | |||
| 738 |
/** |
||
| 739 |
* @dev Returns the number of values on the set. O(1). |
||
| 740 |
*/ |
||
| 741 |
function length(BytesSet storage set) internal view returns (uint256) {
|
||
| 742 |
return set._values.length; |
||
| 743 |
} |
||
| 744 | |||
| 745 |
/** |
||
| 746 |
* @dev Returns the value stored at position `index` in the set. O(1). |
||
| 747 |
* |
||
| 748 |
* Note that there are no guarantees on the ordering of values inside the |
||
| 749 |
* array, and it may change when more values are added or removed. |
||
| 750 |
* |
||
| 751 |
* Requirements: |
||
| 752 |
* |
||
| 753 |
* - `index` must be strictly less than {length}.
|
||
| 754 |
*/ |
||
| 755 |
function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
|
||
| 756 |
return set._values[index]; |
||
| 757 |
} |
||
| 758 | |||
| 759 |
/** |
||
| 760 |
* @dev Return the entire set in an array |
||
| 761 |
* |
||
| 762 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 763 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 764 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 765 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 766 |
*/ |
||
| 767 |
function values(BytesSet storage set) internal view returns (bytes[] memory) {
|
||
| 768 |
return set._values; |
||
| 769 |
} |
||
| 770 | |||
| 771 |
/** |
||
| 772 |
* @dev Return a slice of the set in an array |
||
| 773 |
* |
||
| 774 |
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
||
| 775 |
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
||
| 776 |
* this function has an unbounded cost, and using it as part of a state-changing function may render the function |
||
| 777 |
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
||
| 778 |
*/ |
||
| 779 |
function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
|
||
| 780 |
unchecked {
|
||
| 781 |
end = Math.min(end, length(set)); |
||
| 782 |
start = Math.min(start, end); |
||
| 783 | |||
| 784 |
uint256 len = end - start; |
||
| 785 |
bytes[] memory result = new bytes[](len); |
||
| 786 |
for (uint256 i = 0; i < len; ++i) {
|
||
| 787 |
result[i] = Arrays.unsafeAccess(set._values, start + i).value; |
||
| 788 |
} |
||
| 789 |
return result; |
||
| 790 |
} |
||
| 791 |
} |
||
| 792 |
} |
||
| 793 |
86.9%
lib/v2-core/src/hooks/BaseHook.sol
Lines covered: 73 / 84 (86.9%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// External |
||
| 5 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 6 | |||
| 7 |
// Superform |
||
| 8 |
import { HookDataDecoder } from "../libraries/HookDataDecoder.sol";
|
||
| 9 |
import { ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector } from "../interfaces/ISuperHook.sol";
|
||
| 10 | |||
| 11 |
/// @title BaseHook |
||
| 12 |
/// @author Superform Labs |
||
| 13 |
/// @notice Base implementation for all hooks in the Superform system |
||
| 14 |
/// @dev Provides core security validation and execution flow management for hooks |
||
| 15 |
/// All specialized hooks should inherit from this base contract |
||
| 16 |
/// Implements the ISuperHook interface defined lifecycle methods |
||
| 17 |
/// Uses a transient storage pattern for stateful execution context |
||
| 18 |
abstract contract BaseHook is ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector {
|
||
| 19 |
using HookDataDecoder for bytes; |
||
| 20 | |||
| 21 |
/*////////////////////////////////////////////////////////////// |
||
| 22 |
STORAGE |
||
| 23 |
//////////////////////////////////////////////////////////////*/ |
||
| 24 | |||
| 25 |
/// @notice The number of shares used by this hook's operation |
||
| 26 |
/// @dev Used for accounting and tracking consumption of position shares |
||
| 27 |
uint256 public transient usedShares; |
||
| 28 | |||
| 29 |
/// @notice The special token address (if any) associated with this hook's operation |
||
| 30 |
/// @dev May be used to track token addresses for various operations |
||
| 31 |
address public transient spToken; |
||
| 32 | |||
| 33 |
/// @notice The primary asset address this hook operates on |
||
| 34 |
/// @dev Typically the base token or asset being processed |
||
| 35 |
address public transient asset; |
||
| 36 | |||
| 37 |
/// @notice Execution nonce for creating unique contexts |
||
| 38 |
✓ 1.4M
|
uint256 public transient executionNonce; |
|
| 39 | |||
| 40 |
/// @notice Last execution context caller |
||
| 41 |
address public transient lastCaller; |
||
| 42 | |||
| 43 |
// Storage offsets for different state variables |
||
| 44 |
✓ 257.6K
|
uint256 private constant OUT_AMOUNT_OFFSET = 1; |
|
| 45 |
✓ 257.6K
|
uint256 private constant PRE_EXECUTE_MUTEX_OFFSET = 2; |
|
| 46 |
✓ 249.0K
|
uint256 private constant POST_EXECUTE_MUTEX_OFFSET = 3; |
|
| 47 | |||
| 48 |
/// @notice Base storage key for hook execution state |
||
| 49 |
✓ 1.4M
|
bytes32 private constant HOOK_EXECUTION_STORAGE = keccak256("hook.execution.state");
|
|
| 50 | |||
| 51 |
/// @notice Storage key for account context mapping |
||
| 52 |
✓ 945.5K
|
bytes32 private constant ACCOUNT_CONTEXT_STORAGE = keccak256("hook.account.context");
|
|
| 53 | |||
| 54 |
/// @notice The specific subtype identifier for this hook |
||
| 55 |
/// @dev Used to identify specialized hook types beyond the basic HookType enum |
||
| 56 |
bytes32 public immutable SUB_TYPE; |
||
| 57 | |||
| 58 |
/// @notice The type of hook (NONACCOUNTING, INFLOW, OUTFLOW) |
||
| 59 |
/// @dev Determines how the hook impacts accounting in the system |
||
| 60 |
✓ 284.2K
|
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 |
✓ 2
|
hookType = hookType_; |
|
| 103 |
✓ 2
|
SUB_TYPE = subType_; |
|
| 104 |
} |
||
| 105 | |||
| 106 |
modifier onlyLastCaller() {
|
||
| 107 |
✓ 124.5K
|
if (msg.sender != lastCaller) revert UNAUTHORIZED_CALLER(); |
|
| 108 |
_; |
||
| 109 |
} |
||
| 110 | |||
| 111 |
/*////////////////////////////////////////////////////////////// |
||
| 112 |
EXECUTION SECURITY |
||
| 113 |
//////////////////////////////////////////////////////////////*/ |
||
| 114 |
/// @inheritdoc ISuperHook |
||
| 115 |
function setExecutionContext(address caller) external {
|
||
| 116 |
✓ 181.3K
|
_createExecutionContext(caller); |
|
| 117 |
✓ 181.3K
|
lastCaller = msg.sender; |
|
| 118 |
} |
||
| 119 | |||
| 120 |
/// @dev Standard build pattern - MUST include preExecute first, postExecute last |
||
| 121 |
/// @inheritdoc ISuperHook |
||
| 122 |
function build( |
||
| 123 |
address prevHook, |
||
| 124 |
address account, |
||
| 125 |
bytes calldata hookData |
||
| 126 |
) |
||
| 127 |
external |
||
| 128 |
view |
||
| 129 |
virtual |
||
| 130 |
✓ 181.3K
|
returns (Execution[] memory executions) |
|
| 131 |
{
|
||
| 132 |
// Get hook-specific executions |
||
| 133 |
✓ 181.3K
|
Execution[] memory hookExecutions = _buildHookExecutions(prevHook, account, hookData); |
|
| 134 | |||
| 135 |
// Always include pre + hook + post |
||
| 136 |
✓ 133.1K
|
executions = new Execution[](hookExecutions.length + 2); |
|
| 137 | |||
| 138 |
// FIRST: preExecute |
||
| 139 |
✓ 133.1K
|
executions[0] = Execution({
|
|
| 140 |
✓ 133.1K
|
target: address(this), |
|
| 141 |
✓ 133.1K
|
value: 0, |
|
| 142 |
✓ 133.1K
|
callData: abi.encodeCall(this.preExecute, (prevHook, account, hookData)) |
|
| 143 |
}); |
||
| 144 | |||
| 145 |
// MIDDLE: hook-specific operations |
||
| 146 |
✓ 266.2K
|
for (uint256 i = 0; i < hookExecutions.length; i++) {
|
|
| 147 |
✓ 133.1K
|
executions[i + 1] = hookExecutions[i]; |
|
| 148 |
} |
||
| 149 | |||
| 150 |
// LAST: postExecute |
||
| 151 |
✓ 133.1K
|
executions[executions.length - 1] = Execution({
|
|
| 152 |
✓ 133.1K
|
target: address(this), |
|
| 153 |
✓ 133.1K
|
value: 0, |
|
| 154 |
✓ 133.1K
|
callData: abi.encodeCall(this.postExecute, (prevHook, account, hookData)) |
|
| 155 |
}); |
||
| 156 |
} |
||
| 157 | |||
| 158 |
/// @inheritdoc ISuperHook |
||
| 159 |
function preExecute(address prevHook, address account, bytes calldata data) external {
|
||
| 160 |
✓ 133.1K
|
if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
|
| 161 |
✓ 133.1K
|
uint256 context = _getCurrentExecutionContext(account); |
|
| 162 |
✓ 133.1K
|
if (_getPreExecuteMutex(context)) revert PRE_EXECUTE_ALREADY_CALLED(); |
|
| 163 |
✓ 133.1K
|
_setPreExecuteMutex(context, true); |
|
| 164 |
✓ 133.1K
|
_preExecute(prevHook, account, data); |
|
| 165 |
} |
||
| 166 | |||
| 167 |
/// @inheritdoc ISuperHook |
||
| 168 |
function postExecute(address prevHook, address account, bytes calldata data) external {
|
||
| 169 |
✓ 124.5K
|
if (msg.sender != account) revert UNAUTHORIZED_CALLER(); |
|
| 170 |
✓ 124.5K
|
uint256 context = _getCurrentExecutionContext(account); |
|
| 171 |
✓ 124.5K
|
if (_getPostExecuteMutex(context)) revert POST_EXECUTE_ALREADY_CALLED(); |
|
| 172 |
✓ 124.5K
|
_setPostExecuteMutex(context, true); |
|
| 173 |
✓ 124.5K
|
_postExecute(prevHook, account, data); |
|
| 174 |
} |
||
| 175 | |||
| 176 |
/// @inheritdoc ISuperHookSetter |
||
| 177 |
function setOutAmount(uint256 _outAmount, address caller) external {
|
||
| 178 |
uint256 context = _getCurrentExecutionContext(caller); |
||
| 179 |
if (_getPreExecuteMutex(context) || _getPostExecuteMutex(context)) {
|
||
| 180 |
revert CANNOT_SET_OUT_AMOUNT(); |
||
| 181 |
} |
||
| 182 | |||
| 183 |
bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
||
| 184 |
assembly {
|
||
| 185 |
✓ 257.6K
|
tstore(key, _outAmount) |
|
| 186 |
} |
||
| 187 |
} |
||
| 188 | |||
| 189 |
✓ 124.5K
|
function getOutAmount(address caller) public view returns (uint256) {
|
|
| 190 |
✓ 124.5K
|
return _getOutAmount(_getCurrentExecutionContext(caller)); |
|
| 191 |
} |
||
| 192 | |||
| 193 |
/// @inheritdoc ISuperHook |
||
| 194 |
function resetExecutionState(address caller) external onlyLastCaller {
|
||
| 195 |
✓ 124.5K
|
uint256 context = _getCurrentExecutionContext(caller); |
|
| 196 |
✓ 124.5K
|
if (!_getPreExecuteMutex(context) || !_getPostExecuteMutex(context)) {
|
|
| 197 |
revert INCOMPLETE_HOOK_EXECUTION(); |
||
| 198 |
} |
||
| 199 | |||
| 200 |
✓ 124.5K
|
_clearExecutionState(context); |
|
| 201 |
} |
||
| 202 | |||
| 203 |
/*////////////////////////////////////////////////////////////// |
||
| 204 |
VIEW METHODS |
||
| 205 |
//////////////////////////////////////////////////////////////*/ |
||
| 206 |
/// @inheritdoc ISuperHook |
||
| 207 |
function subtype() external view returns (bytes32) {
|
||
| 208 |
return SUB_TYPE; |
||
| 209 |
} |
||
| 210 | |||
| 211 |
/// @inheritdoc ISuperHookInspector |
||
| 212 |
function inspect(bytes calldata) external view virtual returns (bytes memory) { }
|
||
| 213 | |||
| 214 |
/*////////////////////////////////////////////////////////////// |
||
| 215 |
INTERNAL METHODS |
||
| 216 |
//////////////////////////////////////////////////////////////*/ |
||
| 217 |
/// @notice Internal implementation of build |
||
| 218 |
/// @dev Abstract function to be implemented by derived hooks |
||
| 219 |
/// Called during build to generate hook-specific execution sequences |
||
| 220 |
/// @param prevHook The previous hook in the chain, or address(0) if first hook |
||
| 221 |
/// @param account The account that operations will be performed for |
||
| 222 |
/// @param data Hook-specific parameters and configuration data |
||
| 223 |
/// @return executions Array of execution objects containing target, value, and callData |
||
| 224 |
function _buildHookExecutions( |
||
| 225 |
address prevHook, |
||
| 226 |
address account, |
||
| 227 |
bytes calldata data |
||
| 228 |
) |
||
| 229 |
internal |
||
| 230 |
view |
||
| 231 |
virtual |
||
| 232 |
returns (Execution[] memory executions); |
||
| 233 | |||
| 234 |
/// @notice Internal implementation of preExecute |
||
| 235 |
/// @dev Abstract function to be implemented by derived hooks |
||
| 236 |
/// Called before execution to validate inputs and prepare the hook's state |
||
| 237 |
/// Typically sets up the hook context by parsing parameters from data |
||
| 238 |
/// May check balances, permissions, or other preconditions |
||
| 239 |
/// @param prevHook The previous hook in the chain, or address(0) if first hook |
||
| 240 |
/// @param account The account that operations will be performed for |
||
| 241 |
/// @param data Hook-specific parameters and configuration data |
||
| 242 |
function _preExecute(address prevHook, address account, bytes calldata data) internal virtual { }
|
||
| 243 | |||
| 244 |
/// @notice Internal implementation of postExecute |
||
| 245 |
/// @dev Abstract function to be implemented by derived hooks |
||
| 246 |
/// Called after execution to finalize the hook's state and set output values |
||
| 247 |
/// Typically calculates final results and sets outAmount, usedShares, etc. |
||
| 248 |
/// May perform additional validations on execution results |
||
| 249 |
/// @param prevHook The previous hook in the chain, or address(0) if first hook |
||
| 250 |
/// @param account The account operations were performed for |
||
| 251 |
/// @param data Hook-specific parameters and configuration data |
||
| 252 |
function _postExecute(address prevHook, address account, bytes calldata data) internal virtual { }
|
||
| 253 | |||
| 254 |
/// @notice Decodes a boolean value from a byte array at the specified offset |
||
| 255 |
/// @dev Helper function for extracting boolean values from packed data |
||
| 256 |
/// Used when parsing hook-specific data parameters |
||
| 257 |
/// @param data The byte array containing the encoded data |
||
| 258 |
/// @param offset The position in the array to read from |
||
| 259 |
/// @return The decoded boolean value (true if byte is non-zero) |
||
| 260 |
✓ 182.1K
|
function _decodeBool(bytes memory data, uint256 offset) internal pure returns (bool) {
|
|
| 261 |
✓ 182.1K
|
return data[offset] != 0; |
|
| 262 |
} |
||
| 263 | |||
| 264 |
/// @notice Replaces an amount value within a byte array at the specified offset |
||
| 265 |
/// @dev Used to modify hook calldata for amount adjustments without full re-encoding |
||
| 266 |
/// Particularly useful for modifying amounts in multi-hook execution chains |
||
| 267 |
/// Directly modifies the bytes in-place for gas efficiency |
||
| 268 |
/// @param data The original byte array containing encoded calldata |
||
| 269 |
/// @param amount The new amount value to insert |
||
| 270 |
/// @param offset The position in the array where the amount starts |
||
| 271 |
/// @return The modified byte array with the replaced amount |
||
| 272 |
function _replaceCalldataAmount( |
||
| 273 |
bytes memory data, |
||
| 274 |
uint256 amount, |
||
| 275 |
uint256 offset |
||
| 276 |
) |
||
| 277 |
internal |
||
| 278 |
pure |
||
| 279 |
✓ 180.6K
|
returns (bytes memory) |
|
| 280 |
{
|
||
| 281 |
✓ 180.6K
|
bytes memory newAmountEncoded = abi.encodePacked(amount); |
|
| 282 |
✓ 6.0M
|
for (uint256 i; i < 32; ++i) {
|
|
| 283 |
✓ 5.8M
|
data[offset + i] = newAmountEncoded[i]; |
|
| 284 |
} |
||
| 285 |
✓ 180.6K
|
return data; |
|
| 286 |
} |
||
| 287 | |||
| 288 |
✓ 945.5K
|
function _makeAccountContextKey(address account) private pure returns (bytes32) {
|
|
| 289 |
✓ 945.5K
|
return keccak256(abi.encodePacked(ACCOUNT_CONTEXT_STORAGE, account)); |
|
| 290 |
} |
||
| 291 | |||
| 292 |
✓ 181.3K
|
function _createExecutionContext(address caller) private returns (uint256) {
|
|
| 293 |
// Always increment nonce for new execution context |
||
| 294 |
✓ 181.3K
|
executionNonce++; |
|
| 295 |
✓ 181.3K
|
bytes32 key = _makeAccountContextKey(caller); |
|
| 296 | |||
| 297 |
// Store this context for the current caller |
||
| 298 |
✓ 181.3K
|
uint256 currentNonce = executionNonce; // Load into local variable for assembly |
|
| 299 |
assembly {
|
||
| 300 |
✓ 181.3K
|
tstore(key, currentNonce) |
|
| 301 |
} |
||
| 302 | |||
| 303 |
✓ 181.3K
|
return executionNonce; |
|
| 304 |
} |
||
| 305 | |||
| 306 |
✓ 764.2K
|
function _getCurrentExecutionContext(address caller) private view returns (uint256 context) {
|
|
| 307 |
✓ 764.2K
|
bytes32 key = _makeAccountContextKey(caller); |
|
| 308 |
assembly {
|
||
| 309 |
✓ 1.4M
|
context := tload(key) |
|
| 310 |
} |
||
| 311 |
} |
||
| 312 | |||
| 313 |
function _makeKey(uint256 context, uint256 offset) private pure returns (bytes32) {
|
||
| 314 |
✓ 1.4M
|
return keccak256(abi.encodePacked(HOOK_EXECUTION_STORAGE, context, offset)); |
|
| 315 |
} |
||
| 316 | |||
| 317 |
✓ 124.5K
|
function _getOutAmount(uint256 context) private view returns (uint256 value) {
|
|
| 318 |
✓ 124.5K
|
bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
|
| 319 |
assembly {
|
||
| 320 |
value := tload(key) |
||
| 321 |
} |
||
| 322 |
} |
||
| 323 | |||
| 324 |
function _setOutAmount(uint256 value, address caller) internal {
|
||
| 325 |
✓ 257.6K
|
uint256 context = _getCurrentExecutionContext(caller); |
|
| 326 |
✓ 257.6K
|
bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET); |
|
| 327 |
assembly {
|
||
| 328 |
tstore(key, value) |
||
| 329 |
} |
||
| 330 |
} |
||
| 331 | |||
| 332 |
✓ 257.6K
|
function _getPreExecuteMutex(uint256 context) private view returns (bool value) {
|
|
| 333 |
✓ 257.6K
|
bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
|
| 334 |
assembly {
|
||
| 335 |
value := tload(key) |
||
| 336 |
} |
||
| 337 |
} |
||
| 338 | |||
| 339 |
function _setPreExecuteMutex(uint256 context, bool value) private {
|
||
| 340 |
✓ 257.6K
|
bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET); |
|
| 341 |
assembly {
|
||
| 342 |
tstore(key, value) |
||
| 343 |
} |
||
| 344 |
} |
||
| 345 | |||
| 346 |
✓ 249.0K
|
function _getPostExecuteMutex(uint256 context) private view returns (bool value) {
|
|
| 347 |
✓ 249.0K
|
bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
|
| 348 |
assembly {
|
||
| 349 |
value := tload(key) |
||
| 350 |
} |
||
| 351 |
} |
||
| 352 | |||
| 353 |
function _setPostExecuteMutex(uint256 context, bool value) private {
|
||
| 354 |
✓ 249.0K
|
bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET); |
|
| 355 |
assembly {
|
||
| 356 |
✓ 506.6K
|
tstore(key, value) |
|
| 357 |
} |
||
| 358 |
} |
||
| 359 | |||
| 360 |
function _clearExecutionState(uint256 context) private {
|
||
| 361 |
✓ 124.5K
|
_setPreExecuteMutex(context, false); |
|
| 362 |
✓ 124.5K
|
_setPostExecuteMutex(context, false); |
|
| 363 |
} |
||
| 364 |
} |
||
| 365 |
0.0%
lib/v2-core/src/hooks/VaultBankLockableHook.sol
Lines covered: 0 / 2 (0.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { HookDataDecoder } from "../libraries/HookDataDecoder.sol";
|
||
| 5 | |||
| 6 |
/// @title VaultBankLockableHook |
||
| 7 |
/// @author Superform Labs |
||
| 8 |
/// @notice Base implementation for all vault bank lockable hooks in the Superform system |
||
| 9 |
abstract contract VaultBankLockableHook {
|
||
| 10 |
using HookDataDecoder for bytes; |
||
| 11 | |||
| 12 |
/// @notice The vault bank address (if applicable) for cross-chain operations |
||
| 13 |
/// @dev Used primarily in bridge hooks to track source/destination vault banks |
||
| 14 |
address public transient vaultBank; |
||
| 15 | |||
| 16 |
/// @notice The destination chain ID for cross-chain operations |
||
| 17 |
/// @dev Used primarily in bridge hooks to track target chain |
||
| 18 |
uint256 public transient dstChainId; |
||
| 19 |
} |
||
| 20 |
94.3%
lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol
Lines covered: 33 / 35 (94.3%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 8 |
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 13 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 14 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 15 |
import {
|
||
| 16 |
ISuperHookResult, |
||
| 17 |
ISuperHookInflowOutflow, |
||
| 18 |
ISuperHookContextAware, |
||
| 19 |
ISuperHookInspector |
||
| 20 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 21 | |||
| 22 |
/// @title ApproveAndDeposit4626VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @notice This hook does not support tokens reverting on 0 approval |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice address token = BytesLib.toAddress(data, 52); |
||
| 29 |
/// @notice uint256 amount = BytesLib.toUint256(data, 72); |
||
| 30 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
||
| 31 |
contract ApproveAndDeposit4626VaultHook is |
||
| 32 |
BaseHook, |
||
| 33 |
VaultBankLockableHook, |
||
| 34 |
ISuperHookInflowOutflow, |
||
| 35 |
ISuperHookContextAware |
||
| 36 |
{
|
||
| 37 |
using HookDataDecoder for bytes; |
||
| 38 | |||
| 39 |
✓ 15.4K
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 40 |
✓ 15.4K
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
|
| 41 | |||
| 42 |
✓ 1
|
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
|
|
| 43 | |||
| 44 |
/*////////////////////////////////////////////////////////////// |
||
| 45 |
VIEW METHODS |
||
| 46 |
//////////////////////////////////////////////////////////////*/ |
||
| 47 |
/// @inheritdoc BaseHook |
||
| 48 |
function _buildHookExecutions( |
||
| 49 |
address prevHook, |
||
| 50 |
address account, |
||
| 51 |
bytes calldata data |
||
| 52 |
) |
||
| 53 |
internal |
||
| 54 |
view |
||
| 55 |
override |
||
| 56 |
✓ 15.4K
|
returns (Execution[] memory executions) |
|
| 57 |
{
|
||
| 58 |
✓ 15.4K
|
address yieldSource = data.extractYieldSource(); |
|
| 59 |
✓ 15.4K
|
address token = BytesLib.toAddress(data, 52); |
|
| 60 |
✓ 15.4K
|
uint256 amount = _decodeAmount(data); |
|
| 61 |
✓ 15.4K
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 62 | |||
| 63 |
✓ 15.4K
|
if (usePrevHookAmount) {
|
|
| 64 |
✓ 505
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 65 |
} |
||
| 66 | |||
| 67 |
✓ 14.9K
|
⟲ 587
|
if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 68 |
✓ 14.3K
|
if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 69 | |||
| 70 |
✓ 14.3K
|
executions = new Execution[](4); |
|
| 71 |
✓ 14.3K
|
executions[0] = |
|
| 72 |
✓ 14.3K
|
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
|
| 73 |
✓ 14.3K
|
executions[1] = |
|
| 74 |
✓ 14.3K
|
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
|
|
| 75 |
✓ 14.3K
|
executions[2] = |
|
| 76 |
✓ 14.3K
|
Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) });
|
|
| 77 |
✓ 14.3K
|
executions[3] = |
|
| 78 |
✓ 14.3K
|
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
|
| 79 |
} |
||
| 80 | |||
| 81 |
/*////////////////////////////////////////////////////////////// |
||
| 82 |
EXTERNAL METHODS |
||
| 83 |
//////////////////////////////////////////////////////////////*/ |
||
| 84 | |||
| 85 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 86 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 87 |
return _decodeAmount(data); |
||
| 88 |
} |
||
| 89 | |||
| 90 |
/// @inheritdoc ISuperHookContextAware |
||
| 91 |
✓ 15.4K
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 92 |
✓ 15.4K
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 93 |
} |
||
| 94 | |||
| 95 |
/// @inheritdoc ISuperHookInspector |
||
| 96 |
✓ 36.6K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 97 |
return abi.encodePacked( |
||
| 98 |
✓ 36.6K
|
data.extractYieldSource(), |
|
| 99 |
✓ 36.6K
|
BytesLib.toAddress(data, 52) //token |
|
| 100 |
); |
||
| 101 |
} |
||
| 102 | |||
| 103 |
/*////////////////////////////////////////////////////////////// |
||
| 104 |
INTERNAL METHODS |
||
| 105 |
//////////////////////////////////////////////////////////////*/ |
||
| 106 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 107 |
// store current balance |
||
| 108 |
✓ 14.3K
|
_setOutAmount(_getBalance(account, data), account); |
|
| 109 |
✓ 14.3K
|
spToken = data.extractYieldSource(); |
|
| 110 |
} |
||
| 111 | |||
| 112 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 113 |
✓ 28.7K
|
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
|
| 114 |
} |
||
| 115 | |||
| 116 |
/*////////////////////////////////////////////////////////////// |
||
| 117 |
PRIVATE METHODS |
||
| 118 |
//////////////////////////////////////////////////////////////*/ |
||
| 119 |
✓ 15.4K
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 120 |
✓ 15.4K
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 121 |
} |
||
| 122 | |||
| 123 |
✓ 28.7K
|
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 124 |
✓ 28.7K
|
return IERC4626(data.extractYieldSource()).balanceOf(account); |
|
| 125 |
} |
||
| 126 |
} |
||
| 127 |
59.3%
lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol
Lines covered: 16 / 27 (59.3%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 8 | |||
| 9 |
// Superform |
||
| 10 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 11 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 12 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 13 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 14 |
import {
|
||
| 15 |
ISuperHookResult, |
||
| 16 |
ISuperHookInflowOutflow, |
||
| 17 |
ISuperHookContextAware, |
||
| 18 |
ISuperHookInspector |
||
| 19 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 20 | |||
| 21 |
/// @title Deposit4626VaultHook |
||
| 22 |
/// @author Superform Labs |
||
| 23 |
/// @dev data has the following structure |
||
| 24 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 25 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 26 |
/// @notice uint256 amount = BytesLib.toUint256(data, 52); |
||
| 27 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 28 |
contract Deposit4626VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
|
||
| 29 |
using HookDataDecoder for bytes; |
||
| 30 | |||
| 31 |
✓ 803
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 32 |
✓ 803
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 33 | |||
| 34 |
✓ 1
|
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
|
|
| 35 |
/*////////////////////////////////////////////////////////////// |
||
| 36 |
VIEW METHODS |
||
| 37 |
//////////////////////////////////////////////////////////////*/ |
||
| 38 |
/// @inheritdoc BaseHook |
||
| 39 | |||
| 40 |
function _buildHookExecutions( |
||
| 41 |
address prevHook, |
||
| 42 |
address account, |
||
| 43 |
bytes calldata data |
||
| 44 |
) |
||
| 45 |
internal |
||
| 46 |
view |
||
| 47 |
override |
||
| 48 |
✓ 803
|
returns (Execution[] memory executions) |
|
| 49 |
{
|
||
| 50 |
✓ 803
|
address yieldSource = data.extractYieldSource(); |
|
| 51 |
✓ 803
|
uint256 amount = _decodeAmount(data); |
|
| 52 |
✓ 803
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 53 | |||
| 54 |
✓ 803
|
if (usePrevHookAmount) {
|
|
| 55 |
✓ 396
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 56 |
} |
||
| 57 | |||
| 58 |
✓ 407
|
⟲ 407
|
if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 59 |
if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 60 | |||
| 61 |
executions = new Execution[](1); |
||
| 62 |
executions[0] = |
||
| 63 |
Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) });
|
||
| 64 |
} |
||
| 65 | |||
| 66 |
/*////////////////////////////////////////////////////////////// |
||
| 67 |
EXTERNAL METHODS |
||
| 68 |
//////////////////////////////////////////////////////////////*/ |
||
| 69 | |||
| 70 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 71 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 72 |
return _decodeAmount(data); |
||
| 73 |
} |
||
| 74 | |||
| 75 |
/// @inheritdoc ISuperHookContextAware |
||
| 76 |
✓ 803
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 77 |
✓ 803
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 78 |
} |
||
| 79 | |||
| 80 |
/// @inheritdoc ISuperHookInspector |
||
| 81 |
✓ 5.0K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 82 |
✓ 5.0K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 83 |
} |
||
| 84 | |||
| 85 |
/*////////////////////////////////////////////////////////////// |
||
| 86 |
INTERNAL METHODS |
||
| 87 |
//////////////////////////////////////////////////////////////*/ |
||
| 88 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 89 |
// store current balance |
||
| 90 |
_setOutAmount(_getBalance(account, data), account); |
||
| 91 |
spToken = data.extractYieldSource(); |
||
| 92 |
} |
||
| 93 | |||
| 94 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 95 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 96 |
} |
||
| 97 | |||
| 98 |
/*////////////////////////////////////////////////////////////// |
||
| 99 |
PRIVATE METHODS |
||
| 100 |
//////////////////////////////////////////////////////////////*/ |
||
| 101 |
✓ 803
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 102 |
✓ 803
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 103 |
} |
||
| 104 | |||
| 105 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 106 |
return IERC4626(data.extractYieldSource()).balanceOf(account); |
||
| 107 |
} |
||
| 108 |
} |
||
| 109 |
97.6%
lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol
Lines covered: 40 / 41 (97.6%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 8 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResultOutflow, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookOutflow, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title Redeem4626VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @dev data has the following structure |
||
| 25 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 26 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 27 |
/// @notice address owner = BytesLib.toAddress(data, 52); |
||
| 28 |
/// @notice uint256 shares = BytesLib.toUint256(data, 72); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
||
| 30 |
contract Redeem4626VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
✓ 682.7K
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 34 |
✓ 181.3K
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
|
| 35 | |||
| 36 |
✓ 1
|
constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC4626) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
✓ 181.3K
|
returns (Execution[] memory executions) |
|
| 51 |
{
|
||
| 52 |
✓ 181.3K
|
address yieldSource = data.extractYieldSource(); |
|
| 53 |
✓ 181.3K
|
address owner = BytesLib.toAddress(data, 52); |
|
| 54 |
✓ 181.3K
|
uint256 shares = _decodeAmount(data); |
|
| 55 |
✓ 181.3K
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 56 | |||
| 57 |
✓ 181.3K
|
if (usePrevHookAmount) {
|
|
| 58 |
✓ 765
|
shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
|
| 59 |
} |
||
| 60 | |||
| 61 |
✓ 180.6K
|
⟲ 47.4K
|
if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 62 |
✓ 133.1K
|
if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 63 | |||
| 64 |
✓ 133.1K
|
executions = new Execution[](1); |
|
| 65 |
✓ 133.1K
|
executions[0] = Execution({
|
|
| 66 |
target: yieldSource, |
||
| 67 |
value: 0, |
||
| 68 |
✓ 133.1K
|
callData: abi.encodeCall(IERC4626.redeem, (shares, account, owner)) |
|
| 69 |
}); |
||
| 70 |
} |
||
| 71 | |||
| 72 |
/*////////////////////////////////////////////////////////////// |
||
| 73 |
EXTERNAL METHODS |
||
| 74 |
//////////////////////////////////////////////////////////////*/ |
||
| 75 | |||
| 76 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 77 |
✓ 501.4K
|
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
|
| 78 |
✓ 501.4K
|
return _decodeAmount(data); |
|
| 79 |
} |
||
| 80 | |||
| 81 |
/// @inheritdoc ISuperHookContextAware |
||
| 82 |
✓ 765
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 83 |
✓ 765
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 84 |
} |
||
| 85 | |||
| 86 |
/// @inheritdoc ISuperHookOutflow |
||
| 87 |
✓ 180.6K
|
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
|
|
| 88 |
✓ 180.6K
|
return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
|
| 89 |
} |
||
| 90 | |||
| 91 |
/// @inheritdoc ISuperHookInspector |
||
| 92 |
✓ 305.3K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 93 |
return abi.encodePacked( |
||
| 94 |
✓ 305.3K
|
data.extractYieldSource(), |
|
| 95 |
✓ 305.3K
|
BytesLib.toAddress(data, 52) // owner |
|
| 96 |
); |
||
| 97 |
} |
||
| 98 | |||
| 99 |
/*////////////////////////////////////////////////////////////// |
||
| 100 |
INTERNAL METHODS |
||
| 101 |
//////////////////////////////////////////////////////////////*/ |
||
| 102 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 103 |
✓ 133.1K
|
address yieldSource = data.extractYieldSource(); |
|
| 104 |
✓ 133.1K
|
asset = IERC4626(yieldSource).asset(); |
|
| 105 |
✓ 133.1K
|
_setOutAmount(_getBalance(account, data), account); |
|
| 106 |
✓ 133.1K
|
usedShares = _getSharesBalance(account, data); |
|
| 107 |
✓ 133.1K
|
spToken = yieldSource; |
|
| 108 |
} |
||
| 109 | |||
| 110 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 111 |
✓ 124.5K
|
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
|
| 112 |
✓ 124.5K
|
usedShares = usedShares - _getSharesBalance(account, data); |
|
| 113 |
} |
||
| 114 | |||
| 115 |
/*////////////////////////////////////////////////////////////// |
||
| 116 |
PRIVATE METHODS |
||
| 117 |
//////////////////////////////////////////////////////////////*/ |
||
| 118 |
✓ 682.7K
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 119 |
✓ 682.7K
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 120 |
} |
||
| 121 | |||
| 122 |
✓ 257.6K
|
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
|
| 123 |
✓ 257.6K
|
return IERC20(asset).balanceOf(account); |
|
| 124 |
} |
||
| 125 | |||
| 126 |
✓ 257.6K
|
function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 127 |
✓ 257.6K
|
address yieldSource = data.extractYieldSource(); |
|
| 128 |
✓ 257.6K
|
address owner = BytesLib.toAddress(data, 52); |
|
| 129 |
✓ 257.6K
|
if (owner == address(0)) {
|
|
| 130 |
owner = account; |
||
| 131 |
} |
||
| 132 |
✓ 257.6K
|
return IERC4626(yieldSource).balanceOf(owner); |
|
| 133 |
} |
||
| 134 |
} |
||
| 135 |
89.7%
lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol
Lines covered: 35 / 39 (89.7%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 8 |
import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 13 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 14 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 15 |
import {
|
||
| 16 |
ISuperHookResult, |
||
| 17 |
ISuperHookInflowOutflow, |
||
| 18 |
ISuperHookContextAware, |
||
| 19 |
ISuperHookInspector |
||
| 20 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 21 | |||
| 22 |
/// @title ApproveAndDeposit5115VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @notice This hook does not support tokens reverting on 0 approval |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice address tokenIn = BytesLib.toAddress(data, 52); |
||
| 29 |
/// @notice uint256 amount = BytesLib.toUint256(data, 72); |
||
| 30 |
/// @notice uint256 minSharesOut = BytesLib.toUint256(data, 104); |
||
| 31 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
||
| 32 |
contract ApproveAndDeposit5115VaultHook is |
||
| 33 |
BaseHook, |
||
| 34 |
VaultBankLockableHook, |
||
| 35 |
ISuperHookInflowOutflow, |
||
| 36 |
ISuperHookContextAware |
||
| 37 |
{
|
||
| 38 |
using HookDataDecoder for bytes; |
||
| 39 | |||
| 40 |
✓ 1.3K
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 41 |
✓ 1.3K
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
|
| 42 | |||
| 43 |
✓ 1
|
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { }
|
|
| 44 | |||
| 45 |
/*////////////////////////////////////////////////////////////// |
||
| 46 |
VIEW METHODS |
||
| 47 |
//////////////////////////////////////////////////////////////*/ |
||
| 48 |
/// @inheritdoc BaseHook |
||
| 49 |
function _buildHookExecutions( |
||
| 50 |
address prevHook, |
||
| 51 |
address account, |
||
| 52 |
bytes calldata data |
||
| 53 |
) |
||
| 54 |
internal |
||
| 55 |
view |
||
| 56 |
override |
||
| 57 |
✓ 1.3K
|
returns (Execution[] memory executions) |
|
| 58 |
{
|
||
| 59 |
✓ 1.3K
|
address yieldSource = data.extractYieldSource(); |
|
| 60 |
✓ 1.3K
|
address tokenIn = BytesLib.toAddress(data, 52); |
|
| 61 |
✓ 1.3K
|
uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 62 |
✓ 1.3K
|
uint256 minSharesOut = BytesLib.toUint256(data, 104); |
|
| 63 |
✓ 1.3K
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 64 | |||
| 65 |
✓ 1.3K
|
if (usePrevHookAmount) {
|
|
| 66 |
✓ 587
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 67 |
} |
||
| 68 | |||
| 69 |
✓ 710
|
⟲ 343
|
if (amount == 0) revert AMOUNT_NOT_VALID(); |
| 70 |
✓ 367
|
if (yieldSource == address(0) || account == address(0) || tokenIn == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 71 | |||
| 72 |
✓ 367
|
executions = new Execution[](4); |
|
| 73 |
✓ 367
|
executions[0] = |
|
| 74 |
✓ 367
|
Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
|
| 75 |
✓ 367
|
executions[1] = |
|
| 76 |
✓ 367
|
Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
|
|
| 77 |
✓ 367
|
executions[2] = Execution({
|
|
| 78 |
✓ 367
|
target: yieldSource, |
|
| 79 |
✓ 367
|
value: 0, |
|
| 80 |
✓ 367
|
callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
|
| 81 |
}); |
||
| 82 |
✓ 367
|
executions[3] = |
|
| 83 |
✓ 367
|
Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
|
| 84 |
} |
||
| 85 | |||
| 86 |
/*////////////////////////////////////////////////////////////// |
||
| 87 |
EXTERNAL METHODS |
||
| 88 |
//////////////////////////////////////////////////////////////*/ |
||
| 89 | |||
| 90 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 91 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 92 |
return _decodeAmount(data); |
||
| 93 |
} |
||
| 94 | |||
| 95 |
/// @inheritdoc ISuperHookContextAware |
||
| 96 |
✓ 1.3K
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 97 |
✓ 1.3K
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 98 |
} |
||
| 99 | |||
| 100 |
/// @inheritdoc ISuperHookInspector |
||
| 101 |
✓ 9.2K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 102 |
return abi.encodePacked( |
||
| 103 |
✓ 9.2K
|
data.extractYieldSource(), |
|
| 104 |
✓ 9.2K
|
BytesLib.toAddress(data, 52) // tokenIn |
|
| 105 |
); |
||
| 106 |
} |
||
| 107 | |||
| 108 |
/*////////////////////////////////////////////////////////////// |
||
| 109 |
INTERNAL METHODS |
||
| 110 |
//////////////////////////////////////////////////////////////*/ |
||
| 111 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 112 |
✓ 367
|
_setOutAmount(_getBalance(account, data), account); |
|
| 113 |
✓ 367
|
spToken = data.extractYieldSource(); |
|
| 114 |
✓ 367
|
asset = BytesLib.toAddress(data, 52); |
|
| 115 |
} |
||
| 116 | |||
| 117 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 118 |
✓ 367
|
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
|
| 119 |
} |
||
| 120 | |||
| 121 |
/*////////////////////////////////////////////////////////////// |
||
| 122 |
PRIVATE METHODS |
||
| 123 |
//////////////////////////////////////////////////////////////*/ |
||
| 124 |
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
||
| 125 |
return BytesLib.toUint256(data, AMOUNT_POSITION); |
||
| 126 |
} |
||
| 127 | |||
| 128 |
✓ 367
|
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 129 |
✓ 367
|
return IStandardizedYield(data.extractYieldSource()).balanceOf(account); |
|
| 130 |
} |
||
| 131 |
} |
||
| 132 |
39.4%
lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol
Lines covered: 13 / 33 (39.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 { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
|
||
| 8 | |||
| 9 |
// Superform |
||
| 10 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 11 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 12 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 13 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 14 |
import {
|
||
| 15 |
ISuperHookResult, |
||
| 16 |
ISuperHookInflowOutflow, |
||
| 17 |
ISuperHookContextAware, |
||
| 18 |
ISuperHookInspector |
||
| 19 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 20 | |||
| 21 |
/// @title Deposit5115VaultHook |
||
| 22 |
/// @author Superform Labs |
||
| 23 |
/// @dev data has the following structure |
||
| 24 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 25 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 26 |
/// @notice address tokenIn = BytesLib.toAddress(data, 52); |
||
| 27 |
/// @notice uint256 amount = BytesLib.toUint256(data, 72); |
||
| 28 |
/// @notice uint256 minSharesOut = BytesLib.toUint256(data, 104); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
||
| 30 |
contract Deposit5115VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
✓ 621
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 34 |
✓ 621
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
|
| 35 | |||
| 36 |
✓ 1
|
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
✓ 621
|
returns (Execution[] memory executions) |
|
| 51 |
{
|
||
| 52 |
✓ 621
|
address yieldSource = data.extractYieldSource(); |
|
| 53 |
✓ 621
|
address tokenIn = BytesLib.toAddress(data, 52); |
|
| 54 |
✓ 621
|
uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 55 |
✓ 621
|
uint256 minSharesOut = BytesLib.toUint256(data, 104); |
|
| 56 |
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
||
| 57 | |||
| 58 |
if (usePrevHookAmount) {
|
||
| 59 |
amount = ISuperHookResult(prevHook).getOutAmount(account); |
||
| 60 |
} |
||
| 61 | |||
| 62 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 63 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 64 | |||
| 65 |
executions = new Execution[](1); |
||
| 66 |
executions[0] = Execution({
|
||
| 67 |
target: yieldSource, |
||
| 68 |
value: tokenIn == address(0) ? amount : 0, |
||
| 69 |
callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut)) |
||
| 70 |
}); |
||
| 71 |
} |
||
| 72 | |||
| 73 |
/*////////////////////////////////////////////////////////////// |
||
| 74 |
EXTERNAL METHODS |
||
| 75 |
//////////////////////////////////////////////////////////////*/ |
||
| 76 | |||
| 77 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 78 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 79 |
return _decodeAmount(data); |
||
| 80 |
} |
||
| 81 | |||
| 82 |
/// @inheritdoc ISuperHookContextAware |
||
| 83 |
✓ 621
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 84 |
✓ 621
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 85 |
} |
||
| 86 | |||
| 87 |
/// @inheritdoc ISuperHookInspector |
||
| 88 |
✓ 5.8K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 89 |
return abi.encodePacked( |
||
| 90 |
✓ 5.8K
|
data.extractYieldSource(), |
|
| 91 |
✓ 5.8K
|
BytesLib.toAddress(data, 52) // tokenIn |
|
| 92 |
); |
||
| 93 |
} |
||
| 94 | |||
| 95 |
/*////////////////////////////////////////////////////////////// |
||
| 96 |
INTERNAL METHODS |
||
| 97 |
//////////////////////////////////////////////////////////////*/ |
||
| 98 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 99 |
_setOutAmount(_getBalance(account, data), account); |
||
| 100 |
spToken = data.extractYieldSource(); |
||
| 101 |
asset = BytesLib.toAddress(data, 52); |
||
| 102 |
} |
||
| 103 | |||
| 104 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 105 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 106 |
} |
||
| 107 | |||
| 108 |
/*////////////////////////////////////////////////////////////// |
||
| 109 |
PRIVATE METHODS |
||
| 110 |
//////////////////////////////////////////////////////////////*/ |
||
| 111 |
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
||
| 112 |
return BytesLib.toUint256(data, AMOUNT_POSITION); |
||
| 113 |
} |
||
| 114 | |||
| 115 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 116 |
return IStandardizedYield(data.extractYieldSource()).balanceOf(account); |
||
| 117 |
} |
||
| 118 |
} |
||
| 119 |
57.9%
lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol
Lines covered: 22 / 38 (57.9%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 7 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 8 |
import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResultOutflow, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookOutflow, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title Redeem5115VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @dev data has the following structure |
||
| 25 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 26 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 27 |
/// @notice address tokenOut = BytesLib.toAddress(data, 52); |
||
| 28 |
/// @notice uint256 shares = BytesLib.toUint256(data, 72); |
||
| 29 |
/// @notice uint256 minTokenOut = BytesLib.toUint256(data, 104); |
||
| 30 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 136); |
||
| 31 |
contract Redeem5115VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
|
||
| 32 |
using HookDataDecoder for bytes; |
||
| 33 | |||
| 34 |
✓ 30.8K
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 35 |
✓ 706
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136; |
|
| 36 | |||
| 37 |
✓ 1
|
constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC5115) { }
|
|
| 38 | |||
| 39 |
/*////////////////////////////////////////////////////////////// |
||
| 40 |
VIEW METHODS |
||
| 41 |
//////////////////////////////////////////////////////////////*/ |
||
| 42 |
/// @inheritdoc BaseHook |
||
| 43 |
function _buildHookExecutions( |
||
| 44 |
address prevHook, |
||
| 45 |
address account, |
||
| 46 |
bytes calldata data |
||
| 47 |
) |
||
| 48 |
internal |
||
| 49 |
view |
||
| 50 |
override |
||
| 51 |
✓ 1.4K
|
returns (Execution[] memory executions) |
|
| 52 |
{
|
||
| 53 |
✓ 1.4K
|
address yieldSource = data.extractYieldSource(); |
|
| 54 |
✓ 1.4K
|
address tokenOut = BytesLib.toAddress(data, 52); |
|
| 55 |
✓ 1.4K
|
uint256 shares = _decodeAmount(data); |
|
| 56 |
✓ 1.4K
|
uint256 minTokenOut = BytesLib.toUint256(data, 104); |
|
| 57 |
✓ 706
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 58 | |||
| 59 |
✓ 706
|
if (usePrevHookAmount) {
|
|
| 60 |
✓ 706
|
shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
|
| 61 |
} |
||
| 62 | |||
| 63 |
if (shares == 0) revert AMOUNT_NOT_VALID(); |
||
| 64 |
if (yieldSource == address(0) || tokenOut == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 65 | |||
| 66 |
executions = new Execution[](1); |
||
| 67 |
executions[0] = Execution({
|
||
| 68 |
target: yieldSource, |
||
| 69 |
value: 0, |
||
| 70 |
callData: abi.encodeCall(IStandardizedYield.redeem, (account, shares, tokenOut, minTokenOut, false)) |
||
| 71 |
}); |
||
| 72 |
} |
||
| 73 | |||
| 74 |
/*////////////////////////////////////////////////////////////// |
||
| 75 |
EXTERNAL METHODS |
||
| 76 |
//////////////////////////////////////////////////////////////*/ |
||
| 77 | |||
| 78 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 79 |
✓ 29.4K
|
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
|
| 80 |
✓ 29.4K
|
return _decodeAmount(data); |
|
| 81 |
} |
||
| 82 | |||
| 83 |
/// @inheritdoc ISuperHookContextAware |
||
| 84 |
✓ 706
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 85 |
✓ 706
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 86 |
} |
||
| 87 | |||
| 88 |
/// @inheritdoc ISuperHookOutflow |
||
| 89 |
✓ 711
|
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
|
|
| 90 |
✓ 711
|
return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
|
| 91 |
} |
||
| 92 | |||
| 93 |
/// @inheritdoc ISuperHookInspector |
||
| 94 |
✓ 30.8K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 95 |
return abi.encodePacked( |
||
| 96 |
✓ 30.8K
|
data.extractYieldSource(), |
|
| 97 |
✓ 30.8K
|
BytesLib.toAddress(data, 52) // tokenOut |
|
| 98 |
); |
||
| 99 |
} |
||
| 100 | |||
| 101 |
/*////////////////////////////////////////////////////////////// |
||
| 102 |
INTERNAL METHODS |
||
| 103 |
//////////////////////////////////////////////////////////////*/ |
||
| 104 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 105 |
asset = BytesLib.toAddress(data, 52); // tokenOut from data |
||
| 106 |
_setOutAmount(_getBalance(account, data), account); |
||
| 107 |
usedShares = _getSharesBalance(account, data); |
||
| 108 |
spToken = data.extractYieldSource(); |
||
| 109 |
} |
||
| 110 | |||
| 111 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 112 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 113 |
usedShares = usedShares - _getSharesBalance(account, data); |
||
| 114 |
} |
||
| 115 | |||
| 116 |
/*////////////////////////////////////////////////////////////// |
||
| 117 |
PRIVATE METHODS |
||
| 118 |
//////////////////////////////////////////////////////////////*/ |
||
| 119 |
✓ 30.8K
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 120 |
✓ 30.8K
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 121 |
} |
||
| 122 | |||
| 123 |
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
||
| 124 |
return IERC20(asset).balanceOf(account); |
||
| 125 |
} |
||
| 126 | |||
| 127 |
function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 128 |
address yieldSource = data.extractYieldSource(); |
||
| 129 |
return IStandardizedYield(yieldSource).balanceOf(account); |
||
| 130 |
} |
||
| 131 |
} |
||
| 132 |
47.2%
lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol
Lines covered: 17 / 36 (47.2%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 7 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 8 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import {
|
||
| 12 |
ISuperHookResult, |
||
| 13 |
ISuperHookInflowOutflow, |
||
| 14 |
ISuperHookContextAware, |
||
| 15 |
ISuperHookInspector |
||
| 16 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 17 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 18 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 19 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 20 | |||
| 21 |
/// @title ApproveAndRequestDeposit7540VaultHook |
||
| 22 |
/// @author Superform Labs |
||
| 23 |
/// @notice This hook does not support tokens reverting on 0 approval |
||
| 24 |
/// @dev data has the following structure |
||
| 25 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 26 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 27 |
/// @notice address token = BytesLib.toAddress(data, 52); |
||
| 28 |
/// @notice uint256 amount = BytesLib.toUint256(data, 72); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 104); |
||
| 30 |
contract ApproveAndRequestDeposit7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
✓ 4.8K
|
uint256 private constant AMOUNT_POSITION = 72; |
|
| 34 |
✓ 4.8K
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104; |
|
| 35 | |||
| 36 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
✓ 4.8K
|
returns (Execution[] memory executions) |
|
| 51 |
{
|
||
| 52 |
✓ 4.8K
|
address yieldSource = data.extractYieldSource(); |
|
| 53 |
✓ 4.8K
|
address token = BytesLib.toAddress(data, 52); |
|
| 54 |
✓ 4.8K
|
uint256 amount = _decodeAmount(data); |
|
| 55 |
✓ 4.8K
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 56 | |||
| 57 |
✓ 4.8K
|
if (usePrevHookAmount) {
|
|
| 58 |
✓ 4.8K
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 59 |
} |
||
| 60 | |||
| 61 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 62 |
if (yieldSource == address(0) || account == address(0) || token == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 63 | |||
| 64 |
executions = new Execution[](4); |
||
| 65 |
executions[0] = |
||
| 66 |
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
||
| 67 |
executions[1] = |
||
| 68 |
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
|
||
| 69 |
executions[2] = Execution({
|
||
| 70 |
target: yieldSource, |
||
| 71 |
value: 0, |
||
| 72 |
callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
||
| 73 |
}); |
||
| 74 |
executions[3] = |
||
| 75 |
Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
|
||
| 76 |
} |
||
| 77 | |||
| 78 |
/*////////////////////////////////////////////////////////////// |
||
| 79 |
EXTERNAL METHODS |
||
| 80 |
//////////////////////////////////////////////////////////////*/ |
||
| 81 | |||
| 82 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 83 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 84 |
return _decodeAmount(data); |
||
| 85 |
} |
||
| 86 | |||
| 87 |
/// @inheritdoc ISuperHookContextAware |
||
| 88 |
✓ 4.8K
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 89 |
✓ 4.8K
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 90 |
} |
||
| 91 | |||
| 92 |
/// @inheritdoc ISuperHookInspector |
||
| 93 |
✓ 10.0K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 94 |
return abi.encodePacked( |
||
| 95 |
✓ 10.0K
|
data.extractYieldSource(), |
|
| 96 |
✓ 10.0K
|
BytesLib.toAddress(data, 52) //token |
|
| 97 |
); |
||
| 98 |
} |
||
| 99 | |||
| 100 |
/*////////////////////////////////////////////////////////////// |
||
| 101 |
INTERNAL METHODS |
||
| 102 |
//////////////////////////////////////////////////////////////*/ |
||
| 103 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 104 |
_setOutAmount(_getBalance(account, data), account); |
||
| 105 |
} |
||
| 106 | |||
| 107 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 108 |
_setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
||
| 109 |
} |
||
| 110 | |||
| 111 |
/*////////////////////////////////////////////////////////////// |
||
| 112 |
PRIVATE METHODS |
||
| 113 |
//////////////////////////////////////////////////////////////*/ |
||
| 114 |
✓ 4.8K
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 115 |
✓ 4.8K
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 116 |
} |
||
| 117 | |||
| 118 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 119 |
return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account); |
||
| 120 |
} |
||
| 121 |
} |
||
| 122 |
100.0%
lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol
Lines covered: 11 / 11 (100.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 6 |
import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
|
||
| 7 | |||
| 8 |
// Superform |
||
| 9 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 10 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 11 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 12 |
import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
|
||
| 13 | |||
| 14 |
/// @title CancelDepositRequest7540Hook |
||
| 15 |
/// @author Superform Labs |
||
| 16 |
/// @dev data has the following structure |
||
| 17 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 18 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 19 |
contract CancelDepositRequest7540Hook is BaseHook {
|
||
| 20 |
using HookDataDecoder for bytes; |
||
| 21 | |||
| 22 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_DEPOSIT_REQUEST) { }
|
|
| 23 | |||
| 24 |
/*////////////////////////////////////////////////////////////// |
||
| 25 |
VIEW METHODS |
||
| 26 |
//////////////////////////////////////////////////////////////*/ |
||
| 27 |
/// @inheritdoc BaseHook |
||
| 28 |
function _buildHookExecutions( |
||
| 29 |
address, |
||
| 30 |
address account, |
||
| 31 |
bytes calldata data |
||
| 32 |
) |
||
| 33 |
internal |
||
| 34 |
pure |
||
| 35 |
override |
||
| 36 |
✓ 1.3K
|
returns (Execution[] memory executions) |
|
| 37 |
{
|
||
| 38 |
✓ 1.3K
|
address yieldSource = data.extractYieldSource(); |
|
| 39 | |||
| 40 |
✓ 1.3K
|
if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 41 | |||
| 42 |
✓ 1.3K
|
executions = new Execution[](1); |
|
| 43 |
✓ 1.3K
|
executions[0] = Execution({
|
|
| 44 |
✓ 1.3K
|
target: yieldSource, |
|
| 45 |
✓ 1.3K
|
value: 0, |
|
| 46 |
✓ 1.3K
|
callData: abi.encodeCall(IERC7540CancelDeposit.cancelDepositRequest, (0, account)) |
|
| 47 |
}); |
||
| 48 |
} |
||
| 49 | |||
| 50 |
/// @inheritdoc ISuperHookInspector |
||
| 51 |
✓ 5.3K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 52 |
✓ 5.3K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 53 |
} |
||
| 54 |
} |
||
| 55 |
100.0%
lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol
Lines covered: 11 / 11 (100.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 6 |
import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
|
||
| 7 | |||
| 8 |
// Superform |
||
| 9 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 10 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 11 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 12 |
import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
|
||
| 13 | |||
| 14 |
/// @title CancelRedeemRequest7540Hook |
||
| 15 |
/// @author Superform Labs |
||
| 16 |
/// @dev data has the following structure |
||
| 17 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 18 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 19 |
contract CancelRedeemRequest7540Hook is BaseHook {
|
||
| 20 |
using HookDataDecoder for bytes; |
||
| 21 | |||
| 22 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM_REQUEST) { }
|
|
| 23 | |||
| 24 |
/*////////////////////////////////////////////////////////////// |
||
| 25 |
VIEW METHODS |
||
| 26 |
//////////////////////////////////////////////////////////////*/ |
||
| 27 |
/// @inheritdoc BaseHook |
||
| 28 |
function _buildHookExecutions( |
||
| 29 |
address, |
||
| 30 |
address account, |
||
| 31 |
bytes calldata data |
||
| 32 |
) |
||
| 33 |
internal |
||
| 34 |
pure |
||
| 35 |
override |
||
| 36 |
✓ 581
|
returns (Execution[] memory executions) |
|
| 37 |
{
|
||
| 38 |
✓ 581
|
address yieldSource = data.extractYieldSource(); |
|
| 39 | |||
| 40 |
✓ 581
|
if (yieldSource == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 41 | |||
| 42 |
✓ 581
|
executions = new Execution[](1); |
|
| 43 |
✓ 581
|
executions[0] = Execution({
|
|
| 44 |
✓ 581
|
target: yieldSource, |
|
| 45 |
✓ 581
|
value: 0, |
|
| 46 |
✓ 581
|
callData: abi.encodeCall(IERC7540CancelRedeem.cancelRedeemRequest, (0, account)) |
|
| 47 |
}); |
||
| 48 |
} |
||
| 49 | |||
| 50 |
/// @inheritdoc ISuperHookInspector |
||
| 51 |
✓ 6.2K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 52 |
✓ 6.2K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 53 |
} |
||
| 54 |
} |
||
| 55 |
95.5%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol
Lines covered: 21 / 22 (95.5%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
|
||
| 8 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 9 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 10 | |||
| 11 |
// Superform |
||
| 12 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 13 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 14 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 15 |
import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
|
||
| 16 | |||
| 17 |
/// @title ClaimCancelDepositRequest7540Hook |
||
| 18 |
/// @author Superform Labs |
||
| 19 |
/// @dev data has the following structure |
||
| 20 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 21 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 22 |
/// @notice address receiver = BytesLib.toAddress(data, 52); |
||
| 23 |
contract ClaimCancelDepositRequest7540Hook is BaseHook, ISuperHookAsyncCancelations {
|
||
| 24 |
using HookDataDecoder for bytes; |
||
| 25 | |||
| 26 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_DEPOSIT_REQUEST) { }
|
|
| 27 | |||
| 28 |
/*////////////////////////////////////////////////////////////// |
||
| 29 |
VIEW METHODS |
||
| 30 |
//////////////////////////////////////////////////////////////*/ |
||
| 31 |
/// @inheritdoc BaseHook |
||
| 32 |
function _buildHookExecutions( |
||
| 33 |
address, |
||
| 34 |
address account, |
||
| 35 |
bytes calldata data |
||
| 36 |
) |
||
| 37 |
internal |
||
| 38 |
pure |
||
| 39 |
override |
||
| 40 |
✓ 647
|
returns (Execution[] memory executions) |
|
| 41 |
{
|
||
| 42 |
✓ 647
|
address yieldSource = data.extractYieldSource(); |
|
| 43 |
✓ 647
|
address receiver = BytesLib.toAddress(data, 52); |
|
| 44 | |||
| 45 |
✓ 647
|
if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 46 | |||
| 47 |
✓ 647
|
executions = new Execution[](1); |
|
| 48 |
✓ 647
|
executions[0] = Execution({
|
|
| 49 |
✓ 647
|
target: yieldSource, |
|
| 50 |
✓ 647
|
value: 0, |
|
| 51 |
✓ 647
|
callData: abi.encodeCall(IERC7540CancelDeposit.claimCancelDepositRequest, (0, receiver, account)) |
|
| 52 |
}); |
||
| 53 |
} |
||
| 54 | |||
| 55 |
/*////////////////////////////////////////////////////////////// |
||
| 56 |
EXTERNAL METHODS |
||
| 57 |
//////////////////////////////////////////////////////////////*/ |
||
| 58 | |||
| 59 |
/// @inheritdoc ISuperHookAsyncCancelations |
||
| 60 |
function isAsyncCancelHook() external pure returns (CancelationType) {
|
||
| 61 |
return CancelationType.INFLOW; |
||
| 62 |
} |
||
| 63 | |||
| 64 |
/// @inheritdoc ISuperHookInspector |
||
| 65 |
✓ 6.0K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 66 |
return abi.encodePacked( |
||
| 67 |
✓ 6.0K
|
data.extractYieldSource(), |
|
| 68 |
✓ 6.0K
|
BytesLib.toAddress(data, 52) //receiver |
|
| 69 |
); |
||
| 70 |
} |
||
| 71 | |||
| 72 |
/*////////////////////////////////////////////////////////////// |
||
| 73 |
INTERNAL METHODS |
||
| 74 |
//////////////////////////////////////////////////////////////*/ |
||
| 75 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 76 |
✓ 647
|
address yieldSource = data.extractYieldSource(); |
|
| 77 |
✓ 647
|
address receiver = BytesLib.toAddress(data, 52); |
|
| 78 |
✓ 647
|
asset = IERC7540(yieldSource).asset(); |
|
| 79 |
// store current balance |
||
| 80 |
✓ 476
|
_setOutAmount(_getBalance(receiver, data), account); |
|
| 81 |
} |
||
| 82 | |||
| 83 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 84 |
✓ 149
|
address receiver = BytesLib.toAddress(data, 52); |
|
| 85 | |||
| 86 |
✓ 149
|
_setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account); |
|
| 87 |
} |
||
| 88 | |||
| 89 |
/*////////////////////////////////////////////////////////////// |
||
| 90 |
PRIVATE METHODS |
||
| 91 |
//////////////////////////////////////////////////////////////*/ |
||
| 92 | |||
| 93 |
✓ 625
|
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
|
| 94 |
✓ 625
|
return IERC20(asset).balanceOf(account); |
|
| 95 |
} |
||
| 96 |
} |
||
| 97 |
95.0%
lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol
Lines covered: 19 / 20 (95.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 8 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 9 |
import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
|
||
| 10 | |||
| 11 |
// Superform |
||
| 12 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 13 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 14 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 15 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 16 |
import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
|
||
| 17 | |||
| 18 |
/// @title ClaimCancelRedeemRequest7540Hook |
||
| 19 |
/// @author Superform Labs |
||
| 20 |
/// @dev data has the following structure |
||
| 21 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 22 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 23 |
/// @notice address receiver = BytesLib.toAddress(data, 52); |
||
| 24 |
contract ClaimCancelRedeemRequest7540Hook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations {
|
||
| 25 |
using HookDataDecoder for bytes; |
||
| 26 | |||
| 27 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_REDEEM_REQUEST) { }
|
|
| 28 | |||
| 29 |
/*////////////////////////////////////////////////////////////// |
||
| 30 |
VIEW METHODS |
||
| 31 |
//////////////////////////////////////////////////////////////*/ |
||
| 32 |
/// @inheritdoc BaseHook |
||
| 33 |
function _buildHookExecutions( |
||
| 34 |
address, |
||
| 35 |
address account, |
||
| 36 |
bytes calldata data |
||
| 37 |
) |
||
| 38 |
internal |
||
| 39 |
pure |
||
| 40 |
override |
||
| 41 |
✓ 1.0K
|
returns (Execution[] memory executions) |
|
| 42 |
{
|
||
| 43 |
✓ 1.0K
|
address yieldSource = data.extractYieldSource(); |
|
| 44 |
✓ 1.0K
|
address receiver = BytesLib.toAddress(data, 52); |
|
| 45 | |||
| 46 |
✓ 1.0K
|
if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 47 | |||
| 48 |
✓ 1.0K
|
executions = new Execution[](1); |
|
| 49 |
✓ 1.0K
|
executions[0] = Execution({
|
|
| 50 |
✓ 1.0K
|
target: yieldSource, |
|
| 51 |
✓ 1.0K
|
value: 0, |
|
| 52 |
✓ 1.0K
|
callData: abi.encodeCall(IERC7540CancelRedeem.claimCancelRedeemRequest, (0, receiver, account)) |
|
| 53 |
}); |
||
| 54 |
} |
||
| 55 | |||
| 56 |
/*////////////////////////////////////////////////////////////// |
||
| 57 |
EXTERNAL METHODS |
||
| 58 |
//////////////////////////////////////////////////////////////*/ |
||
| 59 | |||
| 60 |
/// @inheritdoc ISuperHookAsyncCancelations |
||
| 61 |
function isAsyncCancelHook() external pure returns (CancelationType) {
|
||
| 62 |
return CancelationType.OUTFLOW; |
||
| 63 |
} |
||
| 64 | |||
| 65 |
/// @inheritdoc ISuperHookInspector |
||
| 66 |
✓ 4.1K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 67 |
return abi.encodePacked( |
||
| 68 |
✓ 4.1K
|
data.extractYieldSource(), |
|
| 69 |
✓ 4.1K
|
BytesLib.toAddress(data, 52) //receiver |
|
| 70 |
); |
||
| 71 |
} |
||
| 72 | |||
| 73 |
/*////////////////////////////////////////////////////////////// |
||
| 74 |
INTERNAL METHODS |
||
| 75 |
//////////////////////////////////////////////////////////////*/ |
||
| 76 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 77 |
✓ 1.0K
|
_setOutAmount(_getBalance(account, data), account); |
|
| 78 |
✓ 526
|
spToken = IERC7540(data.extractYieldSource()).share(); |
|
| 79 |
} |
||
| 80 | |||
| 81 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 82 |
✓ 526
|
address receiver = BytesLib.toAddress(data, 52); |
|
| 83 |
✓ 526
|
_setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account); |
|
| 84 |
} |
||
| 85 | |||
| 86 |
/*////////////////////////////////////////////////////////////// |
||
| 87 |
PRIVATE METHODS |
||
| 88 |
//////////////////////////////////////////////////////////////*/ |
||
| 89 | |||
| 90 |
✓ 1.5K
|
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 91 |
✓ 1.5K
|
return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
|
| 92 |
} |
||
| 93 |
} |
||
| 94 |
55.6%
lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol
Lines covered: 15 / 27 (55.6%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 8 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 13 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 14 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 15 |
import {
|
||
| 16 |
ISuperHookResult, |
||
| 17 |
ISuperHookInflowOutflow, |
||
| 18 |
ISuperHookContextAware, |
||
| 19 |
ISuperHookInspector |
||
| 20 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 21 | |||
| 22 |
/// @title Deposit7540VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @dev data has the following structure |
||
| 25 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 26 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 27 |
/// @notice uint256 amount = BytesLib.toUint256(data, 52); |
||
| 28 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 29 |
contract Deposit7540VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
|
||
| 30 |
using HookDataDecoder for bytes; |
||
| 31 | |||
| 32 |
✓ 535
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 33 |
✓ 535
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 34 | |||
| 35 |
✓ 1
|
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC7540) { }
|
|
| 36 | |||
| 37 |
/*////////////////////////////////////////////////////////////// |
||
| 38 |
VIEW METHODS |
||
| 39 |
//////////////////////////////////////////////////////////////*/ |
||
| 40 |
/// @inheritdoc BaseHook |
||
| 41 |
function _buildHookExecutions( |
||
| 42 |
address prevHook, |
||
| 43 |
address account, |
||
| 44 |
bytes calldata data |
||
| 45 |
) |
||
| 46 |
internal |
||
| 47 |
view |
||
| 48 |
override |
||
| 49 |
✓ 535
|
returns (Execution[] memory executions) |
|
| 50 |
{
|
||
| 51 |
✓ 535
|
address yieldSource = data.extractYieldSource(); |
|
| 52 |
✓ 535
|
uint256 amount = _decodeAmount(data); |
|
| 53 |
✓ 535
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 54 | |||
| 55 |
✓ 535
|
if (usePrevHookAmount) {
|
|
| 56 |
✓ 535
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 57 |
} |
||
| 58 | |||
| 59 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 60 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 61 | |||
| 62 |
executions = new Execution[](1); |
||
| 63 |
executions[0] = Execution({
|
||
| 64 |
target: yieldSource, |
||
| 65 |
value: 0, |
||
| 66 |
callData: abi.encodeCall(IERC7540.deposit, (amount, account, account)) |
||
| 67 |
}); |
||
| 68 |
} |
||
| 69 | |||
| 70 |
/*////////////////////////////////////////////////////////////// |
||
| 71 |
EXTERNAL METHODS |
||
| 72 |
//////////////////////////////////////////////////////////////*/ |
||
| 73 | |||
| 74 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 75 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 76 |
return _decodeAmount(data); |
||
| 77 |
} |
||
| 78 | |||
| 79 |
/// @inheritdoc ISuperHookContextAware |
||
| 80 |
✓ 535
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 81 |
✓ 535
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 82 |
} |
||
| 83 | |||
| 84 |
/// @inheritdoc ISuperHookInspector |
||
| 85 |
✓ 3.5K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 86 |
✓ 3.5K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 87 |
} |
||
| 88 | |||
| 89 |
/*////////////////////////////////////////////////////////////// |
||
| 90 |
INTERNAL METHODS |
||
| 91 |
//////////////////////////////////////////////////////////////*/ |
||
| 92 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 93 |
// store current balance |
||
| 94 |
_setOutAmount(_getBalance(account, data), account); |
||
| 95 |
spToken = IERC7540(data.extractYieldSource()).share(); |
||
| 96 |
} |
||
| 97 | |||
| 98 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 99 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 100 |
} |
||
| 101 | |||
| 102 |
/*////////////////////////////////////////////////////////////// |
||
| 103 |
PRIVATE METHODS |
||
| 104 |
//////////////////////////////////////////////////////////////*/ |
||
| 105 |
✓ 535
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 106 |
✓ 535
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 107 |
} |
||
| 108 | |||
| 109 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 110 |
return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
||
| 111 |
} |
||
| 112 |
} |
||
| 113 |
100.0%
lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol
Lines covered: 36 / 36 (100.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 8 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResultOutflow, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookOutflow, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title Redeem7540VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @notice This hook does not support tokens reverting on 0 approval |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice uint256 shares = BytesLib.toUint256(data, 52); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 30 |
contract Redeem7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
✓ 81.9K
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 34 |
✓ 14.4K
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 35 | |||
| 36 |
✓ 1
|
constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
✓ 14.4K
|
returns (Execution[] memory executions) |
|
| 51 |
{
|
||
| 52 |
✓ 14.4K
|
address yieldSource = data.extractYieldSource(); |
|
| 53 |
✓ 14.4K
|
uint256 shares = _decodeAmount(data); |
|
| 54 |
✓ 14.4K
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 55 | |||
| 56 |
✓ 14.4K
|
if (usePrevHookAmount) {
|
|
| 57 |
✓ 556
|
shares = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
|
| 58 |
} |
||
| 59 | |||
| 60 |
✓ 13.9K
|
⟲ 7.5K
|
if (shares == 0) revert AMOUNT_NOT_VALID(); |
| 61 |
✓ 6.3K
|
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 62 | |||
| 63 |
✓ 6.3K
|
executions = new Execution[](1); |
|
| 64 |
✓ 6.3K
|
executions[0] = Execution({
|
|
| 65 |
target: yieldSource, |
||
| 66 |
value: 0, |
||
| 67 |
✓ 6.3K
|
callData: abi.encodeCall(IERC7540.redeem, (shares, account, account)) |
|
| 68 |
}); |
||
| 69 |
} |
||
| 70 | |||
| 71 |
/*////////////////////////////////////////////////////////////// |
||
| 72 |
EXTERNAL METHODS |
||
| 73 |
//////////////////////////////////////////////////////////////*/ |
||
| 74 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 75 |
✓ 67.5K
|
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
|
| 76 |
✓ 67.5K
|
return _decodeAmount(data); |
|
| 77 |
} |
||
| 78 | |||
| 79 |
/// @inheritdoc ISuperHookContextAware |
||
| 80 |
✓ 556
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 81 |
✓ 556
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 82 |
} |
||
| 83 | |||
| 84 |
/// @inheritdoc ISuperHookOutflow |
||
| 85 |
✓ 13.9K
|
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
|
|
| 86 |
✓ 13.9K
|
return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
|
| 87 |
} |
||
| 88 | |||
| 89 |
/// @inheritdoc ISuperHookInspector |
||
| 90 |
✓ 54.9K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 91 |
✓ 54.9K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 92 |
} |
||
| 93 | |||
| 94 |
/*////////////////////////////////////////////////////////////// |
||
| 95 |
INTERNAL METHODS |
||
| 96 |
//////////////////////////////////////////////////////////////*/ |
||
| 97 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 98 |
✓ 6.3K
|
address yieldSource = data.extractYieldSource(); |
|
| 99 |
✓ 6.3K
|
asset = IERC7540(yieldSource).asset(); |
|
| 100 |
✓ 6.3K
|
_setOutAmount(_getBalance(account, data), account); |
|
| 101 |
✓ 6.3K
|
usedShares = _getSharesBalance(account, data); |
|
| 102 |
✓ 6.3K
|
spToken = IERC7540(yieldSource).share(); |
|
| 103 |
} |
||
| 104 | |||
| 105 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 106 |
✓ 1.6K
|
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
|
| 107 |
✓ 1.6K
|
usedShares = usedShares - _getSharesBalance(account, data); |
|
| 108 |
} |
||
| 109 | |||
| 110 |
/*////////////////////////////////////////////////////////////// |
||
| 111 |
PRIVATE METHODS |
||
| 112 |
//////////////////////////////////////////////////////////////*/ |
||
| 113 |
✓ 81.9K
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 114 |
✓ 81.9K
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 115 |
} |
||
| 116 | |||
| 117 |
✓ 8.0K
|
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
|
| 118 |
✓ 8.0K
|
return IERC20(asset).balanceOf(account); |
|
| 119 |
} |
||
| 120 | |||
| 121 |
✓ 8.0K
|
function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 122 |
✓ 8.0K
|
address yieldSource = data.extractYieldSource(); |
|
| 123 |
✓ 8.0K
|
return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
|
| 124 |
} |
||
| 125 |
} |
||
| 126 |
55.6%
lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol
Lines covered: 15 / 27 (55.6%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 8 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResult, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookAsyncCancelations, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title RequestDeposit7540VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @dev data has the following structure |
||
| 25 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 26 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 27 |
/// @notice uint256 amount = BytesLib.toUint256(data, 52); |
||
| 28 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 29 |
contract RequestDeposit7540VaultHook is |
||
| 30 |
BaseHook, |
||
| 31 |
ISuperHookInflowOutflow, |
||
| 32 |
ISuperHookAsyncCancelations, |
||
| 33 |
ISuperHookContextAware |
||
| 34 |
{
|
||
| 35 |
using HookDataDecoder for bytes; |
||
| 36 | |||
| 37 |
✓ 596
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 38 |
✓ 596
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 39 | |||
| 40 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
|
|
| 41 | |||
| 42 |
/*////////////////////////////////////////////////////////////// |
||
| 43 |
VIEW METHODS |
||
| 44 |
//////////////////////////////////////////////////////////////*/ |
||
| 45 |
/// @inheritdoc BaseHook |
||
| 46 |
function _buildHookExecutions( |
||
| 47 |
address prevHook, |
||
| 48 |
address account, |
||
| 49 |
bytes calldata data |
||
| 50 |
) |
||
| 51 |
internal |
||
| 52 |
view |
||
| 53 |
override |
||
| 54 |
✓ 596
|
returns (Execution[] memory executions) |
|
| 55 |
{
|
||
| 56 |
✓ 596
|
address yieldSource = data.extractYieldSource(); |
|
| 57 |
✓ 596
|
uint256 amount = _decodeAmount(data); |
|
| 58 |
✓ 596
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 59 | |||
| 60 |
✓ 596
|
if (usePrevHookAmount) {
|
|
| 61 |
✓ 596
|
amount = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 62 |
} |
||
| 63 | |||
| 64 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 65 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 66 | |||
| 67 |
executions = new Execution[](1); |
||
| 68 |
executions[0] = Execution({
|
||
| 69 |
target: yieldSource, |
||
| 70 |
value: 0, |
||
| 71 |
callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account)) |
||
| 72 |
}); |
||
| 73 |
} |
||
| 74 | |||
| 75 |
/// @inheritdoc ISuperHookAsyncCancelations |
||
| 76 |
function isAsyncCancelHook() external pure returns (CancelationType) {
|
||
| 77 |
return CancelationType.NONE; |
||
| 78 |
} |
||
| 79 | |||
| 80 |
/*////////////////////////////////////////////////////////////// |
||
| 81 |
EXTERNAL METHODS |
||
| 82 |
//////////////////////////////////////////////////////////////*/ |
||
| 83 | |||
| 84 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 85 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 86 |
return _decodeAmount(data); |
||
| 87 |
} |
||
| 88 | |||
| 89 |
/// @inheritdoc ISuperHookContextAware |
||
| 90 |
✓ 596
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 91 |
✓ 596
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 92 |
} |
||
| 93 | |||
| 94 |
/// @inheritdoc ISuperHookInspector |
||
| 95 |
✓ 7.1K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 96 |
✓ 7.1K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 97 |
} |
||
| 98 | |||
| 99 |
/*////////////////////////////////////////////////////////////// |
||
| 100 |
INTERNAL METHODS |
||
| 101 |
//////////////////////////////////////////////////////////////*/ |
||
| 102 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 103 |
_setOutAmount(_getBalance(account, data), account); |
||
| 104 |
} |
||
| 105 | |||
| 106 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 107 |
_setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
||
| 108 |
} |
||
| 109 | |||
| 110 |
/*////////////////////////////////////////////////////////////// |
||
| 111 |
PRIVATE METHODS |
||
| 112 |
//////////////////////////////////////////////////////////////*/ |
||
| 113 |
✓ 596
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 114 |
✓ 596
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 115 |
} |
||
| 116 | |||
| 117 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 118 |
return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account); |
||
| 119 |
} |
||
| 120 |
} |
||
| 121 |
55.6%
lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol
Lines covered: 15 / 27 (55.6%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 8 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 9 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 10 | |||
| 11 |
// Superform |
||
| 12 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 13 |
import {
|
||
| 14 |
ISuperHookResult, |
||
| 15 |
ISuperHookInflowOutflow, |
||
| 16 |
ISuperHookAsyncCancelations, |
||
| 17 |
ISuperHookContextAware, |
||
| 18 |
ISuperHookInspector |
||
| 19 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 20 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 21 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 22 | |||
| 23 |
/// @title RequestRedeem7540VaultHook |
||
| 24 |
/// @author Superform Labs |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice uint256 shares = BytesLib.toUint256(data, 52); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 30 |
contract RequestRedeem7540VaultHook is |
||
| 31 |
BaseHook, |
||
| 32 |
ISuperHookInflowOutflow, |
||
| 33 |
ISuperHookAsyncCancelations, |
||
| 34 |
ISuperHookContextAware |
||
| 35 |
{
|
||
| 36 |
using HookDataDecoder for bytes; |
||
| 37 | |||
| 38 |
✓ 521
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 39 |
✓ 521
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 40 | |||
| 41 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
|
|
| 42 | |||
| 43 |
/*////////////////////////////////////////////////////////////// |
||
| 44 |
VIEW METHODS |
||
| 45 |
//////////////////////////////////////////////////////////////*/ |
||
| 46 |
/// @inheritdoc BaseHook |
||
| 47 |
function _buildHookExecutions( |
||
| 48 |
address prevHook, |
||
| 49 |
address account, |
||
| 50 |
bytes calldata data |
||
| 51 |
) |
||
| 52 |
internal |
||
| 53 |
view |
||
| 54 |
override |
||
| 55 |
✓ 521
|
returns (Execution[] memory executions) |
|
| 56 |
{
|
||
| 57 |
✓ 521
|
address yieldSource = data.extractYieldSource(); |
|
| 58 |
✓ 521
|
uint256 shares = _decodeAmount(data); |
|
| 59 |
✓ 521
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 60 | |||
| 61 |
✓ 521
|
if (usePrevHookAmount) {
|
|
| 62 |
✓ 521
|
shares = ISuperHookResult(prevHook).getOutAmount(account); |
|
| 63 |
} |
||
| 64 | |||
| 65 |
if (shares == 0) revert AMOUNT_NOT_VALID(); |
||
| 66 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 67 | |||
| 68 |
executions = new Execution[](1); |
||
| 69 |
executions[0] = Execution({
|
||
| 70 |
target: yieldSource, |
||
| 71 |
value: 0, |
||
| 72 |
callData: abi.encodeCall(IERC7540.requestRedeem, (shares, account, account)) |
||
| 73 |
}); |
||
| 74 |
} |
||
| 75 | |||
| 76 |
/// @inheritdoc ISuperHookAsyncCancelations |
||
| 77 |
function isAsyncCancelHook() external pure returns (CancelationType) {
|
||
| 78 |
return CancelationType.NONE; |
||
| 79 |
} |
||
| 80 | |||
| 81 |
/*////////////////////////////////////////////////////////////// |
||
| 82 |
EXTERNAL METHODS |
||
| 83 |
//////////////////////////////////////////////////////////////*/ |
||
| 84 | |||
| 85 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 86 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 87 |
return _decodeAmount(data); |
||
| 88 |
} |
||
| 89 | |||
| 90 |
/// @inheritdoc ISuperHookContextAware |
||
| 91 |
✓ 521
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 92 |
✓ 521
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 93 |
} |
||
| 94 | |||
| 95 |
/// @inheritdoc ISuperHookInspector |
||
| 96 |
✓ 3.2K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 97 |
✓ 3.2K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 98 |
} |
||
| 99 | |||
| 100 |
/*////////////////////////////////////////////////////////////// |
||
| 101 |
INTERNAL METHODS |
||
| 102 |
//////////////////////////////////////////////////////////////*/ |
||
| 103 | |||
| 104 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 105 |
_setOutAmount(_getBalance(account, data), account); |
||
| 106 |
} |
||
| 107 | |||
| 108 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 109 |
_setOutAmount(getOutAmount(account) - _getBalance(account, data), account); |
||
| 110 |
} |
||
| 111 | |||
| 112 |
/*////////////////////////////////////////////////////////////// |
||
| 113 |
PRIVATE METHODS |
||
| 114 |
//////////////////////////////////////////////////////////////*/ |
||
| 115 | |||
| 116 |
✓ 521
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 117 |
✓ 521
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 118 |
} |
||
| 119 | |||
| 120 |
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 121 |
return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
||
| 122 |
} |
||
| 123 |
} |
||
| 124 |
41.7%
lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol
Lines covered: 15 / 36 (41.7%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 8 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResultOutflow, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookOutflow, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title Withdraw7540VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice uint256 amount = BytesLib.toUint256(data, 52); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 30 |
contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
✓ 682
|
uint256 private constant AMOUNT_POSITION = 52; |
|
| 34 |
✓ 682
|
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
|
| 35 | |||
| 36 |
✓ 2
|
constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
✓ 682
|
returns (Execution[] memory executions) |
|
| 51 |
{
|
||
| 52 |
✓ 682
|
address yieldSource = data.extractYieldSource(); |
|
| 53 |
✓ 682
|
uint256 amount = _decodeAmount(data); |
|
| 54 |
✓ 682
|
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 55 | |||
| 56 |
✓ 682
|
if (usePrevHookAmount) {
|
|
| 57 |
✓ 682
|
amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
|
| 58 |
} |
||
| 59 | |||
| 60 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 61 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 62 | |||
| 63 |
executions = new Execution[](1); |
||
| 64 |
executions[0] = Execution({
|
||
| 65 |
target: yieldSource, |
||
| 66 |
value: 0, |
||
| 67 |
callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
||
| 68 |
}); |
||
| 69 |
} |
||
| 70 | |||
| 71 |
/*////////////////////////////////////////////////////////////// |
||
| 72 |
EXTERNAL METHODS |
||
| 73 |
//////////////////////////////////////////////////////////////*/ |
||
| 74 | |||
| 75 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 76 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 77 |
return _decodeAmount(data); |
||
| 78 |
} |
||
| 79 | |||
| 80 |
/// @inheritdoc ISuperHookContextAware |
||
| 81 |
✓ 682
|
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
|
| 82 |
✓ 682
|
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
|
| 83 |
} |
||
| 84 | |||
| 85 |
/// @inheritdoc ISuperHookOutflow |
||
| 86 |
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
|
||
| 87 |
return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
||
| 88 |
} |
||
| 89 | |||
| 90 |
/// @inheritdoc ISuperHookInspector |
||
| 91 |
✓ 6.4K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 92 |
✓ 6.4K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 93 |
} |
||
| 94 | |||
| 95 |
/*////////////////////////////////////////////////////////////// |
||
| 96 |
INTERNAL METHODS |
||
| 97 |
//////////////////////////////////////////////////////////////*/ |
||
| 98 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 99 |
address yieldSource = data.extractYieldSource(); |
||
| 100 |
asset = IERC7540(yieldSource).asset(); |
||
| 101 |
_setOutAmount(_getBalance(account, data), account); |
||
| 102 |
usedShares = _getSharesBalance(account, data); |
||
| 103 |
spToken = IERC7540(yieldSource).share(); |
||
| 104 |
} |
||
| 105 | |||
| 106 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 107 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 108 |
usedShares = usedShares - _getSharesBalance(account, data); |
||
| 109 |
} |
||
| 110 | |||
| 111 |
/*////////////////////////////////////////////////////////////// |
||
| 112 |
PRIVATE METHODS |
||
| 113 |
//////////////////////////////////////////////////////////////*/ |
||
| 114 |
✓ 682
|
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
|
| 115 |
✓ 682
|
return BytesLib.toUint256(data, AMOUNT_POSITION); |
|
| 116 |
} |
||
| 117 | |||
| 118 |
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
||
| 119 |
return IERC20(asset).balanceOf(account); |
||
| 120 |
} |
||
| 121 | |||
| 122 |
function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 123 |
address yieldSource = data.extractYieldSource(); |
||
| 124 |
return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
||
| 125 |
} |
||
| 126 |
} |
||
| 127 |
93.3%
lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol
Lines covered: 14 / 15 (93.3%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 6 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 8 | |||
| 9 |
// Superform |
||
| 10 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 11 |
import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
|
||
| 12 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 13 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 14 |
import { ISuperVault } from "../../../vendor/superform/ISuperVault.sol";
|
||
| 15 |
import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
|
||
| 16 | |||
| 17 |
/// @title CancelRedeemHook |
||
| 18 |
/// @author Superform Labs |
||
| 19 |
/// @dev data has the following structure |
||
| 20 |
/// @notice bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 21 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 22 |
contract CancelRedeemHook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations {
|
||
| 23 |
using HookDataDecoder for bytes; |
||
| 24 | |||
| 25 |
✓ 1
|
constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM) { }
|
|
| 26 | |||
| 27 |
/*////////////////////////////////////////////////////////////// |
||
| 28 |
VIEW METHODS |
||
| 29 |
//////////////////////////////////////////////////////////////*/ |
||
| 30 |
function _buildHookExecutions( |
||
| 31 |
address, |
||
| 32 |
address account, |
||
| 33 |
bytes calldata data |
||
| 34 |
) |
||
| 35 |
internal |
||
| 36 |
pure |
||
| 37 |
override |
||
| 38 |
✓ 922
|
returns (Execution[] memory executions) |
|
| 39 |
{
|
||
| 40 |
✓ 922
|
address yieldSource = data.extractYieldSource(); |
|
| 41 | |||
| 42 |
✓ 922
|
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
|
| 43 | |||
| 44 |
✓ 922
|
executions = new Execution[](1); |
|
| 45 |
✓ 922
|
executions[0] = |
|
| 46 |
✓ 922
|
Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(ISuperVault.cancelRedeem, (account)) });
|
|
| 47 |
} |
||
| 48 | |||
| 49 |
/*////////////////////////////////////////////////////////////// |
||
| 50 |
EX`TERNAL METHODS |
||
| 51 |
//////////////////////////////////////////////////////////////*/ |
||
| 52 | |||
| 53 |
/// @inheritdoc ISuperHookAsyncCancelations |
||
| 54 |
function isAsyncCancelHook() external pure returns (CancelationType) {
|
||
| 55 |
return CancelationType.OUTFLOW; |
||
| 56 |
} |
||
| 57 | |||
| 58 |
/// @inheritdoc ISuperHookInspector |
||
| 59 |
✓ 5.0K
|
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
|
| 60 |
✓ 5.0K
|
return abi.encodePacked(data.extractYieldSource()); |
|
| 61 |
} |
||
| 62 | |||
| 63 |
/*////////////////////////////////////////////////////////////// |
||
| 64 |
INTERNAL METHODS |
||
| 65 |
//////////////////////////////////////////////////////////////*/ |
||
| 66 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 67 |
✓ 922
|
_setOutAmount(_getBalance(account, data), account); |
|
| 68 |
✓ 368
|
spToken = data.extractYieldSource(); |
|
| 69 |
} |
||
| 70 | |||
| 71 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 72 |
✓ 368
|
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
|
| 73 |
} |
||
| 74 |
/*////////////////////////////////////////////////////////////// |
||
| 75 |
PRIVATE METHODS |
||
| 76 |
//////////////////////////////////////////////////////////////*/ |
||
| 77 | |||
| 78 |
✓ 922
|
function _getBalance(address account, bytes memory data) private view returns (uint256) {
|
|
| 79 |
✓ 922
|
return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account); |
|
| 80 |
} |
||
| 81 |
} |
||
| 82 |
2.8%
lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol
Lines covered: 1 / 36 (2.8%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { BytesLib } from "../../../vendor/BytesLib.sol";
|
||
| 6 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 7 |
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
||
| 8 |
import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
|
||
| 9 | |||
| 10 |
// Superform |
||
| 11 |
import { BaseHook } from "../../BaseHook.sol";
|
||
| 12 |
import {
|
||
| 13 |
ISuperHookResultOutflow, |
||
| 14 |
ISuperHookInflowOutflow, |
||
| 15 |
ISuperHookOutflow, |
||
| 16 |
ISuperHookContextAware, |
||
| 17 |
ISuperHookInspector |
||
| 18 |
} from "../../../interfaces/ISuperHook.sol"; |
||
| 19 |
import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
|
||
| 20 |
import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
|
||
| 21 | |||
| 22 |
/// @title Withdraw7540VaultHook |
||
| 23 |
/// @author Superform Labs |
||
| 24 |
/// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible |
||
| 25 |
/// @dev data has the following structure |
||
| 26 |
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0); |
||
| 27 |
/// @notice address yieldSource = BytesLib.toAddress(data, 32); |
||
| 28 |
/// @notice uint256 amount = BytesLib.toUint256(data, 52); |
||
| 29 |
/// @notice bool usePrevHookAmount = _decodeBool(data, 84); |
||
| 30 |
contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
|
||
| 31 |
using HookDataDecoder for bytes; |
||
| 32 | |||
| 33 |
uint256 private constant AMOUNT_POSITION = 52; |
||
| 34 |
uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84; |
||
| 35 | |||
| 36 |
✓ 2
|
constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
|
|
| 37 | |||
| 38 |
/*////////////////////////////////////////////////////////////// |
||
| 39 |
VIEW METHODS |
||
| 40 |
//////////////////////////////////////////////////////////////*/ |
||
| 41 |
/// @inheritdoc BaseHook |
||
| 42 |
function _buildHookExecutions( |
||
| 43 |
address prevHook, |
||
| 44 |
address account, |
||
| 45 |
bytes calldata data |
||
| 46 |
) |
||
| 47 |
internal |
||
| 48 |
view |
||
| 49 |
override |
||
| 50 |
returns (Execution[] memory executions) |
||
| 51 |
{
|
||
| 52 |
address yieldSource = data.extractYieldSource(); |
||
| 53 |
uint256 amount = _decodeAmount(data); |
||
| 54 |
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
||
| 55 | |||
| 56 |
if (usePrevHookAmount) {
|
||
| 57 |
amount = ISuperHookResultOutflow(prevHook).getOutAmount(account); |
||
| 58 |
} |
||
| 59 | |||
| 60 |
if (amount == 0) revert AMOUNT_NOT_VALID(); |
||
| 61 |
if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID(); |
||
| 62 | |||
| 63 |
executions = new Execution[](1); |
||
| 64 |
executions[0] = Execution({
|
||
| 65 |
target: yieldSource, |
||
| 66 |
value: 0, |
||
| 67 |
callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account)) |
||
| 68 |
}); |
||
| 69 |
} |
||
| 70 | |||
| 71 |
/*////////////////////////////////////////////////////////////// |
||
| 72 |
EXTERNAL METHODS |
||
| 73 |
//////////////////////////////////////////////////////////////*/ |
||
| 74 | |||
| 75 |
/// @inheritdoc ISuperHookInflowOutflow |
||
| 76 |
function decodeAmount(bytes memory data) external pure returns (uint256) {
|
||
| 77 |
return _decodeAmount(data); |
||
| 78 |
} |
||
| 79 | |||
| 80 |
/// @inheritdoc ISuperHookContextAware |
||
| 81 |
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
|
||
| 82 |
return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION); |
||
| 83 |
} |
||
| 84 | |||
| 85 |
/// @inheritdoc ISuperHookOutflow |
||
| 86 |
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
|
||
| 87 |
return _replaceCalldataAmount(data, amount, AMOUNT_POSITION); |
||
| 88 |
} |
||
| 89 | |||
| 90 |
/// @inheritdoc ISuperHookInspector |
||
| 91 |
function inspect(bytes calldata data) external pure override returns (bytes memory) {
|
||
| 92 |
return abi.encodePacked(data.extractYieldSource()); |
||
| 93 |
} |
||
| 94 | |||
| 95 |
/*////////////////////////////////////////////////////////////// |
||
| 96 |
INTERNAL METHODS |
||
| 97 |
//////////////////////////////////////////////////////////////*/ |
||
| 98 |
function _preExecute(address, address account, bytes calldata data) internal override {
|
||
| 99 |
address yieldSource = data.extractYieldSource(); |
||
| 100 |
asset = IERC7540(yieldSource).asset(); |
||
| 101 |
_setOutAmount(_getBalance(account, data), account); |
||
| 102 |
usedShares = _getSharesBalance(account, data); |
||
| 103 |
spToken = IERC7540(yieldSource).share(); |
||
| 104 |
} |
||
| 105 | |||
| 106 |
function _postExecute(address, address account, bytes calldata data) internal override {
|
||
| 107 |
_setOutAmount(_getBalance(account, data) - getOutAmount(account), account); |
||
| 108 |
usedShares = usedShares - _getSharesBalance(account, data); |
||
| 109 |
} |
||
| 110 | |||
| 111 |
/*////////////////////////////////////////////////////////////// |
||
| 112 |
PRIVATE METHODS |
||
| 113 |
//////////////////////////////////////////////////////////////*/ |
||
| 114 |
function _decodeAmount(bytes memory data) private pure returns (uint256) {
|
||
| 115 |
return BytesLib.toUint256(data, AMOUNT_POSITION); |
||
| 116 |
} |
||
| 117 | |||
| 118 |
function _getBalance(address account, bytes memory) private view returns (uint256) {
|
||
| 119 |
return IERC20(asset).balanceOf(account); |
||
| 120 |
} |
||
| 121 | |||
| 122 |
function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
|
||
| 123 |
address yieldSource = data.extractYieldSource(); |
||
| 124 |
return IERC7540(yieldSource).claimableRedeemRequest(0, account); |
||
| 125 |
} |
||
| 126 |
} |
||
| 127 |
0.0%
lib/v2-core/src/interfaces/ISuperHook.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
|
||
| 6 | |||
| 7 |
/** |
||
| 8 |
* @title SuperHook System |
||
| 9 |
* @author Superform Labs |
||
| 10 |
* @notice The hook system provides a modular and composable way to execute operations on assets |
||
| 11 |
* @dev The hook system architecture consists of several interfaces that work together: |
||
| 12 |
* - ISuperHook: The base interface all hooks implement, with lifecycle methods |
||
| 13 |
* - ISuperHookResult: Provides execution results and output information |
||
| 14 |
* - Specialized interfaces (ISuperHookOutflow, ISuperHookLoans, etc.) for specific behaviors |
||
| 15 |
* |
||
| 16 |
* Hooks are executed in sequence, where each hook can access the results from previous hooks. |
||
| 17 |
* The three main types of hooks are: |
||
| 18 |
* - NONACCOUNTING: Utility hooks that don't update the accounting system |
||
| 19 |
* - INFLOW: Hooks that process deposits or additions to positions |
||
| 20 |
* - OUTFLOW: Hooks that process withdrawals or reductions to positions |
||
| 21 |
*/ |
||
| 22 |
interface ISuperLockableHook {
|
||
| 23 |
/// @notice The vault bank address used to lock SuperPositions |
||
| 24 |
/// @dev Only relevant for cross-chain operations where positions are locked |
||
| 25 |
/// @return The vault bank address, or address(0) if not applicable |
||
| 26 |
function vaultBank() external view returns (address); |
||
| 27 | |||
| 28 |
/// @notice The destination chain ID for cross-chain operations |
||
| 29 |
/// @dev Used to identify the target chain for cross-chain position transfers |
||
| 30 |
/// @return The destination chain ID, or 0 if not a cross-chain operation |
||
| 31 |
function dstChainId() external view returns (uint256); |
||
| 32 |
} |
||
| 33 | |||
| 34 |
interface ISuperHookSetter {
|
||
| 35 |
/// @notice Sets the output amount for the hook |
||
| 36 |
/// @dev Used for updating `outAmount` when fees were deducted |
||
| 37 |
/// @param outAmount The amount of tokens processed by the hook |
||
| 38 |
/// @param caller The caller address for context identification |
||
| 39 |
function setOutAmount(uint256 outAmount, address caller) external; |
||
| 40 |
} |
||
| 41 |
/// @title ISuperHookInspector |
||
| 42 |
/// @author Superform Labs |
||
| 43 |
/// @notice Interface for the SuperHookInspector contract that manages hook inspection |
||
| 44 | |||
| 45 |
interface ISuperHookInspector {
|
||
| 46 |
/// @notice Inspect the hook |
||
| 47 |
/// @param data The hook data to inspect |
||
| 48 |
/// @return argsEncoded The arguments of the hook encoded |
||
| 49 |
function inspect(bytes calldata data) external view returns (bytes memory argsEncoded); |
||
| 50 |
} |
||
| 51 | |||
| 52 |
/// @title ISuperHookResult |
||
| 53 |
/// @author Superform Labs |
||
| 54 |
/// @notice Interface that exposes the result of a hook execution |
||
| 55 |
/// @dev All hooks must implement this interface to provide standardized access to execution results. |
||
| 56 |
/// These results are used by subsequent hooks in the execution chain and by the executor. |
||
| 57 |
interface ISuperHookResult {
|
||
| 58 |
/*////////////////////////////////////////////////////////////// |
||
| 59 |
VIEW METHODS |
||
| 60 |
//////////////////////////////////////////////////////////////*/ |
||
| 61 | |||
| 62 |
/// @notice The type of hook |
||
| 63 |
/// @dev Used to determine how accounting should process this hook's results |
||
| 64 |
/// @return The hook type (NONACCOUNTING, INFLOW, or OUTFLOW) |
||
| 65 |
function hookType() external view returns (ISuperHook.HookType); |
||
| 66 | |||
| 67 |
/// @notice The SuperPosition (SP) token associated with this hook |
||
| 68 |
/// @dev For vault hooks, this would be the tokenized position representing shares |
||
| 69 |
/// @return The address of the SP token, or address(0) if not applicable |
||
| 70 |
function spToken() external view returns (address); |
||
| 71 | |||
| 72 |
/// @notice The underlying asset token being processed |
||
| 73 |
/// @dev For most hooks, this is the actual token being deposited or withdrawn |
||
| 74 |
/// @return The address of the asset token, or address(0) for native assets |
||
| 75 |
function asset() external view returns (address); |
||
| 76 | |||
| 77 |
/// @notice The amount of tokens processed by the hook in a given caller context, subject to fees after update |
||
| 78 |
/// @dev This is the primary output value used by subsequent hooks |
||
| 79 |
/// @param caller The caller address for context identification |
||
| 80 |
/// @return The amount of tokens (assets or shares) processed |
||
| 81 |
function getOutAmount(address caller) external view returns (uint256); |
||
| 82 |
} |
||
| 83 | |||
| 84 |
/// @title ISuperHookContextAware |
||
| 85 |
/// @author Superform Labs |
||
| 86 |
/// @notice Interface for hooks that can use previous hook results in their execution |
||
| 87 |
/// @dev Enables contextual awareness and data flow between hooks in a chain |
||
| 88 |
interface ISuperHookContextAware {
|
||
| 89 |
/// @notice Determines if this hook should use the amount from the previous hook |
||
| 90 |
/// @dev Used to create hook chains where output from one hook becomes input to the next |
||
| 91 |
/// @param data The hook-specific data containing configuration |
||
| 92 |
/// @return True if the hook should use the previous hook's output amount |
||
| 93 |
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool); |
||
| 94 |
} |
||
| 95 | |||
| 96 |
/// @title ISuperHookInflowOutflow |
||
| 97 |
/// @author Superform Labs |
||
| 98 |
/// @notice Interface for hooks that handle both inflows and outflows |
||
| 99 |
/// @dev Provides standardized amount extraction for both deposit and withdrawal operations |
||
| 100 |
interface ISuperHookInflowOutflow {
|
||
| 101 |
/// @notice Extracts the amount from the hook's calldata |
||
| 102 |
/// @dev Used to determine the quantity of assets or shares being processed |
||
| 103 |
/// @param data The hook-specific calldata containing the amount |
||
| 104 |
/// @return The amount of tokens to process |
||
| 105 |
function decodeAmount(bytes memory data) external pure returns (uint256); |
||
| 106 |
} |
||
| 107 | |||
| 108 |
/// @title ISuperHookOutflow |
||
| 109 |
/// @author Superform Labs |
||
| 110 |
/// @notice Interface for hooks that specifically handle outflows (withdrawals) |
||
| 111 |
/// @dev Provides additional functionality needed only for outflow operations |
||
| 112 |
interface ISuperHookOutflow {
|
||
| 113 |
/// @notice Replace the amount in the calldata |
||
| 114 |
/// @param data The data to replace the amount in |
||
| 115 |
/// @param amount The amount to replace |
||
| 116 |
/// @return data The data with the replaced amount |
||
| 117 |
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory); |
||
| 118 |
} |
||
| 119 | |||
| 120 |
/// @title ISuperHookResultOutflow |
||
| 121 |
/// @author Superform Labs |
||
| 122 |
/// @notice Extended result interface for outflow hook operations |
||
| 123 |
/// @dev Extends the base result interface with outflow-specific information |
||
| 124 |
interface ISuperHookResultOutflow is ISuperHookResult {
|
||
| 125 |
/// @notice The amount of shares consumed during outflow processing |
||
| 126 |
/// @dev Used for cost basis calculation in the accounting system |
||
| 127 |
/// @return The amount of shares consumed from the user's position |
||
| 128 |
function usedShares() external view returns (uint256); |
||
| 129 |
} |
||
| 130 | |||
| 131 |
/// @title ISuperHookLoans |
||
| 132 |
/// @author Superform Labs |
||
| 133 |
/// @notice Interface for hooks that interact with lending protocols |
||
| 134 |
/// @dev Extends context awareness to enable loan operations within hook chains |
||
| 135 |
interface ISuperHookLoans is ISuperHookContextAware {
|
||
| 136 |
/// @notice Gets the address of the token being borrowed |
||
| 137 |
/// @dev Used to identify which asset is being borrowed from the lending protocol |
||
| 138 |
/// @param data The hook-specific data containing loan information |
||
| 139 |
/// @return The address of the borrowed token |
||
| 140 |
function getLoanTokenAddress(bytes memory data) external pure returns (address); |
||
| 141 | |||
| 142 |
/// @notice Gets the address of the token used as collateral |
||
| 143 |
/// @dev Used to identify which asset is being used to secure the loan |
||
| 144 |
/// @param data The hook-specific data containing collateral information |
||
| 145 |
/// @return The address of the collateral token |
||
| 146 |
function getCollateralTokenAddress(bytes memory data) external view returns (address); |
||
| 147 | |||
| 148 |
/// @notice Gets the current loan token balance for an account |
||
| 149 |
/// @dev Used to track outstanding loan amounts |
||
| 150 |
/// @param account The account to check the loan balance for |
||
| 151 |
/// @param data The hook-specific data containing loan parameters |
||
| 152 |
/// @return The amount of tokens currently borrowed |
||
| 153 |
function getLoanTokenBalance(address account, bytes memory data) external view returns (uint256); |
||
| 154 | |||
| 155 |
/// @notice Gets the current collateral token balance for an account |
||
| 156 |
/// @dev Used to track collateral positions |
||
| 157 |
/// @param account The account to check the collateral balance for |
||
| 158 |
/// @param data The hook-specific data containing collateral parameters |
||
| 159 |
/// @return The amount of tokens currently used as collateral |
||
| 160 |
function getCollateralTokenBalance(address account, bytes memory data) external view returns (uint256); |
||
| 161 |
} |
||
| 162 | |||
| 163 |
/// @title ISuperHookAsyncCancelations |
||
| 164 |
/// @author Superform Labs |
||
| 165 |
/// @notice Interface for hooks that can cancel asynchronous operations |
||
| 166 |
/// @dev Used to handle cancellation of pending operations that haven't completed |
||
| 167 |
interface ISuperHookAsyncCancelations {
|
||
| 168 |
/// @notice Types of cancellations that can be performed |
||
| 169 |
/// @dev Distinguishes between different operation types that can be canceled |
||
| 170 |
enum CancelationType {
|
||
| 171 |
NONE, // Not a cancelation hook |
||
| 172 |
INFLOW, // Cancels a pending deposit operation |
||
| 173 |
OUTFLOW // Cancels a pending withdrawal operation |
||
| 174 | |||
| 175 |
} |
||
| 176 | |||
| 177 |
/// @notice Identifies the type of async operation this hook can cancel |
||
| 178 |
/// @dev Used to verify the hook is appropriate for the operation being canceled |
||
| 179 |
/// @return asyncType The type of cancellation this hook performs |
||
| 180 |
function isAsyncCancelHook() external pure returns (CancelationType asyncType); |
||
| 181 |
} |
||
| 182 | |||
| 183 |
/// @title ISuperHook |
||
| 184 |
/// @author Superform Labs |
||
| 185 |
/// @notice The core hook interface that all hooks must implement |
||
| 186 |
/// @dev Defines the lifecycle methods and execution flow for the hook system |
||
| 187 |
/// Hooks are executed in sequence with results passed between them |
||
| 188 |
interface ISuperHook {
|
||
| 189 |
/*////////////////////////////////////////////////////////////// |
||
| 190 | |||
| 191 |
ENUMS |
||
| 192 |
//////////////////////////////////////////////////////////////*/ |
||
| 193 |
/// @notice Defines the possible types of hooks in the system |
||
| 194 |
/// @dev Used to determine how the hook affects accounting and what operations it performs |
||
| 195 |
enum HookType {
|
||
| 196 |
NONACCOUNTING, // Hook doesn't affect accounting (e.g., a swap or bridge) |
||
| 197 |
INFLOW, // Hook processes deposits or positions being added |
||
| 198 |
OUTFLOW // Hook processes withdrawals or positions being removed |
||
| 199 | |||
| 200 |
} |
||
| 201 | |||
| 202 |
/*////////////////////////////////////////////////////////////// |
||
| 203 |
VIEW METHODS |
||
| 204 |
//////////////////////////////////////////////////////////////*/ |
||
| 205 |
/// @notice Builds the execution array for the hook operation |
||
| 206 |
/// @dev This is the core method where hooks define their on-chain interactions |
||
| 207 |
/// The returned executions are a sequence of contract calls to perform |
||
| 208 |
/// No state changes should occur in this method |
||
| 209 |
/// @param prevHook The address of the previous hook in the chain, or address(0) if first |
||
| 210 |
/// @param account The account to perform executions for (usually an ERC7579 account) |
||
| 211 |
/// @param data The hook-specific parameters and configuration data |
||
| 212 |
/// @return executions Array of Execution structs defining calls to make |
||
| 213 |
function build( |
||
| 214 |
address prevHook, |
||
| 215 |
address account, |
||
| 216 |
bytes calldata data |
||
| 217 |
) |
||
| 218 |
external |
||
| 219 |
view |
||
| 220 |
returns (Execution[] memory executions); |
||
| 221 | |||
| 222 |
/*////////////////////////////////////////////////////////////// |
||
| 223 |
PUBLIC METHODS |
||
| 224 |
//////////////////////////////////////////////////////////////*/ |
||
| 225 |
/// @notice Prepares the hook for execution |
||
| 226 |
/// @dev Called before the main execution, used to validate inputs and set execution context |
||
| 227 |
/// This method may perform state changes to set up the hook's execution state |
||
| 228 |
/// @param prevHook The address of the previous hook in the chain, or address(0) if first |
||
| 229 |
/// @param account The account to perform operations for |
||
| 230 |
/// @param data The hook-specific parameters and configuration data |
||
| 231 |
function preExecute(address prevHook, address account, bytes memory data) external; |
||
| 232 | |||
| 233 |
/// @notice Finalizes the hook after execution |
||
| 234 |
/// @dev Called after the main execution, used to update hook state and calculate results |
||
| 235 |
/// Sets output values (outAmount, usedShares, etc.) for subsequent hooks |
||
| 236 |
/// @param prevHook The address of the previous hook in the chain, or address(0) if first |
||
| 237 |
/// @param account The account operations were performed for |
||
| 238 |
/// @param data The hook-specific parameters and configuration data |
||
| 239 |
function postExecute(address prevHook, address account, bytes memory data) external; |
||
| 240 | |||
| 241 |
/// @notice Returns the specific subtype identification for this hook |
||
| 242 |
/// @dev Used to categorize hooks beyond the basic HookType |
||
| 243 |
/// For example, a hook might be of type INFLOW but subtype VAULT_DEPOSIT |
||
| 244 |
/// @return A bytes32 identifier for the specific hook functionality |
||
| 245 |
function subtype() external view returns (bytes32); |
||
| 246 | |||
| 247 |
/// @notice Resets hook mutexes |
||
| 248 |
/// @param caller The caller address for context identification |
||
| 249 |
function resetExecutionState(address caller) external; |
||
| 250 | |||
| 251 |
/// @notice Sets the caller address that initiated the execution |
||
| 252 |
/// @dev Used for security validation between preExecute and postExecute calls |
||
| 253 |
/// @param caller The caller address for context identification |
||
| 254 |
function setExecutionContext(address caller) external; |
||
| 255 | |||
| 256 |
/// @notice Returns the execution nonce for the current execution context |
||
| 257 |
/// @dev Used to ensure unique execution contexts and prevent replay attacks |
||
| 258 |
/// @return The execution nonce |
||
| 259 |
function executionNonce() external view returns (uint256); |
||
| 260 | |||
| 261 |
/// @notice Returns the last caller registered by `setExecutionContext` |
||
| 262 |
/// @return The last caller address |
||
| 263 |
function lastCaller() external view returns (address); |
||
| 264 |
} |
||
| 265 |
0.0%
lib/v2-core/src/interfaces/accounting/IYieldSourceOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// Superform |
||
| 5 |
import { IOracle } from "../../vendor/awesome-oracles/IOracle.sol";
|
||
| 6 | |||
| 7 |
/// @title IYieldSourceOracle |
||
| 8 |
/// @author Superform Labs |
||
| 9 |
/// @notice Interface for oracles that provide price and TVL data for yield-bearing assets |
||
| 10 |
interface IYieldSourceOracle {
|
||
| 11 |
/*////////////////////////////////////////////////////////////// |
||
| 12 |
ERRORS |
||
| 13 |
//////////////////////////////////////////////////////////////*/ |
||
| 14 | |||
| 15 |
/// @notice Error when array lengths do not match in batch operations |
||
| 16 |
/// @dev Thrown when the lengths of input arrays in multi-asset operations don't match |
||
| 17 |
error ARRAY_LENGTH_MISMATCH(); |
||
| 18 | |||
| 19 |
/// @notice Error when base asset is not valid for the yield source |
||
| 20 |
/// @dev Thrown when attempting to use an asset that isn't supported by the yield source |
||
| 21 |
error INVALID_BASE_ASSET(); |
||
| 22 | |||
| 23 |
/*////////////////////////////////////////////////////////////// |
||
| 24 |
STRUCTS |
||
| 25 |
//////////////////////////////////////////////////////////////*/ |
||
| 26 | |||
| 27 |
/// @notice Struct to hold local variables for getTVLMultipleUSD |
||
| 28 |
/// @dev Used to manage complex computation state without stack-too-deep errors |
||
| 29 |
/// These variables support the calculation of USD-denominated TVL values |
||
| 30 |
/// across multiple yield sources and owners |
||
| 31 |
struct TVLMultipleUSDVars {
|
||
| 32 |
/// @notice Number of yield sources being processed |
||
| 33 |
uint256 length; |
||
| 34 |
/// @notice Number of share owners being processed |
||
| 35 |
uint256 ownersLength; |
||
| 36 |
/// @notice Base amount in the underlying asset's native units |
||
| 37 |
uint256 baseAmount; |
||
| 38 |
/// @notice Accumulated TVL in USD for a specific user |
||
| 39 |
uint256 userTvlUSD; |
||
| 40 |
/// @notice Accumulated total TVL in USD across all sources |
||
| 41 |
uint256 totalTvlUSD; |
||
| 42 |
/// @notice Current yield source being processed |
||
| 43 |
address yieldSource; |
||
| 44 |
/// @notice Array of addresses that own shares in the yield source |
||
| 45 |
address[] owners; |
||
| 46 |
/// @notice Oracle registry used for price conversions |
||
| 47 |
IOracle registry; |
||
| 48 |
} |
||
| 49 | |||
| 50 |
/*////////////////////////////////////////////////////////////// |
||
| 51 |
VIEW METHODS |
||
| 52 |
//////////////////////////////////////////////////////////////*/ |
||
| 53 | |||
| 54 |
/// @notice Returns the number of decimals of the yield source shares |
||
| 55 |
/// @dev Critical for accurately interpreting share amounts and calculating prices |
||
| 56 |
/// Different yield sources may have different decimal precision |
||
| 57 |
/// @param yieldSourceAddress The address of the yield-bearing token contract |
||
| 58 |
/// @return decimals The number of decimals used by the yield source's share token |
||
| 59 |
function decimals(address yieldSourceAddress) external view returns (uint8); |
||
| 60 | |||
| 61 |
/// @notice Calculates the number of shares that would be received for a given amount of assets |
||
| 62 |
/// @dev Used for deposit simulations and to calculate current exchange rates |
||
| 63 |
/// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI) |
||
| 64 |
/// @param assetIn The underlying asset being deposited (e.g., USDC, DAI) |
||
| 65 |
/// @param assetsIn The amount of underlying assets to deposit, in the asset's native units |
||
| 66 |
/// @return shares The number of yield-bearing shares that would be received |
||
| 67 |
function getShareOutput( |
||
| 68 |
address yieldSourceAddress, |
||
| 69 |
address assetIn, |
||
| 70 |
uint256 assetsIn |
||
| 71 |
) |
||
| 72 |
external |
||
| 73 |
view |
||
| 74 |
returns (uint256); |
||
| 75 | |||
| 76 |
/// @notice Calculates the number of underlying assets that would be received for a given amount of shares |
||
| 77 |
/// @dev Used for withdrawal simulations and to calculate current yield |
||
| 78 |
/// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI) |
||
| 79 |
/// @param assetIn The underlying asset to receive (e.g., USDC, DAI) |
||
| 80 |
/// @param sharesIn The amount of yield-bearing shares to redeem |
||
| 81 |
/// @return assets The number of underlying assets that would be received |
||
| 82 |
function getAssetOutput( |
||
| 83 |
address yieldSourceAddress, |
||
| 84 |
address assetIn, |
||
| 85 |
uint256 sharesIn |
||
| 86 |
) |
||
| 87 |
external |
||
| 88 |
view |
||
| 89 |
returns (uint256); |
||
| 90 | |||
| 91 |
/// @notice Retrieves the current price per share in terms of the underlying asset |
||
| 92 |
/// @dev Core function for calculating yields and determining returns |
||
| 93 |
/// @param yieldSourceAddress The yield-bearing token address to get the price for |
||
| 94 |
/// @return pricePerShare The current price per share in underlying asset terms, scaled by decimals |
||
| 95 |
function getPricePerShare(address yieldSourceAddress) external view returns (uint256); |
||
| 96 | |||
| 97 |
/// @notice Calculates the total value locked in a yield source by a specific owner |
||
| 98 |
/// @dev Used to track individual position sizes within the system |
||
| 99 |
/// @param yieldSourceAddress The yield-bearing token address to check |
||
| 100 |
/// @param ownerOfShares The address owning the yield-bearing tokens |
||
| 101 |
/// @return tvl The total value locked by the owner, in underlying asset terms |
||
| 102 |
function getTVLByOwnerOfShares(address yieldSourceAddress, address ownerOfShares) external view returns (uint256); |
||
| 103 | |||
| 104 |
/// @notice Gets the share balance of a specific owner in a yield source |
||
| 105 |
/// @dev Returns raw share balance without converting to underlying assets |
||
| 106 |
/// Used to track participation in the system and for accounting |
||
| 107 |
/// @param yieldSourceAddress The yield-bearing token address |
||
| 108 |
/// @param ownerOfShares The address to check the balance for |
||
| 109 |
/// @return balance The number of yield-bearing tokens owned by the address |
||
| 110 |
function getBalanceOfOwner(address yieldSourceAddress, address ownerOfShares) external view returns (uint256); |
||
| 111 | |||
| 112 |
/// @notice Calculates the total value locked across all users in a yield source |
||
| 113 |
/// @dev Critical for monitoring the size of each yield source in the system |
||
| 114 |
/// @param yieldSourceAddress The yield-bearing token address to check |
||
| 115 |
/// @return tvl The total value locked in the yield source, in underlying asset terms |
||
| 116 |
function getTVL(address yieldSourceAddress) external view returns (uint256); |
||
| 117 | |||
| 118 |
/// @notice Batch version of getPricePerShare for multiple yield sources |
||
| 119 |
/// @dev Efficiently retrieves current prices for multiple yield sources |
||
| 120 |
/// @param yieldSourceAddresses Array of yield-bearing token addresses |
||
| 121 |
/// @return pricesPerShare Array of current prices for each yield source |
||
| 122 |
function getPricePerShareMultiple(address[] memory yieldSourceAddresses) |
||
| 123 |
external |
||
| 124 |
view |
||
| 125 |
returns (uint256[] memory pricesPerShare); |
||
| 126 | |||
| 127 |
/// @notice Batch version of getTVLByOwnerOfShares for multiple yield sources and owners |
||
| 128 |
/// @dev Efficiently calculates TVL for multiple owners across multiple yield sources |
||
| 129 |
/// @param yieldSourceAddresses Array of yield-bearing token addresses |
||
| 130 |
/// @param ownersOfShares 2D array where each sub-array contains owner addresses for a yield source |
||
| 131 |
/// @return userTvls 2D array of TVL values for each owner in each yield source |
||
| 132 |
function getTVLByOwnerOfSharesMultiple( |
||
| 133 |
address[] memory yieldSourceAddresses, |
||
| 134 |
address[][] memory ownersOfShares |
||
| 135 |
) |
||
| 136 |
external |
||
| 137 |
view |
||
| 138 |
returns (uint256[][] memory userTvls); |
||
| 139 | |||
| 140 |
/// @notice Batch version of getTVL for multiple yield sources |
||
| 141 |
/// @dev Efficiently calculates total TVL across multiple yield sources |
||
| 142 |
/// @param yieldSourceAddresses Array of yield-bearing token addresses |
||
| 143 |
/// @return tvls Array containing the total TVL for each yield source |
||
| 144 |
function getTVLMultiple(address[] memory yieldSourceAddresses) external view returns (uint256[] memory tvls); |
||
| 145 | |||
| 146 |
/// @notice Calculates the asset output with fees added for an outflow operation |
||
| 147 |
/// @dev Gets the asset output from the oracle and adds any applicable fees |
||
| 148 |
/// Uses try/catch to handle cases where oracle configuration doesn't exist |
||
| 149 |
/// @param yieldSourceOracleId Identifier for the yield source oracle configuration |
||
| 150 |
/// @param yieldSourceAddress Address of the yield-bearing asset |
||
| 151 |
/// @param assetOut Address of the output asset |
||
| 152 |
/// @param user Address of the user performing the outflow |
||
| 153 |
/// @param usedShares Amount of shares being withdrawn |
||
| 154 |
/// @return Total asset amount including fees |
||
| 155 |
function getAssetOutputWithFees( |
||
| 156 |
bytes32 yieldSourceOracleId, |
||
| 157 |
address yieldSourceAddress, |
||
| 158 |
address assetOut, |
||
| 159 |
address user, |
||
| 160 |
uint256 usedShares |
||
| 161 |
) |
||
| 162 |
external |
||
| 163 |
view |
||
| 164 |
returns (uint256); |
||
| 165 |
} |
||
| 166 |
100.0%
lib/v2-core/src/libraries/HookDataDecoder.sol
Lines covered: 3 / 3 (100.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { BytesLib } from "../vendor/BytesLib.sol";
|
||
| 5 | |||
| 6 |
/// @title HookDataDecoder |
||
| 7 |
/// @author Superform Labs |
||
| 8 |
/// @notice Library for decoding hook data |
||
| 9 |
✓ 16.6K
|
⟲ 16.6K
|
library HookDataDecoder {
|
| 10 |
function extractYieldSourceOracleId(bytes memory data) internal pure returns (bytes32) {
|
||
| 11 |
return bytes32(BytesLib.slice(data, 0, 32)); |
||
| 12 |
} |
||
| 13 | |||
| 14 |
✓ 877.4K
|
function extractYieldSource(bytes memory data) internal pure returns (address) {
|
|
| 15 |
✓ 877.4K
|
return BytesLib.toAddress(data, 32); |
|
| 16 |
} |
||
| 17 |
} |
||
| 18 |
100.0%
lib/v2-core/src/libraries/HookSubTypes.sol
Lines covered: 23 / 23 (100.0%)
| 1 |
// SPDX-License-Identifier: Apache-2.0 |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title HookSubTypes |
||
| 5 |
/// @author Superform Labs |
||
| 6 |
/// @notice Library for hook subtypes |
||
| 7 |
✓ 1
|
library HookSubTypes {
|
|
| 8 |
✓ 853
|
bytes32 public constant BRIDGE = keccak256(bytes("Bridge"));
|
|
| 9 |
✓ 465
|
bytes32 public constant CANCEL_DEPOSIT = keccak256(bytes("CancelDeposit"));
|
|
| 10 |
✓ 438
|
bytes32 public constant CANCEL_DEPOSIT_REQUEST = keccak256(bytes("CancelDepositRequest"));
|
|
| 11 |
✓ 539
|
bytes32 public constant CANCEL_REDEEM = keccak256(bytes("CancelRedeem"));
|
|
| 12 |
✓ 459
|
bytes32 public constant CANCEL_REDEEM_REQUEST = keccak256(bytes("CancelRedeemRequest"));
|
|
| 13 |
✓ 1.1K
|
bytes32 public constant CLAIM = keccak256(bytes("Claim"));
|
|
| 14 |
✓ 506
|
bytes32 public constant CLAIM_CANCEL_DEPOSIT_REQUEST = keccak256(bytes("ClaimCancelDepositRequest"));
|
|
| 15 |
✓ 477
|
bytes32 public constant CLAIM_CANCEL_REDEEM_REQUEST = keccak256(bytes("ClaimCancelRedeemRequest"));
|
|
| 16 |
✓ 473
|
bytes32 public constant COOLDOWN = keccak256(bytes("Cooldown"));
|
|
| 17 |
✓ 562
|
bytes32 public constant ETHENA = keccak256(bytes("Ethena"));
|
|
| 18 |
✓ 2.9K
|
bytes32 public constant ERC4626 = keccak256(bytes("ERC4626"));
|
|
| 19 |
✓ 658
|
bytes32 public constant ERC5115 = keccak256(bytes("ERC5115"));
|
|
| 20 |
✓ 2.5K
|
bytes32 public constant ERC7540 = keccak256(bytes("ERC7540"));
|
|
| 21 |
✓ 466
|
bytes32 public constant LOAN = keccak256(bytes("Loan"));
|
|
| 22 |
✓ 475
|
bytes32 public constant LOAN_REPAY = keccak256(bytes("LoanRepay"));
|
|
| 23 |
✓ 425
|
bytes32 public constant MISC = keccak256(bytes("Misc"));
|
|
| 24 |
✓ 432
|
bytes32 public constant STAKE = keccak256(bytes("Stake"));
|
|
| 25 |
✓ 669
|
bytes32 public constant SWAP = keccak256(bytes("Swap"));
|
|
| 26 |
✓ 484
|
bytes32 public constant TOKEN = keccak256(bytes("Token"));
|
|
| 27 |
✓ 451
|
bytes32 public constant UNSTAKE = keccak256(bytes("Unstake"));
|
|
| 28 |
✓ 447
|
bytes32 public constant PTYT = keccak256(bytes("PTYT"));
|
|
| 29 |
✓ 481
|
bytes32 public constant VAULT_BANK = keccak256(bytes("VaultBank"));
|
|
| 30 | |||
| 31 |
function getHookSubType(string memory hookSubtype) internal pure returns (bytes32) {
|
||
| 32 |
return keccak256(bytes(hookSubtype)); |
||
| 33 |
} |
||
| 34 |
} |
||
| 35 |
100.0%
lib/v2-core/src/vendor/BytesLib.sol
Lines covered: 7 / 7 (100.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 |
✓ 27.0K
|
⟲ 27.0K
|
library BytesLib {
|
| 12 |
function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {
|
||
| 13 |
bytes memory tempBytes; |
||
| 14 | |||
| 15 |
assembly {
|
||
| 16 |
// Get a location of some free memory and store it in tempBytes as |
||
| 17 |
// Solidity does for memory variables. |
||
| 18 |
tempBytes := mload(0x40) |
||
| 19 | |||
| 20 |
// Store the length of the first bytes array at the beginning of |
||
| 21 |
// the memory for tempBytes. |
||
| 22 |
let length := mload(_preBytes) |
||
| 23 |
mstore(tempBytes, length) |
||
| 24 | |||
| 25 |
// Maintain a memory counter for the current write location in the |
||
| 26 |
// temp bytes array by adding the 32 bytes for the array length to |
||
| 27 |
// the starting location. |
||
| 28 |
let mc := add(tempBytes, 0x20) |
||
| 29 |
// Stop copying when the memory counter reaches the length of the |
||
| 30 |
// first bytes array. |
||
| 31 |
let end := add(mc, length) |
||
| 32 | |||
| 33 |
for {
|
||
| 34 |
// Initialize a copy counter to the start of the _preBytes data, |
||
| 35 |
// 32 bytes into its memory. |
||
| 36 |
let cc := add(_preBytes, 0x20) |
||
| 37 |
} lt(mc, end) {
|
||
| 38 |
// Increase both counters by 32 bytes each iteration. |
||
| 39 |
mc := add(mc, 0x20) |
||
| 40 |
cc := add(cc, 0x20) |
||
| 41 |
} {
|
||
| 42 |
// Write the _preBytes data into the tempBytes memory 32 bytes |
||
| 43 |
// at a time. |
||
| 44 |
mstore(mc, mload(cc)) |
||
| 45 |
} |
||
| 46 | |||
| 47 |
// Add the length of _postBytes to the current length of tempBytes |
||
| 48 |
// and store it as the new length in the first 32 bytes of the |
||
| 49 |
// tempBytes memory. |
||
| 50 |
length := mload(_postBytes) |
||
| 51 |
mstore(tempBytes, add(length, mload(tempBytes))) |
||
| 52 | |||
| 53 |
// Move the memory counter back from a multiple of 0x20 to the |
||
| 54 |
// actual end of the _preBytes data. |
||
| 55 |
mc := end |
||
| 56 |
// Stop copying when the memory counter reaches the new combined |
||
| 57 |
// length of the arrays. |
||
| 58 |
end := add(mc, length) |
||
| 59 | |||
| 60 |
for { let cc := add(_postBytes, 0x20) } lt(mc, end) {
|
||
| 61 |
mc := add(mc, 0x20) |
||
| 62 |
cc := add(cc, 0x20) |
||
| 63 |
} { mstore(mc, mload(cc)) }
|
||
| 64 | |||
| 65 |
// Update the free-memory pointer by padding our last write location |
||
| 66 |
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the |
||
| 67 |
// next 32 byte block, then round down to the nearest multiple of |
||
| 68 |
// 32. If the sum of the length of the two arrays is zero then add |
||
| 69 |
// one before rounding down to leave a blank 32 bytes (the length block with 0). |
||
| 70 |
mstore( |
||
| 71 |
0x40, |
||
| 72 |
and( |
||
| 73 |
add(add(end, iszero(add(length, mload(_preBytes)))), 31), |
||
| 74 |
not(31) // Round down to the nearest 32 bytes. |
||
| 75 |
) |
||
| 76 |
) |
||
| 77 |
} |
||
| 78 | |||
| 79 |
return tempBytes; |
||
| 80 |
} |
||
| 81 | |||
| 82 |
function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
|
||
| 83 |
assembly {
|
||
| 84 |
// Read the first 32 bytes of _preBytes storage, which is the length |
||
| 85 |
// of the array. (We don't need to use the offset into the slot |
||
| 86 |
// because arrays use the entire slot.) |
||
| 87 |
let fslot := sload(_preBytes.slot) |
||
| 88 |
// Arrays of 31 bytes or less have an even value in their slot, |
||
| 89 |
// while longer arrays have an odd value. The actual length is |
||
| 90 |
// the slot divided by two for odd values, and the lowest order |
||
| 91 |
// byte divided by two for even values. |
||
| 92 |
// If the slot is even, bitwise and the slot with 255 and divide by |
||
| 93 |
// two to get the length. If the slot is odd, bitwise and the slot |
||
| 94 |
// with -1 and divide by two. |
||
| 95 |
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
||
| 96 |
let mlength := mload(_postBytes) |
||
| 97 |
let newlength := add(slength, mlength) |
||
| 98 |
// slength can contain both the length and contents of the array |
||
| 99 |
// if length < 32 bytes so let's prepare for that |
||
| 100 |
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
||
| 101 |
switch add(lt(slength, 32), lt(newlength, 32)) |
||
| 102 |
case 2 {
|
||
| 103 |
// Since the new array still fits in the slot, we just need to |
||
| 104 |
// update the contents of the slot. |
||
| 105 |
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length |
||
| 106 |
sstore( |
||
| 107 |
_preBytes.slot, |
||
| 108 |
// all the modifications to the slot are inside this |
||
| 109 |
// next block |
||
| 110 |
add( |
||
| 111 |
// we can just add to the slot contents because the |
||
| 112 |
// bytes we want to change are the LSBs |
||
| 113 |
fslot, |
||
| 114 |
add( |
||
| 115 |
mul( |
||
| 116 |
div( |
||
| 117 |
// load the bytes from memory |
||
| 118 |
mload(add(_postBytes, 0x20)), |
||
| 119 |
// zero all bytes to the right |
||
| 120 |
exp(0x100, sub(32, mlength)) |
||
| 121 |
), |
||
| 122 |
// and now shift left the number of bytes to |
||
| 123 |
// leave space for the length in the slot |
||
| 124 |
exp(0x100, sub(32, newlength)) |
||
| 125 |
), |
||
| 126 |
// increase length by the double of the memory |
||
| 127 |
// bytes length |
||
| 128 |
mul(mlength, 2) |
||
| 129 |
) |
||
| 130 |
) |
||
| 131 |
) |
||
| 132 |
} |
||
| 133 |
case 1 {
|
||
| 134 |
// The stored value fits in the slot, but the combined value |
||
| 135 |
// will exceed it. |
||
| 136 |
// get the keccak hash to get the contents of the array |
||
| 137 |
mstore(0x0, _preBytes.slot) |
||
| 138 |
let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
||
| 139 | |||
| 140 |
// save new length |
||
| 141 |
sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
||
| 142 | |||
| 143 |
// The contents of the _postBytes array start 32 bytes into |
||
| 144 |
// the structure. Our first read should obtain the `submod` |
||
| 145 |
// bytes that can fit into the unused space in the last word |
||
| 146 |
// of the stored array. To get this, we read 32 bytes starting |
||
| 147 |
// from `submod`, so the data we read overlaps with the array |
||
| 148 |
// contents by `submod` bytes. Masking the lowest-order |
||
| 149 |
// `submod` bytes allows us to add that value directly to the |
||
| 150 |
// stored value. |
||
| 151 | |||
| 152 |
let submod := sub(32, slength) |
||
| 153 |
let mc := add(_postBytes, submod) |
||
| 154 |
let end := add(_postBytes, mlength) |
||
| 155 |
let mask := sub(exp(0x100, submod), 1) |
||
| 156 | |||
| 157 |
sstore( |
||
| 158 |
sc, |
||
| 159 |
add( |
||
| 160 |
and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), |
||
| 161 |
and(mload(mc), mask) |
||
| 162 |
) |
||
| 163 |
) |
||
| 164 | |||
| 165 |
for {
|
||
| 166 |
mc := add(mc, 0x20) |
||
| 167 |
sc := add(sc, 1) |
||
| 168 |
} lt(mc, end) {
|
||
| 169 |
sc := add(sc, 1) |
||
| 170 |
mc := add(mc, 0x20) |
||
| 171 |
} { sstore(sc, mload(mc)) }
|
||
| 172 | |||
| 173 |
mask := exp(0x100, sub(mc, end)) |
||
| 174 | |||
| 175 |
sstore(sc, mul(div(mload(mc), mask), mask)) |
||
| 176 |
} |
||
| 177 |
default {
|
||
| 178 |
// get the keccak hash to get the contents of the array |
||
| 179 |
mstore(0x0, _preBytes.slot) |
||
| 180 |
// Start copying to the last used word of the stored array. |
||
| 181 |
let sc := add(keccak256(0x0, 0x20), div(slength, 32)) |
||
| 182 | |||
| 183 |
// save new length |
||
| 184 |
sstore(_preBytes.slot, add(mul(newlength, 2), 1)) |
||
| 185 | |||
| 186 |
// Copy over the first `submod` bytes of the new data as in |
||
| 187 |
// case 1 above. |
||
| 188 |
let slengthmod := mod(slength, 32) |
||
| 189 |
let mlengthmod := mod(mlength, 32) |
||
| 190 |
let submod := sub(32, slengthmod) |
||
| 191 |
let mc := add(_postBytes, submod) |
||
| 192 |
let end := add(_postBytes, mlength) |
||
| 193 |
let mask := sub(exp(0x100, submod), 1) |
||
| 194 | |||
| 195 |
sstore(sc, add(sload(sc), and(mload(mc), mask))) |
||
| 196 | |||
| 197 |
for {
|
||
| 198 |
sc := add(sc, 1) |
||
| 199 |
mc := add(mc, 0x20) |
||
| 200 |
} lt(mc, end) {
|
||
| 201 |
sc := add(sc, 1) |
||
| 202 |
mc := add(mc, 0x20) |
||
| 203 |
} { sstore(sc, mload(mc)) }
|
||
| 204 | |||
| 205 |
mask := exp(0x100, sub(mc, end)) |
||
| 206 | |||
| 207 |
sstore(sc, mul(div(mload(mc), mask), mask)) |
||
| 208 |
} |
||
| 209 |
} |
||
| 210 |
} |
||
| 211 | |||
| 212 |
function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
|
||
| 213 |
// We're using the unchecked block below because otherwise execution ends |
||
| 214 |
// with the native overflow error code. |
||
| 215 |
unchecked {
|
||
| 216 |
require(_length + 31 >= _length, "slice_overflow"); |
||
| 217 |
} |
||
| 218 |
require(_bytes.length >= _start + _length, "slice_outOfBounds"); |
||
| 219 | |||
| 220 |
bytes memory tempBytes; |
||
| 221 | |||
| 222 |
assembly {
|
||
| 223 |
switch iszero(_length) |
||
| 224 |
case 0 {
|
||
| 225 |
// Get a location of some free memory and store it in tempBytes as |
||
| 226 |
// Solidity does for memory variables. |
||
| 227 |
tempBytes := mload(0x40) |
||
| 228 | |||
| 229 |
// The first word of the slice result is potentially a partial |
||
| 230 |
// word read from the original array. To read it, we calculate |
||
| 231 |
// the length of that partial word and start copying that many |
||
| 232 |
// bytes into the array. The first word we copy will start with |
||
| 233 |
// data we don't care about, but the last `lengthmod` bytes will |
||
| 234 |
// land at the beginning of the contents of the new array. When |
||
| 235 |
// we're done copying, we overwrite the full first word with |
||
| 236 |
// the actual length of the slice. |
||
| 237 |
let lengthmod := and(_length, 31) |
||
| 238 | |||
| 239 |
// The multiplication in the next line is necessary |
||
| 240 |
// because when slicing multiples of 32 bytes (lengthmod == 0) |
||
| 241 |
// the following copy loop was copying the origin's length |
||
| 242 |
// and then ending prematurely not copying everything it should. |
||
| 243 |
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) |
||
| 244 |
let end := add(mc, _length) |
||
| 245 | |||
| 246 |
for {
|
||
| 247 |
// The multiplication in the next line has the same exact purpose |
||
| 248 |
// as the one above. |
||
| 249 |
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) |
||
| 250 |
} lt(mc, end) {
|
||
| 251 |
mc := add(mc, 0x20) |
||
| 252 |
cc := add(cc, 0x20) |
||
| 253 |
} { mstore(mc, mload(cc)) }
|
||
| 254 | |||
| 255 |
mstore(tempBytes, _length) |
||
| 256 | |||
| 257 |
//update free-memory pointer |
||
| 258 |
//allocating the array padded to 32 bytes like the compiler does now |
||
| 259 |
mstore(0x40, and(add(mc, 31), not(31))) |
||
| 260 |
} |
||
| 261 |
//if we want a zero-length slice let's just return a zero-length array |
||
| 262 |
default {
|
||
| 263 |
tempBytes := mload(0x40) |
||
| 264 |
//zero out the 32 bytes slice we are about to return |
||
| 265 |
//we need to do it because Solidity does not garbage collect |
||
| 266 |
mstore(tempBytes, 0) |
||
| 267 | |||
| 268 |
mstore(0x40, add(tempBytes, 0x20)) |
||
| 269 |
} |
||
| 270 |
} |
||
| 271 | |||
| 272 |
return tempBytes; |
||
| 273 |
} |
||
| 274 | |||
| 275 |
✓ 1.6M
|
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
|
|
| 276 |
✓ 1.6M
|
require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); |
|
| 277 |
address tempAddress; |
||
| 278 | |||
| 279 |
assembly {
|
||
| 280 |
✓ 1.6M
|
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) |
|
| 281 |
} |
||
| 282 | |||
| 283 |
return tempAddress; |
||
| 284 |
} |
||
| 285 | |||
| 286 |
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
|
||
| 287 |
require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); |
||
| 288 |
uint8 tempUint; |
||
| 289 | |||
| 290 |
assembly {
|
||
| 291 |
tempUint := mload(add(add(_bytes, 0x1), _start)) |
||
| 292 |
} |
||
| 293 | |||
| 294 |
return tempUint; |
||
| 295 |
} |
||
| 296 | |||
| 297 |
function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
|
||
| 298 |
require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); |
||
| 299 |
uint16 tempUint; |
||
| 300 | |||
| 301 |
assembly {
|
||
| 302 |
tempUint := mload(add(add(_bytes, 0x2), _start)) |
||
| 303 |
} |
||
| 304 | |||
| 305 |
return tempUint; |
||
| 306 |
} |
||
| 307 | |||
| 308 |
function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
|
||
| 309 |
require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); |
||
| 310 |
uint32 tempUint; |
||
| 311 | |||
| 312 |
assembly {
|
||
| 313 |
tempUint := mload(add(add(_bytes, 0x4), _start)) |
||
| 314 |
} |
||
| 315 | |||
| 316 |
return tempUint; |
||
| 317 |
} |
||
| 318 | |||
| 319 |
function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
|
||
| 320 |
require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); |
||
| 321 |
uint64 tempUint; |
||
| 322 | |||
| 323 |
assembly {
|
||
| 324 |
tempUint := mload(add(add(_bytes, 0x8), _start)) |
||
| 325 |
} |
||
| 326 | |||
| 327 |
return tempUint; |
||
| 328 |
} |
||
| 329 | |||
| 330 |
function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
|
||
| 331 |
require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); |
||
| 332 |
uint96 tempUint; |
||
| 333 | |||
| 334 |
assembly {
|
||
| 335 |
tempUint := mload(add(add(_bytes, 0xc), _start)) |
||
| 336 |
} |
||
| 337 | |||
| 338 |
return tempUint; |
||
| 339 |
} |
||
| 340 | |||
| 341 |
function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
|
||
| 342 |
require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); |
||
| 343 |
uint128 tempUint; |
||
| 344 | |||
| 345 |
assembly {
|
||
| 346 |
tempUint := mload(add(add(_bytes, 0x10), _start)) |
||
| 347 |
} |
||
| 348 | |||
| 349 |
return tempUint; |
||
| 350 |
} |
||
| 351 | |||
| 352 |
✓ 682.7K
|
function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
|
|
| 353 |
✓ 682.7K
|
require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); |
|
| 354 |
uint256 tempUint; |
||
| 355 | |||
| 356 |
assembly {
|
||
| 357 |
✓ 682.7K
|
tempUint := mload(add(add(_bytes, 0x20), _start)) |
|
| 358 |
} |
||
| 359 | |||
| 360 |
return tempUint; |
||
| 361 |
} |
||
| 362 | |||
| 363 |
function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
|
||
| 364 |
require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); |
||
| 365 |
bytes32 tempBytes32; |
||
| 366 | |||
| 367 |
assembly {
|
||
| 368 |
tempBytes32 := mload(add(add(_bytes, 0x20), _start)) |
||
| 369 |
} |
||
| 370 | |||
| 371 |
return tempBytes32; |
||
| 372 |
} |
||
| 373 | |||
| 374 |
function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
|
||
| 375 |
bool success = true; |
||
| 376 | |||
| 377 |
assembly {
|
||
| 378 |
let length := mload(_preBytes) |
||
| 379 | |||
| 380 |
// if lengths don't match the arrays are not equal |
||
| 381 |
switch eq(length, mload(_postBytes)) |
||
| 382 |
case 1 {
|
||
| 383 |
// cb is a circuit breaker in the for loop since there's |
||
| 384 |
// no said feature for inline assembly loops |
||
| 385 |
// cb = 1 - don't breaker |
||
| 386 |
// cb = 0 - break |
||
| 387 |
let cb := 1 |
||
| 388 | |||
| 389 |
let mc := add(_preBytes, 0x20) |
||
| 390 |
let end := add(mc, length) |
||
| 391 | |||
| 392 |
for { let cc := add(_postBytes, 0x20) }
|
||
| 393 |
// the next line is the loop condition: |
||
| 394 |
// while(uint256(mc < end) + cb == 2) |
||
| 395 |
eq(add(lt(mc, end), cb), 2) {
|
||
| 396 |
mc := add(mc, 0x20) |
||
| 397 |
cc := add(cc, 0x20) |
||
| 398 |
} {
|
||
| 399 |
// if any of these checks fails then arrays are not equal |
||
| 400 |
if iszero(eq(mload(mc), mload(cc))) {
|
||
| 401 |
// unsuccess: |
||
| 402 |
success := 0 |
||
| 403 |
cb := 0 |
||
| 404 |
} |
||
| 405 |
} |
||
| 406 |
} |
||
| 407 |
default {
|
||
| 408 |
// unsuccess: |
||
| 409 |
success := 0 |
||
| 410 |
} |
||
| 411 |
} |
||
| 412 | |||
| 413 |
return success; |
||
| 414 |
} |
||
| 415 | |||
| 416 |
function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
|
||
| 417 |
bool success = true; |
||
| 418 | |||
| 419 |
assembly {
|
||
| 420 |
// we know _preBytes_offset is 0 |
||
| 421 |
let fslot := sload(_preBytes.slot) |
||
| 422 |
// Decode the length of the stored array like in concatStorage(). |
||
| 423 |
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) |
||
| 424 |
let mlength := mload(_postBytes) |
||
| 425 | |||
| 426 |
// if lengths don't match the arrays are not equal |
||
| 427 |
switch eq(slength, mlength) |
||
| 428 |
case 1 {
|
||
| 429 |
// slength can contain both the length and contents of the array |
||
| 430 |
// if length < 32 bytes so let's prepare for that |
||
| 431 |
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage |
||
| 432 |
if iszero(iszero(slength)) {
|
||
| 433 |
switch lt(slength, 32) |
||
| 434 |
case 1 {
|
||
| 435 |
// blank the last byte which is the length |
||
| 436 |
fslot := mul(div(fslot, 0x100), 0x100) |
||
| 437 | |||
| 438 |
if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
|
||
| 439 |
// unsuccess: |
||
| 440 |
success := 0 |
||
| 441 |
} |
||
| 442 |
} |
||
| 443 |
default {
|
||
| 444 |
// cb is a circuit breaker in the for loop since there's |
||
| 445 |
// no said feature for inline assembly loops |
||
| 446 |
// cb = 1 - don't breaker |
||
| 447 |
// cb = 0 - break |
||
| 448 |
let cb := 1 |
||
| 449 | |||
| 450 |
// get the keccak hash to get the contents of the array |
||
| 451 |
mstore(0x0, _preBytes.slot) |
||
| 452 |
let sc := keccak256(0x0, 0x20) |
||
| 453 | |||
| 454 |
let mc := add(_postBytes, 0x20) |
||
| 455 |
let end := add(mc, mlength) |
||
| 456 | |||
| 457 |
// the next line is the loop condition: |
||
| 458 |
// while(uint256(mc < end) + cb == 2) |
||
| 459 |
for { } eq(add(lt(mc, end), cb), 2) {
|
||
| 460 |
sc := add(sc, 1) |
||
| 461 |
mc := add(mc, 0x20) |
||
| 462 |
} {
|
||
| 463 |
if iszero(eq(sload(sc), mload(mc))) {
|
||
| 464 |
// unsuccess: |
||
| 465 |
success := 0 |
||
| 466 |
cb := 0 |
||
| 467 |
} |
||
| 468 |
} |
||
| 469 |
} |
||
| 470 |
} |
||
| 471 |
} |
||
| 472 |
default {
|
||
| 473 |
// unsuccess: |
||
| 474 |
success := 0 |
||
| 475 |
} |
||
| 476 |
} |
||
| 477 | |||
| 478 |
return success; |
||
| 479 |
} |
||
| 480 |
} |
||
| 481 |
0.0%
lib/v2-core/src/vendor/awesome-oracles/IOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity ^0.8.20; |
||
| 3 | |||
| 4 |
/// @title Common interface for price oracles. |
||
| 5 |
/// @dev Implements the spec at https://eips.ethereum.org/EIPS/eip-7726 |
||
| 6 |
interface IOracle {
|
||
| 7 |
/// @notice The oracle does not support the given base/quote pair. |
||
| 8 |
/// @param base The asset that the user needs to know the value or price for. |
||
| 9 |
/// @param quote The asset in which the user needs to value or price the base. |
||
| 10 |
error OracleUnsupportedPair(address base, address quote); |
||
| 11 | |||
| 12 |
/// @notice The oracle is not capable to provide data within a degree of confidence. |
||
| 13 |
/// @param base The asset that the user needs to know the value or price for. |
||
| 14 |
/// @param quote The asset in which the user needs to value or price the base. |
||
| 15 |
error OracleUntrustedData(address base, address quote); |
||
| 16 | |||
| 17 |
/// @notice Returns the value of `baseAmount` of `base` in `quote` terms. |
||
| 18 |
/// @dev MUST round down towards 0. |
||
| 19 |
/// MUST revert with `OracleUnsupportedPair` if not capable to provide data for the specified `base` and `quote` |
||
| 20 |
/// pair. |
||
| 21 |
/// MUST revert with `OracleUntrustedData` if not capable to provide data within a degree of confidence publicly |
||
| 22 |
/// specified. |
||
| 23 |
/// @param baseAmount The amount of `base` to convert. |
||
| 24 |
/// @param base The asset that the user needs to know the value for. |
||
| 25 |
/// @param quote The asset in which the user needs to value the base. |
||
| 26 |
/// @return quoteAmount The value of `baseAmount` of `base` in `quote` terms |
||
| 27 |
function getQuote(uint256 baseAmount, address base, address quote) external view returns (uint256 quoteAmount); |
||
| 28 |
} |
||
| 29 |
0.0%
lib/v2-core/src/vendor/pendle/IStandardizedYield.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: GPL-3.0-or-later |
||
| 2 |
/* |
||
| 3 |
* MIT License |
||
| 4 |
* =========== |
||
| 5 |
* |
||
| 6 |
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||
| 7 |
* of this software and associated documentation files (the "Software"), to deal |
||
| 8 |
* in the Software without restriction, including without limitation the rights |
||
| 9 |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
| 10 |
* copies of the Software, and to permit persons to whom the Software is |
||
| 11 |
* furnished to do so, subject to the following conditions: |
||
| 12 |
* |
||
| 13 |
* The above copyright notice and this permission notice shall be included in all |
||
| 14 |
* copies or substantial portions of the Software. |
||
| 15 |
* |
||
| 16 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
| 17 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
| 18 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||
| 19 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
| 20 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||
| 21 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||
| 22 |
*/ |
||
| 23 | |||
| 24 |
pragma solidity ^0.8.0; |
||
| 25 | |||
| 26 |
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
||
| 27 | |||
| 28 |
interface IStandardizedYield is IERC20Metadata {
|
||
| 29 |
/// @dev Emitted when any base tokens is deposited to mint shares |
||
| 30 |
event Deposit( |
||
| 31 |
address indexed caller, |
||
| 32 |
address indexed receiver, |
||
| 33 |
address indexed tokenIn, |
||
| 34 |
uint256 amountDeposited, |
||
| 35 |
uint256 amountSyOut |
||
| 36 |
); |
||
| 37 | |||
| 38 |
/// @dev Emitted when any shares are redeemed for base tokens |
||
| 39 |
event Redeem( |
||
| 40 |
address indexed caller, |
||
| 41 |
address indexed receiver, |
||
| 42 |
address indexed tokenOut, |
||
| 43 |
uint256 amountSyToRedeem, |
||
| 44 |
uint256 amountTokenOut |
||
| 45 |
); |
||
| 46 | |||
| 47 |
/// @dev check `assetInfo()` for more information |
||
| 48 |
enum AssetType {
|
||
| 49 |
TOKEN, |
||
| 50 |
LIQUIDITY |
||
| 51 |
} |
||
| 52 | |||
| 53 |
/// @dev Emitted when (`user`) claims their rewards |
||
| 54 |
event ClaimRewards(address indexed user, address[] rewardTokens, uint256[] rewardAmounts); |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @notice mints an amount of shares by depositing a base token. |
||
| 58 |
* @param receiver shares recipient address |
||
| 59 |
* @param tokenIn address of the base tokens to mint shares |
||
| 60 |
* @param amountTokenToDeposit amount of base tokens to be transferred from (`msg.sender`) |
||
| 61 |
* @param minSharesOut reverts if amount of shares minted is lower than this |
||
| 62 |
* @return amountSharesOut amount of shares minted |
||
| 63 |
* @dev Emits a {Deposit} event
|
||
| 64 |
* |
||
| 65 |
* Requirements: |
||
| 66 |
* - (`tokenIn`) must be a valid base token. |
||
| 67 |
*/ |
||
| 68 |
function deposit( |
||
| 69 |
address receiver, |
||
| 70 |
address tokenIn, |
||
| 71 |
uint256 amountTokenToDeposit, |
||
| 72 |
uint256 minSharesOut |
||
| 73 |
) |
||
| 74 |
external |
||
| 75 |
payable |
||
| 76 |
returns (uint256 amountSharesOut); |
||
| 77 | |||
| 78 |
/** |
||
| 79 |
* @notice redeems an amount of base tokens by burning some shares |
||
| 80 |
* @param receiver recipient address |
||
| 81 |
* @param amountSharesToRedeem amount of shares to be burned |
||
| 82 |
* @param tokenOut address of the base token to be redeemed |
||
| 83 |
* @param minTokenOut reverts if amount of base token redeemed is lower than this |
||
| 84 |
* @param burnFromInternalBalance if true, burns from balance of `address(this)`, otherwise burns from `msg.sender` |
||
| 85 |
* @return amountTokenOut amount of base tokens redeemed |
||
| 86 |
* @dev Emits a {Redeem} event
|
||
| 87 |
* |
||
| 88 |
* Requirements: |
||
| 89 |
* - (`tokenOut`) must be a valid base token. |
||
| 90 |
*/ |
||
| 91 |
function redeem( |
||
| 92 |
address receiver, |
||
| 93 |
uint256 amountSharesToRedeem, |
||
| 94 |
address tokenOut, |
||
| 95 |
uint256 minTokenOut, |
||
| 96 |
bool burnFromInternalBalance |
||
| 97 |
) |
||
| 98 |
external |
||
| 99 |
returns (uint256 amountTokenOut); |
||
| 100 | |||
| 101 |
/** |
||
| 102 |
* @notice exchangeRate * syBalance / 1e18 must return the asset balance of the account |
||
| 103 |
* @notice vice-versa, if a user uses some amount of tokens equivalent to X asset, the amount of sy |
||
| 104 |
* he can mint must be X * exchangeRate / 1e18 |
||
| 105 |
* @dev SYUtils's assetToSy & syToAsset should be used instead of raw multiplication |
||
| 106 |
* & division |
||
| 107 |
*/ |
||
| 108 |
function exchangeRate() external view returns (uint256 res); |
||
| 109 | |||
| 110 |
/** |
||
| 111 |
* @notice claims reward for (`user`) |
||
| 112 |
* @param user the user receiving their rewards |
||
| 113 |
* @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens` |
||
| 114 |
* @dev |
||
| 115 |
* Emits a `ClaimRewards` event |
||
| 116 |
* See {getRewardTokens} for list of reward tokens
|
||
| 117 |
*/ |
||
| 118 |
function claimRewards(address user) external returns (uint256[] memory rewardAmounts); |
||
| 119 | |||
| 120 |
/** |
||
| 121 |
* @notice get the amount of unclaimed rewards for (`user`) |
||
| 122 |
* @param user the user to check for |
||
| 123 |
* @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens` |
||
| 124 |
*/ |
||
| 125 |
function accruedRewards(address user) external view returns (uint256[] memory rewardAmounts); |
||
| 126 | |||
| 127 |
function rewardIndexesCurrent() external returns (uint256[] memory indexes); |
||
| 128 | |||
| 129 |
function rewardIndexesStored() external view returns (uint256[] memory indexes); |
||
| 130 | |||
| 131 |
/** |
||
| 132 |
* @notice returns the list of reward token addresses |
||
| 133 |
*/ |
||
| 134 |
function getRewardTokens() external view returns (address[] memory); |
||
| 135 | |||
| 136 |
/** |
||
| 137 |
* @notice returns the address of the underlying yield token |
||
| 138 |
*/ |
||
| 139 |
function yieldToken() external view returns (address); |
||
| 140 | |||
| 141 |
/** |
||
| 142 |
* @notice returns all tokens that can mint this SY |
||
| 143 |
*/ |
||
| 144 |
function getTokensIn() external view returns (address[] memory res); |
||
| 145 | |||
| 146 |
/** |
||
| 147 |
* @notice returns all tokens that can be redeemed by this SY |
||
| 148 |
*/ |
||
| 149 |
function getTokensOut() external view returns (address[] memory res); |
||
| 150 | |||
| 151 |
function isValidTokenIn(address token) external view returns (bool); |
||
| 152 | |||
| 153 |
function isValidTokenOut(address token) external view returns (bool); |
||
| 154 | |||
| 155 |
function previewDeposit( |
||
| 156 |
address tokenIn, |
||
| 157 |
uint256 amountTokenToDeposit |
||
| 158 |
) |
||
| 159 |
external |
||
| 160 |
view |
||
| 161 |
returns (uint256 amountSharesOut); |
||
| 162 | |||
| 163 |
function previewRedeem( |
||
| 164 |
address tokenOut, |
||
| 165 |
uint256 amountSharesToRedeem |
||
| 166 |
) |
||
| 167 |
external |
||
| 168 |
view |
||
| 169 |
returns (uint256 amountTokenOut); |
||
| 170 | |||
| 171 |
/** |
||
| 172 |
* @notice This function contains information to interpret what the asset is |
||
| 173 |
* @return assetType the type of the asset (0 for ERC20 tokens, 1 for AMM liquidity tokens, |
||
| 174 |
* 2 for bridged yield bearing tokens like wstETH, rETH on Arbi whose the underlying asset doesn't exist on the |
||
| 175 |
* chain) |
||
| 176 |
* @return assetAddress the address of the asset |
||
| 177 |
* @return assetDecimals the decimals of the asset |
||
| 178 |
*/ |
||
| 179 |
function assetInfo() external view returns (AssetType assetType, address assetAddress, uint8 assetDecimals); |
||
| 180 |
} |
||
| 181 |
0.0%
lib/v2-core/src/vendor/standards/ERC7540/IERC7540Vault.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity >=0.8.0; |
||
| 3 | |||
| 4 |
import { IERC7741 } from "../ERC7741/IERC7741.sol";
|
||
| 5 | |||
| 6 |
interface IERC7540Operator {
|
||
| 7 |
/** |
||
| 8 |
* @dev The event emitted when an operator is set. |
||
| 9 |
* |
||
| 10 |
* @param controller The address of the controller. |
||
| 11 |
* @param operator The address of the operator. |
||
| 12 |
* @param approved The approval status. |
||
| 13 |
*/ |
||
| 14 |
event OperatorSet(address indexed controller, address indexed operator, bool approved); |
||
| 15 | |||
| 16 |
/** |
||
| 17 |
* @dev Sets or removes an operator for the caller. |
||
| 18 |
* |
||
| 19 |
* @param operator The address of the operator. |
||
| 20 |
* @param approved The approval status. |
||
| 21 |
* @return Whether the call was executed successfully or not |
||
| 22 |
*/ |
||
| 23 |
function setOperator(address operator, bool approved) external returns (bool); |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev Returns `true` if the `operator` is approved as an operator for an `controller`. |
||
| 27 |
* |
||
| 28 |
* @param controller The address of the controller. |
||
| 29 |
* @param operator The address of the operator. |
||
| 30 |
* @return status The approval status |
||
| 31 |
*/ |
||
| 32 |
function isOperator(address controller, address operator) external view returns (bool status); |
||
| 33 |
} |
||
| 34 | |||
| 35 |
interface IERC7540Deposit is IERC7540Operator {
|
||
| 36 |
event DepositRequest( |
||
| 37 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 38 |
); |
||
| 39 |
/** |
||
| 40 |
* @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit. |
||
| 41 |
* |
||
| 42 |
* - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow. |
||
| 43 |
* - MUST revert if all of assets cannot be requested for deposit. |
||
| 44 |
* - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 45 |
* approval of ERC-20 tokens from owner to sender is NOT enough. |
||
| 46 |
* |
||
| 47 |
* @param assets the amount of deposit assets to transfer from owner |
||
| 48 |
* @param controller the controller of the request who will be able to operate the request |
||
| 49 |
* @param owner the source of the deposit assets |
||
| 50 |
* |
||
| 51 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token. |
||
| 52 |
*/ |
||
| 53 | |||
| 54 |
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @dev Returns the amount of requested assets in Pending state. |
||
| 58 |
* |
||
| 59 |
* - MUST NOT include any assets in Claimable state for deposit or mint. |
||
| 60 |
* - MUST NOT show any variations depending on the caller. |
||
| 61 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 62 |
*/ |
||
| 63 |
function pendingDepositRequest( |
||
| 64 |
uint256 requestId, |
||
| 65 |
address controller |
||
| 66 |
) |
||
| 67 |
external |
||
| 68 |
view |
||
| 69 |
returns (uint256 pendingAssets); |
||
| 70 | |||
| 71 |
/** |
||
| 72 |
* @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint. |
||
| 73 |
* |
||
| 74 |
* - MUST NOT include any assets in Pending state. |
||
| 75 |
* - MUST NOT show any variations depending on the caller. |
||
| 76 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 77 |
*/ |
||
| 78 |
function claimableDepositRequest( |
||
| 79 |
uint256 requestId, |
||
| 80 |
address controller |
||
| 81 |
) |
||
| 82 |
external |
||
| 83 |
view |
||
| 84 |
returns (uint256 claimableAssets); |
||
| 85 | |||
| 86 |
/** |
||
| 87 |
* @dev Mints shares Vault shares to receiver by claiming the Request of the controller. |
||
| 88 |
* |
||
| 89 |
* - MUST emit the Deposit event. |
||
| 90 |
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
||
| 91 |
*/ |
||
| 92 |
function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
||
| 93 | |||
| 94 |
/** |
||
| 95 |
* @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller. |
||
| 96 |
* |
||
| 97 |
* - MUST emit the Deposit event. |
||
| 98 |
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
||
| 99 |
*/ |
||
| 100 |
function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
||
| 101 |
} |
||
| 102 | |||
| 103 |
interface IERC7540Redeem is IERC7540Operator {
|
||
| 104 |
event RedeemRequest( |
||
| 105 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 106 |
); |
||
| 107 | |||
| 108 |
/** |
||
| 109 |
* @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem. |
||
| 110 |
* |
||
| 111 |
* - MUST support a redeem Request flow where the control of shares is taken from sender directly |
||
| 112 |
* where msg.sender has ERC-20 approval over the shares of owner. |
||
| 113 |
* - MUST revert if all of shares cannot be requested for redeem. |
||
| 114 |
* |
||
| 115 |
* @param shares the amount of shares to be redeemed to transfer from owner |
||
| 116 |
* @param controller the controller of the request who will be able to operate the request |
||
| 117 |
* @param owner the source of the shares to be redeemed |
||
| 118 |
* |
||
| 119 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault's share token. |
||
| 120 |
*/ |
||
| 121 |
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
||
| 122 | |||
| 123 |
/** |
||
| 124 |
* @dev Returns the amount of requested shares in Pending state. |
||
| 125 |
* |
||
| 126 |
* - MUST NOT include any shares in Claimable state for redeem or withdraw. |
||
| 127 |
* - MUST NOT show any variations depending on the caller. |
||
| 128 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 129 |
*/ |
||
| 130 |
function pendingRedeemRequest( |
||
| 131 |
uint256 requestId, |
||
| 132 |
address controller |
||
| 133 |
) |
||
| 134 |
external |
||
| 135 |
view |
||
| 136 |
returns (uint256 pendingShares); |
||
| 137 | |||
| 138 |
/** |
||
| 139 |
* @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw. |
||
| 140 |
* |
||
| 141 |
* - MUST NOT include any shares in Pending state for redeem or withdraw. |
||
| 142 |
* - MUST NOT show any variations depending on the caller. |
||
| 143 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 144 |
*/ |
||
| 145 |
function claimableRedeemRequest( |
||
| 146 |
uint256 requestId, |
||
| 147 |
address controller |
||
| 148 |
) |
||
| 149 |
external |
||
| 150 |
view |
||
| 151 |
returns (uint256 claimableShares); |
||
| 152 |
} |
||
| 153 | |||
| 154 |
interface IERC7540CancelDeposit {
|
||
| 155 |
event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender); |
||
| 156 |
event CancelDepositClaim( |
||
| 157 |
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets |
||
| 158 |
); |
||
| 159 | |||
| 160 |
/** |
||
| 161 |
* @dev Submits a Request for cancelling the pending deposit Request |
||
| 162 |
* |
||
| 163 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 164 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 165 |
* - MUST set pendingCancelDepositRequest to `true` for the returned requestId after request |
||
| 166 |
* - MUST increase claimableCancelDepositRequest for the returned requestId after fulfillment |
||
| 167 |
* - SHOULD be claimable using `claimCancelDepositRequest` |
||
| 168 |
* Note: while `pendingCancelDepositRequest` is `true`, `requestDeposit` cannot be called |
||
| 169 |
*/ |
||
| 170 |
function cancelDepositRequest(uint256 requestId, address controller) external; |
||
| 171 | |||
| 172 |
/** |
||
| 173 |
* @dev Returns whether the deposit Request is pending cancelation |
||
| 174 |
* |
||
| 175 |
* - MUST NOT show any variations depending on the caller. |
||
| 176 |
*/ |
||
| 177 |
function pendingCancelDepositRequest( |
||
| 178 |
uint256 requestId, |
||
| 179 |
address controller |
||
| 180 |
) |
||
| 181 |
external |
||
| 182 |
view |
||
| 183 |
returns (bool isPending); |
||
| 184 | |||
| 185 |
/** |
||
| 186 |
* @dev Returns the amount of assets that were canceled from a deposit Request, and can now be claimed. |
||
| 187 |
* |
||
| 188 |
* - MUST NOT show any variations depending on the caller. |
||
| 189 |
*/ |
||
| 190 |
function claimableCancelDepositRequest( |
||
| 191 |
uint256 requestId, |
||
| 192 |
address controller |
||
| 193 |
) |
||
| 194 |
external |
||
| 195 |
view |
||
| 196 |
returns (uint256 claimableAssets); |
||
| 197 | |||
| 198 |
/** |
||
| 199 |
* @dev Claims the canceled deposit assets, and removes the pending cancelation Request |
||
| 200 |
* |
||
| 201 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 202 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 203 |
* - MUST set pendingCancelDepositRequest to `false` for the returned requestId after request |
||
| 204 |
* - MUST set claimableCancelDepositRequest to 0 for the returned requestId after fulfillment |
||
| 205 |
*/ |
||
| 206 |
function claimCancelDepositRequest( |
||
| 207 |
uint256 requestId, |
||
| 208 |
address receiver, |
||
| 209 |
address controller |
||
| 210 |
) |
||
| 211 |
external |
||
| 212 |
returns (uint256 assets); |
||
| 213 |
} |
||
| 214 | |||
| 215 |
interface IERC7540CancelRedeem {
|
||
| 216 |
event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender); |
||
| 217 |
event CancelRedeemClaim( |
||
| 218 |
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares |
||
| 219 |
); |
||
| 220 | |||
| 221 |
/** |
||
| 222 |
* @dev Submits a Request for cancelling the pending redeem Request |
||
| 223 |
* |
||
| 224 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 225 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 226 |
* - MUST set pendingCancelRedeemRequest to `true` for the returned requestId after request |
||
| 227 |
* - MUST increase claimableCancelRedeemRequest for the returned requestId after fulfillment |
||
| 228 |
* - SHOULD be claimable using `claimCancelRedeemRequest` |
||
| 229 |
* Note: while `pendingCancelRedeemRequest` is `true`, `requestRedeem` cannot be called |
||
| 230 |
*/ |
||
| 231 |
function cancelRedeemRequest(uint256 requestId, address controller) external; |
||
| 232 | |||
| 233 |
/** |
||
| 234 |
* @dev Returns whether the redeem Request is pending cancelation |
||
| 235 |
* |
||
| 236 |
* - MUST NOT show any variations depending on the caller. |
||
| 237 |
*/ |
||
| 238 |
function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool isPending); |
||
| 239 | |||
| 240 |
/** |
||
| 241 |
* @dev Returns the amount of shares that were canceled from a redeem Request, and can now be claimed. |
||
| 242 |
* |
||
| 243 |
* - MUST NOT show any variations depending on the caller. |
||
| 244 |
*/ |
||
| 245 |
function claimableCancelRedeemRequest( |
||
| 246 |
uint256 requestId, |
||
| 247 |
address controller |
||
| 248 |
) |
||
| 249 |
external |
||
| 250 |
view |
||
| 251 |
returns (uint256 claimableShares); |
||
| 252 | |||
| 253 |
/** |
||
| 254 |
* @dev Claims the canceled redeem shares, and removes the pending cancelation Request |
||
| 255 |
* |
||
| 256 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 257 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 258 |
* - MUST set pendingCancelRedeemRequest to `false` for the returned requestId after request |
||
| 259 |
* - MUST set claimableCancelRedeemRequest to 0 for the returned requestId after fulfillment |
||
| 260 |
*/ |
||
| 261 |
function claimCancelRedeemRequest( |
||
| 262 |
uint256 requestId, |
||
| 263 |
address receiver, |
||
| 264 |
address controller |
||
| 265 |
) |
||
| 266 |
external |
||
| 267 |
returns (uint256 shares); |
||
| 268 |
} |
||
| 269 | |||
| 270 |
/** |
||
| 271 |
* @title IERC7540 |
||
| 272 |
* @dev Fully async ERC7540 implementation according to the standard |
||
| 273 |
* @dev Adapted from Centrifuge's IERC7540 implementation |
||
| 274 |
*/ |
||
| 275 |
interface IERC7540 is IERC7540Deposit, IERC7540Redeem { }
|
||
| 276 | |||
| 277 |
/** |
||
| 278 |
* @title IERC7540Vault |
||
| 279 |
* @dev This is the specific set of interfaces used by the SuperVaults |
||
| 280 |
*/ |
||
| 281 |
interface IERC7540Vault is IERC7540, IERC7741 {
|
||
| 282 |
event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
||
| 283 |
event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
||
| 284 |
} |
||
| 285 |
0.0%
lib/v2-core/src/vendor/standards/ERC7741/IERC7741.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity >=0.8.0; |
||
| 3 | |||
| 4 |
interface IERC7741 {
|
||
| 5 |
/** |
||
| 6 |
* @dev Grants or revokes permissions for `operator` to manage Requests on behalf of the |
||
| 7 |
* `msg.sender`, using an [EIP-712](./eip-712.md) signature. |
||
| 8 |
*/ |
||
| 9 |
function authorizeOperator( |
||
| 10 |
address controller, |
||
| 11 |
address operator, |
||
| 12 |
bool approved, |
||
| 13 |
bytes32 nonce, |
||
| 14 |
uint256 deadline, |
||
| 15 |
bytes memory signature |
||
| 16 |
) |
||
| 17 |
external |
||
| 18 |
returns (bool); |
||
| 19 | |||
| 20 |
/** |
||
| 21 |
* @dev Revokes the given `nonce` for `msg.sender` as the `owner`. |
||
| 22 |
*/ |
||
| 23 |
function invalidateNonce(bytes32 nonce) external; |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev Returns whether the given `nonce` has been used for the `controller`. |
||
| 27 |
*/ |
||
| 28 |
function authorizations(address controller, bytes32 nonce) external view returns (bool used); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR |
||
| 32 |
* should be unique to the contract and chain to prevent replay attacks from other domains, |
||
| 33 |
* and satisfy the requirements of EIP-712, but is otherwise unconstrained. |
||
| 34 |
*/ |
||
| 35 |
function DOMAIN_SEPARATOR() external view returns (bytes32); |
||
| 36 |
} |
||
| 37 |
0.0%
lib/v2-core/src/vendor/superform/ISuperVault.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ISuperVault |
||
| 5 |
/// @notice Interface for SuperVault core contract that manages share minting |
||
| 6 |
/// @author Superform Labs |
||
| 7 |
interface ISuperVault {
|
||
| 8 |
/*////////////////////////////////////////////////////////////// |
||
| 9 |
ERRORS |
||
| 10 |
//////////////////////////////////////////////////////////////*/ |
||
| 11 |
error ALREADY_INITIALIZED(); |
||
| 12 |
error INVALID_ASSET(); |
||
| 13 |
error INVALID_STRATEGY(); |
||
| 14 |
error INVALID_ESCROW(); |
||
| 15 |
error ZERO_ADDRESS(); |
||
| 16 |
error ZERO_AMOUNT(); |
||
| 17 |
error INVALID_OWNER_OR_OPERATOR(); |
||
| 18 |
error INVALID_AMOUNT(); |
||
| 19 |
error REQUEST_NOT_FOUND(); |
||
| 20 |
error UNAUTHORIZED(); |
||
| 21 |
error TIMELOCK_NOT_EXPIRED(); |
||
| 22 |
error INVALID_SIGNATURE(); |
||
| 23 |
error NOT_IMPLEMENTED(); |
||
| 24 |
error INVALID_NONCE(); |
||
| 25 |
error INVALID_WITHDRAW_PRICE(); |
||
| 26 |
error TRANSFER_FAILED(); |
||
| 27 |
error CAP_EXCEEDED(); |
||
| 28 |
error INVALID_PPS(); |
||
| 29 |
error INVALID_CONTROLLER(); |
||
| 30 | |||
| 31 |
/*////////////////////////////////////////////////////////////// |
||
| 32 |
EVENTS |
||
| 33 |
//////////////////////////////////////////////////////////////*/ |
||
| 34 | |||
| 35 |
event RedeemClaimable( |
||
| 36 |
address indexed user, |
||
| 37 |
uint256 indexed requestId, |
||
| 38 |
uint256 assets, |
||
| 39 |
uint256 shares, |
||
| 40 |
uint256 averageWithdrawPrice, |
||
| 41 |
uint256 accumulatorShares, |
||
| 42 |
uint256 accumulatorCostBasis |
||
| 43 |
); |
||
| 44 | |||
| 45 |
event NonceInvalidated(address indexed sender, bytes32 indexed nonce); |
||
| 46 | |||
| 47 |
event RedeemRequestCancelled(address indexed controller, address indexed sender); |
||
| 48 | |||
| 49 |
/*////////////////////////////////////////////////////////////// |
||
| 50 |
EXTERNAL METHODS |
||
| 51 |
//////////////////////////////////////////////////////////////*/ |
||
| 52 | |||
| 53 |
function cancelRedeem(address controller) external; |
||
| 54 | |||
| 55 |
/// @notice Mint new shares, only callable by strategy |
||
| 56 |
/// @param amount The amount of shares to mint |
||
| 57 |
function mintShares(uint256 amount) external; |
||
| 58 | |||
| 59 |
/// @notice Burn shares, only callable by strategy |
||
| 60 |
/// @param amount The amount of shares to burn |
||
| 61 |
function burnShares(uint256 amount) external; |
||
| 62 | |||
| 63 |
/// @notice Callback function for when a redeem becomes claimable |
||
| 64 |
/// @param user The user whose redeem is claimable |
||
| 65 |
/// @param assets The amount of assets to be received |
||
| 66 |
/// @param shares The amount of shares redeemed |
||
| 67 |
/// @param averageWithdrawPrice The average price of the redeem |
||
| 68 |
/// @param accumulatorShares The amount of shares in the accumulator |
||
| 69 |
/// @param accumulatorCostBasis The cost basis of the accumulator |
||
| 70 |
function onRedeemClaimable( |
||
| 71 |
address user, |
||
| 72 |
uint256 assets, |
||
| 73 |
uint256 shares, |
||
| 74 |
uint256 averageWithdrawPrice, |
||
| 75 |
uint256 accumulatorShares, |
||
| 76 |
uint256 accumulatorCostBasis |
||
| 77 |
) |
||
| 78 |
external; |
||
| 79 |
} |
||
| 80 |
0.0%
lib/v2-core/src/vendor/vaults/7540/IERC7540.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
interface IERC7575 {
|
||
| 5 |
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); |
||
| 6 |
event Withdraw( |
||
| 7 |
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares |
||
| 8 |
); |
||
| 9 | |||
| 10 |
/** |
||
| 11 |
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
||
| 12 |
* |
||
| 13 |
* - MUST be an ERC-20 token contract. |
||
| 14 |
* - MUST NOT revert. |
||
| 15 |
*/ |
||
| 16 |
function asset() external view returns (address assetTokenAddress); |
||
| 17 | |||
| 18 |
/** |
||
| 19 |
* @dev Returns the address of the share token |
||
| 20 |
* |
||
| 21 |
* - MUST be an ERC-20 token contract. |
||
| 22 |
* - MUST NOT revert. |
||
| 23 |
*/ |
||
| 24 |
function share() external view returns (address shareTokenAddress); |
||
| 25 | |||
| 26 |
/** |
||
| 27 |
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
||
| 28 |
* scenario where all the conditions are met. |
||
| 29 |
* |
||
| 30 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 31 |
* - MUST NOT show any variations depending on the caller. |
||
| 32 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 33 |
* - MUST NOT revert. |
||
| 34 |
* |
||
| 35 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 36 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 37 |
* from. |
||
| 38 |
*/ |
||
| 39 |
function convertToShares(uint256 assets) external view returns (uint256 shares); |
||
| 40 | |||
| 41 |
/** |
||
| 42 |
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
||
| 43 |
* scenario where all the conditions are met. |
||
| 44 |
* |
||
| 45 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 46 |
* - MUST NOT show any variations depending on the caller. |
||
| 47 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 48 |
* - MUST NOT revert. |
||
| 49 |
* |
||
| 50 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 51 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 52 |
* from. |
||
| 53 |
*/ |
||
| 54 |
function convertToAssets(uint256 shares) external view returns (uint256 assets); |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
||
| 58 |
* |
||
| 59 |
* - SHOULD include any compounding that occurs from yield. |
||
| 60 |
* - MUST be inclusive of any fees that are charged against assets in the Vault. |
||
| 61 |
* - MUST NOT revert. |
||
| 62 |
*/ |
||
| 63 |
function totalAssets() external view returns (uint256 totalManagedAssets); |
||
| 64 | |||
| 65 |
/** |
||
| 66 |
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
||
| 67 |
* through a deposit call. |
||
| 68 |
* |
||
| 69 |
* - MUST return a limited value if receiver is subject to some deposit limit. |
||
| 70 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
||
| 71 |
* - MUST NOT revert. |
||
| 72 |
*/ |
||
| 73 |
function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
||
| 74 | |||
| 75 |
/** |
||
| 76 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
||
| 77 |
* current on-chain conditions. |
||
| 78 |
* |
||
| 79 |
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
||
| 80 |
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
||
| 81 |
* in the same transaction. |
||
| 82 |
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
||
| 83 |
* deposit would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 84 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 85 |
* - MUST NOT revert. |
||
| 86 |
* |
||
| 87 |
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
||
| 88 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 89 |
*/ |
||
| 90 |
function previewDeposit(uint256 assets) external view returns (uint256 shares); |
||
| 91 | |||
| 92 |
/** |
||
| 93 |
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
||
| 94 |
* |
||
| 95 |
* - MUST emit the Deposit event. |
||
| 96 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 97 |
* deposit execution, and are accounted for during deposit. |
||
| 98 |
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
||
| 99 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 100 |
* |
||
| 101 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 102 |
*/ |
||
| 103 |
function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
||
| 104 | |||
| 105 |
/** |
||
| 106 |
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
||
| 107 |
* - MUST return a limited value if receiver is subject to some mint limit. |
||
| 108 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
||
| 109 |
* - MUST NOT revert. |
||
| 110 |
*/ |
||
| 111 |
function maxMint(address receiver) external view returns (uint256 maxShares); |
||
| 112 | |||
| 113 |
/** |
||
| 114 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
||
| 115 |
* current on-chain conditions. |
||
| 116 |
* |
||
| 117 |
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
||
| 118 |
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
||
| 119 |
* same transaction. |
||
| 120 |
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
||
| 121 |
* would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 122 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 123 |
* - MUST NOT revert. |
||
| 124 |
* |
||
| 125 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
||
| 126 |
* share price or some other type of condition, meaning the depositor will lose assets by minting. |
||
| 127 |
*/ |
||
| 128 |
function previewMint(uint256 shares) external view returns (uint256 assets); |
||
| 129 | |||
| 130 |
/** |
||
| 131 |
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
||
| 132 |
* |
||
| 133 |
* - MUST emit the Deposit event. |
||
| 134 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
||
| 135 |
* execution, and are accounted for during mint. |
||
| 136 |
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
||
| 137 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 138 |
* |
||
| 139 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 140 |
*/ |
||
| 141 |
function mint(uint256 shares, address receiver) external returns (uint256 assets); |
||
| 142 | |||
| 143 |
/** |
||
| 144 |
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
||
| 145 |
* Vault, through a withdraw call. |
||
| 146 |
* |
||
| 147 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 148 |
* - MUST NOT revert. |
||
| 149 |
*/ |
||
| 150 |
function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
||
| 151 | |||
| 152 |
/** |
||
| 153 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
||
| 154 |
* given current on-chain conditions. |
||
| 155 |
* |
||
| 156 |
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
||
| 157 |
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
||
| 158 |
* called |
||
| 159 |
* in the same transaction. |
||
| 160 |
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
||
| 161 |
* the withdrawal would be accepted, regardless if the user has enough shares, etc. |
||
| 162 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 163 |
* - MUST NOT revert. |
||
| 164 |
* |
||
| 165 |
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
||
| 166 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 167 |
*/ |
||
| 168 |
function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
||
| 169 | |||
| 170 |
/** |
||
| 171 |
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
||
| 172 |
* |
||
| 173 |
* - MUST emit the Withdraw event. |
||
| 174 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 175 |
* withdraw execution, and are accounted for during withdraw. |
||
| 176 |
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
||
| 177 |
* not having enough shares, etc). |
||
| 178 |
* |
||
| 179 |
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 180 |
* Those methods should be performed separately. |
||
| 181 |
*/ |
||
| 182 |
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
||
| 183 | |||
| 184 |
/** |
||
| 185 |
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
||
| 186 |
* through a redeem call. |
||
| 187 |
* |
||
| 188 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 189 |
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
||
| 190 |
* - MUST NOT revert. |
||
| 191 |
*/ |
||
| 192 |
function maxRedeem(address owner) external view returns (uint256 maxShares); |
||
| 193 | |||
| 194 |
/** |
||
| 195 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, |
||
| 196 |
* given current on-chain conditions. |
||
| 197 |
* |
||
| 198 |
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
||
| 199 |
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
||
| 200 |
* same transaction. |
||
| 201 |
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
||
| 202 |
* redemption would be accepted, regardless if the user has enough shares, etc. |
||
| 203 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 204 |
* - MUST NOT revert. |
||
| 205 |
* |
||
| 206 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
||
| 207 |
* share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
||
| 208 |
*/ |
||
| 209 |
function previewRedeem(uint256 shares) external view returns (uint256 assets); |
||
| 210 | |||
| 211 |
/** |
||
| 212 |
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
||
| 213 |
* |
||
| 214 |
* - MUST emit the Withdraw event. |
||
| 215 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 216 |
* redeem execution, and are accounted for during redeem. |
||
| 217 |
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
||
| 218 |
* not having enough shares, etc). |
||
| 219 |
* |
||
| 220 |
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 221 |
* Those methods should be performed separately. |
||
| 222 |
*/ |
||
| 223 |
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
||
| 224 |
} |
||
| 225 |
// As defined by https://eips.ethereum.org/EIPS/eip-7540#request-flows |
||
| 226 | |||
| 227 |
interface IERC7540 is IERC7575 {
|
||
| 228 |
event DepositRequest( |
||
| 229 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 230 |
); |
||
| 231 | |||
| 232 |
event RedeemRequest( |
||
| 233 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 234 |
); |
||
| 235 | |||
| 236 |
/*////////////////////////////////////////////////////////////// |
||
| 237 |
VIEW METHODS |
||
| 238 |
//////////////////////////////////////////////////////////////*/ |
||
| 239 |
/// @notice Check if the contract supports an interface |
||
| 240 |
/// @param interfaceId The selector of the interface to check |
||
| 241 |
function supportsInterface(bytes4 interfaceId) external view returns (bool); |
||
| 242 | |||
| 243 |
/// @notice Preview the amount of assets that would be received for a given amount of shares |
||
| 244 |
/// @param shares The amount of shares to redeem |
||
| 245 |
function previewRedeem(uint256 shares) external view returns (uint256 assets); |
||
| 246 | |||
| 247 |
/// @notice Check if a deposit request is pending |
||
| 248 |
/// @param requestId The id of the request to check |
||
| 249 |
/// @param controller The address of the controller |
||
| 250 |
function pendingDepositRequest(uint256 requestId, address controller) external view returns (uint256); |
||
| 251 | |||
| 252 |
/// @notice Check if a deposit request is pending cancellation |
||
| 253 |
/// @param requestId The id of the request to check |
||
| 254 |
/// @param controller The address of the controller |
||
| 255 |
function pendingCancelDepositRequest(uint256 requestId, address controller) external view returns (bool); |
||
| 256 | |||
| 257 |
/// @notice Check if a redeem request is pending |
||
| 258 |
/// @param requestId The id of the request to check |
||
| 259 |
/// @param controller The address of the controller |
||
| 260 |
function pendingRedeemRequest(uint256 requestId, address controller) external view returns (uint256); |
||
| 261 | |||
| 262 |
/// @notice Get the amount of assets that can be claimed from a deposit request |
||
| 263 |
/// @param requestId The id of the request to check |
||
| 264 |
/// @param controller The address of the controller |
||
| 265 |
function claimableDepositRequest(uint256 requestId, address controller) external view returns (uint256); |
||
| 266 | |||
| 267 |
/// @notice Get the amount of shares that can be claimed from a redeem request |
||
| 268 |
/// @param requestId The id of the request to check |
||
| 269 |
/// @param controller The address of the controller |
||
| 270 |
function claimableRedeemRequest(uint256 requestId, address controller) external view returns (uint256); |
||
| 271 | |||
| 272 |
/// @notice Check if a redeem request is pending cancellation |
||
| 273 |
/// @param requestId The id of the request to check |
||
| 274 |
/// @param controller The address of the controller |
||
| 275 |
function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool); |
||
| 276 | |||
| 277 |
/*////////////////////////////////////////////////////////////// |
||
| 278 |
EXTERNAL METHODS |
||
| 279 |
//////////////////////////////////////////////////////////////*/ |
||
| 280 |
/// @notice Mints amount of shares by claiming the controller's request |
||
| 281 |
/// @dev sender must be the controller or an operator approved by the controller |
||
| 282 |
/// @param assets The amount of assets to deposit |
||
| 283 |
/// @param receiver The address of the receiver |
||
| 284 |
/// @param controller The address of the controller |
||
| 285 |
function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
||
| 286 | |||
| 287 |
/// @notice Mint exact amount of shares into the vault by claiming the controller's request |
||
| 288 |
/// @param shares The amount of shares to mint |
||
| 289 |
/// @param receiver The address of the receiver |
||
| 290 |
/// @param controller The address of the controller |
||
| 291 |
function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
||
| 292 | |||
| 293 |
/// @notice Burn shares from the vault and sends exact amount of assets to the receiver |
||
| 294 |
/// @param assets The amount of assets to withdraw |
||
| 295 |
/// @param receiver The address of the receiver |
||
| 296 |
/// @param owner The address of the owner |
||
| 297 |
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
||
| 298 | |||
| 299 |
/// @notice Burns exact shares from the vault and sends assets to the receiver |
||
| 300 |
/// @param shares The amount of shares to redeem |
||
| 301 |
/// @param receiver The address of the receiver |
||
| 302 |
/// @param owner The address of the owner |
||
| 303 |
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
||
| 304 | |||
| 305 |
/// @notice Request a deposit of assets |
||
| 306 |
/// @param assets The amount of assets to deposit |
||
| 307 |
/// @param controller The address of the controller |
||
| 308 |
/// @param owner The address of the owner |
||
| 309 |
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
||
| 310 | |||
| 311 |
/// @notice Request a redeem of shares |
||
| 312 |
/// @param shares The amount of shares to redeem |
||
| 313 |
/// @param controller The address of the controller |
||
| 314 |
/// @param owner The address of the owner |
||
| 315 |
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
||
| 316 | |||
| 317 |
/// @notice Cancel a deposit request |
||
| 318 |
/// @param requestId The id of the request to cancel |
||
| 319 |
/// @param controller The address of the controller |
||
| 320 |
function cancelDepositRequest(uint256 requestId, address controller) external; |
||
| 321 | |||
| 322 |
/// @notice Claims the canceled deposit assets, and removes the pending cancelation Request |
||
| 323 |
/// @dev sender must be the controller |
||
| 324 |
/// @param requestId The id of the request to claim |
||
| 325 |
/// @param receiver The address of the receiver |
||
| 326 |
/// @param controller The address of the controller |
||
| 327 |
function claimCancelDepositRequest( |
||
| 328 |
uint256 requestId, |
||
| 329 |
address receiver, |
||
| 330 |
address controller |
||
| 331 |
) |
||
| 332 |
external |
||
| 333 |
returns (uint256 assets); |
||
| 334 | |||
| 335 |
/// @notice Cancel a redeem request |
||
| 336 |
/// @param requestId The id of the request to cancel |
||
| 337 |
/// @param controller The address of the controller |
||
| 338 |
function cancelRedeemRequest(uint256 requestId, address controller) external; |
||
| 339 | |||
| 340 |
/// @notice Claims the canceled redeem shares, and removes the pending cancelation Request |
||
| 341 |
/// @dev sender must be the controller |
||
| 342 |
/// @param requestId The id of the request to claim |
||
| 343 |
/// @param receiver The address of the receiver |
||
| 344 |
/// @param controller The address of the controller |
||
| 345 |
function claimCancelRedeemRequest( |
||
| 346 |
uint256 requestId, |
||
| 347 |
address receiver, |
||
| 348 |
address controller |
||
| 349 |
) |
||
| 350 |
external |
||
| 351 |
returns (uint256 shares); |
||
| 352 | |||
| 353 |
/// @notice Get the pool id |
||
| 354 |
function poolId() external view returns (uint64); |
||
| 355 | |||
| 356 |
/// @notice Get the tranche id |
||
| 357 |
function trancheId() external view returns (bytes16); |
||
| 358 | |||
| 359 |
/*////////////////////////////////////////////////////////////// |
||
| 360 |
OWNER METHODS |
||
| 361 |
//////////////////////////////////////////////////////////////*/ |
||
| 362 |
function setOperator(address operator, bool approved) external returns (bool); |
||
| 363 |
function isOperator(address controller, address operator) external view returns (bool status); |
||
| 364 |
} |
||
| 365 |
31.2%
src/SuperGovernor.sol
Lines covered: 119 / 381 (31.2%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// external |
||
| 5 |
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
|
||
| 6 |
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||
| 7 |
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
|
||
| 8 | |||
| 9 |
// Superform |
||
| 10 |
import { ISuperGovernor, FeeType } from "./interfaces/ISuperGovernor.sol";
|
||
| 11 |
import { ISuperVaultAggregator } from "./interfaces/SuperVault/ISuperVaultAggregator.sol";
|
||
| 12 |
import { ISuperAssetFactory } from "./interfaces/SuperAsset/ISuperAssetFactory.sol";
|
||
| 13 |
import { ISuperOracle } from "./interfaces/oracles/ISuperOracle.sol";
|
||
| 14 |
import { ISuperOracleL2 } from "./interfaces/oracles/ISuperOracleL2.sol";
|
||
| 15 | |||
| 16 |
/// @title SuperGovernor |
||
| 17 |
/// @author Superform Labs |
||
| 18 |
/// @notice Central registry for all deployed contracts in the Superform periphery |
||
| 19 |
contract SuperGovernor is ISuperGovernor, AccessControl {
|
||
| 20 |
using EnumerableSet for EnumerableSet.AddressSet; |
||
| 21 | |||
| 22 |
/*////////////////////////////////////////////////////////////// |
||
| 23 |
STORAGE |
||
| 24 |
//////////////////////////////////////////////////////////////*/ |
||
| 25 |
// Address registry |
||
| 26 |
mapping(bytes32 id => address address_) private _addressRegistry; |
||
| 27 | |||
| 28 |
// PPS Oracle management |
||
| 29 |
// Active PPS Oracle quorum requirement |
||
| 30 |
uint256 private _activePPSOracleQuorum; |
||
| 31 |
// Current active PPS Oracle |
||
| 32 |
address private _activePPSOracle; |
||
| 33 |
// Proposed new active PPS Oracle |
||
| 34 |
address private _proposedActivePPSOracle; |
||
| 35 |
// Effective time for proposed active PPS Oracle change |
||
| 36 |
uint256 private _activePPSOracleEffectiveTime; |
||
| 37 | |||
| 38 |
// Hook registry |
||
| 39 |
EnumerableSet.AddressSet private _registeredHooks; |
||
| 40 |
EnumerableSet.AddressSet private _registeredFulfillRequestsHooks; |
||
| 41 | |||
| 42 |
// SuperBank Hook Target validation |
||
| 43 |
mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private superBankHooksMerkleRoots; |
||
| 44 | |||
| 45 |
// VaultBank registry |
||
| 46 |
EnumerableSet.AddressSet private _vaultBanks; |
||
| 47 |
mapping(uint64 chainId => address vaultBank) private _vaultBanksByChainId; |
||
| 48 | |||
| 49 |
// VaultBank Hook Target validation |
||
| 50 |
mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private vaultBankHooksMerkleRoots; |
||
| 51 | |||
| 52 |
// Global freeze for manager takeovers |
||
| 53 |
bool private _managerTakeoversFrozen; |
||
| 54 | |||
| 55 |
// Validator registry |
||
| 56 |
EnumerableSet.AddressSet private _validators; |
||
| 57 | |||
| 58 |
// Relayer registry |
||
| 59 |
EnumerableSet.AddressSet private _relayers; |
||
| 60 | |||
| 61 |
// Protected keepers registry (cannot be added as authorized callers by managers) |
||
| 62 |
EnumerableSet.AddressSet private _protectedKeepers; |
||
| 63 | |||
| 64 |
// Executor registry |
||
| 65 |
EnumerableSet.AddressSet private _executors; |
||
| 66 | |||
| 67 |
// Polymer prover |
||
| 68 |
address private _prover; |
||
| 69 | |||
| 70 |
// Whitelisted incentive tokens |
||
| 71 |
mapping(address token => bool isWhitelisted) private _isWhitelistedIncentiveToken; |
||
| 72 |
EnumerableSet.AddressSet private _proposedWhitelistedIncentiveTokens; |
||
| 73 |
EnumerableSet.AddressSet private _proposedRemoveWhitelistedIncentiveTokens; |
||
| 74 |
uint256 private _proposedAddWhitelistedIncentiveTokensEffectiveTime; |
||
| 75 |
uint256 private _proposedRemoveWhitelistedIncentiveTokensEffectiveTime; |
||
| 76 | |||
| 77 |
// Fee management |
||
| 78 |
// Current fee values |
||
| 79 |
mapping(FeeType type_ => uint256 value) private _feeValues; |
||
| 80 |
// Proposed fee values |
||
| 81 |
mapping(FeeType type_ => uint256 proposedValue) private _proposedFeeValues; |
||
| 82 |
// Effective times for proposed fee updates |
||
| 83 |
mapping(FeeType type_ => uint256 effectiveTime) private _feeEffectiveTimes; |
||
| 84 | |||
| 85 |
mapping(address _oracle => GasInfo info) private _oracleGasInfo; |
||
| 86 | |||
| 87 |
// Upkeep control |
||
| 88 |
bool private _upkeepPaymentsEnabled; |
||
| 89 |
bool private _proposedUpkeepPaymentsEnabled; |
||
| 90 |
uint256 private _upkeepPaymentsChangeEffectiveTime; |
||
| 91 | |||
| 92 |
// Superform managers (exempt from upkeep costs) |
||
| 93 |
EnumerableSet.AddressSet private _superformManagers; |
||
| 94 | |||
| 95 |
// Min staleness configuration to prevent maxStaleness from being set too low |
||
| 96 |
uint256 private _minStaleness; |
||
| 97 |
uint256 private _proposedMinStaleness; |
||
| 98 |
uint256 private _minStalenesEffectiveTime; |
||
| 99 | |||
| 100 |
// Oracle constants |
||
| 101 |
address private constant NATIVE_TOKEN = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); |
||
| 102 |
address private constant USD_TOKEN = address(840); |
||
| 103 |
address private constant GAS_QUOTE = |
||
| 104 |
address(uint160(uint256(keccak256("GAS_QUOTE"))));
|
||
| 105 |
address private constant GWEI_QUOTE = |
||
| 106 |
address(uint160(uint256(keccak256("GWEI_QUOTE"))));
|
||
| 107 |
bytes32 private constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
|
||
| 108 | |||
| 109 |
// Timelock configuration |
||
| 110 |
✓ 274.4K
|
uint256 private constant TIMELOCK = 7 days; |
|
| 111 |
✓ 119.2K
|
uint256 private constant BPS_MAX = 10_000; // 100% in basis points |
|
| 112 | |||
| 113 |
// Role definitions |
||
| 114 |
bytes32 private constant _SUPER_GOVERNOR_ROLE = keccak256("SUPER_GOVERNOR_ROLE");
|
||
| 115 |
bytes32 private constant _GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
|
||
| 116 |
bytes32 private constant _BANK_MANAGER_ROLE = keccak256("BANK_MANAGER_ROLE");
|
||
| 117 |
✓ 1
|
bytes32 private constant _GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
|
|
| 118 |
bytes32 private constant _SUPER_ASSET_FACTORY = keccak256("SUPER_ASSET_FACTORY");
|
||
| 119 |
bytes32 private constant _GAS_MANAGER_ROLE = keccak256("GAS_MANAGER_ROLE");
|
||
| 120 | |||
| 121 |
// Common contract keys |
||
| 122 |
✓ 269.4K
|
bytes32 public constant UP = keccak256("UP");
|
|
| 123 |
bytes32 public constant SUP = keccak256("SUP");
|
||
| 124 |
✓ 1
|
bytes32 public constant TREASURY = keccak256("TREASURY");
|
|
| 125 |
bytes32 public constant VAULT_BANK = keccak256("VAULT_BANK");
|
||
| 126 |
✓ 1.6K
|
bytes32 public constant SUPER_BANK = keccak256("SUPER_BANK");
|
|
| 127 |
bytes32 public constant SUPER_ORACLE = keccak256("SUPER_ORACLE");
|
||
| 128 |
bytes32 public constant BANK_MANAGER = keccak256("BANK_MANAGER");
|
||
| 129 |
bytes32 public constant ECDSAPPSORACLE = keccak256("ECDSAPPSORACLE");
|
||
| 130 |
✓ 16.1M
|
bytes32 public constant SUPER_VAULT_AGGREGATOR = keccak256("SUPER_VAULT_AGGREGATOR");
|
|
| 131 | |||
| 132 |
/*////////////////////////////////////////////////////////////// |
||
| 133 |
CONSTRUCTOR |
||
| 134 |
//////////////////////////////////////////////////////////////*/ |
||
| 135 |
/// @notice Initializes the SuperGovernor contract |
||
| 136 |
/// @param superGovernor Address of the default admin (will have SUPER_GOVERNOR_ROLE) |
||
| 137 |
/// @param governor Address that will have the GOVERNOR_ROLE for daily operations |
||
| 138 |
/// @param bankManager Address that will have the BANK_MANAGER_ROLE for daily operations |
||
| 139 |
/// @param treasury_ Address of the treasury |
||
| 140 |
/// @param prover_ Address of the prover |
||
| 141 |
constructor(address superGovernor, address governor, address bankManager, address gasManager, address treasury_, address prover_) {
|
||
| 142 |
if ( |
||
| 143 |
✓ 1
|
superGovernor == address(0) || treasury_ == address(0) || governor == address(0) |
|
| 144 |
✓ 1
|
|| bankManager == address(0) || prover_ == address(0) || gasManager == address(0) |
|
| 145 |
) revert INVALID_ADDRESS(); |
||
| 146 | |||
| 147 |
// Set up roles |
||
| 148 |
✓ 1
|
_grantRole(DEFAULT_ADMIN_ROLE, superGovernor); |
|
| 149 |
✓ 1
|
_grantRole(_SUPER_GOVERNOR_ROLE, superGovernor); |
|
| 150 |
✓ 1
|
_grantRole(_GOVERNOR_ROLE, governor); |
|
| 151 |
✓ 1
|
_grantRole(_BANK_MANAGER_ROLE, bankManager); |
|
| 152 |
✓ 1
|
_grantRole(_GAS_MANAGER_ROLE, gasManager); |
|
| 153 |
// Setup GUARDIAN_ROLE without assigning any address |
||
| 154 |
✓ 1
|
_setRoleAdmin(_GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE); |
|
| 155 | |||
| 156 |
// Set role admins |
||
| 157 |
✓ 1
|
_setRoleAdmin(_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
|
| 158 |
✓ 1
|
_setRoleAdmin(_SUPER_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE); |
|
| 159 |
✓ 1
|
_setRoleAdmin(_BANK_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
|
| 160 |
✓ 1
|
_setRoleAdmin(_GAS_MANAGER_ROLE, DEFAULT_ADMIN_ROLE); |
|
| 161 | |||
| 162 |
// Initialize with default fees |
||
| 163 |
✓ 1
|
_feeValues[FeeType.REVENUE_SHARE] = 2000; // 20% revenue share |
|
| 164 |
✓ 1
|
_feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE] = 2000; // 20% performance fee |
|
| 165 |
✓ 1
|
_feeValues[FeeType.SUPER_ASSET_SWAP_FEE] = 4000; // 40% swap fee |
|
| 166 |
✓ 1
|
emit FeeUpdated(FeeType.REVENUE_SHARE, _feeValues[FeeType.REVENUE_SHARE]); |
|
| 167 |
✓ 1
|
emit FeeUpdated(FeeType.SUPER_VAULT_PERFORMANCE_FEE, _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE]); |
|
| 168 |
✓ 1
|
emit FeeUpdated(FeeType.SUPER_ASSET_SWAP_FEE, _feeValues[FeeType.SUPER_ASSET_SWAP_FEE]); |
|
| 169 | |||
| 170 |
// Set treasury in address registry |
||
| 171 |
✓ 1
|
_addressRegistry[TREASURY] = treasury_; |
|
| 172 |
✓ 1
|
emit AddressSet(TREASURY, treasury_); |
|
| 173 | |||
| 174 |
// Initialize minimum staleness (5 minutes to prevent extremely low staleness values) |
||
| 175 |
✓ 1
|
_minStaleness = 300; // 5 minutes in seconds |
|
| 176 | |||
| 177 |
// Initialize prover |
||
| 178 |
✓ 1
|
_prover = prover_; |
|
| 179 |
✓ 1
|
emit ProverSet(prover_); |
|
| 180 |
} |
||
| 181 | |||
| 182 |
/*////////////////////////////////////////////////////////////// |
||
| 183 |
CONTRACT REGISTRY FUNCTIONS |
||
| 184 |
//////////////////////////////////////////////////////////////*/ |
||
| 185 |
/// @inheritdoc ISuperGovernor |
||
| 186 |
function setAddress(bytes32 key, address value) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 187 |
✓ 3
|
if (value == address(0)) revert INVALID_ADDRESS(); |
|
| 188 | |||
| 189 |
✓ 3
|
_addressRegistry[key] = value; |
|
| 190 |
✓ 3
|
emit AddressSet(key, value); |
|
| 191 |
} |
||
| 192 | |||
| 193 |
/*////////////////////////////////////////////////////////////// |
||
| 194 |
PERIPHERY CONFIGURATIONS |
||
| 195 |
//////////////////////////////////////////////////////////////*/ |
||
| 196 |
/// @inheritdoc ISuperGovernor |
||
| 197 |
function setProver(address prover) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 198 |
if (prover == address(0)) revert INVALID_ADDRESS(); |
||
| 199 | |||
| 200 |
_prover = prover; |
||
| 201 |
emit ProverSet(prover); |
||
| 202 |
} |
||
| 203 | |||
| 204 |
/// @inheritdoc ISuperGovernor |
||
| 205 |
function changePrimaryManager(address strategy, address newManager) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 206 |
// Check if takeovers are globally frozen |
||
| 207 |
✓ 134.7K
|
if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN(); |
|
| 208 | |||
| 209 |
✓ 134.7K
|
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
|
| 210 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 211 | |||
| 212 |
// Call the interface method to change the manager |
||
| 213 |
// This function can only be called by the SuperGovernor and bypasses the timelock |
||
| 214 |
✓ 134.7K
|
ISuperVaultAggregator(aggregator).changePrimaryManager(strategy, newManager); |
|
| 215 |
} |
||
| 216 | |||
| 217 |
/// @inheritdoc ISuperGovernor |
||
| 218 |
function freezeManagerTakeover() external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 219 |
if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN(); |
||
| 220 | |||
| 221 |
// Set frozen status to true (permanent, cannot be undone) |
||
| 222 |
_managerTakeoversFrozen = true; |
||
| 223 | |||
| 224 |
// Emit event for the frozen status |
||
| 225 |
emit ManagerTakeoversFrozen(); |
||
| 226 |
} |
||
| 227 | |||
| 228 |
/// @inheritdoc ISuperGovernor |
||
| 229 |
function changeHooksRootUpdateTimelock(uint256 newTimelock) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 230 |
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
||
| 231 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 232 | |||
| 233 |
// Call the SuperVaultAggregator to change the hooks root update timelock |
||
| 234 |
ISuperVaultAggregator(aggregator).setHooksRootUpdateTimelock(newTimelock); |
||
| 235 |
} |
||
| 236 | |||
| 237 |
/// @inheritdoc ISuperGovernor |
||
| 238 |
function proposeGlobalHooksRoot(bytes32 newRoot) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 239 |
✓ 259.5K
|
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
|
| 240 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 241 | |||
| 242 |
✓ 259.5K
|
ISuperVaultAggregator(aggregator).proposeGlobalHooksRoot(newRoot); |
|
| 243 |
} |
||
| 244 | |||
| 245 |
/// @inheritdoc ISuperGovernor |
||
| 246 |
function setGlobalHooksRootVetoStatus(bool vetoed) external onlyRole(_GUARDIAN_ROLE) {
|
||
| 247 |
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
||
| 248 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 249 | |||
| 250 |
ISuperVaultAggregator(aggregator).setGlobalHooksRootVetoStatus(vetoed); |
||
| 251 |
} |
||
| 252 | |||
| 253 |
/// @inheritdoc ISuperGovernor |
||
| 254 |
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external onlyRole(_GUARDIAN_ROLE) {
|
||
| 255 |
if (strategy == address(0)) revert INVALID_ADDRESS(); |
||
| 256 | |||
| 257 |
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
||
| 258 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 259 | |||
| 260 |
ISuperVaultAggregator(aggregator).setStrategyHooksRootVetoStatus(strategy, vetoed); |
||
| 261 |
} |
||
| 262 | |||
| 263 |
/// @inheritdoc ISuperGovernor |
||
| 264 |
function setSuperAssetManager(address superAsset, address superAssetManager) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 265 |
if (superAsset == address(0) || superAssetManager == address(0)) revert INVALID_ADDRESS(); |
||
| 266 |
address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
||
| 267 |
if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 268 |
ISuperAssetFactory factory = ISuperAssetFactory(value); |
||
| 269 |
factory.setSuperAssetManager(superAsset, superAssetManager); |
||
| 270 |
} |
||
| 271 | |||
| 272 |
/// @inheritdoc ISuperGovernor |
||
| 273 |
function addICCToWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 274 |
if (icc == address(0)) revert INVALID_ADDRESS(); |
||
| 275 |
address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
||
| 276 |
if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 277 |
ISuperAssetFactory factory = ISuperAssetFactory(value); |
||
| 278 |
factory.addICCToWhitelist(icc); |
||
| 279 |
} |
||
| 280 | |||
| 281 |
/// @inheritdoc ISuperGovernor |
||
| 282 |
function removeICCFromWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 283 |
if (icc == address(0)) revert INVALID_ADDRESS(); |
||
| 284 |
address value = _addressRegistry[_SUPER_ASSET_FACTORY]; |
||
| 285 |
if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 286 |
ISuperAssetFactory factory = ISuperAssetFactory(value); |
||
| 287 |
factory.removeICCFromWhitelist(icc); |
||
| 288 |
} |
||
| 289 | |||
| 290 |
/// @inheritdoc ISuperGovernor |
||
| 291 |
function setOracleMaxStaleness(uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 292 |
if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
||
| 293 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 294 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 295 | |||
| 296 |
ISuperOracle(oracle).setMaxStaleness(newMaxStaleness); |
||
| 297 |
} |
||
| 298 | |||
| 299 |
/// @inheritdoc ISuperGovernor |
||
| 300 |
function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 301 |
if (feed == address(0)) revert INVALID_ADDRESS(); |
||
| 302 |
if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
||
| 303 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 304 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 305 | |||
| 306 |
ISuperOracle(oracle).setFeedMaxStaleness(feed, newMaxStaleness); |
||
| 307 |
} |
||
| 308 | |||
| 309 |
/// @inheritdoc ISuperGovernor |
||
| 310 |
function setOracleFeedMaxStalenessBatch( |
||
| 311 |
address[] calldata feeds_, |
||
| 312 |
uint256[] calldata newMaxStalenessList_ |
||
| 313 |
) |
||
| 314 |
external |
||
| 315 |
onlyRole(_GOVERNOR_ROLE) |
||
| 316 |
{
|
||
| 317 |
// Validate all staleness values before proceeding |
||
| 318 |
for (uint256 i; i < newMaxStalenessList_.length; i++) {
|
||
| 319 |
if (newMaxStalenessList_[i] < _minStaleness) revert MAX_STALENESS_TOO_LOW(); |
||
| 320 |
} |
||
| 321 | |||
| 322 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 323 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 324 | |||
| 325 |
ISuperOracle(oracle).setFeedMaxStalenessBatch(feeds_, newMaxStalenessList_); |
||
| 326 |
} |
||
| 327 | |||
| 328 |
/// @inheritdoc ISuperGovernor |
||
| 329 |
function queueOracleUpdate( |
||
| 330 |
address[] calldata bases_, |
||
| 331 |
address[] calldata quotes_, |
||
| 332 |
bytes32[] calldata providers_, |
||
| 333 |
address[] calldata feeds_ |
||
| 334 |
) |
||
| 335 |
external |
||
| 336 |
onlyRole(_GOVERNOR_ROLE) |
||
| 337 |
{
|
||
| 338 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 339 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 340 | |||
| 341 |
ISuperOracle(oracle).queueOracleUpdate(bases_, quotes_, providers_, feeds_); |
||
| 342 |
} |
||
| 343 | |||
| 344 |
/// @inheritdoc ISuperGovernor |
||
| 345 |
function queueOracleProviderRemoval(bytes32[] calldata providers) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 346 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 347 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 348 | |||
| 349 |
ISuperOracle(oracle).queueProviderRemoval(providers); |
||
| 350 |
} |
||
| 351 | |||
| 352 |
/// @inheritdoc ISuperGovernor |
||
| 353 |
function batchSetOracleUptimeFeed( |
||
| 354 |
address[] calldata dataOracles_, |
||
| 355 |
address[] calldata uptimeOracles_, |
||
| 356 |
uint256[] calldata gracePeriods_ |
||
| 357 |
) |
||
| 358 |
external |
||
| 359 |
onlyRole(_GOVERNOR_ROLE) |
||
| 360 |
{
|
||
| 361 |
address oracleL2 = _addressRegistry[SUPER_ORACLE]; |
||
| 362 |
if (oracleL2 == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 363 | |||
| 364 |
ISuperOracleL2(oracleL2).batchSetUptimeFeed(dataOracles_, uptimeOracles_, gracePeriods_); |
||
| 365 |
} |
||
| 366 | |||
| 367 |
/// @inheritdoc ISuperGovernor |
||
| 368 |
function setEmergencyPrice(address token, uint256 price) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 369 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 370 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 371 | |||
| 372 |
ISuperOracle(oracle).setEmergencyPrice(token, price); |
||
| 373 |
} |
||
| 374 | |||
| 375 |
/// @inheritdoc ISuperGovernor |
||
| 376 |
function batchSetEmergencyPrices( |
||
| 377 |
address[] calldata tokens_, |
||
| 378 |
uint256[] calldata prices_ |
||
| 379 |
) |
||
| 380 |
external |
||
| 381 |
onlyRole(_GOVERNOR_ROLE) |
||
| 382 |
{
|
||
| 383 |
address oracle = _addressRegistry[SUPER_ORACLE]; |
||
| 384 |
if (oracle == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 385 | |||
| 386 |
ISuperOracle(oracle).batchSetEmergencyPrice(tokens_, prices_); |
||
| 387 |
} |
||
| 388 | |||
| 389 |
/*////////////////////////////////////////////////////////////// |
||
| 390 |
HOOK MANAGEMENT |
||
| 391 |
//////////////////////////////////////////////////////////////*/ |
||
| 392 |
/// @inheritdoc ISuperGovernor |
||
| 393 |
function registerHook(address hook, bool isFulfillRequestsHook) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 394 |
✓ 18
|
if (hook == address(0)) revert INVALID_ADDRESS(); |
|
| 395 | |||
| 396 |
✓ 18
|
if (isFulfillRequestsHook && _registeredFulfillRequestsHooks.add(hook)) {
|
|
| 397 |
✓ 5
|
emit FulfillRequestsHookRegistered(hook); |
|
| 398 |
} |
||
| 399 |
✓ 18
|
if (_registeredHooks.add(hook)) {
|
|
| 400 |
✓ 18
|
emit HookApproved(hook); |
|
| 401 |
} |
||
| 402 |
} |
||
| 403 | |||
| 404 |
/// @inheritdoc ISuperGovernor |
||
| 405 |
function unregisterHook(address hook) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 406 |
if (_registeredFulfillRequestsHooks.remove(hook)) {
|
||
| 407 |
emit FulfillRequestsHookUnregistered(hook); |
||
| 408 |
} |
||
| 409 |
if (_registeredHooks.remove(hook)) {
|
||
| 410 |
// Clear merkle root data for the unregistered hook to prevent stale data |
||
| 411 |
delete superBankHooksMerkleRoots[hook]; |
||
| 412 |
delete vaultBankHooksMerkleRoots[hook]; |
||
| 413 | |||
| 414 |
emit HookRemoved(hook); |
||
| 415 |
} |
||
| 416 |
} |
||
| 417 | |||
| 418 |
/*////////////////////////////////////////////////////////////// |
||
| 419 |
EXECUTORS MANAGEMENT |
||
| 420 |
//////////////////////////////////////////////////////////////*/ |
||
| 421 |
/// @inheritdoc ISuperGovernor |
||
| 422 |
function addExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 423 |
if (executor == address(0)) revert INVALID_ADDRESS(); |
||
| 424 |
if (!_executors.add(executor)) revert EXECUTOR_ALREADY_REGISTERED(); |
||
| 425 | |||
| 426 |
emit ExecutorAdded(executor); |
||
| 427 |
} |
||
| 428 | |||
| 429 |
/// @inheritdoc ISuperGovernor |
||
| 430 |
function removeExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 431 |
if (!_executors.remove(executor)) revert EXECUTOR_NOT_REGISTERED(); |
||
| 432 | |||
| 433 |
emit ExecutorRemoved(executor); |
||
| 434 |
} |
||
| 435 | |||
| 436 |
/*////////////////////////////////////////////////////////////// |
||
| 437 |
RELAYER MANAGEMENT |
||
| 438 |
//////////////////////////////////////////////////////////////*/ |
||
| 439 |
/// @inheritdoc ISuperGovernor |
||
| 440 |
function addRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 441 |
if (relayer == address(0)) revert INVALID_ADDRESS(); |
||
| 442 |
if (!_relayers.add(relayer)) revert RELAYER_ALREADY_REGISTERED(); |
||
| 443 | |||
| 444 |
emit RelayerAdded(relayer); |
||
| 445 |
} |
||
| 446 | |||
| 447 |
/// @inheritdoc ISuperGovernor |
||
| 448 |
function removeRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 449 |
if (!_relayers.remove(relayer)) revert RELAYER_NOT_REGISTERED(); |
||
| 450 | |||
| 451 |
emit RelayerRemoved(relayer); |
||
| 452 |
} |
||
| 453 | |||
| 454 |
/*////////////////////////////////////////////////////////////// |
||
| 455 |
VALIDATOR MANAGEMENT |
||
| 456 |
//////////////////////////////////////////////////////////////*/ |
||
| 457 |
/// @inheritdoc ISuperGovernor |
||
| 458 |
function addValidator(address validator) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 459 |
if (validator == address(0)) revert INVALID_ADDRESS(); |
||
| 460 |
if (!_validators.add(validator)) revert VALIDATOR_ALREADY_REGISTERED(); |
||
| 461 | |||
| 462 |
emit ValidatorAdded(validator); |
||
| 463 |
} |
||
| 464 | |||
| 465 |
/// @inheritdoc ISuperGovernor |
||
| 466 |
function removeValidator(address validator) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 467 |
if (!_validators.remove(validator)) revert VALIDATOR_NOT_REGISTERED(); |
||
| 468 | |||
| 469 |
emit ValidatorRemoved(validator); |
||
| 470 |
} |
||
| 471 | |||
| 472 |
/*////////////////////////////////////////////////////////////// |
||
| 473 |
PPS ORACLE MANAGEMENT |
||
| 474 |
//////////////////////////////////////////////////////////////*/ |
||
| 475 |
/// @inheritdoc ISuperGovernor |
||
| 476 |
function setActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 477 |
✓ 1
|
if (oracle == address(0)) revert INVALID_ADDRESS(); |
|
| 478 | |||
| 479 |
// If this is the first oracle or replacing a zero oracle, set it immediately |
||
| 480 |
✓ 1
|
if (_activePPSOracle == address(0)) {
|
|
| 481 |
✓ 1
|
_activePPSOracle = oracle; |
|
| 482 |
✓ 1
|
emit ActivePPSOracleSet(oracle); |
|
| 483 |
} else {
|
||
| 484 |
// Otherwise require the timelock process |
||
| 485 |
revert MUST_USE_TIMELOCK_FOR_CHANGE(); |
||
| 486 |
} |
||
| 487 |
} |
||
| 488 | |||
| 489 |
/// @inheritdoc ISuperGovernor |
||
| 490 |
function proposeActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 491 |
if (oracle == address(0)) revert INVALID_ADDRESS(); |
||
| 492 | |||
| 493 |
_proposedActivePPSOracle = oracle; |
||
| 494 |
_activePPSOracleEffectiveTime = block.timestamp + TIMELOCK; |
||
| 495 | |||
| 496 |
✓ 3.9K
|
emit ActivePPSOracleProposed(oracle, _activePPSOracleEffectiveTime); |
|
| 497 |
} |
||
| 498 | |||
| 499 |
/// @inheritdoc ISuperGovernor |
||
| 500 |
function executeActivePPSOracleChange() external {
|
||
| 501 |
if (_proposedActivePPSOracle == address(0)) revert NO_PROPOSED_PPS_ORACLE(); |
||
| 502 | |||
| 503 |
if (block.timestamp < _activePPSOracleEffectiveTime) {
|
||
| 504 |
revert TIMELOCK_NOT_EXPIRED(); |
||
| 505 |
} |
||
| 506 | |||
| 507 |
address oldOracle = _activePPSOracle; |
||
| 508 |
_activePPSOracle = _proposedActivePPSOracle; |
||
| 509 | |||
| 510 |
// Reset proposal data |
||
| 511 |
_proposedActivePPSOracle = address(0); |
||
| 512 |
_activePPSOracleEffectiveTime = 0; |
||
| 513 | |||
| 514 |
emit ActivePPSOracleChanged(oldOracle, _activePPSOracle); |
||
| 515 |
} |
||
| 516 | |||
| 517 |
/// @inheritdoc ISuperGovernor |
||
| 518 |
function setPPSOracleQuorum(uint256 quorum) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 519 |
if (quorum == 0) revert INVALID_QUORUM(); |
||
| 520 | |||
| 521 |
_activePPSOracleQuorum = quorum; |
||
| 522 |
emit PPSOracleQuorumUpdated(quorum); |
||
| 523 |
} |
||
| 524 | |||
| 525 |
/*////////////////////////////////////////////////////////////// |
||
| 526 |
REVENUE SHARE MANAGEMENT |
||
| 527 |
//////////////////////////////////////////////////////////////*/ |
||
| 528 |
/// @inheritdoc ISuperGovernor |
||
| 529 |
function proposeFee(FeeType feeType, uint256 value) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 530 |
✓ 119.2K
|
⟲ 91.5K
|
if (value > BPS_MAX) revert INVALID_FEE_VALUE(); |
| 531 | |||
| 532 |
✓ 27.7K
|
_proposedFeeValues[feeType] = value; |
|
| 533 |
✓ 27.7K
|
_feeEffectiveTimes[feeType] = block.timestamp + TIMELOCK; |
|
| 534 | |||
| 535 |
✓ 27.7K
|
emit FeeProposed(feeType, value, _feeEffectiveTimes[feeType]); |
|
| 536 |
} |
||
| 537 | |||
| 538 |
/// @inheritdoc ISuperGovernor |
||
| 539 |
function executeFeeUpdate(FeeType feeType) external {
|
||
| 540 |
✓ 161.3K
|
uint256 effectiveTime = _feeEffectiveTimes[feeType]; |
|
| 541 |
✓ 161.3K
|
if (effectiveTime == 0) revert NO_PROPOSED_FEE(feeType); |
|
| 542 |
✓ 4.0K
|
if (block.timestamp < effectiveTime) {
|
|
| 543 |
✓ 114
|
⟲ 114
|
revert TIMELOCK_NOT_EXPIRED(); |
| 544 |
} |
||
| 545 | |||
| 546 |
// Update the fee value |
||
| 547 |
✓ 3.9K
|
_feeValues[feeType] = _proposedFeeValues[feeType]; |
|
| 548 | |||
| 549 |
// Reset proposal data |
||
| 550 |
✓ 3.9K
|
delete _proposedFeeValues[feeType]; |
|
| 551 |
✓ 3.9K
|
delete _feeEffectiveTimes[feeType]; |
|
| 552 | |||
| 553 |
✓ 3.9K
|
emit FeeUpdated(feeType, _feeValues[feeType]); |
|
| 554 |
} |
||
| 555 | |||
| 556 |
/// @inheritdoc ISuperGovernor |
||
| 557 |
function executeUpkeepClaim(uint256 amount) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 558 |
✓ 132.7K
|
address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR]; |
|
| 559 |
if (aggregator == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 560 | |||
| 561 |
✓ 132.7K
|
ISuperVaultAggregator(aggregator).claimUpkeep(amount); |
|
| 562 |
} |
||
| 563 | |||
| 564 |
/*////////////////////////////////////////////////////////////// |
||
| 565 |
UPKEEP COST MANAGEMENT |
||
| 566 |
//////////////////////////////////////////////////////////////*/ |
||
| 567 |
/// @inheritdoc ISuperGovernor |
||
| 568 |
function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external onlyRole(_GAS_MANAGER_ROLE) {
|
||
| 569 |
if (oracle == address(0)) revert INVALID_ADDRESS(); |
||
| 570 |
if (baseGasBatch == 0 || gasIncreasePerEntryBatch == 0) revert INVALID_GAS_INFO(); |
||
| 571 | |||
| 572 |
_oracleGasInfo[oracle] = GasInfo({baseGasBatch: baseGasBatch, gasIncreasePerEntryBatch: gasIncreasePerEntryBatch});
|
||
| 573 |
emit GasInfoSet(oracle, baseGasBatch, gasIncreasePerEntryBatch); |
||
| 574 |
} |
||
| 575 | |||
| 576 |
/// @notice Proposes a change to the upkeep payments enabled status |
||
| 577 |
/// @param enabled The proposed new status for upkeep payments |
||
| 578 |
function proposeUpkeepPaymentsChange(bool enabled) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 579 |
✓ 274.4K
|
_proposedUpkeepPaymentsEnabled = enabled; |
|
| 580 |
✓ 274.4K
|
_upkeepPaymentsChangeEffectiveTime = block.timestamp + TIMELOCK; |
|
| 581 | |||
| 582 |
✓ 274.4K
|
emit UpkeepPaymentsChangeProposed(enabled, _upkeepPaymentsChangeEffectiveTime); |
|
| 583 |
} |
||
| 584 | |||
| 585 |
/// @notice Executes a previously proposed change to upkeep payments status after timelock expires |
||
| 586 |
function executeUpkeepPaymentsChange() external {
|
||
| 587 |
✓ 141.4K
|
⟲ 84.4K
|
if (_upkeepPaymentsChangeEffectiveTime == 0) revert NO_PENDING_CHANGE(); |
| 588 |
✓ 57.0K
|
⟲ 2.7K
|
if (block.timestamp < _upkeepPaymentsChangeEffectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 589 | |||
| 590 |
✓ 54.3K
|
_upkeepPaymentsEnabled = _proposedUpkeepPaymentsEnabled; |
|
| 591 |
✓ 54.3K
|
_upkeepPaymentsChangeEffectiveTime = 0; |
|
| 592 |
✓ 54.3K
|
_proposedUpkeepPaymentsEnabled = false; |
|
| 593 | |||
| 594 |
✓ 54.3K
|
emit UpkeepPaymentsChanged(_upkeepPaymentsEnabled); |
|
| 595 |
} |
||
| 596 | |||
| 597 |
/*////////////////////////////////////////////////////////////// |
||
| 598 |
MIN STALENESS MANAGEMENT |
||
| 599 |
//////////////////////////////////////////////////////////////*/ |
||
| 600 |
/// @inheritdoc ISuperGovernor |
||
| 601 |
function proposeMinStaleness(uint256 newMinStaleness) external onlyRole(_SUPER_GOVERNOR_ROLE) {
|
||
| 602 |
✓ 118.6K
|
_proposedMinStaleness = newMinStaleness; |
|
| 603 |
✓ 118.6K
|
_minStalenesEffectiveTime = block.timestamp + TIMELOCK; |
|
| 604 | |||
| 605 |
✓ 118.6K
|
emit MinStalenesProposed(newMinStaleness, _minStalenesEffectiveTime); |
|
| 606 |
} |
||
| 607 | |||
| 608 |
/// @inheritdoc ISuperGovernor |
||
| 609 |
function executeMinStalenesChange() external {
|
||
| 610 |
✓ 126.5K
|
uint256 minStalenesEffectiveTime = _minStalenesEffectiveTime; |
|
| 611 |
✓ 126.5K
|
⟲ 99.4K
|
if (minStalenesEffectiveTime == 0) revert NO_PROPOSED_MIN_STALENESS(); |
| 612 |
✓ 27.1K
|
⟲ 1.2K
|
if (block.timestamp < minStalenesEffectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
| 613 | |||
| 614 |
✓ 25.8K
|
_minStaleness = _proposedMinStaleness; |
|
| 615 | |||
| 616 |
// Reset proposal data |
||
| 617 |
✓ 25.8K
|
_proposedMinStaleness = 0; |
|
| 618 |
✓ 25.8K
|
_minStalenesEffectiveTime = 0; |
|
| 619 | |||
| 620 |
✓ 25.8K
|
emit MinStalenesChanged(_minStaleness); |
|
| 621 |
} |
||
| 622 | |||
| 623 |
/*////////////////////////////////////////////////////////////// |
||
| 624 |
SUPERFORM MANAGER MANAGEMENT |
||
| 625 |
//////////////////////////////////////////////////////////////*/ |
||
| 626 |
/// @inheritdoc ISuperGovernor |
||
| 627 |
function addSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 628 |
if (manager == address(0)) revert INVALID_ADDRESS(); |
||
| 629 |
if (!_superformManagers.add(manager)) revert MANAGER_ALREADY_REGISTERED(); |
||
| 630 | |||
| 631 |
emit SuperformManagerAdded(manager); |
||
| 632 |
} |
||
| 633 | |||
| 634 |
/// @inheritdoc ISuperGovernor |
||
| 635 |
function removeSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 636 |
if (!_superformManagers.remove(manager)) revert MANAGER_NOT_REGISTERED(); |
||
| 637 | |||
| 638 |
emit SuperformManagerRemoved(manager); |
||
| 639 |
} |
||
| 640 | |||
| 641 |
/*////////////////////////////////////////////////////////////// |
||
| 642 |
VAULT HOOKS MGMT |
||
| 643 |
//////////////////////////////////////////////////////////////*/ |
||
| 644 | |||
| 645 |
/// @inheritdoc ISuperGovernor |
||
| 646 |
function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 647 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 648 |
if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
||
| 649 | |||
| 650 |
uint256 effectiveTime = block.timestamp + TIMELOCK; |
||
| 651 |
ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
||
| 652 |
data.proposedRoot = proposedRoot; |
||
| 653 |
data.effectiveTime = effectiveTime; |
||
| 654 | |||
| 655 |
emit VaultBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
||
| 656 |
} |
||
| 657 | |||
| 658 |
/// @inheritdoc ISuperGovernor |
||
| 659 |
function executeVaultBankHookMerkleRootUpdate(address hook) external {
|
||
| 660 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 661 | |||
| 662 |
ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
||
| 663 | |||
| 664 |
// Check if there's a proposed update |
||
| 665 |
bytes32 proposedRoot = data.proposedRoot; |
||
| 666 |
if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
||
| 667 | |||
| 668 |
// Check if the effective time has passed |
||
| 669 |
if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
||
| 670 | |||
| 671 |
// Update the Merkle root |
||
| 672 |
data.currentRoot = proposedRoot; |
||
| 673 | |||
| 674 |
// Reset the proposal |
||
| 675 |
data.proposedRoot = bytes32(0); |
||
| 676 |
data.effectiveTime = 0; |
||
| 677 | |||
| 678 |
emit VaultBankHookMerkleRootUpdated(hook, proposedRoot); |
||
| 679 |
} |
||
| 680 | |||
| 681 |
/*////////////////////////////////////////////////////////////// |
||
| 682 |
SUPERBANK HOOKS MGMT |
||
| 683 |
//////////////////////////////////////////////////////////////*/ |
||
| 684 |
/// @inheritdoc ISuperGovernor |
||
| 685 |
function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 686 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 687 |
if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT(); |
||
| 688 | |||
| 689 |
uint256 effectiveTime = block.timestamp + TIMELOCK; |
||
| 690 |
ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
||
| 691 |
data.proposedRoot = proposedRoot; |
||
| 692 |
data.effectiveTime = effectiveTime; |
||
| 693 | |||
| 694 |
emit SuperBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime); |
||
| 695 |
} |
||
| 696 | |||
| 697 |
/// @inheritdoc ISuperGovernor |
||
| 698 |
function executeSuperBankHookMerkleRootUpdate(address hook) external {
|
||
| 699 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 700 | |||
| 701 |
ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
||
| 702 | |||
| 703 |
// Check if there's a proposed update |
||
| 704 |
bytes32 proposedRoot = data.proposedRoot; |
||
| 705 |
if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT(); |
||
| 706 | |||
| 707 |
// Check if the effective time has passed |
||
| 708 |
if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED(); |
||
| 709 | |||
| 710 |
// Update the Merkle root |
||
| 711 |
data.currentRoot = proposedRoot; |
||
| 712 | |||
| 713 |
// Reset the proposal |
||
| 714 |
data.proposedRoot = bytes32(0); |
||
| 715 |
data.effectiveTime = 0; |
||
| 716 | |||
| 717 |
emit SuperBankHookMerkleRootUpdated(hook, proposedRoot); |
||
| 718 |
} |
||
| 719 | |||
| 720 |
/*////////////////////////////////////////////////////////////// |
||
| 721 |
VAULT BANK MANAGEMENT |
||
| 722 |
//////////////////////////////////////////////////////////////*/ |
||
| 723 |
/// @inheritdoc ISuperGovernor |
||
| 724 |
function addVaultBank(uint64 chainId, address vaultBank) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 725 |
if (chainId == 0) revert INVALID_CHAIN_ID(); |
||
| 726 |
if (vaultBank == address(0)) revert INVALID_ADDRESS(); |
||
| 727 | |||
| 728 |
if (_vaultBanksByChainId[chainId] != address(0)) {
|
||
| 729 |
_vaultBanks.remove(_vaultBanksByChainId[chainId]); |
||
| 730 |
} |
||
| 731 | |||
| 732 |
_vaultBanks.add(vaultBank); |
||
| 733 |
_vaultBanksByChainId[chainId] = vaultBank; |
||
| 734 | |||
| 735 |
emit VaultBankAddressAdded(chainId, vaultBank); |
||
| 736 |
} |
||
| 737 | |||
| 738 |
/// @inheritdoc ISuperGovernor |
||
| 739 |
function getVaultBank(uint64 chainId) external view returns (address) {
|
||
| 740 |
return _vaultBanksByChainId[chainId]; |
||
| 741 |
} |
||
| 742 | |||
| 743 |
/*////////////////////////////////////////////////////////////// |
||
| 744 |
INCENTIVE TOKEN MANAGEMENT |
||
| 745 |
//////////////////////////////////////////////////////////////*/ |
||
| 746 |
/// @inheritdoc ISuperGovernor |
||
| 747 |
function proposeAddIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 748 |
✓ 4.4M
|
for (uint256 i; i < tokens.length; i++) {
|
|
| 749 |
✓ 4.3M
|
⟲ 48.1K
|
if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
| 750 |
✓ 4.3M
|
_proposedWhitelistedIncentiveTokens.add(tokens[i]); |
|
| 751 |
} |
||
| 752 | |||
| 753 |
✓ 74.8K
|
_proposedAddWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
|
| 754 | |||
| 755 |
emit WhitelistedIncentiveTokensProposed( |
||
| 756 |
✓ 74.8K
|
_proposedWhitelistedIncentiveTokens.values(), _proposedAddWhitelistedIncentiveTokensEffectiveTime |
|
| 757 |
); |
||
| 758 |
} |
||
| 759 | |||
| 760 |
/// @inheritdoc ISuperGovernor |
||
| 761 |
function executeAddIncentiveTokens() external {
|
||
| 762 |
if ( |
||
| 763 |
✓ 142.2K
|
_proposedAddWhitelistedIncentiveTokensEffectiveTime == 0 |
|
| 764 |
✓ 22.8K
|
|| block.timestamp < _proposedAddWhitelistedIncentiveTokensEffectiveTime |
|
| 765 |
✓ 120.1K
|
⟲ 120.1K
|
) revert TIMELOCK_NOT_EXPIRED(); |
| 766 | |||
| 767 |
// Get all proposed tokens before modifying the set |
||
| 768 |
✓ 22.1K
|
address[] memory tokensToAdd = _proposedWhitelistedIncentiveTokens.values(); |
|
| 769 |
✓ 22.1K
|
uint256 len = tokensToAdd.length; |
|
| 770 |
address token; |
||
| 771 |
✓ 807.1K
|
for (uint256 i; i < len; i++) {
|
|
| 772 |
✓ 785.0K
|
token = tokensToAdd[i]; |
|
| 773 |
✓ 785.0K
|
_isWhitelistedIncentiveToken[token] = true; |
|
| 774 |
// Remove from proposed whitelisted tokens |
||
| 775 |
✓ 785.0K
|
_proposedWhitelistedIncentiveTokens.remove(token); |
|
| 776 |
} |
||
| 777 | |||
| 778 |
// Emit event once with all tokens |
||
| 779 |
✓ 22.1K
|
emit WhitelistedIncentiveTokensAdded(tokensToAdd); |
|
| 780 | |||
| 781 |
// Reset proposal timestamp |
||
| 782 |
✓ 22.1K
|
_proposedAddWhitelistedIncentiveTokensEffectiveTime = 0; |
|
| 783 |
} |
||
| 784 | |||
| 785 |
/// @inheritdoc ISuperGovernor |
||
| 786 |
function proposeRemoveIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 787 |
for (uint256 i; i < tokens.length; i++) {
|
||
| 788 |
if (tokens[i] == address(0)) revert INVALID_ADDRESS(); |
||
| 789 |
if (!_isWhitelistedIncentiveToken[tokens[i]]) revert NOT_WHITELISTED_INCENTIVE_TOKEN(); |
||
| 790 | |||
| 791 |
_proposedRemoveWhitelistedIncentiveTokens.add(tokens[i]); |
||
| 792 |
} |
||
| 793 | |||
| 794 |
_proposedRemoveWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK; |
||
| 795 | |||
| 796 |
emit WhitelistedIncentiveTokensProposed( |
||
| 797 |
_proposedRemoveWhitelistedIncentiveTokens.values(), _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
||
| 798 |
); |
||
| 799 |
} |
||
| 800 | |||
| 801 |
/// @inheritdoc ISuperGovernor |
||
| 802 |
function executeRemoveIncentiveTokens() external {
|
||
| 803 |
if ( |
||
| 804 |
✓ 148.3K
|
_proposedRemoveWhitelistedIncentiveTokensEffectiveTime == 0 |
|
| 805 |
|| block.timestamp < _proposedRemoveWhitelistedIncentiveTokensEffectiveTime |
||
| 806 |
✓ 148.3K
|
⟲ 148.3K
|
) revert TIMELOCK_NOT_EXPIRED(); |
| 807 | |||
| 808 |
// Get all proposed tokens before modifying the set |
||
| 809 |
address[] memory tokensToRemove = _proposedRemoveWhitelistedIncentiveTokens.values(); |
||
| 810 |
uint256 len = tokensToRemove.length; |
||
| 811 |
address token; |
||
| 812 |
for (uint256 i; i < len; i++) {
|
||
| 813 |
token = tokensToRemove[i]; |
||
| 814 |
if (_isWhitelistedIncentiveToken[token]) {
|
||
| 815 |
_isWhitelistedIncentiveToken[token] = false; |
||
| 816 |
} |
||
| 817 |
// Remove from proposed whitelisted tokens to be removed |
||
| 818 |
_proposedRemoveWhitelistedIncentiveTokens.remove(token); |
||
| 819 |
} |
||
| 820 | |||
| 821 |
// Emit event once with all tokens |
||
| 822 |
emit WhitelistedIncentiveTokensRemoved(tokensToRemove); |
||
| 823 | |||
| 824 |
// Reset proposal timestamp |
||
| 825 |
_proposedRemoveWhitelistedIncentiveTokensEffectiveTime = 0; |
||
| 826 |
} |
||
| 827 | |||
| 828 |
/*////////////////////////////////////////////////////////////// |
||
| 829 |
EXTERNAL VIEW FUNCTIONS |
||
| 830 |
//////////////////////////////////////////////////////////////*/ |
||
| 831 |
/// @inheritdoc ISuperGovernor |
||
| 832 |
function SUPER_GOVERNOR_ROLE() external pure returns (bytes32) {
|
||
| 833 |
return _SUPER_GOVERNOR_ROLE; |
||
| 834 |
} |
||
| 835 | |||
| 836 |
/// @inheritdoc ISuperGovernor |
||
| 837 |
function GOVERNOR_ROLE() external pure returns (bytes32) {
|
||
| 838 |
return _GOVERNOR_ROLE; |
||
| 839 |
} |
||
| 840 | |||
| 841 |
/// @inheritdoc ISuperGovernor |
||
| 842 |
function BANK_MANAGER_ROLE() external pure returns (bytes32) {
|
||
| 843 |
return _BANK_MANAGER_ROLE; |
||
| 844 |
} |
||
| 845 | |||
| 846 |
/// @inheritdoc ISuperGovernor |
||
| 847 |
function GAS_MANAGER_ROLE() external pure returns (bytes32) {
|
||
| 848 |
return _GAS_MANAGER_ROLE; |
||
| 849 |
} |
||
| 850 | |||
| 851 |
/// @inheritdoc ISuperGovernor |
||
| 852 |
function GUARDIAN_ROLE() external pure returns (bytes32) {
|
||
| 853 |
return _GUARDIAN_ROLE; |
||
| 854 |
} |
||
| 855 | |||
| 856 |
/// @inheritdoc ISuperGovernor |
||
| 857 |
function SUPER_ASSET_FACTORY() external pure returns (bytes32) {
|
||
| 858 |
return _SUPER_ASSET_FACTORY; |
||
| 859 |
} |
||
| 860 | |||
| 861 |
/// @inheritdoc ISuperGovernor |
||
| 862 |
✓ 16.4M
|
function getAddress(bytes32 key) external view returns (address) {
|
|
| 863 |
✓ 16.4M
|
address value = _addressRegistry[key]; |
|
| 864 |
if (value == address(0)) revert CONTRACT_NOT_FOUND(); |
||
| 865 |
return value; |
||
| 866 |
} |
||
| 867 | |||
| 868 |
/// @inheritdoc ISuperGovernor |
||
| 869 |
function isManagerTakeoverFrozen() external view returns (bool) {
|
||
| 870 |
return _managerTakeoversFrozen; |
||
| 871 |
} |
||
| 872 | |||
| 873 |
/// @inheritdoc ISuperGovernor |
||
| 874 |
✓ 127.5K
|
function isHookRegistered(address hook) external view returns (bool) {
|
|
| 875 |
✓ 127.5K
|
return _registeredHooks.contains(hook); |
|
| 876 |
} |
||
| 877 | |||
| 878 |
/// @inheritdoc ISuperGovernor |
||
| 879 |
✓ 376.8K
|
function isFulfillRequestsHookRegistered(address hook) external view returns (bool) {
|
|
| 880 |
✓ 376.8K
|
return _registeredFulfillRequestsHooks.contains(hook); |
|
| 881 |
} |
||
| 882 | |||
| 883 |
/// @inheritdoc ISuperGovernor |
||
| 884 |
function getRegisteredHooks() external view returns (address[] memory) {
|
||
| 885 |
return _registeredHooks.values(); |
||
| 886 |
} |
||
| 887 | |||
| 888 |
/// @inheritdoc ISuperGovernor |
||
| 889 |
function getRegisteredFulfillRequestsHooks() external view returns (address[] memory) {
|
||
| 890 |
return _registeredFulfillRequestsHooks.values(); |
||
| 891 |
} |
||
| 892 | |||
| 893 |
/// @inheritdoc ISuperGovernor |
||
| 894 |
function isValidator(address validator) external view returns (bool) {
|
||
| 895 |
return _validators.contains(validator); |
||
| 896 |
} |
||
| 897 | |||
| 898 |
/// @inheritdoc ISuperGovernor |
||
| 899 |
function isGuardian(address guardian) external view returns (bool) {
|
||
| 900 |
return hasRole(_GUARDIAN_ROLE, guardian); |
||
| 901 |
} |
||
| 902 | |||
| 903 |
/// @inheritdoc ISuperGovernor |
||
| 904 |
function isRelayer(address relayer) external view returns (bool) {
|
||
| 905 |
return _relayers.contains(relayer); |
||
| 906 |
} |
||
| 907 | |||
| 908 |
/// @inheritdoc ISuperGovernor |
||
| 909 |
function isExecutor(address executor) external view returns (bool) {
|
||
| 910 |
return _executors.contains(executor); |
||
| 911 |
} |
||
| 912 | |||
| 913 |
/// @inheritdoc ISuperGovernor |
||
| 914 |
function getValidators() external view returns (address[] memory) {
|
||
| 915 |
return _validators.values(); |
||
| 916 |
} |
||
| 917 | |||
| 918 |
/// @inheritdoc ISuperGovernor |
||
| 919 |
function getRelayers() external view returns (address[] memory) {
|
||
| 920 |
return _relayers.values(); |
||
| 921 |
} |
||
| 922 | |||
| 923 |
/// @inheritdoc ISuperGovernor |
||
| 924 |
function getExecutors() external view returns (address[] memory) {
|
||
| 925 |
return _executors.values(); |
||
| 926 |
} |
||
| 927 | |||
| 928 |
/// @inheritdoc ISuperGovernor |
||
| 929 |
function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime) {
|
||
| 930 |
return (_proposedActivePPSOracle, _activePPSOracleEffectiveTime); |
||
| 931 |
} |
||
| 932 | |||
| 933 |
/// @inheritdoc ISuperGovernor |
||
| 934 |
function getPPSOracleQuorum() external view returns (uint256) {
|
||
| 935 |
return _activePPSOracleQuorum; |
||
| 936 |
} |
||
| 937 | |||
| 938 |
/// @inheritdoc ISuperGovernor |
||
| 939 |
function getActivePPSOracle() external view returns (address) {
|
||
| 940 |
if (_activePPSOracle == address(0)) revert NO_ACTIVE_PPS_ORACLE(); |
||
| 941 |
return _activePPSOracle; |
||
| 942 |
} |
||
| 943 | |||
| 944 |
/// @inheritdoc ISuperGovernor |
||
| 945 |
function isActivePPSOracle(address oracle) external view returns (bool) {
|
||
| 946 |
✓ 260.5K
|
return oracle == _activePPSOracle; |
|
| 947 |
} |
||
| 948 | |||
| 949 |
/// @inheritdoc ISuperGovernor |
||
| 950 |
function getFee(FeeType feeType) external view returns (uint256) {
|
||
| 951 |
return _feeValues[feeType]; |
||
| 952 |
} |
||
| 953 | |||
| 954 |
/// @inheritdoc ISuperGovernor |
||
| 955 |
function getGasInfo(address oracle_) external view returns (GasInfo memory) {
|
||
| 956 |
return _oracleGasInfo[oracle_]; |
||
| 957 |
} |
||
| 958 | |||
| 959 |
/// @inheritdoc ISuperGovernor |
||
| 960 |
✓ 11.5K
|
function getUpkeepCostPerBatchUpdate(address oracle_, uint256 chargeableEntries_) external view returns (uint256) {
|
|
| 961 |
// Calculate total gas cost |
||
| 962 |
✓ 11.5K
|
uint256 totalGas = _oracleGasInfo[oracle_].baseGasBatch + |
|
| 963 |
✓ 11.5K
|
(_oracleGasInfo[oracle_].gasIncreasePerEntryBatch * chargeableEntries_); |
|
| 964 | |||
| 965 |
✓ 11.5K
|
return _convertGasToUp(totalGas); |
|
| 966 |
} |
||
| 967 | |||
| 968 |
/// @inheritdoc ISuperGovernor |
||
| 969 |
function getMinStaleness() external view returns (uint256) {
|
||
| 970 |
✓ 112.6K
|
return _minStaleness; |
|
| 971 |
} |
||
| 972 | |||
| 973 |
/// @inheritdoc ISuperGovernor |
||
| 974 |
function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime) {
|
||
| 975 |
return (_proposedMinStaleness, _minStalenesEffectiveTime); |
||
| 976 |
} |
||
| 977 | |||
| 978 |
/// @inheritdoc ISuperGovernor |
||
| 979 |
function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32) {
|
||
| 980 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 981 |
return superBankHooksMerkleRoots[hook].currentRoot; |
||
| 982 |
} |
||
| 983 | |||
| 984 |
/// @inheritdoc ISuperGovernor |
||
| 985 |
function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32) {
|
||
| 986 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 987 |
return vaultBankHooksMerkleRoots[hook].currentRoot; |
||
| 988 |
} |
||
| 989 | |||
| 990 |
/// @inheritdoc ISuperGovernor |
||
| 991 |
function getProposedSuperBankHookMerkleRoot(address hook) |
||
| 992 |
external |
||
| 993 |
view |
||
| 994 |
returns (bytes32 proposedRoot, uint256 effectiveTime) |
||
| 995 |
{
|
||
| 996 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 997 |
ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook]; |
||
| 998 |
return (data.proposedRoot, data.effectiveTime); |
||
| 999 |
} |
||
| 1000 | |||
| 1001 |
/// @inheritdoc ISuperGovernor |
||
| 1002 |
function getProposedVaultBankHookMerkleRoot(address hook) |
||
| 1003 |
external |
||
| 1004 |
view |
||
| 1005 |
returns (bytes32 proposedRoot, uint256 effectiveTime) |
||
| 1006 |
{
|
||
| 1007 |
if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED(); |
||
| 1008 |
ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook]; |
||
| 1009 |
return (data.proposedRoot, data.effectiveTime); |
||
| 1010 |
} |
||
| 1011 | |||
| 1012 |
/// @inheritdoc ISuperGovernor |
||
| 1013 |
function getProver() external view returns (address) {
|
||
| 1014 |
return _prover; |
||
| 1015 |
} |
||
| 1016 | |||
| 1017 |
/// @inheritdoc ISuperGovernor |
||
| 1018 |
function isUpkeepPaymentsEnabled() external view returns (bool enabled) {
|
||
| 1019 |
✓ 124.0K
|
return _upkeepPaymentsEnabled; |
|
| 1020 |
} |
||
| 1021 | |||
| 1022 |
/// @inheritdoc ISuperGovernor |
||
| 1023 |
function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime) {
|
||
| 1024 |
return (_proposedUpkeepPaymentsEnabled, _upkeepPaymentsChangeEffectiveTime); |
||
| 1025 |
} |
||
| 1026 | |||
| 1027 |
/// @inheritdoc ISuperGovernor |
||
| 1028 |
✓ 11.5K
|
function isSuperformManager(address manager) external view returns (bool isSuperform) {
|
|
| 1029 |
✓ 11.5K
|
return _superformManagers.contains(manager); |
|
| 1030 |
} |
||
| 1031 | |||
| 1032 |
/// @inheritdoc ISuperGovernor |
||
| 1033 |
function getAllSuperformManagers() external view returns (address[] memory managers) {
|
||
| 1034 |
return _superformManagers.values(); |
||
| 1035 |
} |
||
| 1036 | |||
| 1037 |
/// @inheritdoc ISuperGovernor |
||
| 1038 |
function getManagersPaginated( |
||
| 1039 |
uint256 cursor, |
||
| 1040 |
uint256 limit |
||
| 1041 |
) |
||
| 1042 |
external |
||
| 1043 |
view |
||
| 1044 |
returns (address[] memory chunkOfManagers, uint256 next) |
||
| 1045 |
{
|
||
| 1046 |
uint256 len = _superformManagers.length(); |
||
| 1047 | |||
| 1048 |
// clamp limit so we don’t read past end |
||
| 1049 |
uint256 realLimit = limit; |
||
| 1050 |
// If cursor is beyond the end, return empty array |
||
| 1051 |
if (cursor >= len) {
|
||
| 1052 |
return (new address[](0), 0); |
||
| 1053 |
} |
||
| 1054 | |||
| 1055 |
uint256 remaining = len - cursor; |
||
| 1056 |
if (realLimit > remaining) realLimit = remaining; |
||
| 1057 | |||
| 1058 |
chunkOfManagers = new address[](realLimit); |
||
| 1059 |
for (uint256 i; i < realLimit; ++i) {
|
||
| 1060 |
chunkOfManagers[i] = _superformManagers.at(cursor + i); |
||
| 1061 |
} |
||
| 1062 | |||
| 1063 |
next = (cursor + realLimit < len) ? cursor + realLimit : 0; |
||
| 1064 |
} |
||
| 1065 | |||
| 1066 |
/// @inheritdoc ISuperGovernor |
||
| 1067 |
function getSuperformManagersCount() external view returns (uint256) {
|
||
| 1068 |
return _superformManagers.length(); |
||
| 1069 |
} |
||
| 1070 | |||
| 1071 |
/// @inheritdoc ISuperGovernor |
||
| 1072 |
function isWhitelistedIncentiveToken(address token) external view returns (bool) {
|
||
| 1073 |
return _isWhitelistedIncentiveToken[token]; |
||
| 1074 |
} |
||
| 1075 | |||
| 1076 |
/// @inheritdoc ISuperGovernor |
||
| 1077 |
function registerProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 1078 |
if (keeper == address(0)) revert INVALID_ADDRESS(); |
||
| 1079 |
if (_protectedKeepers.contains(keeper)) revert KEEPER_ALREADY_REGISTERED(); |
||
| 1080 | |||
| 1081 |
_protectedKeepers.add(keeper); |
||
| 1082 |
emit ProtectedKeeperRegistered(keeper); |
||
| 1083 |
} |
||
| 1084 | |||
| 1085 |
/// @inheritdoc ISuperGovernor |
||
| 1086 |
function unregisterProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) {
|
||
| 1087 |
if (!_protectedKeepers.contains(keeper)) revert KEEPER_NOT_REGISTERED(); |
||
| 1088 | |||
| 1089 |
_protectedKeepers.remove(keeper); |
||
| 1090 |
emit ProtectedKeeperUnregistered(keeper); |
||
| 1091 |
} |
||
| 1092 | |||
| 1093 |
/// @inheritdoc ISuperGovernor |
||
| 1094 |
✓ 951
|
function isProtectedKeeper(address keeper) external view returns (bool) {
|
|
| 1095 |
✓ 951
|
return _protectedKeepers.contains(keeper); |
|
| 1096 |
} |
||
| 1097 | |||
| 1098 |
/// @inheritdoc ISuperGovernor |
||
| 1099 |
function getProtectedKeepers() external view returns (address[] memory) {
|
||
| 1100 |
return _protectedKeepers.values(); |
||
| 1101 |
} |
||
| 1102 | |||
| 1103 |
/// @inheritdoc ISuperGovernor |
||
| 1104 |
function getProtectedKeepersCount() external view returns (uint256) {
|
||
| 1105 |
return _protectedKeepers.length(); |
||
| 1106 |
} |
||
| 1107 | |||
| 1108 |
/*////////////////////////////////////////////////////////////// |
||
| 1109 |
INTERNAL FUNCTIONS |
||
| 1110 |
//////////////////////////////////////////////////////////////*/ |
||
| 1111 |
✓ 11.5K
|
function _convertGasToUp(uint256 gasAmount) internal view returns (uint256) {
|
|
| 1112 |
✓ 11.5K
|
address oracle = _addressRegistry[SUPER_ORACLE]; |
|
| 1113 |
✓ 11.5K
|
⟲ 11.5K
|
if (oracle == address(0)) revert SUPER_ORACLE_NOT_FOUND(); |
| 1114 |
address upToken = _addressRegistry[UP]; |
||
| 1115 |
if (upToken == address(0)) revert UP_NOT_FOUND(); |
||
| 1116 | |||
| 1117 |
// Step 1: convert gas to ETH |
||
| 1118 |
(uint256 ethAmount,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
||
| 1119 |
gasAmount, |
||
| 1120 |
GAS_QUOTE, |
||
| 1121 |
GWEI_QUOTE, |
||
| 1122 |
AVERAGE_PROVIDER |
||
| 1123 |
); |
||
| 1124 | |||
| 1125 |
// Step 2: convert ETH to USD |
||
| 1126 |
(uint256 ethToUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
||
| 1127 |
ethAmount, |
||
| 1128 |
NATIVE_TOKEN, |
||
| 1129 |
USD_TOKEN, |
||
| 1130 |
AVERAGE_PROVIDER |
||
| 1131 |
); |
||
| 1132 | |||
| 1133 |
// Step 3: convert USD to UP (how much USD per UP token) |
||
| 1134 |
(uint256 upPerUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider( |
||
| 1135 |
1e18, // 1 UP token (18 decimals) |
||
| 1136 |
upToken, |
||
| 1137 |
USD_TOKEN, |
||
| 1138 |
AVERAGE_PROVIDER |
||
| 1139 |
); |
||
| 1140 | |||
| 1141 |
// Calculate required UP tokens |
||
| 1142 |
// usdAmount / upPerUsd = required UP tokens |
||
| 1143 |
uint256 requiredUpTokens = Math.mulDiv(ethToUsd, 1e18, upPerUsd, Math.Rounding.Ceil); |
||
| 1144 |
return requiredUpTokens; |
||
| 1145 |
} |
||
| 1146 |
} |
||
| 1147 |
85.0%
src/SuperVault/SuperVault.sol
Lines covered: 147 / 173 (85.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// External |
||
| 5 |
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
|
||
| 6 |
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||
| 7 |
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 8 | |||
| 9 |
// OpenZeppelin Upgradeable |
||
| 10 |
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||
| 11 |
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
|
||
| 12 |
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||
| 13 |
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
| 14 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 15 |
import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
|
||
| 16 |
import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
|
||
| 17 |
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
|
||
| 18 | |||
| 19 |
// Interfaces |
||
| 20 |
import { ISuperVault } from "../interfaces/SuperVault/ISuperVault.sol";
|
||
| 21 |
import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 22 |
import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
|
||
| 23 |
import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
|
||
| 24 |
import { IERC7540Operator, IERC7540Redeem, IERC7741 } from "../vendor/standards/ERC7540/IERC7540Vault.sol";
|
||
| 25 |
import { IERC7575 } from "../vendor/standards/ERC7575/IERC7575.sol";
|
||
| 26 |
import { ISuperVaultEscrow } from "../interfaces/SuperVault/ISuperVaultEscrow.sol";
|
||
| 27 | |||
| 28 |
// Libraries |
||
| 29 |
import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol";
|
||
| 30 | |||
| 31 |
/// @title SuperVault |
||
| 32 |
/// @author Superform Labs |
||
| 33 |
/// @notice SuperVault vault contract implementing ERC4626 with synchronous deposits and asynchronous redeems via |
||
| 34 |
/// ERC7540 |
||
| 35 |
contract SuperVault is |
||
| 36 |
Initializable, |
||
| 37 |
ERC20Upgradeable, |
||
| 38 |
IERC7540Redeem, |
||
| 39 |
IERC7741, |
||
| 40 |
IERC4626, |
||
| 41 |
ISuperVault, |
||
| 42 |
ReentrancyGuardUpgradeable, |
||
| 43 |
EIP712Upgradeable |
||
| 44 |
{
|
||
| 45 |
using AssetMetadataLib for address; |
||
| 46 |
using SafeERC20 for IERC20; |
||
| 47 |
using Math for uint256; |
||
| 48 | |||
| 49 |
/*////////////////////////////////////////////////////////////// |
||
| 50 |
CONSTANTS |
||
| 51 |
//////////////////////////////////////////////////////////////*/ |
||
| 52 |
✓ 268.3K
|
uint256 private constant REQUEST_ID = 0; |
|
| 53 |
✓ 1.3M
|
uint256 private constant BPS_PRECISION = 10_000; |
|
| 54 | |||
| 55 |
// EIP712 TypeHash |
||
| 56 |
bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH = |
||
| 57 |
keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)");
|
||
| 58 | |||
| 59 |
/*////////////////////////////////////////////////////////////// |
||
| 60 |
STATE |
||
| 61 |
//////////////////////////////////////////////////////////////*/ |
||
| 62 |
✓ 12.8K
|
address public share; |
|
| 63 |
IERC20 private _asset; |
||
| 64 |
uint8 private _underlyingDecimals; |
||
| 65 |
ISuperVaultStrategy public strategy; |
||
| 66 |
address public escrow; |
||
| 67 |
✓ 942.1K
|
uint256 public PRECISION; |
|
| 68 | |||
| 69 |
// Core contracts |
||
| 70 |
✓ 11.3M
|
ISuperGovernor public immutable superGovernor; |
|
| 71 | |||
| 72 |
/// @inheritdoc IERC7540Operator |
||
| 73 |
mapping(address owner => mapping(address operator => bool)) public isOperator; |
||
| 74 | |||
| 75 |
// Authorization tracking |
||
| 76 |
mapping(address controller => mapping(bytes32 nonce => bool used)) private _authorizations; |
||
| 77 | |||
| 78 |
/*////////////////////////////////////////////////////////////// |
||
| 79 |
CONSTRUCTOR |
||
| 80 |
//////////////////////////////////////////////////////////////*/ |
||
| 81 | |||
| 82 |
constructor(address superGovernor_) {
|
||
| 83 |
✓ 1
|
if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
|
| 84 |
✓ 1
|
superGovernor = ISuperGovernor(superGovernor_); |
|
| 85 |
✓ 1
|
emit SuperGovernorSet(superGovernor_); |
|
| 86 | |||
| 87 |
✓ 1
|
_disableInitializers(); |
|
| 88 |
} |
||
| 89 | |||
| 90 |
/*////////////////////////////////////////////////////////////// |
||
| 91 |
INITIALIZATION |
||
| 92 |
//////////////////////////////////////////////////////////////*/ |
||
| 93 | |||
| 94 |
/// @notice Initialize the vault with required parameters |
||
| 95 |
/// @param asset_ The underlying asset token address |
||
| 96 |
/// @param name_ The name of the vault token |
||
| 97 |
/// @param symbol_ The symbol of the vault token |
||
| 98 |
/// @param strategy_ The strategy contract address |
||
| 99 |
/// @param escrow_ The escrow contract address |
||
| 100 |
function initialize( |
||
| 101 |
address asset_, |
||
| 102 |
string memory name_, |
||
| 103 |
string memory symbol_, |
||
| 104 |
address strategy_, |
||
| 105 |
address escrow_ |
||
| 106 |
) |
||
| 107 |
external |
||
| 108 |
initializer |
||
| 109 |
{
|
||
| 110 |
✓ 228.0K
|
if (asset_ == address(0)) revert INVALID_ASSET(); |
|
| 111 |
✓ 228.0K
|
if (strategy_ == address(0)) revert INVALID_STRATEGY(); |
|
| 112 |
✓ 228.0K
|
if (escrow_ == address(0)) revert INVALID_ESCROW(); |
|
| 113 | |||
| 114 |
// Initialize parent contracts |
||
| 115 |
✓ 228.0K
|
__ERC20_init(name_, symbol_); |
|
| 116 |
✓ 228.0K
|
__ReentrancyGuard_init(); |
|
| 117 |
✓ 228.0K
|
__EIP712_init(name_, "1"); |
|
| 118 | |||
| 119 |
// Set asset and precision |
||
| 120 |
✓ 228.0K
|
_asset = IERC20(asset_); |
|
| 121 |
✓ 228.0K
|
(bool success, uint8 assetDecimals) = asset_.tryGetAssetDecimals(); |
|
| 122 |
✓ 210.8K
|
⟲ 69.6K
|
if (!success) revert INVALID_ASSET(); |
| 123 |
✓ 141.2K
|
_underlyingDecimals = assetDecimals; |
|
| 124 |
✓ 141.2K
|
PRECISION = 10 ** _underlyingDecimals; |
|
| 125 |
✓ 122.2K
|
share = address(this); |
|
| 126 |
✓ 122.2K
|
strategy = ISuperVaultStrategy(strategy_); |
|
| 127 |
✓ 122.2K
|
escrow = escrow_; |
|
| 128 |
} |
||
| 129 | |||
| 130 |
/*////////////////////////////////////////////////////////////// |
||
| 131 |
ERC20 OVERRIDES |
||
| 132 |
//////////////////////////////////////////////////////////////*/ |
||
| 133 | |||
| 134 |
/*////////////////////////////////////////////////////////////// |
||
| 135 |
USER EXTERNAL FUNCTIONS |
||
| 136 |
//////////////////////////////////////////////////////////////*/ |
||
| 137 | |||
| 138 |
/// @inheritdoc IERC4626 |
||
| 139 |
✓ 567.4K
|
function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) {
|
|
| 140 |
✓ 567.4K
|
⟲ 2.6K
|
if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 141 |
✓ 564.8K
|
⟲ 8.9K
|
if (assets == 0) revert ZERO_AMOUNT(); |
| 142 | |||
| 143 |
// Forward assets from msg-sender to strategy |
||
| 144 |
✓ 555.9K
|
_asset.safeTransferFrom(msg.sender, address(strategy), assets); |
|
| 145 | |||
| 146 |
// Single executor call: strategy skims entry fee, accounts on NET, returns net shares |
||
| 147 |
✓ 102.6K
|
shares = strategy.handleOperations4626Deposit(receiver, assets); |
|
| 148 |
✓ 82.8K
|
if (shares == 0) revert ZERO_AMOUNT(); |
|
| 149 | |||
| 150 |
// Mint the net shares |
||
| 151 |
✓ 82.8K
|
_mint(receiver, shares); |
|
| 152 | |||
| 153 |
✓ 82.8K
|
emit Deposit(msg.sender, receiver, assets, shares); |
|
| 154 |
} |
||
| 155 | |||
| 156 |
/// @inheritdoc IERC4626 |
||
| 157 |
✓ 776.8K
|
function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) {
|
|
| 158 |
✓ 776.8K
|
⟲ 2.4K
|
if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 159 |
✓ 774.4K
|
⟲ 8.0K
|
if (shares == 0) revert ZERO_AMOUNT(); |
| 160 | |||
| 161 |
✓ 766.4K
|
uint256 assetsNet; |
|
| 162 |
✓ 766.4K
|
(assets, assetsNet) = strategy.quoteMintAssetsGross(shares); |
|
| 163 | |||
| 164 |
// Forward quoted gross assets from msg-sender to strategy |
||
| 165 |
✓ 644.3K
|
_asset.safeTransferFrom(msg.sender, address(strategy), assets); |
|
| 166 | |||
| 167 |
// Single executor call: strategy handles fees and accounts on NET |
||
| 168 |
✓ 277.7K
|
strategy.handleOperations4626Mint(receiver, shares, assets, assetsNet); |
|
| 169 | |||
| 170 |
// Mint the exact shares asked |
||
| 171 |
✓ 270.7K
|
_mint(receiver, shares); |
|
| 172 | |||
| 173 |
✓ 270.7K
|
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 |
✓ 661.2K
|
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256) {
|
|
| 179 |
✓ 661.2K
|
⟲ 244.3K
|
if (shares == 0) revert ZERO_AMOUNT(); |
| 180 |
✓ 416.9K
|
if (owner == address(0) || controller == address(0)) revert ZERO_ADDRESS(); |
|
| 181 |
✓ 416.9K
|
if (owner != msg.sender && !isOperator[owner][msg.sender]) revert INVALID_OWNER_OR_OPERATOR(); |
|
| 182 |
✓ 416.9K
|
⟲ 148.5K
|
if (balanceOf(owner) < shares) revert INVALID_AMOUNT(); |
| 183 | |||
| 184 |
// Enforce auditor's invariant for current accounting model |
||
| 185 |
✓ 268.4K
|
if (controller != owner) revert CONTROLLER_MUST_EQUAL_OWNER(); |
|
| 186 | |||
| 187 |
// Transfer shares to escrow for temporary locking |
||
| 188 |
✓ 268.4K
|
_approve(owner, escrow, shares); |
|
| 189 |
✓ 268.4K
|
ISuperVaultEscrow(escrow).escrowShares(owner, shares); |
|
| 190 | |||
| 191 |
// Forward to strategy (7540 path) |
||
| 192 |
✓ 268.4K
|
strategy.handleOperations7540(ISuperVaultStrategy.Operation.RedeemRequest, controller, address(0), shares); |
|
| 193 | |||
| 194 |
✓ 268.3K
|
emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares); |
|
| 195 |
return REQUEST_ID; |
||
| 196 |
} |
||
| 197 | |||
| 198 |
/// @inheritdoc ISuperVault |
||
| 199 |
function cancelRedeem(address controller) external {
|
||
| 200 |
✓ 143.6K
|
_validateController(controller); |
|
| 201 | |||
| 202 |
✓ 143.6K
|
uint256 shares = strategy.pendingRedeemRequest(controller); |
|
| 203 | |||
| 204 |
// Forward to strategy (7540 path) |
||
| 205 |
✓ 143.6K
|
strategy.handleOperations7540(ISuperVaultStrategy.Operation.CancelRedeem, controller, address(0), 0); |
|
| 206 | |||
| 207 |
// Return shares to controller |
||
| 208 |
✓ 1.3K
|
ISuperVaultEscrow(escrow).returnShares(controller, shares); |
|
| 209 | |||
| 210 |
✓ 1.3K
|
emit RedeemRequestCancelled(controller, msg.sender); |
|
| 211 |
} |
||
| 212 | |||
| 213 |
/// @inheritdoc IERC7540Operator |
||
| 214 |
✓ 135.4K
|
function setOperator(address operator, bool approved) external returns (bool success) {
|
|
| 215 |
✓ 135.4K
|
⟲ 41.1K
|
if (msg.sender == operator) revert UNAUTHORIZED(); |
| 216 |
✓ 94.3K
|
isOperator[msg.sender][operator] = approved; |
|
| 217 |
✓ 94.3K
|
emit OperatorSet(msg.sender, operator, approved); |
|
| 218 |
✓ 94.3K
|
return true; |
|
| 219 |
} |
||
| 220 | |||
| 221 |
/// @inheritdoc IERC7741 |
||
| 222 |
function authorizeOperator( |
||
| 223 |
address controller, |
||
| 224 |
address operator, |
||
| 225 |
bool approved, |
||
| 226 |
bytes32 nonce, |
||
| 227 |
uint256 deadline, |
||
| 228 |
bytes memory signature |
||
| 229 |
) |
||
| 230 |
external |
||
| 231 |
returns (bool) |
||
| 232 |
{
|
||
| 233 |
if (controller == operator) revert UNAUTHORIZED(); |
||
| 234 |
if (block.timestamp > deadline) revert DEADLINE_PASSED(); |
||
| 235 |
if (_authorizations[controller][nonce]) revert UNAUTHORIZED(); |
||
| 236 | |||
| 237 |
_authorizations[controller][nonce] = true; |
||
| 238 | |||
| 239 |
bytes32 structHash = |
||
| 240 |
keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline)); |
||
| 241 |
bytes32 digest = _hashTypedDataV4(structHash); |
||
| 242 | |||
| 243 |
if (!_isValidSignature(controller, digest, signature)) revert INVALID_SIGNATURE(); |
||
| 244 | |||
| 245 |
isOperator[controller][operator] = approved; |
||
| 246 |
emit OperatorSet(controller, operator, approved); |
||
| 247 | |||
| 248 |
return true; |
||
| 249 |
} |
||
| 250 | |||
| 251 |
/*////////////////////////////////////////////////////////////// |
||
| 252 |
USER EXTERNAL VIEW FUNCTIONS |
||
| 253 |
//////////////////////////////////////////////////////////////*/ |
||
| 254 | |||
| 255 |
//--ERC7540-- |
||
| 256 | |||
| 257 |
/// @inheritdoc IERC7540Redeem |
||
| 258 |
function pendingRedeemRequest( |
||
| 259 |
uint256, /*requestId*/ |
||
| 260 |
address controller |
||
| 261 |
) |
||
| 262 |
external |
||
| 263 |
view |
||
| 264 |
✓ 15.2M
|
returns (uint256 pendingShares) |
|
| 265 |
{
|
||
| 266 |
✓ 15.2M
|
return strategy.pendingRedeemRequest(controller); |
|
| 267 |
} |
||
| 268 | |||
| 269 |
/// @inheritdoc IERC7540Redeem |
||
| 270 |
function claimableRedeemRequest( |
||
| 271 |
uint256, /*requestId*/ |
||
| 272 |
address controller |
||
| 273 |
) |
||
| 274 |
external |
||
| 275 |
view |
||
| 276 |
✓ 4.1M
|
returns (uint256 claimableShares) |
|
| 277 |
{
|
||
| 278 |
✓ 4.1M
|
return maxRedeem(controller); |
|
| 279 |
} |
||
| 280 | |||
| 281 |
//--Operator Management-- |
||
| 282 | |||
| 283 |
/// @inheritdoc IERC7741 |
||
| 284 |
function authorizations(address controller, bytes32 nonce) external view returns (bool used) {
|
||
| 285 |
return _authorizations[controller][nonce]; |
||
| 286 |
} |
||
| 287 | |||
| 288 |
/// @inheritdoc IERC7741 |
||
| 289 |
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
|
||
| 290 |
return _domainSeparatorV4(); |
||
| 291 |
} |
||
| 292 | |||
| 293 |
/// @inheritdoc IERC7741 |
||
| 294 |
function invalidateNonce(bytes32 nonce) external {
|
||
| 295 |
✓ 119.1K
|
⟲ 175
|
if (_authorizations[msg.sender][nonce]) revert INVALID_NONCE(); |
| 296 |
✓ 118.9K
|
_authorizations[msg.sender][nonce] = true; |
|
| 297 | |||
| 298 |
✓ 118.9K
|
emit NonceInvalidated(msg.sender, nonce); |
|
| 299 |
} |
||
| 300 | |||
| 301 |
/*////////////////////////////////////////////////////////////// |
||
| 302 |
ERC4626 IMPLEMENTATION |
||
| 303 |
//////////////////////////////////////////////////////////////*/ |
||
| 304 |
function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) {
|
||
| 305 |
✓ 140.8K
|
return _underlyingDecimals; |
|
| 306 |
} |
||
| 307 | |||
| 308 |
function asset() public view virtual override returns (address) {
|
||
| 309 |
✓ 11.2M
|
return address(_asset); |
|
| 310 |
} |
||
| 311 | |||
| 312 |
/// @inheritdoc IERC4626 |
||
| 313 |
✓ 3.9K
|
function totalAssets() external view override returns (uint256) {
|
|
| 314 |
✓ 3.9K
|
uint256 supply = totalSupply(); |
|
| 315 |
✓ 3.9K
|
if (supply == 0) return 0; |
|
| 316 |
✓ 220
|
uint256 currentPPS = _getStoredPPS(); |
|
| 317 |
✓ 220
|
return Math.mulDiv(supply, currentPPS, PRECISION, Math.Rounding.Floor); |
|
| 318 |
} |
||
| 319 | |||
| 320 |
/// @inheritdoc IERC4626 |
||
| 321 |
✓ 313.8K
|
function convertToShares(uint256 assets) public view override returns (uint256) {
|
|
| 322 |
✓ 313.8K
|
uint256 pps = _getStoredPPS(); |
|
| 323 |
✓ 313.8K
|
if (pps == 0) return 0; |
|
| 324 |
✓ 311.1K
|
return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor); |
|
| 325 |
} |
||
| 326 | |||
| 327 |
/// @inheritdoc IERC4626 |
||
| 328 |
✓ 8.1M
|
function convertToAssets(uint256 shares) public view override returns (uint256) {
|
|
| 329 |
✓ 8.1M
|
uint256 currentPPS = _getStoredPPS(); |
|
| 330 |
✓ 8.1M
|
if (currentPPS == 0) return 0; |
|
| 331 |
✓ 8.1M
|
return Math.mulDiv(shares, currentPPS, PRECISION, Math.Rounding.Floor); |
|
| 332 |
} |
||
| 333 | |||
| 334 |
/// @inheritdoc IERC4626 |
||
| 335 |
✓ 341.9K
|
function maxDeposit(address) public view override returns (uint256) {
|
|
| 336 |
✓ 341.9K
|
if (_isPaused()) return 0; |
|
| 337 |
✓ 332.3K
|
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 |
✓ 768.8K
|
function maxWithdraw(address owner) public view override returns (uint256) {
|
|
| 348 |
✓ 768.8K
|
return strategy.claimableWithdraw(owner); |
|
| 349 |
} |
||
| 350 | |||
| 351 |
/// @inheritdoc IERC4626 |
||
| 352 |
✓ 4.4M
|
function maxRedeem(address owner) public view override returns (uint256) {
|
|
| 353 |
✓ 4.4M
|
uint256 withdrawPrice = strategy.getAverageWithdrawPrice(owner); |
|
| 354 |
✓ 4.4M
|
if (withdrawPrice == 0) return 0; |
|
| 355 |
✓ 128.9K
|
return maxWithdraw(owner).mulDiv(PRECISION, withdrawPrice, Math.Rounding.Floor); |
|
| 356 |
} |
||
| 357 | |||
| 358 |
/// @inheritdoc IERC4626 |
||
| 359 |
✓ 966.7K
|
function previewDeposit(uint256 assets) public view override returns (uint256) {
|
|
| 360 |
✓ 966.7K
|
uint256 pps = _getStoredPPS(); |
|
| 361 |
✓ 965.8K
|
if (pps == 0) return 0; |
|
| 362 | |||
| 363 |
✓ 956.5K
|
(uint256 feeBps,) = _getManagementFeeConfig(); |
|
| 364 | |||
| 365 |
✓ 956.5K
|
if (feeBps == 0) return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor); |
|
| 366 |
// fee-on-gross: fee = ceil(gross * feeBps / BPS) |
||
| 367 |
✓ 929.4K
|
uint256 fee = Math.mulDiv(assets, feeBps, BPS_PRECISION, Math.Rounding.Ceil); |
|
| 368 | |||
| 369 |
✓ 929.4K
|
uint256 assetsNet = assets - fee; |
|
| 370 |
✓ 929.4K
|
return Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
|
| 371 |
} |
||
| 372 | |||
| 373 |
/// @inheritdoc IERC4626 |
||
| 374 |
✓ 1.4M
|
function previewMint(uint256 shares) public view override returns (uint256) {
|
|
| 375 |
✓ 1.4M
|
uint256 pps = _getStoredPPS(); |
|
| 376 |
✓ 1.4M
|
if (pps == 0) return 0; |
|
| 377 | |||
| 378 |
✓ 1.4M
|
uint256 assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
|
| 379 | |||
| 380 |
✓ 1.3M
|
(uint256 feeBps,) = _getManagementFeeConfig(); |
|
| 381 |
✓ 1.3M
|
if (feeBps == 0) return assetsNet; |
|
| 382 |
✓ 1.3M
|
if (feeBps >= BPS_PRECISION) return 0; // impossible to mint (would require infinite gross) |
|
| 383 | |||
| 384 |
✓ 1.2M
|
return Math.mulDiv(assetsNet, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil); |
|
| 385 |
} |
||
| 386 | |||
| 387 |
/// @inheritdoc IERC4626 |
||
| 388 |
✓ 1.8K
|
function previewWithdraw(uint256 /* assets*/ ) public pure override returns (uint256) {
|
|
| 389 |
✓ 1.8K
|
⟲ 1.8K
|
revert NOT_IMPLEMENTED(); |
| 390 |
} |
||
| 391 | |||
| 392 |
/// @inheritdoc IERC4626 |
||
| 393 |
function previewRedeem(uint256 /* shares*/ ) public pure override returns (uint256) {
|
||
| 394 |
revert NOT_IMPLEMENTED(); |
||
| 395 |
} |
||
| 396 | |||
| 397 |
/// @inheritdoc IERC4626 |
||
| 398 |
function withdraw( |
||
| 399 |
uint256 assets, |
||
| 400 |
address receiver, |
||
| 401 |
address controller |
||
| 402 |
) |
||
| 403 |
public |
||
| 404 |
override |
||
| 405 |
nonReentrant |
||
| 406 |
✓ 412.2K
|
returns (uint256 shares) |
|
| 407 |
{
|
||
| 408 |
✓ 412.2K
|
⟲ 4.5K
|
if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 409 |
✓ 407.7K
|
_validateController(controller); |
|
| 410 | |||
| 411 |
✓ 406.7K
|
uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
|
| 412 |
✓ 406.7K
|
⟲ 399.6K
|
if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE(); |
| 413 | |||
| 414 |
✓ 7.1K
|
uint256 maxWithdrawAmount = maxWithdraw(controller); |
|
| 415 |
✓ 7.1K
|
⟲ 449
|
if (assets > maxWithdrawAmount) revert INVALID_AMOUNT(); |
| 416 | |||
| 417 |
// Calculate shares based on assets and average withdraw price |
||
| 418 |
✓ 6.7K
|
shares = assets.mulDiv(PRECISION, averageWithdrawPrice, Math.Rounding.Floor); |
|
| 419 | |||
| 420 |
// Take assets from strategy (7540 path) |
||
| 421 |
✓ 6.7K
|
strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
|
| 422 | |||
| 423 |
✓ 6.3K
|
emit Withdraw(msg.sender, receiver, controller, assets, shares); |
|
| 424 |
} |
||
| 425 | |||
| 426 |
/// @inheritdoc IERC4626 |
||
| 427 |
function redeem( |
||
| 428 |
uint256 shares, |
||
| 429 |
address receiver, |
||
| 430 |
address controller |
||
| 431 |
) |
||
| 432 |
public |
||
| 433 |
override |
||
| 434 |
nonReentrant |
||
| 435 |
✓ 608.6K
|
returns (uint256 assets) |
|
| 436 |
{
|
||
| 437 |
✓ 608.6K
|
⟲ 987
|
if (receiver == address(0)) revert ZERO_ADDRESS(); |
| 438 |
✓ 607.6K
|
_validateController(controller); |
|
| 439 | |||
| 440 |
✓ 606.6K
|
uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller); |
|
| 441 |
✓ 606.6K
|
⟲ 585.7K
|
if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE(); |
| 442 | |||
| 443 |
// Calculate assets based on shares and average withdraw price |
||
| 444 |
✓ 20.9K
|
assets = shares.mulDiv(averageWithdrawPrice, PRECISION, Math.Rounding.Floor); |
|
| 445 | |||
| 446 |
✓ 20.9K
|
uint256 maxWithdrawAmount = maxWithdraw(controller); |
|
| 447 |
✓ 20.9K
|
⟲ 418
|
if (assets > maxWithdrawAmount) revert INVALID_AMOUNT(); |
| 448 | |||
| 449 |
// Take assets from strategy (7540 path) |
||
| 450 |
✓ 20.5K
|
strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets); |
|
| 451 | |||
| 452 |
✓ 19.3K
|
emit Withdraw(msg.sender, receiver, controller, assets, shares); |
|
| 453 |
} |
||
| 454 | |||
| 455 |
// @inheritdoc ISuperVault |
||
| 456 |
function burnShares(uint256 amount) external {
|
||
| 457 |
✓ 257.6K
|
⟲ 133.2K
|
if (msg.sender != address(strategy)) revert UNAUTHORIZED(); |
| 458 |
✓ 124.4K
|
_burn(escrow, amount); |
|
| 459 |
} |
||
| 460 | |||
| 461 |
// @inheritdoc ISuperVault |
||
| 462 |
function onRedeemClaimable( |
||
| 463 |
address user, |
||
| 464 |
uint256 assets, |
||
| 465 |
uint256 shares, |
||
| 466 |
uint256 averageWithdrawPrice, |
||
| 467 |
uint256 accumulatorShares, |
||
| 468 |
uint256 accumulatorCostBasis |
||
| 469 |
) |
||
| 470 |
external |
||
| 471 |
{
|
||
| 472 |
✓ 124.4K
|
if (msg.sender != address(strategy)) revert UNAUTHORIZED(); |
|
| 473 |
✓ 124.4K
|
emit RedeemClaimable( |
|
| 474 |
user, REQUEST_ID, assets, shares, averageWithdrawPrice, accumulatorShares, accumulatorCostBasis |
||
| 475 |
); |
||
| 476 |
} |
||
| 477 | |||
| 478 |
/*////////////////////////////////////////////////////////////// |
||
| 479 |
ERC165 INTERFACE |
||
| 480 |
//////////////////////////////////////////////////////////////*/ |
||
| 481 |
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
|
||
| 482 |
return interfaceId == type(IERC7540Redeem).interfaceId || interfaceId == type(IERC165).interfaceId |
||
| 483 |
|| interfaceId == type(IERC7741).interfaceId || interfaceId == type(IERC4626).interfaceId |
||
| 484 |
|| interfaceId == type(IERC7575).interfaceId || interfaceId == type(IERC7540Operator).interfaceId; |
||
| 485 |
} |
||
| 486 | |||
| 487 |
/*////////////////////////////////////////////////////////////// |
||
| 488 |
INTERNAL FUNCTIONS |
||
| 489 |
//////////////////////////////////////////////////////////////*/ |
||
| 490 |
/// @notice Validates that the controller is the msg.sender or has been authorized by the controller |
||
| 491 |
/// @param controller The controller to validate |
||
| 492 |
function _validateController(address controller) internal view {
|
||
| 493 |
✓ 1.2M
|
⟲ 2.0K
|
if (controller != msg.sender && !isOperator[controller][msg.sender]) revert INVALID_CONTROLLER(); |
| 494 |
} |
||
| 495 | |||
| 496 |
/// @notice Verify an EIP712 signature using OpenZeppelin's ECDSA library |
||
| 497 |
/// @param signer The signer to verify |
||
| 498 |
/// @param digest The digest to verify |
||
| 499 |
/// @param signature The signature to verify |
||
| 500 |
function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal pure returns (bool) {
|
||
| 501 |
address recoveredSigner = ECDSA.recover(digest, signature); |
||
| 502 |
return recoveredSigner == signer; |
||
| 503 |
} |
||
| 504 | |||
| 505 |
/// @notice Overrides the ERC20 _update function to update the state of the vault when a transfer occurs |
||
| 506 |
/// @param from The address of the sender |
||
| 507 |
/// @param to The address of the recipient |
||
| 508 |
/// @param value The amount of shares being transferred |
||
| 509 |
function _update(address from, address to, uint256 value) internal override(ERC20Upgradeable) {
|
||
| 510 |
/// @dev Move only accumulators pro-rata between actual users, not to/from infrastructure contracts |
||
| 511 |
✓ 894.2K
|
if (from != address(0) && to != address(0) && to != address(escrow) && from != address(escrow)) {
|
|
| 512 |
✓ 146.7K
|
uint256 shares = value; |
|
| 513 |
// Zero-value transfers are legal: treat as accounting no-op. |
||
| 514 |
✓ 146.7K
|
if (shares > 0) {
|
|
| 515 |
✓ 143.3K
|
strategy.moveAccumulatorOnTransfer(from, to, shares); |
|
| 516 |
} |
||
| 517 |
} |
||
| 518 |
✓ 892.4K
|
super._update(from, to, value); |
|
| 519 |
} |
||
| 520 | |||
| 521 |
✓ 10.8M
|
function _getStoredPPS() internal view returns (uint256) {
|
|
| 522 |
✓ 10.8M
|
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 |
✓ 341.9K
|
function _isPaused() internal view returns (bool) {
|
|
| 529 |
✓ 341.9K
|
address aggregatorAddress = superGovernor.getAddress(superGovernor.SUPER_VAULT_AGGREGATOR()); |
|
| 530 |
✓ 341.9K
|
return ISuperVaultAggregator(aggregatorAddress).isStrategyPaused(address(strategy)); |
|
| 531 |
} |
||
| 532 | |||
| 533 |
/// @dev Read management fee config (view-only for previews) |
||
| 534 |
✓ 2.2M
|
function _getManagementFeeConfig() internal view returns (uint256 feeBps, address recipient) {
|
|
| 535 |
✓ 2.2M
|
ISuperVaultStrategy.FeeConfig memory cfg = strategy.getConfigInfo(); |
|
| 536 |
✓ 2.2M
|
return (cfg.managementFeeBps, cfg.recipient); |
|
| 537 |
} |
||
| 538 |
} |
||
| 539 |
51.0%
src/SuperVault/SuperVaultAggregator.sol
Lines covered: 265 / 520 (51.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// External |
||
| 5 |
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
||
| 6 |
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
| 7 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 8 |
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
||
| 9 |
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||
| 10 |
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
||
| 11 | |||
| 12 |
// Superform |
||
| 13 |
import {SuperVault} from "./SuperVault.sol";
|
||
| 14 |
import {SuperVaultStrategy} from "./SuperVaultStrategy.sol";
|
||
| 15 |
import {SuperVaultEscrow} from "./SuperVaultEscrow.sol";
|
||
| 16 |
import {ISuperGovernor} from "../interfaces/ISuperGovernor.sol";
|
||
| 17 |
import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
|
||
| 18 |
// Libraries |
||
| 19 |
import {AssetMetadataLib} from "../libraries/AssetMetadataLib.sol";
|
||
| 20 | |||
| 21 |
/// @title SuperVaultAggregator |
||
| 22 |
/// @author Superform Labs |
||
| 23 |
/// @notice Registry and PPS oracle for all SuperVaults |
||
| 24 |
/// @dev Creates new SuperVault trios and manages PPS updates |
||
| 25 |
contract SuperVaultAggregator is ISuperVaultAggregator {
|
||
| 26 |
using AssetMetadataLib for address; |
||
| 27 |
using Clones for address; |
||
| 28 |
using SafeERC20 for IERC20; |
||
| 29 |
using Math for uint256; |
||
| 30 |
using EnumerableSet for EnumerableSet.AddressSet; |
||
| 31 | |||
| 32 |
/*////////////////////////////////////////////////////////////// |
||
| 33 |
STORAGE |
||
| 34 |
//////////////////////////////////////////////////////////////*/ |
||
| 35 |
// Vault implementation contracts |
||
| 36 |
address public immutable VAULT_IMPLEMENTATION; |
||
| 37 |
address public immutable STRATEGY_IMPLEMENTATION; |
||
| 38 |
address public immutable ESCROW_IMPLEMENTATION; |
||
| 39 | |||
| 40 |
// Governance |
||
| 41 |
ISuperGovernor public immutable SUPER_GOVERNOR; |
||
| 42 | |||
| 43 |
// Claimable upkeep |
||
| 44 |
✓ 20.1M
|
uint256 public claimableUpkeep; |
|
| 45 | |||
| 46 |
// Strategy data storage |
||
| 47 |
mapping(address strategy => StrategyData) private _strategyData; |
||
| 48 | |||
| 49 |
// Upkeep balances |
||
| 50 |
mapping(address manager => uint256 upkeep) private _managerUpkeepBalance; |
||
| 51 | |||
| 52 |
// Stake balances |
||
| 53 |
mapping(address manager => uint256 stake) private _managerStakeBalance; |
||
| 54 | |||
| 55 |
// Registry of created vaults |
||
| 56 |
EnumerableSet.AddressSet private _superVaults; |
||
| 57 |
EnumerableSet.AddressSet private _superVaultStrategies; |
||
| 58 |
EnumerableSet.AddressSet private _superVaultEscrows; |
||
| 59 | |||
| 60 |
// Constant for PPS decimals |
||
| 61 |
uint256 public constant PPS_DECIMALS = 18; |
||
| 62 | |||
| 63 |
// Maximum number of secondary managers per strategy to prevent governance DoS on manager replacement |
||
| 64 |
✓ 109.7K
|
uint256 public constant MAX_SECONDARY_MANAGERS = 5; |
|
| 65 | |||
| 66 |
// Maximum number of strategies to process in `batchForwardPPS` |
||
| 67 |
✓ 260.5K
|
uint256 public constant MAX_STRATEGIES = 300; |
|
| 68 | |||
| 69 |
// Timelock for manager changes and Merkle root updates |
||
| 70 |
uint256 private constant _MANAGER_CHANGE_TIMELOCK = 7 days; |
||
| 71 |
✓ 1
|
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 |
✓ 260.5K
|
if (!SUPER_GOVERNOR.isActivePPSOracle(msg.sender)) {
|
|
| 88 |
revert UNAUTHORIZED_PPS_ORACLE(); |
||
| 89 |
} |
||
| 90 |
_; |
||
| 91 |
} |
||
| 92 | |||
| 93 |
/// @notice Validates that a strategy exists (has been created by this aggregator) |
||
| 94 |
modifier validStrategy(address strategy) {
|
||
| 95 |
✓ 16.3M
|
if (!_superVaultStrategies.contains(strategy)) |
|
| 96 |
✓ 140.2K
|
⟲ 975.7K
|
revert UNKNOWN_STRATEGY(); |
| 97 |
✓ 4.2K
|
_; |
|
| 98 |
} |
||
| 99 | |||
| 100 |
/*////////////////////////////////////////////////////////////// |
||
| 101 |
CONSTRUCTOR |
||
| 102 |
//////////////////////////////////////////////////////////////*/ |
||
| 103 |
/// @notice Initializes the SuperVaultAggregator |
||
| 104 |
/// @param superGovernor_ Address of the SuperGovernor contract |
||
| 105 |
/// @param vaultImpl_ Address of the pre-deployed SuperVault implementation |
||
| 106 |
/// @param strategyImpl_ Address of the pre-deployed SuperVaultStrategy implementation |
||
| 107 |
/// @param escrowImpl_ Address of the pre-deployed SuperVaultEscrow implementation |
||
| 108 |
constructor( |
||
| 109 |
address superGovernor_, |
||
| 110 |
address vaultImpl_, |
||
| 111 |
address strategyImpl_, |
||
| 112 |
address escrowImpl_ |
||
| 113 |
) {
|
||
| 114 |
✓ 1
|
if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
|
| 115 |
✓ 1
|
if (vaultImpl_ == address(0)) revert ZERO_ADDRESS(); |
|
| 116 |
✓ 1
|
if (strategyImpl_ == address(0)) revert ZERO_ADDRESS(); |
|
| 117 |
✓ 1
|
if (escrowImpl_ == address(0)) revert ZERO_ADDRESS(); |
|
| 118 | |||
| 119 |
✓ 1
|
SUPER_GOVERNOR = ISuperGovernor(superGovernor_); |
|
| 120 |
✓ 1
|
VAULT_IMPLEMENTATION = vaultImpl_; |
|
| 121 |
✓ 1
|
STRATEGY_IMPLEMENTATION = strategyImpl_; |
|
| 122 |
✓ 1
|
ESCROW_IMPLEMENTATION = escrowImpl_; |
|
| 123 |
} |
||
| 124 | |||
| 125 |
/*////////////////////////////////////////////////////////////// |
||
| 126 |
VAULT CREATION |
||
| 127 |
//////////////////////////////////////////////////////////////*/ |
||
| 128 |
/// @inheritdoc ISuperVaultAggregator |
||
| 129 |
function createVault( |
||
| 130 |
VaultCreationParams calldata params |
||
| 131 |
✓ 232.6K
|
) external returns (address superVault, address strategy, address escrow) {
|
|
| 132 |
// Input validation |
||
| 133 |
if ( |
||
| 134 |
✓ 232.6K
|
params.asset == address(0) || |
|
| 135 |
✓ 231.6K
|
params.mainManager == address(0) || |
|
| 136 |
✓ 230.8K
|
params.feeConfig.recipient == address(0) |
|
| 137 |
) {
|
||
| 138 |
✓ 4.6K
|
⟲ 4.6K
|
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 |
✓ 228.0K
|
vars.currentNonce = _vaultCreationNonce++; |
|
| 146 |
✓ 228.0K
|
vars.salt = keccak256( |
|
| 147 |
abi.encode( |
||
| 148 |
✓ 228.0K
|
msg.sender, |
|
| 149 |
✓ 228.0K
|
params.asset, |
|
| 150 |
✓ 228.0K
|
params.name, |
|
| 151 |
✓ 228.0K
|
params.symbol, |
|
| 152 |
✓ 228.0K
|
vars.currentNonce |
|
| 153 |
) |
||
| 154 |
); |
||
| 155 | |||
| 156 |
// Create minimal proxies |
||
| 157 |
✓ 228.0K
|
superVault = VAULT_IMPLEMENTATION.cloneDeterministic(vars.salt); |
|
| 158 |
✓ 228.0K
|
escrow = ESCROW_IMPLEMENTATION.cloneDeterministic(vars.salt); |
|
| 159 |
✓ 228.0K
|
strategy = STRATEGY_IMPLEMENTATION.cloneDeterministic(vars.salt); |
|
| 160 | |||
| 161 |
// Initialize superVault |
||
| 162 |
✓ 228.0K
|
SuperVault(superVault).initialize( |
|
| 163 |
✓ 228.0K
|
params.asset, |
|
| 164 |
✓ 228.0K
|
params.name, |
|
| 165 |
✓ 228.0K
|
params.symbol, |
|
| 166 |
✓ 228.0K
|
strategy, |
|
| 167 |
✓ 228.0K
|
escrow |
|
| 168 |
); |
||
| 169 | |||
| 170 |
// Initialize escrow |
||
| 171 |
✓ 122.2K
|
SuperVaultEscrow(escrow).initialize(superVault, strategy); |
|
| 172 | |||
| 173 |
// Initialize strategy |
||
| 174 |
✓ 122.2K
|
SuperVaultStrategy(payable(strategy)).initialize( |
|
| 175 |
✓ 122.2K
|
superVault, |
|
| 176 |
✓ 122.2K
|
params.feeConfig |
|
| 177 |
); |
||
| 178 | |||
| 179 |
// Store vault trio in registry |
||
| 180 |
✓ 112.6K
|
_superVaults.add(superVault); |
|
| 181 |
✓ 112.6K
|
_superVaultStrategies.add(strategy); |
|
| 182 |
✓ 112.6K
|
_superVaultEscrows.add(escrow); |
|
| 183 | |||
| 184 |
// Get asset decimals |
||
| 185 |
✓ 112.6K
|
(bool success, uint8 assetDecimals) = params |
|
| 186 |
.asset |
||
| 187 |
.tryGetAssetDecimals(); |
||
| 188 |
✓ 112.6K
|
if (!success) revert INVALID_ASSET(); |
|
| 189 |
✓ 112.6K
|
vars.initialPPS = 10 ** assetDecimals; // 1.0 as initial PPS |
|
| 190 | |||
| 191 |
// Validate maxStaleness against minimum required staleness |
||
| 192 |
✓ 112.6K
|
if (params.maxStaleness < SUPER_GOVERNOR.getMinStaleness()) {
|
|
| 193 |
✓ 2.9K
|
⟲ 2.9K
|
revert MAX_STALENESS_TOO_LOW(); |
| 194 |
} |
||
| 195 | |||
| 196 |
// Initialize StrategyData individually to avoid mapping assignment issues |
||
| 197 |
✓ 109.7K
|
_strategyData[strategy].pps = vars.initialPPS; |
|
| 198 |
// Initialize standard deviation to 0 |
||
| 199 |
✓ 109.7K
|
_strategyData[strategy].lastUpdateTimestamp = block.timestamp; |
|
| 200 |
✓ 109.7K
|
_strategyData[strategy].minUpdateInterval = params.minUpdateInterval; |
|
| 201 |
✓ 109.7K
|
_strategyData[strategy].maxStaleness = params.maxStaleness; |
|
| 202 |
✓ 109.7K
|
_strategyData[strategy].isPaused = false; |
|
| 203 |
✓ 109.7K
|
_strategyData[strategy].mainManager = params.mainManager; |
|
| 204 | |||
| 205 |
✓ 109.7K
|
uint256 secondaryLen = params.secondaryManagers.length; |
|
| 206 |
✓ 242.9K
|
for (uint256 i; i < secondaryLen; ++i) {
|
|
| 207 |
✓ 133.3K
|
_strategyData[strategy].secondaryManagers.add( |
|
| 208 |
✓ 133.3K
|
params.secondaryManagers[i] |
|
| 209 |
); |
||
| 210 |
} |
||
| 211 |
if ( |
||
| 212 |
✓ 109.7K
|
_strategyData[strategy].secondaryManagers.length() >= |
|
| 213 |
MAX_SECONDARY_MANAGERS |
||
| 214 |
) {
|
||
| 215 |
✓ 221
|
⟲ 221
|
revert TOO_MANY_SECONDARY_MANAGERS(); |
| 216 |
} |
||
| 217 | |||
| 218 |
// Set default threshold values |
||
| 219 |
✓ 109.4K
|
_strategyData[strategy].dispersionThreshold = type(uint256).max; // Default: max (disabled) |
|
| 220 |
✓ 109.4K
|
_strategyData[strategy].deviationThreshold = type(uint256).max; // Default: max (disabled) |
|
| 221 | |||
| 222 |
emit VaultDeployed( |
||
| 223 |
superVault, |
||
| 224 |
strategy, |
||
| 225 |
✓ 109.4K
|
escrow, |
|
| 226 |
✓ 109.4K
|
params.asset, |
|
| 227 |
✓ 109.4K
|
params.name, |
|
| 228 |
✓ 109.4K
|
params.symbol, |
|
| 229 |
✓ 109.4K
|
vars.currentNonce |
|
| 230 |
); |
||
| 231 |
emit PPSUpdated( |
||
| 232 |
strategy, |
||
| 233 |
✓ 109.4K
|
vars.initialPPS, |
|
| 234 |
✓ 109.4K
|
0, |
|
| 235 |
0, |
||
| 236 |
0, |
||
| 237 |
✓ 109.4K
|
_strategyData[strategy].lastUpdateTimestamp |
|
| 238 |
); |
||
| 239 | |||
| 240 |
✓ 109.4K
|
return (superVault, strategy, escrow); |
|
| 241 |
} |
||
| 242 | |||
| 243 |
/*////////////////////////////////////////////////////////////// |
||
| 244 |
PPS UPDATE FUNCTIONS |
||
| 245 |
//////////////////////////////////////////////////////////////*/ |
||
| 246 |
/// @inheritdoc ISuperVaultAggregator |
||
| 247 |
function forwardPPS(ForwardPPSArgs calldata args) external onlyPPSOracle {
|
||
| 248 |
✓ 260.5K
|
uint256 strategiesLength = args.strategies.length; |
|
| 249 |
✓ 260.5K
|
if (strategiesLength > MAX_STRATEGIES) revert MAX_STRATEGIES_EXCEEDED(); |
|
| 250 | |||
| 251 |
✓ 260.5K
|
⟲ 798
|
if (strategiesLength == 0) revert ZERO_ARRAY_LENGTH(); |
| 252 |
// Validate input array lengths |
||
| 253 |
if ( |
||
| 254 |
✓ 259.7K
|
strategiesLength != args.ppss.length || |
|
| 255 |
✓ 124.8K
|
strategiesLength != args.ppsStdevs.length || |
|
| 256 |
✓ 124.0K
|
strategiesLength != args.validatorSets.length || |
|
| 257 |
✓ 124.0K
|
strategiesLength != args.timestamps.length || |
|
| 258 |
✓ 124.0K
|
strategiesLength != args.totalValidators.length |
|
| 259 |
✓ 135.8K
|
⟲ 135.8K
|
) revert ARRAY_LENGTH_MISMATCH(); |
| 260 | |||
| 261 |
✓ 124.0K
|
bool paymentsEnabled = SUPER_GOVERNOR.isUpkeepPaymentsEnabled(); |
|
| 262 |
✓ 124.0K
|
uint256 chargeableCount; |
|
| 263 |
✓ 124.0K
|
if (paymentsEnabled) {
|
|
| 264 |
✓ 23.0K
|
for (uint256 i; i < strategiesLength; ++i) {
|
|
| 265 |
// Skip invalid strategies without reverting |
||
| 266 |
✓ 124.0K
|
if (!_superVaultStrategies.contains(args.strategies[i])) {
|
|
| 267 |
emit UnknownStrategy(args.strategies[i]); |
||
| 268 |
continue; |
||
| 269 |
} |
||
| 270 | |||
| 271 |
// Skip when invalid timestamp is provided |
||
| 272 |
✓ 11.5K
|
if (args.timestamps[i] > block.timestamp) {
|
|
| 273 |
emit ProvidedTimestampExceedsBlockTimestamp( |
||
| 274 |
args.strategies[i], |
||
| 275 |
args.timestamps[i], |
||
| 276 |
block.timestamp |
||
| 277 |
); |
||
| 278 |
continue; |
||
| 279 |
} |
||
| 280 | |||
| 281 |
// Skip Superform manager |
||
| 282 |
✓ 11.5K
|
address manager = _strategyData[args.strategies[i]].mainManager; |
|
| 283 |
✓ 11.5K
|
if (SUPER_GOVERNOR.isSuperformManager(manager)) {
|
|
| 284 |
emit SuperformManager(args.strategies[i], manager); |
||
| 285 |
continue; |
||
| 286 |
} |
||
| 287 | |||
| 288 |
// Skip if updateAuthority is in the authorized callers list |
||
| 289 |
// These are manager-designated keepers that should be exempt from fees |
||
| 290 |
// NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller) |
||
| 291 |
/// @dev: cannot underflow; it's checked above already and it skips the entry if that's the case |
||
| 292 |
if ( |
||
| 293 |
✓ 11.5K
|
_strategyData[args.strategies[i]] |
|
| 294 |
.authorizedCallers |
||
| 295 |
✓ 11.5K
|
.contains(args.updateAuthority) |
|
| 296 |
) {
|
||
| 297 |
emit AuthorizedCaller( |
||
| 298 |
args.strategies[i], |
||
| 299 |
args.updateAuthority |
||
| 300 |
); |
||
| 301 |
continue; |
||
| 302 |
} |
||
| 303 | |||
| 304 |
// Count only non-stale entries as chargeable |
||
| 305 |
if ( |
||
| 306 |
✓ 11.5K
|
block.timestamp - args.timestamps[i] <= |
|
| 307 |
✓ 11.5K
|
_strategyData[args.strategies[i]].maxStaleness |
|
| 308 |
) {
|
||
| 309 |
✓ 11.5K
|
++chargeableCount; |
|
| 310 |
} |
||
| 311 |
} |
||
| 312 |
} |
||
| 313 | |||
| 314 |
///@dev Total upkeep cost is determined by the oracle based on the number of chargeable entries |
||
| 315 |
✓ 124.0K
|
uint256 totalCost = paymentsEnabled |
|
| 316 |
✓ 11.5K
|
? SUPER_GOVERNOR.getUpkeepCostPerBatchUpdate( |
|
| 317 |
✓ 11.5K
|
msg.sender, |
|
| 318 |
chargeableCount |
||
| 319 |
) |
||
| 320 |
✓ 112.5K
|
: 0; |
|
| 321 | |||
| 322 |
// Compute per-entry charge |
||
| 323 |
✓ 112.5K
|
uint256 perEntry = 0; |
|
| 324 |
✓ 112.5K
|
if (paymentsEnabled && chargeableCount > 0) {
|
|
| 325 |
perEntry = totalCost / chargeableCount; |
||
| 326 |
} |
||
| 327 | |||
| 328 |
// Process all valid strategies |
||
| 329 |
✓ 225.0K
|
for (uint256 i; i < strategiesLength; ++i) {
|
|
| 330 |
// Skip invalid strategies without reverting |
||
| 331 |
✓ 112.5K
|
if (!_superVaultStrategies.contains(args.strategies[i])) continue; |
|
| 332 | |||
| 333 |
// Skip when invalid timestamp is provided (future timestamp) |
||
| 334 |
✓ 112.5K
|
if (args.timestamps[i] > block.timestamp) {
|
|
| 335 |
emit ProvidedTimestampExceedsBlockTimestamp( |
||
| 336 |
args.strategies[i], |
||
| 337 |
args.timestamps[i], |
||
| 338 |
block.timestamp |
||
| 339 |
); |
||
| 340 |
continue; |
||
| 341 |
} |
||
| 342 | |||
| 343 |
✓ 112.5K
|
uint256 upkeepCost; |
|
| 344 |
✓ 112.5K
|
if (paymentsEnabled) {
|
|
| 345 |
// check exemption due to staleness of a given strategy |
||
| 346 |
/// @dev cannot underflow as it's already checked above, in the previous `for` loop |
||
| 347 |
if ( |
||
| 348 |
block.timestamp - args.timestamps[i] > |
||
| 349 |
_strategyData[args.strategies[i]].maxStaleness |
||
| 350 |
) {
|
||
| 351 |
upkeepCost = 0; |
||
| 352 |
emit StaleUpdate( |
||
| 353 |
args.strategies[i], |
||
| 354 |
args.updateAuthority, |
||
| 355 |
args.timestamps[i] |
||
| 356 |
); |
||
| 357 |
} else {
|
||
| 358 |
address manager = _strategyData[args.strategies[i]] |
||
| 359 |
.mainManager; |
||
| 360 |
if ( |
||
| 361 |
SUPER_GOVERNOR.isSuperformManager(manager) || |
||
| 362 |
_strategyData[args.strategies[i]] |
||
| 363 |
.authorizedCallers |
||
| 364 |
.contains(args.updateAuthority) |
||
| 365 |
) {
|
||
| 366 |
upkeepCost = 0; |
||
| 367 |
} else {
|
||
| 368 |
// Split the total batch cost fairly across chargeable entries |
||
| 369 |
upkeepCost = perEntry; |
||
| 370 |
} |
||
| 371 |
} |
||
| 372 |
} |
||
| 373 | |||
| 374 |
// Forward update |
||
| 375 |
✓ 112.5K
|
_forwardPPS( |
|
| 376 |
PPSUpdateData({
|
||
| 377 |
✓ 112.5K
|
strategy: args.strategies[i], |
|
| 378 |
✓ 112.5K
|
isExempt: (!paymentsEnabled) || (upkeepCost == 0), // If payments are disabled or the update is exempt from UP payments |
|
| 379 |
✓ 112.5K
|
pps: args.ppss[i], |
|
| 380 |
✓ 112.5K
|
ppsStdev: args.ppsStdevs[i], |
|
| 381 |
✓ 112.5K
|
validatorSet: args.validatorSets[i], |
|
| 382 |
✓ 112.5K
|
totalValidators: args.totalValidators[i], |
|
| 383 |
✓ 112.5K
|
timestamp: args.timestamps[i], |
|
| 384 |
✓ 112.5K
|
upkeepCost: upkeepCost |
|
| 385 |
}) |
||
| 386 |
); |
||
| 387 |
} |
||
| 388 |
} |
||
| 389 | |||
| 390 |
/*////////////////////////////////////////////////////////////// |
||
| 391 |
UPKEEP MANAGEMENT |
||
| 392 |
//////////////////////////////////////////////////////////////*/ |
||
| 393 |
/// @inheritdoc ISuperVaultAggregator |
||
| 394 |
function depositUpkeep(address manager, uint256 amount) external {
|
||
| 395 |
✓ 121.9K
|
⟲ 1.7K
|
if (amount == 0) revert ZERO_AMOUNT(); |
| 396 | |||
| 397 |
// Get the UP token address from SUPER_GOVERNOR |
||
| 398 |
✓ 120.2K
|
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
|
| 399 | |||
| 400 |
// Transfer UP tokens from msg.sender to this contract |
||
| 401 |
✓ 120.2K
|
IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount); |
|
| 402 | |||
| 403 |
// Update upkeep balance |
||
| 404 |
✓ 31.4K
|
_managerUpkeepBalance[manager] += amount; |
|
| 405 | |||
| 406 |
✓ 31.4K
|
emit UpkeepDeposited(manager, amount); |
|
| 407 |
} |
||
| 408 | |||
| 409 |
/// @inheritdoc ISuperVaultAggregator |
||
| 410 |
function claimUpkeep(uint256 amount) external {
|
||
| 411 |
// Only SUPER_GOVERNOR can claim upkeep |
||
| 412 |
✓ 275.6K
|
if (msg.sender != address(SUPER_GOVERNOR)) {
|
|
| 413 |
✓ 142.9K
|
⟲ 142.9K
|
revert CALLER_NOT_AUTHORIZED(); |
| 414 |
} |
||
| 415 | |||
| 416 |
✓ 132.7K
|
⟲ 131.1K
|
if (claimableUpkeep < amount) revert INSUFFICIENT_UPKEEP(); |
| 417 |
✓ 1.6K
|
claimableUpkeep -= amount; |
|
| 418 | |||
| 419 |
// Get the UP token address from SUPER_GOVERNOR |
||
| 420 |
✓ 1.6K
|
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
|
| 421 | |||
| 422 |
// Transfer UP tokens to `SuperBank` |
||
| 423 |
✓ 1.6K
|
address _superBank = _getSuperBank(); |
|
| 424 |
✓ 1.6K
|
IERC20(upToken).safeTransfer(_superBank, amount); |
|
| 425 |
✓ 1.6K
|
emit UpkeepClaimed(_superBank, amount); |
|
| 426 |
} |
||
| 427 | |||
| 428 |
/// @inheritdoc ISuperVaultAggregator |
||
| 429 |
function withdrawUpkeep(uint256 amount) external {
|
||
| 430 |
✓ 120.3K
|
⟲ 1.6K
|
if (amount == 0) revert ZERO_AMOUNT(); |
| 431 | |||
| 432 |
// Check sufficient balance |
||
| 433 |
✓ 118.7K
|
if (_managerUpkeepBalance[msg.sender] < amount) {
|
|
| 434 |
✓ 118.0K
|
⟲ 118.0K
|
revert INSUFFICIENT_UPKEEP_BALANCE(); |
| 435 |
} |
||
| 436 | |||
| 437 |
// Get the UP token address from SUPER_GOVERNOR |
||
| 438 |
✓ 664
|
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
|
| 439 | |||
| 440 |
// Update upkeep balance |
||
| 441 |
unchecked {
|
||
| 442 |
✓ 664
|
_managerUpkeepBalance[msg.sender] -= amount; |
|
| 443 |
} |
||
| 444 | |||
| 445 |
// Transfer UP tokens to manager |
||
| 446 |
✓ 664
|
IERC20(upToken).safeTransfer(msg.sender, amount); |
|
| 447 | |||
| 448 |
✓ 664
|
emit UpkeepWithdrawn(msg.sender, amount); |
|
| 449 |
} |
||
| 450 | |||
| 451 |
/*////////////////////////////////////////////////////////////// |
||
| 452 |
STAKE MANAGEMENT |
||
| 453 |
//////////////////////////////////////////////////////////////*/ |
||
| 454 |
/// @notice Deposits UP tokens as stake for manager economic security |
||
| 455 |
/// @param manager Address of the manager to deposit stake for |
||
| 456 |
/// @param amount Amount of UP tokens to deposit as stake |
||
| 457 |
function depositStake(address manager, uint256 amount) external {
|
||
| 458 |
✓ 147.8K
|
⟲ 1.8K
|
if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 459 |
✓ 146.0K
|
⟲ 1.1K
|
if (manager == address(0)) revert ZERO_ADDRESS(); |
| 460 | |||
| 461 |
// Get the UP token address from SUPER_GOVERNOR |
||
| 462 |
✓ 145.0K
|
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
|
| 463 | |||
| 464 |
// Transfer UP tokens from msg.sender to this contract |
||
| 465 |
✓ 145.0K
|
IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount); |
|
| 466 | |||
| 467 |
// Update stake balance |
||
| 468 |
✓ 20.3K
|
_managerStakeBalance[manager] += amount; |
|
| 469 | |||
| 470 |
✓ 53.3K
|
emit StakeDeposited(manager, amount); |
|
| 471 |
} |
||
| 472 | |||
| 473 |
/// @notice Withdraws UP tokens from stake balance |
||
| 474 |
/// @param amount Amount of UP tokens to withdraw from stake |
||
| 475 |
function withdrawStake(uint256 amount) external {
|
||
| 476 |
✓ 160.6K
|
⟲ 1.8K
|
if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
| 477 | |||
| 478 |
// Check sufficient balance |
||
| 479 |
✓ 158.9K
|
if (_managerStakeBalance[msg.sender] < amount) {
|
|
| 480 |
✓ 157.0K
|
⟲ 157.0K
|
revert INSUFFICIENT_STAKE_BALANCE(); |
| 481 |
} |
||
| 482 | |||
| 483 |
// Get the UP token address from SUPER_GOVERNOR |
||
| 484 |
✓ 1.9K
|
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
|
| 485 | |||
| 486 |
// Update stake balance |
||
| 487 |
unchecked {
|
||
| 488 |
✓ 1.9K
|
_managerStakeBalance[msg.sender] -= amount; |
|
| 489 |
} |
||
| 490 | |||
| 491 |
// Transfer UP tokens to manager |
||
| 492 |
✓ 1.9K
|
IERC20(upToken).safeTransfer(msg.sender, amount); |
|
| 493 | |||
| 494 |
✓ 1.9K
|
emit StakeWithdrawn(msg.sender, amount); |
|
| 495 |
} |
||
| 496 | |||
| 497 |
/// @notice Slashes a manager's stake balance by a specified amount |
||
| 498 |
/// @param manager The manager whose stake will be slashed |
||
| 499 |
/// @param amount The amount of UP tokens to slash from the manager's stake balance |
||
| 500 |
function slashStake(address manager, uint256 amount) external {
|
||
| 501 |
// Only SUPER_GOVERNOR can slash stake |
||
| 502 |
✓ 124.2K
|
if (msg.sender != address(SUPER_GOVERNOR)) {
|
|
| 503 |
✓ 124.2K
|
⟲ 124.2K
|
revert CALLER_NOT_AUTHORIZED(); |
| 504 |
} |
||
| 505 | |||
| 506 |
// Validate inputs |
||
| 507 |
if (manager == address(0)) revert ZERO_ADDRESS(); |
||
| 508 |
if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency |
||
| 509 | |||
| 510 |
// Check if manager has sufficient stake balance to slash |
||
| 511 |
if (_managerStakeBalance[manager] < amount) {
|
||
| 512 |
revert INSUFFICIENT_STAKE_BALANCE(); |
||
| 513 |
} |
||
| 514 | |||
| 515 |
// Reduce manager's stake balance |
||
| 516 |
_managerStakeBalance[manager] -= amount; |
||
| 517 | |||
| 518 |
// Get the UP token address and SuperBank address |
||
| 519 |
address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP()); |
||
| 520 |
address superBank = _getSuperBank(); |
||
| 521 | |||
| 522 |
// Transfer slashed amount directly to SuperBank |
||
| 523 |
IERC20(upToken).safeTransfer(superBank, amount); |
||
| 524 | |||
| 525 |
// Emit event for transparency |
||
| 526 |
emit StakeSlashed(manager, amount); |
||
| 527 |
} |
||
| 528 | |||
| 529 |
/*////////////////////////////////////////////////////////////// |
||
| 530 |
AUTHORIZED CALLER MANAGEMENT |
||
| 531 |
//////////////////////////////////////////////////////////////*/ |
||
| 532 |
/// @inheritdoc ISuperVaultAggregator |
||
| 533 |
function addAuthorizedCaller( |
||
| 534 |
address strategy, |
||
| 535 |
address caller |
||
| 536 |
✓ 129.3K
|
) external validStrategy(strategy) {
|
|
| 537 |
// Either primary or secondary manager can add authorized callers |
||
| 538 |
✓ 1.7K
|
if (!isAnyManager(msg.sender, strategy)) |
|
| 539 |
✓ 673
|
⟲ 673
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 540 | |||
| 541 |
✓ 1.1K
|
⟲ 120
|
if (caller == address(0)) revert ZERO_ADDRESS(); |
| 542 | |||
| 543 |
// Prevent managers from adding protected keepers to circumvent fees |
||
| 544 |
✓ 951
|
if (SUPER_GOVERNOR.isProtectedKeeper(caller)) {
|
|
| 545 |
revert CANNOT_ADD_PROTECTED_KEEPER(); |
||
| 546 |
} |
||
| 547 | |||
| 548 |
// Check if caller is already authorized and add if not |
||
| 549 |
✓ 951
|
if (!_strategyData[strategy].authorizedCallers.add(caller)) {
|
|
| 550 |
revert CALLER_ALREADY_AUTHORIZED(); |
||
| 551 |
} |
||
| 552 |
✓ 951
|
emit AuthorizedCallerAdded(strategy, caller); |
|
| 553 |
} |
||
| 554 | |||
| 555 |
/// @inheritdoc ISuperVaultAggregator |
||
| 556 |
function removeAuthorizedCaller( |
||
| 557 |
address strategy, |
||
| 558 |
address caller |
||
| 559 |
✓ 148.6K
|
) external validStrategy(strategy) {
|
|
| 560 |
// Either primary or secondary manager can remove authorized callers |
||
| 561 |
✓ 17.6K
|
if (!isAnyManager(msg.sender, strategy)) |
|
| 562 |
✓ 4.5K
|
⟲ 4.5K
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 563 | |||
| 564 |
// Remove the caller |
||
| 565 |
✓ 13.0K
|
if (!_strategyData[strategy].authorizedCallers.remove(caller)) {
|
|
| 566 |
✓ 13.0K
|
⟲ 13.0K
|
revert CALLER_NOT_AUTHORIZED(); |
| 567 |
} |
||
| 568 |
emit AuthorizedCallerRemoved(strategy, caller); |
||
| 569 |
} |
||
| 570 | |||
| 571 |
/*////////////////////////////////////////////////////////////// |
||
| 572 |
MANAGER MANAGEMENT FUNCTIONS |
||
| 573 |
//////////////////////////////////////////////////////////////*/ |
||
| 574 |
/// @inheritdoc ISuperVaultAggregator |
||
| 575 |
function addSecondaryManager( |
||
| 576 |
address strategy, |
||
| 577 |
address manager |
||
| 578 |
✓ 127.4K
|
) external validStrategy(strategy) {
|
|
| 579 |
// Only the primary manager can add secondary managers |
||
| 580 |
✓ 10.4K
|
if (msg.sender != _strategyData[strategy].mainManager) |
|
| 581 |
✓ 7.0K
|
⟲ 7.0K
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 582 | |||
| 583 |
✓ 3.4K
|
⟲ 365
|
if (manager == address(0)) revert ZERO_ADDRESS(); |
| 584 | |||
| 585 |
// Check if manager is already the primary manager |
||
| 586 |
✓ 3.1K
|
if (_strategyData[strategy].mainManager == manager) |
|
| 587 |
✓ 114
|
⟲ 114
|
revert MANAGER_ALREADY_EXISTS(); |
| 588 | |||
| 589 |
// Enforce a cap on secondary managers to prevent governance DoS on changePrimaryManager |
||
| 590 |
if ( |
||
| 591 |
✓ 2.9K
|
_strategyData[strategy].secondaryManagers.length() >= |
|
| 592 |
MAX_SECONDARY_MANAGERS |
||
| 593 |
) {
|
||
| 594 |
revert TOO_MANY_SECONDARY_MANAGERS(); |
||
| 595 |
} |
||
| 596 | |||
| 597 |
// Add as secondary manager using EnumerableSet |
||
| 598 |
✓ 2.9K
|
if (!_strategyData[strategy].secondaryManagers.add(manager)) |
|
| 599 |
revert MANAGER_ALREADY_EXISTS(); |
||
| 600 | |||
| 601 |
✓ 2.9K
|
emit SecondaryManagerAdded(strategy, manager); |
|
| 602 |
} |
||
| 603 | |||
| 604 |
/// @inheritdoc ISuperVaultAggregator |
||
| 605 |
function removeSecondaryManager( |
||
| 606 |
address strategy, |
||
| 607 |
address manager |
||
| 608 |
✓ 110.7K
|
) external validStrategy(strategy) {
|
|
| 609 |
// Only the primary manager can remove secondary managers |
||
| 610 |
✓ 7.1K
|
if (msg.sender != _strategyData[strategy].mainManager) |
|
| 611 |
✓ 4.3K
|
⟲ 4.3K
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 612 | |||
| 613 |
// Remove the manager using EnumerableSet |
||
| 614 |
✓ 2.9K
|
if (!_strategyData[strategy].secondaryManagers.remove(manager)) |
|
| 615 |
✓ 1.3K
|
⟲ 1.3K
|
revert MANAGER_NOT_FOUND(); |
| 616 | |||
| 617 |
✓ 1.6K
|
emit SecondaryManagerRemoved(strategy, manager); |
|
| 618 |
} |
||
| 619 | |||
| 620 |
/// @inheritdoc ISuperVaultAggregator |
||
| 621 |
function updatePPSVerificationThresholds( |
||
| 622 |
address strategy, |
||
| 623 |
uint256 dispersionThreshold_, |
||
| 624 |
uint256 deviationThreshold_, |
||
| 625 |
uint256 mnThreshold_ |
||
| 626 |
✓ 142.0K
|
) external validStrategy(strategy) {
|
|
| 627 |
// Since this is a risky call, we only allow main managers as callers |
||
| 628 |
✓ 13.7K
|
if (msg.sender != _strategyData[strategy].mainManager) {
|
|
| 629 |
✓ 3.7K
|
⟲ 3.7K
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 630 |
} |
||
| 631 | |||
| 632 |
// Update the thresholds |
||
| 633 |
✓ 10.0K
|
_strategyData[strategy].dispersionThreshold = dispersionThreshold_; |
|
| 634 |
✓ 10.0K
|
_strategyData[strategy].deviationThreshold = deviationThreshold_; |
|
| 635 |
✓ 10.0K
|
_strategyData[strategy].mnThreshold = mnThreshold_; |
|
| 636 | |||
| 637 |
// Emit the event |
||
| 638 |
✓ 10.0K
|
emit PPSVerificationThresholdsUpdated( |
|
| 639 |
strategy, |
||
| 640 |
dispersionThreshold_, |
||
| 641 |
deviationThreshold_, |
||
| 642 |
mnThreshold_ |
||
| 643 |
); |
||
| 644 |
} |
||
| 645 | |||
| 646 |
/// @inheritdoc ISuperVaultAggregator |
||
| 647 |
function changeGlobalLeavesStatus( |
||
| 648 |
bytes32[] memory leaves, |
||
| 649 |
bool[] memory statuses, |
||
| 650 |
address strategy |
||
| 651 |
) external validStrategy(strategy) {
|
||
| 652 |
// Only the primary manager can change global leaves status |
||
| 653 |
if (msg.sender != _strategyData[strategy].mainManager) {
|
||
| 654 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 655 |
} |
||
| 656 |
uint256 leavesLen = leaves.length; |
||
| 657 |
// Check array lengths match |
||
| 658 |
if (leavesLen != statuses.length) {
|
||
| 659 |
revert MISMATCHED_ARRAY_LENGTHS(); |
||
| 660 |
} |
||
| 661 | |||
| 662 |
// Update banned status for each leaf |
||
| 663 |
for (uint256 i; i < leavesLen; i++) {
|
||
| 664 |
_strategyData[strategy].bannedLeaves[leaves[i]] = statuses[i]; |
||
| 665 |
} |
||
| 666 | |||
| 667 |
// Emit event |
||
| 668 |
emit GlobalLeavesStatusChanged(strategy, leaves, statuses); |
||
| 669 |
} |
||
| 670 | |||
| 671 |
/// @inheritdoc ISuperVaultAggregator |
||
| 672 |
function changePrimaryManager( |
||
| 673 |
address strategy, |
||
| 674 |
address newManager |
||
| 675 |
✓ 275.9K
|
) external validStrategy(strategy) {
|
|
| 676 |
// Only SuperGovernor can call this |
||
| 677 |
✓ 135.7K
|
if (msg.sender != address(SUPER_GOVERNOR)) {
|
|
| 678 |
✓ 993
|
⟲ 993
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 679 |
} |
||
| 680 | |||
| 681 |
✓ 134.7K
|
if (strategy == address(0)) revert ZERO_ADDRESS(); |
|
| 682 | |||
| 683 |
✓ 134.7K
|
if (newManager == address(0)) revert ZERO_ADDRESS(); |
|
| 684 | |||
| 685 |
✓ 134.7K
|
address oldManager = _strategyData[strategy].mainManager; |
|
| 686 | |||
| 687 |
// SECURITY: Clear any pending manager proposals to prevent malicious re-takeover |
||
| 688 |
✓ 134.7K
|
_strategyData[strategy].proposedManager = address(0); |
|
| 689 |
✓ 134.7K
|
_strategyData[strategy].managerChangeEffectiveTime = 0; |
|
| 690 | |||
| 691 |
// SECURITY: Clear any pending hooks root proposals to prevent malicious hook updates |
||
| 692 |
✓ 134.7K
|
_strategyData[strategy].proposedHooksRoot = bytes32(0); |
|
| 693 |
✓ 134.7K
|
_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 |
✓ 134.7K
|
address[] memory clearedSecondaryManagers = _strategyData[strategy] |
|
| 698 |
.secondaryManagers |
||
| 699 |
.values(); |
||
| 700 | |||
| 701 |
// Clear the entire secondary managers set |
||
| 702 |
✓ 170.9K
|
for (uint256 i = 0; i < clearedSecondaryManagers.length; i++) {
|
|
| 703 |
✓ 36.2K
|
_strategyData[strategy].secondaryManagers.remove( |
|
| 704 |
✓ 36.2K
|
clearedSecondaryManagers[i] |
|
| 705 |
); |
||
| 706 |
✓ 36.2K
|
emit SecondaryManagerRemoved(strategy, clearedSecondaryManagers[i]); |
|
| 707 |
} |
||
| 708 | |||
| 709 |
// Set the new primary manager |
||
| 710 |
✓ 134.7K
|
_strategyData[strategy].mainManager = newManager; |
|
| 711 | |||
| 712 |
✓ 134.7K
|
emit PrimaryManagerChanged(strategy, oldManager, newManager); |
|
| 713 |
} |
||
| 714 | |||
| 715 |
/// @inheritdoc ISuperVaultAggregator |
||
| 716 |
function proposeChangePrimaryManager( |
||
| 717 |
address strategy, |
||
| 718 |
address newManager |
||
| 719 |
✓ 256.4K
|
) external validStrategy(strategy) {
|
|
| 720 |
// Only secondary managers can propose changes to the primary manager |
||
| 721 |
✓ 145.4K
|
if (!_strategyData[strategy].secondaryManagers.contains(msg.sender)) {
|
|
| 722 |
✓ 145.4K
|
⟲ 145.4K
|
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
| 723 |
} |
||
| 724 | |||
| 725 |
if (newManager == address(0)) revert ZERO_ADDRESS(); |
||
| 726 | |||
| 727 |
// Set up the proposal with 7-day timelock |
||
| 728 |
uint256 effectiveTime = block.timestamp + _MANAGER_CHANGE_TIMELOCK; |
||
| 729 | |||
| 730 |
// Store proposal in the strategy data |
||
| 731 |
_strategyData[strategy].proposedManager = newManager; |
||
| 732 |
_strategyData[strategy].managerChangeEffectiveTime = effectiveTime; |
||
| 733 | |||
| 734 |
emit PrimaryManagerChangeProposed( |
||
| 735 |
strategy, |
||
| 736 |
msg.sender, |
||
| 737 |
newManager, |
||
| 738 |
effectiveTime |
||
| 739 |
); |
||
| 740 |
} |
||
| 741 | |||
| 742 |
/// @inheritdoc ISuperVaultAggregator |
||
| 743 |
function executeChangePrimaryManager( |
||
| 744 |
address strategy |
||
| 745 |
✓ 272.1K
|
) external validStrategy(strategy) {
|
|
| 746 |
// Check if there is a pending proposal |
||
| 747 |
✓ 155.0K
|
if (_strategyData[strategy].proposedManager == address(0)) |
|
| 748 |
✓ 155.0K
|
⟲ 155.0K
|
revert NO_PENDING_MANAGER_CHANGE(); |
| 749 | |||
| 750 |
// Check if the timelock period has passed |
||
| 751 |
if ( |
||
| 752 |
block.timestamp < _strategyData[strategy].managerChangeEffectiveTime |
||
| 753 |
) revert TIMELOCK_NOT_EXPIRED(); |
||
| 754 | |||
| 755 |
address newManager = _strategyData[strategy].proposedManager; |
||
| 756 |
address oldManager = _strategyData[strategy].mainManager; |
||
| 757 | |||
| 758 |
// If new manager is already a secondary manager, remove them |
||
| 759 |
_strategyData[strategy].secondaryManagers.remove(newManager); |
||
| 760 | |||
| 761 |
// Make the old primary manager a secondary manager |
||
| 762 |
if ( |
||
| 763 |
_strategyData[strategy].secondaryManagers.length() < |
||
| 764 |
MAX_SECONDARY_MANAGERS |
||
| 765 |
) {
|
||
| 766 |
_strategyData[strategy].secondaryManagers.add(oldManager); |
||
| 767 |
} |
||
| 768 | |||
| 769 |
// Set the new primary manager |
||
| 770 |
_strategyData[strategy].mainManager = newManager; |
||
| 771 | |||
| 772 |
// Clear the proposal |
||
| 773 |
_strategyData[strategy].proposedManager = address(0); |
||
| 774 | |||
| 775 |
emit PrimaryManagerChanged(strategy, oldManager, newManager); |
||
| 776 |
} |
||
| 777 | |||
| 778 |
/*////////////////////////////////////////////////////////////// |
||
| 779 |
HOOK VALIDATION FUNCTIONS |
||
| 780 |
//////////////////////////////////////////////////////////////*/ |
||
| 781 |
/// @inheritdoc ISuperVaultAggregator |
||
| 782 |
function setHooksRootUpdateTimelock(uint256 newTimelock) external {
|
||
| 783 |
// Only SUPER_GOVERNOR can update the timelock |
||
| 784 |
if (msg.sender != address(SUPER_GOVERNOR)) {
|
||
| 785 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 786 |
} |
||
| 787 | |||
| 788 |
// Update the timelock |
||
| 789 |
_hooksRootUpdateTimelock = newTimelock; |
||
| 790 | |||
| 791 |
emit HooksRootUpdateTimelockChanged(newTimelock); |
||
| 792 |
} |
||
| 793 | |||
| 794 |
/// @inheritdoc ISuperVaultAggregator |
||
| 795 |
function proposeGlobalHooksRoot(bytes32 newRoot) external {
|
||
| 796 |
// Only SUPER_GOVERNOR can update the global hooks root |
||
| 797 |
✓ 259.5K
|
if (msg.sender != address(SUPER_GOVERNOR)) {
|
|
| 798 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 799 |
} |
||
| 800 | |||
| 801 |
// Set new root with timelock |
||
| 802 |
✓ 259.5K
|
_proposedGlobalHooksRoot = newRoot; |
|
| 803 |
✓ 259.5K
|
uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
|
| 804 |
✓ 259.5K
|
_globalHooksRootEffectiveTime = effectiveTime; |
|
| 805 | |||
| 806 |
✓ 259.5K
|
emit GlobalHooksRootUpdateProposed(newRoot, effectiveTime); |
|
| 807 |
} |
||
| 808 | |||
| 809 |
/// @inheritdoc ISuperVaultAggregator |
||
| 810 |
function executeGlobalHooksRootUpdate() external {
|
||
| 811 |
bytes32 proposedRoot = _proposedGlobalHooksRoot; |
||
| 812 |
// Ensure there is a pending proposal |
||
| 813 |
if (proposedRoot == bytes32(0)) {
|
||
| 814 |
revert NO_PENDING_GLOBAL_ROOT_CHANGE(); |
||
| 815 |
} |
||
| 816 | |||
| 817 |
// Check if timelock period has elapsed |
||
| 818 |
if (block.timestamp < _globalHooksRootEffectiveTime) {
|
||
| 819 |
revert ROOT_UPDATE_NOT_READY(); |
||
| 820 |
} |
||
| 821 | |||
| 822 |
// Update the global hooks root |
||
| 823 |
bytes32 oldRoot = _globalHooksRoot; |
||
| 824 |
_globalHooksRoot = _proposedGlobalHooksRoot; |
||
| 825 |
_globalHooksRootEffectiveTime = 0; |
||
| 826 |
_proposedGlobalHooksRoot = bytes32(0); |
||
| 827 | |||
| 828 |
✓ 262.0K
|
emit GlobalHooksRootUpdated(oldRoot, proposedRoot); |
|
| 829 |
} |
||
| 830 | |||
| 831 |
/// @inheritdoc ISuperVaultAggregator |
||
| 832 |
function setGlobalHooksRootVetoStatus(bool vetoed) external {
|
||
| 833 |
// Only SuperGovernor can call this |
||
| 834 |
if (msg.sender != address(SUPER_GOVERNOR)) {
|
||
| 835 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 836 |
} |
||
| 837 | |||
| 838 |
// Don't emit event if status doesn't change |
||
| 839 |
if (_globalHooksRootVetoed == vetoed) {
|
||
| 840 |
return; |
||
| 841 |
} |
||
| 842 | |||
| 843 |
// Update veto status |
||
| 844 |
_globalHooksRootVetoed = vetoed; |
||
| 845 | |||
| 846 |
emit GlobalHooksRootVetoStatusChanged(vetoed, _globalHooksRoot); |
||
| 847 |
} |
||
| 848 | |||
| 849 |
/// @inheritdoc ISuperVaultAggregator |
||
| 850 |
function proposeStrategyHooksRoot( |
||
| 851 |
address strategy, |
||
| 852 |
bytes32 newRoot |
||
| 853 |
) external validStrategy(strategy) {
|
||
| 854 |
// Only the main manager can propose strategy-specific hooks root |
||
| 855 |
if (_strategyData[strategy].mainManager != msg.sender) {
|
||
| 856 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 857 |
} |
||
| 858 | |||
| 859 |
// Set proposed root with timelock |
||
| 860 |
_strategyData[strategy].proposedHooksRoot = newRoot; |
||
| 861 |
uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock; |
||
| 862 |
_strategyData[strategy].hooksRootEffectiveTime = effectiveTime; |
||
| 863 | |||
| 864 |
emit StrategyHooksRootUpdateProposed( |
||
| 865 |
strategy, |
||
| 866 |
msg.sender, |
||
| 867 |
newRoot, |
||
| 868 |
effectiveTime |
||
| 869 |
); |
||
| 870 |
} |
||
| 871 | |||
| 872 |
/// @inheritdoc ISuperVaultAggregator |
||
| 873 |
function executeStrategyHooksRootUpdate( |
||
| 874 |
address strategy |
||
| 875 |
) external validStrategy(strategy) {
|
||
| 876 |
bytes32 proposedRoot = _strategyData[strategy].proposedHooksRoot; |
||
| 877 |
// Ensure there is a pending proposal |
||
| 878 |
if (proposedRoot == bytes32(0)) {
|
||
| 879 |
revert NO_PENDING_MANAGER_CHANGE(); // Reusing error for simplicity |
||
| 880 |
} |
||
| 881 | |||
| 882 |
// Check if timelock period has elapsed |
||
| 883 |
if (block.timestamp < _strategyData[strategy].hooksRootEffectiveTime) {
|
||
| 884 |
revert ROOT_UPDATE_NOT_READY(); |
||
| 885 |
} |
||
| 886 | |||
| 887 |
// Update the strategy's hooks root |
||
| 888 |
bytes32 oldRoot = _strategyData[strategy].managerHooksRoot; |
||
| 889 |
_strategyData[strategy].managerHooksRoot = proposedRoot; |
||
| 890 | |||
| 891 |
// Reset proposal state |
||
| 892 |
_strategyData[strategy].proposedHooksRoot = bytes32(0); |
||
| 893 |
_strategyData[strategy].hooksRootEffectiveTime = 0; |
||
| 894 | |||
| 895 |
emit StrategyHooksRootUpdated(strategy, oldRoot, proposedRoot); |
||
| 896 |
} |
||
| 897 | |||
| 898 |
/// @inheritdoc ISuperVaultAggregator |
||
| 899 |
function setStrategyHooksRootVetoStatus( |
||
| 900 |
address strategy, |
||
| 901 |
bool vetoed |
||
| 902 |
) external validStrategy(strategy) {
|
||
| 903 |
// Only SuperGovernor can call this |
||
| 904 |
if (msg.sender != address(SUPER_GOVERNOR)) {
|
||
| 905 |
revert UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 906 |
} |
||
| 907 | |||
| 908 |
// Don't emit event if status doesn't change |
||
| 909 |
if (_strategyData[strategy].hooksRootVetoed == vetoed) {
|
||
| 910 |
return; |
||
| 911 |
} |
||
| 912 | |||
| 913 |
// Update veto status |
||
| 914 |
_strategyData[strategy].hooksRootVetoed = vetoed; |
||
| 915 | |||
| 916 |
emit StrategyHooksRootVetoStatusChanged( |
||
| 917 |
strategy, |
||
| 918 |
vetoed, |
||
| 919 |
_strategyData[strategy].managerHooksRoot |
||
| 920 |
); |
||
| 921 |
} |
||
| 922 |
/// @inheritdoc ISuperVaultAggregator |
||
| 923 | |||
| 924 |
function isGlobalHooksRootVetoed() external view returns (bool vetoed) {
|
||
| 925 |
✓ 368.0K
|
return _globalHooksRootVetoed; |
|
| 926 |
} |
||
| 927 | |||
| 928 |
/// @inheritdoc ISuperVaultAggregator |
||
| 929 |
function isStrategyHooksRootVetoed( |
||
| 930 |
address strategy |
||
| 931 |
) external view returns (bool vetoed) {
|
||
| 932 |
return _strategyData[strategy].hooksRootVetoed; |
||
| 933 |
} |
||
| 934 | |||
| 935 |
/*////////////////////////////////////////////////////////////// |
||
| 936 |
VIEW FUNCTIONS |
||
| 937 |
//////////////////////////////////////////////////////////////*/ |
||
| 938 | |||
| 939 |
/// @inheritdoc ISuperVaultAggregator |
||
| 940 |
function getCurrentNonce() external view returns (uint256) {
|
||
| 941 |
return _vaultCreationNonce; |
||
| 942 |
} |
||
| 943 | |||
| 944 |
/// @inheritdoc ISuperVaultAggregator |
||
| 945 |
function getHooksRootUpdateTimelock() external view returns (uint256) {
|
||
| 946 |
return _hooksRootUpdateTimelock; |
||
| 947 |
} |
||
| 948 | |||
| 949 |
/// @inheritdoc ISuperVaultAggregator |
||
| 950 |
function getPPS( |
||
| 951 |
address strategy |
||
| 952 |
✓ 16.3M
|
) external view validStrategy(strategy) returns (uint256 pps) {
|
|
| 953 |
✓ 16.3M
|
return _strategyData[strategy].pps; |
|
| 954 |
} |
||
| 955 | |||
| 956 |
/// @inheritdoc ISuperVaultAggregator |
||
| 957 |
function getPPSWithStdDev( |
||
| 958 |
address strategy |
||
| 959 |
) |
||
| 960 |
external |
||
| 961 |
view |
||
| 962 |
validStrategy(strategy) |
||
| 963 |
returns (uint256 pps, uint256 ppsStdev) |
||
| 964 |
{
|
||
| 965 |
return (_strategyData[strategy].pps, _strategyData[strategy].ppsStdev); |
||
| 966 |
} |
||
| 967 | |||
| 968 |
/// @inheritdoc ISuperVaultAggregator |
||
| 969 |
function getLastUpdateTimestamp( |
||
| 970 |
address strategy |
||
| 971 |
) external view returns (uint256 timestamp) {
|
||
| 972 |
return _strategyData[strategy].lastUpdateTimestamp; |
||
| 973 |
} |
||
| 974 | |||
| 975 |
/// @inheritdoc ISuperVaultAggregator |
||
| 976 |
function getMinUpdateInterval( |
||
| 977 |
address strategy |
||
| 978 |
) external view returns (uint256 interval) {
|
||
| 979 |
return _strategyData[strategy].minUpdateInterval; |
||
| 980 |
} |
||
| 981 | |||
| 982 |
/// @inheritdoc ISuperVaultAggregator |
||
| 983 |
function getMaxStaleness( |
||
| 984 |
address strategy |
||
| 985 |
) external view returns (uint256 staleness) {
|
||
| 986 |
return _strategyData[strategy].maxStaleness; |
||
| 987 |
} |
||
| 988 | |||
| 989 |
/// @inheritdoc ISuperVaultAggregator |
||
| 990 |
function getPPSVerificationThresholds( |
||
| 991 |
address strategy |
||
| 992 |
) |
||
| 993 |
external |
||
| 994 |
view |
||
| 995 |
validStrategy(strategy) |
||
| 996 |
returns ( |
||
| 997 |
uint256 dispersionThreshold, |
||
| 998 |
uint256 deviationThreshold, |
||
| 999 |
uint256 mnThreshold |
||
| 1000 |
) |
||
| 1001 |
{
|
||
| 1002 |
return ( |
||
| 1003 |
_strategyData[strategy].dispersionThreshold, |
||
| 1004 |
_strategyData[strategy].deviationThreshold, |
||
| 1005 |
_strategyData[strategy].mnThreshold |
||
| 1006 |
); |
||
| 1007 |
} |
||
| 1008 | |||
| 1009 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1010 |
function isStrategyPaused( |
||
| 1011 |
address strategy |
||
| 1012 |
✓ 1.2M
|
) external view returns (bool isPaused) {
|
|
| 1013 |
✓ 1.2M
|
return _strategyData[strategy].isPaused; |
|
| 1014 |
} |
||
| 1015 | |||
| 1016 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1017 |
function getUpkeepBalance( |
||
| 1018 |
address manager |
||
| 1019 |
) external view returns (uint256 balance) {
|
||
| 1020 |
return _managerUpkeepBalance[manager]; |
||
| 1021 |
} |
||
| 1022 | |||
| 1023 |
/// @notice Gets the current stake balance for a manager |
||
| 1024 |
/// @param manager Address of the manager |
||
| 1025 |
/// @return balance Current stake balance in UP tokens |
||
| 1026 |
function getStakeBalance( |
||
| 1027 |
address manager |
||
| 1028 |
) external view returns (uint256 balance) {
|
||
| 1029 |
return _managerStakeBalance[manager]; |
||
| 1030 |
} |
||
| 1031 | |||
| 1032 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1033 |
function getAuthorizedCallers( |
||
| 1034 |
address strategy |
||
| 1035 |
) external view returns (address[] memory callers) {
|
||
| 1036 |
return _strategyData[strategy].authorizedCallers.values(); |
||
| 1037 |
} |
||
| 1038 | |||
| 1039 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1040 |
function getMainManager( |
||
| 1041 |
address strategy |
||
| 1042 |
) external view returns (address manager) {
|
||
| 1043 |
manager = _strategyData[strategy].mainManager; |
||
| 1044 |
if (manager == address(0)) revert ZERO_ADDRESS(); |
||
| 1045 | |||
| 1046 |
return manager; |
||
| 1047 |
} |
||
| 1048 | |||
| 1049 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1050 |
function isMainManager( |
||
| 1051 |
address manager, |
||
| 1052 |
address strategy |
||
| 1053 |
✓ 902.4K
|
) external view returns (bool) {
|
|
| 1054 |
✓ 902.4K
|
return _strategyData[strategy].mainManager == manager; |
|
| 1055 |
} |
||
| 1056 | |||
| 1057 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1058 |
function getSecondaryManagers( |
||
| 1059 |
address strategy |
||
| 1060 |
) external view returns (address[] memory) {
|
||
| 1061 |
return _strategyData[strategy].secondaryManagers.values(); |
||
| 1062 |
} |
||
| 1063 | |||
| 1064 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1065 |
function isSecondaryManager( |
||
| 1066 |
address manager, |
||
| 1067 |
address strategy |
||
| 1068 |
) external view returns (bool) {
|
||
| 1069 |
return _strategyData[strategy].secondaryManagers.contains(manager); |
||
| 1070 |
} |
||
| 1071 | |||
| 1072 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1073 |
function isAnyManager( |
||
| 1074 |
address manager, |
||
| 1075 |
address strategy |
||
| 1076 |
✓ 673.6K
|
) public view returns (bool) {
|
|
| 1077 |
// Check if primary manager |
||
| 1078 |
✓ 673.6K
|
if (_strategyData[strategy].mainManager == manager) {
|
|
| 1079 |
✓ 651.0K
|
return true; |
|
| 1080 |
} |
||
| 1081 | |||
| 1082 |
// Check if secondary manager using EnumerableSet |
||
| 1083 |
✓ 22.6K
|
return _strategyData[strategy].secondaryManagers.contains(manager); |
|
| 1084 |
} |
||
| 1085 | |||
| 1086 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1087 |
function getAllSuperVaults() external view returns (address[] memory) {
|
||
| 1088 |
return _superVaults.values(); |
||
| 1089 |
} |
||
| 1090 | |||
| 1091 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1092 |
function superVaults(uint256 index) external view returns (address) {
|
||
| 1093 |
if (index >= _superVaults.length()) revert INDEX_OUT_OF_BOUNDS(); |
||
| 1094 |
return _superVaults.at(index); |
||
| 1095 |
} |
||
| 1096 | |||
| 1097 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1098 |
function getAllSuperVaultStrategies() |
||
| 1099 |
external |
||
| 1100 |
view |
||
| 1101 |
returns (address[] memory) |
||
| 1102 |
{
|
||
| 1103 |
return _superVaultStrategies.values(); |
||
| 1104 |
} |
||
| 1105 | |||
| 1106 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1107 |
function superVaultStrategies( |
||
| 1108 |
uint256 index |
||
| 1109 |
) external view returns (address) {
|
||
| 1110 |
if (index >= _superVaultStrategies.length()) |
||
| 1111 |
revert INDEX_OUT_OF_BOUNDS(); |
||
| 1112 |
return _superVaultStrategies.at(index); |
||
| 1113 |
} |
||
| 1114 | |||
| 1115 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1116 |
function getAllSuperVaultEscrows() |
||
| 1117 |
external |
||
| 1118 |
view |
||
| 1119 |
returns (address[] memory) |
||
| 1120 |
{
|
||
| 1121 |
return _superVaultEscrows.values(); |
||
| 1122 |
} |
||
| 1123 | |||
| 1124 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1125 |
function superVaultEscrows(uint256 index) external view returns (address) {
|
||
| 1126 |
if (index >= _superVaultEscrows.length()) revert INDEX_OUT_OF_BOUNDS(); |
||
| 1127 |
return _superVaultEscrows.at(index); |
||
| 1128 |
} |
||
| 1129 | |||
| 1130 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1131 |
function validateHook( |
||
| 1132 |
address strategy, |
||
| 1133 |
ValidateHookArgs calldata args |
||
| 1134 |
) external view virtual returns (bool isValid) {
|
||
| 1135 |
// Cache all state variables in struct |
||
| 1136 |
HookValidationCache memory cache = HookValidationCache({
|
||
| 1137 |
globalHooksRootVetoed: _globalHooksRootVetoed, |
||
| 1138 |
globalHooksRoot: _globalHooksRoot, |
||
| 1139 |
strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
||
| 1140 |
strategyRoot: _strategyData[strategy].managerHooksRoot |
||
| 1141 |
}); |
||
| 1142 | |||
| 1143 |
// Early return false if either global or strategy hooks root is vetoed |
||
| 1144 |
if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
|
||
| 1145 |
return false; |
||
| 1146 |
} |
||
| 1147 | |||
| 1148 |
// Try to validate against global root first |
||
| 1149 |
if ( |
||
| 1150 |
_validateSingleHook( |
||
| 1151 |
args.hookAddress, |
||
| 1152 |
args.hookArgs, |
||
| 1153 |
args.globalProof, |
||
| 1154 |
true, |
||
| 1155 |
cache, |
||
| 1156 |
strategy |
||
| 1157 |
) |
||
| 1158 |
) {
|
||
| 1159 |
return true; |
||
| 1160 |
} |
||
| 1161 | |||
| 1162 |
// If global validation fails, try strategy root |
||
| 1163 |
return |
||
| 1164 |
_validateSingleHook( |
||
| 1165 |
args.hookAddress, |
||
| 1166 |
args.hookArgs, |
||
| 1167 |
args.strategyProof, |
||
| 1168 |
false, |
||
| 1169 |
cache, |
||
| 1170 |
strategy |
||
| 1171 |
); |
||
| 1172 |
} |
||
| 1173 | |||
| 1174 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1175 |
function validateHooks( |
||
| 1176 |
address strategy, |
||
| 1177 |
ValidateHookArgs[] calldata argsArray |
||
| 1178 |
) external view returns (bool[] memory validHooks) {
|
||
| 1179 |
uint256 length = argsArray.length; |
||
| 1180 | |||
| 1181 |
// Cache all state variables in struct |
||
| 1182 |
HookValidationCache memory cache = HookValidationCache({
|
||
| 1183 |
globalHooksRootVetoed: _globalHooksRootVetoed, |
||
| 1184 |
globalHooksRoot: _globalHooksRoot, |
||
| 1185 |
strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed, |
||
| 1186 |
strategyRoot: _strategyData[strategy].managerHooksRoot |
||
| 1187 |
}); |
||
| 1188 | |||
| 1189 |
// Early return all false if either global or strategy hooks root is vetoed |
||
| 1190 |
if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
|
||
| 1191 |
return new bool[](length); // Array initialized with all false values |
||
| 1192 |
} |
||
| 1193 | |||
| 1194 |
// Validate each hook |
||
| 1195 |
validHooks = new bool[](length); |
||
| 1196 |
for (uint256 i; i < length; i++) {
|
||
| 1197 |
// Try global root first |
||
| 1198 |
if ( |
||
| 1199 |
_validateSingleHook( |
||
| 1200 |
argsArray[i].hookAddress, |
||
| 1201 |
argsArray[i].hookArgs, |
||
| 1202 |
argsArray[i].globalProof, |
||
| 1203 |
true, |
||
| 1204 |
cache, |
||
| 1205 |
strategy |
||
| 1206 |
) |
||
| 1207 |
) {
|
||
| 1208 |
validHooks[i] = true; |
||
| 1209 |
} else {
|
||
| 1210 |
// Try strategy root |
||
| 1211 |
validHooks[i] = _validateSingleHook( |
||
| 1212 |
argsArray[i].hookAddress, |
||
| 1213 |
argsArray[i].hookArgs, |
||
| 1214 |
argsArray[i].strategyProof, |
||
| 1215 |
false, |
||
| 1216 |
cache, |
||
| 1217 |
strategy |
||
| 1218 |
); |
||
| 1219 |
} |
||
| 1220 |
// If both conditions fail, validHooks[i] remains false (default value) |
||
| 1221 |
} |
||
| 1222 | |||
| 1223 |
return validHooks; |
||
| 1224 |
} |
||
| 1225 | |||
| 1226 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1227 |
function getGlobalHooksRoot() external view returns (bytes32 root) {
|
||
| 1228 |
return _globalHooksRoot; |
||
| 1229 |
} |
||
| 1230 | |||
| 1231 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1232 |
function getProposedGlobalHooksRoot() |
||
| 1233 |
external |
||
| 1234 |
view |
||
| 1235 |
returns (bytes32 root, uint256 effectiveTime) |
||
| 1236 |
{
|
||
| 1237 |
return (_proposedGlobalHooksRoot, _globalHooksRootEffectiveTime); |
||
| 1238 |
} |
||
| 1239 | |||
| 1240 |
/// @notice Checks if the global hooks root is active (timelock period has passed) |
||
| 1241 |
/// @return isActive True if the global hooks root is active |
||
| 1242 |
function isGlobalHooksRootActive() external view returns (bool) {
|
||
| 1243 |
return |
||
| 1244 |
block.timestamp >= _globalHooksRootEffectiveTime && |
||
| 1245 |
_globalHooksRoot != bytes32(0); |
||
| 1246 |
} |
||
| 1247 | |||
| 1248 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1249 |
function getStrategyHooksRoot( |
||
| 1250 |
address strategy |
||
| 1251 |
) external view returns (bytes32 root) {
|
||
| 1252 |
return _strategyData[strategy].managerHooksRoot; |
||
| 1253 |
} |
||
| 1254 | |||
| 1255 |
/// @inheritdoc ISuperVaultAggregator |
||
| 1256 |
function getProposedStrategyHooksRoot( |
||
| 1257 |
address strategy |
||
| 1258 |
) external view returns (bytes32 root, uint256 effectiveTime) {
|
||
| 1259 |
return ( |
||
| 1260 |
_strategyData[strategy].proposedHooksRoot, |
||
| 1261 |
_strategyData[strategy].hooksRootEffectiveTime |
||
| 1262 |
); |
||
| 1263 |
} |
||
| 1264 | |||
| 1265 |
/*////////////////////////////////////////////////////////////// |
||
| 1266 |
INTERNAL HELPER FUNCTIONS |
||
| 1267 |
//////////////////////////////////////////////////////////////*/ |
||
| 1268 |
/// @notice Internal implementation of forwarding PPS updates |
||
| 1269 |
/// @param args Struct containing all parameters for PPS update |
||
| 1270 |
function _forwardPPS(PPSUpdateData memory args) internal {
|
||
| 1271 |
// Check rate limiting |
||
| 1272 |
✓ 112.5K
|
uint256 minInterval = _strategyData[args.strategy].minUpdateInterval; |
|
| 1273 |
✓ 112.5K
|
uint256 lastUpdate = _strategyData[args.strategy].lastUpdateTimestamp; |
|
| 1274 |
✓ 112.5K
|
if (block.timestamp - lastUpdate < minInterval) {
|
|
| 1275 |
✓ 1.0K
|
emit UpdateTooFrequent(); |
|
| 1276 |
✓ 1.0K
|
return; |
|
| 1277 |
} |
||
| 1278 | |||
| 1279 |
// Ensure timestamp is monotonically increasing to prevent out-of-order updates |
||
| 1280 |
✓ 111.5K
|
if (args.timestamp <= lastUpdate) {
|
|
| 1281 |
✓ 29
|
emit TimestampNotMonotonic(); |
|
| 1282 |
✓ 29
|
return; |
|
| 1283 |
} |
||
| 1284 | |||
| 1285 |
// Get the strategy's manager to deduct upkeep cost from |
||
| 1286 |
✓ 111.4K
|
address manager = _strategyData[args.strategy].mainManager; |
|
| 1287 | |||
| 1288 |
// Flag to track if any check failed |
||
| 1289 |
bool checksFailed = false; |
||
| 1290 | |||
| 1291 |
// C2.1) Dispersion Check: Check if the standard deviation is too high relative to mean |
||
| 1292 |
if ( |
||
| 1293 |
✓ 111.4K
|
_strategyData[args.strategy].dispersionThreshold != |
|
| 1294 |
type(uint256).max && |
||
| 1295 |
✓ 11.2K
|
args.pps > 0 |
|
| 1296 |
) {
|
||
| 1297 |
// Calculate dispersion as stddev/mean |
||
| 1298 |
✓ 11.1K
|
uint256 dispersion = (args.ppsStdev * 1e18) / args.pps; // Scaled by 1e18 for precision |
|
| 1299 |
✓ 11.1K
|
if (dispersion > _strategyData[args.strategy].dispersionThreshold) {
|
|
| 1300 |
checksFailed = true; |
||
| 1301 |
emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DISPERSION"); |
||
| 1302 |
} |
||
| 1303 |
} |
||
| 1304 | |||
| 1305 |
// C2.2) Deviation Check: Check if new PPS deviates too much from current PPS |
||
| 1306 |
✓ 111.4K
|
uint256 currentPPS = _strategyData[args.strategy].pps; |
|
| 1307 |
if ( |
||
| 1308 |
✓ 111.4K
|
_strategyData[args.strategy].deviationThreshold != |
|
| 1309 |
type(uint256).max && |
||
| 1310 |
✓ 11.2K
|
currentPPS > 0 |
|
| 1311 |
) {
|
||
| 1312 |
// Calculate absolute deviation, scaled by 1e18 |
||
| 1313 |
✓ 11.1K
|
uint256 absDiff = args.pps > currentPPS |
|
| 1314 |
✓ 8.9K
|
? (args.pps - currentPPS) |
|
| 1315 |
✓ 2.3K
|
: (currentPPS - args.pps); |
|
| 1316 |
✓ 11.1K
|
uint256 relativeDeviation = (absDiff * 1e18) / currentPPS; |
|
| 1317 |
if ( |
||
| 1318 |
relativeDeviation > |
||
| 1319 |
✓ 11.1K
|
_strategyData[args.strategy].deviationThreshold |
|
| 1320 |
) {
|
||
| 1321 |
✓ 9.3K
|
checksFailed = true; |
|
| 1322 |
✓ 9.3K
|
emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DEVIATION"); |
|
| 1323 |
} |
||
| 1324 |
} |
||
| 1325 | |||
| 1326 |
// C2.3) M/N Check: Check if enough validators participated |
||
| 1327 |
if ( |
||
| 1328 |
✓ 111.4K
|
args.totalValidators > 0 && |
|
| 1329 |
_strategyData[args.strategy].mnThreshold > 0 |
||
| 1330 |
) {
|
||
| 1331 |
// Calculate participation rate, scaled by 1e18 |
||
| 1332 |
uint256 participationRate = (args.validatorSet * 1e18) / |
||
| 1333 |
args.totalValidators; |
||
| 1334 |
if (participationRate < _strategyData[args.strategy].mnThreshold) {
|
||
| 1335 |
checksFailed = true; |
||
| 1336 |
emit StrategyCheckFailed( |
||
| 1337 |
args.strategy, |
||
| 1338 |
"INSUFFICIENT_VALIDATOR_PARTICIPATION" |
||
| 1339 |
); |
||
| 1340 |
} |
||
| 1341 |
} |
||
| 1342 | |||
| 1343 |
// Pause strategy if any check failed |
||
| 1344 |
✓ 111.4K
|
if (checksFailed && !_strategyData[args.strategy].isPaused) {
|
|
| 1345 |
✓ 8.1K
|
_strategyData[args.strategy].isPaused = true; |
|
| 1346 |
✓ 8.1K
|
emit StrategyPaused(args.strategy); |
|
| 1347 |
} |
||
| 1348 |
// Unpause strategy if all checks passed and strategy was previously paused |
||
| 1349 |
✓ 103.3K
|
else if (!checksFailed && _strategyData[args.strategy].isPaused) {
|
|
| 1350 |
✓ 1.6K
|
_strategyData[args.strategy].isPaused = false; |
|
| 1351 |
✓ 1.6K
|
emit StrategyUnpaused(args.strategy); |
|
| 1352 |
} |
||
| 1353 | |||
| 1354 |
// Handle upkeep costs unless exempt |
||
| 1355 |
✓ 111.4K
|
if (!args.isExempt) {
|
|
| 1356 |
// Check if manager has sufficient upkeep balance |
||
| 1357 |
if (_managerUpkeepBalance[manager] < args.upkeepCost) {
|
||
| 1358 |
emit InsufficientUpkeep( |
||
| 1359 |
args.strategy, |
||
| 1360 |
manager, |
||
| 1361 |
_managerUpkeepBalance[manager], |
||
| 1362 |
args.upkeepCost |
||
| 1363 |
); |
||
| 1364 |
return; |
||
| 1365 |
} |
||
| 1366 | |||
| 1367 |
// Deduct the upkeep cost and emit event |
||
| 1368 |
_managerUpkeepBalance[manager] -= args.upkeepCost; |
||
| 1369 | |||
| 1370 |
// Add claimable upkeep for the `feeRecipient` |
||
| 1371 |
claimableUpkeep += args.upkeepCost; |
||
| 1372 | |||
| 1373 |
emit UpkeepSpent(manager, args.upkeepCost); |
||
| 1374 |
} |
||
| 1375 | |||
| 1376 |
// Update PPS, ppsStdev and timestamp in StrategyData |
||
| 1377 |
✓ 111.4K
|
_strategyData[args.strategy].pps = args.pps; |
|
| 1378 |
✓ 111.4K
|
_strategyData[args.strategy].ppsStdev = args.ppsStdev; |
|
| 1379 |
✓ 111.4K
|
_strategyData[args.strategy].lastUpdateTimestamp = args.timestamp; |
|
| 1380 | |||
| 1381 |
emit PPSUpdated( |
||
| 1382 |
✓ 111.4K
|
args.strategy, |
|
| 1383 |
✓ 111.4K
|
args.pps, |
|
| 1384 |
✓ 111.4K
|
args.ppsStdev, |
|
| 1385 |
✓ 111.4K
|
args.validatorSet, |
|
| 1386 |
✓ 111.4K
|
args.totalValidators, |
|
| 1387 |
✓ 111.4K
|
args.timestamp |
|
| 1388 |
); |
||
| 1389 |
} |
||
| 1390 | |||
| 1391 |
/// @notice Check if an update authority is exempt from paying upkeep costs |
||
| 1392 |
/// @param strategy Address of the strategy being updated |
||
| 1393 |
/// @param updateAuthority Address initiating the update |
||
| 1394 |
/// @param timestamp Timestamp of the PPS measurement |
||
| 1395 |
/// @return isExempt True if the authority is exempt from paying upkeep |
||
| 1396 |
function _isExemptFromUpkeep( |
||
| 1397 |
address strategy, |
||
| 1398 |
address updateAuthority, |
||
| 1399 |
uint256 timestamp |
||
| 1400 |
) internal returns (bool) {
|
||
| 1401 |
// Check if upkeep payments are globally disabled in SuperGovernor |
||
| 1402 |
if (!SUPER_GOVERNOR.isUpkeepPaymentsEnabled()) {
|
||
| 1403 |
return true; |
||
| 1404 |
} |
||
| 1405 | |||
| 1406 |
// Update is exempt if it is stale |
||
| 1407 |
if ( |
||
| 1408 |
block.timestamp - timestamp > _strategyData[strategy].maxStaleness |
||
| 1409 |
) {
|
||
| 1410 |
emit StaleUpdate(strategy, updateAuthority, timestamp); |
||
| 1411 |
return true; |
||
| 1412 |
} |
||
| 1413 | |||
| 1414 |
// If manager is a superform manager, they're exempt from upkeep fees |
||
| 1415 |
address manager = _strategyData[strategy].mainManager; |
||
| 1416 |
if (SUPER_GOVERNOR.isSuperformManager(manager)) {
|
||
| 1417 |
return true; |
||
| 1418 |
} |
||
| 1419 | |||
| 1420 |
// Check if the updateAuthority is in the authorized callers list |
||
| 1421 |
// These are manager-designated keepers that should be exempt from fees |
||
| 1422 |
// NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller) |
||
| 1423 |
if ( |
||
| 1424 |
_strategyData[strategy].authorizedCallers.contains(updateAuthority) |
||
| 1425 |
) {
|
||
| 1426 |
return true; |
||
| 1427 |
} |
||
| 1428 | |||
| 1429 |
return false; |
||
| 1430 |
} |
||
| 1431 | |||
| 1432 |
/// @notice Creates a leaf node for Merkle verification from hook address and arguments |
||
| 1433 |
/// @param hookAddress The address of the hook contract |
||
| 1434 |
/// @param hookArgs The packed-encoded hook arguments (from solidityPack in JS) |
||
| 1435 |
/// @return leaf The leaf node hash |
||
| 1436 |
function _createLeaf( |
||
| 1437 |
address hookAddress, |
||
| 1438 |
bytes calldata hookArgs |
||
| 1439 |
) internal pure returns (bytes32) {
|
||
| 1440 |
/// @dev The leaf now includes both hook address and args to prevent cross-hook replay attacks |
||
| 1441 |
/// @dev Different hooks with identical encoded args will have different authorization leaves |
||
| 1442 |
/// @dev This matches StandardMerkleTree's standardLeafHash: keccak256(keccak256(abi.encode(hookAddress, |
||
| 1443 |
/// hookArgs))) |
||
| 1444 |
/// @dev but uses bytes.concat for explicit concatenation |
||
| 1445 |
return |
||
| 1446 |
keccak256( |
||
| 1447 |
bytes.concat(keccak256(abi.encode(hookAddress, hookArgs))) |
||
| 1448 |
); |
||
| 1449 |
} |
||
| 1450 | |||
| 1451 |
/** |
||
| 1452 |
* @dev Internal function to validate a single hook against either global or strategy root |
||
| 1453 |
* @param hookAddress The address of the hook contract |
||
| 1454 |
* @param hookArgs Hook arguments |
||
| 1455 |
* @param proof Merkle proof for the specified root |
||
| 1456 |
* @param isGlobalProof Whether to validate against global root (true) or strategy root (false) |
||
| 1457 |
* @param cache Cached hook validation state variables |
||
| 1458 |
* @param strategy Address of the strategy (needed to check banned leaves for global proofs) |
||
| 1459 |
* @return True if hook is valid, false otherwise |
||
| 1460 |
*/ |
||
| 1461 |
function _validateSingleHook( |
||
| 1462 |
address hookAddress, |
||
| 1463 |
bytes calldata hookArgs, |
||
| 1464 |
bytes32[] calldata proof, |
||
| 1465 |
bool isGlobalProof, |
||
| 1466 |
HookValidationCache memory cache, |
||
| 1467 |
address strategy |
||
| 1468 |
) internal view returns (bool) {
|
||
| 1469 |
// Create leaf node from the hook address and arguments |
||
| 1470 |
bytes32 leaf = _createLeaf(hookAddress, hookArgs); |
||
| 1471 | |||
| 1472 |
if (isGlobalProof) {
|
||
| 1473 |
// Validate against global root |
||
| 1474 |
if ( |
||
| 1475 |
cache.globalHooksRootVetoed || |
||
| 1476 |
cache.globalHooksRoot == bytes32(0) |
||
| 1477 |
) {
|
||
| 1478 |
return false; |
||
| 1479 |
} |
||
| 1480 | |||
| 1481 |
// Check if this leaf is banned by the manager |
||
| 1482 |
if (_strategyData[strategy].bannedLeaves[leaf]) {
|
||
| 1483 |
return false; |
||
| 1484 |
} |
||
| 1485 | |||
| 1486 |
// For single-leaf trees, empty proof is valid when root equals leaf |
||
| 1487 |
if (proof.length == 0) {
|
||
| 1488 |
return cache.globalHooksRoot == leaf; |
||
| 1489 |
} |
||
| 1490 |
return MerkleProof.verify(proof, cache.globalHooksRoot, leaf); |
||
| 1491 |
} else {
|
||
| 1492 |
// Validate against strategy root |
||
| 1493 |
if ( |
||
| 1494 |
cache.strategyHooksRootVetoed || |
||
| 1495 |
cache.strategyRoot == bytes32(0) |
||
| 1496 |
) {
|
||
| 1497 |
return false; |
||
| 1498 |
} |
||
| 1499 |
// For single-leaf trees, empty proof is valid when root equals leaf |
||
| 1500 |
if (proof.length == 0) {
|
||
| 1501 |
return cache.strategyRoot == leaf; |
||
| 1502 |
} |
||
| 1503 |
return MerkleProof.verify(proof, cache.strategyRoot, leaf); |
||
| 1504 |
} |
||
| 1505 |
} |
||
| 1506 | |||
| 1507 |
/** |
||
| 1508 |
* @dev Internal function to return the `SuperBank` address |
||
| 1509 |
* @return superBank The superBank address |
||
| 1510 |
*/ |
||
| 1511 |
✓ 1.6K
|
function _getSuperBank() internal view returns (address) {
|
|
| 1512 |
✓ 1.6K
|
return SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK()); |
|
| 1513 |
} |
||
| 1514 |
} |
||
| 1515 |
75.0%
src/SuperVault/SuperVaultEscrow.sol
Lines covered: 9 / 12 (75.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 5 |
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
| 6 | |||
| 7 |
/// @title SuperVaultEscrow |
||
| 8 |
/// @author Superform Labs |
||
| 9 |
/// @notice Escrow contract for SuperVault shares during request/claim process |
||
| 10 |
✓ 1
|
contract SuperVaultEscrow {
|
|
| 11 |
using SafeERC20 for IERC20; |
||
| 12 | |||
| 13 |
/*////////////////////////////////////////////////////////////// |
||
| 14 |
ERRORS |
||
| 15 |
//////////////////////////////////////////////////////////////*/ |
||
| 16 |
error ALREADY_INITIALIZED(); |
||
| 17 |
error UNAUTHORIZED(); |
||
| 18 |
error ZERO_ADDRESS(); |
||
| 19 | |||
| 20 |
/*////////////////////////////////////////////////////////////// |
||
| 21 |
STATE |
||
| 22 |
//////////////////////////////////////////////////////////////*/ |
||
| 23 |
bool public initialized; |
||
| 24 |
address public vault; |
||
| 25 |
address public strategy; |
||
| 26 | |||
| 27 |
/*////////////////////////////////////////////////////////////// |
||
| 28 |
MODIFIERS |
||
| 29 |
//////////////////////////////////////////////////////////////*/ |
||
| 30 |
modifier onlyVault() {
|
||
| 31 |
✓ 364.6K
|
⟲ 223.2K
|
if (msg.sender != vault) revert UNAUTHORIZED(); |
| 32 |
_; |
||
| 33 |
} |
||
| 34 | |||
| 35 |
/*////////////////////////////////////////////////////////////// |
||
| 36 |
INITIALIZATION |
||
| 37 |
//////////////////////////////////////////////////////////////*/ |
||
| 38 | |||
| 39 |
/// @notice Initialize the escrow with required parameters |
||
| 40 |
/// @param vaultAddress The vault contract address |
||
| 41 |
/// @param strategyAddress The strategy contract address |
||
| 42 |
function initialize(address vaultAddress, address strategyAddress) external {
|
||
| 43 |
✓ 251.5K
|
⟲ 129.3K
|
if (initialized) revert ALREADY_INITIALIZED(); |
| 44 |
✓ 122.2K
|
if (vaultAddress == address(0) || strategyAddress == address(0)) revert ZERO_ADDRESS(); |
|
| 45 | |||
| 46 |
✓ 122.2K
|
initialized = true; |
|
| 47 |
✓ 122.2K
|
vault = vaultAddress; |
|
| 48 |
✓ 122.2K
|
strategy = strategyAddress; |
|
| 49 |
} |
||
| 50 | |||
| 51 |
/*////////////////////////////////////////////////////////////// |
||
| 52 |
VAULT FUNCTIONS |
||
| 53 |
//////////////////////////////////////////////////////////////*/ |
||
| 54 | |||
| 55 |
/// @notice Transfer shares from user to escrow during redeem request |
||
| 56 |
/// @param from The address to transfer shares from |
||
| 57 |
/// @param amount The amount of shares to transfer |
||
| 58 |
function escrowShares(address from, uint256 amount) external onlyVault {
|
||
| 59 |
✓ 268.4K
|
IERC20(vault).safeTransferFrom(from, address(this), amount); |
|
| 60 |
} |
||
| 61 | |||
| 62 |
/// @notice Return shares from escrow to user during redeem cancellation |
||
| 63 |
/// @param to The address to return shares to |
||
| 64 |
/// @param amount The amount of shares to return |
||
| 65 |
function returnShares(address to, uint256 amount) external onlyVault {
|
||
| 66 |
✓ 1.3K
|
IERC20(vault).safeTransfer(to, amount); |
|
| 67 |
} |
||
| 68 |
} |
||
| 69 |
81.6%
src/SuperVault/SuperVaultStrategy.sol
Lines covered: 479 / 587 (81.6%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
// External |
||
| 5 |
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||
| 6 |
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
||
| 7 |
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
|
||
| 8 |
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
| 9 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 10 |
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||
| 11 |
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 12 |
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||
| 13 | |||
| 14 |
// Core Interfaces |
||
| 15 |
import {ISuperHook, ISuperHookResult, ISuperHookOutflow, ISuperHookInflowOutflow, ISuperHookResultOutflow, ISuperHookContextAware, ISuperHookInspector} from "@superform-v2-core/src/interfaces/ISuperHook.sol";
|
||
| 16 |
import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
|
||
| 17 | |||
| 18 |
// Periphery Interfaces |
||
| 19 |
import {ISuperVault} from "../interfaces/SuperVault/ISuperVault.sol";
|
||
| 20 |
import {HookDataDecoder} from "@superform-v2-core/src/libraries/HookDataDecoder.sol";
|
||
| 21 |
import {ISuperVaultStrategy} from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 22 |
import {ISuperGovernor, FeeType} from "../interfaces/ISuperGovernor.sol";
|
||
| 23 |
import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
|
||
| 24 | |||
| 25 |
/// @title SuperVaultStrategy |
||
| 26 |
/// @author Superform Labs |
||
| 27 |
/// @notice Strategy implementation for SuperVault that executes strategies |
||
| 28 |
contract SuperVaultStrategy is |
||
| 29 |
ISuperVaultStrategy, |
||
| 30 |
Initializable, |
||
| 31 |
ReentrancyGuardUpgradeable |
||
| 32 |
{
|
||
| 33 |
using EnumerableSet for EnumerableSet.AddressSet; |
||
| 34 |
using SafeERC20 for IERC20; |
||
| 35 |
using Math for uint256; |
||
| 36 | |||
| 37 |
/*////////////////////////////////////////////////////////////// |
||
| 38 |
CONSTANTS |
||
| 39 |
//////////////////////////////////////////////////////////////*/ |
||
| 40 |
✓ 62.1K
|
uint256 private constant ONE_WEEK = 7 days; |
|
| 41 |
✓ 698.0K
|
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 |
✓ 366.9K
|
uint256 private constant TOLERANCE_CONSTANT = 10 wei; |
|
| 45 | |||
| 46 |
// Slippage tolerance in BPS (1%) |
||
| 47 |
✓ 126.1K
|
uint256 private constant SV_SLIPPAGE_TOLERANCE_BPS = 100; |
|
| 48 | |||
| 49 |
uint256 public PRECISION; |
||
| 50 | |||
| 51 |
/*////////////////////////////////////////////////////////////// |
||
| 52 |
STATE |
||
| 53 |
//////////////////////////////////////////////////////////////*/ |
||
| 54 |
address private _vault; |
||
| 55 |
IERC20 private _asset; |
||
| 56 |
uint8 private _vaultDecimals; |
||
| 57 | |||
| 58 |
// Global configuration |
||
| 59 |
uint256 private _maxPPSSlippage; |
||
| 60 | |||
| 61 |
// Fee configuration |
||
| 62 |
FeeConfig private feeConfig; |
||
| 63 |
FeeConfig private proposedFeeConfig; |
||
| 64 |
uint256 private feeConfigEffectiveTime; |
||
| 65 | |||
| 66 |
// Core contracts |
||
| 67 |
ISuperGovernor public immutable superGovernor; |
||
| 68 | |||
| 69 |
// Emergency withdrawable configuration |
||
| 70 |
bool public emergencyWithdrawable; |
||
| 71 |
bool public proposedEmergencyWithdrawable; |
||
| 72 |
uint256 public emergencyWithdrawableEffectiveTime; |
||
| 73 | |||
| 74 |
// Yield source configuration - simplified mapping from source to oracle |
||
| 75 |
mapping(address source => address oracle) private yieldSources; |
||
| 76 |
EnumerableSet.AddressSet private yieldSourcesList; |
||
| 77 | |||
| 78 |
// --- Redeem Request State --- |
||
| 79 |
mapping(address controller => SuperVaultState state) |
||
| 80 |
private superVaultState; |
||
| 81 | |||
| 82 |
constructor(address superGovernor_) {
|
||
| 83 |
✓ 1
|
if (superGovernor_ == address(0)) revert ZERO_ADDRESS(); |
|
| 84 | |||
| 85 |
✓ 1
|
superGovernor = ISuperGovernor(superGovernor_); |
|
| 86 |
✓ 1
|
emit SuperGovernorSet(superGovernor_); |
|
| 87 |
✓ 1
|
_disableInitializers(); |
|
| 88 |
} |
||
| 89 | |||
| 90 |
/// @notice Allows the contract to receive native ETH |
||
| 91 |
/// @dev Required for hooks that may send ETH back to the strategy |
||
| 92 |
receive() external payable {}
|
||
| 93 | |||
| 94 |
/*////////////////////////////////////////////////////////////// |
||
| 95 |
INITIALIZATION |
||
| 96 |
//////////////////////////////////////////////////////////////*/ |
||
| 97 |
function initialize( |
||
| 98 |
address vaultAddress, |
||
| 99 |
FeeConfig memory feeConfigData |
||
| 100 |
) external initializer {
|
||
| 101 |
✓ 122.2K
|
if (vaultAddress == address(0)) revert INVALID_VAULT(); |
|
| 102 |
// if either fee is configured, check if recipient is address (0), if it is revert with ZERO ADDRESS |
||
| 103 |
// if both fees are 0, no need check address (it just passes the if). Recipient can be configured later |
||
| 104 |
if ( |
||
| 105 |
✓ 122.2K
|
(feeConfigData.performanceFeeBps > 0 || |
|
| 106 |
✓ 3.9K
|
feeConfigData.managementFeeBps > 0) && |
|
| 107 |
✓ 122.0K
|
feeConfigData.recipient == address(0) |
|
| 108 |
) revert ZERO_ADDRESS(); |
||
| 109 |
✓ 122.2K
|
if (feeConfigData.performanceFeeBps > BPS_PRECISION) |
|
| 110 |
✓ 8.4K
|
⟲ 8.4K
|
revert INVALID_PERFORMANCE_FEE_BPS(); |
| 111 |
✓ 113.8K
|
if (feeConfigData.managementFeeBps > BPS_PRECISION) |
|
| 112 |
✓ 1.2K
|
⟲ 1.2K
|
revert INVALID_PERFORMANCE_FEE_BPS(); |
| 113 | |||
| 114 |
✓ 112.6K
|
__ReentrancyGuard_init(); |
|
| 115 | |||
| 116 |
✓ 112.6K
|
_vault = vaultAddress; |
|
| 117 |
✓ 112.6K
|
_asset = IERC20(IERC4626(vaultAddress).asset()); |
|
| 118 |
✓ 112.6K
|
_vaultDecimals = IERC20Metadata(vaultAddress).decimals(); |
|
| 119 |
✓ 112.6K
|
PRECISION = 10 ** _vaultDecimals; |
|
| 120 |
✓ 112.6K
|
feeConfig = feeConfigData; |
|
| 121 |
✓ 112.6K
|
_maxPPSSlippage = 500; // 5% as a start, configurable later |
|
| 122 | |||
| 123 |
✓ 112.6K
|
emit Initialized(_vault); |
|
| 124 |
} |
||
| 125 | |||
| 126 |
/*////////////////////////////////////////////////////////////// |
||
| 127 |
CORE STRATEGY OPERATIONS |
||
| 128 |
//////////////////////////////////////////////////////////////*/ |
||
| 129 | |||
| 130 |
/// @inheritdoc ISuperVaultStrategy |
||
| 131 |
function handleOperations4626Deposit( |
||
| 132 |
address controller, |
||
| 133 |
uint256 assetsGross |
||
| 134 |
✓ 261.2K
|
) external returns (uint256 sharesNet) {
|
|
| 135 |
✓ 261.2K
|
_requireVault(); |
|
| 136 | |||
| 137 |
✓ 102.6K
|
if (assetsGross == 0) revert INVALID_AMOUNT(); |
|
| 138 |
✓ 102.6K
|
if (controller == address(0)) revert ZERO_ADDRESS(); |
|
| 139 | |||
| 140 |
// Check if strategy is paused or if global hooks root is vetoed |
||
| 141 |
✓ 102.6K
|
⟲ 5.2K
|
if (_isPaused()) revert STRATEGY_PAUSED(); |
| 142 |
✓ 97.4K
|
if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) {
|
|
| 143 |
revert OPERATIONS_BLOCKED_BY_VETO(); |
||
| 144 |
} |
||
| 145 | |||
| 146 |
// Fee skim in ASSETS (asset-side entry fee) |
||
| 147 |
✓ 97.4K
|
uint256 feeBps = feeConfig.managementFeeBps; |
|
| 148 |
✓ 97.4K
|
uint256 feeAssets = feeBps == 0 |
|
| 149 |
✓ 1.4K
|
? 0 |
|
| 150 |
✓ 96.0K
|
: Math.mulDiv( |
|
| 151 |
✓ 96.0K
|
assetsGross, |
|
| 152 |
✓ 96.0K
|
feeBps, |
|
| 153 |
BPS_PRECISION, |
||
| 154 |
✓ 96.0K
|
Math.Rounding.Ceil |
|
| 155 |
); |
||
| 156 | |||
| 157 |
✓ 97.4K
|
uint256 assetsNet = assetsGross - feeAssets; |
|
| 158 |
✓ 97.4K
|
⟲ 4.1K
|
if (assetsNet == 0) revert INVALID_AMOUNT(); |
| 159 | |||
| 160 |
✓ 93.3K
|
if (feeAssets != 0) {
|
|
| 161 |
✓ 91.9K
|
address recipient = feeConfig.recipient; |
|
| 162 |
if (recipient == address(0)) revert ZERO_ADDRESS(); |
||
| 163 |
✓ 91.9K
|
_safeTokenTransfer(address(_asset), recipient, feeAssets); |
|
| 164 |
✓ 91.9K
|
emit ManagementFeePaid(controller, recipient, feeAssets, feeBps); |
|
| 165 |
} |
||
| 166 | |||
| 167 |
// Compute shares on NET using current PPS |
||
| 168 |
✓ 93.3K
|
uint256 pps = getStoredPPS(); |
|
| 169 |
✓ 93.3K
|
⟲ 667
|
if (pps == 0) revert INVALID_PPS(); |
| 170 |
✓ 92.7K
|
sharesNet = Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor); |
|
| 171 |
✓ 92.7K
|
⟲ 9.9K
|
if (sharesNet == 0) revert INVALID_AMOUNT(); |
| 172 | |||
| 173 |
// Account on NET |
||
| 174 |
✓ 82.8K
|
SuperVaultState storage state = superVaultState[controller]; |
|
| 175 |
✓ 82.8K
|
state.accumulatorShares += sharesNet; |
|
| 176 |
✓ 82.8K
|
state.accumulatorCostBasis += assetsNet; |
|
| 177 |
✓ 82.8K
|
emit DepositHandled(controller, assetsNet, sharesNet); |
|
| 178 |
✓ 82.8K
|
return sharesNet; |
|
| 179 |
} |
||
| 180 | |||
| 181 |
/// @inheritdoc ISuperVaultStrategy |
||
| 182 |
function handleOperations4626Mint( |
||
| 183 |
address controller, |
||
| 184 |
uint256 sharesNet, |
||
| 185 |
uint256 assetsGross, |
||
| 186 |
uint256 assetsNet |
||
| 187 |
) external {
|
||
| 188 |
✓ 406.6K
|
_requireVault(); |
|
| 189 | |||
| 190 |
✓ 277.7K
|
if (sharesNet == 0) revert INVALID_AMOUNT(); |
|
| 191 |
✓ 277.7K
|
if (controller == address(0)) revert ZERO_ADDRESS(); |
|
| 192 | |||
| 193 |
// Check if strategy is paused or if global hooks root is vetoed |
||
| 194 |
✓ 277.7K
|
⟲ 7.1K
|
if (_isPaused()) revert STRATEGY_PAUSED(); |
| 195 |
✓ 270.7K
|
if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) {
|
|
| 196 |
revert OPERATIONS_BLOCKED_BY_VETO(); |
||
| 197 |
} |
||
| 198 | |||
| 199 |
✓ 270.7K
|
uint256 feeBps = feeConfig.managementFeeBps; |
|
| 200 |
// Transfer fee if needed |
||
| 201 |
✓ 270.7K
|
if (feeBps != 0) {
|
|
| 202 |
✓ 269.9K
|
uint256 feeAssets = assetsGross - assetsNet; |
|
| 203 |
✓ 269.9K
|
if (feeAssets != 0) {
|
|
| 204 |
✓ 269.9K
|
address recipient = feeConfig.recipient; |
|
| 205 |
if (recipient == address(0)) revert ZERO_ADDRESS(); |
||
| 206 |
✓ 269.9K
|
_safeTokenTransfer(address(_asset), recipient, feeAssets); |
|
| 207 |
emit ManagementFeePaid( |
||
| 208 |
✓ 269.9K
|
controller, |
|
| 209 |
✓ 269.9K
|
recipient, |
|
| 210 |
✓ 269.9K
|
feeAssets, |
|
| 211 |
✓ 269.9K
|
feeBps |
|
| 212 |
); |
||
| 213 |
} |
||
| 214 |
} |
||
| 215 | |||
| 216 |
// Account on NET |
||
| 217 |
✓ 270.7K
|
SuperVaultState storage state = superVaultState[controller]; |
|
| 218 |
✓ 270.7K
|
state.accumulatorShares += sharesNet; |
|
| 219 |
✓ 270.7K
|
state.accumulatorCostBasis += assetsNet; |
|
| 220 |
✓ 270.7K
|
emit DepositHandled(controller, assetsNet, sharesNet); |
|
| 221 |
} |
||
| 222 | |||
| 223 |
/// @inheritdoc ISuperVaultStrategy |
||
| 224 |
function quoteMintAssetsGross( |
||
| 225 |
uint256 shares |
||
| 226 |
✓ 766.4K
|
) external view returns (uint256 assetsGross, uint256 assetsNet) {
|
|
| 227 |
✓ 766.4K
|
uint256 pps = getStoredPPS(); |
|
| 228 |
✓ 766.4K
|
⟲ 5.4K
|
if (pps == 0) revert INVALID_PPS(); |
| 229 |
✓ 761.0K
|
assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil); |
|
| 230 |
✓ 721.0K
|
if (assetsNet == 0) revert INVALID_AMOUNT(); |
|
| 231 | |||
| 232 |
✓ 721.0K
|
uint256 feeBps = feeConfig.managementFeeBps; |
|
| 233 |
✓ 721.0K
|
if (feeBps == 0) return (assetsNet, assetsNet); |
|
| 234 |
✓ 698.0K
|
⟲ 3.7K
|
if (feeBps >= BPS_PRECISION) revert INVALID_AMOUNT(); // prevents div-by-zero (100% fee) |
| 235 |
✓ 694.3K
|
assetsGross = Math.mulDiv( |
|
| 236 |
✓ 694.3K
|
assetsNet, |
|
| 237 |
BPS_PRECISION, |
||
| 238 |
✓ 694.3K
|
(BPS_PRECISION - feeBps), |
|
| 239 |
✓ 694.3K
|
Math.Rounding.Ceil |
|
| 240 |
); |
||
| 241 |
✓ 621.2K
|
return (assetsGross, assetsNet); |
|
| 242 |
} |
||
| 243 | |||
| 244 |
/// @inheritdoc ISuperVaultStrategy |
||
| 245 |
function handleOperations7540( |
||
| 246 |
Operation operation, |
||
| 247 |
address controller, |
||
| 248 |
address receiver, |
||
| 249 |
uint256 amount |
||
| 250 |
) external {
|
||
| 251 |
✓ 564.0K
|
_requireVault(); |
|
| 252 |
✓ 439.2K
|
if (operation == Operation.RedeemRequest) {
|
|
| 253 |
✓ 268.4K
|
_handleRequestRedeem(controller, amount); // amount = shares |
|
| 254 |
✓ 170.8K
|
} else if (operation == Operation.CancelRedeem) {
|
|
| 255 |
✓ 143.6K
|
_handleCancelRedeem(controller); |
|
| 256 |
✓ 27.2K
|
} else if (operation == Operation.ClaimRedeem) {
|
|
| 257 |
✓ 27.2K
|
_handleClaimRedeem(controller, receiver, amount); // amount = assets |
|
| 258 |
} else {
|
||
| 259 |
✓ 241.3K
|
⟲ 241.3K
|
revert ACTION_TYPE_DISALLOWED(); |
| 260 |
} |
||
| 261 |
} |
||
| 262 | |||
| 263 |
/*////////////////////////////////////////////////////////////// |
||
| 264 |
MANAGER EXTERNAL ACCESS FUNCTIONS |
||
| 265 |
//////////////////////////////////////////////////////////////*/ |
||
| 266 | |||
| 267 |
/// @inheritdoc ISuperVaultStrategy |
||
| 268 |
function executeHooks( |
||
| 269 |
ExecuteArgs calldata args |
||
| 270 |
) external payable nonReentrant {
|
||
| 271 |
✓ 264.6K
|
_isManager(msg.sender); |
|
| 272 | |||
| 273 |
✓ 250.2K
|
uint256 hooksLength = args.hooks.length; |
|
| 274 |
✓ 250.2K
|
⟲ 1.3K
|
if (hooksLength == 0) revert ZERO_LENGTH(); |
| 275 |
✓ 248.9K
|
if (args.hookCalldata.length != hooksLength) |
|
| 276 |
✓ 113.8K
|
⟲ 113.8K
|
revert INVALID_ARRAY_LENGTH(); |
| 277 |
✓ 135.1K
|
if (args.expectedAssetsOrSharesOut.length != hooksLength) |
|
| 278 |
✓ 7.2K
|
⟲ 7.2K
|
revert INVALID_ARRAY_LENGTH(); |
| 279 |
✓ 127.9K
|
if (args.globalProofs.length != hooksLength) |
|
| 280 |
✓ 352
|
⟲ 352
|
revert INVALID_ARRAY_LENGTH(); |
| 281 |
✓ 127.5K
|
if (args.strategyProofs.length != hooksLength) |
|
| 282 |
revert INVALID_ARRAY_LENGTH(); |
||
| 283 | |||
| 284 |
✓ 127.5K
|
address prevHook; |
|
| 285 |
✓ 141.8K
|
for (uint256 i; i < hooksLength; ++i) {
|
|
| 286 |
✓ 127.5K
|
address hook = args.hooks[i]; |
|
| 287 |
✓ 127.5K
|
if (!_isRegisteredHook(hook)) revert INVALID_HOOK(); |
|
| 288 | |||
| 289 |
// Check if the hook was validated |
||
| 290 |
if ( |
||
| 291 |
!_validateHook( |
||
| 292 |
✓ 127.5K
|
hook, |
|
| 293 |
✓ 127.5K
|
args.hookCalldata[i], |
|
| 294 |
✓ 127.5K
|
args.globalProofs[i], |
|
| 295 |
✓ 127.5K
|
args.strategyProofs[i] |
|
| 296 |
) |
||
| 297 |
) {
|
||
| 298 |
revert HOOK_VALIDATION_FAILED(); |
||
| 299 |
} |
||
| 300 | |||
| 301 |
✓ 127.5K
|
prevHook = _processSingleHookExecution( |
|
| 302 |
✓ 127.5K
|
hook, |
|
| 303 |
✓ 127.5K
|
prevHook, |
|
| 304 |
✓ 127.5K
|
args.hookCalldata[i], |
|
| 305 |
✓ 127.5K
|
args.expectedAssetsOrSharesOut[i] |
|
| 306 |
); |
||
| 307 |
} |
||
| 308 |
✓ 14.3K
|
emit HooksExecuted(args.hooks); |
|
| 309 |
} |
||
| 310 | |||
| 311 |
/// @inheritdoc ISuperVaultStrategy |
||
| 312 |
function fulfillRedeemRequests( |
||
| 313 |
FulfillArgs calldata args |
||
| 314 |
) external nonReentrant {
|
||
| 315 |
✓ 389.7K
|
_isManager(msg.sender); |
|
| 316 | |||
| 317 |
// Check if strategy is paused |
||
| 318 |
✓ 386.7K
|
⟲ 1.6K
|
if (_isPaused()) revert STRATEGY_PAUSED(); |
| 319 | |||
| 320 |
✓ 385.1K
|
uint256 hooksLength = args.hooks.length; |
|
| 321 |
✓ 385.1K
|
⟲ 419
|
if (hooksLength == 0) revert ZERO_LENGTH(); |
| 322 |
✓ 384.7K
|
uint256 controllersLength = args.controllers.length; |
|
| 323 |
✓ 384.7K
|
⟲ 2.0K
|
if (controllersLength == 0) revert ZERO_LENGTH(); |
| 324 |
✓ 382.7K
|
if (args.hookCalldata.length != hooksLength) |
|
| 325 |
✓ 3.6K
|
⟲ 3.6K
|
revert INVALID_ARRAY_LENGTH(); |
| 326 |
✓ 379.1K
|
if (args.expectedAssetsOrSharesOut.length != hooksLength) |
|
| 327 |
✓ 100
|
⟲ 100
|
revert INVALID_ARRAY_LENGTH(); |
| 328 |
✓ 379.0K
|
if (args.globalProofs.length != hooksLength) |
|
| 329 |
✓ 1.4K
|
⟲ 1.4K
|
revert INVALID_ARRAY_LENGTH(); |
| 330 |
✓ 377.6K
|
if (args.strategyProofs.length != hooksLength) |
|
| 331 |
revert INVALID_ARRAY_LENGTH(); |
||
| 332 | |||
| 333 |
✓ 377.6K
|
uint256 currentPPS = getStoredPPS(); |
|
| 334 |
✓ 377.6K
|
⟲ 792
|
if (currentPPS == 0) revert INVALID_PPS(); |
| 335 | |||
| 336 |
// Pre-calculate totals to ensure no overburn of escrowed shares |
||
| 337 |
✓ 376.8K
|
uint256 totalRequestedShares; |
|
| 338 |
✓ 757.1K
|
for (uint256 i; i < controllersLength; ++i) {
|
|
| 339 |
✓ 380.3K
|
totalRequestedShares += superVaultState[args.controllers[i]] |
|
| 340 |
.pendingRedeemRequest; |
||
| 341 |
} |
||
| 342 |
✓ 376.8K
|
uint256 intendedShares; |
|
| 343 |
✓ 743.7K
|
for (uint256 i; i < hooksLength; ++i) {
|
|
| 344 |
✓ 376.8K
|
address hook = args.hooks[i]; |
|
| 345 |
✓ 376.8K
|
if (!_isFulfillRequestsHook(hook)) revert INVALID_HOOK(); |
|
| 346 |
// Check if the hook was validated |
||
| 347 |
if ( |
||
| 348 |
✓ 504.3K
|
!_validateHook( |
|
| 349 |
✓ 376.8K
|
hook, |
|
| 350 |
✓ 376.8K
|
args.hookCalldata[i], |
|
| 351 |
✓ 376.8K
|
args.globalProofs[i], |
|
| 352 |
✓ 504.3K
|
args.strategyProofs[i] |
|
| 353 |
) |
||
| 354 |
) {
|
||
| 355 |
revert HOOK_VALIDATION_FAILED(); |
||
| 356 |
} |
||
| 357 |
✓ 376.8K
|
if (args.expectedAssetsOrSharesOut[i] == 0) |
|
| 358 |
✓ 1.7K
|
⟲ 1.7K
|
revert ZERO_EXPECTED_VALUE(); |
| 359 | |||
| 360 |
✓ 375.1K
|
intendedShares += ISuperHookInflowOutflow(hook).decodeAmount( |
|
| 361 |
✓ 375.1K
|
args.hookCalldata[i] |
|
| 362 |
); |
||
| 363 |
} |
||
| 364 | |||
| 365 |
// Enforce both lower- and upper-bounds on intended shares to prevent escrow overburn |
||
| 366 |
✓ 366.9K
|
if (intendedShares + TOLERANCE_CONSTANT < totalRequestedShares) {
|
|
| 367 |
✓ 14.2K
|
⟲ 14.2K
|
revert INVALID_REDEEM_FILL(); |
| 368 |
} |
||
| 369 | |||
| 370 |
✓ 352.7K
|
if (intendedShares > totalRequestedShares + TOLERANCE_CONSTANT) {
|
|
| 371 |
revert INVALID_REDEEM_FILL(); |
||
| 372 |
} |
||
| 373 | |||
| 374 |
✓ 352.7K
|
uint256 processedShares; |
|
| 375 |
✓ 477.1K
|
for (uint256 i; i < hooksLength; ++i) {
|
|
| 376 |
✓ 352.7K
|
address hook = args.hooks[i]; |
|
| 377 |
✓ 352.7K
|
uint256 amountSharesSpent = _processSingleFulfillHookExecution( |
|
| 378 |
hook, |
||
| 379 |
✓ 352.7K
|
args.hookCalldata[i], |
|
| 380 |
✓ 352.7K
|
args.expectedAssetsOrSharesOut[i], |
|
| 381 |
✓ 352.7K
|
currentPPS |
|
| 382 |
); |
||
| 383 |
✓ 124.4K
|
processedShares += amountSharesSpent; |
|
| 384 |
} |
||
| 385 | |||
| 386 |
// Post-condition: processed shares must match intended shares |
||
| 387 |
✓ 124.4K
|
if (processedShares != intendedShares) revert INVALID_REDEEM_FILL(); |
|
| 388 | |||
| 389 |
✓ 124.4K
|
_processRedeemFulfillments( |
|
| 390 |
✓ 124.4K
|
args.controllers, |
|
| 391 |
✓ 124.4K
|
controllersLength, |
|
| 392 |
✓ 124.4K
|
processedShares, |
|
| 393 |
✓ 124.4K
|
currentPPS |
|
| 394 |
); |
||
| 395 | |||
| 396 |
✓ 124.4K
|
ISuperVault(_vault).burnShares(processedShares); |
|
| 397 | |||
| 398 |
emit RedeemRequestsFulfilled( |
||
| 399 |
✓ 124.4K
|
args.hooks, |
|
| 400 |
✓ 124.4K
|
args.controllers, |
|
| 401 |
✓ 124.4K
|
processedShares, |
|
| 402 |
✓ 124.4K
|
currentPPS |
|
| 403 |
); |
||
| 404 |
} |
||
| 405 | |||
| 406 |
/*////////////////////////////////////////////////////////////// |
||
| 407 |
YIELD SOURCE MANAGEMENT |
||
| 408 |
//////////////////////////////////////////////////////////////*/ |
||
| 409 | |||
| 410 |
// @inheritdoc ISuperVaultStrategy |
||
| 411 |
function manageYieldSource( |
||
| 412 |
address source, |
||
| 413 |
address oracle, |
||
| 414 |
uint8 actionType |
||
| 415 |
) external {
|
||
| 416 |
✓ 448.0K
|
_isPrimaryManager(msg.sender); |
|
| 417 |
✓ 422.8K
|
_manageYieldSource(source, oracle, actionType); |
|
| 418 |
} |
||
| 419 | |||
| 420 |
// @inheritdoc ISuperVaultStrategy |
||
| 421 |
function manageYieldSources( |
||
| 422 |
address[] calldata sources, |
||
| 423 |
address[] calldata oracles, |
||
| 424 |
uint8[] calldata actionTypes |
||
| 425 |
) external {
|
||
| 426 |
✓ 121.5K
|
_isPrimaryManager(msg.sender); |
|
| 427 | |||
| 428 |
✓ 114.1K
|
uint256 length = sources.length; |
|
| 429 |
✓ 114.1K
|
⟲ 731
|
if (length == 0) revert ZERO_LENGTH(); |
| 430 |
✓ 113.4K
|
⟲ 112.5K
|
if (oracles.length != length) revert INVALID_ARRAY_LENGTH(); |
| 431 |
✓ 857
|
⟲ 665
|
if (actionTypes.length != length) revert INVALID_ARRAY_LENGTH(); |
| 432 | |||
| 433 |
✓ 192
|
for (uint256 i; i < length; ++i) {
|
|
| 434 |
✓ 192
|
_manageYieldSource(sources[i], oracles[i], actionTypes[i]); |
|
| 435 |
} |
||
| 436 |
} |
||
| 437 | |||
| 438 |
// @inheritdoc ISuperVaultStrategy |
||
| 439 |
function proposeVaultFeeConfigUpdate( |
||
| 440 |
uint256 performanceFeeBps, |
||
| 441 |
uint256 managementFeeBps, |
||
| 442 |
address recipient |
||
| 443 |
) external {
|
||
| 444 |
✓ 199.7K
|
_isPrimaryManager(msg.sender); |
|
| 445 | |||
| 446 |
✓ 193.7K
|
if (performanceFeeBps > BPS_PRECISION) |
|
| 447 |
✓ 106.0K
|
⟲ 106.0K
|
revert INVALID_PERFORMANCE_FEE_BPS(); |
| 448 |
✓ 87.7K
|
if (managementFeeBps > BPS_PRECISION) |
|
| 449 |
✓ 18.6K
|
⟲ 18.6K
|
revert INVALID_PERFORMANCE_FEE_BPS(); |
| 450 |
✓ 69.1K
|
⟲ 7.1K
|
if (recipient == address(0)) revert ZERO_ADDRESS(); |
| 451 |
✓ 62.1K
|
proposedFeeConfig = FeeConfig({
|
|
| 452 |
performanceFeeBps: performanceFeeBps, |
||
| 453 |
managementFeeBps: managementFeeBps, |
||
| 454 |
recipient: recipient |
||
| 455 |
}); |
||
| 456 |
✓ 62.1K
|
feeConfigEffectiveTime = block.timestamp + ONE_WEEK; |
|
| 457 |
✓ 63.3K
|
emit VaultFeeConfigProposed( |
|
| 458 |
performanceFeeBps, |
||
| 459 |
managementFeeBps, |
||
| 460 |
recipient, |
||
| 461 |
feeConfigEffectiveTime |
||
| 462 |
); |
||
| 463 |
} |
||
| 464 | |||
| 465 |
// @inheritdoc ISuperVaultStrategy |
||
| 466 |
function executeVaultFeeConfigUpdate() external {
|
||
| 467 |
✓ 161.8K
|
if (block.timestamp < feeConfigEffectiveTime) |
|
| 468 |
✓ 125
|
⟲ 125
|
revert INVALID_TIMESTAMP(); |
| 469 |
✓ 161.6K
|
⟲ 104.5K
|
if (proposedFeeConfig.recipient == address(0)) revert ZERO_ADDRESS(); |
| 470 |
✓ 57.1K
|
feeConfig = proposedFeeConfig; |
|
| 471 |
✓ 57.1K
|
delete proposedFeeConfig; |
|
| 472 |
✓ 57.1K
|
feeConfigEffectiveTime = 0; |
|
| 473 |
✓ 57.1K
|
emit VaultFeeConfigUpdated( |
|
| 474 |
feeConfig.performanceFeeBps, |
||
| 475 |
feeConfig.managementFeeBps, |
||
| 476 |
feeConfig.recipient |
||
| 477 |
); |
||
| 478 |
} |
||
| 479 | |||
| 480 |
// @inheritdoc ISuperVaultStrategy |
||
| 481 |
function updateMaxPPSSlippage(uint256 maxSlippageBps) external {
|
||
| 482 |
✓ 124.0K
|
_isPrimaryManager(msg.sender); |
|
| 483 |
✓ 116.6K
|
⟲ 90.3K
|
if (maxSlippageBps > BPS_PRECISION) revert INVALID_MAX_SLIPPAGE_BPS(); |
| 484 |
✓ 26.2K
|
_maxPPSSlippage = maxSlippageBps; |
|
| 485 |
✓ 26.2K
|
emit MaxPPSSlippageUpdated(maxSlippageBps); |
|
| 486 |
} |
||
| 487 | |||
| 488 |
// @inheritdoc ISuperVaultStrategy |
||
| 489 |
function manageEmergencyWithdraw( |
||
| 490 |
uint8 action, |
||
| 491 |
address recipient, |
||
| 492 |
uint256 amount |
||
| 493 |
) external {
|
||
| 494 |
✓ 138.6K
|
if (action == 1) {
|
|
| 495 |
✓ 5.8K
|
_proposeEmergencyWithdraw(); |
|
| 496 |
✓ 132.7K
|
} else if (action == 2) {
|
|
| 497 |
✓ 2.7K
|
_executeEmergencyWithdrawActivation(); |
|
| 498 |
✓ 130.1K
|
} else if (action == 3) {
|
|
| 499 |
✓ 488
|
_performEmergencyWithdraw(recipient, amount); |
|
| 500 |
✓ 129.6K
|
} else if (action == 4) {
|
|
| 501 |
✓ 2.8K
|
_cancelEmergencyWithdrawProposal(); |
|
| 502 |
} else {
|
||
| 503 |
revert ACTION_TYPE_DISALLOWED(); |
||
| 504 |
} |
||
| 505 |
} |
||
| 506 | |||
| 507 |
/*////////////////////////////////////////////////////////////// |
||
| 508 |
ACCOUNTING MANAGEMENT |
||
| 509 |
//////////////////////////////////////////////////////////////*/ |
||
| 510 |
/// @inheritdoc ISuperVaultStrategy |
||
| 511 |
function moveAccumulatorOnTransfer( |
||
| 512 |
address from, |
||
| 513 |
address to, |
||
| 514 |
uint256 shares |
||
| 515 |
) external {
|
||
| 516 |
✓ 266.1K
|
_requireVault(); |
|
| 517 |
✓ 141.5K
|
if (shares == 0) return; |
|
| 518 | |||
| 519 |
✓ 141.5K
|
SuperVaultState storage fromState = superVaultState[from]; |
|
| 520 |
✓ 141.5K
|
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 |
✓ 141.5K
|
uint256 availableAccumulatorShares = fromState.accumulatorShares; |
|
| 526 |
✓ 141.5K
|
if (availableAccumulatorShares == 0) return; // No accumulator to move |
|
| 527 | |||
| 528 |
✓ 10.2K
|
uint256 sharesToMove = shares > availableAccumulatorShares |
|
| 529 |
✓ 5.7K
|
? availableAccumulatorShares |
|
| 530 |
✓ 4.5K
|
: shares; |
|
| 531 | |||
| 532 |
// Pro-rata move of cost basis (NO PPS here; preserves fee correctness) |
||
| 533 |
✓ 10.2K
|
uint256 movedCostBasis = sharesToMove == availableAccumulatorShares |
|
| 534 |
✓ 5.7K
|
? fromState.accumulatorCostBasis |
|
| 535 |
✓ 4.5K
|
: Math.mulDiv( |
|
| 536 |
✓ 4.5K
|
sharesToMove, |
|
| 537 |
✓ 4.5K
|
fromState.accumulatorCostBasis, |
|
| 538 |
✓ 4.5K
|
availableAccumulatorShares, |
|
| 539 |
✓ 4.5K
|
Math.Rounding.Floor |
|
| 540 |
); /// @audit is there a value st when transfered transfers minimal shares and maximum cost basis |
||
| 541 | |||
| 542 |
✓ 10.2K
|
fromState.accumulatorShares -= sharesToMove; |
|
| 543 |
✓ 10.2K
|
fromState.accumulatorCostBasis -= movedCostBasis; |
|
| 544 | |||
| 545 |
✓ 10.2K
|
toState.accumulatorShares += sharesToMove; |
|
| 546 |
✓ 10.2K
|
toState.accumulatorCostBasis += movedCostBasis; |
|
| 547 | |||
| 548 |
// Never touch: pendingRedeemRequest, averageRequestPPS, maxWithdraw, averageWithdrawPrice |
||
| 549 |
} |
||
| 550 | |||
| 551 |
/*////////////////////////////////////////////////////////////// |
||
| 552 |
VIEW FUNCTIONS |
||
| 553 |
//////////////////////////////////////////////////////////////*/ |
||
| 554 | |||
| 555 |
// @inheritdoc ISuperVaultStrategy |
||
| 556 |
function getVaultInfo() |
||
| 557 |
external |
||
| 558 |
view |
||
| 559 |
returns (address vault, address asset, uint8 vaultDecimals) |
||
| 560 |
{
|
||
| 561 |
vault = _vault; |
||
| 562 |
asset = address(_asset); |
||
| 563 |
vaultDecimals = _vaultDecimals; |
||
| 564 |
} |
||
| 565 | |||
| 566 |
// @inheritdoc ISuperVaultStrategy |
||
| 567 |
function getConfigInfo() |
||
| 568 |
external |
||
| 569 |
view |
||
| 570 |
✓ 2.2M
|
returns (FeeConfig memory feeConfig_) |
|
| 571 |
{
|
||
| 572 |
✓ 2.2M
|
feeConfig_ = feeConfig; |
|
| 573 |
} |
||
| 574 | |||
| 575 |
// @inheritdoc ISuperVaultStrategy |
||
| 576 |
✓ 12.6M
|
function getStoredPPS() public view returns (uint256) {
|
|
| 577 |
✓ 12.6M
|
return _getSuperVaultAggregator().getPPS(address(this)); |
|
| 578 |
} |
||
| 579 | |||
| 580 |
// @inheritdoc ISuperVaultStrategy |
||
| 581 |
function getSuperVaultState( |
||
| 582 |
address controller |
||
| 583 |
✓ 26.7M
|
) external view returns (SuperVaultState memory state) {
|
|
| 584 |
✓ 26.7M
|
return superVaultState[controller]; |
|
| 585 |
} |
||
| 586 | |||
| 587 |
// @inheritdoc ISuperVaultStrategy |
||
| 588 |
function getYieldSource( |
||
| 589 |
address source |
||
| 590 |
) external view returns (YieldSource memory) {
|
||
| 591 |
return YieldSource({oracle: yieldSources[source]});
|
||
| 592 |
} |
||
| 593 | |||
| 594 |
// @inheritdoc ISuperVaultStrategy |
||
| 595 |
function getYieldSourcesList() |
||
| 596 |
external |
||
| 597 |
view |
||
| 598 |
returns (YieldSourceInfo[] memory) |
||
| 599 |
{
|
||
| 600 |
uint256 length = yieldSourcesList.length(); |
||
| 601 |
YieldSourceInfo[] memory sourcesInfo = new YieldSourceInfo[](length); |
||
| 602 | |||
| 603 |
for (uint256 i; i < length; ++i) {
|
||
| 604 |
address sourceAddress = yieldSourcesList.at(i); |
||
| 605 |
address oracle = yieldSources[sourceAddress]; |
||
| 606 | |||
| 607 |
sourcesInfo[i] = YieldSourceInfo({
|
||
| 608 |
sourceAddress: sourceAddress, |
||
| 609 |
oracle: oracle |
||
| 610 |
}); |
||
| 611 |
} |
||
| 612 | |||
| 613 |
return sourcesInfo; |
||
| 614 |
} |
||
| 615 | |||
| 616 |
// @inheritdoc ISuperVaultStrategy |
||
| 617 |
function getYieldSources() external view returns (address[] memory) {
|
||
| 618 |
return yieldSourcesList.values(); |
||
| 619 |
} |
||
| 620 | |||
| 621 |
// @inheritdoc ISuperVaultStrategy |
||
| 622 |
function getYieldSourcesCount() external view returns (uint256) {
|
||
| 623 |
return yieldSourcesList.length(); |
||
| 624 |
} |
||
| 625 | |||
| 626 |
// @inheritdoc ISuperVaultStrategy |
||
| 627 |
function containsYieldSource(address source) external view returns (bool) {
|
||
| 628 |
return yieldSourcesList.contains(source); |
||
| 629 |
} |
||
| 630 | |||
| 631 |
// @inheritdoc ISuperVaultStrategy |
||
| 632 |
function pendingRedeemRequest( |
||
| 633 |
address controller |
||
| 634 |
✓ 15.8M
|
) external view returns (uint256 pendingShares) {
|
|
| 635 |
✓ 15.8M
|
return superVaultState[controller].pendingRedeemRequest; |
|
| 636 |
} |
||
| 637 | |||
| 638 |
// @inheritdoc ISuperVaultStrategy |
||
| 639 |
function claimableWithdraw( |
||
| 640 |
address controller |
||
| 641 |
✓ 757.1K
|
) external view returns (uint256 claimableAssets) {
|
|
| 642 |
✓ 757.1K
|
return superVaultState[controller].maxWithdraw; |
|
| 643 |
} |
||
| 644 | |||
| 645 |
// @inheritdoc ISuperVaultStrategy |
||
| 646 |
function getAverageWithdrawPrice( |
||
| 647 |
address controller |
||
| 648 |
✓ 5.4M
|
) external view returns (uint256 averageWithdrawPrice) {
|
|
| 649 |
✓ 5.4M
|
return superVaultState[controller].averageWithdrawPrice; |
|
| 650 |
} |
||
| 651 | |||
| 652 |
// @inheritdoc ISuperVaultStrategy |
||
| 653 |
function previewPerformanceFee( |
||
| 654 |
address controller, |
||
| 655 |
uint256 sharesToRedeem |
||
| 656 |
) |
||
| 657 |
external |
||
| 658 |
view |
||
| 659 |
returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee) |
||
| 660 |
{
|
||
| 661 |
if (sharesToRedeem == 0) return (0, 0, 0); |
||
| 662 | |||
| 663 |
// Get controller's state |
||
| 664 |
SuperVaultState storage state = superVaultState[controller]; |
||
| 665 | |||
| 666 |
// Check if controller has enough shares |
||
| 667 |
if (sharesToRedeem > state.accumulatorShares) return (0, 0, 0); |
||
| 668 | |||
| 669 |
// Get the current price per share |
||
| 670 |
uint256 currentPPS = getStoredPPS(); |
||
| 671 | |||
| 672 |
// Calculate historical assets (cost basis) |
||
| 673 |
uint256 historicalAssets = 0; |
||
| 674 |
if (state.accumulatorShares > 0) {
|
||
| 675 |
historicalAssets = sharesToRedeem.mulDiv( |
||
| 676 |
state.accumulatorCostBasis, |
||
| 677 |
state.accumulatorShares, |
||
| 678 |
Math.Rounding.Floor |
||
| 679 |
); |
||
| 680 |
} |
||
| 681 | |||
| 682 |
// Calculate current value of shares in asset terms |
||
| 683 |
uint256 currentAssetsWithFees = sharesToRedeem.mulDiv( |
||
| 684 |
currentPPS, |
||
| 685 |
PRECISION, |
||
| 686 |
Math.Rounding.Floor |
||
| 687 |
); |
||
| 688 | |||
| 689 |
// Calculate fee (if any) using same logic as _calculateAndTransferFee |
||
| 690 |
if (currentAssetsWithFees > historicalAssets) {
|
||
| 691 |
uint256 profit = currentAssetsWithFees - historicalAssets; |
||
| 692 |
uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
||
| 693 |
totalFee = profit.mulDiv( |
||
| 694 |
performanceFeeBps, |
||
| 695 |
BPS_PRECISION, |
||
| 696 |
Math.Rounding.Floor |
||
| 697 |
); |
||
| 698 | |||
| 699 |
if (totalFee > 0) {
|
||
| 700 |
// Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
||
| 701 |
superformFee = totalFee.mulDiv( |
||
| 702 |
superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
||
| 703 |
BPS_PRECISION, |
||
| 704 |
Math.Rounding.Floor |
||
| 705 |
); |
||
| 706 |
recipientFee = totalFee - superformFee; |
||
| 707 |
} |
||
| 708 |
} |
||
| 709 | |||
| 710 |
return (totalFee, superformFee, recipientFee); |
||
| 711 |
} |
||
| 712 | |||
| 713 |
/*////////////////////////////////////////////////////////////// |
||
| 714 |
INTERNAL FUNCTIONS |
||
| 715 |
//////////////////////////////////////////////////////////////*/ |
||
| 716 | |||
| 717 |
/// @notice Process a single hook execution |
||
| 718 |
/// @param hook Hook address |
||
| 719 |
/// @param prevHook Previous hook address |
||
| 720 |
/// @param hookCalldata Hook calldata |
||
| 721 |
/// @param expectedAssetsOrSharesOut Expected assets or shares output |
||
| 722 |
/// @return processedHook Processed hook address |
||
| 723 |
function _processSingleHookExecution( |
||
| 724 |
address hook, |
||
| 725 |
address prevHook, |
||
| 726 |
bytes memory hookCalldata, |
||
| 727 |
uint256 expectedAssetsOrSharesOut |
||
| 728 |
) internal returns (address) {
|
||
| 729 |
ExecutionVars memory vars; |
||
| 730 |
✓ 127.5K
|
vars.hookContract = ISuperHook(hook); |
|
| 731 | |||
| 732 |
✓ 127.5K
|
vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
|
| 733 |
✓ 127.5K
|
hookCalldata |
|
| 734 |
); |
||
| 735 |
✓ 127.5K
|
if (yieldSources[vars.targetedYieldSource] == address(0)) |
|
| 736 |
✓ 95.7K
|
⟲ 95.7K
|
revert YIELD_SOURCE_NOT_FOUND(); |
| 737 | |||
| 738 |
✓ 31.8K
|
bool usePrevHookAmount = _decodeHookUsePrevHookAmount( |
|
| 739 |
✓ 31.8K
|
hook, |
|
| 740 |
✓ 31.8K
|
hookCalldata |
|
| 741 |
); |
||
| 742 |
✓ 31.8K
|
if (usePrevHookAmount && prevHook != address(0)) {
|
|
| 743 |
vars.outAmount = _getPreviousHookOutAmount(prevHook); |
||
| 744 |
if (expectedAssetsOrSharesOut == 0) revert ZERO_EXPECTED_VALUE(); |
||
| 745 |
uint256 minExpectedPrevOut = expectedAssetsOrSharesOut * |
||
| 746 |
(BPS_PRECISION - _getSlippageTolerance()); |
||
| 747 |
if (vars.outAmount * BPS_PRECISION < minExpectedPrevOut) {
|
||
| 748 |
revert MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET(); |
||
| 749 |
} |
||
| 750 |
} |
||
| 751 | |||
| 752 |
✓ 31.8K
|
ISuperHook(address(vars.hookContract)).setExecutionContext( |
|
| 753 |
✓ 31.8K
|
address(this) |
|
| 754 |
); |
||
| 755 |
✓ 31.8K
|
vars.executions = vars.hookContract.build( |
|
| 756 |
✓ 31.8K
|
prevHook, |
|
| 757 |
✓ 31.8K
|
address(this), |
|
| 758 |
✓ 31.8K
|
hookCalldata |
|
| 759 |
); |
||
| 760 |
✓ 111.2K
|
for (uint256 j; j < vars.executions.length; ++j) {
|
|
| 761 |
✓ 96.1K
|
(vars.success, ) = vars.executions[j].target.call{
|
|
| 762 |
✓ 96.1K
|
value: vars.executions[j].value |
|
| 763 |
✓ 96.1K
|
}(vars.executions[j].callData); |
|
| 764 |
✓ 4.0K
|
⟲ 4.0K
|
if (!vars.success) revert OPERATION_FAILED(); |
| 765 |
} |
||
| 766 |
✓ 15.2K
|
ISuperHook(address(vars.hookContract)).resetExecutionState( |
|
| 767 |
✓ 15.2K
|
address(this) |
|
| 768 |
); |
||
| 769 | |||
| 770 |
✓ 15.2K
|
uint256 actualOutput = ISuperHookResult(hook).getOutAmount( |
|
| 771 |
✓ 15.2K
|
address(this) |
|
| 772 |
); |
||
| 773 |
✓ 15.2K
|
⟲ 828
|
if (actualOutput == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 774 | |||
| 775 |
✓ 14.3K
|
uint256 minExpectedOut = (expectedAssetsOrSharesOut * |
|
| 776 |
✓ 14.3K
|
(BPS_PRECISION - _getSlippageTolerance())) / BPS_PRECISION; |
|
| 777 |
✓ 14.3K
|
if (actualOutput < minExpectedOut) {
|
|
| 778 |
revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
||
| 779 |
} |
||
| 780 | |||
| 781 |
emit HookExecuted( |
||
| 782 |
✓ 14.3K
|
hook, |
|
| 783 |
✓ 14.3K
|
prevHook, |
|
| 784 |
✓ 14.3K
|
vars.targetedYieldSource, |
|
| 785 |
✓ 14.3K
|
usePrevHookAmount, |
|
| 786 |
✓ 14.3K
|
hookCalldata |
|
| 787 |
); |
||
| 788 | |||
| 789 |
✓ 14.3K
|
return hook; |
|
| 790 |
} |
||
| 791 | |||
| 792 |
/// @notice Process a single hook fulfillment execution |
||
| 793 |
/// @param hook Hook address |
||
| 794 |
/// @param hookCalldata Hook calldata |
||
| 795 |
/// @param expectedAssetOutput Expected asset output |
||
| 796 |
/// @param currentPPS Current price per share |
||
| 797 |
/// @return processedShares Processed shares |
||
| 798 |
function _processSingleFulfillHookExecution( |
||
| 799 |
address hook, |
||
| 800 |
bytes memory hookCalldata, |
||
| 801 |
uint256 expectedAssetOutput, |
||
| 802 |
uint256 currentPPS |
||
| 803 |
) internal returns (uint256) {
|
||
| 804 |
OutflowExecutionVars memory vars; |
||
| 805 |
✓ 352.7K
|
vars.hookContract = ISuperHook(hook); |
|
| 806 |
✓ 352.7K
|
vars.hookType = ISuperHookResult(hook).hookType(); |
|
| 807 |
✓ 352.7K
|
if (vars.hookType != ISuperHook.HookType.OUTFLOW) |
|
| 808 |
revert INVALID_HOOK_TYPE(); |
||
| 809 | |||
| 810 |
✓ 352.7K
|
vars.targetedYieldSource = HookDataDecoder.extractYieldSource( |
|
| 811 |
✓ 352.7K
|
hookCalldata |
|
| 812 |
); |
||
| 813 |
✓ 352.7K
|
if (yieldSources[vars.targetedYieldSource] == address(0)) |
|
| 814 |
✓ 129.6K
|
⟲ 129.6K
|
revert YIELD_SOURCE_NOT_FOUND(); |
| 815 | |||
| 816 |
// we must always encode supervault shares when fulfilling redemptions |
||
| 817 |
✓ 223.2K
|
vars.superVaultShares = ISuperHookInflowOutflow(hook).decodeAmount( |
|
| 818 |
✓ 223.2K
|
hookCalldata |
|
| 819 |
); |
||
| 820 | |||
| 821 |
// Calculate underlying shares and update hook calldata |
||
| 822 |
✓ 223.2K
|
vars.amountOfAssets = vars.superVaultShares.mulDiv( |
|
| 823 |
✓ 223.2K
|
currentPPS, |
|
| 824 |
✓ 223.2K
|
PRECISION, |
|
| 825 |
Math.Rounding.Floor |
||
| 826 |
); |
||
| 827 |
✓ 223.2K
|
vars.svAsset = address(_asset); |
|
| 828 |
✓ 195.2K
|
vars.amountConvertedToUnderlyingShares = IYieldSourceOracle( |
|
| 829 |
✓ 223.2K
|
yieldSources[vars.targetedYieldSource] |
|
| 830 |
).getShareOutput( |
||
| 831 |
✓ 223.2K
|
vars.targetedYieldSource, |
|
| 832 |
✓ 223.2K
|
vars.svAsset, |
|
| 833 |
✓ 223.2K
|
vars.amountOfAssets |
|
| 834 |
); |
||
| 835 |
✓ 195.2K
|
hookCalldata = ISuperHookOutflow(hook).replaceCalldataAmount( |
|
| 836 |
✓ 195.2K
|
hookCalldata, |
|
| 837 |
vars.amountConvertedToUnderlyingShares |
||
| 838 |
); |
||
| 839 | |||
| 840 |
✓ 195.2K
|
vars.balanceAssetBefore = _getTokenBalance(vars.svAsset, address(this)); |
|
| 841 | |||
| 842 |
✓ 195.2K
|
ISuperHook(address(vars.hookContract)).setExecutionContext( |
|
| 843 |
✓ 195.2K
|
address(this) |
|
| 844 |
); |
||
| 845 | |||
| 846 |
✓ 195.2K
|
vars.executions = vars.hookContract.build( |
|
| 847 |
✓ 195.2K
|
address(0), |
|
| 848 |
✓ 195.2K
|
address(this), |
|
| 849 |
✓ 195.2K
|
hookCalldata |
|
| 850 |
); |
||
| 851 |
✓ 531.1K
|
for (uint256 j; j < vars.executions.length; ++j) {
|
|
| 852 |
✓ 405.0K
|
(vars.success, ) = vars.executions[j].target.call( |
|
| 853 |
✓ 405.0K
|
vars.executions[j].callData |
|
| 854 |
); |
||
| 855 |
✓ 13.4K
|
⟲ 13.4K
|
if (!vars.success) revert OPERATION_FAILED(); |
| 856 |
} |
||
| 857 |
✓ 126.1K
|
ISuperHook(address(vars.hookContract)).resetExecutionState( |
|
| 858 |
✓ 126.1K
|
address(this) |
|
| 859 |
); |
||
| 860 | |||
| 861 |
✓ 126.1K
|
vars.outAmount = |
|
| 862 |
✓ 126.1K
|
_getTokenBalance(vars.svAsset, address(this)) - |
|
| 863 |
✓ 126.1K
|
vars.balanceAssetBefore; |
|
| 864 | |||
| 865 |
✓ 126.1K
|
⟲ 22
|
if (vars.outAmount == 0) revert ZERO_OUTPUT_AMOUNT(); |
| 866 |
if ( |
||
| 867 |
✓ 126.1K
|
vars.outAmount * BPS_PRECISION < |
|
| 868 |
✓ 126.1K
|
expectedAssetOutput * (BPS_PRECISION - _getSlippageTolerance()) |
|
| 869 |
) {
|
||
| 870 |
✓ 1.7K
|
⟲ 1.7K
|
revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
| 871 |
} |
||
| 872 |
✓ 124.4K
|
emit FulfillHookExecuted(hook, vars.targetedYieldSource, hookCalldata); |
|
| 873 | |||
| 874 |
✓ 124.4K
|
return vars.superVaultShares; |
|
| 875 |
} |
||
| 876 | |||
| 877 |
/// @notice Process redeem fulfillments for multiple controllers |
||
| 878 |
/// @param controllers Array of controller addresses |
||
| 879 |
/// @param controllersLength Length of controllers array |
||
| 880 |
/// @param processedShares Total shares processed |
||
| 881 |
/// @param currentPPS Current price per share |
||
| 882 |
function _processRedeemFulfillments( |
||
| 883 |
address[] calldata controllers, |
||
| 884 |
uint256 controllersLength, |
||
| 885 |
uint256 processedShares, |
||
| 886 |
uint256 currentPPS |
||
| 887 |
) internal {
|
||
| 888 |
✓ 248.8K
|
for (uint256 i; i < controllersLength; ++i) {
|
|
| 889 |
✓ 124.4K
|
SuperVaultState storage state = superVaultState[controllers[i]]; |
|
| 890 | |||
| 891 |
✓ 124.4K
|
uint256 crtControllerRequestedAmount = state.pendingRedeemRequest; |
|
| 892 |
// Check for PPS slippage if there's a recorded request PPS and max slippage is set |
||
| 893 |
✓ 124.4K
|
if (state.averageRequestPPS > 0 && _maxPPSSlippage > 0) {
|
|
| 894 |
✓ 122.7K
|
uint256 averageRequestPPS = state.averageRequestPPS; |
|
| 895 |
// Calculate the percentage decrease from request PPS to current PPS |
||
| 896 |
✓ 122.7K
|
if (currentPPS < averageRequestPPS) {
|
|
| 897 |
uint256 decrease = ( |
||
| 898 |
(averageRequestPPS - currentPPS).mulDiv( |
||
| 899 |
BPS_PRECISION, |
||
| 900 |
averageRequestPPS, |
||
| 901 |
Math.Rounding.Floor |
||
| 902 |
) |
||
| 903 |
); |
||
| 904 |
// If decrease exceeds maximum allowed slippage, revert |
||
| 905 |
if (decrease > _maxPPSSlippage) revert SLIPPAGE_EXCEEDED(); |
||
| 906 |
} |
||
| 907 |
} |
||
| 908 | |||
| 909 |
✓ 124.4K
|
uint256 currentAssets = _calculateHistoricalAssetsAndProcessFees( |
|
| 910 |
✓ 124.4K
|
state, |
|
| 911 |
✓ 124.4K
|
crtControllerRequestedAmount, |
|
| 912 |
✓ 124.4K
|
currentPPS |
|
| 913 |
); |
||
| 914 | |||
| 915 |
// Update user state, no partial redeems allowed |
||
| 916 |
✓ 124.4K
|
state.pendingRedeemRequest = 0; |
|
| 917 |
✓ 124.4K
|
state.maxWithdraw += currentAssets; |
|
| 918 |
✓ 124.4K
|
state.averageRequestPPS = 0; // Reset PPS value after fulfillment |
|
| 919 | |||
| 920 |
// Call vault callback |
||
| 921 |
✓ 124.4K
|
_onRedeemClaimable( |
|
| 922 |
✓ 124.4K
|
controllers[i], |
|
| 923 |
✓ 124.4K
|
currentAssets, |
|
| 924 |
✓ 124.4K
|
crtControllerRequestedAmount, |
|
| 925 |
✓ 124.4K
|
state.averageWithdrawPrice, |
|
| 926 |
✓ 124.4K
|
state.accumulatorShares, |
|
| 927 |
✓ 124.4K
|
state.accumulatorCostBasis |
|
| 928 |
); |
||
| 929 |
} |
||
| 930 |
} |
||
| 931 | |||
| 932 |
/// @notice Calculate historical assets and process fees |
||
| 933 |
/// @param state User's vault state |
||
| 934 |
/// @param requestedShares Shares being redeemed |
||
| 935 |
/// @param currentPricePerShare Current price per share |
||
| 936 |
function _calculateHistoricalAssetsAndProcessFees( |
||
| 937 |
SuperVaultState storage state, |
||
| 938 |
uint256 requestedShares, |
||
| 939 |
uint256 currentPricePerShare |
||
| 940 |
✓ 124.4K
|
) private returns (uint256 currentAssets) {
|
|
| 941 |
// Calculate cost basis based on requested shares |
||
| 942 |
✓ 124.4K
|
uint256 historicalAssets = _calculateCostBasis(state, requestedShares); |
|
| 943 |
✓ 124.4K
|
uint256 currentAssetsWithFees; |
|
| 944 |
// Process fees and get final assets |
||
| 945 |
✓ 124.4K
|
(currentAssetsWithFees, currentAssets) = _processFees( |
|
| 946 |
✓ 124.4K
|
requestedShares, |
|
| 947 |
✓ 124.4K
|
currentPricePerShare, |
|
| 948 |
✓ 124.4K
|
historicalAssets |
|
| 949 |
); |
||
| 950 | |||
| 951 |
// Update average withdraw price if needed |
||
| 952 |
✓ 124.4K
|
if (requestedShares > 0) {
|
|
| 953 |
✓ 124.4K
|
_updateAverageWithdrawPrice( |
|
| 954 |
✓ 124.4K
|
state, |
|
| 955 |
✓ 124.4K
|
requestedShares, |
|
| 956 |
✓ 124.4K
|
currentAssetsWithFees |
|
| 957 |
); |
||
| 958 |
} |
||
| 959 | |||
| 960 |
✓ 124.4K
|
return currentAssets; |
|
| 961 |
} |
||
| 962 | |||
| 963 |
/// @notice Calculate cost basis for requested shares using weighted average approach |
||
| 964 |
/// @param state User's vault state |
||
| 965 |
/// @param requestedShares Shares being redeemed |
||
| 966 |
function _calculateCostBasis( |
||
| 967 |
SuperVaultState storage state, |
||
| 968 |
uint256 requestedShares |
||
| 969 |
✓ 124.4K
|
) private returns (uint256 costBasis) {
|
|
| 970 |
✓ 124.4K
|
if (requestedShares > state.accumulatorShares) |
|
| 971 |
revert INSUFFICIENT_SHARES(); |
||
| 972 | |||
| 973 |
// Calculate cost basis proportionally |
||
| 974 |
✓ 124.4K
|
costBasis = requestedShares.mulDiv( |
|
| 975 |
✓ 124.4K
|
state.accumulatorCostBasis, |
|
| 976 |
✓ 124.4K
|
state.accumulatorShares, |
|
| 977 |
✓ 124.4K
|
Math.Rounding.Floor |
|
| 978 |
); |
||
| 979 | |||
| 980 |
// Update user's accumulator state |
||
| 981 |
✓ 124.4K
|
state.accumulatorShares -= requestedShares; |
|
| 982 |
✓ 124.4K
|
state.accumulatorCostBasis -= costBasis; |
|
| 983 | |||
| 984 |
return costBasis; |
||
| 985 |
} |
||
| 986 | |||
| 987 |
// --- Fee Processing --- |
||
| 988 |
/// @notice Calculate and transfer fees based on profit |
||
| 989 |
/// @param requestedShares Shares being redeemed |
||
| 990 |
/// @param currentPricePerShare Current price per share |
||
| 991 |
/// @param historicalAssets Historical value of shares in assets |
||
| 992 |
/// @return currentAssetsWithFees Current value of shares in assets (not net of fees) |
||
| 993 |
/// @return currentAssets Current assets after fee deduction |
||
| 994 |
function _processFees( |
||
| 995 |
uint256 requestedShares, |
||
| 996 |
uint256 currentPricePerShare, |
||
| 997 |
uint256 historicalAssets |
||
| 998 |
✓ 124.4K
|
) private returns (uint256 currentAssetsWithFees, uint256 currentAssets) {
|
|
| 999 |
// Calculate current value of the shares at current price |
||
| 1000 |
✓ 124.4K
|
currentAssetsWithFees = requestedShares.mulDiv( |
|
| 1001 |
✓ 124.4K
|
currentPricePerShare, |
|
| 1002 |
✓ 124.4K
|
PRECISION, |
|
| 1003 |
Math.Rounding.Floor |
||
| 1004 |
); |
||
| 1005 | |||
| 1006 |
// Apply fees only on profit |
||
| 1007 |
✓ 124.4K
|
currentAssets = _calculateAndTransferFee( |
|
| 1008 |
✓ 124.4K
|
currentAssetsWithFees, |
|
| 1009 |
✓ 124.4K
|
historicalAssets |
|
| 1010 |
); |
||
| 1011 | |||
| 1012 |
// Ensure we don't exceed available balance |
||
| 1013 |
✓ 124.4K
|
uint256 balanceOfStrategy = _getTokenBalance( |
|
| 1014 |
✓ 124.4K
|
address(_asset), |
|
| 1015 |
✓ 124.4K
|
address(this) |
|
| 1016 |
); |
||
| 1017 |
✓ 124.4K
|
currentAssets = currentAssets > balanceOfStrategy |
|
| 1018 |
✓ 9.6K
|
? balanceOfStrategy |
|
| 1019 |
✓ 114.8K
|
: currentAssets; |
|
| 1020 | |||
| 1021 |
✓ 124.4K
|
return (currentAssetsWithFees, currentAssets); |
|
| 1022 |
} |
||
| 1023 | |||
| 1024 |
/// @notice Calculate fee on profit and transfer to recipient |
||
| 1025 |
/// @param currentAssetsWithFees Current value of shares in assets (not net of fees) |
||
| 1026 |
/// @param historicalAssets Historical value of shares in assets |
||
| 1027 |
/// @return currentAssets Current assets after fee deduction |
||
| 1028 |
function _calculateAndTransferFee( |
||
| 1029 |
uint256 currentAssetsWithFees, |
||
| 1030 |
uint256 historicalAssets |
||
| 1031 |
) private returns (uint256 currentAssets) {
|
||
| 1032 |
✓ 124.4K
|
currentAssets = currentAssetsWithFees; |
|
| 1033 |
✓ 124.4K
|
if (currentAssetsWithFees > historicalAssets) {
|
|
| 1034 |
uint256 profit = currentAssetsWithFees - historicalAssets; |
||
| 1035 |
uint256 performanceFeeBps = feeConfig.performanceFeeBps; |
||
| 1036 |
uint256 totalFee = profit.mulDiv( |
||
| 1037 |
performanceFeeBps, |
||
| 1038 |
BPS_PRECISION, |
||
| 1039 |
Math.Rounding.Floor |
||
| 1040 |
); |
||
| 1041 |
if (totalFee > 0) {
|
||
| 1042 |
// Calculate Superform's portion of the fee using revenueShare from SuperGovernor |
||
| 1043 |
uint256 superformFee = totalFee.mulDiv( |
||
| 1044 |
superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE), |
||
| 1045 |
BPS_PRECISION, |
||
| 1046 |
Math.Rounding.Floor |
||
| 1047 |
); |
||
| 1048 |
uint256 recipientFee = totalFee - superformFee; |
||
| 1049 | |||
| 1050 |
// Transfer fees |
||
| 1051 |
if (superformFee > 0) {
|
||
| 1052 |
// Get treasury address from SuperGovernor |
||
| 1053 |
address treasury = superGovernor.getAddress( |
||
| 1054 |
superGovernor.TREASURY() |
||
| 1055 |
); |
||
| 1056 |
_safeTokenTransfer(address(_asset), treasury, superformFee); |
||
| 1057 |
emit FeePaid(treasury, superformFee, performanceFeeBps); |
||
| 1058 |
} |
||
| 1059 | |||
| 1060 |
if (recipientFee > 0) {
|
||
| 1061 |
address recipient = feeConfig.recipient; |
||
| 1062 |
if (recipient == address(0)) revert ZERO_ADDRESS(); |
||
| 1063 |
_safeTokenTransfer( |
||
| 1064 |
address(_asset), |
||
| 1065 |
recipient, |
||
| 1066 |
recipientFee |
||
| 1067 |
); |
||
| 1068 |
emit FeePaid(recipient, recipientFee, performanceFeeBps); |
||
| 1069 |
} |
||
| 1070 | |||
| 1071 |
currentAssets -= totalFee; |
||
| 1072 |
} |
||
| 1073 |
} |
||
| 1074 |
return currentAssets; |
||
| 1075 |
} |
||
| 1076 | |||
| 1077 |
/// @notice Internal function to update the average withdraw price |
||
| 1078 |
/// @param state Storage reference to the vault state |
||
| 1079 |
/// @param requestedShares Number of shares requested |
||
| 1080 |
/// @param currentAssetsWithFees Current assets with fees |
||
| 1081 |
function _updateAverageWithdrawPrice( |
||
| 1082 |
SuperVaultState storage state, |
||
| 1083 |
uint256 requestedShares, |
||
| 1084 |
uint256 currentAssetsWithFees |
||
| 1085 |
) private {
|
||
| 1086 |
✓ 124.4K
|
uint256 existingShares; |
|
| 1087 |
✓ 124.4K
|
uint256 existingAssets; |
|
| 1088 | |||
| 1089 |
✓ 124.4K
|
if (state.maxWithdraw > 0 && state.averageWithdrawPrice > 0) {
|
|
| 1090 |
existingShares = state.maxWithdraw.mulDiv( |
||
| 1091 |
PRECISION, |
||
| 1092 |
state.averageWithdrawPrice, |
||
| 1093 |
Math.Rounding.Floor |
||
| 1094 |
); |
||
| 1095 |
existingAssets = state.maxWithdraw; |
||
| 1096 |
} |
||
| 1097 | |||
| 1098 |
✓ 124.4K
|
uint256 newTotalShares = existingShares + requestedShares; |
|
| 1099 |
✓ 124.4K
|
uint256 newTotalAssets = existingAssets + currentAssetsWithFees; |
|
| 1100 | |||
| 1101 |
✓ 124.4K
|
if (newTotalShares > 0) {
|
|
| 1102 |
✓ 124.4K
|
state.averageWithdrawPrice = newTotalAssets.mulDiv( |
|
| 1103 |
✓ 124.4K
|
PRECISION, |
|
| 1104 |
✓ 124.4K
|
newTotalShares, |
|
| 1105 |
Math.Rounding.Floor |
||
| 1106 |
); |
||
| 1107 |
} |
||
| 1108 |
} |
||
| 1109 | |||
| 1110 |
/// @notice Internal function to get the SuperVaultAggregator |
||
| 1111 |
/// @return The SuperVaultAggregator |
||
| 1112 |
function _getSuperVaultAggregator() |
||
| 1113 |
internal |
||
| 1114 |
view |
||
| 1115 |
✓ 15.8M
|
returns (ISuperVaultAggregator) |
|
| 1116 |
{
|
||
| 1117 |
✓ 15.8M
|
address aggregatorAddress = superGovernor.getAddress( |
|
| 1118 |
✓ 15.8M
|
superGovernor.SUPER_VAULT_AGGREGATOR() |
|
| 1119 |
); |
||
| 1120 | |||
| 1121 |
return ISuperVaultAggregator(aggregatorAddress); |
||
| 1122 |
} |
||
| 1123 | |||
| 1124 |
/// @notice Internal function to check if a manager is authorized |
||
| 1125 |
/// @param manager_ The manager to check |
||
| 1126 |
function _isManager(address manager_) internal view {
|
||
| 1127 |
✓ 654.3K
|
if (!_getSuperVaultAggregator().isAnyManager(manager_, address(this))) {
|
|
| 1128 |
✓ 63.7K
|
⟲ 63.7K
|
revert MANAGER_NOT_AUTHORIZED(); |
| 1129 |
} |
||
| 1130 |
} |
||
| 1131 | |||
| 1132 |
/// @notice Internal function to check if a manager is the primary manager |
||
| 1133 |
/// @param manager_ The manager to check |
||
| 1134 |
function _isPrimaryManager(address manager_) internal view {
|
||
| 1135 |
if ( |
||
| 1136 |
✓ 902.4K
|
!_getSuperVaultAggregator().isMainManager(manager_, address(this)) |
|
| 1137 |
) {
|
||
| 1138 |
revert MANAGER_NOT_AUTHORIZED(); |
||
| 1139 |
} |
||
| 1140 |
} |
||
| 1141 | |||
| 1142 |
/// @notice Internal function to manage a yield source |
||
| 1143 |
/// @param source Address of the yield source |
||
| 1144 |
/// @param oracle Address of the oracle |
||
| 1145 |
/// @param actionType Type of action: 0=Add, 1=UpdateOracle, 2=Remove |
||
| 1146 |
function _manageYieldSource( |
||
| 1147 |
address source, |
||
| 1148 |
address oracle, |
||
| 1149 |
uint8 actionType |
||
| 1150 |
) internal {
|
||
| 1151 |
✓ 423.0K
|
if (actionType == 0) {
|
|
| 1152 |
✓ 298.4K
|
_addYieldSource(source, oracle); |
|
| 1153 |
✓ 124.6K
|
} else if (actionType == 1) {
|
|
| 1154 |
✓ 4.5K
|
_updateYieldSourceOracle(source, oracle); |
|
| 1155 |
✓ 120.1K
|
} else if (actionType == 2) {
|
|
| 1156 |
✓ 5.6K
|
_removeYieldSource(source); |
|
| 1157 |
} else {
|
||
| 1158 |
revert ACTION_TYPE_DISALLOWED(); |
||
| 1159 |
} |
||
| 1160 |
} |
||
| 1161 | |||
| 1162 |
/// @notice Internal function to add a yield source |
||
| 1163 |
/// @param source Address of the yield source |
||
| 1164 |
/// @param oracle Address of the oracle |
||
| 1165 |
function _addYieldSource(address source, address oracle) internal {
|
||
| 1166 |
✓ 298.4K
|
⟲ 426
|
if (source == address(0) || oracle == address(0)) revert ZERO_ADDRESS(); |
| 1167 |
✓ 298.0K
|
if (yieldSources[source] != address(0)) |
|
| 1168 |
✓ 15.5K
|
⟲ 15.5K
|
revert YIELD_SOURCE_ALREADY_EXISTS(); |
| 1169 |
✓ 282.4K
|
yieldSources[source] = oracle; |
|
| 1170 |
✓ 282.4K
|
if (!yieldSourcesList.add(source)) revert YIELD_SOURCE_ALREADY_EXISTS(); |
|
| 1171 | |||
| 1172 |
✓ 282.4K
|
emit YieldSourceAdded(source, oracle); |
|
| 1173 |
} |
||
| 1174 | |||
| 1175 |
/// @notice Internal function to update a yield source's oracle |
||
| 1176 |
/// @param source Address of the yield source |
||
| 1177 |
/// @param oracle Address of the oracle |
||
| 1178 |
function _updateYieldSourceOracle(address source, address oracle) internal {
|
||
| 1179 |
✓ 4.5K
|
⟲ 797
|
if (oracle == address(0)) revert ZERO_ADDRESS(); |
| 1180 |
✓ 3.7K
|
address oldOracle = yieldSources[source]; |
|
| 1181 |
✓ 1.6K
|
⟲ 1.6K
|
if (oldOracle == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1182 |
✓ 2.0K
|
yieldSources[source] = oracle; |
|
| 1183 | |||
| 1184 |
✓ 2.0K
|
emit YieldSourceOracleUpdated(source, oldOracle, oracle); |
|
| 1185 |
} |
||
| 1186 | |||
| 1187 |
/// @notice Internal function to remove a yield source |
||
| 1188 |
/// @param source Address of the yield source |
||
| 1189 |
function _removeYieldSource(address source) internal {
|
||
| 1190 |
✓ 5.6K
|
⟲ 1.8K
|
if (yieldSources[source] == address(0)) revert YIELD_SOURCE_NOT_FOUND(); |
| 1191 | |||
| 1192 |
// Remove from mapping |
||
| 1193 |
✓ 3.8K
|
delete yieldSources[source]; |
|
| 1194 | |||
| 1195 |
// Remove from EnumerableSet |
||
| 1196 |
✓ 3.8K
|
if (!yieldSourcesList.remove(source)) revert YIELD_SOURCE_NOT_FOUND(); |
|
| 1197 | |||
| 1198 |
✓ 3.8K
|
emit YieldSourceRemoved(source); |
|
| 1199 |
} |
||
| 1200 | |||
| 1201 |
/// @notice Internal function to propose an emergency withdraw |
||
| 1202 |
function _proposeEmergencyWithdraw() internal {
|
||
| 1203 |
✓ 5.8K
|
_isPrimaryManager(msg.sender); |
|
| 1204 | |||
| 1205 |
✓ 5.7K
|
proposedEmergencyWithdrawable = true; |
|
| 1206 |
✓ 5.7K
|
emergencyWithdrawableEffectiveTime = block.timestamp + ONE_WEEK; |
|
| 1207 |
emit EmergencyWithdrawableProposed( |
||
| 1208 |
✓ 5.7K
|
true, |
|
| 1209 |
emergencyWithdrawableEffectiveTime |
||
| 1210 |
); |
||
| 1211 |
} |
||
| 1212 | |||
| 1213 |
/// @notice Internal function to execute an emergency withdraw |
||
| 1214 |
function _executeEmergencyWithdrawActivation() internal {
|
||
| 1215 |
✓ 2.7K
|
⟲ 918
|
if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL(); |
| 1216 |
✓ 1.7K
|
if (block.timestamp < emergencyWithdrawableEffectiveTime) |
|
| 1217 |
revert INVALID_TIMESTAMP(); |
||
| 1218 |
✓ 1.7K
|
emergencyWithdrawable = proposedEmergencyWithdrawable; |
|
| 1219 |
✓ 1.7K
|
proposedEmergencyWithdrawable = false; |
|
| 1220 |
✓ 1.7K
|
emergencyWithdrawableEffectiveTime = 0; |
|
| 1221 |
✓ 1.7K
|
emit EmergencyWithdrawableUpdated(emergencyWithdrawable); |
|
| 1222 |
} |
||
| 1223 | |||
| 1224 |
/// @notice Internal function to cancel an emergency withdraw proposal |
||
| 1225 |
function _cancelEmergencyWithdrawProposal() internal {
|
||
| 1226 |
✓ 2.8K
|
_isPrimaryManager(msg.sender); |
|
| 1227 | |||
| 1228 |
✓ 2.7K
|
⟲ 623
|
if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL(); |
| 1229 |
✓ 2.1K
|
proposedEmergencyWithdrawable = false; |
|
| 1230 |
✓ 2.1K
|
emergencyWithdrawableEffectiveTime = 0; |
|
| 1231 |
✓ 2.1K
|
emit EmergencyWithdrawableProposalCanceled(); |
|
| 1232 |
} |
||
| 1233 | |||
| 1234 |
/// @notice Internal function to perform an emergency withdraw |
||
| 1235 |
/// @param recipient Address to receive the assets |
||
| 1236 |
/// @param amount Amount of assets to withdraw |
||
| 1237 |
function _performEmergencyWithdraw( |
||
| 1238 |
address recipient, |
||
| 1239 |
uint256 amount |
||
| 1240 |
) internal {
|
||
| 1241 |
✓ 488
|
_isPrimaryManager(msg.sender); |
|
| 1242 | |||
| 1243 |
✓ 468
|
⟲ 468
|
if (!emergencyWithdrawable) revert INVALID_EMERGENCY_WITHDRAWAL(); |
| 1244 |
if (recipient == address(0)) revert ZERO_ADDRESS(); |
||
| 1245 |
uint256 freeAssets = _getTokenBalance(address(_asset), address(this)); |
||
| 1246 |
if (amount == 0 || amount > freeAssets) revert INSUFFICIENT_FUNDS(); |
||
| 1247 |
_safeTokenTransfer(address(_asset), recipient, amount); |
||
| 1248 |
emit EmergencyWithdrawal(recipient, amount); |
||
| 1249 |
} |
||
| 1250 | |||
| 1251 |
/// @notice Internal function to check if a hook is a fulfill requests hook |
||
| 1252 |
/// @param hook Address of the hook |
||
| 1253 |
/// @return True if the hook is a fulfill requests hook, false otherwise |
||
| 1254 |
✓ 376.8K
|
function _isFulfillRequestsHook(address hook) private view returns (bool) {
|
|
| 1255 |
✓ 376.8K
|
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 |
✓ 127.5K
|
function _isRegisteredHook(address hook) private view returns (bool) {
|
|
| 1262 |
✓ 127.5K
|
return superGovernor.isHookRegistered(hook); |
|
| 1263 |
} |
||
| 1264 | |||
| 1265 |
/// @notice Internal function to decode a hook's use previous hook amount |
||
| 1266 |
/// @param hook Address of the hook |
||
| 1267 |
/// @param hookCalldata Call data for the hook |
||
| 1268 |
/// @return True if the hook should use the previous hook amount, false otherwise |
||
| 1269 |
function _decodeHookUsePrevHookAmount( |
||
| 1270 |
address hook, |
||
| 1271 |
bytes memory hookCalldata |
||
| 1272 |
✓ 31.8K
|
) private pure returns (bool) {
|
|
| 1273 |
try |
||
| 1274 |
✓ 31.8K
|
ISuperHookContextAware(hook).decodeUsePrevHookAmount(hookCalldata) |
|
| 1275 |
returns (bool usePrevHookAmount) {
|
||
| 1276 |
✓ 26.7K
|
return usePrevHookAmount; |
|
| 1277 |
} catch {
|
||
| 1278 |
✓ 5.1K
|
return false; |
|
| 1279 |
} |
||
| 1280 |
} |
||
| 1281 | |||
| 1282 |
/// @notice Internal function to get the previous hook's output amount |
||
| 1283 |
/// @param prevHook Address of the previous hook |
||
| 1284 |
/// @return Output amount of the previous hook |
||
| 1285 |
function _getPreviousHookOutAmount( |
||
| 1286 |
address prevHook |
||
| 1287 |
) private view returns (uint256) {
|
||
| 1288 |
return ISuperHookResultOutflow(prevHook).getOutAmount(address(this)); |
||
| 1289 |
} |
||
| 1290 | |||
| 1291 |
/// @notice Internal function to handle a redeem claimable |
||
| 1292 |
/// @param controller Address of the controller |
||
| 1293 |
/// @param assetsFulfilled Amount of assets fulfilled |
||
| 1294 |
/// @param sharesFulfilled Amount of shares fulfilled |
||
| 1295 |
/// @param averageWithdrawPrice Average withdraw price |
||
| 1296 |
/// @param accumulatorShares Accumulator shares |
||
| 1297 |
/// @param accumulatorCostBasis Accumulator cost basis |
||
| 1298 |
function _onRedeemClaimable( |
||
| 1299 |
address controller, |
||
| 1300 |
uint256 assetsFulfilled, |
||
| 1301 |
uint256 sharesFulfilled, |
||
| 1302 |
uint256 averageWithdrawPrice, |
||
| 1303 |
uint256 accumulatorShares, |
||
| 1304 |
uint256 accumulatorCostBasis |
||
| 1305 |
) private {
|
||
| 1306 |
✓ 124.4K
|
ISuperVault(_vault).onRedeemClaimable( |
|
| 1307 |
controller, |
||
| 1308 |
assetsFulfilled, |
||
| 1309 |
sharesFulfilled, |
||
| 1310 |
averageWithdrawPrice, |
||
| 1311 |
accumulatorShares, |
||
| 1312 |
accumulatorCostBasis |
||
| 1313 |
); |
||
| 1314 |
} |
||
| 1315 | |||
| 1316 |
/// @notice Internal function to handle a redeem |
||
| 1317 |
/// @param controller Address of the controller |
||
| 1318 |
/// @param shares Amount of shares |
||
| 1319 |
function _handleRequestRedeem(address controller, uint256 shares) private {
|
||
| 1320 |
✓ 268.4K
|
if (shares == 0) revert INVALID_AMOUNT(); |
|
| 1321 |
✓ 268.4K
|
if (controller == address(0)) revert ZERO_ADDRESS(); |
|
| 1322 |
✓ 268.4K
|
SuperVaultState storage state = superVaultState[controller]; |
|
| 1323 | |||
| 1324 |
// Defense-in-depth: assert controller has accumulator shares |
||
| 1325 |
✓ 268.4K
|
if (state.accumulatorShares == 0) revert INSUFFICIENT_SHARES(); |
|
| 1326 | |||
| 1327 |
// Get current PPS from aggregator to use as baseline for slippage protection |
||
| 1328 |
✓ 268.4K
|
uint256 currentPPS = getStoredPPS(); |
|
| 1329 |
✓ 268.4K
|
⟲ 133
|
if (currentPPS == 0) revert INVALID_PPS(); |
| 1330 | |||
| 1331 |
// Calculate weighted average of PPS if there's an existing request |
||
| 1332 |
✓ 268.3K
|
if (state.pendingRedeemRequest > 0) {
|
|
| 1333 |
// Calculate weighted average of PPS based on share amounts |
||
| 1334 |
✓ 11.0K
|
uint256 existingSharesInRequest = state.pendingRedeemRequest; |
|
| 1335 |
✓ 11.0K
|
uint256 newTotalSharesInRequest = existingSharesInRequest + shares; |
|
| 1336 | |||
| 1337 |
// Use weighted average formula: (existingShares * existingPPS + newShares * currentPPS) / totalShares |
||
| 1338 |
✓ 11.0K
|
state.averageRequestPPS = |
|
| 1339 |
✓ 11.0K
|
((existingSharesInRequest * state.averageRequestPPS) + |
|
| 1340 |
✓ 11.0K
|
(shares * currentPPS)) / |
|
| 1341 |
newTotalSharesInRequest; |
||
| 1342 | |||
| 1343 |
// Update total shares |
||
| 1344 |
✓ 11.0K
|
state.pendingRedeemRequest = newTotalSharesInRequest; |
|
| 1345 |
} else {
|
||
| 1346 |
// First request for this controller |
||
| 1347 |
✓ 257.3K
|
state.pendingRedeemRequest = shares; |
|
| 1348 |
✓ 257.3K
|
state.averageRequestPPS = currentPPS; |
|
| 1349 |
} |
||
| 1350 | |||
| 1351 |
✓ 268.3K
|
emit RedeemRequestPlaced(controller, controller, shares); |
|
| 1352 |
} |
||
| 1353 | |||
| 1354 |
/// @notice Internal function to handle a redeem cancellation |
||
| 1355 |
/// @param controller Address of the controller |
||
| 1356 |
function _handleCancelRedeem(address controller) private {
|
||
| 1357 |
✓ 143.6K
|
if (controller == address(0)) revert ZERO_ADDRESS(); |
|
| 1358 |
✓ 143.6K
|
SuperVaultState storage state = superVaultState[controller]; |
|
| 1359 |
✓ 143.6K
|
uint256 pendingShares = state.pendingRedeemRequest; |
|
| 1360 |
✓ 143.6K
|
⟲ 142.4K
|
if (pendingShares == 0) revert REQUEST_NOT_FOUND(); |
| 1361 |
// Only clear pending request metadata |
||
| 1362 |
✓ 1.3K
|
state.pendingRedeemRequest = 0; |
|
| 1363 |
✓ 1.3K
|
state.averageRequestPPS = 0; |
|
| 1364 |
✓ 1.3K
|
emit RedeemRequestCanceled(controller, pendingShares); |
|
| 1365 |
} |
||
| 1366 | |||
| 1367 |
/// @notice Internal function to handle a redeem claim |
||
| 1368 |
/// @param controller Address of the controller |
||
| 1369 |
/// @param receiver Address of the receiver |
||
| 1370 |
/// @param assetsToClaim Amount of assets to claim |
||
| 1371 |
function _handleClaimRedeem( |
||
| 1372 |
address controller, |
||
| 1373 |
address receiver, |
||
| 1374 |
uint256 assetsToClaim |
||
| 1375 |
) private {
|
||
| 1376 |
✓ 27.2K
|
⟲ 1.6K
|
if (assetsToClaim == 0) revert INVALID_AMOUNT(); |
| 1377 |
✓ 25.6K
|
if (controller == address(0)) revert ZERO_ADDRESS(); |
|
| 1378 |
✓ 25.6K
|
SuperVaultState storage state = superVaultState[controller]; |
|
| 1379 | |||
| 1380 |
// Handle dust collection for rounding errors |
||
| 1381 |
✓ 25.6K
|
uint256 actualAmountToClaim = assetsToClaim; |
|
| 1382 |
✓ 25.6K
|
uint256 remainingAssets = _asset.balanceOf(address(this)); |
|
| 1383 | |||
| 1384 |
// If user is requesting slightly more than available due to rounding errors, |
||
| 1385 |
// and the difference is small (dust), give them the remaining balance |
||
| 1386 |
if ( |
||
| 1387 |
✓ 25.6K
|
assetsToClaim > remainingAssets && |
|
| 1388 |
assetsToClaim - remainingAssets <= TOLERANCE_CONSTANT |
||
| 1389 |
) {
|
||
| 1390 |
actualAmountToClaim = remainingAssets; |
||
| 1391 |
} |
||
| 1392 | |||
| 1393 |
✓ 25.6K
|
if (state.maxWithdraw < actualAmountToClaim) |
|
| 1394 |
revert INVALID_REDEEM_CLAIM(); |
||
| 1395 |
✓ 25.6K
|
state.maxWithdraw -= actualAmountToClaim; |
|
| 1396 |
✓ 25.6K
|
_asset.safeTransfer(receiver, actualAmountToClaim); |
|
| 1397 |
emit RedeemRequestFulfilled( |
||
| 1398 |
receiver, |
||
| 1399 |
controller, |
||
| 1400 |
actualAmountToClaim, |
||
| 1401 |
✓ 25.6K
|
0 |
|
| 1402 |
); |
||
| 1403 |
} |
||
| 1404 | |||
| 1405 |
/// @notice Internal function to safely transfer tokens |
||
| 1406 |
/// @param token Address of the token |
||
| 1407 |
/// @param recipient Address to receive the tokens |
||
| 1408 |
/// @param amount Amount of tokens to transfer |
||
| 1409 |
function _safeTokenTransfer( |
||
| 1410 |
address token, |
||
| 1411 |
address recipient, |
||
| 1412 |
uint256 amount |
||
| 1413 |
) private {
|
||
| 1414 |
✓ 361.8K
|
if (amount > 0) IERC20(token).safeTransfer(recipient, amount); |
|
| 1415 |
} |
||
| 1416 | |||
| 1417 |
/// @notice Internal function to get the token balance of an account |
||
| 1418 |
/// @param token Address of the token |
||
| 1419 |
/// @param account Address of the account |
||
| 1420 |
/// @return Token balance of the account |
||
| 1421 |
function _getTokenBalance( |
||
| 1422 |
address token, |
||
| 1423 |
address account |
||
| 1424 |
✓ 445.6K
|
) private view returns (uint256) {
|
|
| 1425 |
✓ 445.6K
|
return IERC20(token).balanceOf(account); |
|
| 1426 |
} |
||
| 1427 | |||
| 1428 |
/// @notice Internal function to get the slippage tolerance |
||
| 1429 |
/// @return Slippage tolerance |
||
| 1430 |
function _getSlippageTolerance() private pure returns (uint256) {
|
||
| 1431 |
return SV_SLIPPAGE_TOLERANCE_BPS; |
||
| 1432 |
} |
||
| 1433 | |||
| 1434 |
/// @notice Internal function to check if the caller is the vault |
||
| 1435 |
/// @dev This is used to prevent unauthorized access to certain functions |
||
| 1436 |
function _requireVault() internal view {
|
||
| 1437 |
✓ 1.5M
|
⟲ 537.0K
|
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 |
✓ 767.0K
|
function _isPaused() internal view returns (bool) {
|
|
| 1444 |
✓ 767.0K
|
return _getSuperVaultAggregator().isStrategyPaused(address(this)); |
|
| 1445 |
} |
||
| 1446 | |||
| 1447 |
/// @notice Validates a hook using the Merkle root system |
||
| 1448 |
/// @param hook Address of the hook to validate |
||
| 1449 |
/// @param hookCalldata Calldata to be passed to the hook |
||
| 1450 |
/// @param globalProof Merkle proof for the global root |
||
| 1451 |
/// @param strategyProof Merkle proof for the strategy-specific root |
||
| 1452 |
/// @return isValid True if the hook is valid, false otherwise |
||
| 1453 |
function _validateHook( |
||
| 1454 |
address hook, |
||
| 1455 |
bytes memory hookCalldata, |
||
| 1456 |
bytes32[] memory globalProof, |
||
| 1457 |
bytes32[] memory strategyProof |
||
| 1458 |
✓ 504.3K
|
) internal view returns (bool) {
|
|
| 1459 |
return |
||
| 1460 |
✓ 504.3K
|
_getSuperVaultAggregator().validateHook( |
|
| 1461 |
✓ 504.3K
|
address(this), |
|
| 1462 |
ISuperVaultAggregator.ValidateHookArgs({
|
||
| 1463 |
✓ 504.3K
|
hookAddress: hook, |
|
| 1464 |
✓ 504.3K
|
hookArgs: ISuperHookInspector(hook).inspect(hookCalldata), |
|
| 1465 |
✓ 504.3K
|
globalProof: globalProof, |
|
| 1466 |
✓ 504.3K
|
strategyProof: strategyProof |
|
| 1467 |
}) |
||
| 1468 |
); |
||
| 1469 |
} |
||
| 1470 |
} |
||
| 1471 |
0.0%
src/interfaces/ISuperGovernor.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import "@openzeppelin/contracts/access/IAccessControl.sol"; |
||
| 5 | |||
| 6 |
/*////////////////////////////////////////////////////////////// |
||
| 7 |
ENUMS |
||
| 8 |
//////////////////////////////////////////////////////////////*/ |
||
| 9 |
/// @notice Enum representing different types of fees that can be managed |
||
| 10 |
enum FeeType {
|
||
| 11 |
REVENUE_SHARE, |
||
| 12 |
SUPER_VAULT_PERFORMANCE_FEE, |
||
| 13 |
SUPER_ASSET_SWAP_FEE |
||
| 14 |
} |
||
| 15 |
/// @title ISuperGovernor |
||
| 16 |
/// @author Superform Labs |
||
| 17 |
/// @notice Interface for the SuperGovernor contract |
||
| 18 |
/// @dev Central registry for all deployed contracts in the Superform periphery |
||
| 19 | |||
| 20 |
interface ISuperGovernor is IAccessControl {
|
||
| 21 |
/*////////////////////////////////////////////////////////////// |
||
| 22 |
STRUCTS |
||
| 23 |
//////////////////////////////////////////////////////////////*/ |
||
| 24 | |||
| 25 |
/// @notice Structure containing Merkle root data for a hook |
||
| 26 |
struct HookMerkleRootData {
|
||
| 27 |
bytes32 currentRoot; // Current active Merkle root for the hook |
||
| 28 |
bytes32 proposedRoot; // Proposed new Merkle root (zero if no proposal exists) |
||
| 29 |
uint256 effectiveTime; // Timestamp when the proposed root becomes effective |
||
| 30 |
} |
||
| 31 | |||
| 32 |
struct GasInfo {
|
||
| 33 |
// `batchForwardPPS` base gas |
||
| 34 |
uint256 baseGasBatch; |
||
| 35 |
// `batchForwardPPS` gas increase per entry |
||
| 36 |
uint256 gasIncreasePerEntryBatch; |
||
| 37 |
} |
||
| 38 | |||
| 39 |
/*////////////////////////////////////////////////////////////// |
||
| 40 |
ERRORS |
||
| 41 |
//////////////////////////////////////////////////////////////*/ |
||
| 42 |
/// @notice Thrown when a function that should only be called by governor is called by someone else |
||
| 43 |
error ONLY_GOVERNOR(); |
||
| 44 |
/// @notice Thrown when trying to register a contract that is already registered |
||
| 45 |
error CONTRACT_ALREADY_REGISTERED(); |
||
| 46 |
/// @notice Thrown when trying to access a contract that is not registered |
||
| 47 |
error CONTRACT_NOT_FOUND(); |
||
| 48 |
/// @notice Thrown when providing an invalid address (typically zero address) |
||
| 49 |
error INVALID_ADDRESS(); |
||
| 50 |
/// @notice Thrown when providing an invalid chain ID |
||
| 51 |
error INVALID_CHAIN_ID(); |
||
| 52 |
/// @notice Thrown when a hook is already approved |
||
| 53 |
error HOOK_ALREADY_APPROVED(); |
||
| 54 |
/// @notice Thrown when a hook is not approved but expected to be |
||
| 55 |
error HOOK_NOT_APPROVED(); |
||
| 56 |
/// @notice Thrown when a fulfill requests hook is already registered |
||
| 57 |
error FULFILL_REQUESTS_HOOK_ALREADY_REGISTERED(); |
||
| 58 |
/// @notice Thrown when a fulfill requests hook is not registered but expected to be |
||
| 59 |
error FULFILL_REQUESTS_HOOK_NOT_REGISTERED(); |
||
| 60 |
/// @notice Thrown when provided revenue share is invalid (exceeds 100%) |
||
| 61 |
error INVALID_REVENUE_SHARE(); |
||
| 62 |
/// @notice Thrown when an invalid fee value is proposed (must be <= BPS_MAX) |
||
| 63 |
error INVALID_FEE_VALUE(); |
||
| 64 |
/// @notice Thrown when no proposed fee exists but one is expected |
||
| 65 |
error NO_PROPOSED_FEE(FeeType feeType); |
||
| 66 |
/// @notice Thrown when timelock period has not expired |
||
| 67 |
error TIMELOCK_NOT_EXPIRED(); |
||
| 68 |
/// @notice Thrown when a validator is not registered |
||
| 69 |
error VALIDATOR_NOT_REGISTERED(); |
||
| 70 |
/// @notice Thrown when a validator is already registered |
||
| 71 |
error VALIDATOR_ALREADY_REGISTERED(); |
||
| 72 |
/// @notice Thrown when trying to change active PPS oracle directly |
||
| 73 |
error MUST_USE_TIMELOCK_FOR_CHANGE(); |
||
| 74 |
/// @notice Thrown when a SuperBank hook Merkle root is not registered but expected to be |
||
| 75 |
error INVALID_TIMESTAMP(); |
||
| 76 |
/// @notice Thrown when attempting to set an invalid quorum value (typically zero) |
||
| 77 |
error INVALID_QUORUM(); |
||
| 78 |
/// @notice Thrown when no active PPS oracle is set but one is required |
||
| 79 |
error NO_ACTIVE_PPS_ORACLE(); |
||
| 80 |
/// @notice Thrown when no proposed PPS oracle exists but one is expected |
||
| 81 |
error NO_PROPOSED_PPS_ORACLE(); |
||
| 82 |
/// @notice Error thrown when manager takeovers are frozen |
||
| 83 |
error MANAGER_TAKEOVERS_FROZEN(); |
||
| 84 |
/// @notice Thrown when no proposed Merkle root exists but one is expected |
||
| 85 |
error NO_PROPOSED_MERKLE_ROOT(); |
||
| 86 |
/// @notice Thrown when no proposed Merkle root exists but one is expected |
||
| 87 |
error ZERO_PROPOSED_MERKLE_ROOT(); |
||
| 88 |
/// @notice Thrown when no proposed minimum staleness exists but one is expected |
||
| 89 |
error NO_PROPOSED_MIN_STALENESS(); |
||
| 90 |
/// @notice Thrown when the provided maxStaleness is less than the minimum required staleness |
||
| 91 |
error MAX_STALENESS_TOO_LOW(); |
||
| 92 |
/// @notice Thrown when a relayer is not registered |
||
| 93 |
error RELAYER_NOT_REGISTERED(); |
||
| 94 |
/// @notice Thrown when a relayer is already registered |
||
| 95 |
error RELAYER_ALREADY_REGISTERED(); |
||
| 96 |
/// @notice Thrown when an executor is not registered |
||
| 97 |
error EXECUTOR_NOT_REGISTERED(); |
||
| 98 |
/// @notice Thrown when an executor is already registered |
||
| 99 |
error EXECUTOR_ALREADY_REGISTERED(); |
||
| 100 |
/// @notice Thrown when there's no pending change but one is expected |
||
| 101 |
error NO_PENDING_CHANGE(); |
||
| 102 |
/// @notice Thrown when a manager is not registered |
||
| 103 |
error MANAGER_NOT_REGISTERED(); |
||
| 104 |
/// @notice Thrown when a manager is already registered |
||
| 105 |
error MANAGER_ALREADY_REGISTERED(); |
||
| 106 |
/// @notice Thrown when a token is already whitelisted |
||
| 107 |
error TOKEN_ALREADY_WHITELISTED(); |
||
| 108 |
/// @notice Thrown when a token is not proposed for whitelisting but expected to be |
||
| 109 |
error NOT_PROPOSED_INCENTIVE_TOKEN(); |
||
| 110 |
/// @notice Thrown when a token is not whitelisted but expected to be |
||
| 111 |
error NOT_WHITELISTED_INCENTIVE_TOKEN(); |
||
| 112 |
/// @notice Thrown when trying to register a keeper that is already registered |
||
| 113 |
error KEEPER_ALREADY_REGISTERED(); |
||
| 114 |
/// @notice Thrown when trying to unregister a keeper that is not registered |
||
| 115 |
error KEEPER_NOT_REGISTERED(); |
||
| 116 |
/// @notice Thrown when the price is not found |
||
| 117 |
error PRICE_NOT_FOUND(); |
||
| 118 |
/// @notice Thrown when the price is stale |
||
| 119 |
error STALE_ORACLE_PRICE(); |
||
| 120 |
/// @notice Thrown when the super oracle is not found |
||
| 121 |
error SUPER_ORACLE_NOT_FOUND(); |
||
| 122 |
/// @notice Thrown when the up token is not found |
||
| 123 |
error UP_NOT_FOUND(); |
||
| 124 |
/// @notice Thrown when the gas info is invalid |
||
| 125 |
error INVALID_GAS_INFO(); |
||
| 126 | |||
| 127 |
/*////////////////////////////////////////////////////////////// |
||
| 128 |
EVENTS |
||
| 129 |
//////////////////////////////////////////////////////////////*/ |
||
| 130 |
/// @notice Emitted when an address is set in the registry |
||
| 131 |
/// @param key The key used to reference the address |
||
| 132 |
/// @param value The address value |
||
| 133 |
event AddressSet(bytes32 indexed key, address indexed value); |
||
| 134 | |||
| 135 |
/// @notice Emitted when a hook is approved |
||
| 136 |
/// @param hook The address of the approved hook |
||
| 137 |
event HookApproved(address indexed hook); |
||
| 138 | |||
| 139 |
/// @notice Emitted when a hook is removed |
||
| 140 |
/// @param hook The address of the removed hook |
||
| 141 |
event HookRemoved(address indexed hook); |
||
| 142 | |||
| 143 |
/// @notice Emitted when a fulfill requests hook is registered |
||
| 144 |
/// @param hook The address of the registered fulfill requests hook |
||
| 145 |
event FulfillRequestsHookRegistered(address indexed hook); |
||
| 146 | |||
| 147 |
/// @notice Emitted when a fulfill requests hook is unregistered |
||
| 148 |
/// @param hook The address of the unregistered fulfill requests hook |
||
| 149 |
event FulfillRequestsHookUnregistered(address indexed hook); |
||
| 150 | |||
| 151 |
/// @notice Emitted when a validator is registered |
||
| 152 |
/// @param validator The address of the registered validator |
||
| 153 |
event ValidatorAdded(address indexed validator); |
||
| 154 | |||
| 155 |
/// @notice Emitted when a validator is removed |
||
| 156 |
/// @param validator The address of the removed validator |
||
| 157 |
event ValidatorRemoved(address indexed validator); |
||
| 158 | |||
| 159 |
/// @notice Emitted when revenue share is updated |
||
| 160 |
/// @param share The new revenue share percentage |
||
| 161 |
event RevenueShareUpdated(uint256 share); |
||
| 162 | |||
| 163 |
/// @notice Emitted when a new fee is proposed |
||
| 164 |
/// @param feeType The type of fee being proposed |
||
| 165 |
/// @param value The proposed fee value (in basis points) |
||
| 166 |
/// @param effectiveTime The timestamp when the fee will be effective |
||
| 167 |
event FeeProposed(FeeType indexed feeType, uint256 value, uint256 effectiveTime); |
||
| 168 | |||
| 169 |
/// @notice Emitted when a fee is updated |
||
| 170 |
/// @param feeType The type of fee being updated |
||
| 171 |
/// @param value The new fee value (in basis points) |
||
| 172 |
event FeeUpdated(FeeType indexed feeType, uint256 value); |
||
| 173 | |||
| 174 |
/// @notice Emitted when a new SuperBank hook Merkle root is proposed |
||
| 175 |
/// @param hook The hook address for which the Merkle root is being proposed |
||
| 176 |
/// @param newRoot The new Merkle root |
||
| 177 |
/// @param effectiveTime The timestamp when the new root will be effective |
||
| 178 |
event SuperBankHookMerkleRootProposed(address indexed hook, bytes32 newRoot, uint256 effectiveTime); |
||
| 179 | |||
| 180 |
/// @notice Emitted when the SuperBank hook Merkle root is updated. |
||
| 181 |
/// @param hook The address of the hook for which the Merkle root was updated. |
||
| 182 |
/// @param newRoot The new Merkle root. |
||
| 183 |
event SuperBankHookMerkleRootUpdated(address indexed hook, bytes32 newRoot); |
||
| 184 | |||
| 185 |
/// @notice Emitted when the VaultBank hook Merkle root is proposed |
||
| 186 |
/// @param hook The hook address for which the Merkle root is being proposed |
||
| 187 |
/// @param newRoot The new Merkle root |
||
| 188 |
/// @param effectiveTime The timestamp when the new root will be effective |
||
| 189 |
event VaultBankHookMerkleRootProposed(address indexed hook, bytes32 newRoot, uint256 effectiveTime); |
||
| 190 | |||
| 191 |
/// @notice Emitted when the VaultBank hook Merkle root is updated. |
||
| 192 |
/// @param hook The address of the hook for which the Merkle root was updated. |
||
| 193 |
/// @param newRoot The new Merkle root. |
||
| 194 |
event VaultBankHookMerkleRootUpdated(address indexed hook, bytes32 newRoot); |
||
| 195 | |||
| 196 |
/// @notice Emitted when the active PPS Oracle's quorum requirement is updated |
||
| 197 |
/// @param quorum The new quorum value |
||
| 198 |
event PPSOracleQuorumUpdated(uint256 quorum); |
||
| 199 | |||
| 200 |
/// @notice Emitted when an active PPS oracle is initially set |
||
| 201 |
/// @param oracle The address of the set oracle |
||
| 202 |
event ActivePPSOracleSet(address indexed oracle); |
||
| 203 | |||
| 204 |
/// @notice Emitted when a new PPS oracle is proposed |
||
| 205 |
/// @param oracle The address of the proposed oracle |
||
| 206 |
/// @param effectiveTime The timestamp when the proposal will be effective |
||
| 207 |
event ActivePPSOracleProposed(address indexed oracle, uint256 effectiveTime); |
||
| 208 | |||
| 209 |
/// @notice Emitted when the active PPS oracle is changed |
||
| 210 |
/// @param oldOracle The address of the previous oracle |
||
| 211 |
/// @param newOracle The address of the new oracle |
||
| 212 |
event ActivePPSOracleChanged(address indexed oldOracle, address indexed newOracle); |
||
| 213 | |||
| 214 |
/// @notice Event emitted when manager takeovers are permanently frozen |
||
| 215 |
event ManagerTakeoversFrozen(); |
||
| 216 | |||
| 217 |
/// @notice Emitted when a relayer is added |
||
| 218 |
/// @param relayer The address of the added relayer |
||
| 219 |
event RelayerAdded(address indexed relayer); |
||
| 220 | |||
| 221 |
/// @notice Emitted when a relayer is removed |
||
| 222 |
/// @param relayer The address of the removed relayer |
||
| 223 |
event RelayerRemoved(address indexed relayer); |
||
| 224 | |||
| 225 |
/// @notice Emitted when a vault bank is added |
||
| 226 |
/// @param chainId The chain ID of the added vault bank |
||
| 227 |
/// @param vaultBank The address of the added vault bank |
||
| 228 |
event VaultBankAddressAdded(uint64 indexed chainId, address indexed vaultBank); |
||
| 229 | |||
| 230 |
/// @notice Emitted when an executor is added |
||
| 231 |
/// @param executor The address of the added executor |
||
| 232 |
event ExecutorAdded(address indexed executor); |
||
| 233 | |||
| 234 |
/// @notice Emitted when an executor is removed |
||
| 235 |
/// @param executor The address of the removed executor |
||
| 236 |
event ExecutorRemoved(address indexed executor); |
||
| 237 | |||
| 238 |
/// @notice Emitted when a prover is set |
||
| 239 |
/// @param prover The address of the prover |
||
| 240 |
event ProverSet(address indexed prover); |
||
| 241 | |||
| 242 |
/// @notice Emitted when a change to upkeep payments status is proposed |
||
| 243 |
/// @param enabled The proposed status (enabled/disabled) |
||
| 244 |
/// @param effectiveTime The timestamp when the status change will be effective |
||
| 245 |
event UpkeepPaymentsChangeProposed(bool enabled, uint256 effectiveTime); |
||
| 246 | |||
| 247 |
/// @notice Emitted when upkeep payments status is changed |
||
| 248 |
/// @param enabled The new status (enabled/disabled) |
||
| 249 |
event UpkeepPaymentsChanged(bool enabled); |
||
| 250 | |||
| 251 |
/// @notice Emitted when a new minimum staleness is proposed |
||
| 252 |
/// @param newMinStaleness The proposed minimum staleness value |
||
| 253 |
/// @param effectiveTime The timestamp when the new value will be effective |
||
| 254 |
event MinStalenesProposed(uint256 newMinStaleness, uint256 effectiveTime); |
||
| 255 | |||
| 256 |
/// @notice Emitted when the minimum staleness is changed |
||
| 257 |
/// @param newMinStaleness The new minimum staleness value |
||
| 258 |
event MinStalenesChanged(uint256 newMinStaleness); |
||
| 259 | |||
| 260 |
/// @notice Emitted when a superform manager is added |
||
| 261 |
/// @param manager The address of the added manager |
||
| 262 |
event SuperformManagerAdded(address indexed manager); |
||
| 263 | |||
| 264 |
/// @notice Emitted when a superform manager is removed |
||
| 265 |
/// @param manager The address of the removed manager |
||
| 266 |
event SuperformManagerRemoved(address indexed manager); |
||
| 267 | |||
| 268 |
/// @notice Emitted when incentive tokens are proposed for whitelisting |
||
| 269 |
/// @param tokens The addresses of the proposed tokens |
||
| 270 |
/// @param effectiveTime The timestamp when the proposal will be effective |
||
| 271 |
event WhitelistedIncentiveTokensProposed(address[] tokens, uint256 effectiveTime); |
||
| 272 | |||
| 273 |
/// @notice Emitted when whitelisted incentive tokens are added |
||
| 274 |
/// @param tokens The addresses of the added tokens |
||
| 275 |
event WhitelistedIncentiveTokensAdded(address[] tokens); |
||
| 276 | |||
| 277 |
/// @notice Emitted when whitelisted incentive tokens are removed |
||
| 278 |
/// @param tokens The addresses of the removed tokens |
||
| 279 |
event WhitelistedIncentiveTokensRemoved(address[] tokens); |
||
| 280 | |||
| 281 |
/// @notice Emitted when a protected keeper is registered |
||
| 282 |
/// @param keeper Address of the keeper being registered |
||
| 283 |
event ProtectedKeeperRegistered(address indexed keeper); |
||
| 284 | |||
| 285 |
/// @notice Emitted when a protected keeper is unregistered |
||
| 286 |
/// @param keeper Address of the keeper being unregistered |
||
| 287 |
event ProtectedKeeperUnregistered(address indexed keeper); |
||
| 288 | |||
| 289 |
/// @notice Emitted when gas info is set |
||
| 290 |
/// @param oracle The address of the oracle |
||
| 291 |
/// @param baseGasBatch The base gas for the oracle |
||
| 292 |
/// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle |
||
| 293 |
event GasInfoSet(address indexed oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch); |
||
| 294 | |||
| 295 |
/*////////////////////////////////////////////////////////////// |
||
| 296 |
CONTRACT REGISTRY FUNCTIONS |
||
| 297 |
//////////////////////////////////////////////////////////////*/ |
||
| 298 |
/// @notice Sets an address in the registry |
||
| 299 |
/// @param key The key to associate with the address |
||
| 300 |
/// @param value The address value |
||
| 301 |
function setAddress(bytes32 key, address value) external; |
||
| 302 | |||
| 303 |
/*////////////////////////////////////////////////////////////// |
||
| 304 |
PERIPHERY CONFIGURATIONS |
||
| 305 |
//////////////////////////////////////////////////////////////*/ |
||
| 306 |
/// @notice Sets the prover address |
||
| 307 |
/// @param prover The address of the prover |
||
| 308 |
function setProver(address prover) external; |
||
| 309 | |||
| 310 |
/// @notice Change the primary manager for a strategy |
||
| 311 |
/// @dev Only SuperGovernor can call this function directly |
||
| 312 |
/// @param strategy The strategy address |
||
| 313 |
/// @param newManager The new primary manager address |
||
| 314 |
function changePrimaryManager(address strategy, address newManager) external; |
||
| 315 | |||
| 316 |
/// @notice Permanently freezes all manager takeovers globally |
||
| 317 |
function freezeManagerTakeover() external; |
||
| 318 | |||
| 319 |
/// @notice Changes the hooks root update timelock duration |
||
| 320 |
/// @param newTimelock New timelock duration in seconds |
||
| 321 |
function changeHooksRootUpdateTimelock(uint256 newTimelock) external; |
||
| 322 | |||
| 323 |
/// @notice Proposes a new global hooks Merkle root |
||
| 324 |
/// @dev Only GOVERNOR_ROLE can call this function |
||
| 325 |
/// @param newRoot New Merkle root for global hooks validation |
||
| 326 |
function proposeGlobalHooksRoot(bytes32 newRoot) external; |
||
| 327 | |||
| 328 |
/// @notice Sets veto status for global hooks Merkle root |
||
| 329 |
/// @dev Only GUARDIAN_ROLE can call this function |
||
| 330 |
/// @param vetoed Whether to veto (true) or unveto (false) the global hooks root |
||
| 331 |
function setGlobalHooksRootVetoStatus(bool vetoed) external; |
||
| 332 | |||
| 333 |
/// @notice Sets veto status for a strategy-specific hooks Merkle root |
||
| 334 |
/// @dev Only GUARDIAN_ROLE can call this function |
||
| 335 |
/// @param strategy Address of the strategy to affect |
||
| 336 |
/// @param vetoed Whether to veto (true) or unveto (false) the strategy hooks root |
||
| 337 |
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external; |
||
| 338 | |||
| 339 |
/// @notice Sets the superasset manager for a superasset |
||
| 340 |
/// @param superAsset The superasset address |
||
| 341 |
/// @param superAssetManager The new superasset manager address |
||
| 342 |
function setSuperAssetManager(address superAsset, address superAssetManager) external; |
||
| 343 | |||
| 344 |
/// @notice Adds an ICC to the whitelist |
||
| 345 |
/// @param icc The ICC address to add |
||
| 346 |
function addICCToWhitelist(address icc) external; |
||
| 347 | |||
| 348 |
/// @notice Removes an ICC from the whitelist |
||
| 349 |
/// @param icc The ICC address to remove |
||
| 350 |
function removeICCFromWhitelist(address icc) external; |
||
| 351 | |||
| 352 |
/// @notice Sets the maximum staleness period for all oracle feeds |
||
| 353 |
/// @param newMaxStaleness The new maximum staleness period in seconds |
||
| 354 |
function setOracleMaxStaleness(uint256 newMaxStaleness) external; |
||
| 355 | |||
| 356 |
/// @notice Sets the maximum staleness period for a specific oracle feed |
||
| 357 |
/// @param feed The address of the feed to set staleness for |
||
| 358 |
/// @param newMaxStaleness The new maximum staleness period in seconds |
||
| 359 |
function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external; |
||
| 360 | |||
| 361 |
/// @notice Sets the maximum staleness periods for multiple oracle feeds in batch |
||
| 362 |
/// @param feeds The addresses of the feeds to set staleness for |
||
| 363 |
/// @param newMaxStalenessList The new maximum staleness periods in seconds |
||
| 364 |
function setOracleFeedMaxStalenessBatch( |
||
| 365 |
address[] calldata feeds, |
||
| 366 |
uint256[] calldata newMaxStalenessList |
||
| 367 |
) |
||
| 368 |
external; |
||
| 369 | |||
| 370 |
/// @notice Queues an oracle update for execution after timelock period |
||
| 371 |
/// @param bases Base asset addresses |
||
| 372 |
/// @param quotes Quote asset addresses |
||
| 373 |
/// @param providers Provider identifiers |
||
| 374 |
/// @param feeds Feed addresses |
||
| 375 |
function queueOracleUpdate( |
||
| 376 |
address[] calldata bases, |
||
| 377 |
address[] calldata quotes, |
||
| 378 |
bytes32[] calldata providers, |
||
| 379 |
address[] calldata feeds |
||
| 380 |
) |
||
| 381 |
external; |
||
| 382 | |||
| 383 |
/// @notice Queues a provider removal for execution after timelock period |
||
| 384 |
/// @param providers The providers to remove |
||
| 385 |
function queueOracleProviderRemoval(bytes32[] calldata providers) external; |
||
| 386 | |||
| 387 |
/// @notice Sets uptime feeds for multiple data oracles in batch (Layer 2 only) |
||
| 388 |
/// @param dataOracles Array of data oracle addresses to set uptime feeds for |
||
| 389 |
/// @param uptimeOracles Array of uptime feed addresses to set |
||
| 390 |
/// @param gracePeriods Array of grace periods in seconds after sequencer restart |
||
| 391 |
function batchSetOracleUptimeFeed( |
||
| 392 |
address[] calldata dataOracles, |
||
| 393 |
address[] calldata uptimeOracles, |
||
| 394 |
uint256[] calldata gracePeriods |
||
| 395 |
) |
||
| 396 |
external; |
||
| 397 | |||
| 398 |
/// @notice Sets the emergency price for a token |
||
| 399 |
/// @param token The address of the token |
||
| 400 |
/// @param price The emergency price to set |
||
| 401 |
function setEmergencyPrice(address token, uint256 price) external; |
||
| 402 | |||
| 403 |
/// @notice Sets the emergency price for multiple tokens o |
||
| 404 |
/// @param tokens Array of token addresses |
||
| 405 |
/// @param prices Array of emergency prices |
||
| 406 |
function batchSetEmergencyPrices(address[] calldata tokens, uint256[] calldata prices) external; |
||
| 407 | |||
| 408 |
/*////////////////////////////////////////////////////////////// |
||
| 409 |
HOOK MANAGEMENT |
||
| 410 |
//////////////////////////////////////////////////////////////*/ |
||
| 411 |
/// @notice Registers a hook for use in SuperVaults |
||
| 412 |
/// @param hook The address of the hook to register |
||
| 413 |
/// @param isFulfillRequestsHook Whether the hook is a fulfill requests hook |
||
| 414 |
function registerHook(address hook, bool isFulfillRequestsHook) external; |
||
| 415 | |||
| 416 |
/// @notice Unregisters a hook from the approved list |
||
| 417 |
/// @param hook The address of the hook to unregister |
||
| 418 |
function unregisterHook(address hook) external; |
||
| 419 | |||
| 420 |
/*////////////////////////////////////////////////////////////// |
||
| 421 |
EXECUTOR MANAGEMENT |
||
| 422 |
//////////////////////////////////////////////////////////////*/ |
||
| 423 |
/// @notice Adds an executor to the approved list |
||
| 424 |
/// @param executor The address of the executor to add |
||
| 425 |
function addExecutor(address executor) external; |
||
| 426 | |||
| 427 |
/// @notice Removes an executor from the approved list |
||
| 428 |
/// @param executor The address of the executor to remove |
||
| 429 |
function removeExecutor(address executor) external; |
||
| 430 | |||
| 431 |
/*////////////////////////////////////////////////////////////// |
||
| 432 |
RELAYER MANAGEMENT |
||
| 433 |
//////////////////////////////////////////////////////////////*/ |
||
| 434 |
/// @notice Adds a relayer to the approved list |
||
| 435 |
/// @param relayer The address of the relayer to add |
||
| 436 |
function addRelayer(address relayer) external; |
||
| 437 | |||
| 438 |
/// @notice Removes a relayer from the approved list |
||
| 439 |
/// @param relayer The address of the relayer to remove |
||
| 440 |
function removeRelayer(address relayer) external; |
||
| 441 | |||
| 442 |
/*////////////////////////////////////////////////////////////// |
||
| 443 |
VALIDATOR MANAGEMENT |
||
| 444 |
//////////////////////////////////////////////////////////////*/ |
||
| 445 |
/// @notice Adds a validator to the approved list |
||
| 446 |
/// @param validator The address of the validator to add |
||
| 447 |
function addValidator(address validator) external; |
||
| 448 | |||
| 449 |
/// @notice Removes a validator from the approved list |
||
| 450 |
/// @param validator The address of the validator to remove |
||
| 451 |
function removeValidator(address validator) external; |
||
| 452 | |||
| 453 |
/*////////////////////////////////////////////////////////////// |
||
| 454 |
PPS ORACLE MANAGEMENT |
||
| 455 |
//////////////////////////////////////////////////////////////*/ |
||
| 456 |
/// @notice Sets the active PPS oracle (only if there is no active oracle yet) |
||
| 457 |
/// @param oracle Address of the PPS oracle to set as active |
||
| 458 |
function setActivePPSOracle(address oracle) external; |
||
| 459 | |||
| 460 |
/// @notice Proposes a new active PPS oracle (when there is already an active one) |
||
| 461 |
/// @param oracle Address of the PPS oracle to propose as active |
||
| 462 |
function proposeActivePPSOracle(address oracle) external; |
||
| 463 | |||
| 464 |
/// @notice Executes a previously proposed PPS oracle change after timelock has expired |
||
| 465 |
function executeActivePPSOracleChange() external; |
||
| 466 | |||
| 467 |
/// @notice Sets the quorum requirement for the active PPS Oracle |
||
| 468 |
/// @param quorum The new quorum value |
||
| 469 |
function setPPSOracleQuorum(uint256 quorum) external; |
||
| 470 | |||
| 471 |
/*////////////////////////////////////////////////////////////// |
||
| 472 |
REVENUE SHARE MANAGEMENT |
||
| 473 |
//////////////////////////////////////////////////////////////*/ |
||
| 474 |
/// @notice Proposes a new fee value |
||
| 475 |
/// @param feeType The type of fee to propose |
||
| 476 |
/// @param value The proposed fee value (in basis points) |
||
| 477 |
function proposeFee(FeeType feeType, uint256 value) external; |
||
| 478 | |||
| 479 |
/// @notice Executes a previously proposed fee update after timelock has expired |
||
| 480 |
/// @param feeType The type of ffee to execute the update for |
||
| 481 |
function executeFeeUpdate(FeeType feeType) external; |
||
| 482 | |||
| 483 |
/// @notice Executes an upkeep claim on `SuperVaultAggregator` |
||
| 484 |
/// @param amount The amount to claim |
||
| 485 |
function executeUpkeepClaim(uint256 amount) external; |
||
| 486 | |||
| 487 |
/*////////////////////////////////////////////////////////////// |
||
| 488 |
UPKEEP COST MANAGEMENT |
||
| 489 |
//////////////////////////////////////////////////////////////*/ |
||
| 490 |
/// @notice Sets gas info for an oracle |
||
| 491 |
/// @param oracle The address of the oracle |
||
| 492 |
/// @param baseGasBatch The base gas for the oracle |
||
| 493 |
/// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle |
||
| 494 |
function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external; |
||
| 495 | |||
| 496 |
/// @notice Proposes a change to upkeep payments enabled status |
||
| 497 |
/// @param enabled The proposed enabled status |
||
| 498 |
function proposeUpkeepPaymentsChange(bool enabled) external; |
||
| 499 | |||
| 500 |
/// @notice Executes a previously proposed upkeep payments status change |
||
| 501 |
function executeUpkeepPaymentsChange() external; |
||
| 502 | |||
| 503 |
/*////////////////////////////////////////////////////////////// |
||
| 504 |
MIN STALENESS MANAGEMENT |
||
| 505 |
//////////////////////////////////////////////////////////////*/ |
||
| 506 |
/// @notice Proposes a new minimum staleness value to prevent maxStaleness from being set too low |
||
| 507 |
/// @param newMinStaleness The proposed new minimum staleness value in seconds |
||
| 508 |
function proposeMinStaleness(uint256 newMinStaleness) external; |
||
| 509 | |||
| 510 |
/// @notice Executes a previously proposed minimum staleness change after timelock has expired |
||
| 511 |
function executeMinStalenesChange() external; |
||
| 512 | |||
| 513 |
/*////////////////////////////////////////////////////////////// |
||
| 514 |
SUPERFORM MANAGER MANAGEMENT |
||
| 515 |
//////////////////////////////////////////////////////////////*/ |
||
| 516 | |||
| 517 |
/// @notice Adds a manager to the superform managers list |
||
| 518 |
/// @param manager Address of the manager to add |
||
| 519 |
function addSuperformManager(address manager) external; |
||
| 520 | |||
| 521 |
/// @notice Removes a manager from the superform managers list |
||
| 522 |
/// @param manager Address of the manager to remove |
||
| 523 |
function removeSuperformManager(address manager) external; |
||
| 524 | |||
| 525 |
/*////////////////////////////////////////////////////////////// |
||
| 526 |
VAULT HOOKS MGMT |
||
| 527 |
//////////////////////////////////////////////////////////////*/ |
||
| 528 |
/// @notice Proposes a new Merkle root for a specific hook's allowed targets. |
||
| 529 |
/// @param hook The address of the hook to update the Merkle root for. |
||
| 530 |
/// @param proposedRoot The proposed new Merkle root. |
||
| 531 |
function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external; |
||
| 532 | |||
| 533 |
/// @notice Executes a previously proposed Merkle root update for a specific hook if the effective time has passed. |
||
| 534 |
/// @param hook The address of the hook to execute the update for. |
||
| 535 |
function executeVaultBankHookMerkleRootUpdate(address hook) external; |
||
| 536 | |||
| 537 |
/*////////////////////////////////////////////////////////////// |
||
| 538 |
SUPERBANK HOOKS MGMT |
||
| 539 |
//////////////////////////////////////////////////////////////*/ |
||
| 540 |
/// @notice Proposes a new Merkle root for a specific hook's allowed targets. |
||
| 541 |
/// @param hook The address of the hook to update the Merkle root for. |
||
| 542 |
/// @param proposedRoot The proposed new Merkle root. |
||
| 543 |
function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external; |
||
| 544 | |||
| 545 |
/// @notice Executes a previously proposed Merkle root update for a specific hook if the effective time has passed. |
||
| 546 |
/// @param hook The address of the hook to execute the update for. |
||
| 547 |
function executeSuperBankHookMerkleRootUpdate(address hook) external; |
||
| 548 | |||
| 549 |
/*////////////////////////////////////////////////////////////// |
||
| 550 |
VAULT BANK MANAGEMENT |
||
| 551 |
//////////////////////////////////////////////////////////////*/ |
||
| 552 |
/// @notice Adds a vault bank address for a specific chain ID |
||
| 553 |
/// @param chainId The chain ID to add the vault bank for |
||
| 554 |
/// @param vaultBank The address of the vault bank to add |
||
| 555 |
function addVaultBank(uint64 chainId, address vaultBank) external; |
||
| 556 | |||
| 557 |
/*////////////////////////////////////////////////////////////// |
||
| 558 |
INCENTIVE TOKEN MANAGEMENT |
||
| 559 |
//////////////////////////////////////////////////////////////*/ |
||
| 560 |
/// @notice Proposes whitelisted incentive tokens |
||
| 561 |
/// @param tokens The addresses of the tokens to add |
||
| 562 |
function proposeAddIncentiveTokens(address[] memory tokens) external; |
||
| 563 | |||
| 564 |
/// @notice Executes a previously proposed whitelisted incentive token update after timelock has expired |
||
| 565 |
function executeAddIncentiveTokens() external; |
||
| 566 | |||
| 567 |
/// @notice Proposes a new whitelisted incentive token |
||
| 568 |
/// @param tokens The addresses of the tokens to add |
||
| 569 |
function proposeRemoveIncentiveTokens(address[] memory tokens) external; |
||
| 570 | |||
| 571 |
/// @notice Executes a previously proposed whitelisted incentive tokens removal after timelock has expired |
||
| 572 |
function executeRemoveIncentiveTokens() external; |
||
| 573 | |||
| 574 |
/*////////////////////////////////////////////////////////////// |
||
| 575 |
EXTERNAL VIEW FUNCTIONS |
||
| 576 |
//////////////////////////////////////////////////////////////*/ |
||
| 577 |
/// @notice The identifier of the role that grants access to critical governance functions |
||
| 578 |
function SUPER_GOVERNOR_ROLE() external view returns (bytes32); |
||
| 579 | |||
| 580 |
/// @notice The identifier of the role that grants access to daily operations like hooks and validators |
||
| 581 |
function GOVERNOR_ROLE() external view returns (bytes32); |
||
| 582 | |||
| 583 |
/// @notice The identifier of the role that grants access to bank management functions |
||
| 584 |
function BANK_MANAGER_ROLE() external view returns (bytes32); |
||
| 585 | |||
| 586 |
/// @notice The identifier of the role that grants access to gas management functions |
||
| 587 |
function GAS_MANAGER_ROLE() external view returns (bytes32); |
||
| 588 | |||
| 589 |
/// @notice The identifier of the role that grants access to guardian functions |
||
| 590 |
function GUARDIAN_ROLE() external view returns (bytes32); |
||
| 591 | |||
| 592 |
/// @notice The identifier of the role that grants access to superasset factory |
||
| 593 |
function SUPER_ASSET_FACTORY() external view returns (bytes32); |
||
| 594 | |||
| 595 |
/// @notice Gets an address from the registry |
||
| 596 |
/// @param key The key of the address to get |
||
| 597 |
/// @return The address value |
||
| 598 |
function getAddress(bytes32 key) external view returns (address); |
||
| 599 | |||
| 600 |
/// @notice Checks if manager takeovers are frozen |
||
| 601 |
/// @return True if manager takeovers are frozen, false otherwise |
||
| 602 |
function isManagerTakeoverFrozen() external view returns (bool); |
||
| 603 | |||
| 604 |
/// @notice Gets the vault bank address for a specific chain ID |
||
| 605 |
/// @param chainId The chain ID to get the vault bank for |
||
| 606 |
/// @return The vault bank address |
||
| 607 |
function getVaultBank(uint64 chainId) external view returns (address); |
||
| 608 | |||
| 609 |
/// @notice Checks if a hook is registered |
||
| 610 |
/// @param hook The address of the hook to check |
||
| 611 |
/// @return True if the hook is registered, false otherwise |
||
| 612 |
function isHookRegistered(address hook) external view returns (bool); |
||
| 613 | |||
| 614 |
/// @notice Checks if a hook is registered as a fulfill requests hook |
||
| 615 |
/// @param hook The address of the hook to check |
||
| 616 |
/// @return True if the hook is registered as a fulfill requests hook, false otherwise |
||
| 617 |
function isFulfillRequestsHookRegistered(address hook) external view returns (bool); |
||
| 618 | |||
| 619 |
/// @notice Gets all registered hooks |
||
| 620 |
/// @return An array of registered hook addresses |
||
| 621 |
function getRegisteredHooks() external view returns (address[] memory); |
||
| 622 | |||
| 623 |
/// @notice Gets all registered fulfill requests hooks |
||
| 624 |
/// @return An array of registered fulfill requests hook addresses |
||
| 625 |
function getRegisteredFulfillRequestsHooks() external view returns (address[] memory); |
||
| 626 | |||
| 627 |
/// @notice Checks if an address is an approved validator |
||
| 628 |
/// @param validator The address to check |
||
| 629 |
/// @return True if the address is an approved validator, false otherwise |
||
| 630 |
function isValidator(address validator) external view returns (bool); |
||
| 631 | |||
| 632 |
/// @notice Checks if an address has the guardian role |
||
| 633 |
/// @param guardian Address to check |
||
| 634 |
/// @return true if the address has the GUARDIAN_ROLE |
||
| 635 |
function isGuardian(address guardian) external view returns (bool); |
||
| 636 | |||
| 637 |
/// @notice Checks if an address is an approved relayer |
||
| 638 |
/// @param relayer The address to check |
||
| 639 |
/// @return True if the address is an approved relayer, false otherwise |
||
| 640 |
function isRelayer(address relayer) external view returns (bool); |
||
| 641 | |||
| 642 |
/// @notice Checks if an address is an approved executor |
||
| 643 |
/// @param executor The address to check |
||
| 644 |
/// @return True if the address is an approved executor, false otherwise |
||
| 645 |
function isExecutor(address executor) external view returns (bool); |
||
| 646 | |||
| 647 |
/// @notice Returns all registered validators |
||
| 648 |
/// @return List of validator addresses |
||
| 649 |
function getValidators() external view returns (address[] memory); |
||
| 650 | |||
| 651 |
/// @notice Returns all registered relayers |
||
| 652 |
/// @return List of relayer addresses |
||
| 653 |
function getRelayers() external view returns (address[] memory); |
||
| 654 | |||
| 655 |
/// @notice Returns all registered executors |
||
| 656 |
/// @return List of executor addresses |
||
| 657 |
function getExecutors() external view returns (address[] memory); |
||
| 658 | |||
| 659 |
/// @notice Gets the proposed active PPS oracle and its effective time |
||
| 660 |
/// @return proposedOracle The proposed oracle address |
||
| 661 |
/// @return effectiveTime The timestamp when the proposed oracle will become effective |
||
| 662 |
function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime); |
||
| 663 | |||
| 664 |
/// @notice Gets the current quorum requirement for the active PPS Oracle |
||
| 665 |
/// @return The current quorum requirement |
||
| 666 |
function getPPSOracleQuorum() external view returns (uint256); |
||
| 667 | |||
| 668 |
/// @notice Gets the active PPS oracle |
||
| 669 |
/// @return The active PPS oracle address |
||
| 670 |
function getActivePPSOracle() external view returns (address); |
||
| 671 | |||
| 672 |
/// @notice Checks if an address is the current active PPS oracle |
||
| 673 |
/// @param oracle The address to check |
||
| 674 |
/// @return True if the address is the active PPS oracle, false otherwise |
||
| 675 |
function isActivePPSOracle(address oracle) external view returns (bool); |
||
| 676 | |||
| 677 |
/// @notice Gets the current fee value for a specific fee type |
||
| 678 |
/// @param feeType The type of fee to get |
||
| 679 |
/// @return The current fee value (in basis points) |
||
| 680 |
function getFee(FeeType feeType) external view returns (uint256); |
||
| 681 | |||
| 682 |
/// @notice Gets the current upkeep cost per batch update for PPS updates |
||
| 683 |
/// @param oracle The address of the PPS oracle |
||
| 684 |
/// @param chargeableEntries The number of chargeable entries |
||
| 685 |
/// @return The current upkeep cost per batch update in UP tokens |
||
| 686 |
function getUpkeepCostPerBatchUpdate(address oracle, uint256 chargeableEntries) external view returns (uint256); |
||
| 687 | |||
| 688 |
/// @notice Gets the proposed upkeep cost per update and its effective time |
||
| 689 |
/// @notice Gets the current minimum staleness value |
||
| 690 |
/// @return The current minimum staleness value in seconds |
||
| 691 |
function getMinStaleness() external view returns (uint256); |
||
| 692 | |||
| 693 |
/// @notice Gets the proposed minimum staleness value and its effective time |
||
| 694 |
/// @return proposedMinStaleness The proposed new minimum staleness value |
||
| 695 |
/// @return effectiveTime The timestamp when the new value will become effective |
||
| 696 |
function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime); |
||
| 697 | |||
| 698 |
/// @notice Returns the current Merkle root for a specific hook's allowed targets. |
||
| 699 |
/// @param hook The address of the hook to get the Merkle root for. |
||
| 700 |
/// @return The Merkle root for the hook's allowed targets. |
||
| 701 |
function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32); |
||
| 702 | |||
| 703 |
/// @notice Returns the current Merkle root for a specific hook's allowed targets. |
||
| 704 |
/// @param hook The address of the hook to get the Merkle root for. |
||
| 705 |
/// @return The Merkle root for the hook's allowed targets. |
||
| 706 |
function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32); |
||
| 707 | |||
| 708 |
/// @notice Gets the proposed Merkle root and its effective time for a specific hook. |
||
| 709 |
/// @param hook The address of the hook to get the proposed Merkle root for. |
||
| 710 |
/// @return proposedRoot The proposed Merkle root. |
||
| 711 |
/// @return effectiveTime The timestamp when the proposed root will become effective. |
||
| 712 |
function getProposedSuperBankHookMerkleRoot(address hook) |
||
| 713 |
external |
||
| 714 |
view |
||
| 715 |
returns (bytes32 proposedRoot, uint256 effectiveTime); |
||
| 716 | |||
| 717 |
/// @notice Gets the proposed Merkle root and its effective time for a specific hook. |
||
| 718 |
/// @param hook The address of the hook to get the proposed Merkle root for. |
||
| 719 |
/// @return proposedRoot The proposed Merkle root. |
||
| 720 |
/// @return effectiveTime The timestamp when the proposed root will become effective. |
||
| 721 |
function getProposedVaultBankHookMerkleRoot(address hook) |
||
| 722 |
external |
||
| 723 |
view |
||
| 724 |
returns (bytes32 proposedRoot, uint256 effectiveTime); |
||
| 725 | |||
| 726 |
/// @notice Checks if a token is whitelisted as an incentive token |
||
| 727 |
/// @param token The address of the token to check |
||
| 728 |
/// @return True if the token is whitelisted as an incentive token, false otherwise |
||
| 729 |
function isWhitelistedIncentiveToken(address token) external view returns (bool); |
||
| 730 | |||
| 731 |
/// @notice Gets the prover address |
||
| 732 |
/// @return The address of the prover |
||
| 733 |
function getProver() external view returns (address); |
||
| 734 | |||
| 735 |
/// @notice Checks if upkeep payments are currently enabled |
||
| 736 |
/// @return enabled True if upkeep payments are enabled |
||
| 737 |
function isUpkeepPaymentsEnabled() external view returns (bool); |
||
| 738 | |||
| 739 |
/// @notice Gets the proposed upkeep payments status and effective time |
||
| 740 |
/// @return enabled The proposed status |
||
| 741 |
/// @return effectiveTime The timestamp when the change becomes effective |
||
| 742 |
function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime); |
||
| 743 | |||
| 744 |
/// @notice Checks if an address is a registered superform manager |
||
| 745 |
/// @param manager The address to check |
||
| 746 |
/// @return isSuperform True if the address is a superform manager |
||
| 747 |
function isSuperformManager(address manager) external view returns (bool); |
||
| 748 | |||
| 749 |
/// @notice Gets the list of all superform managers |
||
| 750 |
/// @return managers The list of all superform manager addresses |
||
| 751 |
function getAllSuperformManagers() external view returns (address[] memory); |
||
| 752 | |||
| 753 |
/// @notice Returns up to `limit` superform managers starting from `cursor` |
||
| 754 |
/// @param cursor The index to start reading from (0 … len-1) |
||
| 755 |
/// @param limit The maximum number of records to return |
||
| 756 |
/// @return chunkOfManagers The array slice [cursor … cursor+limit-1] |
||
| 757 |
/// @return next The next cursor value the caller should use, or 0 to indicate done |
||
| 758 |
function getManagersPaginated( |
||
| 759 |
uint256 cursor, |
||
| 760 |
uint256 limit |
||
| 761 |
) |
||
| 762 |
external |
||
| 763 |
view |
||
| 764 |
returns (address[] memory chunkOfManagers, uint256 next); |
||
| 765 | |||
| 766 |
/// @notice Gets the number of superform managers |
||
| 767 |
/// @return The number of superform managers |
||
| 768 |
function getSuperformManagersCount() external view returns (uint256); |
||
| 769 | |||
| 770 |
/// @notice Gets the SUP ID |
||
| 771 |
/// @return The ID of the SUP token |
||
| 772 |
function SUP() external view returns (bytes32); |
||
| 773 | |||
| 774 |
/// @notice Gets the UP ID |
||
| 775 |
/// @return The ID of the UP token |
||
| 776 |
function UP() external view returns (bytes32); |
||
| 777 | |||
| 778 |
/// @notice Gets the Treasury ID |
||
| 779 |
/// @return The ID for the Treasury in the registry |
||
| 780 |
function TREASURY() external view returns (bytes32); |
||
| 781 | |||
| 782 |
/// @notice Gets the SuperOracle ID |
||
| 783 |
/// @return The ID for the SuperOracle in the registry |
||
| 784 |
function SUPER_ORACLE() external view returns (bytes32); |
||
| 785 | |||
| 786 |
/// @notice Gets the ECDSA PPS Oracle ID |
||
| 787 |
/// @return The ID for the ECDSA PPS Oracle in the registry |
||
| 788 |
function ECDSAPPSORACLE() external view returns (bytes32); |
||
| 789 | |||
| 790 |
/// @notice Gets the SuperVaultAggregator ID |
||
| 791 |
/// @return The ID for the SuperVaultAggregator in the registry |
||
| 792 |
function SUPER_VAULT_AGGREGATOR() external view returns (bytes32); |
||
| 793 | |||
| 794 |
/// @notice Gets the SuperBank ID |
||
| 795 |
/// @return The ID for the SuperBank in the registry |
||
| 796 |
function SUPER_BANK() external view returns (bytes32); |
||
| 797 | |||
| 798 |
/// @notice Registers a keeper that cannot be added as authorized caller by managers |
||
| 799 |
/// @dev Only governance can register protected keepers |
||
| 800 |
/// @param keeper Address of the keeper to register |
||
| 801 |
function registerProtectedKeeper(address keeper) external; |
||
| 802 | |||
| 803 |
/// @notice Unregisters a protected keeper |
||
| 804 |
/// @dev Only governance can unregister protected keepers |
||
| 805 |
/// @param keeper Address of the keeper to unregister |
||
| 806 |
function unregisterProtectedKeeper(address keeper) external; |
||
| 807 | |||
| 808 |
/// @notice Checks if an address is a registered protected keeper |
||
| 809 |
/// @param keeper Address to check |
||
| 810 |
/// @return True if the address is a registered protected keeper |
||
| 811 |
function isProtectedKeeper(address keeper) external view returns (bool); |
||
| 812 | |||
| 813 |
/// @notice Gets all registered protected keepers |
||
| 814 |
/// @return Array of all registered protected keeper addresses |
||
| 815 |
function getProtectedKeepers() external view returns (address[] memory); |
||
| 816 | |||
| 817 |
/// @notice Gets the number of registered protected keepers |
||
| 818 |
/// @return The number of registered protected keepers |
||
| 819 |
function getProtectedKeepersCount() external view returns (uint256); |
||
| 820 | |||
| 821 |
/// @notice Gets the gas info for a specific SuperVault PPS Oracle |
||
| 822 |
/// @param oracle_ The address of the oracle to get gas info for |
||
| 823 |
/// @return The gas info for the specified oracle |
||
| 824 |
function getGasInfo(address oracle_) external view returns (GasInfo memory); |
||
| 825 |
} |
||
| 826 |
0.0%
src/interfaces/SuperAsset/ISuperAssetFactory.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/** |
||
| 5 |
* @title ISuperAssetFactory |
||
| 6 |
* @notice Interface for the SuperAssetFactory contract which deploys SuperAsset and its dependencies |
||
| 7 |
*/ |
||
| 8 |
interface ISuperAssetFactory {
|
||
| 9 |
/*////////////////////////////////////////////////////////////// |
||
| 10 |
EVENTS |
||
| 11 |
//////////////////////////////////////////////////////////////*/ |
||
| 12 | |||
| 13 |
/// @notice Emitted when a new SuperAsset and its dependencies are created |
||
| 14 |
/// @param superAsset Address of the deployed SuperAsset contract |
||
| 15 |
/// @param incentiveFund Address of the deployed IncentiveFundContract |
||
| 16 |
/// @param incentiveCalc Address of the deployed IncentiveCalculationContract |
||
| 17 |
/// @param name Name of the SuperAsset token |
||
| 18 |
/// @param symbol Symbol of the SuperAsset token |
||
| 19 |
event SuperAssetCreated( |
||
| 20 |
address indexed superAsset, address indexed incentiveFund, address incentiveCalc, string name, string symbol |
||
| 21 |
); |
||
| 22 | |||
| 23 |
/*////////////////////////////////////////////////////////////// |
||
| 24 |
ERRORS |
||
| 25 |
//////////////////////////////////////////////////////////////*/ |
||
| 26 | |||
| 27 |
/// @notice Thrown when an address parameter is zero |
||
| 28 |
error ZERO_ADDRESS(); |
||
| 29 | |||
| 30 |
/// @notice Thrown when the caller is not authorized |
||
| 31 |
error UNAUTHORIZED(); |
||
| 32 | |||
| 33 |
/// @notice Thrown when ICC is not whitelisted |
||
| 34 |
error ICC_NOT_WHITELISTED(); |
||
| 35 | |||
| 36 |
/*////////////////////////////////////////////////////////////// |
||
| 37 |
STRUCTS |
||
| 38 |
//////////////////////////////////////////////////////////////*/ |
||
| 39 | |||
| 40 |
/// @notice Parameters required for creating a new SuperAsset |
||
| 41 |
/// @param name Name of the SuperAsset token |
||
| 42 |
/// @param symbol Symbol of the SuperAsset token |
||
| 43 |
/// @param swapFeeInPercentage Initial swap fee percentage for deposits |
||
| 44 |
/// @param swapFeeOutPercentage Initial swap fee percentage for redemptions |
||
| 45 |
/// @param asset Address of the primary asset |
||
| 46 |
/// @param superAssetManager Address of the manager |
||
| 47 |
/// @param superAssetStrategist Address of the strategist |
||
| 48 |
/// @param incentiveFundManager Address of the incentive fund manager |
||
| 49 |
/// @param incentiveCalculationContract Address of the incentive calculation contract |
||
| 50 |
/// @param tokenInIncentive Address of the token for incoming incentives |
||
| 51 |
/// @param tokenOutIncentive Address of the token for outgoing incentives |
||
| 52 |
struct AssetCreationParams {
|
||
| 53 |
string name; |
||
| 54 |
string symbol; |
||
| 55 |
uint256 swapFeeInPercentage; |
||
| 56 |
uint256 swapFeeOutPercentage; |
||
| 57 |
address asset; |
||
| 58 |
address superAssetManager; |
||
| 59 |
address superAssetStrategist; |
||
| 60 |
address incentiveFundManager; |
||
| 61 |
address incentiveCalculationContract; |
||
| 62 |
address tokenInIncentive; |
||
| 63 |
address tokenOutIncentive; |
||
| 64 |
} |
||
| 65 | |||
| 66 |
/// @notice Data for a SuperAsset |
||
| 67 |
/// @param superAssetStrategist Address of the strategist |
||
| 68 |
/// @param superAssetManager Address of the manager |
||
| 69 |
/// @param incentiveFundManager Address of the incentive fund manager |
||
| 70 |
/// @param incentiveCalculationContract Address of the incentive calculation contract |
||
| 71 |
/// @param incentiveFundContract Address of the incentive fund contract |
||
| 72 |
struct SuperAssetData {
|
||
| 73 |
address superAssetStrategist; |
||
| 74 |
address superAssetManager; |
||
| 75 |
address incentiveFundManager; |
||
| 76 |
address incentiveCalculationContract; |
||
| 77 |
address incentiveFundContract; |
||
| 78 |
} |
||
| 79 | |||
| 80 |
/*////////////////////////////////////////////////////////////// |
||
| 81 |
EXTERNAL METHODS |
||
| 82 |
//////////////////////////////////////////////////////////////*/ |
||
| 83 | |||
| 84 |
/// @notice Sets the manager for a SuperAsset |
||
| 85 |
/// @param superAsset Address of the SuperAsset contract |
||
| 86 |
/// @param _superAssetManager Address of the manager |
||
| 87 |
function setSuperAssetManager(address superAsset, address _superAssetManager) external; |
||
| 88 | |||
| 89 |
/// @notice Sets the strategist for a SuperAsset |
||
| 90 |
/// @param superAsset Address of the SuperAsset contract |
||
| 91 |
/// @param _superAssetStrategist Address of the strategist |
||
| 92 |
function setSuperAssetStrategist(address superAsset, address _superAssetStrategist) external; |
||
| 93 | |||
| 94 |
/// @notice Sets the incentive fund manager for a SuperAsset |
||
| 95 |
/// @param superAsset Address of the SuperAsset contract |
||
| 96 |
/// @param _incentiveFundManager Address of the incentive fund manager |
||
| 97 |
function setIncentiveFundManager(address superAsset, address _incentiveFundManager) external; |
||
| 98 | |||
| 99 |
/// @notice Sets the incentive calculation contract for a SuperAsset |
||
| 100 |
/// @param superAsset Address of the SuperAsset contract |
||
| 101 |
/// @param incentiveCalculationContract Address of the incentive calculation contract |
||
| 102 |
function setIncentiveCalculationContract(address superAsset, address incentiveCalculationContract) external; |
||
| 103 | |||
| 104 |
/// @notice Gets the manager for a SuperAsset |
||
| 105 |
/// @param superAsset Address of the SuperAsset contract |
||
| 106 |
/// @return superAssetManager Address of the manager |
||
| 107 |
function getSuperAssetManager(address superAsset) external view returns (address); |
||
| 108 | |||
| 109 |
/// @notice Gets the strategist for a SuperAsset |
||
| 110 |
/// @param superAsset Address of the SuperAsset contract |
||
| 111 |
/// @return superAssetStrategist Address of the strategist |
||
| 112 |
function getSuperAssetStrategist(address superAsset) external view returns (address); |
||
| 113 | |||
| 114 |
/// @notice Gets the incentive fund manager for a SuperAsset |
||
| 115 |
/// @param superAsset Address of the SuperAsset contract |
||
| 116 |
/// @return incentiveFundManager Address of the incentive fund manager |
||
| 117 |
function getIncentiveFundManager(address superAsset) external view returns (address); |
||
| 118 | |||
| 119 |
/// @notice Gets the incentive calculation contract for a SuperAsset |
||
| 120 |
/// @param superAsset Address of the SuperAsset contract |
||
| 121 |
/// @return incentiveCalculationContract Address of the incentive calculation contract |
||
| 122 |
function getIncentiveCalculationContract(address superAsset) external view returns (address); |
||
| 123 | |||
| 124 |
/// @notice Gets the incentive fund contract for a SuperAsset |
||
| 125 |
/// @param superAsset Address of the SuperAsset contract |
||
| 126 |
/// @return incentiveFundContract Address of the incentive fund contract |
||
| 127 |
function getIncentiveFundContract(address superAsset) external view returns (address); |
||
| 128 | |||
| 129 |
/// @notice Adds an Incentive Calculation Contract to the whitelist |
||
| 130 |
/// @param icc Address of the Incentive Calculation Contract |
||
| 131 |
function addICCToWhitelist(address icc) external; |
||
| 132 | |||
| 133 |
/// @notice Removes an Incentive Calculation Contract from the whitelist |
||
| 134 |
/// @param icc Address of the Incentive Calculation Contract |
||
| 135 |
function removeICCFromWhitelist(address icc) external; |
||
| 136 | |||
| 137 |
/// @notice Checks if an Incentive Calculation Contract is whitelisted |
||
| 138 |
/// @param icc Address of the Incentive Calculation Contract |
||
| 139 |
/// @return isValid Whether the Incentive Calculation Contract is whitelisted |
||
| 140 |
function isICCWhitelisted(address icc) external view returns (bool); |
||
| 141 | |||
| 142 |
/// @notice Creates a new SuperAsset instance with its dependencies |
||
| 143 |
/// @param params Parameters for creating the SuperAsset |
||
| 144 |
/// @return superAsset Address of the deployed SuperAsset contract |
||
| 145 |
/// @return incentiveFund Address of the deployed IncentiveFundContract |
||
| 146 |
function createSuperAsset(AssetCreationParams calldata params) |
||
| 147 |
external |
||
| 148 |
returns (address superAsset, address incentiveFund); |
||
| 149 |
} |
||
| 150 |
0.0%
src/interfaces/SuperVault/ISuperVault.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ISuperVault |
||
| 5 |
/// @notice Interface for SuperVault core contract that manages share minting |
||
| 6 |
/// @author Superform Labs |
||
| 7 |
interface ISuperVault {
|
||
| 8 |
/*////////////////////////////////////////////////////////////// |
||
| 9 |
ERRORS |
||
| 10 |
//////////////////////////////////////////////////////////////*/ |
||
| 11 |
error INVALID_ASSET(); |
||
| 12 |
error INVALID_STRATEGY(); |
||
| 13 |
error INVALID_ESCROW(); |
||
| 14 |
error ZERO_ADDRESS(); |
||
| 15 |
error ZERO_AMOUNT(); |
||
| 16 |
error INVALID_OWNER_OR_OPERATOR(); |
||
| 17 |
error INVALID_AMOUNT(); |
||
| 18 |
error REQUEST_NOT_FOUND(); |
||
| 19 |
error UNAUTHORIZED(); |
||
| 20 |
error DEADLINE_PASSED(); |
||
| 21 |
error INVALID_SIGNATURE(); |
||
| 22 |
error NOT_IMPLEMENTED(); |
||
| 23 |
error INVALID_NONCE(); |
||
| 24 |
error INVALID_WITHDRAW_PRICE(); |
||
| 25 |
error TRANSFER_FAILED(); |
||
| 26 |
error CAP_EXCEEDED(); |
||
| 27 |
error INVALID_PPS(); |
||
| 28 |
error INVALID_CONTROLLER(); |
||
| 29 |
error CONTROLLER_MUST_EQUAL_OWNER(); |
||
| 30 | |||
| 31 |
/*////////////////////////////////////////////////////////////// |
||
| 32 |
EVENTS |
||
| 33 |
//////////////////////////////////////////////////////////////*/ |
||
| 34 | |||
| 35 |
event RedeemClaimable( |
||
| 36 |
address indexed user, |
||
| 37 |
uint256 indexed requestId, |
||
| 38 |
uint256 assets, |
||
| 39 |
uint256 shares, |
||
| 40 |
uint256 averageWithdrawPrice, |
||
| 41 |
uint256 accumulatorShares, |
||
| 42 |
uint256 accumulatorCostBasis |
||
| 43 |
); |
||
| 44 | |||
| 45 |
event NonceInvalidated(address indexed sender, bytes32 indexed nonce); |
||
| 46 | |||
| 47 |
event RedeemRequestCancelled(address indexed controller, address indexed sender); |
||
| 48 | |||
| 49 |
event SuperGovernorSet(address indexed superGovernor); |
||
| 50 | |||
| 51 |
/*////////////////////////////////////////////////////////////// |
||
| 52 |
EXTERNAL METHODS |
||
| 53 |
//////////////////////////////////////////////////////////////*/ |
||
| 54 | |||
| 55 |
function cancelRedeem(address controller) external; |
||
| 56 | |||
| 57 |
/// @notice Burn shares, only callable by strategy |
||
| 58 |
/// @param amount The amount of shares to burn |
||
| 59 |
function burnShares(uint256 amount) external; |
||
| 60 | |||
| 61 |
/// @notice Callback function for when a redeem becomes claimable |
||
| 62 |
/// @param user The user whose redeem is claimable |
||
| 63 |
/// @param assets The amount of assets to be received |
||
| 64 |
/// @param shares The amount of shares redeemed |
||
| 65 |
/// @param averageWithdrawPrice The average price of the redeem |
||
| 66 |
/// @param accumulatorShares The amount of shares in the accumulator |
||
| 67 |
/// @param accumulatorCostBasis The cost basis of the accumulator |
||
| 68 |
function onRedeemClaimable( |
||
| 69 |
address user, |
||
| 70 |
uint256 assets, |
||
| 71 |
uint256 shares, |
||
| 72 |
uint256 averageWithdrawPrice, |
||
| 73 |
uint256 accumulatorShares, |
||
| 74 |
uint256 accumulatorCostBasis |
||
| 75 |
) |
||
| 76 |
external; |
||
| 77 |
} |
||
| 78 |
0.0%
src/interfaces/SuperVault/ISuperVaultAggregator.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||
| 5 |
import { ISuperVaultStrategy } from "../SuperVault/ISuperVaultStrategy.sol";
|
||
| 6 | |||
| 7 |
/// @title ISuperVaultAggregator |
||
| 8 |
/// @author Superform Labs |
||
| 9 |
/// @notice Interface for the SuperVaultAggregator contract |
||
| 10 |
/// @dev Registry and PPS oracle for all SuperVaults |
||
| 11 |
interface ISuperVaultAggregator {
|
||
| 12 |
/*////////////////////////////////////////////////////////////// |
||
| 13 |
STRUCTS |
||
| 14 |
//////////////////////////////////////////////////////////////*/ |
||
| 15 |
/// @notice Arguments for forwarding PPS updates to avoid stack too deep errors |
||
| 16 |
/// @param strategy Address of the strategy being updated |
||
| 17 |
/// @param isExempt Whether the update is exempt from paying upkeep |
||
| 18 |
/// @param pps New price-per-share value |
||
| 19 |
/// @param ppsStdev Standard deviation of the price-per-share |
||
| 20 |
/// @param validatorSet Number of validators who calculated this PPS |
||
| 21 |
/// @param totalValidators Total number of validators in the network |
||
| 22 |
/// @param timestamp Timestamp when the value was generated |
||
| 23 |
/// @param upkeepCost Amount of upkeep tokens to charge if not exempt |
||
| 24 |
struct PPSUpdateData {
|
||
| 25 |
address strategy; |
||
| 26 |
bool isExempt; |
||
| 27 |
uint256 pps; |
||
| 28 |
uint256 ppsStdev; |
||
| 29 |
uint256 validatorSet; |
||
| 30 |
uint256 totalValidators; |
||
| 31 |
uint256 timestamp; |
||
| 32 |
uint256 upkeepCost; |
||
| 33 |
} |
||
| 34 | |||
| 35 |
/// @notice Local variables for vault creation to avoid stack too deep |
||
| 36 |
/// @param currentNonce Current vault creation nonce |
||
| 37 |
/// @param salt Salt for deterministic proxy creation |
||
| 38 |
/// @param success Whether asset decimals retrieval was successful |
||
| 39 |
/// @param assetDecimals Decimals of the underlying asset |
||
| 40 |
/// @param underlyingDecimals Final decimals to use (18 if retrieval failed) |
||
| 41 |
/// @param initialPPS Initial price-per-share value |
||
| 42 |
struct VaultCreationLocalVars {
|
||
| 43 |
uint256 currentNonce; |
||
| 44 |
bytes32 salt; |
||
| 45 |
bool success; |
||
| 46 |
uint8 assetDecimals; |
||
| 47 |
uint8 underlyingDecimals; |
||
| 48 |
uint256 initialPPS; |
||
| 49 |
} |
||
| 50 | |||
| 51 |
/// @notice Strategy configuration and state data |
||
| 52 |
/// @param pps Current price-per-share value |
||
| 53 |
/// @param lastUpdateTimestamp Last time PPS was updated |
||
| 54 |
/// @param minUpdateInterval Minimum time interval between PPS updates |
||
| 55 |
/// @param maxStaleness Maximum time allowed between PPS updates before staleness |
||
| 56 |
/// @param isPaused Whether the strategy is paused |
||
| 57 |
/// @param mainManager Address of the primary manager controlling the strategy |
||
| 58 |
/// @param secondaryManagers Set of secondary managers that can manage the strategy |
||
| 59 |
/// @param authorizedCallers List of callers authorized to update PPS without paying upkeep |
||
| 60 |
struct StrategyData {
|
||
| 61 |
uint256 pps; |
||
| 62 |
uint256 ppsStdev; |
||
| 63 |
uint256 lastUpdateTimestamp; |
||
| 64 |
uint256 minUpdateInterval; |
||
| 65 |
uint256 maxStaleness; |
||
| 66 |
bool isPaused; |
||
| 67 |
address mainManager; |
||
| 68 |
EnumerableSet.AddressSet secondaryManagers; |
||
| 69 |
EnumerableSet.AddressSet authorizedCallers; |
||
| 70 |
// Manager change proposal data |
||
| 71 |
address proposedManager; |
||
| 72 |
uint256 managerChangeEffectiveTime; |
||
| 73 |
// Hook validation data |
||
| 74 |
bytes32 managerHooksRoot; |
||
| 75 |
// Hook root update proposal data |
||
| 76 |
bytes32 proposedHooksRoot; |
||
| 77 |
uint256 hooksRootEffectiveTime; |
||
| 78 |
// Veto status |
||
| 79 |
bool hooksRootVetoed; |
||
| 80 |
// PPS Verification thresholds |
||
| 81 |
uint256 dispersionThreshold; // Threshold for standard deviation / mean |
||
| 82 |
uint256 deviationThreshold; // Threshold for abs(new - current) / current |
||
| 83 |
uint256 mnThreshold; // Threshold for validatorSet / totalValidators ratio, scaled by 1e18 |
||
| 84 |
// Banned global leaves mapping |
||
| 85 |
mapping(bytes32 => bool) bannedLeaves; // Mapping of leaf hash to banned status |
||
| 86 |
} |
||
| 87 | |||
| 88 |
/// @notice Parameters for creating a new SuperVault trio |
||
| 89 |
/// @param asset Address of the underlying asset |
||
| 90 |
/// @param name Name of the vault token |
||
| 91 |
/// @param symbol Symbol of the vault token |
||
| 92 |
/// @param mainManager Address of the vault mainManager |
||
| 93 |
/// @param minUpdateInterval Minimum time interval between PPS updates |
||
| 94 |
/// @param maxStaleness Maximum time allowed between PPS updates before staleness |
||
| 95 |
/// @param feeConfig Fee configuration for the vault |
||
| 96 |
struct VaultCreationParams {
|
||
| 97 |
address asset; |
||
| 98 |
string name; |
||
| 99 |
string symbol; |
||
| 100 |
address mainManager; |
||
| 101 |
address[] secondaryManagers; |
||
| 102 |
uint256 minUpdateInterval; |
||
| 103 |
uint256 maxStaleness; |
||
| 104 |
ISuperVaultStrategy.FeeConfig feeConfig; |
||
| 105 |
} |
||
| 106 | |||
| 107 |
/// @notice Struct to hold cached hook validation state variables to avoid stack too deep |
||
| 108 |
/// @param globalHooksRootVetoed Cached global hooks root veto status |
||
| 109 |
/// @param globalHooksRoot Cached global hooks root |
||
| 110 |
/// @param strategyHooksRootVetoed Cached strategy hooks root veto status |
||
| 111 |
/// @param strategyRoot Cached strategy hooks root |
||
| 112 |
struct HookValidationCache {
|
||
| 113 |
bool globalHooksRootVetoed; |
||
| 114 |
bytes32 globalHooksRoot; |
||
| 115 |
bool strategyHooksRootVetoed; |
||
| 116 |
bytes32 strategyRoot; |
||
| 117 |
} |
||
| 118 | |||
| 119 |
/// @notice Arguments for validating a hook to avoid stack too deep |
||
| 120 |
/// @param hookAddress Address of the hook contract |
||
| 121 |
/// @param hookArgs Encoded arguments for the hook operation |
||
| 122 |
/// @param globalProof Merkle proof for the global root |
||
| 123 |
/// @param strategyProof Merkle proof for the strategy-specific root |
||
| 124 |
struct ValidateHookArgs {
|
||
| 125 |
address hookAddress; |
||
| 126 |
bytes hookArgs; |
||
| 127 |
bytes32[] globalProof; |
||
| 128 |
bytes32[] strategyProof; |
||
| 129 |
} |
||
| 130 | |||
| 131 |
/*////////////////////////////////////////////////////////////// |
||
| 132 |
EVENTS |
||
| 133 |
//////////////////////////////////////////////////////////////*/ |
||
| 134 |
/// @notice Emitted when a new vault trio is created |
||
| 135 |
/// @param vault Address of the created SuperVault |
||
| 136 |
/// @param strategy Address of the created SuperVaultStrategy |
||
| 137 |
/// @param escrow Address of the created SuperVaultEscrow |
||
| 138 |
/// @param asset Address of the underlying asset |
||
| 139 |
/// @param name Name of the vault token |
||
| 140 |
/// @param symbol Symbol of the vault token |
||
| 141 |
/// @param nonce The nonce used for vault creation |
||
| 142 |
event VaultDeployed( |
||
| 143 |
address indexed vault, |
||
| 144 |
address indexed strategy, |
||
| 145 |
address escrow, |
||
| 146 |
address asset, |
||
| 147 |
string name, |
||
| 148 |
string symbol, |
||
| 149 |
uint256 indexed nonce |
||
| 150 |
); |
||
| 151 | |||
| 152 |
/// @notice Emitted when a PPS value is updated |
||
| 153 |
/// @param strategy Address of the strategy |
||
| 154 |
/// @param pps New price-per-share value |
||
| 155 |
/// @param ppsStdev Standard deviation of price-per-share value |
||
| 156 |
/// @param validatorSet Number of validators who calculated this PPS |
||
| 157 |
/// @param totalValidators Total number of validators in the network |
||
| 158 |
/// @param timestamp Timestamp of the update |
||
| 159 |
event PPSUpdated( |
||
| 160 |
address indexed strategy, |
||
| 161 |
uint256 pps, |
||
| 162 |
uint256 ppsStdev, |
||
| 163 |
uint256 validatorSet, |
||
| 164 |
uint256 totalValidators, |
||
| 165 |
uint256 timestamp |
||
| 166 |
); |
||
| 167 | |||
| 168 |
/// @notice Emitted when a strategy is paused due to missed updates |
||
| 169 |
/// @param strategy Address of the paused strategy |
||
| 170 |
event StrategyPaused(address indexed strategy); |
||
| 171 | |||
| 172 |
/// @notice Emitted when a strategy is unpaused |
||
| 173 |
/// @param strategy Address of the unpaused strategy |
||
| 174 |
event StrategyUnpaused(address indexed strategy); |
||
| 175 | |||
| 176 |
/// @notice Emitted when a strategy validation check fails but execution continues |
||
| 177 |
/// @param strategy Address of the strategy that failed the check |
||
| 178 |
/// @param reason String description of which check failed |
||
| 179 |
event StrategyCheckFailed(address indexed strategy, string reason); |
||
| 180 | |||
| 181 |
/// @notice Emitted when upkeep tokens are deposited |
||
| 182 |
/// @param manager Address of the manager |
||
| 183 |
/// @param amount Amount of UP tokens deposited |
||
| 184 |
event UpkeepDeposited(address indexed manager, uint256 amount); |
||
| 185 | |||
| 186 |
/// @notice Emitted when upkeep tokens are withdrawn |
||
| 187 |
/// @param manager Address of the manager |
||
| 188 |
/// @param amount Amount of UP tokens withdrawn |
||
| 189 |
event UpkeepWithdrawn(address indexed manager, uint256 amount); |
||
| 190 | |||
| 191 |
/// @notice Emitted when upkeep tokens are spent for validation |
||
| 192 |
/// @param manager Address of the manager |
||
| 193 |
/// @param amount Amount of UP tokens spent |
||
| 194 |
event UpkeepSpent(address indexed manager, uint256 amount); |
||
| 195 | |||
| 196 |
/// @notice Emitted when stake tokens are deposited |
||
| 197 |
/// @param manager Address of the manager |
||
| 198 |
/// @param amount Amount of UP tokens deposited as stake |
||
| 199 |
event StakeDeposited(address indexed manager, uint256 amount); |
||
| 200 | |||
| 201 |
/// @notice Emitted when stake tokens are withdrawn |
||
| 202 |
/// @param manager Address of the manager |
||
| 203 |
/// @param amount Amount of UP tokens withdrawn from stake |
||
| 204 |
event StakeWithdrawn(address indexed manager, uint256 amount); |
||
| 205 | |||
| 206 |
/// @notice Emitted when a manager's stake is slashed |
||
| 207 |
/// @param manager The manager whose stake was slashed |
||
| 208 |
/// @param amount The amount of UP tokens slashed |
||
| 209 |
event StakeSlashed(address indexed manager, uint256 amount); |
||
| 210 | |||
| 211 |
/// @notice Emitted when an authorized caller is added for a strategy |
||
| 212 |
/// @param strategy Address of the strategy |
||
| 213 |
/// @param caller Address of the authorized caller |
||
| 214 |
event AuthorizedCallerAdded(address indexed strategy, address indexed caller); |
||
| 215 | |||
| 216 |
/// @notice Emitted when an authorized caller is removed for a strategy |
||
| 217 |
/// @param strategy Address of the strategy |
||
| 218 |
/// @param caller Address of the removed caller |
||
| 219 |
event AuthorizedCallerRemoved(address indexed strategy, address indexed caller); |
||
| 220 | |||
| 221 |
/// @notice Emitted when a secondary manager is added to a strategy |
||
| 222 |
/// @param strategy Address of the strategy |
||
| 223 |
/// @param manager Address of the manager added |
||
| 224 |
event SecondaryManagerAdded(address indexed strategy, address indexed manager); |
||
| 225 | |||
| 226 |
/// @notice Emitted when a secondary manager is removed from a strategy |
||
| 227 |
/// @param strategy Address of the strategy |
||
| 228 |
/// @param manager Address of the manager removed |
||
| 229 |
event SecondaryManagerRemoved(address indexed strategy, address indexed manager); |
||
| 230 | |||
| 231 |
/// @notice Emitted when a primary manager is changed |
||
| 232 |
/// @param strategy Address of the strategy |
||
| 233 |
/// @param oldManager Address of the old primary manager |
||
| 234 |
/// @param newManager Address of the new primary manager |
||
| 235 |
event PrimaryManagerChanged( |
||
| 236 |
address indexed strategy, address indexed oldManager, address indexed newManager |
||
| 237 |
); |
||
| 238 | |||
| 239 |
/// @notice Emitted when a primary manager is changed to a superform manager |
||
| 240 |
/// @param strategy Address of the strategy |
||
| 241 |
/// @param oldManager Address of the old primary manager |
||
| 242 |
/// @param newManager Address of the new primary manager (superform manager) |
||
| 243 |
event PrimaryManagerChangedToSuperform( |
||
| 244 |
address indexed strategy, address indexed oldManager, address indexed newManager |
||
| 245 |
); |
||
| 246 | |||
| 247 |
/// @notice Emitted when a change to primary manager is proposed by a secondary manager |
||
| 248 |
/// @param strategy Address of the strategy |
||
| 249 |
/// @param proposer Address of the secondary manager who made the proposal |
||
| 250 |
/// @param newManager Address of the proposed new primary manager |
||
| 251 |
/// @param effectiveTime Timestamp when the proposal can be executed |
||
| 252 |
event PrimaryManagerChangeProposed( |
||
| 253 |
address indexed strategy, address indexed proposer, address indexed newManager, uint256 effectiveTime |
||
| 254 |
); |
||
| 255 | |||
| 256 |
/// @notice Emitted when a PPS update is stale (Validators could get slashed for innactivity) |
||
| 257 |
/// @param strategy Address of the strategy |
||
| 258 |
/// @param updateAuthority Address of the update authority |
||
| 259 |
/// @param timestamp Timestamp of the stale update |
||
| 260 |
event StaleUpdate(address indexed strategy, address indexed updateAuthority, uint256 timestamp); |
||
| 261 | |||
| 262 |
/// @notice Emitted when the upkeep cost per update is changed |
||
| 263 |
/// @param oldCost Previous upkeep cost per update |
||
| 264 |
/// @param newCost New upkeep cost per update |
||
| 265 |
event UpkeepCostUpdated(uint256 oldCost, uint256 newCost); |
||
| 266 | |||
| 267 |
/// @notice Emitted when the global hooks Merkle root is being updated |
||
| 268 |
/// @param root New root value |
||
| 269 |
/// @param effectiveTime Timestamp when the root becomes effective |
||
| 270 |
event GlobalHooksRootUpdateProposed(bytes32 indexed root, uint256 effectiveTime); |
||
| 271 | |||
| 272 |
/// @notice Emitted when the global hooks Merkle root is updated |
||
| 273 |
/// @param oldRoot Previous root value |
||
| 274 |
/// @param newRoot New root value |
||
| 275 |
event GlobalHooksRootUpdated(bytes32 indexed oldRoot, bytes32 newRoot); |
||
| 276 | |||
| 277 |
/// @notice Emitted when a strategy-specific hooks Merkle root is updated |
||
| 278 |
/// @param strategy Address of the strategy |
||
| 279 |
/// @param oldRoot Previous root value (may be zero) |
||
| 280 |
/// @param newRoot New root value |
||
| 281 |
event StrategyHooksRootUpdated(address indexed strategy, bytes32 oldRoot, bytes32 newRoot); |
||
| 282 | |||
| 283 |
/// @notice Emitted when a strategy-specific hooks Merkle root is proposed |
||
| 284 |
/// @param strategy Address of the strategy |
||
| 285 |
/// @param proposer Address of the account proposing the new root |
||
| 286 |
/// @param root New root value |
||
| 287 |
/// @param effectiveTime Timestamp when the root becomes effective |
||
| 288 |
event StrategyHooksRootUpdateProposed( |
||
| 289 |
address indexed strategy, address indexed proposer, bytes32 root, uint256 effectiveTime |
||
| 290 |
); |
||
| 291 | |||
| 292 |
/// @notice Emitted when a proposed global hooks root update is vetoed by SuperGovernor |
||
| 293 |
/// @param vetoed Whether the root is being vetoed (true) or unvetoed (false) |
||
| 294 |
/// @param root The root value affected |
||
| 295 |
event GlobalHooksRootVetoStatusChanged(bool vetoed, bytes32 indexed root); |
||
| 296 | |||
| 297 |
/// @notice Emitted when a strategy's hooks Merkle root veto status changes |
||
| 298 |
/// @param strategy Address of the strategy |
||
| 299 |
/// @param vetoed Whether the root is being vetoed (true) or unvetoed (false) |
||
| 300 |
/// @param root The root value affected |
||
| 301 |
event StrategyHooksRootVetoStatusChanged(address indexed strategy, bool vetoed, bytes32 indexed root); |
||
| 302 | |||
| 303 |
/// @notice Emitted when a strategy's PPS verification thresholds are updated |
||
| 304 |
/// @param strategy Address of the strategy |
||
| 305 |
/// @param dispersionThreshold New dispersion threshold (stddev/mean) |
||
| 306 |
/// @param deviationThreshold New deviation threshold (abs diff/current) |
||
| 307 |
/// @param mnThreshold New M/N threshold (validatorSet/totalValidators) |
||
| 308 |
event PPSVerificationThresholdsUpdated( |
||
| 309 |
address indexed strategy, uint256 dispersionThreshold, uint256 deviationThreshold, uint256 mnThreshold |
||
| 310 |
); |
||
| 311 | |||
| 312 |
/// @notice Emitted when the hooks root update timelock is changed |
||
| 313 |
/// @param newTimelock New timelock duration in seconds |
||
| 314 |
event HooksRootUpdateTimelockChanged(uint256 newTimelock); |
||
| 315 | |||
| 316 |
/// @notice Emitted when global leaves status is changed for a strategy |
||
| 317 |
/// @param strategy Address of the strategy |
||
| 318 |
/// @param leaves Array of leaf hashes that had their status changed |
||
| 319 |
/// @param statuses Array of new banned statuses (true = banned, false = allowed) |
||
| 320 |
event GlobalLeavesStatusChanged(address indexed strategy, bytes32[] leaves, bool[] statuses); |
||
| 321 | |||
| 322 |
/// @notice Emitted when a proposed global hooks root update is vetoed by a guardian |
||
| 323 |
/// @param guardian Address of the guardian who vetoed the update |
||
| 324 |
/// @param root The vetoed root value |
||
| 325 |
event GlobalHooksRootVetoed(address indexed guardian, bytes32 indexed root); |
||
| 326 | |||
| 327 |
/// @notice Emitted when a proposed strategy hooks root update is vetoed by a guardian |
||
| 328 |
/// @param guardian Address of the guardian who vetoed the update |
||
| 329 |
/// @param strategy Address of the strategy whose root update was vetoed |
||
| 330 |
/// @param root The vetoed root value |
||
| 331 |
event StrategyHooksRootVetoed(address indexed guardian, address indexed strategy, bytes32 indexed root); |
||
| 332 | |||
| 333 |
/// @notice Emitted when upkeep is claimed |
||
| 334 |
/// @param superBank Address of the superBank |
||
| 335 |
/// @param amount Amount of upkeep claimed |
||
| 336 |
event UpkeepClaimed(address indexed superBank, uint256 amount); |
||
| 337 | |||
| 338 |
/// @notice Emitted when PPS update is too frequent (before minUpdateInterval) |
||
| 339 |
event UpdateTooFrequent(); |
||
| 340 | |||
| 341 |
/// @notice Emitted when PPS update timestamp is not monotonically increasing |
||
| 342 |
event TimestampNotMonotonic(); |
||
| 343 | |||
| 344 |
/// @notice Emitted when a manager does not have enough upkeep balance |
||
| 345 |
event InsufficientUpkeep(address indexed strategy, address indexed manager, uint256 balance, uint256 cost); |
||
| 346 | |||
| 347 |
/// @notice Emitted when the provided timestamp is too large |
||
| 348 |
event ProvidedTimestampExceedsBlockTimestamp(address indexed strategy, uint256 argsTimestamp, uint256 blockTimestamp); |
||
| 349 | |||
| 350 |
/// @notice Emitted when a strategy is unknown |
||
| 351 |
event UnknownStrategy(address indexed strategy); |
||
| 352 | |||
| 353 |
/// @notice Emitted when a strategy is managed by Superform |
||
| 354 |
event SuperformManager(address indexed strategy, address indexed manager); |
||
| 355 | |||
| 356 |
/// @notice Emitted when the caller is authorized |
||
| 357 |
event AuthorizedCaller(address indexed strategy, address indexed caller); |
||
| 358 | |||
| 359 |
/*/////////////////////////////////////////////////////////////// |
||
| 360 |
ERRORS |
||
| 361 |
//////////////////////////////////////////////////////////////*/ |
||
| 362 |
/// @notice Thrown when address provided is zero |
||
| 363 |
error ZERO_ADDRESS(); |
||
| 364 |
/// @notice Thrown when amount provided is zero |
||
| 365 |
error ZERO_AMOUNT(); |
||
| 366 |
/// @notice Thrown when array length is zero |
||
| 367 |
error ZERO_ARRAY_LENGTH(); |
||
| 368 |
/// @notice Thrown when array length is zero |
||
| 369 |
error ARRAY_LENGTH_MISMATCH(); |
||
| 370 |
/// @notice Thrown when asset is invalid |
||
| 371 |
error INVALID_ASSET(); |
||
| 372 |
/// @notice Thrown when insufficient upkeep balance for operation |
||
| 373 |
error INSUFFICIENT_UPKEEP(); |
||
| 374 |
/// @notice Thrown when vault is paused but operation requires active state |
||
| 375 |
error VAULT_PAUSED(); |
||
| 376 |
/// @notice Thrown when caller is not an approved PPS oracle |
||
| 377 |
error UNAUTHORIZED_PPS_ORACLE(); |
||
| 378 |
/// @notice Thrown when PPS update is too stale (after maxStaleness) |
||
| 379 |
error UPDATE_TOO_STALE(); |
||
| 380 |
/// @notice Thrown when caller is not authorized for update |
||
| 381 |
error UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 382 |
/// @notice Thrown when strategy address is not a known SuperVault strategy |
||
| 383 |
error UNKNOWN_STRATEGY(); |
||
| 384 |
/// @notice Thrown when withdrawing more upkeep than available |
||
| 385 |
error INSUFFICIENT_UPKEEP_BALANCE(); |
||
| 386 |
/// @notice Thrown when withdrawing more stake than available |
||
| 387 |
error INSUFFICIENT_STAKE_BALANCE(); |
||
| 388 |
/// @notice Thrown when caller is already authorized |
||
| 389 |
error CALLER_ALREADY_AUTHORIZED(); |
||
| 390 |
/// @notice Thrown when caller is not authorized |
||
| 391 |
error CALLER_NOT_AUTHORIZED(); |
||
| 392 |
/// @notice Thrown when array index is out of bounds |
||
| 393 |
error INDEX_OUT_OF_BOUNDS(); |
||
| 394 |
/// @notice Thrown when attempting to remove the last manager |
||
| 395 |
error CANNOT_REMOVE_LAST_MANAGER(); |
||
| 396 |
/// @notice Thrown when attempting to add a manager that already exists |
||
| 397 |
error MANAGER_ALREADY_EXISTS(); |
||
| 398 |
/// @notice Thrown when there is no pending global hooks root change |
||
| 399 |
error NO_PENDING_GLOBAL_ROOT_CHANGE(); |
||
| 400 |
/// @notice Thrown when attempting to execute an in-progress manager change before timelock elapsed |
||
| 401 |
error MANAGER_CHANGE_NOT_READY(); |
||
| 402 |
/// @notice Thrown when attempting to execute a hooks root change before timelock has elapsed |
||
| 403 |
error ROOT_UPDATE_NOT_READY(); |
||
| 404 |
/// @notice Thrown when a provided hook fails Merkle proof validation |
||
| 405 |
error HOOK_VALIDATION_FAILED(); |
||
| 406 |
/// @notice Thrown when a non-guardian tries to veto a root update |
||
| 407 |
error NOT_A_GUARDIAN(); |
||
| 408 |
/// @notice Thrown when trying to veto a root update that doesn't exist |
||
| 409 |
error NO_PENDING_ROOT_UPDATE(); |
||
| 410 |
/// @notice Thrown when manager is not found |
||
| 411 |
error MANAGER_NOT_FOUND(); |
||
| 412 |
/// @notice Thrown when there is no pending manager change proposal |
||
| 413 |
error NO_PENDING_MANAGER_CHANGE(); |
||
| 414 |
/// @notice Thrown when caller is not authorized to update settings |
||
| 415 |
error UNAUTHORIZED_CALLER(); |
||
| 416 |
/// @notice Thrown when the timelock for a proposed change has not expired |
||
| 417 |
error TIMELOCK_NOT_EXPIRED(); |
||
| 418 |
/// @notice Thrown when an array length is invalid |
||
| 419 |
error INVALID_ARRAY_LENGTH(); |
||
| 420 |
/// @notice Thrown when trying to add a protected keeper as an authorized caller |
||
| 421 |
error CANNOT_ADD_PROTECTED_KEEPER(); |
||
| 422 |
/// @notice Thrown when the provided maxStaleness is less than the minimum required staleness |
||
| 423 |
error MAX_STALENESS_TOO_LOW(); |
||
| 424 |
/// @notice Thrown when arrays have mismatched lengths |
||
| 425 |
error MISMATCHED_ARRAY_LENGTHS(); |
||
| 426 |
/// @notice Thrown when timestamp is invalid |
||
| 427 |
error INVALID_TIMESTAMP(uint256 index); |
||
| 428 |
/// @notice Thrown when too many secondary managers are added |
||
| 429 |
error TOO_MANY_SECONDARY_MANAGERS(); |
||
| 430 |
/// @notice Thrown when the number of strategies exceeds the maximum allowed |
||
| 431 |
error MAX_STRATEGIES_EXCEEDED(); |
||
| 432 |
/// @notice Thrown when provided timestamp is too large |
||
| 433 |
error TIMESTAMP_EXCEEDS_BLOCK(); |
||
| 434 | |||
| 435 |
/*////////////////////////////////////////////////////////////// |
||
| 436 |
VAULT CREATION |
||
| 437 |
//////////////////////////////////////////////////////////////*/ |
||
| 438 |
/// @notice Creates a new SuperVault trio (SuperVault, SuperVaultStrategy, SuperVaultEscrow) |
||
| 439 |
/// @param params Parameters for the new vault creation |
||
| 440 |
/// @return superVault Address of the created SuperVault |
||
| 441 |
/// @return strategy Address of the created SuperVaultStrategy |
||
| 442 |
/// @return escrow Address of the created SuperVaultEscrow |
||
| 443 |
function createVault(VaultCreationParams calldata params) |
||
| 444 |
external |
||
| 445 |
returns (address superVault, address strategy, address escrow); |
||
| 446 | |||
| 447 |
/*////////////////////////////////////////////////////////////// |
||
| 448 |
PPS UPDATE FUNCTIONS |
||
| 449 |
//////////////////////////////////////////////////////////////*/ |
||
| 450 |
/// @notice Arguments for batch forwarding PPS updates |
||
| 451 |
/// @param strategies Array of strategy addresses |
||
| 452 |
/// @param ppss Array of price-per-share values |
||
| 453 |
/// @param ppsStdevs Array of standard deviations of price-per-share values |
||
| 454 |
/// @param validatorSets Array of numbers of validators who calculated each PPS |
||
| 455 |
/// @param totalValidators Total number of validators in the network |
||
| 456 |
/// @param timestamps Array of timestamps when values were generated |
||
| 457 |
struct ForwardPPSArgs {
|
||
| 458 |
address[] strategies; |
||
| 459 |
uint256[] ppss; |
||
| 460 |
uint256[] ppsStdevs; |
||
| 461 |
uint256[] validatorSets; |
||
| 462 |
uint256[] totalValidators; |
||
| 463 |
uint256[] timestamps; |
||
| 464 |
address updateAuthority; |
||
| 465 |
} |
||
| 466 | |||
| 467 |
/// @notice Batch forwards validated PPS updates to multiple strategies |
||
| 468 |
/// @param args Struct containing all batch PPS update parameters |
||
| 469 |
function forwardPPS(ForwardPPSArgs calldata args) external; |
||
| 470 | |||
| 471 |
/*////////////////////////////////////////////////////////////// |
||
| 472 |
UPKEEP MANAGEMENT |
||
| 473 |
//////////////////////////////////////////////////////////////*/ |
||
| 474 | |||
| 475 |
/// @notice Deposits UP tokens for manager upkeep |
||
| 476 |
/// @param manager Address of the manager to deposit for |
||
| 477 |
/// @param amount Amount of UP tokens to deposit |
||
| 478 |
function depositUpkeep(address manager, uint256 amount) external; |
||
| 479 | |||
| 480 |
/// @notice Withdraws UP tokens from manager upkeep balance |
||
| 481 |
/// @param amount Amount of UP tokens to withdraw |
||
| 482 |
function withdrawUpkeep(uint256 amount) external; |
||
| 483 | |||
| 484 |
/// @notice Claims upkeep tokens from the contract |
||
| 485 |
/// @param amount Amount of UP tokens to claim |
||
| 486 |
function claimUpkeep(uint256 amount) external; |
||
| 487 | |||
| 488 |
/*////////////////////////////////////////////////////////////// |
||
| 489 |
STAKE MANAGEMENT |
||
| 490 |
//////////////////////////////////////////////////////////////*/ |
||
| 491 | |||
| 492 |
/// @notice Deposits UP tokens as stake for manager economic security |
||
| 493 |
/// @param manager Address of the manager to deposit stake for |
||
| 494 |
/// @param amount Amount of UP tokens to deposit as stake |
||
| 495 |
function depositStake(address manager, uint256 amount) external; |
||
| 496 | |||
| 497 |
/// @notice Withdraws UP tokens from manager stake balance |
||
| 498 |
/// @param amount Amount of UP tokens to withdraw from stake |
||
| 499 |
function withdrawStake(uint256 amount) external; |
||
| 500 | |||
| 501 |
/// @notice Slashes a manager's stake balance by a specified amount |
||
| 502 |
/// @param manager The manager whose stake will be slashed |
||
| 503 |
/// @param amount The amount of UP tokens to slash from the manager's stake balance |
||
| 504 |
function slashStake(address manager, uint256 amount) external; |
||
| 505 | |||
| 506 |
/*////////////////////////////////////////////////////////////// |
||
| 507 |
AUTHORIZED CALLER MANAGEMENT |
||
| 508 |
//////////////////////////////////////////////////////////////*/ |
||
| 509 | |||
| 510 |
/// @notice Adds an authorized caller for a strategy |
||
| 511 |
/// @param strategy Address of the strategy |
||
| 512 |
/// @param caller Address of the caller to authorize |
||
| 513 |
function addAuthorizedCaller(address strategy, address caller) external; |
||
| 514 | |||
| 515 |
/// @notice Removes an authorized caller for a strategy |
||
| 516 |
/// @param strategy Address of the strategy |
||
| 517 |
/// @param caller Address of the caller to remove |
||
| 518 |
function removeAuthorizedCaller(address strategy, address caller) external; |
||
| 519 | |||
| 520 |
/*////////////////////////////////////////////////////////////// |
||
| 521 |
MANAGER MANAGEMENT FUNCTIONS |
||
| 522 |
//////////////////////////////////////////////////////////////*/ |
||
| 523 | |||
| 524 |
/// @notice Adds a secondary manager to a strategy |
||
| 525 |
/// @notice A manager can either be secondary or primary |
||
| 526 |
/// @param strategy Address of the strategy |
||
| 527 |
/// @param manager Address of the manager to add |
||
| 528 |
function addSecondaryManager(address strategy, address manager) external; |
||
| 529 | |||
| 530 |
/// @notice Removes a secondary manager from a strategy |
||
| 531 |
/// @param strategy Address of the strategy |
||
| 532 |
/// @param manager Address of the manager to remove |
||
| 533 |
function removeSecondaryManager(address strategy, address manager) external; |
||
| 534 | |||
| 535 |
/// @notice Changes the primary manager of a strategy immediately (only callable by SuperGovernor) |
||
| 536 |
/// @notice A manager can either be secondary or primary |
||
| 537 |
/// @param strategy Address of the strategy |
||
| 538 |
/// @param newManager Address of the new primary manager |
||
| 539 |
function changePrimaryManager(address strategy, address newManager) external; |
||
| 540 | |||
| 541 |
/// @notice Proposes a change to the primary manager (callable by secondary managers) |
||
| 542 |
/// @notice A manager can either be secondary or primary |
||
| 543 |
/// @param strategy Address of the strategy |
||
| 544 |
/// @param newManager Address of the proposed new primary manager |
||
| 545 |
function proposeChangePrimaryManager(address strategy, address newManager) external; |
||
| 546 | |||
| 547 |
/// @notice Executes a previously proposed change to the primary manager after timelock |
||
| 548 |
/// @param strategy Address of the strategy |
||
| 549 |
function executeChangePrimaryManager(address strategy) external; |
||
| 550 | |||
| 551 |
/*////////////////////////////////////////////////////////////// |
||
| 552 |
HOOK VALIDATION FUNCTIONS |
||
| 553 |
//////////////////////////////////////////////////////////////*/ |
||
| 554 |
/// @notice Sets a new hooks root update timelock duration |
||
| 555 |
/// @param newTimelock The new timelock duration in seconds |
||
| 556 |
function setHooksRootUpdateTimelock(uint256 newTimelock) external; |
||
| 557 | |||
| 558 |
/// @notice Proposes an update to the global hooks Merkle root |
||
| 559 |
/// @dev Only callable by SUPER_GOVERNOR |
||
| 560 |
/// @param newRoot New Merkle root for global hooks validation |
||
| 561 |
function proposeGlobalHooksRoot(bytes32 newRoot) external; |
||
| 562 | |||
| 563 |
/// @notice Executes a previously proposed global hooks root update after timelock period |
||
| 564 |
/// @dev Can be called by anyone after the timelock period has elapsed |
||
| 565 |
function executeGlobalHooksRootUpdate() external; |
||
| 566 | |||
| 567 |
/// @notice Proposes an update to a strategy-specific hooks Merkle root |
||
| 568 |
/// @dev Only callable by the main manager for the strategy |
||
| 569 |
/// @param strategy Address of the strategy |
||
| 570 |
/// @param newRoot New Merkle root for strategy-specific hooks |
||
| 571 |
function proposeStrategyHooksRoot(address strategy, bytes32 newRoot) external; |
||
| 572 | |||
| 573 |
/// @notice Executes a previously proposed strategy hooks root update after timelock period |
||
| 574 |
/// @dev Can be called by anyone after the timelock period has elapsed |
||
| 575 |
/// @param strategy Address of the strategy whose root update to execute |
||
| 576 |
function executeStrategyHooksRootUpdate(address strategy) external; |
||
| 577 | |||
| 578 |
/// @notice Set veto status for the global hooks root |
||
| 579 |
/// @dev Only callable by SuperGovernor |
||
| 580 |
/// @param vetoed Whether to veto (true) or unveto (false) the global hooks root |
||
| 581 |
function setGlobalHooksRootVetoStatus(bool vetoed) external; |
||
| 582 | |||
| 583 |
/// @notice Set veto status for a strategy-specific hooks root |
||
| 584 |
/// @notice Sets the veto status of a strategy's hooks Merkle root |
||
| 585 |
/// @param strategy Address of the strategy |
||
| 586 |
/// @param vetoed Whether to veto (true) or unveto (false) |
||
| 587 |
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external; |
||
| 588 | |||
| 589 |
/// @notice Updates the PPS verification thresholds for a strategy |
||
| 590 |
/// @param strategy Address of the strategy |
||
| 591 |
/// @param dispersionThreshold_ New dispersion threshold (stddev/mean ratio, scaled by 1e18) |
||
| 592 |
/// @param deviationThreshold_ New deviation threshold (abs diff/current ratio, scaled by 1e18) |
||
| 593 |
/// @param mnThreshold_ New M/N threshold (validatorSet/totalValidators ratio, scaled by 1e18) |
||
| 594 |
function updatePPSVerificationThresholds( |
||
| 595 |
address strategy, |
||
| 596 |
uint256 dispersionThreshold_, |
||
| 597 |
uint256 deviationThreshold_, |
||
| 598 |
uint256 mnThreshold_ |
||
| 599 |
) |
||
| 600 |
external; |
||
| 601 | |||
| 602 |
/// @notice Changes the banned status of global leaves for a specific strategy |
||
| 603 |
/// @dev Only callable by the primary manager of the strategy |
||
| 604 |
/// @param leaves Array of leaf hashes to change status for |
||
| 605 |
/// @param statuses Array of banned statuses (true = banned, false = allowed) |
||
| 606 |
/// @param strategy Address of the strategy to change banned leaves for |
||
| 607 |
function changeGlobalLeavesStatus( |
||
| 608 |
bytes32[] memory leaves, |
||
| 609 |
bool[] memory statuses, |
||
| 610 |
address strategy |
||
| 611 |
) |
||
| 612 |
external; |
||
| 613 | |||
| 614 |
/*////////////////////////////////////////////////////////////// |
||
| 615 |
VIEW FUNCTIONS |
||
| 616 |
//////////////////////////////////////////////////////////////*/ |
||
| 617 | |||
| 618 |
/// @notice Returns the current vault creation nonce |
||
| 619 |
/// @dev This nonce is incremented every time a new vault is created |
||
| 620 |
/// @return Current vault creation nonce |
||
| 621 |
function getCurrentNonce() external view returns (uint256); |
||
| 622 | |||
| 623 |
/// @notice Check if the global hooks root is currently vetoed |
||
| 624 |
/// @return vetoed True if the global hooks root is vetoed |
||
| 625 |
function isGlobalHooksRootVetoed() external view returns (bool vetoed); |
||
| 626 | |||
| 627 |
/// @notice Check if a strategy hooks root is currently vetoed |
||
| 628 |
/// @param strategy Address of the strategy to check |
||
| 629 |
/// @return vetoed True if the strategy hooks root is vetoed |
||
| 630 |
function isStrategyHooksRootVetoed(address strategy) external view returns (bool vetoed); |
||
| 631 | |||
| 632 |
/// @notice Gets the current hooks root update timelock duration |
||
| 633 |
/// @return The current timelock duration in seconds |
||
| 634 |
function getHooksRootUpdateTimelock() external view returns (uint256); |
||
| 635 | |||
| 636 |
/// @notice Gets the current PPS (price-per-share) for a strategy |
||
| 637 |
/// @param strategy Address of the strategy |
||
| 638 |
/// @return pps Current price-per-share value |
||
| 639 |
function getPPS(address strategy) external view returns (uint256 pps); |
||
| 640 | |||
| 641 |
/// @notice Gets the current PPS and its standard deviation for a strategy |
||
| 642 |
/// @param strategy Address of the strategy |
||
| 643 |
/// @return pps Current price-per-share value |
||
| 644 |
/// @return ppsStdev Standard deviation of price-per-share value |
||
| 645 |
function getPPSWithStdDev(address strategy) external view returns (uint256 pps, uint256 ppsStdev); |
||
| 646 | |||
| 647 |
/// @notice Gets the last update timestamp for a strategy's PPS |
||
| 648 |
/// @param strategy Address of the strategy |
||
| 649 |
/// @return timestamp Last update timestamp |
||
| 650 |
function getLastUpdateTimestamp(address strategy) external view returns (uint256 timestamp); |
||
| 651 | |||
| 652 |
/// @notice Gets the minimum update interval for a strategy |
||
| 653 |
/// @param strategy Address of the strategy |
||
| 654 |
/// @return interval Minimum time between updates |
||
| 655 |
function getMinUpdateInterval(address strategy) external view returns (uint256 interval); |
||
| 656 | |||
| 657 |
/// @notice Gets the maximum staleness period for a strategy |
||
| 658 |
/// @param strategy Address of the strategy |
||
| 659 |
/// @return staleness Maximum time allowed between updates |
||
| 660 |
function getMaxStaleness(address strategy) external view returns (uint256 staleness); |
||
| 661 | |||
| 662 |
/// @notice Gets the PPS verification thresholds for a strategy |
||
| 663 |
/// @param strategy Address of the strategy |
||
| 664 |
/// @return dispersionThreshold The current dispersion threshold (stddev/mean ratio, scaled by 1e18) |
||
| 665 |
/// @return deviationThreshold The current deviation threshold (abs diff/current ratio, scaled by 1e18) |
||
| 666 |
/// @return mnThreshold The current M/N threshold (validatorSet/totalValidators ratio, scaled by 1e18) |
||
| 667 |
function getPPSVerificationThresholds(address strategy) |
||
| 668 |
external |
||
| 669 |
view |
||
| 670 |
returns (uint256 dispersionThreshold, uint256 deviationThreshold, uint256 mnThreshold); |
||
| 671 | |||
| 672 |
/// @notice Checks if a strategy is currently paused |
||
| 673 |
/// @param strategy Address of the strategy |
||
| 674 |
/// @return isPaused True if paused, false otherwise |
||
| 675 |
function isStrategyPaused(address strategy) external view returns (bool isPaused); |
||
| 676 | |||
| 677 |
/// @notice Gets the current upkeep balance for a manager |
||
| 678 |
/// @param manager Address of the manager |
||
| 679 |
/// @return balance Current upkeep balance in UP tokens |
||
| 680 |
function getUpkeepBalance(address manager) external view returns (uint256 balance); |
||
| 681 | |||
| 682 |
/// @notice Gets the current stake balance for a manager |
||
| 683 |
/// @param manager Address of the manager |
||
| 684 |
/// @return balance Current stake balance in UP tokens |
||
| 685 |
function getStakeBalance(address manager) external view returns (uint256 balance); |
||
| 686 | |||
| 687 |
/// @notice Gets all authorized callers for a strategy |
||
| 688 |
/// @param strategy Address of the strategy |
||
| 689 |
/// @return callers Array of authorized callers |
||
| 690 |
function getAuthorizedCallers(address strategy) external view returns (address[] memory callers); |
||
| 691 | |||
| 692 |
/// @notice Gets the main manager for a strategy |
||
| 693 |
/// @param strategy Address of the strategy |
||
| 694 |
/// @return manager Address of the main manager |
||
| 695 |
function getMainManager(address strategy) external view returns (address manager); |
||
| 696 | |||
| 697 |
/// @notice Checks if an address is the main manager for a strategy |
||
| 698 |
/// @param manager Address of the manager |
||
| 699 |
/// @param strategy Address of the strategy |
||
| 700 |
/// @return isMainManager True if the address is the main manager, false otherwise |
||
| 701 |
function isMainManager(address manager, address strategy) external view returns (bool isMainManager); |
||
| 702 | |||
| 703 |
/// @notice Gets all secondary managers for a strategy |
||
| 704 |
/// @param strategy Address of the strategy |
||
| 705 |
/// @return secondaryManagers Array of secondary manager addresses |
||
| 706 |
function getSecondaryManagers(address strategy) external view returns (address[] memory secondaryManagers); |
||
| 707 | |||
| 708 |
/// @notice Checks if an address is a secondary manager for a strategy |
||
| 709 |
/// @param manager Address of the manager |
||
| 710 |
/// @param strategy Address of the strategy |
||
| 711 |
/// @return isSecondaryManager True if the address is a secondary manager, false otherwise |
||
| 712 |
function isSecondaryManager( |
||
| 713 |
address manager, |
||
| 714 |
address strategy |
||
| 715 |
) |
||
| 716 |
external |
||
| 717 |
view |
||
| 718 |
returns (bool isSecondaryManager); |
||
| 719 | |||
| 720 |
/// @dev Internal helper function to check if an address is any kind of manager (primary or secondary) |
||
| 721 |
/// @param manager Address to check |
||
| 722 |
/// @param strategy The strategy to check against |
||
| 723 |
/// @return True if the address is either the primary manager or a secondary manager |
||
| 724 |
function isAnyManager(address manager, address strategy) external view returns (bool); |
||
| 725 | |||
| 726 |
/// @notice Gets all created SuperVaults |
||
| 727 |
/// @return Array of SuperVault addresses |
||
| 728 |
function getAllSuperVaults() external view returns (address[] memory); |
||
| 729 | |||
| 730 |
/// @notice Gets a SuperVault by index |
||
| 731 |
/// @param index The index of the SuperVault |
||
| 732 |
/// @return The SuperVault address at the given index |
||
| 733 |
function superVaults(uint256 index) external view returns (address); |
||
| 734 | |||
| 735 |
/// @notice Gets all created SuperVaultStrategies |
||
| 736 |
/// @return Array of SuperVaultStrategy addresses |
||
| 737 |
function getAllSuperVaultStrategies() external view returns (address[] memory); |
||
| 738 | |||
| 739 |
/// @notice Gets a SuperVaultStrategy by index |
||
| 740 |
/// @param index The index of the SuperVaultStrategy |
||
| 741 |
/// @return The SuperVaultStrategy address at the given index |
||
| 742 |
function superVaultStrategies(uint256 index) external view returns (address); |
||
| 743 | |||
| 744 |
/// @notice Gets all created SuperVaultEscrows |
||
| 745 |
/// @return Array of SuperVaultEscrow addresses |
||
| 746 |
function getAllSuperVaultEscrows() external view returns (address[] memory); |
||
| 747 | |||
| 748 |
/// @notice Gets a SuperVaultEscrow by index |
||
| 749 |
/// @param index The index of the SuperVaultEscrow |
||
| 750 |
/// @return The SuperVaultEscrow address at the given index |
||
| 751 |
function superVaultEscrows(uint256 index) external view returns (address); |
||
| 752 | |||
| 753 |
/// @notice Validates a hook against both global and strategy-specific Merkle roots |
||
| 754 |
/// @param strategy Address of the strategy |
||
| 755 |
/// @param args Arguments for hook validation |
||
| 756 |
/// @return isValid True if the hook is valid against either root |
||
| 757 |
function validateHook( |
||
| 758 |
address strategy, |
||
| 759 |
ValidateHookArgs calldata args |
||
| 760 |
) |
||
| 761 |
external |
||
| 762 |
view |
||
| 763 |
returns (bool isValid); |
||
| 764 | |||
| 765 |
/// @notice Batch validates multiple hooks against Merkle roots |
||
| 766 |
/// @param strategy Address of the strategy |
||
| 767 |
/// @param argsArray Array of hook validation arguments |
||
| 768 |
/// @return validHooks Array of booleans indicating which hooks are valid |
||
| 769 |
function validateHooks( |
||
| 770 |
address strategy, |
||
| 771 |
ValidateHookArgs[] calldata argsArray |
||
| 772 |
) |
||
| 773 |
external |
||
| 774 |
view |
||
| 775 |
returns (bool[] memory validHooks); |
||
| 776 | |||
| 777 |
/// @notice Gets the current global hooks Merkle root |
||
| 778 |
/// @return root The current global hooks Merkle root |
||
| 779 |
function getGlobalHooksRoot() external view returns (bytes32 root); |
||
| 780 | |||
| 781 |
/// @notice Gets the proposed global hooks root and effective time |
||
| 782 |
/// @return root The proposed global hooks Merkle root |
||
| 783 |
/// @return effectiveTime The timestamp when the proposed root becomes effective |
||
| 784 |
function getProposedGlobalHooksRoot() external view returns (bytes32 root, uint256 effectiveTime); |
||
| 785 | |||
| 786 |
/// @notice Checks if the global hooks root is active (timelock period has passed) |
||
| 787 |
/// @return isActive True if the global hooks root is active |
||
| 788 |
function isGlobalHooksRootActive() external view returns (bool); |
||
| 789 | |||
| 790 |
/// @notice Gets the hooks Merkle root for a specific strategy |
||
| 791 |
/// @param strategy Address of the strategy |
||
| 792 |
/// @return root The strategy-specific hooks Merkle root |
||
| 793 |
function getStrategyHooksRoot(address strategy) external view returns (bytes32 root); |
||
| 794 | |||
| 795 |
/// @notice Gets the proposed strategy hooks root and effective time |
||
| 796 |
/// @param strategy Address of the strategy |
||
| 797 |
/// @return root The proposed strategy hooks Merkle root |
||
| 798 |
/// @return effectiveTime The timestamp when the proposed root becomes effective |
||
| 799 |
function getProposedStrategyHooksRoot(address strategy) |
||
| 800 |
external |
||
| 801 |
view |
||
| 802 |
returns (bytes32 root, uint256 effectiveTime); |
||
| 803 |
} |
||
| 804 |
0.0%
src/interfaces/SuperVault/ISuperVaultEscrow.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ISuperVaultEscrow |
||
| 5 |
/// @notice Interface for SuperVault escrow contract that holds shares during request/claim process |
||
| 6 |
/// @author Superform Labs |
||
| 7 |
interface ISuperVaultEscrow {
|
||
| 8 |
/*////////////////////////////////////////////////////////////// |
||
| 9 |
ERRORS |
||
| 10 |
//////////////////////////////////////////////////////////////*/ |
||
| 11 |
error ALREADY_INITIALIZED(); |
||
| 12 |
error UNAUTHORIZED(); |
||
| 13 |
error ZERO_ADDRESS(); |
||
| 14 | |||
| 15 |
/*////////////////////////////////////////////////////////////// |
||
| 16 |
INITIALIZATION |
||
| 17 |
//////////////////////////////////////////////////////////////*/ |
||
| 18 | |||
| 19 |
/// @notice Initialize the escrow with required parameters |
||
| 20 |
/// @param vaultAddress The vault contract address |
||
| 21 |
/// @param strategyAddress The strategy contract address |
||
| 22 |
function initialize(address vaultAddress, address strategyAddress) external; |
||
| 23 | |||
| 24 |
/*////////////////////////////////////////////////////////////// |
||
| 25 |
VAULT FUNCTIONS |
||
| 26 |
//////////////////////////////////////////////////////////////*/ |
||
| 27 | |||
| 28 |
/// @notice Transfer shares from user to escrow during redeem request |
||
| 29 |
/// @param from The address to transfer shares from |
||
| 30 |
/// @param amount The amount of shares to transfer |
||
| 31 |
function escrowShares(address from, uint256 amount) external; |
||
| 32 | |||
| 33 |
/// @notice Return shares from escrow to user during redeem cancellation |
||
| 34 |
/// @param to The address to return shares to |
||
| 35 |
/// @param amount The amount of shares to return |
||
| 36 |
function returnShares(address to, uint256 amount) external; |
||
| 37 |
} |
||
| 38 |
0.0%
src/interfaces/SuperVault/ISuperVaultStrategy.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 5 |
import { ISuperHook, Execution } from "@superform-v2-core/src/interfaces/ISuperHook.sol";
|
||
| 6 | |||
| 7 |
/// @title ISuperVaultStrategy |
||
| 8 |
/// @author Superform Labs |
||
| 9 |
/// @notice Interface for SuperVault strategy implementation that manages yield sources and executes strategies |
||
| 10 |
interface ISuperVaultStrategy {
|
||
| 11 |
/*////////////////////////////////////////////////////////////// |
||
| 12 |
ERRORS |
||
| 13 |
//////////////////////////////////////////////////////////////*/ |
||
| 14 | |||
| 15 |
error ZERO_LENGTH(); |
||
| 16 |
error INVALID_HOOK(); |
||
| 17 |
error ZERO_ADDRESS(); |
||
| 18 |
error ACCESS_DENIED(); |
||
| 19 |
error INVALID_AMOUNT(); |
||
| 20 |
error INVALID_MANAGER(); |
||
| 21 |
error OPERATION_FAILED(); |
||
| 22 |
error INVALID_TIMESTAMP(); |
||
| 23 |
error REQUEST_NOT_FOUND(); |
||
| 24 |
error INVALID_HOOK_ROOT(); |
||
| 25 |
error INVALID_HOOK_TYPE(); |
||
| 26 |
error INSUFFICIENT_FUNDS(); |
||
| 27 |
error ZERO_OUTPUT_AMOUNT(); |
||
| 28 |
error INSUFFICIENT_SHARES(); |
||
| 29 |
error ZERO_EXPECTED_VALUE(); |
||
| 30 |
error INVALID_ARRAY_LENGTH(); |
||
| 31 |
error ACTION_TYPE_DISALLOWED(); |
||
| 32 |
error YIELD_SOURCE_NOT_FOUND(); |
||
| 33 |
error INVALID_EMERGENCY_ADMIN(); |
||
| 34 |
error INVALID_PERIPHERY_REGISTRY(); |
||
| 35 |
error YIELD_SOURCE_ALREADY_EXISTS(); |
||
| 36 |
error INVALID_PERFORMANCE_FEE_BPS(); |
||
| 37 |
error INVALID_EMERGENCY_WITHDRAWAL(); |
||
| 38 |
error ASYNC_REQUEST_BLOCKING(); |
||
| 39 |
error MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET(); |
||
| 40 |
error MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET(); |
||
| 41 |
error INVALID_REDEEM_CLAIM(); |
||
| 42 |
error MANAGER_NOT_AUTHORIZED(); |
||
| 43 |
error PPS_UPDATE_RATE_LIMITED(); |
||
| 44 |
error PPS_OUT_OF_BOUNDS(); |
||
| 45 |
error CALCULATION_BLOCK_TOO_OLD(); |
||
| 46 |
error INVALID_PPS(); |
||
| 47 |
error INVALID_REDEEM_FILL(); |
||
| 48 |
error SLIPPAGE_EXCEEDED(); |
||
| 49 |
error INVALID_VAULT(); |
||
| 50 |
error STAKE_TOO_LOW(); |
||
| 51 |
error OPERATIONS_BLOCKED_BY_VETO(); |
||
| 52 |
error HOOK_VALIDATION_FAILED(); |
||
| 53 |
error STRATEGY_PAUSED(); |
||
| 54 |
error INVALID_MAX_SLIPPAGE_BPS(); |
||
| 55 |
error NO_PROPOSAL(); |
||
| 56 | |||
| 57 |
/*////////////////////////////////////////////////////////////// |
||
| 58 |
EVENTS |
||
| 59 |
//////////////////////////////////////////////////////////////*/ |
||
| 60 | |||
| 61 |
event SuperGovernorSet(address indexed superGovernor); |
||
| 62 |
event Initialized(address indexed vault); |
||
| 63 |
event YieldSourceAdded(address indexed source, address indexed oracle); |
||
| 64 |
event YieldSourceOracleUpdated(address indexed source, address indexed oldOracle, address indexed newOracle); |
||
| 65 |
event YieldSourceRemoved(address indexed source); |
||
| 66 | |||
| 67 |
event HookRootUpdated(bytes32 newRoot); |
||
| 68 |
event HookRootProposed(bytes32 proposedRoot, uint256 effectiveTime); |
||
| 69 |
event EmergencyWithdrawableProposed(bool newWithdrawable, uint256 effectiveTime); |
||
| 70 |
event EmergencyWithdrawableUpdated(bool withdrawable); |
||
| 71 |
event EmergencyWithdrawableProposalCanceled(); |
||
| 72 |
event EmergencyWithdrawal(address indexed recipient, uint256 assets); |
||
| 73 |
event VaultFeeConfigUpdated(uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient); |
||
| 74 |
event VaultFeeConfigProposed( |
||
| 75 |
uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient, uint256 effectiveTime |
||
| 76 |
); |
||
| 77 |
event MaxPPSSlippageUpdated(uint256 maxSlippageBps); |
||
| 78 |
event HooksExecuted(address[] hooks); |
||
| 79 |
event RedeemRequestPlaced(address indexed controller, address indexed owner, uint256 shares); |
||
| 80 |
event RedeemRequestFulfilled(address indexed controller, address indexed receiver, uint256 assets, uint256 shares); |
||
| 81 |
event RedeemRequestCanceled(address indexed controller, uint256 shares); |
||
| 82 |
event HookExecuted( |
||
| 83 |
address indexed hook, |
||
| 84 |
address indexed prevHook, |
||
| 85 |
address indexed targetedYieldSource, |
||
| 86 |
bool usePrevHookAmount, |
||
| 87 |
bytes hookCalldata |
||
| 88 |
); |
||
| 89 |
event FulfillHookExecuted(address indexed hook, address indexed targetedYieldSource, bytes hookCalldata); |
||
| 90 | |||
| 91 |
event PPSUpdated(uint256 newPPS, uint256 calculationBlock); |
||
| 92 | |||
| 93 |
event RedeemRequestsFulfilled(address[] hooks, address[] controllers, uint256 processedShares, uint256 currentPPS); |
||
| 94 | |||
| 95 |
event FeePaid(address indexed recipient, uint256 amount, uint256 performanceFeeBps); |
||
| 96 |
event ManagementFeePaid(address indexed controller, address indexed recipient, uint256 feeAssets, uint256 feeBps); |
||
| 97 |
event DepositHandled(address indexed controller, uint256 assets, uint256 shares); |
||
| 98 | |||
| 99 |
/*////////////////////////////////////////////////////////////// |
||
| 100 |
STRUCTS |
||
| 101 |
//////////////////////////////////////////////////////////////*/ |
||
| 102 | |||
| 103 |
struct FeeConfig {
|
||
| 104 |
uint256 performanceFeeBps; // On profit at fulfill time |
||
| 105 |
uint256 managementFeeBps; // Entry fee on deposit/mint (asset-side) |
||
| 106 |
address recipient; // Fee sink (entry + performance) |
||
| 107 |
} |
||
| 108 | |||
| 109 |
/// @notice Structure for hook execution arguments |
||
| 110 |
struct ExecuteArgs {
|
||
| 111 |
/// @notice Array of hooks to execute |
||
| 112 |
address[] hooks; |
||
| 113 |
/// @notice Calldata for each hook (must match hooks array length) |
||
| 114 |
bytes[] hookCalldata; |
||
| 115 |
/// @notice Expected output amounts or output shares |
||
| 116 |
uint256[] expectedAssetsOrSharesOut; |
||
| 117 |
/// @notice Global Merkle proofs for hook validation (must match hooks array length) |
||
| 118 |
bytes32[][] globalProofs; |
||
| 119 |
/// @notice Strategy-specific Merkle proofs for hook validation (must match hooks array length) |
||
| 120 |
bytes32[][] strategyProofs; |
||
| 121 |
} |
||
| 122 | |||
| 123 |
struct FulfillArgs {
|
||
| 124 |
address[] controllers; |
||
| 125 |
address[] hooks; |
||
| 126 |
bytes[] hookCalldata; |
||
| 127 |
uint256[] expectedAssetsOrSharesOut; |
||
| 128 |
bytes32[][] globalProofs; |
||
| 129 |
bytes32[][] strategyProofs; |
||
| 130 |
} |
||
| 131 | |||
| 132 |
struct YieldSource {
|
||
| 133 |
address oracle; // Associated yield source oracle address |
||
| 134 |
} |
||
| 135 | |||
| 136 |
/// @notice Comprehensive information about a yield source including its address and configuration |
||
| 137 |
struct YieldSourceInfo {
|
||
| 138 |
address sourceAddress; // Address of the yield source |
||
| 139 |
address oracle; // Associated yield source oracle address |
||
| 140 |
} |
||
| 141 | |||
| 142 |
/// @notice State specific to asynchronous redeem requests |
||
| 143 |
struct SuperVaultState {
|
||
| 144 |
uint256 pendingRedeemRequest; // Shares requested |
||
| 145 |
uint256 maxWithdraw; // Assets claimable after fulfillment |
||
| 146 |
uint256 averageRequestPPS; // Average PPS at the time of redeem request |
||
| 147 |
// Accumulators needed for fee calculation on redeem |
||
| 148 |
uint256 accumulatorShares; |
||
| 149 |
uint256 accumulatorCostBasis; |
||
| 150 |
uint256 averageWithdrawPrice; // Average price for claimable assets |
||
| 151 |
} |
||
| 152 | |||
| 153 |
struct ExecutionVars {
|
||
| 154 |
bool success; |
||
| 155 |
address targetedYieldSource; |
||
| 156 |
uint256 outAmount; |
||
| 157 |
ISuperHook hookContract; |
||
| 158 |
Execution[] executions; |
||
| 159 |
} |
||
| 160 | |||
| 161 |
struct OutflowExecutionVars {
|
||
| 162 |
bool success; |
||
| 163 |
address targetedYieldSource; |
||
| 164 |
address svAsset; |
||
| 165 |
uint256 outAmount; |
||
| 166 |
uint256 superVaultShares; |
||
| 167 |
uint256 amountOfAssets; |
||
| 168 |
uint256 amountConvertedToUnderlyingShares; |
||
| 169 |
uint256 balanceAssetBefore; |
||
| 170 |
Execution[] executions; |
||
| 171 |
ISuperHook hookContract; |
||
| 172 |
ISuperHook.HookType hookType; |
||
| 173 |
} |
||
| 174 | |||
| 175 |
/*////////////////////////////////////////////////////////////// |
||
| 176 |
ENUMS |
||
| 177 |
//////////////////////////////////////////////////////////////*/ |
||
| 178 |
enum Operation {
|
||
| 179 |
Deposit, |
||
| 180 |
RedeemRequest, |
||
| 181 |
CancelRedeem, |
||
| 182 |
ClaimRedeem, |
||
| 183 |
Claim, |
||
| 184 |
UpdateDepositAccumulators |
||
| 185 |
} |
||
| 186 | |||
| 187 |
/*////////////////////////////////////////////////////////////// |
||
| 188 |
CORE STRATEGY OPERATIONS |
||
| 189 |
//////////////////////////////////////////////////////////////*/ |
||
| 190 | |||
| 191 |
/// @notice Initializes the strategy with required parameters |
||
| 192 |
/// @param vaultAddress Address of the associated SuperVault |
||
| 193 |
/// @param feeConfigData Fee configuration |
||
| 194 |
function initialize(address vaultAddress, FeeConfig memory feeConfigData) external; |
||
| 195 | |||
| 196 |
/// @notice Execute a 4626 deposit by processing assets. |
||
| 197 |
/// @param controller The controller address |
||
| 198 |
/// @param assetsGross The amount of gross assets user has to deposit |
||
| 199 |
/// @return sharesNet The amount of net shares to mint |
||
| 200 |
function handleOperations4626Deposit( |
||
| 201 |
address controller, |
||
| 202 |
uint256 assetsGross |
||
| 203 |
) |
||
| 204 |
external |
||
| 205 |
returns (uint256 sharesNet); |
||
| 206 | |||
| 207 |
/// @notice Execute a 4626 mint by processing shares. |
||
| 208 |
/// @param controller The controller address |
||
| 209 |
/// @param sharesNet The amount of shares to mint |
||
| 210 |
/// @param assetsGross The amount of gross assets user has to deposit |
||
| 211 |
/// @param assetsNet The amount of net assets that strategy will receive |
||
| 212 |
function handleOperations4626Mint( |
||
| 213 |
address controller, |
||
| 214 |
uint256 sharesNet, |
||
| 215 |
uint256 assetsGross, |
||
| 216 |
uint256 assetsNet |
||
| 217 |
) |
||
| 218 |
external; |
||
| 219 | |||
| 220 |
/// @notice Quotes the amount of assets that will be received for a given amount of shares. |
||
| 221 |
/// @param shares The amount of shares to mint |
||
| 222 |
/// @return assetsGross The amount of gross assets that will be received |
||
| 223 |
/// @return assetsNet The amount of net assets that will be received |
||
| 224 |
function quoteMintAssetsGross(uint256 shares) external view returns (uint256 assetsGross, uint256 assetsNet); |
||
| 225 | |||
| 226 |
/// @notice Execute async redeem requests (redeem, cancel, claim). |
||
| 227 |
/// @param op The operation type (RedeemRequest, CancelRedeem, ClaimRedeem) |
||
| 228 |
/// @param controller The controller address |
||
| 229 |
/// @param receiver The receiver address |
||
| 230 |
/// @param amount The amount of assets or shares |
||
| 231 |
function handleOperations7540(Operation op, address controller, address receiver, uint256 amount) external; |
||
| 232 | |||
| 233 |
/*////////////////////////////////////////////////////////////// |
||
| 234 |
MANAGER EXTERNAL ACCESS FUNCTIONS |
||
| 235 |
//////////////////////////////////////////////////////////////*/ |
||
| 236 | |||
| 237 |
/// @notice Execute hooks for general strategy management (rebalancing, etc.). |
||
| 238 |
/// @param args Execution arguments containing hooks, calldata, proofs, expectations. |
||
| 239 |
function executeHooks(ExecuteArgs calldata args) external payable; |
||
| 240 | |||
| 241 |
/// @notice Fulfills pending redeem requests by executing specific fulfill hooks. |
||
| 242 |
/// @param args Execution arguments containing fulfill hooks, calldata, and expected outputs (proofs ignored). |
||
| 243 |
function fulfillRedeemRequests(FulfillArgs calldata args) external; |
||
| 244 | |||
| 245 |
/*////////////////////////////////////////////////////////////// |
||
| 246 |
YIELD SOURCE MANAGEMENT |
||
| 247 |
//////////////////////////////////////////////////////////////*/ |
||
| 248 | |||
| 249 |
/// @notice Manage a single yield source: add, update oracle, or remove |
||
| 250 |
/// @param source Address of the yield source |
||
| 251 |
/// @param oracle Address of the oracle (used for adding/updating, ignored for removal) |
||
| 252 |
/// @param actionType Type of action: 0=Add, 1=UpdateOracle, 2=Remove |
||
| 253 |
function manageYieldSource(address source, address oracle, uint8 actionType) external; |
||
| 254 | |||
| 255 |
/// @notice Batch manage multiple yield sources in a single transaction |
||
| 256 |
/// @param sources Array of yield source addresses |
||
| 257 |
/// @param oracles Array of oracle addresses (used for adding/updating, ignored for removal) |
||
| 258 |
/// @param actionTypes Array of action types: 0=Add, 1=UpdateOracle, 2=Remove |
||
| 259 |
function manageYieldSources( |
||
| 260 |
address[] calldata sources, |
||
| 261 |
address[] calldata oracles, |
||
| 262 |
uint8[] calldata actionTypes |
||
| 263 |
) |
||
| 264 |
external; |
||
| 265 | |||
| 266 |
/// @notice Propose or execute a hook root update |
||
| 267 |
/// @notice Propose changes to vault-specific fee configuration |
||
| 268 |
/// @param performanceFeeBps New performance fee in basis points |
||
| 269 |
/// @param managementFeeBps New management fee in basis points |
||
| 270 |
/// @param recipient New fee recipient |
||
| 271 |
function proposeVaultFeeConfigUpdate( |
||
| 272 |
uint256 performanceFeeBps, |
||
| 273 |
uint256 managementFeeBps, |
||
| 274 |
address recipient |
||
| 275 |
) |
||
| 276 |
external; |
||
| 277 | |||
| 278 |
/// @notice Execute the proposed vault fee configuration update after timelock |
||
| 279 |
function executeVaultFeeConfigUpdate() external; |
||
| 280 | |||
| 281 |
/// @notice Update the maximum allowed PPS slippage for redemptions |
||
| 282 |
/// @param maxSlippageBps Maximum slippage in basis points (e.g., 100 = 1%) |
||
| 283 |
function updateMaxPPSSlippage(uint256 maxSlippageBps) external; |
||
| 284 | |||
| 285 |
/// @notice Manage emergency withdrawals |
||
| 286 |
/// @param action Type of action: 1=Propose, 2=ExecuteActivation, 3=Withdraw, 4=CancelProposal |
||
| 287 |
/// @param recipient The recipient of the withdrawn assets (for action 3) |
||
| 288 |
/// @param amount The amount of assets to withdraw (for action 3) |
||
| 289 |
function manageEmergencyWithdraw(uint8 action, address recipient, uint256 amount) external; |
||
| 290 | |||
| 291 |
/*////////////////////////////////////////////////////////////// |
||
| 292 |
ACCOUNTING MANAGEMENT |
||
| 293 |
//////////////////////////////////////////////////////////////*/ |
||
| 294 |
/// @notice Move accumulator shares and cost basis pro-rata during share transfers |
||
| 295 |
/// @param from The address transferring shares |
||
| 296 |
/// @param to The address receiving shares |
||
| 297 |
/// @param shares The amount of shares being transferred |
||
| 298 |
function moveAccumulatorOnTransfer(address from, address to, uint256 shares) external; |
||
| 299 | |||
| 300 |
/*////////////////////////////////////////////////////////////// |
||
| 301 |
VIEW FUNCTIONS |
||
| 302 |
//////////////////////////////////////////////////////////////*/ |
||
| 303 | |||
| 304 |
/// @notice Get the vault info |
||
| 305 |
function getVaultInfo() external view returns (address vault, address asset, uint8 vaultDecimals); |
||
| 306 | |||
| 307 |
/// @notice Get the fee configurations |
||
| 308 |
function getConfigInfo() external view returns (FeeConfig memory feeConfig); |
||
| 309 | |||
| 310 |
/// @notice Returns the currently stored PPS value. |
||
| 311 |
function getStoredPPS() external view returns (uint256); |
||
| 312 | |||
| 313 |
/// @notice Get a yield source's configuration |
||
| 314 |
function getYieldSource(address source) external view returns (YieldSource memory); |
||
| 315 | |||
| 316 |
/// @notice Get all yield sources with their information |
||
| 317 |
/// @return Array of YieldSourceInfo structs |
||
| 318 |
function getYieldSourcesList() external view returns (YieldSourceInfo[] memory); |
||
| 319 | |||
| 320 |
/// @notice Get all yield source addresses |
||
| 321 |
/// @return Array of yield source addresses |
||
| 322 |
function getYieldSources() external view returns (address[] memory); |
||
| 323 | |||
| 324 |
/// @notice Get the count of yield sources |
||
| 325 |
/// @return Number of yield sources |
||
| 326 |
function getYieldSourcesCount() external view returns (uint256); |
||
| 327 | |||
| 328 |
/// @notice Check if a yield source exists |
||
| 329 |
/// @param source Address of the yield source |
||
| 330 |
/// @return True if the yield source exists |
||
| 331 |
function containsYieldSource(address source) external view returns (bool); |
||
| 332 | |||
| 333 |
/// @notice Get the average withdraw price for a controller |
||
| 334 |
/// @param controller The controller address |
||
| 335 |
/// @return averageWithdrawPrice The average withdraw price |
||
| 336 |
function getAverageWithdrawPrice(address controller) external view returns (uint256 averageWithdrawPrice); |
||
| 337 | |||
| 338 |
/// @notice Get the super vault state for a controller |
||
| 339 |
/// @param controller The controller address |
||
| 340 |
/// @return state The super vault state |
||
| 341 |
function getSuperVaultState(address controller) external view returns (SuperVaultState memory state); |
||
| 342 | |||
| 343 |
/// @notice Previews the fee that would be taken for redeeming a specific amount of shares |
||
| 344 |
/// @param controller The address of the controller requesting the redemption |
||
| 345 |
/// @param sharesToRedeem The number of shares to redeem |
||
| 346 |
/// @return totalFee The estimated fee that would be taken in asset terms |
||
| 347 |
/// @return superformFee The portion of the fee that would go to Superform treasury |
||
| 348 |
/// @return recipientFee The portion of the fee that would go to the fee recipient |
||
| 349 |
function previewPerformanceFee( |
||
| 350 |
address controller, |
||
| 351 |
uint256 sharesToRedeem |
||
| 352 |
) |
||
| 353 |
external |
||
| 354 |
view |
||
| 355 |
returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee); |
||
| 356 | |||
| 357 |
/// @notice Get the pending redeem request amount (shares) for a controller |
||
| 358 |
/// @param controller The controller address |
||
| 359 |
/// @return pendingShares The amount of shares pending redemption |
||
| 360 |
function pendingRedeemRequest(address controller) external view returns (uint256 pendingShares); |
||
| 361 | |||
| 362 |
/// @notice Get the claimable withdraw amount (assets) for a controller |
||
| 363 |
/// @param controller The controller address |
||
| 364 |
/// @return claimableAssets The amount of assets claimable |
||
| 365 |
function claimableWithdraw(address controller) external view returns (uint256 claimableAssets); |
||
| 366 |
} |
||
| 367 |
0.0%
src/interfaces/oracles/IECDSAPPSOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ECDSAPPSOracle |
||
| 5 |
/// @author Superform Labs |
||
| 6 |
/// @notice Interface for PPS oracles that provide price-per-share updates |
||
| 7 |
/// @dev All PPS oracle implementations must conform to this interface |
||
| 8 |
interface IECDSAPPSOracle {
|
||
| 9 |
/*////////////////////////////////////////////////////////////// |
||
| 10 |
ERRORS |
||
| 11 |
//////////////////////////////////////////////////////////////*/ |
||
| 12 |
/// @notice Thrown when the proof is invalid or cannot be verified |
||
| 13 |
error INVALID_PROOF(); |
||
| 14 |
/// @notice Thrown when a validator is not registered or authorized |
||
| 15 |
error INVALID_VALIDATOR(); |
||
| 16 |
/// @notice Thrown when the quorum of validators is not met |
||
| 17 |
error QUORUM_NOT_MET(); |
||
| 18 |
/// @notice Thrown when the input arrays have different lengths |
||
| 19 |
error ARRAY_LENGTH_MISMATCH(); |
||
| 20 |
/// @notice Thrown when the input array is empty |
||
| 21 |
error ZERO_LENGTH_ARRAY(); |
||
| 22 |
/// @notice Thrown when the timestamp in the proof is invalid |
||
| 23 |
error INVALID_TIMESTAMP(); |
||
| 24 |
/// @notice Thrown when the strategy address in the proof does not match |
||
| 25 |
error STRATEGY_MISMATCH(); |
||
| 26 |
/// @notice Thrown when the pps value in the proof does not match |
||
| 27 |
error PPS_MISMATCH(); |
||
| 28 |
/// @notice Thrown when the oracle is not set as the active PPS Oracle in SuperGovernor |
||
| 29 |
error NOT_ACTIVE_PPS_ORACLE(); |
||
| 30 |
/// @notice Thrown when the dispersion (standard deviation / mean) is too high |
||
| 31 |
error HIGH_PPS_DISPERSION(); |
||
| 32 |
/// @notice Thrown when the deviation from previous PPS is too high |
||
| 33 |
error HIGH_PPS_DEVIATION(); |
||
| 34 |
/// @notice Thrown when too few validators participated in the round |
||
| 35 |
error INSUFFICIENT_VALIDATOR_PARTICIPATION(); |
||
| 36 |
/// @notice Thrown when the reported validator count doesn't match the actual number of valid signatures |
||
| 37 |
error VALIDATOR_COUNT_MISMATCH(); |
||
| 38 |
/// @notice Thrown when the validatorSet doesn't match the actual number of valid signatures |
||
| 39 |
error INVALID_VALIDATOR_SET(); |
||
| 40 |
/// @notice Thrown when the totalValidators doesn't match the actual total number of validators |
||
| 41 |
error INVALID_TOTAL_VALIDATORS(); |
||
| 42 | |||
| 43 |
/*////////////////////////////////////////////////////////////// |
||
| 44 |
EVENTS |
||
| 45 |
//////////////////////////////////////////////////////////////*/ |
||
| 46 |
/// @notice Emitted when a PPS update is validated and forwarded |
||
| 47 |
/// @param strategy Address of the strategy |
||
| 48 |
/// @param pps The validated price-per-share value |
||
| 49 |
/// @param ppsStdev The standard deviation of the price-per-share |
||
| 50 |
/// @param validatorSet Number of validators who calculated the PPS |
||
| 51 |
/// @param totalValidators Total number of validators in the network |
||
| 52 |
/// @param timestamp Timestamp when the value was generated |
||
| 53 |
/// @param sender Address that submitted the update |
||
| 54 |
event PPSValidated( |
||
| 55 |
address indexed strategy, |
||
| 56 |
uint256 pps, |
||
| 57 |
uint256 ppsStdev, |
||
| 58 |
uint256 validatorSet, |
||
| 59 |
uint256 totalValidators, |
||
| 60 |
uint256 timestamp, |
||
| 61 |
address indexed sender |
||
| 62 |
); |
||
| 63 | |||
| 64 |
/// @notice Emitted when proof validation failed |
||
| 65 |
/// @param strategy Address of the strategy |
||
| 66 |
/// @param reason Revert reason |
||
| 67 |
event ProofValidationFailed(address indexed strategy, string reason); |
||
| 68 | |||
| 69 |
/// @notice Emitted when proof validation failed |
||
| 70 |
/// @param strategy Address of the strategy |
||
| 71 |
/// @param data Revert encoded data |
||
| 72 |
event ProofValidationFailedLowLevel(address indexed strategy, bytes data); |
||
| 73 | |||
| 74 |
/// @notice Emitted when batch forward PPS failed |
||
| 75 |
/// @param reason Revert reason |
||
| 76 |
event BatchForwardPPSFailed(string reason); |
||
| 77 | |||
| 78 |
/// @notice Emitted when batch forward PPS failed |
||
| 79 |
/// @param lowLevelData Revert encoded data |
||
| 80 |
event BatchForwardPPSFailedLowLevel(bytes lowLevelData); |
||
| 81 | |||
| 82 |
/*////////////////////////////////////////////////////////////// |
||
| 83 |
STRUCTS |
||
| 84 |
//////////////////////////////////////////////////////////////*/ |
||
| 85 |
/// @notice Parameters for validating PPS proofs |
||
| 86 |
/// @param strategy Address of the strategy |
||
| 87 |
/// @param proofs Array of cryptographic proofs |
||
| 88 |
/// @param pps Price-per-share value (mean) |
||
| 89 |
/// @param ppsStdev Standard deviation of the price-per-share |
||
| 90 |
/// @param validatorSet Number of validators who calculated this PPS |
||
| 91 |
/// @param totalValidators Total number of validators in the network |
||
| 92 |
/// @param timestamp Timestamp when the value was generated |
||
| 93 |
struct ValidationParams {
|
||
| 94 |
address strategy; |
||
| 95 |
bytes[] proofs; |
||
| 96 |
uint256 pps; |
||
| 97 |
uint256 ppsStdev; |
||
| 98 |
uint256 validatorSet; |
||
| 99 |
uint256 totalValidators; |
||
| 100 |
uint256 timestamp; |
||
| 101 |
} |
||
| 102 | |||
| 103 |
/// @notice Arguments for batch updating PPS for multiple strategies |
||
| 104 |
/// @param strategies Array of strategy addresses |
||
| 105 |
/// @param proofsArray Array of arrays of cryptographic proofs (one array of proofs per strategy) |
||
| 106 |
/// @param ppss Array of price-per-share values (means) |
||
| 107 |
/// @param ppsStdevs Array of standard deviations of price-per-share values |
||
| 108 |
/// @param validatorSets Array of numbers of validators who calculated each PPS |
||
| 109 |
/// @param totalValidators Array of total number of validators in the network for each update |
||
| 110 |
/// @param timestamps The time and therefore the blockchain(s) state(s) (plural important) this PPS refers to |
||
| 111 |
struct UpdatePPSArgs {
|
||
| 112 |
address[] strategies; |
||
| 113 |
bytes[][] proofsArray; |
||
| 114 |
uint256[] ppss; |
||
| 115 |
uint256[] ppsStdevs; |
||
| 116 |
uint256[] validatorSets; |
||
| 117 |
uint256[] totalValidators; |
||
| 118 |
uint256[] timestamps; |
||
| 119 |
} |
||
| 120 | |||
| 121 |
/*////////////////////////////////////////////////////////////// |
||
| 122 |
VIEW FUNCTIONS |
||
| 123 |
//////////////////////////////////////////////////////////////*/ |
||
| 124 |
/// @notice Returns the current nonce |
||
| 125 |
/// @param strategy_ Address of the strategy |
||
| 126 |
/// @return The current nonce |
||
| 127 |
function noncePerStrategy(address strategy_) external view returns (uint256); |
||
| 128 | |||
| 129 |
/// @notice Returns the domain separator for the contract |
||
| 130 |
/// @return The domain separator |
||
| 131 |
function domainSeparator() external view returns (bytes32); |
||
| 132 | |||
| 133 |
/// @notice Returns the signature typehash |
||
| 134 |
/// @return The typehash |
||
| 135 |
function UPDATE_PPS_TYPEHASH() external view returns (bytes32); |
||
| 136 | |||
| 137 |
/// @notice Validates an array of proofs for a strategy's PPS update |
||
| 138 |
/// @param params Validation parameters |
||
| 139 |
/// @dev Reverts immediately if duplicate signers are found or quorum is not met |
||
| 140 |
function validateProofs(IECDSAPPSOracle.ValidationParams memory params) external view; |
||
| 141 | |||
| 142 |
/*////////////////////////////////////////////////////////////// |
||
| 143 |
EXTERNAL FUNCTIONS |
||
| 144 |
//////////////////////////////////////////////////////////////*/ |
||
| 145 |
/// @notice Updates the PPS for multiple strategies in a batch |
||
| 146 |
/// @param args Struct containing all parameters for batch PPS update |
||
| 147 |
function updatePPS(UpdatePPSArgs calldata args) external; |
||
| 148 |
} |
||
| 149 |
0.0%
src/interfaces/oracles/ISuperOracle.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ISuperOracle |
||
| 5 |
/// @author Superform Labs |
||
| 6 |
/// @notice Interface for SuperOracle |
||
| 7 |
interface ISuperOracle {
|
||
| 8 |
/*////////////////////////////////////////////////////////////// |
||
| 9 |
ERRORS |
||
| 10 |
//////////////////////////////////////////////////////////////*/ |
||
| 11 |
/// @notice Error when address is zero |
||
| 12 |
error ZERO_ADDRESS(); |
||
| 13 | |||
| 14 |
/// @notice Error when array length is zero |
||
| 15 |
error ZERO_ARRAY_LENGTH(); |
||
| 16 | |||
| 17 |
/// @notice Error when oracle provider index is invalid |
||
| 18 |
error INVALID_ORACLE_PROVIDER(); |
||
| 19 | |||
| 20 |
/// @notice Error when no oracles are configured for base asset |
||
| 21 |
error NO_ORACLES_CONFIGURED(); |
||
| 22 | |||
| 23 |
/// @notice Error when no valid reported prices are found |
||
| 24 |
error NO_VALID_REPORTED_PRICES(); |
||
| 25 | |||
| 26 |
/// @notice Error when caller is not admin |
||
| 27 |
error NOT_ADMIN(); |
||
| 28 | |||
| 29 |
/// @notice Error when arrays have mismatched lengths |
||
| 30 |
error ARRAY_LENGTH_MISMATCH(); |
||
| 31 | |||
| 32 |
/// @notice Error when timelock period has not elapsed |
||
| 33 |
error TIMELOCK_NOT_ELAPSED(); |
||
| 34 | |||
| 35 |
/// @notice Error when there is already a pending update |
||
| 36 |
error PENDING_UPDATE_EXISTS(); |
||
| 37 | |||
| 38 |
/// @notice Error when oracle data is untrusted |
||
| 39 |
error ORACLE_UNTRUSTED_DATA(); |
||
| 40 | |||
| 41 |
/// @notice Error when provider max staleness period is not set |
||
| 42 |
error NO_PENDING_UPDATE(); |
||
| 43 | |||
| 44 |
/// @notice Error when quote is not supported (only USD is supported) |
||
| 45 |
error UNSUPPORTED_QUOTE(); |
||
| 46 | |||
| 47 |
/// @notice Error when provider max staleness period is exceeded |
||
| 48 |
error MAX_STALENESS_EXCEEDED(); |
||
| 49 | |||
| 50 |
/// @notice Error when no prices are reported |
||
| 51 |
error NO_PRICES(); |
||
| 52 | |||
| 53 |
/// @notice Error when average provider is not allowed |
||
| 54 |
error AVERAGE_PROVIDER_NOT_ALLOWED(); |
||
| 55 | |||
| 56 |
/// @notice Error when provider is zero |
||
| 57 |
error ZERO_PROVIDER(); |
||
| 58 | |||
| 59 |
/// @notice Error when caller is not authorized to update |
||
| 60 |
error UNAUTHORIZED_UPDATE_AUTHORITY(); |
||
| 61 | |||
| 62 |
/// @notice Error when oracle decimals call fails |
||
| 63 |
error ORACLE_DECIMALS_CALL_FAIL(address oracle); |
||
| 64 | |||
| 65 |
/// @notice Error when oracle round data call fails |
||
| 66 |
error ORACLE_ROUND_DATA_CALL_FAIL(address oracle); |
||
| 67 | |||
| 68 |
/*////////////////////////////////////////////////////////////// |
||
| 69 |
EVENTS |
||
| 70 |
//////////////////////////////////////////////////////////////*/ |
||
| 71 |
/// @notice Emitted when oracles are configured |
||
| 72 |
/// @param bases Array of base assets |
||
| 73 |
/// @param providers Array of provider indexes |
||
| 74 |
/// @param feeds Array of oracle addresses |
||
| 75 |
event OraclesConfigured(address[] bases, address[] quotes, bytes32[] providers, address[] feeds); |
||
| 76 | |||
| 77 |
/// @notice Emitted when oracle update is queued |
||
| 78 |
/// @param bases Array of base assets |
||
| 79 |
/// @param providers Array of provider indexes |
||
| 80 |
/// @param feeds Array of oracle addresses |
||
| 81 |
/// @param timestamp Timestamp when update was queued |
||
| 82 |
event OracleUpdateQueued( |
||
| 83 |
address[] bases, address[] quotes, bytes32[] providers, address[] feeds, uint256 timestamp |
||
| 84 |
); |
||
| 85 | |||
| 86 |
/// @notice Emitted when oracle update is executed |
||
| 87 |
/// @param bases Array of base assets |
||
| 88 |
/// @param providers Array of provider indexes |
||
| 89 |
/// @param feeds Array of oracle addresses |
||
| 90 |
event OracleUpdateExecuted(address[] bases, address[] quotes, bytes32[] providers, address[] feeds); |
||
| 91 | |||
| 92 |
/// @notice Emitted when provider max staleness period is updated |
||
| 93 |
/// @param feed Feed address |
||
| 94 |
/// @param newMaxStaleness New maximum staleness period in seconds |
||
| 95 |
event FeedMaxStalenessUpdated(address feed, uint256 newMaxStaleness); |
||
| 96 | |||
| 97 |
/// @notice Emitted when max staleness period is updated |
||
| 98 |
/// @param newMaxStaleness New maximum staleness period in seconds |
||
| 99 |
event MaxStalenessUpdated(uint256 newMaxStaleness); |
||
| 100 | |||
| 101 |
/// @notice Emitted when provider removal is queued |
||
| 102 |
/// @param providers Array of provider ids to remove |
||
| 103 |
/// @param timestamp Timestamp when removal was queued |
||
| 104 |
event ProviderRemovalQueued(bytes32[] providers, uint256 timestamp); |
||
| 105 | |||
| 106 |
/// @notice Emitted when provider removal is executed |
||
| 107 |
/// @param providers Array of provider ids that were removed |
||
| 108 |
event ProviderRemovalExecuted(bytes32[] providers); |
||
| 109 | |||
| 110 |
/// @notice Emitted when emergency price is updated |
||
| 111 |
/// @param token Token address |
||
| 112 |
/// @param price Emergency price |
||
| 113 |
event EmergencyPriceUpdated(address token, uint256 price); |
||
| 114 | |||
| 115 |
/*////////////////////////////////////////////////////////////// |
||
| 116 |
STRUCTS |
||
| 117 |
//////////////////////////////////////////////////////////////*/ |
||
| 118 |
/// @notice Struct for pending oracle update |
||
| 119 |
struct PendingUpdate {
|
||
| 120 |
address[] bases; |
||
| 121 |
address[] quotes; |
||
| 122 |
bytes32[] providers; |
||
| 123 |
address[] feeds; |
||
| 124 |
uint256 timestamp; |
||
| 125 |
} |
||
| 126 | |||
| 127 |
/// @notice Struct for pending provider removal |
||
| 128 |
struct PendingRemoval {
|
||
| 129 |
bytes32[] providers; |
||
| 130 |
uint256 timestamp; |
||
| 131 |
} |
||
| 132 | |||
| 133 |
/*////////////////////////////////////////////////////////////// |
||
| 134 |
EXTERNAL FUNCTIONS |
||
| 135 |
//////////////////////////////////////////////////////////////*/ |
||
| 136 | |||
| 137 |
/// @notice Get oracle address for a base asset and provider |
||
| 138 |
/// @param base Base asset address |
||
| 139 |
/// @param quote Quote asset address |
||
| 140 |
/// @param provider Provider id |
||
| 141 |
/// @return oracle Oracle address |
||
| 142 |
function getOracleAddress(address base, address quote, bytes32 provider) external view returns (address oracle); |
||
| 143 | |||
| 144 |
/// @notice Get quote from specified oracle provider |
||
| 145 |
/// @param baseAmount Amount of base asset |
||
| 146 |
/// @param base Base asset address |
||
| 147 |
/// @param quote Quote asset address |
||
| 148 |
/// @param oracleProvider Id of oracle provider to use |
||
| 149 |
/// @return quoteAmount The quote amount |
||
| 150 |
function getQuoteFromProvider( |
||
| 151 |
uint256 baseAmount, |
||
| 152 |
address base, |
||
| 153 |
address quote, |
||
| 154 |
bytes32 oracleProvider |
||
| 155 |
) |
||
| 156 |
external |
||
| 157 |
view |
||
| 158 |
returns (uint256 quoteAmount, uint256 deviation, uint256 totalProviders, uint256 availableProviders); |
||
| 159 | |||
| 160 |
/// @notice Queue oracle update for timelock |
||
| 161 |
/// @param bases Array of base assets |
||
| 162 |
/// @param providers Array of provider ids |
||
| 163 |
/// @param quotes Array of quote assets |
||
| 164 |
/// @param feeds Array of oracle addresses |
||
| 165 |
function queueOracleUpdate( |
||
| 166 |
address[] calldata bases, |
||
| 167 |
address[] calldata quotes, |
||
| 168 |
bytes32[] calldata providers, |
||
| 169 |
address[] calldata feeds |
||
| 170 |
) |
||
| 171 |
external; |
||
| 172 | |||
| 173 |
/// @notice Execute queued oracle update after timelock period |
||
| 174 |
function executeOracleUpdate() external; |
||
| 175 | |||
| 176 |
/// @notice Queue provider removal for timelock |
||
| 177 |
/// @param providers Array of provider ids to remove |
||
| 178 |
function queueProviderRemoval(bytes32[] calldata providers) external; |
||
| 179 | |||
| 180 |
/// @notice Execute queued provider removal after timelock period |
||
| 181 |
function executeProviderRemoval() external; |
||
| 182 | |||
| 183 |
/// @notice Set the maximum staleness period for a specific provider |
||
| 184 |
/// @param feed Feed address |
||
| 185 |
/// @param newMaxStaleness New maximum staleness period in seconds |
||
| 186 |
function setFeedMaxStaleness(address feed, uint256 newMaxStaleness) external; |
||
| 187 | |||
| 188 |
/// @notice Set the maximum staleness period for all providers |
||
| 189 |
/// @param newMaxStaleness New maximum staleness period in seconds |
||
| 190 |
function setMaxStaleness(uint256 newMaxStaleness) external; |
||
| 191 | |||
| 192 |
/// @notice Set the maximum staleness period for multiple providers |
||
| 193 |
/// @param feeds Array of feed addresses |
||
| 194 |
/// @param newMaxStalenessList Array of new maximum staleness periods in seconds |
||
| 195 |
function setFeedMaxStalenessBatch(address[] calldata feeds, uint256[] calldata newMaxStalenessList) external; |
||
| 196 | |||
| 197 |
/// @notice Get all active provider ids |
||
| 198 |
/// @return Array of active provider ids |
||
| 199 |
function getActiveProviders() external view returns (bytes32[] memory); |
||
| 200 | |||
| 201 |
/// @notice Get the emergency price for a token |
||
| 202 |
/// @param token Token address |
||
| 203 |
/// @return Emergency price |
||
| 204 |
function getEmergencyPrice(address token) external view returns (uint256); |
||
| 205 | |||
| 206 |
/// @notice Set the emergency price for a token |
||
| 207 |
/// @param token Token address |
||
| 208 |
/// @param price Emergency price |
||
| 209 |
function setEmergencyPrice(address token, uint256 price) external; |
||
| 210 | |||
| 211 |
/// @notice Set the emergency price for multiple tokens in a batch |
||
| 212 |
/// @param tokens Array of token addresses |
||
| 213 |
/// @param prices Array of emergency prices |
||
| 214 |
function batchSetEmergencyPrice(address[] calldata tokens, uint256[] calldata prices) external; |
||
| 215 |
} |
||
| 216 |
0.0%
src/interfaces/oracles/ISuperOracleL2.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
/// @title ISuperOracleL2 |
||
| 5 |
/// @author Superform Labs |
||
| 6 |
/// @notice Interface for Layer 2 Oracle for Superform |
||
| 7 |
interface ISuperOracleL2 {
|
||
| 8 |
/*////////////////////////////////////////////////////////////// |
||
| 9 |
ERRORS |
||
| 10 |
//////////////////////////////////////////////////////////////*/ |
||
| 11 |
/// @notice Error when no uptime feed is configured for the data oracle |
||
| 12 |
error NO_UPTIME_FEED(); |
||
| 13 | |||
| 14 |
/// @notice Error when the L2 sequencer is down |
||
| 15 |
error SEQUENCER_DOWN(); |
||
| 16 | |||
| 17 |
/// @notice Error when the grace period after sequencer restart is not over |
||
| 18 |
error GRACE_PERIOD_NOT_OVER(); |
||
| 19 | |||
| 20 |
/*////////////////////////////////////////////////////////////// |
||
| 21 |
EVENTS |
||
| 22 |
//////////////////////////////////////////////////////////////*/ |
||
| 23 |
/// @notice Emitted when an uptime feed is set for a data oracle |
||
| 24 |
/// @param dataOracle The data oracle address |
||
| 25 |
/// @param uptimeOracle The uptime feed address |
||
| 26 |
event UptimeFeedSet(address dataOracle, address uptimeOracle); |
||
| 27 | |||
| 28 |
/// @notice Emitted when a grace period is set for an uptime oracle |
||
| 29 |
/// @param uptimeOracle The uptime oracle address |
||
| 30 |
/// @param gracePeriod The grace period in seconds |
||
| 31 |
event GracePeriodSet(address uptimeOracle, uint256 gracePeriod); |
||
| 32 | |||
| 33 |
/*////////////////////////////////////////////////////////////// |
||
| 34 |
EXTERNAL FUNCTIONS |
||
| 35 |
//////////////////////////////////////////////////////////////*/ |
||
| 36 |
/// @notice Set uptime feeds for multiple data oracles in batch |
||
| 37 |
/// @param dataOracles Array of data oracle addresses to set uptime feeds for |
||
| 38 |
/// @param uptimeOracles Array of uptime feed addresses to set |
||
| 39 |
/// @param gracePeriods Array of grace periods in seconds after sequencer restart |
||
| 40 |
function batchSetUptimeFeed( |
||
| 41 |
address[] calldata dataOracles, |
||
| 42 |
address[] calldata uptimeOracles, |
||
| 43 |
uint256[] calldata gracePeriods |
||
| 44 |
) external; |
||
| 45 |
} |
||
| 46 |
100.0%
src/libraries/AssetMetadataLib.sol
Lines covered: 10 / 10 (100.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
|
||
| 5 | |||
| 6 |
/// @title AssetMetadataLib |
||
| 7 |
/// @author Superform Labs |
||
| 8 |
/// @notice Library for handling ERC20 metadata operations |
||
| 9 |
✓ 27.5K
|
⟲ 27.5K
|
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 |
✓ 228.0K
|
function tryGetAssetDecimals(address asset_) internal view returns (bool ok, uint8 assetDecimals) {
|
|
| 20 |
✓ 228.0K
|
⟲ 17.2K
|
if(asset_.code.length == 0) revert INVALID_ASSET(); |
| 21 |
|
||
| 22 |
✓ 210.8K
|
(bool success, bytes memory encodedDecimals) = |
|
| 23 |
✓ 210.8K
|
address(asset_).staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); |
|
| 24 |
✓ 210.8K
|
if (success && encodedDecimals.length >= 32) {
|
|
| 25 |
✓ 141.2K
|
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); |
|
| 26 |
✓ 141.2K
|
if (returnedDecimals <= type(uint8).max) {
|
|
| 27 |
✓ 141.2K
|
return (true, uint8(returnedDecimals)); |
|
| 28 |
} |
||
| 29 |
} |
||
| 30 |
✓ 69.6K
|
return (false, 0); |
|
| 31 |
} |
||
| 32 |
} |
||
| 33 |
0.0%
src/vendor/standards/ERC7540/IERC7540Vault.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity >=0.8.0; |
||
| 3 | |||
| 4 |
import { IERC7741 } from "../ERC7741/IERC7741.sol";
|
||
| 5 | |||
| 6 |
interface IERC7540Operator {
|
||
| 7 |
/** |
||
| 8 |
* @dev The event emitted when an operator is set. |
||
| 9 |
* |
||
| 10 |
* @param controller The address of the controller. |
||
| 11 |
* @param operator The address of the operator. |
||
| 12 |
* @param approved The approval status. |
||
| 13 |
*/ |
||
| 14 |
event OperatorSet(address indexed controller, address indexed operator, bool approved); |
||
| 15 | |||
| 16 |
/** |
||
| 17 |
* @dev Sets or removes an operator for the caller. |
||
| 18 |
* |
||
| 19 |
* @param operator The address of the operator. |
||
| 20 |
* @param approved The approval status. |
||
| 21 |
* @return Whether the call was executed successfully or not |
||
| 22 |
*/ |
||
| 23 |
function setOperator(address operator, bool approved) external returns (bool); |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev Returns `true` if the `operator` is approved as an operator for an `controller`. |
||
| 27 |
* |
||
| 28 |
* @param controller The address of the controller. |
||
| 29 |
* @param operator The address of the operator. |
||
| 30 |
* @return status The approval status |
||
| 31 |
*/ |
||
| 32 |
function isOperator(address controller, address operator) external view returns (bool status); |
||
| 33 |
} |
||
| 34 | |||
| 35 |
interface IERC7540Deposit is IERC7540Operator {
|
||
| 36 |
event DepositRequest( |
||
| 37 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 38 |
); |
||
| 39 |
/** |
||
| 40 |
* @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit. |
||
| 41 |
* |
||
| 42 |
* - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow. |
||
| 43 |
* - MUST revert if all of assets cannot be requested for deposit. |
||
| 44 |
* - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 45 |
* approval of ERC-20 tokens from owner to sender is NOT enough. |
||
| 46 |
* |
||
| 47 |
* @param assets the amount of deposit assets to transfer from owner |
||
| 48 |
* @param controller the controller of the request who will be able to operate the request |
||
| 49 |
* @param owner the source of the deposit assets |
||
| 50 |
* |
||
| 51 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token. |
||
| 52 |
*/ |
||
| 53 | |||
| 54 |
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); |
||
| 55 | |||
| 56 |
/** |
||
| 57 |
* @dev Returns the amount of requested assets in Pending state. |
||
| 58 |
* |
||
| 59 |
* - MUST NOT include any assets in Claimable state for deposit or mint. |
||
| 60 |
* - MUST NOT show any variations depending on the caller. |
||
| 61 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 62 |
*/ |
||
| 63 |
function pendingDepositRequest( |
||
| 64 |
uint256 requestId, |
||
| 65 |
address controller |
||
| 66 |
) |
||
| 67 |
external |
||
| 68 |
view |
||
| 69 |
returns (uint256 pendingAssets); |
||
| 70 | |||
| 71 |
/** |
||
| 72 |
* @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint. |
||
| 73 |
* |
||
| 74 |
* - MUST NOT include any assets in Pending state. |
||
| 75 |
* - MUST NOT show any variations depending on the caller. |
||
| 76 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 77 |
*/ |
||
| 78 |
function claimableDepositRequest( |
||
| 79 |
uint256 requestId, |
||
| 80 |
address controller |
||
| 81 |
) |
||
| 82 |
external |
||
| 83 |
view |
||
| 84 |
returns (uint256 claimableAssets); |
||
| 85 | |||
| 86 |
/** |
||
| 87 |
* @dev Mints shares Vault shares to receiver by claiming the Request of the controller. |
||
| 88 |
* |
||
| 89 |
* - MUST emit the Deposit event. |
||
| 90 |
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
||
| 91 |
*/ |
||
| 92 |
function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); |
||
| 93 | |||
| 94 |
/** |
||
| 95 |
* @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller. |
||
| 96 |
* |
||
| 97 |
* - MUST emit the Deposit event. |
||
| 98 |
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. |
||
| 99 |
*/ |
||
| 100 |
function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); |
||
| 101 |
} |
||
| 102 | |||
| 103 |
interface IERC7540Redeem is IERC7540Operator {
|
||
| 104 |
event RedeemRequest( |
||
| 105 |
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets |
||
| 106 |
); |
||
| 107 | |||
| 108 |
/** |
||
| 109 |
* @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem. |
||
| 110 |
* |
||
| 111 |
* - MUST support a redeem Request flow where the control of shares is taken from sender directly |
||
| 112 |
* where msg.sender has ERC-20 approval over the shares of owner. |
||
| 113 |
* - MUST revert if all of shares cannot be requested for redeem. |
||
| 114 |
* |
||
| 115 |
* @param shares the amount of shares to be redeemed to transfer from owner |
||
| 116 |
* @param controller the controller of the request who will be able to operate the request |
||
| 117 |
* @param owner the source of the shares to be redeemed |
||
| 118 |
* |
||
| 119 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault's share token. |
||
| 120 |
*/ |
||
| 121 |
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); |
||
| 122 | |||
| 123 |
/** |
||
| 124 |
* @dev Returns the amount of requested shares in Pending state. |
||
| 125 |
* |
||
| 126 |
* - MUST NOT include any shares in Claimable state for redeem or withdraw. |
||
| 127 |
* - MUST NOT show any variations depending on the caller. |
||
| 128 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 129 |
*/ |
||
| 130 |
function pendingRedeemRequest( |
||
| 131 |
uint256 requestId, |
||
| 132 |
address controller |
||
| 133 |
) |
||
| 134 |
external |
||
| 135 |
view |
||
| 136 |
returns (uint256 pendingShares); |
||
| 137 | |||
| 138 |
/** |
||
| 139 |
* @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw. |
||
| 140 |
* |
||
| 141 |
* - MUST NOT include any shares in Pending state for redeem or withdraw. |
||
| 142 |
* - MUST NOT show any variations depending on the caller. |
||
| 143 |
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. |
||
| 144 |
*/ |
||
| 145 |
function claimableRedeemRequest( |
||
| 146 |
uint256 requestId, |
||
| 147 |
address controller |
||
| 148 |
) |
||
| 149 |
external |
||
| 150 |
view |
||
| 151 |
returns (uint256 claimableShares); |
||
| 152 |
} |
||
| 153 | |||
| 154 |
interface IERC7540CancelDeposit {
|
||
| 155 |
event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender); |
||
| 156 |
event CancelDepositClaim( |
||
| 157 |
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets |
||
| 158 |
); |
||
| 159 | |||
| 160 |
/** |
||
| 161 |
* @dev Submits a Request for cancelling the pending deposit Request |
||
| 162 |
* |
||
| 163 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 164 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 165 |
* - MUST set pendingCancelDepositRequest to `true` for the returned requestId after request |
||
| 166 |
* - MUST increase claimableCancelDepositRequest for the returned requestId after fulfillment |
||
| 167 |
* - SHOULD be claimable using `claimCancelDepositRequest` |
||
| 168 |
* Note: while `pendingCancelDepositRequest` is `true`, `requestDeposit` cannot be called |
||
| 169 |
*/ |
||
| 170 |
function cancelDepositRequest(uint256 requestId, address controller) external; |
||
| 171 | |||
| 172 |
/** |
||
| 173 |
* @dev Returns whether the deposit Request is pending cancelation |
||
| 174 |
* |
||
| 175 |
* - MUST NOT show any variations depending on the caller. |
||
| 176 |
*/ |
||
| 177 |
function pendingCancelDepositRequest( |
||
| 178 |
uint256 requestId, |
||
| 179 |
address controller |
||
| 180 |
) |
||
| 181 |
external |
||
| 182 |
view |
||
| 183 |
returns (bool isPending); |
||
| 184 | |||
| 185 |
/** |
||
| 186 |
* @dev Returns the amount of assets that were canceled from a deposit Request, and can now be claimed. |
||
| 187 |
* |
||
| 188 |
* - MUST NOT show any variations depending on the caller. |
||
| 189 |
*/ |
||
| 190 |
function claimableCancelDepositRequest( |
||
| 191 |
uint256 requestId, |
||
| 192 |
address controller |
||
| 193 |
) |
||
| 194 |
external |
||
| 195 |
view |
||
| 196 |
returns (uint256 claimableAssets); |
||
| 197 | |||
| 198 |
/** |
||
| 199 |
* @dev Claims the canceled deposit assets, and removes the pending cancelation Request |
||
| 200 |
* |
||
| 201 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 202 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 203 |
* - MUST set pendingCancelDepositRequest to `false` for the returned requestId after request |
||
| 204 |
* - MUST set claimableCancelDepositRequest to 0 for the returned requestId after fulfillment |
||
| 205 |
*/ |
||
| 206 |
function claimCancelDepositRequest( |
||
| 207 |
uint256 requestId, |
||
| 208 |
address receiver, |
||
| 209 |
address controller |
||
| 210 |
) |
||
| 211 |
external |
||
| 212 |
returns (uint256 assets); |
||
| 213 |
} |
||
| 214 | |||
| 215 |
interface IERC7540CancelRedeem {
|
||
| 216 |
event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender); |
||
| 217 |
event CancelRedeemClaim( |
||
| 218 |
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares |
||
| 219 |
); |
||
| 220 | |||
| 221 |
/** |
||
| 222 |
* @dev Submits a Request for cancelling the pending redeem Request |
||
| 223 |
* |
||
| 224 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 225 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 226 |
* - MUST set pendingCancelRedeemRequest to `true` for the returned requestId after request |
||
| 227 |
* - MUST increase claimableCancelRedeemRequest for the returned requestId after fulfillment |
||
| 228 |
* - SHOULD be claimable using `claimCancelRedeemRequest` |
||
| 229 |
* Note: while `pendingCancelRedeemRequest` is `true`, `requestRedeem` cannot be called |
||
| 230 |
*/ |
||
| 231 |
function cancelRedeemRequest(uint256 requestId, address controller) external; |
||
| 232 | |||
| 233 |
/** |
||
| 234 |
* @dev Returns whether the redeem Request is pending cancelation |
||
| 235 |
* |
||
| 236 |
* - MUST NOT show any variations depending on the caller. |
||
| 237 |
*/ |
||
| 238 |
function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool isPending); |
||
| 239 | |||
| 240 |
/** |
||
| 241 |
* @dev Returns the amount of shares that were canceled from a redeem Request, and can now be claimed. |
||
| 242 |
* |
||
| 243 |
* - MUST NOT show any variations depending on the caller. |
||
| 244 |
*/ |
||
| 245 |
function claimableCancelRedeemRequest( |
||
| 246 |
uint256 requestId, |
||
| 247 |
address controller |
||
| 248 |
) |
||
| 249 |
external |
||
| 250 |
view |
||
| 251 |
returns (uint256 claimableShares); |
||
| 252 | |||
| 253 |
/** |
||
| 254 |
* @dev Claims the canceled redeem shares, and removes the pending cancelation Request |
||
| 255 |
* |
||
| 256 |
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller, |
||
| 257 |
* approval of ERC-20 tokens from controller to sender is NOT enough. |
||
| 258 |
* - MUST set pendingCancelRedeemRequest to `false` for the returned requestId after request |
||
| 259 |
* - MUST set claimableCancelRedeemRequest to 0 for the returned requestId after fulfillment |
||
| 260 |
*/ |
||
| 261 |
function claimCancelRedeemRequest( |
||
| 262 |
uint256 requestId, |
||
| 263 |
address receiver, |
||
| 264 |
address controller |
||
| 265 |
) |
||
| 266 |
external |
||
| 267 |
returns (uint256 shares); |
||
| 268 |
} |
||
| 269 | |||
| 270 |
/** |
||
| 271 |
* @title IERC7540 |
||
| 272 |
* @dev Fully async ERC7540 implementation according to the standard |
||
| 273 |
* @dev Adapted from Centrifuge's IERC7540 implementation |
||
| 274 |
*/ |
||
| 275 |
interface IERC7540 is IERC7540Deposit, IERC7540Redeem { }
|
||
| 276 | |||
| 277 |
/** |
||
| 278 |
* @title IERC7540Vault |
||
| 279 |
* @dev This is the specific set of interfaces used by the SuperVaults |
||
| 280 |
*/ |
||
| 281 |
interface IERC7540Vault is IERC7540, IERC7741 {
|
||
| 282 |
event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
||
| 283 |
event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares); |
||
| 284 |
} |
||
| 285 |
0.0%
src/vendor/standards/ERC7575/IERC7575.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity >=0.8.0; |
||
| 3 | |||
| 4 |
/** |
||
| 5 |
* @dev Interface of the ERC165 standard, as defined in the |
||
| 6 |
* https://eips.ethereum.org/EIPS/eip-165[EIP]. |
||
| 7 |
* |
||
| 8 |
* Implementers can declare support of contract interfaces, which can then be |
||
| 9 |
* queried by others. |
||
| 10 |
*/ |
||
| 11 |
interface IERC165 {
|
||
| 12 |
/** |
||
| 13 |
* @dev Returns true if this contract implements the interface defined by |
||
| 14 |
* `interfaceId`. See the corresponding |
||
| 15 |
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] |
||
| 16 |
* to learn more about how these ids are created. |
||
| 17 |
* |
||
| 18 |
* This function call must use less than 30 000 gas. |
||
| 19 |
*/ |
||
| 20 |
function supportsInterface(bytes4 interfaceId) external view returns (bool); |
||
| 21 |
} |
||
| 22 | |||
| 23 |
interface IERC7575 is IERC165 {
|
||
| 24 |
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); |
||
| 25 |
event Withdraw( |
||
| 26 |
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares |
||
| 27 |
); |
||
| 28 | |||
| 29 |
/** |
||
| 30 |
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. |
||
| 31 |
* |
||
| 32 |
* - MUST be an ERC-20 token contract. |
||
| 33 |
* - MUST NOT revert. |
||
| 34 |
*/ |
||
| 35 |
function asset() external view returns (address assetTokenAddress); |
||
| 36 | |||
| 37 |
/** |
||
| 38 |
* @dev Returns the address of the share token |
||
| 39 |
* |
||
| 40 |
* - MUST be an ERC-20 token contract. |
||
| 41 |
* - MUST NOT revert. |
||
| 42 |
*/ |
||
| 43 |
function share() external view returns (address shareTokenAddress); |
||
| 44 | |||
| 45 |
/** |
||
| 46 |
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal |
||
| 47 |
* scenario where all the conditions are met. |
||
| 48 |
* |
||
| 49 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 50 |
* - MUST NOT show any variations depending on the caller. |
||
| 51 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 52 |
* - MUST NOT revert. |
||
| 53 |
* |
||
| 54 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 55 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 56 |
* from. |
||
| 57 |
*/ |
||
| 58 |
function convertToShares(uint256 assets) external view returns (uint256 shares); |
||
| 59 | |||
| 60 |
/** |
||
| 61 |
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal |
||
| 62 |
* scenario where all the conditions are met. |
||
| 63 |
* |
||
| 64 |
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault. |
||
| 65 |
* - MUST NOT show any variations depending on the caller. |
||
| 66 |
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. |
||
| 67 |
* - MUST NOT revert. |
||
| 68 |
* |
||
| 69 |
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the |
||
| 70 |
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and |
||
| 71 |
* from. |
||
| 72 |
*/ |
||
| 73 |
function convertToAssets(uint256 shares) external view returns (uint256 assets); |
||
| 74 | |||
| 75 |
/** |
||
| 76 |
* @dev Returns the total amount of the underlying asset that is “managed” by Vault. |
||
| 77 |
* |
||
| 78 |
* - SHOULD include any compounding that occurs from yield. |
||
| 79 |
* - MUST be inclusive of any fees that are charged against assets in the Vault. |
||
| 80 |
* - MUST NOT revert. |
||
| 81 |
*/ |
||
| 82 |
function totalAssets() external view returns (uint256 totalManagedAssets); |
||
| 83 | |||
| 84 |
/** |
||
| 85 |
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, |
||
| 86 |
* through a deposit call. |
||
| 87 |
* |
||
| 88 |
* - MUST return a limited value if receiver is subject to some deposit limit. |
||
| 89 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. |
||
| 90 |
* - MUST NOT revert. |
||
| 91 |
*/ |
||
| 92 |
function maxDeposit(address receiver) external view returns (uint256 maxAssets); |
||
| 93 | |||
| 94 |
/** |
||
| 95 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given |
||
| 96 |
* current on-chain conditions. |
||
| 97 |
* |
||
| 98 |
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit |
||
| 99 |
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called |
||
| 100 |
* in the same transaction. |
||
| 101 |
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the |
||
| 102 |
* deposit would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 103 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 104 |
* - MUST NOT revert. |
||
| 105 |
* |
||
| 106 |
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in |
||
| 107 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 108 |
*/ |
||
| 109 |
function previewDeposit(uint256 assets) external view returns (uint256 shares); |
||
| 110 | |||
| 111 |
/** |
||
| 112 |
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. |
||
| 113 |
* |
||
| 114 |
* - MUST emit the Deposit event. |
||
| 115 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 116 |
* deposit execution, and are accounted for during deposit. |
||
| 117 |
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not |
||
| 118 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 119 |
* |
||
| 120 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 121 |
*/ |
||
| 122 |
function deposit(uint256 assets, address receiver) external returns (uint256 shares); |
||
| 123 | |||
| 124 |
/** |
||
| 125 |
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. |
||
| 126 |
* - MUST return a limited value if receiver is subject to some mint limit. |
||
| 127 |
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. |
||
| 128 |
* - MUST NOT revert. |
||
| 129 |
*/ |
||
| 130 |
function maxMint(address receiver) external view returns (uint256 maxShares); |
||
| 131 | |||
| 132 |
/** |
||
| 133 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given |
||
| 134 |
* current on-chain conditions. |
||
| 135 |
* |
||
| 136 |
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call |
||
| 137 |
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the |
||
| 138 |
* same transaction. |
||
| 139 |
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint |
||
| 140 |
* would be accepted, regardless if the user has enough tokens approved, etc. |
||
| 141 |
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. |
||
| 142 |
* - MUST NOT revert. |
||
| 143 |
* |
||
| 144 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in |
||
| 145 |
* share price or some other type of condition, meaning the depositor will lose assets by minting. |
||
| 146 |
*/ |
||
| 147 |
function previewMint(uint256 shares) external view returns (uint256 assets); |
||
| 148 | |||
| 149 |
/** |
||
| 150 |
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. |
||
| 151 |
* |
||
| 152 |
* - MUST emit the Deposit event. |
||
| 153 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint |
||
| 154 |
* execution, and are accounted for during mint. |
||
| 155 |
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not |
||
| 156 |
* approving enough underlying tokens to the Vault contract, etc). |
||
| 157 |
* |
||
| 158 |
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. |
||
| 159 |
*/ |
||
| 160 |
function mint(uint256 shares, address receiver) external returns (uint256 assets); |
||
| 161 | |||
| 162 |
/** |
||
| 163 |
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the |
||
| 164 |
* Vault, through a withdraw call. |
||
| 165 |
* |
||
| 166 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 167 |
* - MUST NOT revert. |
||
| 168 |
*/ |
||
| 169 |
function maxWithdraw(address owner) external view returns (uint256 maxAssets); |
||
| 170 | |||
| 171 |
/** |
||
| 172 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, |
||
| 173 |
* given current on-chain conditions. |
||
| 174 |
* |
||
| 175 |
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw |
||
| 176 |
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if |
||
| 177 |
* called |
||
| 178 |
* in the same transaction. |
||
| 179 |
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though |
||
| 180 |
* the withdrawal would be accepted, regardless if the user has enough shares, etc. |
||
| 181 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 182 |
* - MUST NOT revert. |
||
| 183 |
* |
||
| 184 |
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in |
||
| 185 |
* share price or some other type of condition, meaning the depositor will lose assets by depositing. |
||
| 186 |
*/ |
||
| 187 |
function previewWithdraw(uint256 assets) external view returns (uint256 shares); |
||
| 188 | |||
| 189 |
/** |
||
| 190 |
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. |
||
| 191 |
* |
||
| 192 |
* - MUST emit the Withdraw event. |
||
| 193 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 194 |
* withdraw execution, and are accounted for during withdraw. |
||
| 195 |
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner |
||
| 196 |
* not having enough shares, etc). |
||
| 197 |
* |
||
| 198 |
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 199 |
* Those methods should be performed separately. |
||
| 200 |
*/ |
||
| 201 |
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); |
||
| 202 | |||
| 203 |
/** |
||
| 204 |
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, |
||
| 205 |
* through a redeem call. |
||
| 206 |
* |
||
| 207 |
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock. |
||
| 208 |
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. |
||
| 209 |
* - MUST NOT revert. |
||
| 210 |
*/ |
||
| 211 |
function maxRedeem(address owner) external view returns (uint256 maxShares); |
||
| 212 | |||
| 213 |
/** |
||
| 214 |
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, |
||
| 215 |
* given current on-chain conditions. |
||
| 216 |
* |
||
| 217 |
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call |
||
| 218 |
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the |
||
| 219 |
* same transaction. |
||
| 220 |
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the |
||
| 221 |
* redemption would be accepted, regardless if the user has enough shares, etc. |
||
| 222 |
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. |
||
| 223 |
* - MUST NOT revert. |
||
| 224 |
* |
||
| 225 |
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in |
||
| 226 |
* share price or some other type of condition, meaning the depositor will lose assets by redeeming. |
||
| 227 |
*/ |
||
| 228 |
function previewRedeem(uint256 shares) external view returns (uint256 assets); |
||
| 229 | |||
| 230 |
/** |
||
| 231 |
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. |
||
| 232 |
* |
||
| 233 |
* - MUST emit the Withdraw event. |
||
| 234 |
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the |
||
| 235 |
* redeem execution, and are accounted for during redeem. |
||
| 236 |
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner |
||
| 237 |
* not having enough shares, etc). |
||
| 238 |
* |
||
| 239 |
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. |
||
| 240 |
* Those methods should be performed separately. |
||
| 241 |
*/ |
||
| 242 |
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); |
||
| 243 |
} |
||
| 244 |
0.0%
src/vendor/standards/ERC7741/IERC7741.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: AGPL-3.0-only |
||
| 2 |
pragma solidity >=0.8.0; |
||
| 3 | |||
| 4 |
interface IERC7741 {
|
||
| 5 |
/** |
||
| 6 |
* @dev Grants or revokes permissions for `operator` to manage Requests on behalf of the |
||
| 7 |
* `msg.sender`, using an [EIP-712](./eip-712.md) signature. |
||
| 8 |
*/ |
||
| 9 |
function authorizeOperator( |
||
| 10 |
address controller, |
||
| 11 |
address operator, |
||
| 12 |
bool approved, |
||
| 13 |
bytes32 nonce, |
||
| 14 |
uint256 deadline, |
||
| 15 |
bytes memory signature |
||
| 16 |
) |
||
| 17 |
external |
||
| 18 |
returns (bool); |
||
| 19 | |||
| 20 |
/** |
||
| 21 |
* @dev Revokes the given `nonce` for `msg.sender` as the `owner`. |
||
| 22 |
*/ |
||
| 23 |
function invalidateNonce(bytes32 nonce) external; |
||
| 24 | |||
| 25 |
/** |
||
| 26 |
* @dev Returns whether the given `nonce` has been used for the `controller`. |
||
| 27 |
*/ |
||
| 28 |
function authorizations(address controller, bytes32 nonce) external view returns (bool used); |
||
| 29 | |||
| 30 |
/** |
||
| 31 |
* @dev Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR |
||
| 32 |
* should be unique to the contract and chain to prevent replay attacks from other domains, |
||
| 33 |
* and satisfy the requirements of EIP-712, but is otherwise unconstrained. |
||
| 34 |
*/ |
||
| 35 |
function DOMAIN_SEPARATOR() external view returns (bytes32); |
||
| 36 |
} |
||
| 37 |
0.0%
test/mocks/MockYieldSourceOracle.sol
Lines covered: 0 / 40 (0.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity ^0.8.30; |
||
| 3 | |||
| 4 |
import { IYieldSourceOracle } from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
|
||
| 5 | |||
| 6 |
// Mock YieldSourceOracle implementation for testing |
||
| 7 |
contract MockYieldSourceOracle is IYieldSourceOracle {
|
||
| 8 |
uint256 public pricePerShare; |
||
| 9 |
uint256 public tvl; |
||
| 10 |
uint256 public tvlByOwner; |
||
| 11 |
bool public validity; |
||
| 12 |
mapping(address => bool) public validAssetMap; |
||
| 13 | |||
| 14 |
constructor(uint256 _pricePerShare, uint256 _tvl, uint256 _tvlByOwner, bool _validity) {
|
||
| 15 |
pricePerShare = _pricePerShare; |
||
| 16 |
tvl = _tvl; |
||
| 17 |
tvlByOwner = _tvlByOwner; |
||
| 18 |
validity = _validity; |
||
| 19 |
} |
||
| 20 | |||
| 21 |
function setPricePerShare(uint256 _pricePerShare) external {
|
||
| 22 |
pricePerShare = _pricePerShare; |
||
| 23 |
} |
||
| 24 | |||
| 25 |
function setTVL(uint256 _tvl) external {
|
||
| 26 |
tvl = _tvl; |
||
| 27 |
} |
||
| 28 | |||
| 29 |
function setTVLByOwner(uint256 _tvlByOwner) external {
|
||
| 30 |
tvlByOwner = _tvlByOwner; |
||
| 31 |
} |
||
| 32 | |||
| 33 |
function setValidity(bool _validity) external {
|
||
| 34 |
validity = _validity; |
||
| 35 |
} |
||
| 36 | |||
| 37 |
function setValidAsset(address asset, bool isValid) external {
|
||
| 38 |
validAssetMap[asset] = isValid; |
||
| 39 |
} |
||
| 40 | |||
| 41 |
function decimals(address) external pure returns (uint8) {
|
||
| 42 |
return 18; |
||
| 43 |
} |
||
| 44 | |||
| 45 |
function getShareOutput(address, address, uint256 assetsIn) external pure returns (uint256) {
|
||
| 46 |
return assetsIn; |
||
| 47 |
} |
||
| 48 | |||
| 49 |
function getAssetOutput(address, address, uint256 sharesIn) public pure returns (uint256) {
|
||
| 50 |
return sharesIn; |
||
| 51 |
} |
||
| 52 | |||
| 53 |
function getAssetOutputWithFees( |
||
| 54 |
bytes32, |
||
| 55 |
address, |
||
| 56 |
address, |
||
| 57 |
address, |
||
| 58 |
uint256 sharesIn |
||
| 59 |
) |
||
| 60 |
public |
||
| 61 |
pure |
||
| 62 |
returns (uint256) |
||
| 63 |
{
|
||
| 64 |
return sharesIn; |
||
| 65 |
} |
||
| 66 | |||
| 67 |
function getBalanceOfOwner(address, address) external view returns (uint256) {
|
||
| 68 |
return tvlByOwner; |
||
| 69 |
} |
||
| 70 | |||
| 71 |
function getPricePerShare(address) external view returns (uint256) {
|
||
| 72 |
return pricePerShare; |
||
| 73 |
} |
||
| 74 | |||
| 75 |
function getTVLByOwnerOfShares(address, address) external view returns (uint256) {
|
||
| 76 |
return tvlByOwner; |
||
| 77 |
} |
||
| 78 | |||
| 79 |
function getTVL(address) external view returns (uint256) {
|
||
| 80 |
return tvl; |
||
| 81 |
} |
||
| 82 | |||
| 83 |
function getPricePerShareMultiple(address[] memory) external view returns (uint256[] memory) {
|
||
| 84 |
uint256[] memory prices = new uint256[](1); |
||
| 85 |
prices[0] = pricePerShare; |
||
| 86 |
return prices; |
||
| 87 |
} |
||
| 88 | |||
| 89 |
function getTVLByOwnerOfSharesMultiple( |
||
| 90 |
address[] memory yieldSources, |
||
| 91 |
address[][] memory |
||
| 92 |
) |
||
| 93 |
external |
||
| 94 |
view |
||
| 95 |
returns (uint256[][] memory) |
||
| 96 |
{
|
||
| 97 |
uint256[][] memory result = new uint256[][](yieldSources.length); |
||
| 98 |
for (uint256 i = 0; i < yieldSources.length; i++) {
|
||
| 99 |
result[i] = new uint256[](1); |
||
| 100 |
result[i][0] = tvlByOwner; |
||
| 101 |
} |
||
| 102 |
return result; |
||
| 103 |
} |
||
| 104 | |||
| 105 |
function getTVLMultiple(address[] memory) external view returns (uint256[] memory) {
|
||
| 106 |
uint256[] memory tvls = new uint256[](1); |
||
| 107 |
tvls[0] = tvl; |
||
| 108 |
return tvls; |
||
| 109 |
} |
||
| 110 | |||
| 111 |
function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
|
||
| 112 |
return validAssetMap[asset]; |
||
| 113 |
} |
||
| 114 | |||
| 115 |
function isValidUnderlyingAssets(address[] memory, address[] memory) external view returns (bool[] memory) {
|
||
| 116 |
bool[] memory validities = new bool[](1); |
||
| 117 |
validities[0] = validity; |
||
| 118 |
return validities; |
||
| 119 |
} |
||
| 120 |
} |
||
| 121 |
100.0%
test/recon/BeforeAfter.sol
Lines covered: 80 / 80 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 5 |
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 6 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 7 | |||
| 8 |
import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 9 | |||
| 10 |
import {Setup} from "./Setup.sol";
|
||
| 11 | |||
| 12 |
enum OpType {
|
||
| 13 |
DEFAULT, |
||
| 14 |
ADD, |
||
| 15 |
REMOVE, |
||
| 16 |
FULFILL, |
||
| 17 |
REQUEST, |
||
| 18 |
TRANSFER, |
||
| 19 |
CANCEL |
||
| 20 |
} |
||
| 21 | |||
| 22 |
// ghost variables for tracking state variable values before and after function calls |
||
| 23 |
abstract contract BeforeAfter is Setup {
|
||
| 24 |
struct Vars {
|
||
| 25 |
mapping(address user => uint256 pendingAsAssets) pendingUserAssets; |
||
| 26 |
mapping(address user => uint256 claimableAsAssets) claimableUserAssets; |
||
| 27 |
mapping(address user => ISuperVaultStrategy.SuperVaultState) state; |
||
| 28 |
mapping(address user => uint256 shares) superVaultShares; |
||
| 29 |
uint256 oraclePPS; |
||
| 30 |
uint256 naivePPS; |
||
| 31 |
uint256 summedTotalShares; |
||
| 32 |
uint256 summedAccumulatorShares; |
||
| 33 |
uint256 summedAccumulatorCostBasis; |
||
| 34 |
uint256 summedTotalAssets; |
||
| 35 |
uint256 strategyAssetBalance; |
||
| 36 |
uint256 summedPendingRedeem; |
||
| 37 |
} |
||
| 38 | |||
| 39 |
Vars internal _before; |
||
| 40 |
Vars internal _after; |
||
| 41 |
OpType internal _currentOp; |
||
| 42 | |||
| 43 |
modifier updateGhosts() {
|
||
| 44 |
✓ 153.1K
|
_currentOp = OpType.DEFAULT; |
|
| 45 |
✓ 153.1K
|
__before(); |
|
| 46 |
_; |
||
| 47 |
✓ 150.1K
|
__after(); |
|
| 48 |
} |
||
| 49 | |||
| 50 |
modifier updateGhostsWithOpType(OpType op) {
|
||
| 51 |
✓ 427.6K
|
_currentOp = op; |
|
| 52 |
✓ 427.6K
|
__before(); |
|
| 53 |
_; |
||
| 54 |
✓ 292.9K
|
__after(); |
|
| 55 |
} |
||
| 56 | |||
| 57 |
function __before() internal {
|
||
| 58 |
( |
||
| 59 |
✓ 2.7M
|
_before.summedAccumulatorShares, |
|
| 60 |
✓ 2.7M
|
_before.summedAccumulatorCostBasis |
|
| 61 |
✓ 2.7M
|
) = _sumSuperVaultValues(); |
|
| 62 |
✓ 2.7M
|
_before.naivePPS = _calculateNaivePPS(); |
|
| 63 |
✓ 2.7M
|
_before.summedTotalShares = _sumTotalShares(); |
|
| 64 |
✓ 2.7M
|
_before.summedTotalAssets = _sumStrategyAssets(); |
|
| 65 |
✓ 2.7M
|
_before.summedPendingRedeem = _sumRequestedRedemptions(); |
|
| 66 | |||
| 67 |
✓ 2.7M
|
_before.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
|
| 68 |
✓ 2.7M
|
_before.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
|
| 69 |
✓ 2.7M
|
_before.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
|
| 70 |
✓ 2.7M
|
_getActor() |
|
| 71 |
); |
||
| 72 |
✓ 2.7M
|
_before.superVaultShares[_getActor()] = superVault.balanceOf( |
|
| 73 |
✓ 2.7M
|
_getActor() |
|
| 74 |
); |
||
| 75 | |||
| 76 |
✓ 2.7M
|
_before.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
|
| 77 |
✓ 2.7M
|
address(superVaultStrategy) |
|
| 78 |
); |
||
| 79 |
✓ 2.7M
|
_before.oraclePPS = superVaultAggregator.getPPS( |
|
| 80 |
✓ 2.7M
|
address(superVaultStrategy) |
|
| 81 |
); |
||
| 82 |
} |
||
| 83 | |||
| 84 |
function __after() internal {
|
||
| 85 |
( |
||
| 86 |
✓ 1.0M
|
_after.summedAccumulatorShares, |
|
| 87 |
✓ 1.0M
|
_after.summedAccumulatorCostBasis |
|
| 88 |
✓ 1.0M
|
) = _sumSuperVaultValues(); |
|
| 89 |
✓ 1.0M
|
_after.naivePPS = _calculateNaivePPS(); |
|
| 90 |
✓ 1.0M
|
_after.summedTotalShares = _sumTotalShares(); |
|
| 91 |
✓ 1.0M
|
_after.summedTotalAssets = _sumStrategyAssets(); |
|
| 92 |
✓ 1.0M
|
_after.summedPendingRedeem = _sumRequestedRedemptions(); |
|
| 93 | |||
| 94 |
✓ 1.0M
|
_after.pendingUserAssets[_getActor()] = _getPendingAsAssets(); |
|
| 95 |
✓ 1.0M
|
_after.claimableUserAssets[_getActor()] = _getClaimableAsAssets(); |
|
| 96 |
✓ 1.0M
|
_after.state[_getActor()] = superVaultStrategy.getSuperVaultState( |
|
| 97 |
✓ 1.0M
|
_getActor() |
|
| 98 |
); |
||
| 99 |
✓ 1.0M
|
_after.superVaultShares[_getActor()] = superVault.balanceOf( |
|
| 100 |
✓ 1.0M
|
_getActor() |
|
| 101 |
); |
||
| 102 | |||
| 103 |
✓ 1.0M
|
_after.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf( |
|
| 104 |
✓ 1.0M
|
address(superVaultStrategy) |
|
| 105 |
); |
||
| 106 |
✓ 1.0M
|
_after.oraclePPS = superVaultAggregator.getPPS( |
|
| 107 |
✓ 1.0M
|
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 |
✓ 3.8M
|
function _sumTotalShares() internal returns (uint256) {
|
|
| 115 |
✓ 3.8M
|
address[] memory actors = _getActors(); |
|
| 116 |
✓ 3.8M
|
uint256 totalShares; |
|
| 117 | |||
| 118 |
✓ 3.8M
|
totalShares += superVault.balanceOf(address(superVaultEscrow)); |
|
| 119 |
✓ 15.1M
|
for (uint256 i; i < actors.length; i++) {
|
|
| 120 |
✓ 11.3M
|
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 |
✓ 3.8M
|
function _calculateNaivePPS() internal returns (uint256 naivePPS) {
|
|
| 130 |
// Get total supply of SuperVault shares |
||
| 131 |
✓ 3.8M
|
uint256 totalSupply = superVault.totalSupply(); |
|
| 132 | |||
| 133 |
// If no shares exist, PPS is 0 |
||
| 134 |
✓ 3.8M
|
if (totalSupply == 0) {
|
|
| 135 |
✓ 2.8M
|
return 0; |
|
| 136 |
} |
||
| 137 | |||
| 138 |
// Calculate total assets across all locations |
||
| 139 |
✓ 941.7K
|
uint256 totalAssets = _sumStrategyAssets(); |
|
| 140 | |||
| 141 |
// Calculate naive PPS: (totalAssets * PRECISION) / totalSupply |
||
| 142 |
// Using 1e18 as precision to match the system's PPS_DECIMALS |
||
| 143 |
✓ 941.7K
|
naivePPS = (totalAssets * superVault.PRECISION()) / totalSupply; |
|
| 144 | |||
| 145 |
return naivePPS; |
||
| 146 |
} |
||
| 147 | |||
| 148 |
✓ 4.7M
|
function _sumStrategyAssets() public view returns (uint256) {
|
|
| 149 |
// Get the underlying asset |
||
| 150 |
✓ 4.7M
|
address asset = superVault.asset(); |
|
| 151 | |||
| 152 |
✓ 4.7M
|
uint256 totalAssets; |
|
| 153 |
// 1. Assets held directly in SuperVaultStrategy |
||
| 154 |
✓ 4.7M
|
totalAssets += IERC20(asset).balanceOf(address(superVaultStrategy)); |
|
| 155 | |||
| 156 |
// 2. Loop through all yield sources and sum underlying asset balances |
||
| 157 |
✓ 4.7M
|
address[] memory yieldSources = _getYieldSources(); |
|
| 158 |
✓ 20.1M
|
for (uint256 i = 0; i < yieldSources.length; i++) {
|
|
| 159 |
✓ 15.4M
|
if (yieldSources[i] != address(0)) {
|
|
| 160 |
// Get the underlying asset balance held in each yield source |
||
| 161 |
✓ 15.4M
|
totalAssets += IERC20(asset).balanceOf(yieldSources[i]); |
|
| 162 |
} |
||
| 163 |
} |
||
| 164 | |||
| 165 |
✓ 4.7M
|
return totalAssets; |
|
| 166 |
} |
||
| 167 | |||
| 168 |
function _sumSuperVaultValues() |
||
| 169 |
internal |
||
| 170 |
view |
||
| 171 |
✓ 3.8M
|
returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis) |
|
| 172 |
{
|
||
| 173 |
✓ 3.8M
|
address[] memory actors = _getActors(); |
|
| 174 | |||
| 175 |
✓ 15.1M
|
for (uint256 i; i < actors.length; i++) {
|
|
| 176 |
✓ 11.3M
|
sumAccumulatorShares += superVaultStrategy |
|
| 177 |
✓ 11.3M
|
.getSuperVaultState(actors[i]) |
|
| 178 |
.accumulatorShares; |
||
| 179 |
✓ 11.3M
|
sumAccumulatorCostBasis += superVaultStrategy |
|
| 180 |
✓ 11.3M
|
.getSuperVaultState(actors[i]) |
|
| 181 |
.accumulatorCostBasis; |
||
| 182 |
} |
||
| 183 |
} |
||
| 184 | |||
| 185 |
✓ 3.8M
|
function _sumRequestedRedemptions() internal returns (uint256) {
|
|
| 186 |
✓ 3.8M
|
address[] memory actors = _getActors(); |
|
| 187 |
✓ 3.8M
|
uint256 totalRequested; |
|
| 188 |
✓ 15.1M
|
for (uint256 i; i < actors.length; i++) {
|
|
| 189 |
✓ 11.3M
|
totalRequested += superVault.pendingRedeemRequest(0, actors[i]); |
|
| 190 |
} |
||
| 191 | |||
| 192 |
return totalRequested; |
||
| 193 |
} |
||
| 194 | |||
| 195 |
✓ 3.8M
|
function _getPendingAsAssets() internal returns (uint256) {
|
|
| 196 |
✓ 3.8M
|
uint256 pendingRedemptions = superVault.pendingRedeemRequest( |
|
| 197 |
0, |
||
| 198 |
✓ 7.5M
|
_getActor() |
|
| 199 |
); |
||
| 200 |
✓ 7.5M
|
uint256 pendingRedemptionsAsAssets = superVault.convertToAssets( |
|
| 201 |
pendingRedemptions |
||
| 202 |
); |
||
| 203 | |||
| 204 |
return pendingRedemptionsAsAssets; |
||
| 205 |
} |
||
| 206 | |||
| 207 |
✓ 3.8M
|
function _getClaimableAsAssets() internal returns (uint256) {
|
|
| 208 |
✓ 3.8M
|
uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
|
| 209 |
0, |
||
| 210 |
✓ 3.8M
|
_getActor() |
|
| 211 |
); |
||
| 212 |
uint256 claimableRedemptionsAsAssets = superVault.convertToAssets( |
||
| 213 |
claimableRedemptions |
||
| 214 |
); |
||
| 215 | |||
| 216 |
return claimableRedemptionsAsAssets; |
||
| 217 |
} |
||
| 218 |
} |
||
| 219 |
100.0%
test/recon/CryticTester.sol
Lines covered: 2 / 2 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {CryticAsserts} from "@chimera/CryticAsserts.sol";
|
||
| 5 | |||
| 6 |
import {TargetFunctions} from "./TargetFunctions.sol";
|
||
| 7 | |||
| 8 |
// echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml --format text --test-limit 1000000 --disable-slither |
||
| 9 |
// medusa fuzz |
||
| 10 |
✓ 21.0M
|
⟲ 32.9K
|
contract CryticTester is TargetFunctions, CryticAsserts {
|
| 11 |
constructor() payable {
|
||
| 12 |
✓ 1
|
setup(); |
|
| 13 |
} |
||
| 14 |
} |
||
| 15 |
29.1%
test/recon/Properties.sol
Lines covered: 94 / 323 (29.1%)
| 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 |
✓ 1
|
uint256 internal TOLERANCE = 10; |
|
| 18 | |||
| 19 |
// `!!!`-prefixed reasons are load-bearing for cross-fuzzer bug dedup in the analysis |
||
| 20 |
// pipeline. Handler assertion failures surface natively on every fuzzer: as Panic(0x01) |
||
| 21 |
// under echidna/medusa/recon and as recorded assertion failures under Foundry |
||
| 22 |
// (`assertions_revert = false`, upstream handler-bug reporting). |
||
| 23 |
string constant ASSERTION_REDEEM_MAX_REDEEM_SHOULD_NOT_REVERT = |
||
| 24 |
"!!! redeeming maxRedeem should not revert"; |
||
| 25 |
string constant ASSERTION_WITHDRAW_MAX_WITHDRAW_SHOULD_NOT_REVERT = |
||
| 26 |
"!!! withdraw of maxWithdraw should not revert"; |
||
| 27 |
string constant ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE = |
||
| 28 |
"!!! previewDeposit and deposit equivalence"; |
||
| 29 |
string constant ASSERTION_PREVIEW_MINT_EQUIVALENCE = |
||
| 30 |
"!!! previewMint and mint equivalence"; |
||
| 31 |
string constant ASSERTION_MINT_REDEEM_SYMMETRICAL = |
||
| 32 |
"!!! user loses assets in mint/redeem flow"; |
||
| 33 |
string constant ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL = |
||
| 34 |
"!!! user loses assets in deposit/withdraw flow"; |
||
| 35 |
string constant ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION = |
||
| 36 |
"!!! maxRedeem should be reset to 0 after full redemption"; |
||
| 37 |
string constant ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL = |
||
| 38 |
"!!! maxWithdraw should be reset to 0 after full withdrawal"; |
||
| 39 |
string constant ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS = |
||
| 40 |
"!!! total shares redeemed must not exceed sum of requested shares"; |
||
| 41 |
string constant ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE = |
||
| 42 |
"!!! Primary manager should always be changeable if not paused"; |
||
| 43 |
string constant ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED = |
||
| 44 |
"!!! users should always be able to withdraw unless the system is paused"; |
||
| 45 |
string constant ASSERTION_CANCEL_REDEEM_PENDING_REQUEST_ZERO = |
||
| 46 |
"!!! pendingRedeemRequests should be 0 after cancelling a redemption"; |
||
| 47 |
string constant ASSERTION_CANCEL_REDEEM_AVG_REQUEST_PPS_ZERO = |
||
| 48 |
"!!! averageRequestPPS should be 0 after cancelling a redemption"; |
||
| 49 |
string constant ASSERTION_CANCEL_REDEEM_NO_OVERPAY = |
||
| 50 |
"!!! user should not receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem"; |
||
| 51 |
string constant ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION = |
||
| 52 |
"!!! previewDeposit returns the correct amount compared to executing a deposit"; |
||
| 53 |
string constant ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION = |
||
| 54 |
"!!! previewMint returns the correct amount compared to executing a mint"; |
||
| 55 |
string constant ASSERTION_TRANSFER_SHARES_CONSERVED = |
||
| 56 |
"!!! shares are lost on transfer"; |
||
| 57 |
string constant ASSERTION_TRANSFER_COST_BASIS_CONSERVED = |
||
| 58 |
"!!! cost basis is lost on transfer"; |
||
| 59 |
string constant ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM = |
||
| 60 |
"!!! Claiming redemptions should never revert with INVALID_REDEEM_CLAIM"; |
||
| 61 |
string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER = |
||
| 62 |
"!!! _update should never revert in transfer"; |
||
| 63 |
string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM = |
||
| 64 |
"!!! _update should never revert in transferFrom"; |
||
| 65 |
string constant ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT = |
||
| 66 |
"!!! strategy incurs loss on fulfillment"; |
||
| 67 |
string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES = |
||
| 68 |
"!!! previewMint and previewDeposit equivalence (from shares)"; |
||
| 69 |
string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS = |
||
| 70 |
"!!! previewMint and previewDeposit equivalence (from assets)"; |
||
| 71 |
string constant ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS = |
||
| 72 |
"!!! previewMint is >= convertToAssets"; |
||
| 73 |
string constant ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT = |
||
| 74 |
"!!! convertToShares is >= previewDepositShares (equivalent without fees)"; |
||
| 75 |
string constant ASSERTION_ERC7540_4_DEPOSIT = |
||
| 76 |
"!!! ERC7540-4: deposit with more than max should revert"; |
||
| 77 |
string constant ASSERTION_ERC7540_4_MINT = |
||
| 78 |
"!!! ERC7540-4: mint with more than max should revert"; |
||
| 79 |
string constant ASSERTION_ERC7540_4_WITHDRAW = |
||
| 80 |
"!!! ERC7540-4: withdraw with more than max should revert"; |
||
| 81 |
string constant ASSERTION_ERC7540_4_REDEEM = |
||
| 82 |
"!!! ERC7540-4: redeem with more than max should revert"; |
||
| 83 |
string constant ASSERTION_ERC7540_5 = |
||
| 84 |
"!!! ERC7540-5: requestRedeem should revert if insufficient share balance"; |
||
| 85 |
string constant ASSERTION_ERC7540_7_WITHDRAW = |
||
| 86 |
"!!! ERC7540-7: withdraw should not revert when amount <= max"; |
||
| 87 |
string constant ASSERTION_ERC7540_7_REDEEM = |
||
| 88 |
"!!! ERC7540-7: redeem should not revert when amount <= max"; |
||
| 89 |
string constant ASSERTION_CANARY = |
||
| 90 |
"!!! canary assertion"; |
||
| 91 |
string constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE = |
||
| 92 |
"Canary invariant"; |
||
| 93 | |||
| 94 |
/// @dev Property: oracle PPS doesn't change on deposit/mint/redeem/withdraw |
||
| 95 |
function invariant_oraclePPSDoesntChangeOnAddOrRemove() public returns (bool) {
|
||
| 96 |
if (_currentOp == OpType.ADD || _currentOp == OpType.REMOVE) {
|
||
| 97 |
eq( |
||
| 98 |
_before.oraclePPS, |
||
| 99 |
_after.oraclePPS, |
||
| 100 |
"deposit/withdrawal changes oracle PPS" |
||
| 101 |
); |
||
| 102 |
} |
||
| 103 |
return true; |
||
| 104 |
} |
||
| 105 | |||
| 106 |
/// @dev Property: naive PPS doesn't change on deposit/mint |
||
| 107 |
// NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
||
| 108 |
// function property_naivePPSDoesntChangeOnDepositOrMint() public {
|
||
| 109 |
// if ( |
||
| 110 |
// (_currentOp == OpType.ADD) && _before.naivePPS != 0 // price starts as zero when no shares minted |
||
| 111 |
// ) {
|
||
| 112 |
// gte( |
||
| 113 |
// _after.naivePPS, |
||
| 114 |
// _before.naivePPS, |
||
| 115 |
// "deposit/mint cannot decrease naive PPS" |
||
| 116 |
// ); |
||
| 117 |
// } |
||
| 118 |
// } |
||
| 119 | |||
| 120 |
/// @dev Property: naive PPS doesn't change on redeem/withdraw |
||
| 121 |
// NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
||
| 122 |
// function property_naivePPSDoesntChangeOnRedeemOrWithdraw() public {
|
||
| 123 |
// if ( |
||
| 124 |
// (_currentOp == OpType.REMOVE) && _before.naivePPS != 0 // price starts as zero when no shares minted |
||
| 125 |
// ) {
|
||
| 126 |
// gte( |
||
| 127 |
// _after.naivePPS, |
||
| 128 |
// _before.naivePPS, |
||
| 129 |
// "redeem/withdraw cannot decrease naive PPS" |
||
| 130 |
// ); |
||
| 131 |
// } |
||
| 132 |
// } |
||
| 133 | |||
| 134 |
/// @dev Property: fulfillRedeemRequest doesn't change naive PPS |
||
| 135 |
// NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price |
||
| 136 |
// function property_naivePPSDoesntChangeOnRedeem() public {
|
||
| 137 |
// if (_currentOp == OpType.FULFILL) {
|
||
| 138 |
// eq( |
||
| 139 |
// _before.naivePPS, |
||
| 140 |
// _after.naivePPS, |
||
| 141 |
// "fulfilling redemption changes naive PPS" |
||
| 142 |
// ); |
||
| 143 |
// } |
||
| 144 |
// } |
||
| 145 | |||
| 146 |
/// @dev Property: maxRedeem and maxWithdraw should always be equivalent |
||
| 147 |
function invariant_maxRedeemMaxWithdrawSymmetry() public returns (bool) {
|
||
| 148 |
uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
||
| 149 |
// convertToShares uses current price instead of avg from fulfillment |
||
| 150 |
uint256 withdrawPrice = superVaultStrategy.getAverageWithdrawPrice( |
||
| 151 |
_getActor() |
||
| 152 |
); |
||
| 153 |
uint256 maxWithdrawAsShares = maxWithdraw.mulDiv( |
||
| 154 |
superVault.PRECISION(), |
||
| 155 |
withdrawPrice, |
||
| 156 |
Math.Rounding.Floor |
||
| 157 |
); |
||
| 158 | |||
| 159 |
uint256 maxRedeem = superVault.maxRedeem(_getActor()); |
||
| 160 | |||
| 161 |
eq(maxWithdrawAsShares, maxRedeem, "maxWithdrawAsShares != maxRedeem"); |
||
| 162 |
return true; |
||
| 163 |
} |
||
| 164 | |||
| 165 |
/// @dev Property: requestRedeem should never reduce SuperVault shares |
||
| 166 |
function invariant_totalSharesDontDecreaseOnRedemptionRequest() public returns (bool) {
|
||
| 167 |
if (_currentOp == OpType.REQUEST) {
|
||
| 168 |
eq( |
||
| 169 |
_before.summedTotalShares, |
||
| 170 |
_after.summedTotalShares, |
||
| 171 |
"requestRedeem should never reduce SuperVault shares" |
||
| 172 |
); |
||
| 173 |
} |
||
| 174 |
return true; |
||
| 175 |
} |
||
| 176 | |||
| 177 |
/// @dev Property: `SuperVault::totalSupply` == SUM(user balances) + balanceOf(escrow) |
||
| 178 |
function invariant_shareSolvency() public returns (bool) {
|
||
| 179 |
uint256 sumOfShares = _sumTotalShares(); |
||
| 180 |
eq(superVault.totalSupply(), sumOfShares, "vault shares are insolvent"); |
||
| 181 |
return true; |
||
| 182 |
} |
||
| 183 | |||
| 184 |
/// @dev Property: balanceOf(escrow) >= SUM(controllers.pendingRedeemRequest) |
||
| 185 |
function invariant_escrowBalance() public returns (bool) {
|
||
| 186 |
address[] memory actors = _getActors(); |
||
| 187 | |||
| 188 |
uint256 escrowBalance = superVault.balanceOf(address(superVaultEscrow)); |
||
| 189 |
uint256 pendingActorShares; |
||
| 190 |
for (uint256 i; i < actors.length; i++) {
|
||
| 191 |
pendingActorShares += superVault.pendingRedeemRequest(0, actors[i]); |
||
| 192 |
} |
||
| 193 | |||
| 194 |
gte( |
||
| 195 |
escrowBalance, |
||
| 196 |
pendingActorShares, |
||
| 197 |
"balanceOf(escrow) < SUM(controllers.pendingRedeemRequest)" |
||
| 198 |
); |
||
| 199 |
return true; |
||
| 200 |
} |
||
| 201 | |||
| 202 |
/// @dev Property: maxMint should be 0 when aggregator is paused |
||
| 203 |
function invariant_maxMintZeroWhenPaused() public returns (bool) {
|
||
| 204 |
bool paused = superVaultAggregator.isStrategyPaused( |
||
| 205 |
address(superVaultStrategy) |
||
| 206 |
); |
||
| 207 |
uint256 maxMint = superVault.maxMint(_getActor()); |
||
| 208 | |||
| 209 |
if (paused) {
|
||
| 210 |
eq(maxMint, 0, "actor has nonzero maxMint when strategy is paused"); |
||
| 211 |
} |
||
| 212 |
return true; |
||
| 213 |
} |
||
| 214 | |||
| 215 |
/// @dev Property: maxDeposit should be 0 when strategy is paused |
||
| 216 |
function invariant_maxDepositZeroWhenPaused() public returns (bool) {
|
||
| 217 |
bool paused = superVaultAggregator.isStrategyPaused( |
||
| 218 |
address(superVaultStrategy) |
||
| 219 |
); |
||
| 220 |
uint256 maxDeposit = superVault.maxDeposit(_getActor()); |
||
| 221 | |||
| 222 |
if (paused) {
|
||
| 223 |
eq( |
||
| 224 |
maxDeposit, |
||
| 225 |
0, |
||
| 226 |
"actor has nonzero maxDeposit when strategy is paused" |
||
| 227 |
); |
||
| 228 |
} |
||
| 229 |
return true; |
||
| 230 |
} |
||
| 231 | |||
| 232 |
/// @dev Property: SUM(accumulatorShares) doesn't change on SuperVault share transfers |
||
| 233 |
function invariant_accumulatorSharesSolvency() public returns (bool) {
|
||
| 234 |
if (_currentOp == OpType.TRANSFER) {
|
||
| 235 |
eq( |
||
| 236 |
_before.summedAccumulatorShares, |
||
| 237 |
_after.summedAccumulatorShares, // TODO: think about way to handle transfer on recipient |
||
| 238 |
"SUM(accumulatorShares) changed on SuperVault share transfers" |
||
| 239 |
); |
||
| 240 |
} |
||
| 241 |
return true; |
||
| 242 |
} |
||
| 243 | |||
| 244 |
/// @dev Property: SUM(accumulatorCostBasis) doesn't change on SuperVault share transfers |
||
| 245 |
function invariant_accumulatorCostBasisSolvency() public returns (bool) {
|
||
| 246 |
if (_currentOp == OpType.TRANSFER) {
|
||
| 247 |
eq( |
||
| 248 |
_before.summedAccumulatorCostBasis, |
||
| 249 |
_after.summedAccumulatorCostBasis, |
||
| 250 |
"SUM(accumulatorCostBasis) changed on SuperVault share transfers" |
||
| 251 |
); |
||
| 252 |
} |
||
| 253 |
return true; |
||
| 254 |
} |
||
| 255 | |||
| 256 |
/// @dev Property: cancelRedeem should never alter the supply of SuperVault tokens |
||
| 257 |
function invariant_cancelDoesntChangeTotalSupply() public returns (bool) {
|
||
| 258 |
if (_currentOp == OpType.CANCEL) {
|
||
| 259 |
eq( |
||
| 260 |
_before.summedTotalShares, |
||
| 261 |
_after.summedTotalShares, |
||
| 262 |
"cancelRedeem should never alter the supply of SuperVault tokens" |
||
| 263 |
); |
||
| 264 |
} |
||
| 265 |
return true; |
||
| 266 |
} |
||
| 267 | |||
| 268 |
/// @dev Property: if totalSupply > 0, then totalAssets > 0 |
||
| 269 |
function invariant_assetBacking() public returns (bool) {
|
||
| 270 |
uint256 summedTotalAssets = _sumStrategyAssets(); |
||
| 271 | |||
| 272 |
if (superVault.totalSupply() > 0) {
|
||
| 273 |
gt( |
||
| 274 |
summedTotalAssets, |
||
| 275 |
0, |
||
| 276 |
"if totalSupply > 0, then totalAssets > 0" |
||
| 277 |
); |
||
| 278 |
} |
||
| 279 |
return true; |
||
| 280 |
} |
||
| 281 | |||
| 282 |
/// @dev Property: SUM(shares) * PPS == totalAssets |
||
| 283 |
function invariant_totalAssets() public returns (bool) {
|
||
| 284 |
uint256 totalShares = _sumTotalShares(); |
||
| 285 |
uint256 pps = superVaultAggregator.getPPS(address(superVaultStrategy)); |
||
| 286 |
uint256 implicitTotalAssets = (totalShares * pps) / |
||
| 287 |
superVault.PRECISION(); |
||
| 288 | |||
| 289 |
uint256 vaultTotalAssets = superVault.totalAssets(); |
||
| 290 |
eq( |
||
| 291 |
implicitTotalAssets, |
||
| 292 |
vaultTotalAssets, |
||
| 293 |
"totalShares * pps != totalAssets" |
||
| 294 |
); |
||
| 295 |
return true; |
||
| 296 |
} |
||
| 297 | |||
| 298 |
/// @dev Property: When a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease |
||
| 299 |
function invariant_avgPPSDoesntDecrease() public returns (bool) {
|
||
| 300 |
uint256 currentPrice = _before.oraclePPS; |
||
| 301 |
uint256 beforeAvgPPS = _before.state[_getActor()].averageRequestPPS; |
||
| 302 |
uint256 afterAvgPPS = _after.state[_getActor()].averageRequestPPS; |
||
| 303 | |||
| 304 |
if (_currentOp == OpType.REQUEST && currentPrice >= beforeAvgPPS) {
|
||
| 305 |
gte( |
||
| 306 |
afterAvgPPS, |
||
| 307 |
beforeAvgPPS, |
||
| 308 |
"when a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease" |
||
| 309 |
); |
||
| 310 |
} |
||
| 311 |
return true; |
||
| 312 |
} |
||
| 313 | |||
| 314 |
/// @dev Property: previewMint and previewDeposit equivalence (from shares) |
||
| 315 |
function global_previewEquivalenceFromShares_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES(uint256 shares) public {
|
||
| 316 |
✓ 137.2K
|
uint256 previewMintAssets = superVault.previewMint(shares); |
|
| 317 |
✓ 103.1K
|
uint256 previewDepositShares = superVault.previewDeposit( |
|
| 318 |
previewMintAssets |
||
| 319 |
); |
||
| 320 |
✓ 102.6K
|
uint256 price = superVaultStrategy.getStoredPPS(); |
|
| 321 | |||
| 322 |
✓ 102.6K
|
if (price > 0) {
|
|
| 323 |
✓ 100.6K
|
eq( |
|
| 324 |
✓ 100.6K
|
shares, |
|
| 325 |
✓ 100.6K
|
previewDepositShares, |
|
| 326 |
✓ 100.6K
|
ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES |
|
| 327 |
); |
||
| 328 |
} |
||
| 329 |
} |
||
| 330 | |||
| 331 |
/// @dev Property: previewMint and previewDeposit equivalence (from assets) |
||
| 332 |
function global_previewEquivalenceFromAssets_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS(uint256 assets) public {
|
||
| 333 |
✓ 203.6K
|
uint256 previewDepositShares = superVault.previewDeposit(assets); |
|
| 334 |
✓ 200.9K
|
uint256 previewMintAssets_under = superVault.previewMint( |
|
| 335 |
previewDepositShares |
||
| 336 |
); |
||
| 337 |
✓ 200.9K
|
uint256 previewMintAssets_over = superVault.previewMint( |
|
| 338 |
✓ 200.9K
|
previewDepositShares + 1 |
|
| 339 |
); |
||
| 340 |
✓ 198.4K
|
uint256 price = superVaultStrategy.getStoredPPS(); |
|
| 341 | |||
| 342 |
✓ 198.4K
|
if (price > 0) {
|
|
| 343 |
✓ 197.4K
|
gte( |
|
| 344 |
✓ 197.4K
|
assets, |
|
| 345 |
✓ 197.4K
|
previewMintAssets_under, |
|
| 346 |
✓ 197.4K
|
ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
|
| 347 |
); |
||
| 348 | |||
| 349 |
✓ 197.4K
|
lte( |
|
| 350 |
✓ 197.4K
|
assets, |
|
| 351 |
✓ 197.4K
|
previewMintAssets_over, |
|
| 352 |
✓ 197.4K
|
ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS |
|
| 353 |
); |
||
| 354 |
} |
||
| 355 |
} |
||
| 356 | |||
| 357 |
/// @dev Property: previewMint is >= convertToAssets |
||
| 358 |
function global_comparePreviewMintAndConvertToAssets_ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS( |
||
| 359 |
uint256 shares |
||
| 360 |
) public {
|
||
| 361 |
✓ 142.5K
|
uint256 previewMintAssets = superVault.previewMint(shares); |
|
| 362 |
✓ 108.3K
|
uint256 convertToAssets = superVault.convertToAssets(shares); |
|
| 363 |
✓ 108.3K
|
gte( |
|
| 364 |
✓ 108.3K
|
previewMintAssets, |
|
| 365 |
✓ 108.3K
|
convertToAssets, |
|
| 366 |
✓ 108.3K
|
ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS |
|
| 367 |
); |
||
| 368 |
} |
||
| 369 | |||
| 370 |
/// @dev Property: convertToShares is >= previewDepositShares (equivalent without fees) |
||
| 371 |
function global_comparePreviewDepositAndConvertToShares_ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT( |
||
| 372 |
uint256 assets |
||
| 373 |
) public {
|
||
| 374 |
✓ 150.7K
|
uint256 previewDepositShares = superVault.previewDeposit(assets); |
|
| 375 |
✓ 147.7K
|
uint256 convertToShares = superVault.convertToShares(assets); |
|
| 376 |
✓ 143.2K
|
gte( |
|
| 377 |
✓ 143.2K
|
convertToShares, |
|
| 378 |
✓ 143.2K
|
previewDepositShares, |
|
| 379 |
✓ 143.2K
|
ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT |
|
| 380 |
); |
||
| 381 |
} |
||
| 382 | |||
| 383 |
// NOTE: Withdrawal properties are more nuanced, as we should ensure that no gain can be had and that losses are imputed to the controller receiving the assets |
||
| 384 | |||
| 385 |
/// @dev Property: After all redemptions are processed, the sum of all claimable is <= balance available |
||
| 386 |
function invariant_sumOfClaimable() public returns (bool) {
|
||
| 387 |
address[] memory actors = _getActors(); |
||
| 388 | |||
| 389 |
uint256 sumPending; |
||
| 390 |
uint256 sumClaimable; |
||
| 391 |
for (uint256 i; i < actors.length; i++) {
|
||
| 392 |
sumPending += superVault.pendingRedeemRequest(0, actors[i]); |
||
| 393 |
sumClaimable += superVault.maxWithdraw(actors[i]); |
||
| 394 |
} |
||
| 395 | |||
| 396 |
uint256 strategyBalance = MockERC20(superVault.asset()).balanceOf( |
||
| 397 |
address(superVaultStrategy) |
||
| 398 |
); |
||
| 399 | |||
| 400 |
// precondition: all pending has been fulfilled |
||
| 401 |
if (sumPending == 0) {
|
||
| 402 |
lte( |
||
| 403 |
sumClaimable, |
||
| 404 |
strategyBalance, |
||
| 405 |
"sum of all claimable is > balance available" |
||
| 406 |
); |
||
| 407 |
} |
||
| 408 |
return true; |
||
| 409 |
} |
||
| 410 | |||
| 411 |
/// @dev Property: If the sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0 |
||
| 412 |
function invariant_sumOfAssetsMaxWithdrawable() public returns (bool) {
|
||
| 413 |
uint256 summedTotalAssets = _sumStrategyAssets(); |
||
| 414 | |||
| 415 |
if (summedTotalAssets == 0) {
|
||
| 416 |
uint256 maxWithdraw = superVault.maxWithdraw(_getActor()); |
||
| 417 |
eq( |
||
| 418 |
maxWithdraw, |
||
| 419 |
0, |
||
| 420 |
"if sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0" |
||
| 421 |
); |
||
| 422 |
} |
||
| 423 |
return true; |
||
| 424 |
} |
||
| 425 | |||
| 426 |
/// @dev Property: averageWithdrawPrice should never decrease when new redemptions are fulfilled at a higher PPS |
||
| 427 |
function invariant_avgPPSMonotonicity() public returns (bool) {
|
||
| 428 |
if ( |
||
| 429 |
_currentOp == OpType.FULFILL && |
||
| 430 |
_before.oraclePPS > _before.state[_getActor()].averageRequestPPS && // fulfilled at a higher price |
||
| 431 |
_after.state[_getActor()].pendingRedeemRequest != 0 // redemptions have all been fulfilled/cancelled; avg gets reset to 0 in this case |
||
| 432 |
) {
|
||
| 433 |
gte( |
||
| 434 |
_after.state[_getActor()].averageRequestPPS, |
||
| 435 |
_before.state[_getActor()].averageRequestPPS, |
||
| 436 |
"averageWithdrawPrice should not decrease when fulfilled at a higher PPS" |
||
| 437 |
); |
||
| 438 |
} |
||
| 439 |
return true; |
||
| 440 |
} |
||
| 441 | |||
| 442 |
/// @dev Property: state.accumulatorShares >= superVaultState[controllers[i]].pendingRedeemRequest for each user |
||
| 443 |
function invariant_accumulatorSharesGtPendingRequests() public returns (bool) {
|
||
| 444 |
address[] memory actors = _getActors(); |
||
| 445 | |||
| 446 |
for (uint256 i; i < actors.length; i++) {
|
||
| 447 |
ISuperVaultStrategy.SuperVaultState |
||
| 448 |
memory state = superVaultStrategy.getSuperVaultState(actors[i]); |
||
| 449 |
gte( |
||
| 450 |
state.accumulatorShares, |
||
| 451 |
state.pendingRedeemRequest, |
||
| 452 |
"state.accumulatorShares < state.pendingRedeemRequest" |
||
| 453 |
); |
||
| 454 |
} |
||
| 455 |
return true; |
||
| 456 |
} |
||
| 457 | |||
| 458 |
/// @dev Property: accumulatorShares is always accurately increased |
||
| 459 |
function invariant_accumulatorSharesIncrease() public returns (bool) {
|
||
| 460 |
if (_currentOp == OpType.ADD) {
|
||
| 461 |
uint256 accumulatorSharesBefore = _before |
||
| 462 |
.state[_getActor()] |
||
| 463 |
.accumulatorShares; |
||
| 464 |
uint256 accumulatorSharesAfter = _after |
||
| 465 |
.state[_getActor()] |
||
| 466 |
.accumulatorShares; |
||
| 467 |
uint256 actorSharesBefore = _before.superVaultShares[_getActor()]; |
||
| 468 |
uint256 actorSharesAfter = _after.superVaultShares[_getActor()]; |
||
| 469 |
eq( |
||
| 470 |
accumulatorSharesAfter - accumulatorSharesBefore, |
||
| 471 |
actorSharesAfter - actorSharesBefore, |
||
| 472 |
"accumulatorShares is always accurately updated" |
||
| 473 |
); |
||
| 474 |
} |
||
| 475 |
return true; |
||
| 476 |
} |
||
| 477 | |||
| 478 |
/// @dev Property: accumulatorCostBasis is always accurately increased |
||
| 479 |
function invariant_accumulatorCostBasisIncrease() public returns (bool) {
|
||
| 480 |
if (_currentOp == OpType.ADD) {
|
||
| 481 |
uint256 accumulatorCostBasisBefore = _before |
||
| 482 |
.state[_getActor()] |
||
| 483 |
.accumulatorCostBasis; |
||
| 484 |
uint256 accumulatorCostBasisAfter = _after |
||
| 485 |
.state[_getActor()] |
||
| 486 |
.accumulatorCostBasis; |
||
| 487 |
eq( |
||
| 488 |
accumulatorCostBasisAfter - accumulatorCostBasisBefore, |
||
| 489 |
_after.strategyAssetBalance - _before.strategyAssetBalance, |
||
| 490 |
"accumulatorShares is always accurately updated" |
||
| 491 |
); |
||
| 492 |
} |
||
| 493 |
return true; |
||
| 494 |
} |
||
| 495 | |||
| 496 |
/// @dev Property: redemptions only burn the requested amount of shares (within tolerance range) |
||
| 497 |
function invariant_fulfillOnlyBurnsRequestedAmount() public returns (bool) {
|
||
| 498 |
uint256 TOLERANCE_CONSTANT = 10 wei; // taken from SuperVaultStrategy |
||
| 499 | |||
| 500 |
if (_currentOp == OpType.FULFILL) {
|
||
| 501 |
uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
||
| 502 |
_after.summedPendingRedeem; |
||
| 503 |
uint256 totalSupplyDelta = _before.summedTotalShares - |
||
| 504 |
_after.summedTotalShares; |
||
| 505 | |||
| 506 |
// Check that burned amount is within tolerance of requested amount |
||
| 507 |
if (totalSupplyDelta < pendingRedeemDelta) {
|
||
| 508 |
// Burned less than requested - check within tolerance |
||
| 509 |
gte( |
||
| 510 |
totalSupplyDelta, |
||
| 511 |
pendingRedeemDelta - TOLERANCE_CONSTANT, |
||
| 512 |
"burned less than requested beyond tolerance" |
||
| 513 |
); |
||
| 514 |
} else {
|
||
| 515 |
// Burned more than requested - check within tolerance |
||
| 516 |
lte( |
||
| 517 |
totalSupplyDelta, |
||
| 518 |
pendingRedeemDelta + TOLERANCE_CONSTANT, |
||
| 519 |
"burned more than requested beyond tolerance" |
||
| 520 |
); |
||
| 521 |
} |
||
| 522 |
} |
||
| 523 |
return true; |
||
| 524 |
} |
||
| 525 | |||
| 526 |
/// @dev Property: accumulatorShares decreases by the exact amounts requested when fulfilling redemptions |
||
| 527 |
function invariant_accumulatorSharesDecreaseOnFulfill_exact() public returns (bool) {
|
||
| 528 |
if (_currentOp == OpType.FULFILL) {
|
||
| 529 |
uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
||
| 530 |
_after.summedAccumulatorShares; |
||
| 531 |
uint256 totalSharesDelta = _before.summedTotalShares - |
||
| 532 |
_after.summedTotalShares; |
||
| 533 |
eq( |
||
| 534 |
accumulatorSharesDelta, |
||
| 535 |
totalSharesDelta, |
||
| 536 |
"accumulatorShares decreases by the exact amounts requested when fulfilling redemptions" |
||
| 537 |
); |
||
| 538 |
} |
||
| 539 |
return true; |
||
| 540 |
} |
||
| 541 | |||
| 542 |
function invariant_accumulatorSharesDecreaseOnFulfill_with_tolerance() |
||
| 543 |
public returns (bool) |
||
| 544 |
{
|
||
| 545 |
if (_currentOp == OpType.FULFILL) {
|
||
| 546 |
uint256 accumulatorSharesDelta = _before.summedAccumulatorShares - |
||
| 547 |
_after.summedAccumulatorShares; |
||
| 548 |
uint256 totalSharesDelta = _before.summedTotalShares - |
||
| 549 |
_after.summedTotalShares; |
||
| 550 |
if (accumulatorSharesDelta >= totalSharesDelta) {
|
||
| 551 |
lte( |
||
| 552 |
accumulatorSharesDelta - totalSharesDelta, |
||
| 553 |
TOLERANCE, |
||
| 554 |
"accumulatorShares decreases by more than TOLERANCE when fulfilling redemptions" |
||
| 555 |
); |
||
| 556 |
} else {
|
||
| 557 |
lte( |
||
| 558 |
totalSharesDelta - accumulatorSharesDelta, |
||
| 559 |
TOLERANCE, |
||
| 560 |
"accumulatorShares decreases by less than TOLERANCE when fulfilling redemptions" |
||
| 561 |
); |
||
| 562 |
} |
||
| 563 |
} |
||
| 564 |
return true; |
||
| 565 |
} |
||
| 566 | |||
| 567 |
/// Optimization Setters |
||
| 568 | |||
| 569 |
function setpreviewAssetsGreater(uint256 shares) public {
|
||
| 570 |
✓ 147.7K
|
uint256 previewMintAssets = superVault.previewMint(shares); |
|
| 571 |
✓ 108.9K
|
uint256 previewDepositAssets = superVault.previewDeposit( |
|
| 572 |
previewMintAssets |
||
| 573 |
); |
||
| 574 | |||
| 575 |
✓ 108.3K
|
if (previewMintAssets > previewDepositAssets) {
|
|
| 576 |
✓ 95.5K
|
previewMintAssetsGreater = int256(previewMintAssets); |
|
| 577 |
} else {
|
||
| 578 |
✓ 12.8K
|
previewDepositAssetsGreater = int256(previewDepositAssets); |
|
| 579 |
} |
||
| 580 |
} |
||
| 581 | |||
| 582 |
function setPreviewSharesGreater(uint256 assets) public {
|
||
| 583 |
✓ 114.3K
|
uint256 previewDepositShares = superVault.previewDeposit(assets); |
|
| 584 |
✓ 111.6K
|
uint256 previewMintShares = superVault.previewMint( |
|
| 585 |
previewDepositShares |
||
| 586 |
); |
||
| 587 | |||
| 588 |
✓ 111.6K
|
if (previewDepositShares > previewMintShares) {
|
|
| 589 |
✓ 786
|
previewDepositSharesGreater = |
|
| 590 |
✓ 786
|
int256(previewDepositShares) - |
|
| 591 |
✓ 786
|
int256(previewMintShares); |
|
| 592 |
} else {
|
||
| 593 |
✓ 103.7K
|
previewMintSharesGreater = |
|
| 594 |
✓ 110.8K
|
int256(previewMintShares) - |
|
| 595 |
✓ 110.8K
|
int256(previewDepositShares); |
|
| 596 |
} |
||
| 597 |
} |
||
| 598 | |||
| 599 |
function setFulfilledDifference() public {
|
||
| 600 |
✓ 105.6K
|
if (_currentOp == OpType.FULFILL) {
|
|
| 601 |
✓ 945
|
uint256 pendingRedeemDelta = _before.summedPendingRedeem - |
|
| 602 |
✓ 945
|
_after.summedPendingRedeem; |
|
| 603 |
✓ 945
|
uint256 totalSupplyDelta = _before.summedTotalShares - |
|
| 604 |
✓ 945
|
_after.summedTotalShares; |
|
| 605 | |||
| 606 |
// Check that burned amount is within tolerance of requested amount |
||
| 607 |
✓ 945
|
if (totalSupplyDelta < pendingRedeemDelta) {
|
|
| 608 |
// Burned less than requested |
||
| 609 |
✓ 945
|
int256 burnedLessThanRequested = int256(pendingRedeemDelta) - |
|
| 610 |
✓ 945
|
int256(totalSupplyDelta); |
|
| 611 |
} else {
|
||
| 612 |
// Burned more than requested |
||
| 613 |
int256 burnedMoreThanRequested = int256(totalSupplyDelta) - |
||
| 614 |
int256(pendingRedeemDelta); |
||
| 615 |
} |
||
| 616 |
} |
||
| 617 |
} |
||
| 618 | |||
| 619 |
/// Optimization Tests |
||
| 620 | |||
| 621 |
/// @dev Optimize the difference between the amount of assets in the system and claimable assets |
||
| 622 |
function optimize_maxDustAccumulation() public view returns (int256) {
|
||
| 623 |
address[] memory actors = _getActors(); |
||
| 624 | |||
| 625 |
uint256 summedClaimableRedemptionsAsAssets; |
||
| 626 |
for (uint256 i; i < actors.length; i++) {
|
||
| 627 |
uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
||
| 628 |
0, |
||
| 629 |
actors[i] |
||
| 630 |
); |
||
| 631 |
summedClaimableRedemptionsAsAssets += superVault.convertToAssets( |
||
| 632 |
claimableRedemptions |
||
| 633 |
); |
||
| 634 |
} |
||
| 635 | |||
| 636 |
uint256 totalAssets = _sumStrategyAssets(); |
||
| 637 | |||
| 638 |
return int256(totalAssets) - int256(summedClaimableRedemptionsAsAssets); |
||
| 639 |
} |
||
| 640 | |||
| 641 |
/// @dev Optimize the difference between the amount of claimable assets and assets in the system |
||
| 642 |
function optimize_moreClaimableThanHeldDifference() |
||
| 643 |
public |
||
| 644 |
view |
||
| 645 |
returns (int256) |
||
| 646 |
{
|
||
| 647 |
address[] memory actors = _getActors(); |
||
| 648 | |||
| 649 |
uint256 summedClaimableRedemptionsAsAssets; |
||
| 650 |
for (uint256 i; i < actors.length; i++) {
|
||
| 651 |
summedClaimableRedemptionsAsAssets += superVault.maxWithdraw( |
||
| 652 |
actors[i] |
||
| 653 |
); |
||
| 654 |
} |
||
| 655 | |||
| 656 |
uint256 totalAssets = _sumStrategyAssets(); |
||
| 657 | |||
| 658 |
if (summedClaimableRedemptionsAsAssets > totalAssets) {
|
||
| 659 |
return |
||
| 660 |
int256(summedClaimableRedemptionsAsAssets) - |
||
| 661 |
int256(totalAssets); |
||
| 662 |
} |
||
| 663 |
} |
||
| 664 | |||
| 665 |
function optimize_burnMoreThanRequestedInRedemption() |
||
| 666 |
public |
||
| 667 |
view |
||
| 668 |
returns (int256) |
||
| 669 |
{
|
||
| 670 |
return burnedMoreThanRequested; |
||
| 671 |
} |
||
| 672 | |||
| 673 |
function optimize_burnLessThanRequestedInRedemption() |
||
| 674 |
public |
||
| 675 |
view |
||
| 676 |
returns (int256) |
||
| 677 |
{
|
||
| 678 |
return burnedLessThanRequested; |
||
| 679 |
} |
||
| 680 | |||
| 681 |
// function optimize_previewMintSharesGreater() public view returns (int256) {
|
||
| 682 |
// return previewMintSharesGreater; |
||
| 683 |
// } |
||
| 684 | |||
| 685 |
// function optimize_previewDepositSharesGreater() |
||
| 686 |
// public |
||
| 687 |
// view |
||
| 688 |
// returns (int256) |
||
| 689 |
// {
|
||
| 690 |
// return previewDepositSharesGreater; |
||
| 691 |
// } |
||
| 692 | |||
| 693 |
// function optimize_previewMintAssetsGreater() public view returns (int256) {
|
||
| 694 |
// return previewMintAssetsGreater; |
||
| 695 |
// } |
||
| 696 | |||
| 697 |
// function optimize_previewDepositAssetsGreater() |
||
| 698 |
// public |
||
| 699 |
// view |
||
| 700 |
// returns (int256) |
||
| 701 |
// {
|
||
| 702 |
// return previewDepositAssetsGreater; |
||
| 703 |
// } |
||
| 704 | |||
| 705 |
function optimize_assetBackingDifference() public view returns (int256) {
|
||
| 706 |
uint256 summedTotalAssets = _sumStrategyAssets(); |
||
| 707 |
uint256 totalSupply = superVault.totalSupply(); |
||
| 708 | |||
| 709 |
if (summedTotalAssets == 0 && totalSupply > 0) {
|
||
| 710 |
return int256(totalSupply); |
||
| 711 |
} |
||
| 712 |
} |
||
| 713 | |||
| 714 |
// Canaries |
||
| 715 | |||
| 716 |
/// @dev Canary assertion helper. A failing input is expected to be discovered during fuzzing. |
||
| 717 |
function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
|
||
| 718 |
✓ 139.8K
|
t(entropy > 0, ASSERTION_CANARY); |
|
| 719 |
} |
||
| 720 | |||
| 721 |
/// @dev Canary global invariant expected to fail immediately. |
||
| 722 |
function invariant_canary() public returns (bool) {
|
||
| 723 |
t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE); |
||
| 724 |
return true; |
||
| 725 |
} |
||
| 726 | |||
| 727 |
// ERC7540 Properties from erc7540-reusable-properties |
||
| 728 | |||
| 729 |
/// @dev Property 7540-1: convertToAssets(totalSupply) == totalAssets unless price is 0.0 |
||
| 730 |
function invariant_erc7540_1() public returns (bool) {
|
||
| 731 |
actor = _getActor(); |
||
| 732 |
t( |
||
| 733 |
erc7540_1(address(superVault)), |
||
| 734 |
"ERC7540-1: convertToAssets(totalSupply) == totalAssets failed" |
||
| 735 |
); |
||
| 736 |
return true; |
||
| 737 |
} |
||
| 738 | |||
| 739 |
/// @dev Property 7540-2: convertToShares(totalAssets) == totalSupply unless price is 0.0 |
||
| 740 |
function invariant_erc7540_2() public returns (bool) {
|
||
| 741 |
actor = _getActor(); |
||
| 742 |
t( |
||
| 743 |
erc7540_2(address(superVault)), |
||
| 744 |
"ERC7540-2: convertToShares(totalAssets) == totalSupply failed" |
||
| 745 |
); |
||
| 746 |
return true; |
||
| 747 |
} |
||
| 748 | |||
| 749 |
/// @dev Property 7540-3: max* never reverts |
||
| 750 |
function invariant_erc7540_3() public returns (bool) {
|
||
| 751 |
actor = _getActor(); |
||
| 752 |
t( |
||
| 753 |
erc7540_3(address(superVault)), |
||
| 754 |
"ERC7540-3: max* functions should never revert" |
||
| 755 |
); |
||
| 756 |
return true; |
||
| 757 |
} |
||
| 758 | |||
| 759 |
/// @dev Property 7540-4: claiming more than max always reverts |
||
| 760 |
function global_erc7540_4_deposit_ASSERTION_ERC7540_4_DEPOSIT(uint256 amt) public stateless {
|
||
| 761 |
✓ 193.1K
|
actor = _getActor(); |
|
| 762 |
✓ 7.9K
|
t( |
|
| 763 |
✓ 193.1K
|
erc7540_4_deposit(address(superVault), amt), |
|
| 764 |
✓ 7.9K
|
ASSERTION_ERC7540_4_DEPOSIT |
|
| 765 |
); |
||
| 766 |
} |
||
| 767 | |||
| 768 |
function global_erc7540_4_mint_ASSERTION_ERC7540_4_MINT(uint256 amt) public stateless {
|
||
| 769 |
✓ 132.3K
|
actor = _getActor(); |
|
| 770 |
✓ 2.9K
|
t( |
|
| 771 |
✓ 132.3K
|
erc7540_4_mint(address(superVault), amt), |
|
| 772 |
✓ 2.9K
|
ASSERTION_ERC7540_4_MINT |
|
| 773 |
); |
||
| 774 |
} |
||
| 775 | |||
| 776 |
function global_erc7540_4_withdraw_ASSERTION_ERC7540_4_WITHDRAW(uint256 amt) public stateless {
|
||
| 777 |
✓ 124.9K
|
actor = _getActor(); |
|
| 778 |
✓ 124.9K
|
t( |
|
| 779 |
✓ 124.9K
|
erc7540_4_withdraw(address(superVault), amt), |
|
| 780 |
✓ 124.9K
|
ASSERTION_ERC7540_4_WITHDRAW |
|
| 781 |
); |
||
| 782 |
} |
||
| 783 | |||
| 784 |
function global_erc7540_4_redeem_ASSERTION_ERC7540_4_REDEEM(uint256 amt) public stateless {
|
||
| 785 |
✓ 129.5K
|
actor = _getActor(); |
|
| 786 |
✓ 129.5K
|
t( |
|
| 787 |
✓ 129.5K
|
erc7540_4_redeem(address(superVault), amt), |
|
| 788 |
✓ 129.5K
|
ASSERTION_ERC7540_4_REDEEM |
|
| 789 |
); |
||
| 790 |
} |
||
| 791 | |||
| 792 |
/// @dev Property 7540-5: requestRedeem reverts if the share balance is less than amount |
||
| 793 |
function global_erc7540_5_ASSERTION_ERC7540_5(uint256 shares) public stateless {
|
||
| 794 |
✓ 130.9K
|
actor = _getActor(); |
|
| 795 |
✓ 129.5K
|
t( |
|
| 796 |
✓ 130.9K
|
erc7540_5(address(superVault), address(superVault), shares), |
|
| 797 |
✓ 129.5K
|
ASSERTION_ERC7540_5 |
|
| 798 |
); |
||
| 799 |
} |
||
| 800 | |||
| 801 |
/// @dev Property 7540-6: preview* always reverts |
||
| 802 |
// NOTE: previewMint and previewDeposit don't revert because deposits are sync |
||
| 803 |
// function crytic_erc7540_6() public {
|
||
| 804 |
// actor = _getActor(); |
||
| 805 |
// t( |
||
| 806 |
// erc7540_6(address(superVault)), |
||
| 807 |
// "ERC7540-6: preview* functions should always revert" |
||
| 808 |
// ); |
||
| 809 |
// } |
||
| 810 | |||
| 811 |
/// @dev Property 7540-7: if max[method] > 0, then [method] (max) should not revert |
||
| 812 |
// NOTE: maxDeposit always returns type(uint256).max |
||
| 813 |
// function crytic_erc7540_7_deposit(uint256 amt) public {
|
||
| 814 |
// actor = _getActor(); |
||
| 815 |
// t( |
||
| 816 |
// erc7540_7_deposit(address(superVault), amt), |
||
| 817 |
// "ERC7540-7: deposit should not revert when amount <= max" |
||
| 818 |
// ); |
||
| 819 |
// } |
||
| 820 | |||
| 821 |
// NOTE: maxMint always returns type(uint256).max |
||
| 822 |
// function crytic_erc7540_7_mint(uint256 amt) public stateless {
|
||
| 823 |
// actor = _getActor(); |
||
| 824 |
// t( |
||
| 825 |
// erc7540_7_mint(address(superVault), amt), |
||
| 826 |
// "ERC7540-7: mint should not revert when amount <= max" |
||
| 827 |
// ); |
||
| 828 |
// } |
||
| 829 | |||
| 830 |
function global_erc7540_7_withdraw_ASSERTION_ERC7540_7_WITHDRAW(uint256 amt) public stateless {
|
||
| 831 |
✓ 145.4K
|
actor = _getActor(); |
|
| 832 |
✓ 145.4K
|
t( |
|
| 833 |
✓ 145.4K
|
erc7540_7_withdraw(address(superVault), amt), |
|
| 834 |
✓ 145.4K
|
ASSERTION_ERC7540_7_WITHDRAW |
|
| 835 |
); |
||
| 836 |
} |
||
| 837 | |||
| 838 |
// NOTE: this implements the check from ERC7540Properties directly because SuperVault logic implementation allows amt to be nonzero but round down to 0 when assets passed into redeem are calculated |
||
| 839 |
function global_erc7540_7_redeem_ASSERTION_ERC7540_7_REDEEM(uint256 amt) public stateless {
|
||
| 840 |
✓ 136.1K
|
actor = _getActor(); |
|
| 841 | |||
| 842 |
✓ 136.1K
|
uint256 maxRedeem = superVault.maxRedeem(actor); |
|
| 843 |
✓ 136.1K
|
amt = between(amt, 0, maxRedeem); |
|
| 844 | |||
| 845 |
✓ 136.1K
|
if (amt == 0) {
|
|
| 846 |
✓ 135.7K
|
return; // Skip |
|
| 847 |
} |
||
| 848 | |||
| 849 |
✓ 373
|
uint256 averageWithdrawPrice = superVaultStrategy |
|
| 850 |
✓ 373
|
.getAverageWithdrawPrice(actor); |
|
| 851 | |||
| 852 |
// calculates assets in the same way as redeem to confirm that a nonzero amount is being requested |
||
| 853 |
✓ 373
|
uint256 assets = amt.mulDiv( |
|
| 854 |
✓ 373
|
averageWithdrawPrice, |
|
| 855 |
✓ 373
|
superVault.PRECISION(), |
|
| 856 |
✓ 373
|
Math.Rounding.Floor |
|
| 857 |
); |
||
| 858 | |||
| 859 |
✓ 373
|
try superVault.redeem(amt, actor, actor) {} catch {
|
|
| 860 |
if (amt > 0 && assets > 0) {
|
||
| 861 |
t( |
||
| 862 |
false, |
||
| 863 |
ASSERTION_ERC7540_7_REDEEM |
||
| 864 |
); |
||
| 865 |
} |
||
| 866 |
} |
||
| 867 |
} |
||
| 868 |
} |
||
| 869 |
99.3%
test/recon/Setup.sol
Lines covered: 150 / 151 (99.3%)
| 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 |
✓ 1
|
uint8 internal constant DECIMALS = 18; |
|
| 64 |
address asset; |
||
| 65 |
✓ 1
|
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 |
✓ 274.4K
|
vm.prank(address(this)); |
|
| 139 |
_; |
||
| 140 |
} |
||
| 141 | |||
| 142 |
modifier asActor() {
|
||
| 143 |
✓ 448.0K
|
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 |
✓ 193.1K
|
bool preFailed = _foundryFailSlotSet(); |
|
| 159 |
_; |
||
| 160 |
✓ 1.0M
|
if (!preFailed && _foundryFailSlotSet()) assert(false); |
|
| 161 |
✓ 1.0M
|
⟲ 1.0M
|
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 |
✓ 3.4M
|
function _foundryFailSlotSet() internal view returns (bool) {
|
|
| 172 |
✓ 3.4M
|
(bool ok, bytes memory ret) = address(vm).staticcall( |
|
| 173 |
✓ 3.4M
|
abi.encodeWithSignature("load(address,bytes32)", address(vm), _FOUNDRY_FAIL_SLOT)
|
|
| 174 |
); |
||
| 175 |
✓ 3.4M
|
return ok && ret.length >= 32 && abi.decode(ret, (bytes32)) != bytes32(0); |
|
| 176 |
} |
||
| 177 | |||
| 178 |
/// === Setup === /// |
||
| 179 |
/// This contains all calls to be performed in the tester constructor, both for Echidna and Foundry |
||
| 180 |
function setup() internal virtual override {
|
||
| 181 |
// 1. Add additional actors |
||
| 182 |
✓ 1
|
_addActor(address(0x100)); // Actor 1 |
|
| 183 |
✓ 1
|
_addActor(address(0x200)); // Actor 2 |
|
| 184 | |||
| 185 |
// 2. Create assets using AssetManager |
||
| 186 |
✓ 1
|
_newAsset(DECIMALS); // Deploy token with 18 decimals |
|
| 187 |
✓ 1
|
_newAsset(DECIMALS); // UP token |
|
| 188 | |||
| 189 |
✓ 1
|
_switchAsset(0); |
|
| 190 | |||
| 191 |
// 3. Deploy all three types of yield sources using YieldManager |
||
| 192 |
// Deploy ERC4626 yield source (default) |
||
| 193 |
✓ 1
|
erc4626YieldSource = _newYieldSource( |
|
| 194 |
✓ 1
|
_getAsset(), |
|
| 195 |
✓ 1
|
YieldSourceType.ERC4626 |
|
| 196 |
); |
||
| 197 | |||
| 198 |
// Deploy ERC5115 yield source |
||
| 199 |
✓ 1
|
erc5115YieldSource = _newYieldSource( |
|
| 200 |
✓ 1
|
_getAsset(), |
|
| 201 |
✓ 1
|
YieldSourceType.ERC5115 |
|
| 202 |
); |
||
| 203 | |||
| 204 |
// Deploy ERC7540 yield source |
||
| 205 |
✓ 1
|
erc7540YieldSource = _newYieldSource( |
|
| 206 |
✓ 1
|
_getAsset(), |
|
| 207 |
✓ 1
|
YieldSourceType.ERC7540 |
|
| 208 |
); |
||
| 209 | |||
| 210 |
// Set ERC4626 as the default active yield source |
||
| 211 |
✓ 1
|
_switchYieldSource(0); // Switch to first yield source in the array (ERC4626) |
|
| 212 | |||
| 213 |
// 4. Deploy SuperGovernor first (required by other contracts) |
||
| 214 |
✓ 1
|
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 |
✓ 1
|
feeRecipient, // treasury |
|
| 220 |
✓ 1
|
address(this) // prover |
|
| 221 |
); |
||
| 222 | |||
| 223 |
// 5. Deploy implementation contracts for the aggregator |
||
| 224 |
✓ 1
|
vaultImpl = new SuperVault(address(superGovernor)); |
|
| 225 |
✓ 1
|
strategyImpl = new SuperVaultStrategy(address(superGovernor)); |
|
| 226 |
✓ 1
|
escrowImpl = new SuperVaultEscrow(); |
|
| 227 | |||
| 228 |
// 6. Deploy SuperVaultAggregator with implementation contracts |
||
| 229 |
✓ 1
|
superVaultAggregator = new UnsafeSuperVaultAggregator( |
|
| 230 |
✓ 1
|
address(superGovernor), |
|
| 231 |
✓ 1
|
address(vaultImpl), |
|
| 232 |
✓ 1
|
address(strategyImpl), |
|
| 233 |
address(escrowImpl) |
||
| 234 |
); |
||
| 235 | |||
| 236 |
// 7. Register the SuperVaultAggregator and UpToken address with SuperGovernor |
||
| 237 |
✓ 1
|
superGovernor.setAddress( |
|
| 238 |
✓ 1
|
superGovernor.SUPER_VAULT_AGGREGATOR(), |
|
| 239 |
✓ 1
|
address(superVaultAggregator) |
|
| 240 |
); |
||
| 241 | |||
| 242 |
✓ 1
|
address[] memory assets = _getAssets(); |
|
| 243 |
✓ 1
|
superGovernor.setAddress(superGovernor.UP(), assets[1]); // the second deployed token in the AssetManager is the UPToken |
|
| 244 |
✓ 1
|
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 |
✓ 1
|
erc4626YieldSourceOracle = new MockERC4626YieldSourceOracle(); |
|
| 250 |
✓ 1
|
erc5115YieldSourceOracle = new MockERC5115YieldSourceOracle(); |
|
| 251 |
✓ 1
|
erc7540YieldSourceOracle = new MockERC7540YieldSourceOracle(); |
|
| 252 |
✓ 1
|
ECDSAPPSOracle = new MockECDSAPPSOracle(); |
|
| 253 | |||
| 254 |
// ECDSAPPSOracle setup |
||
| 255 |
✓ 1
|
superGovernor.setActivePPSOracle(address(ECDSAPPSOracle)); |
|
| 256 |
✓ 1
|
ECDSAPPSOracle.setSUPER_GOVERNORReturn(address(superVaultAggregator)); |
|
| 257 | |||
| 258 |
// Set valid assets for all oracles |
||
| 259 |
✓ 1
|
asset = _getAsset(); |
|
| 260 |
✓ 1
|
erc4626YieldSourceOracle.setValidAsset(asset, true); |
|
| 261 |
✓ 1
|
erc5115YieldSourceOracle.setValidAsset(asset, true); |
|
| 262 |
✓ 1
|
erc7540YieldSourceOracle.setValidAsset(asset, true); |
|
| 263 | |||
| 264 |
// 9. Create a vault trio using the aggregator |
||
| 265 |
✓ 1
|
ISuperVaultAggregator.VaultCreationParams |
|
| 266 |
memory params = ISuperVaultAggregator.VaultCreationParams({
|
||
| 267 |
✓ 1
|
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 |
✓ 1
|
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 |
feeConfig: ISuperVaultStrategy.FeeConfig({
|
||
| 275 |
✓ 1
|
performanceFeeBps: 1000, // 10% performance fee |
|
| 276 |
✓ 1
|
managementFeeBps: 100, // 1% management fee |
|
| 277 |
✓ 1
|
recipient: feeRecipient |
|
| 278 |
}) |
||
| 279 |
}); |
||
| 280 | |||
| 281 |
( |
||
| 282 |
address vaultAddr, |
||
| 283 |
address strategyAddr, |
||
| 284 |
address escrowAddr |
||
| 285 |
✓ 1
|
) = superVaultAggregator.createVault(params); |
|
| 286 | |||
| 287 |
// 10. Store the deployed contracts |
||
| 288 |
✓ 1
|
superVault = SuperVault(vaultAddr); |
|
| 289 |
✓ 1
|
superVaultStrategy = SuperVaultStrategy(payable(strategyAddr)); |
|
| 290 |
✓ 1
|
superVaultEscrow = SuperVaultEscrow(escrowAddr); |
|
| 291 | |||
| 292 |
/// 11. Deploy all hook contracts and helper |
||
| 293 |
✓ 1
|
merkleHelper = new MerkleTestHelper(); |
|
| 294 | |||
| 295 |
// Deploy ERC4626 Hooks |
||
| 296 |
✓ 1
|
approveAndDeposit4626Hook = new ApproveAndDeposit4626VaultHook(); |
|
| 297 |
✓ 1
|
deposit4626Hook = new Deposit4626VaultHook(); |
|
| 298 |
✓ 1
|
redeem4626Hook = new Redeem4626VaultHook(); |
|
| 299 | |||
| 300 |
// Deploy ERC5115 Hooks |
||
| 301 |
✓ 1
|
approveAndDeposit5115Hook = new ApproveAndDeposit5115VaultHook(); |
|
| 302 |
✓ 1
|
deposit5115Hook = new Deposit5115VaultHook(); |
|
| 303 |
✓ 1
|
redeem5115Hook = new Redeem5115VaultHook(); |
|
| 304 | |||
| 305 |
// Deploy ERC7540 Hooks |
||
| 306 |
✓ 1
|
deposit7540Hook = new Deposit7540VaultHook(); |
|
| 307 |
✓ 1
|
redeem7540Hook = new Redeem7540VaultHook(); |
|
| 308 |
✓ 1
|
requestDeposit7540Hook = new RequestDeposit7540VaultHook(); |
|
| 309 |
✓ 1
|
requestRedeem7540Hook = new RequestRedeem7540VaultHook(); |
|
| 310 |
✓ 1
|
approveAndRequestDeposit7540Hook = new ApproveAndRequestDeposit7540VaultHook(); |
|
| 311 |
✓ 1
|
cancelDepositRequest7540Hook = new CancelDepositRequest7540Hook(); |
|
| 312 |
✓ 1
|
cancelRedeemRequest7540Hook = new CancelRedeemRequest7540Hook(); |
|
| 313 |
✓ 1
|
claimCancelDepositRequest7540Hook = new ClaimCancelDepositRequest7540Hook(); |
|
| 314 |
✓ 1
|
claimCancelRedeemRequest7540Hook = new ClaimCancelRedeemRequest7540Hook(); |
|
| 315 |
✓ 1
|
withdraw7540Hook = new Withdraw7540VaultHook(); |
|
| 316 | |||
| 317 |
// Deploy Super Vault Hooks |
||
| 318 |
✓ 1
|
cancelRedeemHook = new CancelRedeemHook(); |
|
| 319 |
✓ 1
|
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 |
✓ 1
|
superGovernor.registerHook(address(approveAndDeposit4626Hook), false); |
|
| 324 |
✓ 1
|
superGovernor.registerHook(address(deposit4626Hook), false); |
|
| 325 |
✓ 1
|
superGovernor.registerHook(address(redeem4626Hook), true); // fulfill request hook |
|
| 326 | |||
| 327 |
// ERC5115 Hooks |
||
| 328 |
✓ 1
|
superGovernor.registerHook(address(approveAndDeposit5115Hook), false); |
|
| 329 |
✓ 1
|
superGovernor.registerHook(address(deposit5115Hook), false); |
|
| 330 |
✓ 1
|
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 |
✓ 1
|
superGovernor.registerHook(address(deposit7540Hook), false); |
|
| 334 |
✓ 1
|
superGovernor.registerHook(address(redeem7540Hook), true); // fulfill request hook |
|
| 335 |
✓ 1
|
superGovernor.registerHook(address(requestDeposit7540Hook), false); |
|
| 336 |
✓ 1
|
superGovernor.registerHook(address(requestRedeem7540Hook), false); |
|
| 337 |
✓ 1
|
superGovernor.registerHook( |
|
| 338 |
✓ 1
|
address(approveAndRequestDeposit7540Hook), |
|
| 339 |
false |
||
| 340 |
); |
||
| 341 |
✓ 1
|
superGovernor.registerHook( |
|
| 342 |
✓ 1
|
address(cancelDepositRequest7540Hook), |
|
| 343 |
false |
||
| 344 |
); |
||
| 345 |
✓ 1
|
superGovernor.registerHook(address(cancelRedeemRequest7540Hook), false); |
|
| 346 |
✓ 1
|
superGovernor.registerHook( |
|
| 347 |
✓ 1
|
address(claimCancelDepositRequest7540Hook), |
|
| 348 |
false |
||
| 349 |
); |
||
| 350 |
✓ 1
|
superGovernor.registerHook( |
|
| 351 |
✓ 1
|
address(claimCancelRedeemRequest7540Hook), |
|
| 352 |
false |
||
| 353 |
); |
||
| 354 |
✓ 1
|
superGovernor.registerHook(address(withdraw7540Hook), true); // fulfill request hook |
|
| 355 | |||
| 356 |
// Super Vault Hooks |
||
| 357 |
✓ 1
|
superGovernor.registerHook(address(cancelRedeemHook), false); |
|
| 358 |
✓ 1
|
superGovernor.registerHook(address(superVaultWithdraw7540Hook), true); // fulfill request hook |
|
| 359 | |||
| 360 |
// 12. Set up approval array for contracts that need token access |
||
| 361 |
✓ 1
|
address[] memory approvalArray = new address[](6); |
|
| 362 |
✓ 1
|
approvalArray[0] = address(superVault); |
|
| 363 |
✓ 1
|
approvalArray[1] = address(superVaultStrategy); |
|
| 364 |
✓ 1
|
approvalArray[2] = address(superVaultAggregator); |
|
| 365 |
✓ 1
|
approvalArray[3] = erc4626YieldSource; |
|
| 366 |
✓ 1
|
approvalArray[4] = erc5115YieldSource; |
|
| 367 |
✓ 1
|
approvalArray[5] = erc7540YieldSource; |
|
| 368 | |||
| 369 |
// 13. Finalize asset deployment (mints to actors and sets approvals) |
||
| 370 |
✓ 1
|
_finalizeAssetDeployment(_getActors(), approvalArray, type(uint88).max); |
|
| 371 |
} |
||
| 372 | |||
| 373 |
/// Get hook addresses for different yield source types |
||
| 374 | |||
| 375 |
function _getApproveAndDepositHookForType( |
||
| 376 |
YieldSourceType sourceType |
||
| 377 |
✓ 30.9K
|
) internal view returns (address) {
|
|
| 378 |
✓ 30.9K
|
if (sourceType == YieldSourceType.ERC4626) {
|
|
| 379 |
✓ 21.8K
|
return address(approveAndDeposit4626Hook); |
|
| 380 |
✓ 9.1K
|
} else if (sourceType == YieldSourceType.ERC5115) {
|
|
| 381 |
✓ 2.8K
|
return address(approveAndDeposit5115Hook); |
|
| 382 |
✓ 6.3K
|
} else if (sourceType == YieldSourceType.ERC7540) {
|
|
| 383 |
✓ 6.3K
|
return address(approveAndRequestDeposit7540Hook); |
|
| 384 |
} |
||
| 385 |
return address(0); |
||
| 386 |
} |
||
| 387 | |||
| 388 |
function _getRedeemHookForType( |
||
| 389 |
YieldSourceType sourceType |
||
| 390 |
✓ 553.2K
|
) internal view returns (address) {
|
|
| 391 |
✓ 553.2K
|
if (sourceType == YieldSourceType.ERC4626) {
|
|
| 392 |
✓ 415.4K
|
return address(redeem4626Hook); |
|
| 393 |
✓ 137.8K
|
} else if (sourceType == YieldSourceType.ERC5115) {
|
|
| 394 |
✓ 61.8K
|
return address(redeem5115Hook); |
|
| 395 |
✓ 76.0K
|
} else if (sourceType == YieldSourceType.ERC7540) {
|
|
| 396 |
✓ 76.0K
|
return address(redeem7540Hook); |
|
| 397 |
} |
||
| 398 |
return address(0); |
||
| 399 |
} |
||
| 400 | |||
| 401 |
/// Get oracle addresses for different yield source types |
||
| 402 | |||
| 403 |
function _getYieldSourceOracleForType( |
||
| 404 |
YieldSourceType sourceType |
||
| 405 |
✓ 300.6K
|
) internal view returns (address) {
|
|
| 406 |
✓ 300.6K
|
if (sourceType == YieldSourceType.ERC4626) {
|
|
| 407 |
✓ 144.7K
|
return address(erc4626YieldSourceOracle); |
|
| 408 |
✓ 156.0K
|
} else if (sourceType == YieldSourceType.ERC5115) {
|
|
| 409 |
✓ 49.2K
|
return address(erc5115YieldSourceOracle); |
|
| 410 |
✓ 106.8K
|
} else if (sourceType == YieldSourceType.ERC7540) {
|
|
| 411 |
✓ 106.8K
|
return address(erc7540YieldSourceOracle); |
|
| 412 |
} |
||
| 413 |
} |
||
| 414 | |||
| 415 |
/// @dev Helper function to determine yield source type from address |
||
| 416 |
function _getYieldSourceTypeFromAddress( |
||
| 417 |
address yieldSource |
||
| 418 |
✓ 730.6K
|
) internal view returns (YieldSourceType) {
|
|
| 419 |
// Get all available yield sources from YieldManager |
||
| 420 |
✓ 730.6K
|
address[] memory yieldSources = _getYieldSources(); |
|
| 421 | |||
| 422 |
// Check which index matches the current yield source |
||
| 423 |
✓ 1.4M
|
for (uint256 i = 0; i < yieldSources.length; i++) {
|
|
| 424 |
✓ 1.3M
|
if (yieldSources[i] == yieldSource) {
|
|
| 425 |
// Return the corresponding type based on creation order: |
||
| 426 |
// Index 0: ERC4626, Index 1: ERC5115, Index 2: ERC7540 |
||
| 427 |
✓ 730.6K
|
if (i == 0) return YieldSourceType.ERC4626; |
|
| 428 |
✓ 265.7K
|
if (i == 1) return YieldSourceType.ERC5115; |
|
| 429 |
✓ 189.6K
|
if (i == 2) return YieldSourceType.ERC7540; |
|
| 430 |
} |
||
| 431 |
} |
||
| 432 | |||
| 433 |
// Default to ERC4626 if not found |
||
| 434 |
✓ 79.7K
|
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 |
✓ 563.0K
|
function _getRandomActor(uint256 entropy) public view returns (address) {
|
|
| 451 |
✓ 563.0K
|
address[] memory actors = _getActors(); |
|
| 452 | |||
| 453 |
✓ 563.0K
|
return actors[entropy % actors.length]; |
|
| 454 |
} |
||
| 455 |
} |
||
| 456 |
0.0%
test/recon/TargetFunctions.sol
Lines covered: 0 / 0 (0.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// Chimera deps |
||
| 5 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 6 | |||
| 7 |
// Helpers |
||
| 8 |
import {Panic} from "@recon/Panic.sol";
|
||
| 9 | |||
| 10 |
// Targets |
||
| 11 |
// NOTE: Always import and apply them in alphabetical order, so much easier to debug! |
||
| 12 |
import {AdminTargets} from "./targets/AdminTargets.sol";
|
||
| 13 |
import {DoomsdayTargets} from "./targets/DoomsdayTargets.sol";
|
||
| 14 |
import {ManagersTargets} from "./targets/ManagersTargets.sol";
|
||
| 15 |
import {OracleTargets} from "./targets/OracleTargets.sol";
|
||
| 16 |
import {SuperVaultTargets} from "./targets/SuperVaultTargets.sol";
|
||
| 17 |
import {SuperVaultAggregatorTargets} from "./targets/SuperVaultAggregatorTargets.sol";
|
||
| 18 |
import {SuperVaultEscrowTargets} from "./targets/SuperVaultEscrowTargets.sol";
|
||
| 19 |
import {SuperVaultStrategyTargets} from "./targets/SuperVaultStrategyTargets.sol";
|
||
| 20 |
import {SuperGovernorTargets} from "./targets/SuperGovernorTargets.sol";
|
||
| 21 |
import {YieldSourceTargets} from "./targets/YieldSourceTargets.sol";
|
||
| 22 | |||
| 23 |
abstract contract TargetFunctions is |
||
| 24 |
AdminTargets, |
||
| 25 |
DoomsdayTargets, |
||
| 26 |
ManagersTargets, |
||
| 27 |
OracleTargets, |
||
| 28 |
SuperVaultTargets, |
||
| 29 |
SuperVaultAggregatorTargets, |
||
| 30 |
SuperVaultEscrowTargets, |
||
| 31 |
SuperVaultStrategyTargets, |
||
| 32 |
SuperGovernorTargets, |
||
| 33 |
YieldSourceTargets |
||
| 34 |
{
|
||
| 35 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 36 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 37 |
} |
||
| 38 |
72.4%
test/recon/helpers/MerkleTestHelper.sol
Lines covered: 21 / 29 (72.4%)
| 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 |
✓ 1
|
contract MerkleTestHelper {
|
|
| 9 |
/// @notice Generate a test Merkle root for ERC4626 deposit and redeem hooks |
||
| 10 |
/// @param depositHook Address of the Deposit4626VaultHook or ApproveAndDeposit4626VaultHook contract |
||
| 11 |
/// @param redeemHook Address of the Redeem4626VaultHook contract |
||
| 12 |
/// @param mockVault Address of the mock ERC4626 vault |
||
| 13 |
/// @param mockToken Address of the mock token (unused but kept for compatibility) |
||
| 14 |
/// @return root The Merkle root for the tree |
||
| 15 |
/// @return proofs Array of proofs for each leaf [depositProof, redeemProof] |
||
| 16 |
function generateTestHooksRoot( |
||
| 17 |
address depositHook, |
||
| 18 |
address redeemHook, |
||
| 19 |
address mockVault, |
||
| 20 |
address mockToken |
||
| 21 |
✓ 142.7K
|
) public pure returns (bytes32 root, bytes32[][] memory proofs) {
|
|
| 22 |
// Create leaves for the two hooks following the exact format from SuperVaultAggregator._createLeaf() |
||
| 23 |
✓ 142.7K
|
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 |
✓ 142.7K
|
bytes memory depositArgs = abi.encodePacked(mockVault); // Only yield source address |
|
| 28 |
✓ 142.7K
|
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 |
✓ 142.7K
|
bytes memory redeemArgs = abi.encodePacked(mockVault); // Only yield source address |
|
| 33 |
✓ 142.7K
|
leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
|
| 34 |
|
||
| 35 |
// Sort leaves to match standard Merkle tree ordering |
||
| 36 |
✓ 142.7K
|
if (leaves[0] > leaves[1]) {
|
|
| 37 |
✓ 38.8K
|
(leaves[0], leaves[1]) = (leaves[1], leaves[0]); |
|
| 38 |
} |
||
| 39 |
|
||
| 40 |
// For a 2-leaf tree, root is hash of the sorted leaves |
||
| 41 |
✓ 142.7K
|
root = keccak256(abi.encodePacked(leaves[0], leaves[1])); |
|
| 42 |
|
||
| 43 |
// Store original leaf hashes for proof assignment |
||
| 44 |
✓ 142.7K
|
bytes32 depositLeaf = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs)))); |
|
| 45 |
✓ 142.7K
|
bytes32 redeemLeaf = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs)))); |
|
| 46 |
|
||
| 47 |
// Generate proofs for each leaf |
||
| 48 |
✓ 142.7K
|
proofs = new bytes32[][](2); |
|
| 49 |
✓ 142.7K
|
proofs[0] = new bytes32[](1); // Proof for deposit hook |
|
| 50 |
✓ 142.7K
|
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 |
✓ 142.7K
|
if (leaves[0] == depositLeaf) {
|
|
| 55 |
// depositHook is first leaf after sorting |
||
| 56 |
✓ 103.9K
|
proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
|
| 57 |
✓ 103.9K
|
proofs[1][0] = leaves[0]; // Sibling of redeem leaf |
|
| 58 |
} else {
|
||
| 59 |
// redeemHook is first leaf after sorting |
||
| 60 |
✓ 38.8K
|
proofs[0][0] = leaves[1]; // Sibling of deposit leaf |
|
| 61 |
✓ 38.8K
|
proofs[1][0] = leaves[0]; // Sibling of redeem leaf |
|
| 62 |
} |
||
| 63 |
|
||
| 64 |
✓ 142.7K
|
return (root, proofs); |
|
| 65 |
} |
||
| 66 |
|
||
| 67 |
/// @notice Generate encoded hook arguments for Deposit4626VaultHook |
||
| 68 |
/// @param yieldSource Address of the yield source vault |
||
| 69 |
/// @param amount Amount to deposit |
||
| 70 |
/// @param usePrevHookAmount Whether to use previous hook amount |
||
| 71 |
/// @return Encoded hook arguments |
||
| 72 |
function encodeDepositHookArgs( |
||
| 73 |
address yieldSource, |
||
| 74 |
uint256 amount, |
||
| 75 |
bool usePrevHookAmount |
||
| 76 |
) public pure returns (bytes memory) {
|
||
| 77 |
return abi.encodePacked( |
||
| 78 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 79 |
yieldSource, |
||
| 80 |
amount, |
||
| 81 |
usePrevHookAmount |
||
| 82 |
); |
||
| 83 |
} |
||
| 84 |
|
||
| 85 |
/// @notice Generate encoded hook arguments for Redeem4626VaultHook |
||
| 86 |
/// @param yieldSource Address of the yield source vault |
||
| 87 |
/// @param owner Address of the owner |
||
| 88 |
/// @param shares Number of shares to redeem |
||
| 89 |
/// @param usePrevHookAmount Whether to use previous hook amount |
||
| 90 |
/// @return Encoded hook arguments |
||
| 91 |
function encodeRedeemHookArgs( |
||
| 92 |
address yieldSource, |
||
| 93 |
address owner, |
||
| 94 |
uint256 shares, |
||
| 95 |
bool usePrevHookAmount |
||
| 96 |
) public pure returns (bytes memory) {
|
||
| 97 |
return abi.encodePacked( |
||
| 98 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 99 |
yieldSource, |
||
| 100 |
owner, |
||
| 101 |
shares, |
||
| 102 |
usePrevHookAmount |
||
| 103 |
); |
||
| 104 |
} |
||
| 105 |
|
||
| 106 |
/// @notice Generate encoded hook arguments for ApproveAndDeposit4626VaultHook |
||
| 107 |
/// @param yieldSource Address of the yield source vault |
||
| 108 |
/// @param token Address of the token to approve and deposit |
||
| 109 |
/// @param amount Amount to deposit |
||
| 110 |
/// @param usePrevHookAmount Whether to use previous hook amount |
||
| 111 |
/// @return Encoded hook arguments |
||
| 112 |
function encodeApproveAndDepositHookArgs( |
||
| 113 |
address yieldSource, |
||
| 114 |
address token, |
||
| 115 |
uint256 amount, |
||
| 116 |
bool usePrevHookAmount |
||
| 117 |
) public pure returns (bytes memory) {
|
||
| 118 |
return abi.encodePacked( |
||
| 119 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 120 |
yieldSource, |
||
| 121 |
token, |
||
| 122 |
amount, |
||
| 123 |
usePrevHookAmount |
||
| 124 |
); |
||
| 125 |
} |
||
| 126 |
|
||
| 127 |
/// @notice Create a leaf hash for a specific hook and arguments |
||
| 128 |
/// @param hookAddress Address of the hook contract |
||
| 129 |
/// @param hookArgs Encoded hook arguments |
||
| 130 |
/// @return leaf The leaf hash |
||
| 131 |
function createLeaf(address hookAddress, bytes memory hookArgs) public pure returns (bytes32 leaf) {
|
||
| 132 |
return keccak256(bytes.concat(keccak256(abi.encode(hookAddress, hookArgs)))); |
||
| 133 |
} |
||
| 134 |
|
||
| 135 |
/// @notice Verify a Merkle proof against a root |
||
| 136 |
/// @param proof Array of proof elements |
||
| 137 |
/// @param root Merkle root |
||
| 138 |
/// @param leaf Leaf to verify |
||
| 139 |
/// @return True if proof is valid |
||
| 140 |
function verifyProof(bytes32[] memory proof, bytes32 root, bytes32 leaf) public pure returns (bool) {
|
||
| 141 |
return MerkleProof.verify(proof, root, leaf); |
||
| 142 |
} |
||
| 143 |
} |
100.0%
test/recon/helpers/UnsafeSuperVaultAggregator.sol
Lines covered: 5 / 5 (100.0%)
| 1 |
// SPDX-License-Identifier: UNLICENSED |
||
| 2 |
pragma solidity 0.8.30; |
||
| 3 | |||
| 4 |
import {SuperVaultAggregator} from "src/SuperVault/SuperVaultAggregator.sol";
|
||
| 5 | |||
| 6 |
/// @dev inherits from aggregator to override hook validation |
||
| 7 |
contract UnsafeSuperVaultAggregator is SuperVaultAggregator {
|
||
| 8 |
constructor( |
||
| 9 |
address superGovernor_, |
||
| 10 |
address vaultImpl_, |
||
| 11 |
address strategyImpl_, |
||
| 12 |
address escrowImpl_ |
||
| 13 |
) |
||
| 14 |
SuperVaultAggregator( |
||
| 15 |
✓ 1
|
superGovernor_, |
|
| 16 |
✓ 1
|
vaultImpl_, |
|
| 17 |
✓ 1
|
strategyImpl_, |
|
| 18 |
✓ 1
|
escrowImpl_ |
|
| 19 |
) |
||
| 20 |
{}
|
||
| 21 | |||
| 22 |
function validateHook( |
||
| 23 |
address strategy, |
||
| 24 |
ValidateHookArgs calldata args |
||
| 25 |
) external view override returns (bool isValid) {
|
||
| 26 |
✓ 504.3K
|
return true; |
|
| 27 |
} |
||
| 28 |
} |
||
| 29 |
88.5%
test/recon/managers/YieldManager.sol
Lines covered: 23 / 26 (88.5%)
| 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 |
✓ 6.0M
|
function _getYieldSource() internal view returns (address) {
|
|
| 46 |
✓ 6.0M
|
if (__yieldSource == address(0)) {
|
|
| 47 |
revert YieldSourceNotSetup(); |
||
| 48 |
} |
||
| 49 | |||
| 50 |
✓ 6.0M
|
return __yieldSource; |
|
| 51 |
} |
||
| 52 | |||
| 53 |
/// @notice Returns all yield sources being used |
||
| 54 |
✓ 5.4M
|
function _getYieldSources() internal view returns (address[] memory) {
|
|
| 55 |
✓ 5.4M
|
return _yieldSources.values(); |
|
| 56 |
} |
||
| 57 | |||
| 58 |
/// @notice Returns the current yield source type |
||
| 59 |
function _getCurrentYieldSourceType() internal view returns (YieldSourceType) {
|
||
| 60 |
✓ 291.1K
|
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 |
✓ 146.6K
|
function _newYieldSource(address asset, YieldSourceType yieldSourceType) internal returns (address) {
|
|
| 68 |
address yieldSource_; |
||
| 69 |
|
||
| 70 |
✓ 146.6K
|
if (yieldSourceType == YieldSourceType.ERC4626) {
|
|
| 71 |
✓ 146.6K
|
yieldSource_ = address(new MockERC4626Tester(asset)); |
|
| 72 |
✓ 2
|
} else if (yieldSourceType == YieldSourceType.ERC5115) {
|
|
| 73 |
✓ 1
|
yieldSource_ = address(new MockERC5115Tester(asset)); |
|
| 74 |
✓ 1
|
} else if (yieldSourceType == YieldSourceType.ERC7540) {
|
|
| 75 |
✓ 1
|
yieldSource_ = address(new MockERC7540Tester(asset)); |
|
| 76 |
} else {
|
||
| 77 |
revert InvalidYieldSourceType(); |
||
| 78 |
} |
||
| 79 |
|
||
| 80 |
✓ 146.6K
|
_addYieldSource(yieldSource_); |
|
| 81 |
✓ 146.6K
|
__yieldSource = yieldSource_; // sets the yield source as the current yield source |
|
| 82 |
✓ 146.6K
|
__currentYieldSourceType = yieldSourceType; |
|
| 83 |
✓ 146.6K
|
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 |
✓ 146.6K
|
function _newVault(address asset) internal returns (address) {
|
|
| 97 |
✓ 146.6K
|
return _newYieldSource(asset, YieldSourceType.ERC4626); |
|
| 98 |
} |
||
| 99 | |||
| 100 |
/// @notice Adds a yield source to the list of yield sources |
||
| 101 |
/// @param target The address of the yield source to add |
||
| 102 |
function _addYieldSource(address target) internal {
|
||
| 103 |
✓ 146.6K
|
if (_yieldSources.contains(target)) {
|
|
| 104 |
revert YieldSourceExists(); |
||
| 105 |
} |
||
| 106 | |||
| 107 |
✓ 146.6K
|
_yieldSources.add(target); |
|
| 108 |
} |
||
| 109 | |||
| 110 |
/// @notice Legacy function name for backward compatibility |
||
| 111 |
/// @param target The address of the yield source to add |
||
| 112 |
function _addVault(address target) internal {
|
||
| 113 |
_addYieldSource(target); |
||
| 114 |
} |
||
| 115 | |||
| 116 |
/// @notice Removes a yield source from the list of yield sources |
||
| 117 |
/// @param target The address of the yield source to remove |
||
| 118 |
function _removeYieldSource(address target) internal {
|
||
| 119 |
if (!_yieldSources.contains(target)) {
|
||
| 120 |
revert YieldSourceNotAdded(); |
||
| 121 |
} |
||
| 122 | |||
| 123 |
_yieldSources.remove(target); |
||
| 124 |
} |
||
| 125 | |||
| 126 |
/// @notice Legacy function name for backward compatibility |
||
| 127 |
/// @param target The address of the yield source to remove |
||
| 128 |
function _removeVault(address target) internal {
|
||
| 129 |
_removeYieldSource(target); |
||
| 130 |
} |
||
| 131 | |||
| 132 |
/// @notice Switches the current yield source based on the entropy |
||
| 133 |
/// @param entropy The entropy to choose a random yield source in the array for switching |
||
| 134 |
function _switchYieldSource(uint256 entropy) internal {
|
||
| 135 |
✓ 668.1K
|
address target = _yieldSources.at(entropy % _yieldSources.length()); |
|
| 136 |
✓ 668.1K
|
__yieldSource = target; |
|
| 137 |
} |
||
| 138 | |||
| 139 |
/// @notice Legacy function name for backward compatibility |
||
| 140 |
/// @param entropy The entropy to choose a random yield source in the array for switching |
||
| 141 |
function _switchVault(uint256 entropy) internal {
|
||
| 142 |
_switchYieldSource(entropy); |
||
| 143 |
} |
||
| 144 | |||
| 145 |
/// @notice Switches to a different yield source type by deploying a new yield source |
||
| 146 |
/// @param asset The asset to create the new yield source for |
||
| 147 |
/// @param newYieldSourceType The new yield source type to switch to |
||
| 148 |
/// @return The address of the new yield source |
||
| 149 |
function _switchYieldSourceType(address asset, YieldSourceType newYieldSourceType) internal returns (address) {
|
||
| 150 |
return _newYieldSource(asset, newYieldSourceType); |
||
| 151 |
} |
||
| 152 |
} |
||
| 153 |
34.4%
test/recon/mocks/MockECDSAPPSOracle.sol
Lines covered: 11 / 32 (34.4%)
| 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 |
✓ 1
|
contract MockECDSAPPSOracle {
|
|
| 8 |
//<>=============================================================<> |
||
| 9 |
//|| || |
||
| 10 |
//|| NON-VIEW FUNCTIONS || |
||
| 11 |
//|| || |
||
| 12 |
//<>=============================================================<> |
||
| 13 |
// Mock implementation of updatePPS |
||
| 14 |
function updatePPS(IECDSAPPSOracle.UpdatePPSArgs memory args) public {
|
||
| 15 |
✓ 260.5K
|
ISuperVaultAggregator.ForwardPPSArgs |
|
| 16 |
memory forwardArgs = ISuperVaultAggregator.ForwardPPSArgs({
|
||
| 17 |
✓ 260.5K
|
strategies: args.strategies, |
|
| 18 |
✓ 260.5K
|
ppss: args.ppss, |
|
| 19 |
✓ 260.5K
|
ppsStdevs: args.ppsStdevs, |
|
| 20 |
✓ 260.5K
|
validatorSets: args.validatorSets, |
|
| 21 |
✓ 260.5K
|
totalValidators: args.totalValidators, |
|
| 22 |
✓ 260.5K
|
timestamps: args.timestamps, |
|
| 23 |
✓ 260.5K
|
updateAuthority: msg.sender |
|
| 24 |
}); |
||
| 25 | |||
| 26 |
✓ 260.5K
|
ISuperVaultAggregator(_SUPER_GOVERNORReturn_0).forwardPPS(forwardArgs); |
|
| 27 |
} |
||
| 28 | |||
| 29 |
//<>=============================================================<> |
||
| 30 |
//|| || |
||
| 31 |
//|| SETTER FUNCTIONS || |
||
| 32 |
//|| || |
||
| 33 |
//<>=============================================================<> |
||
| 34 |
// Function to set return values for SUPER_GOVERNOR |
||
| 35 |
function setSUPER_GOVERNORReturn(address _value0) public {
|
||
| 36 |
✓ 1
|
_SUPER_GOVERNORReturn_0 = _value0; |
|
| 37 |
} |
||
| 38 | |||
| 39 |
// Function to set return values for UPDATE_PPS_TYPEHASH |
||
| 40 |
function setUPDATE_PPS_TYPEHASHReturn(bytes32 _value0) public {
|
||
| 41 |
_UPDATE_PPS_TYPEHASHReturn_0 = _value0; |
||
| 42 |
} |
||
| 43 | |||
| 44 |
// Function to set return values for domainSeparator |
||
| 45 |
function setDomainSeparatorReturn(bytes32 _value0) public {
|
||
| 46 |
_domainSeparatorReturn_0 = _value0; |
||
| 47 |
} |
||
| 48 | |||
| 49 |
/******************************************************************* |
||
| 50 |
* ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ * |
||
| 51 |
*-----------------------------------------------------------------* |
||
| 52 |
* Generally you only need to modify the sections above. * |
||
| 53 |
* The code below handles system operations. * |
||
| 54 |
*******************************************************************/ |
||
| 55 | |||
| 56 |
//<>=============================================================<> |
||
| 57 |
//|| || |
||
| 58 |
//|| ⚠️ STRUCT DEFINITIONS - DO NOT MODIFY ⚠️ || |
||
| 59 |
//|| || |
||
| 60 |
//<>=============================================================<> |
||
| 61 |
// Struct definition removed - using IECDSAPPSOracle.UpdatePPSArgs from interface |
||
| 62 | |||
| 63 |
//<>=============================================================<> |
||
| 64 |
//|| || |
||
| 65 |
//|| ⚠️ EVENTS DEFINITIONS - DO NOT MODIFY ⚠️ || |
||
| 66 |
//|| || |
||
| 67 |
//<>=============================================================<> |
||
| 68 |
event EIP712DomainChanged(); |
||
| 69 |
event PPSValidated( |
||
| 70 |
address strategy, |
||
| 71 |
uint256 pps, |
||
| 72 |
uint256 ppsStdev, |
||
| 73 |
uint256 validatorSet, |
||
| 74 |
uint256 totalValidators, |
||
| 75 |
uint256 timestamp, |
||
| 76 |
address sender |
||
| 77 |
); |
||
| 78 | |||
| 79 |
//<>=============================================================<> |
||
| 80 |
//|| || |
||
| 81 |
//|| ⚠️ INTERNAL STORAGE - DO NOT MODIFY ⚠️ || |
||
| 82 |
//|| || |
||
| 83 |
//<>=============================================================<> |
||
| 84 |
address private _SUPER_GOVERNORReturn_0; |
||
| 85 |
bytes32 private _UPDATE_PPS_TYPEHASHReturn_0; |
||
| 86 |
bytes32 private _domainSeparatorReturn_0; |
||
| 87 |
bytes1 private _eip712DomainReturn_0; |
||
| 88 |
string private _eip712DomainReturn_1; |
||
| 89 |
string private _eip712DomainReturn_2; |
||
| 90 |
uint256 private _eip712DomainReturn_3; |
||
| 91 |
address private _eip712DomainReturn_4; |
||
| 92 |
bytes32 private _eip712DomainReturn_5; |
||
| 93 |
uint256[] private _eip712DomainReturn_6; |
||
| 94 |
uint256 private _nonceReturn_0; |
||
| 95 | |||
| 96 |
//<>=============================================================<> |
||
| 97 |
//|| || |
||
| 98 |
//|| ⚠️ VIEW FUNCTIONS - DO NOT MODIFY ⚠️ || |
||
| 99 |
//|| || |
||
| 100 |
//<>=============================================================<> |
||
| 101 |
// Mock implementation of SUPER_GOVERNOR |
||
| 102 |
function SUPER_GOVERNOR() public view returns (address) {
|
||
| 103 |
return _SUPER_GOVERNORReturn_0; |
||
| 104 |
} |
||
| 105 | |||
| 106 |
// Mock implementation of UPDATE_PPS_TYPEHASH |
||
| 107 |
function UPDATE_PPS_TYPEHASH() public view returns (bytes32) {
|
||
| 108 |
return _UPDATE_PPS_TYPEHASHReturn_0; |
||
| 109 |
} |
||
| 110 | |||
| 111 |
// Mock implementation of domainSeparator |
||
| 112 |
function domainSeparator() public view returns (bytes32) {
|
||
| 113 |
return _domainSeparatorReturn_0; |
||
| 114 |
} |
||
| 115 | |||
| 116 |
// Mock implementation of eip712Domain |
||
| 117 |
function eip712Domain() |
||
| 118 |
public |
||
| 119 |
view |
||
| 120 |
returns ( |
||
| 121 |
bytes1, |
||
| 122 |
string memory, |
||
| 123 |
string memory, |
||
| 124 |
uint256, |
||
| 125 |
address, |
||
| 126 |
bytes32, |
||
| 127 |
uint256[] memory |
||
| 128 |
) |
||
| 129 |
{
|
||
| 130 |
return ( |
||
| 131 |
_eip712DomainReturn_0, |
||
| 132 |
_eip712DomainReturn_1, |
||
| 133 |
_eip712DomainReturn_2, |
||
| 134 |
_eip712DomainReturn_3, |
||
| 135 |
_eip712DomainReturn_4, |
||
| 136 |
_eip712DomainReturn_5, |
||
| 137 |
_eip712DomainReturn_6 |
||
| 138 |
); |
||
| 139 |
} |
||
| 140 | |||
| 141 |
// Mock implementation of nonce |
||
| 142 |
function nonce() public view returns (uint256) {
|
||
| 143 |
return _nonceReturn_0; |
||
| 144 |
} |
||
| 145 |
} |
||
| 146 |
87.0%
test/recon/mocks/MockERC4626Tester.sol
Lines covered: 80 / 92 (87.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
// Heavily inspired by https://github.com/liquity/V2-gov/blob/9632de9a988522775336d9b60cdf2542efc600db/test/mocks/MaliciousInitiative.sol |
||
| 3 |
pragma solidity ^0.8.0; |
||
| 4 | |||
| 5 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 6 | |||
| 7 |
abstract contract ERC4626 is MockERC20 {
|
||
| 8 |
✓ 133.5K
|
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 |
✓ 146.6K
|
constructor(MockERC20 _asset) MockERC20("MockERC4626Tester", "MCT", 18) {
|
|
| 25 |
✓ 146.6K
|
asset = _asset; |
|
| 26 |
} |
||
| 27 | |||
| 28 |
function deposit( |
||
| 29 |
uint256 assets, |
||
| 30 |
address receiver |
||
| 31 |
✓ 33.2K
|
) public virtual returns (uint256) {
|
|
| 32 |
✓ 33.2K
|
uint256 shares = previewDeposit(assets); |
|
| 33 |
✓ 32.7K
|
_deposit(msg.sender, receiver, assets, shares); |
|
| 34 |
return shares; |
||
| 35 |
} |
||
| 36 | |||
| 37 |
function mint( |
||
| 38 |
uint256 shares, |
||
| 39 |
address receiver |
||
| 40 |
✓ 229.6K
|
) public virtual returns (uint256) {
|
|
| 41 |
✓ 229.6K
|
uint256 assets = previewMint(shares); |
|
| 42 |
✓ 227.9K
|
_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 |
✓ 316.3K
|
function totalAssets() public view virtual returns (uint256) {
|
|
| 67 |
✓ 316.3K
|
return asset.balanceOf(address(this)); |
|
| 68 |
} |
||
| 69 | |||
| 70 |
function convertToShares( |
||
| 71 |
uint256 assets |
||
| 72 |
✓ 310.2K
|
) public view virtual returns (uint256) {
|
|
| 73 |
✓ 310.2K
|
uint256 supply = totalSupply; |
|
| 74 |
✓ 310.2K
|
return supply == 0 ? assets : (assets * supply) / totalAssets(); |
|
| 75 |
} |
||
| 76 | |||
| 77 |
function convertToAssets( |
||
| 78 |
uint256 shares |
||
| 79 |
✓ 435.6K
|
) public view virtual returns (uint256) {
|
|
| 80 |
✓ 435.6K
|
uint256 supply = totalSupply; |
|
| 81 |
✓ 435.6K
|
return supply == 0 ? shares : (shares * totalAssets()) / supply; |
|
| 82 |
} |
||
| 83 | |||
| 84 |
function previewDeposit( |
||
| 85 |
uint256 assets |
||
| 86 |
✓ 146.6K
|
) public view virtual returns (uint256) {
|
|
| 87 |
✓ 146.6K
|
return convertToShares(assets); |
|
| 88 |
} |
||
| 89 | |||
| 90 |
function previewMint(uint256 shares) public view virtual returns (uint256) {
|
||
| 91 |
uint256 supply = totalSupply; |
||
| 92 |
return supply == 0 ? shares : (shares * totalAssets()) / supply; |
||
| 93 |
} |
||
| 94 | |||
| 95 |
function previewWithdraw( |
||
| 96 |
uint256 assets |
||
| 97 |
) public view virtual returns (uint256) {
|
||
| 98 |
uint256 supply = totalSupply; |
||
| 99 |
return supply == 0 ? assets : (assets * supply) / totalAssets(); |
||
| 100 |
} |
||
| 101 | |||
| 102 |
function previewRedeem( |
||
| 103 |
uint256 shares |
||
| 104 |
✓ 198.5K
|
) public view virtual returns (uint256) {
|
|
| 105 |
✓ 198.5K
|
return convertToAssets(shares); |
|
| 106 |
} |
||
| 107 | |||
| 108 |
function maxDeposit(address) public view virtual returns (uint256) {
|
||
| 109 |
✓ 13.2K
|
return type(uint256).max; |
|
| 110 |
} |
||
| 111 | |||
| 112 |
function maxMint(address) public view virtual returns (uint256) {
|
||
| 113 |
return type(uint256).max; |
||
| 114 |
} |
||
| 115 | |||
| 116 |
✓ 5.9K
|
function maxWithdraw(address owner) public view virtual returns (uint256) {
|
|
| 117 |
✓ 5.9K
|
return convertToAssets(balanceOf[owner]); |
|
| 118 |
} |
||
| 119 | |||
| 120 |
✓ 5.6K
|
function maxRedeem(address owner) public view virtual returns (uint256) {
|
|
| 121 |
✓ 5.6K
|
return balanceOf[owner]; |
|
| 122 |
} |
||
| 123 | |||
| 124 |
function _deposit( |
||
| 125 |
address caller, |
||
| 126 |
address receiver, |
||
| 127 |
uint256 assets, |
||
| 128 |
uint256 shares |
||
| 129 |
) internal virtual {
|
||
| 130 |
✓ 260.6K
|
asset.transferFrom(caller, address(this), assets); |
|
| 131 |
✓ 194.2K
|
_mint(receiver, shares); |
|
| 132 |
✓ 192.7K
|
emit Deposit(caller, receiver, assets, shares); |
|
| 133 |
} |
||
| 134 | |||
| 135 |
function _withdraw( |
||
| 136 |
address caller, |
||
| 137 |
address receiver, |
||
| 138 |
address owner, |
||
| 139 |
uint256 assets, |
||
| 140 |
uint256 shares |
||
| 141 |
) internal virtual {
|
||
| 142 |
✓ 272.9K
|
if (caller != owner) {
|
|
| 143 |
✓ 138.9K
|
allowance[owner][caller] -= shares; |
|
| 144 |
} |
||
| 145 |
✓ 138.8K
|
_burn(owner, shares); |
|
| 146 |
✓ 129.3K
|
asset.transfer(receiver, assets); |
|
| 147 |
✓ 129.3K
|
emit Withdraw(caller, receiver, owner, assets, shares); |
|
| 148 |
} |
||
| 149 |
} |
||
| 150 | |||
| 151 |
enum FunctionType {
|
||
| 152 |
NONE, |
||
| 153 |
DEPOSIT, |
||
| 154 |
MINT, |
||
| 155 |
WITHDRAW, |
||
| 156 |
REDEEM |
||
| 157 |
} |
||
| 158 | |||
| 159 |
enum RevertType {
|
||
| 160 |
NONE, |
||
| 161 |
THROW, |
||
| 162 |
OOG, |
||
| 163 |
RETURN_BOMB, |
||
| 164 |
REVERT_BOMB |
||
| 165 |
} |
||
| 166 | |||
| 167 |
/// @dev This will use the simplest possible implementation initially to allow getting coverage and will be expanded on as necessary for testing potentially more interesting behaviors |
||
| 168 |
/// @dev Note that blindspots not testable with this current implementation are covered in the ERC4626-integrations.md file |
||
| 169 |
contract MockERC4626Tester is ERC4626 {
|
||
| 170 |
mapping(FunctionType => RevertType) public revertBehaviours; |
||
| 171 | |||
| 172 |
uint8 public decimalsOffset; |
||
| 173 |
/// @dev Track total losses |
||
| 174 |
uint256 public totalLosses; |
||
| 175 |
uint256 public totalGains; |
||
| 176 |
uint256 public lossOnWithdraw; |
||
| 177 |
✓ 146.6K
|
uint256 public MAX_BPS = 10_000; |
|
| 178 | |||
| 179 |
✓ 146.6K
|
constructor(address _asset) ERC4626(MockERC20(_asset)) {}
|
|
| 180 | |||
| 181 |
/// Standard ERC4626 functions /// |
||
| 182 | |||
| 183 |
/// @dev Deposit assets, reverts as specified |
||
| 184 |
function deposit( |
||
| 185 |
uint256 assets, |
||
| 186 |
address receiver |
||
| 187 |
✓ 33.2K
|
) public override returns (uint256) {
|
|
| 188 |
✓ 33.2K
|
_performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
|
| 189 |
✓ 33.2K
|
return super.deposit(assets, receiver); |
|
| 190 |
} |
||
| 191 | |||
| 192 |
/// @dev Mint shares, reverts as specified |
||
| 193 |
function mint( |
||
| 194 |
uint256 shares, |
||
| 195 |
address receiver |
||
| 196 |
✓ 229.6K
|
) public override returns (uint256) {
|
|
| 197 |
✓ 229.6K
|
_performRevertBehaviour(revertBehaviours[FunctionType.MINT]); |
|
| 198 |
✓ 229.6K
|
return super.mint(shares, receiver); |
|
| 199 |
} |
||
| 200 | |||
| 201 |
/// @dev Withdraw assets, reverts as specified |
||
| 202 |
function withdraw( |
||
| 203 |
uint256 assets, |
||
| 204 |
address receiver, |
||
| 205 |
address owner |
||
| 206 |
✓ 93.5K
|
) public override returns (uint256) {
|
|
| 207 |
✓ 93.5K
|
_performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]); |
|
| 208 | |||
| 209 |
✓ 93.5K
|
uint256 shares = previewWithdraw(assets); |
|
| 210 |
✓ 86.5K
|
uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
|
| 211 |
✓ 81.0K
|
_withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
|
| 212 | |||
| 213 |
✓ 974
|
return shares; |
|
| 214 |
} |
||
| 215 | |||
| 216 |
/// @dev Redeem shares, reverts as specified |
||
| 217 |
function redeem( |
||
| 218 |
uint256 shares, |
||
| 219 |
address receiver, |
||
| 220 |
address owner |
||
| 221 |
✓ 198.5K
|
) public override returns (uint256) {
|
|
| 222 |
✓ 198.5K
|
_performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
|
| 223 | |||
| 224 |
✓ 198.5K
|
uint256 assets = previewRedeem(shares); |
|
| 225 |
✓ 197.0K
|
uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
|
| 226 |
✓ 191.9K
|
_withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
|
| 227 | |||
| 228 |
✓ 128.3K
|
return lossyAssets; |
|
| 229 |
} |
||
| 230 | |||
| 231 |
/// @dev Preview deposit, reverts as specified |
||
| 232 |
function previewDeposit( |
||
| 233 |
uint256 assets |
||
| 234 |
✓ 146.6K
|
) public view override returns (uint256) {
|
|
| 235 |
✓ 146.6K
|
_performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]); |
|
| 236 |
✓ 146.6K
|
return super.previewDeposit(assets); |
|
| 237 |
} |
||
| 238 | |||
| 239 |
/// @dev Preview mint, reverts as specified |
||
| 240 |
function previewMint( |
||
| 241 |
uint256 shares |
||
| 242 |
✓ 231.2K
|
) public view override returns (uint256) {
|
|
| 243 |
✓ 231.2K
|
_performRevertBehaviour(revertBehaviours[FunctionType.MINT]); |
|
| 244 |
✓ 231.2K
|
return super.previewMint(shares); |
|
| 245 |
} |
||
| 246 | |||
| 247 |
/// @dev Preview withdraw, reverts as specified |
||
| 248 |
function previewWithdraw( |
||
| 249 |
uint256 assets |
||
| 250 |
✓ 93.5K
|
) public view override returns (uint256) {
|
|
| 251 |
✓ 1.2M
|
_performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]); |
|
| 252 |
✓ 93.5K
|
return super.previewWithdraw(assets); |
|
| 253 |
} |
||
| 254 | |||
| 255 |
/// @dev Preview redeem, reverts as specified |
||
| 256 |
function previewRedeem( |
||
| 257 |
uint256 shares |
||
| 258 |
✓ 198.5K
|
) public view override returns (uint256) {
|
|
| 259 |
✓ 198.5K
|
_performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]); |
|
| 260 |
✓ 198.5K
|
return super.previewRedeem(shares); |
|
| 261 |
} |
||
| 262 | |||
| 263 |
/// @dev Revert in different ways to test the revert behaviour |
||
| 264 |
function _performRevertBehaviour(RevertType action) internal pure {
|
||
| 265 |
✓ 1.2M
|
if (action == RevertType.THROW) {
|
|
| 266 |
revert("A normal Revert");
|
||
| 267 |
} |
||
| 268 | |||
| 269 |
// 3 gas per iteration, consider changing to storage changes if traces are cluttered |
||
| 270 |
✓ 1.2M
|
if (action == RevertType.OOG) {
|
|
| 271 |
uint256 i; |
||
| 272 |
while (true) {
|
||
| 273 |
++i; |
||
| 274 |
} |
||
| 275 |
} |
||
| 276 | |||
| 277 |
✓ 1.2M
|
if (action == RevertType.RETURN_BOMB) {
|
|
| 278 |
uint256 _bytes = 2_000_000; |
||
| 279 |
assembly {
|
||
| 280 |
return(0, _bytes) |
||
| 281 |
} |
||
| 282 |
} |
||
| 283 | |||
| 284 |
✓ 1.2M
|
if (action == RevertType.REVERT_BOMB) {
|
|
| 285 |
uint256 _bytes = 2_000_000; |
||
| 286 |
assembly {
|
||
| 287 |
revert(0, _bytes) |
||
| 288 |
} |
||
| 289 |
} |
||
| 290 | |||
| 291 |
return; // NONE |
||
| 292 |
} |
||
| 293 | |||
| 294 |
/// Custom functions for testing vault behavior /// |
||
| 295 | |||
| 296 |
/// @dev Specify the revert behavior on each function |
||
| 297 |
function setRevertBehavior(FunctionType ft, RevertType rt) public {
|
||
| 298 |
✓ 412
|
revertBehaviours[ft] = rt; |
|
| 299 |
} |
||
| 300 | |||
| 301 |
/// @dev Simulate a loss on the vault's assets |
||
| 302 |
function simulateLoss(uint256 lossAmount) external {
|
||
| 303 |
✓ 86.0K
|
MockERC20(asset).transfer(address(0xbeef), lossAmount); |
|
| 304 |
✓ 1.3K
|
totalLosses += lossAmount; |
|
| 305 |
} |
||
| 306 | |||
| 307 |
/// @dev Simulate a gain on the vault's assets (similar to Yearn's profit taking) |
||
| 308 |
function simulateGain(uint256 gainAmount) external {
|
||
| 309 |
✓ 71.7K
|
MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
|
| 310 |
✓ 13.0K
|
totalGains += gainAmount; |
|
| 311 |
} |
||
| 312 | |||
| 313 |
/// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
||
| 314 |
function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
|
||
| 315 |
✓ 69.7K
|
_lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
|
| 316 |
✓ 69.7K
|
lossOnWithdraw = _lossOnWithdraw; |
|
| 317 |
} |
||
| 318 | |||
| 319 |
/// @dev Set the decimal offset. Only possible with no supply. |
||
| 320 |
function setDecimalsOffset(uint8 targetDecimalsOffset) external {
|
||
| 321 |
✓ 27.3K
|
if (totalSupply != 0) {
|
|
| 322 |
✓ 611
|
revert("Supply is not zero");
|
|
| 323 |
} |
||
| 324 |
✓ 26.6K
|
decimalsOffset = targetDecimalsOffset; |
|
| 325 |
} |
||
| 326 |
} |
||
| 327 |
8.5%
test/recon/mocks/MockERC4626YieldSourceOracle.sol
Lines covered: 4 / 47 (8.5%)
| 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 |
✓ 1
|
contract MockERC4626YieldSourceOracle is IYieldSourceOracle {
|
|
| 10 |
mapping(address => bool) public validAssetMap; |
||
| 11 |
|
||
| 12 |
function setValidAsset(address asset, bool isValid) external {
|
||
| 13 |
✓ 324.6K
|
validAssetMap[asset] = isValid; |
|
| 14 |
} |
||
| 15 |
|
||
| 16 |
function decimals(address yieldSourceAddress) external view returns (uint8) {
|
||
| 17 |
return IERC4626(yieldSourceAddress).decimals(); |
||
| 18 |
} |
||
| 19 |
|
||
| 20 |
function getShareOutput( |
||
| 21 |
address yieldSourceAddress, |
||
| 22 |
address, |
||
| 23 |
uint256 assetsIn |
||
| 24 |
✓ 116.3K
|
) external view returns (uint256) {
|
|
| 25 |
✓ 116.3K
|
return IERC4626(yieldSourceAddress).previewDeposit(assetsIn); |
|
| 26 |
} |
||
| 27 |
|
||
| 28 |
function getAssetOutput( |
||
| 29 |
address yieldSourceAddress, |
||
| 30 |
address, |
||
| 31 |
uint256 sharesIn |
||
| 32 |
) public view returns (uint256) {
|
||
| 33 |
return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
||
| 34 |
} |
||
| 35 |
|
||
| 36 |
function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
|
||
| 37 |
IERC4626 yieldSource = IERC4626(yieldSourceAddress); |
||
| 38 |
uint256 _decimals = yieldSource.decimals(); |
||
| 39 |
return yieldSource.convertToAssets(10 ** _decimals); |
||
| 40 |
} |
||
| 41 |
|
||
| 42 |
function getBalanceOfOwner( |
||
| 43 |
address yieldSourceAddress, |
||
| 44 |
address ownerOfShares |
||
| 45 |
) external view returns (uint256) {
|
||
| 46 |
return IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
||
| 47 |
} |
||
| 48 |
|
||
| 49 |
function getTVLByOwnerOfShares( |
||
| 50 |
address yieldSourceAddress, |
||
| 51 |
address ownerOfShares |
||
| 52 |
) external view returns (uint256) {
|
||
| 53 |
uint256 shares = IERC4626(yieldSourceAddress).balanceOf(ownerOfShares); |
||
| 54 |
return IERC4626(yieldSourceAddress).convertToAssets(shares); |
||
| 55 |
} |
||
| 56 |
|
||
| 57 |
function getTVL(address yieldSourceAddress) external view returns (uint256) {
|
||
| 58 |
return IERC4626(yieldSourceAddress).totalAssets(); |
||
| 59 |
} |
||
| 60 |
|
||
| 61 |
function getPricePerShareMultiple( |
||
| 62 |
address[] memory yieldSourceAddresses |
||
| 63 |
) external view returns (uint256[] memory) {
|
||
| 64 |
uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
||
| 65 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 66 |
IERC4626 yieldSource = IERC4626(yieldSourceAddresses[i]); |
||
| 67 |
uint256 _decimals = yieldSource.decimals(); |
||
| 68 |
prices[i] = yieldSource.convertToAssets(10 ** _decimals); |
||
| 69 |
} |
||
| 70 |
return prices; |
||
| 71 |
} |
||
| 72 |
|
||
| 73 |
function getTVLByOwnerOfSharesMultiple( |
||
| 74 |
address[] memory yieldSourceAddresses, |
||
| 75 |
address[][] memory ownersOfShares |
||
| 76 |
) external view returns (uint256[][] memory) {
|
||
| 77 |
uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
||
| 78 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 79 |
result[i] = new uint256[](ownersOfShares[i].length); |
||
| 80 |
for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
|
||
| 81 |
uint256 shares = IERC4626(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
||
| 82 |
result[i][j] = IERC4626(yieldSourceAddresses[i]).convertToAssets(shares); |
||
| 83 |
} |
||
| 84 |
} |
||
| 85 |
return result; |
||
| 86 |
} |
||
| 87 |
|
||
| 88 |
function getTVLMultiple( |
||
| 89 |
address[] memory yieldSourceAddresses |
||
| 90 |
) external view returns (uint256[] memory) {
|
||
| 91 |
uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
||
| 92 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 93 |
tvls[i] = IERC4626(yieldSourceAddresses[i]).totalAssets(); |
||
| 94 |
} |
||
| 95 |
return tvls; |
||
| 96 |
} |
||
| 97 |
|
||
| 98 |
function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
|
||
| 99 |
return validAssetMap[asset]; |
||
| 100 |
} |
||
| 101 |
|
||
| 102 |
function isValidUnderlyingAssets( |
||
| 103 |
address[] memory, |
||
| 104 |
address[] memory assets |
||
| 105 |
) external view returns (bool[] memory) {
|
||
| 106 |
bool[] memory validities = new bool[](assets.length); |
||
| 107 |
for (uint256 i = 0; i < assets.length; i++) {
|
||
| 108 |
validities[i] = validAssetMap[assets[i]]; |
||
| 109 |
} |
||
| 110 |
return validities; |
||
| 111 |
} |
||
| 112 |
|
||
| 113 |
function getAssetOutputWithFees( |
||
| 114 |
bytes32, |
||
| 115 |
address yieldSourceAddress, |
||
| 116 |
address, |
||
| 117 |
address, |
||
| 118 |
uint256 sharesIn |
||
| 119 |
) external view returns (uint256) {
|
||
| 120 |
return IERC4626(yieldSourceAddress).previewRedeem(sharesIn); |
||
| 121 |
} |
||
| 122 |
} |
22.1%
test/recon/mocks/MockERC5115Tester.sol
Lines covered: 19 / 86 (22.1%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 5 | |||
| 6 |
abstract contract ERC5115 is MockERC20 {
|
||
| 7 |
MockERC20 public immutable yieldToken; |
||
| 8 |
address[] public tokensIn; |
||
| 9 |
address[] public tokensOut; |
||
| 10 | |||
| 11 |
event Deposit( |
||
| 12 |
address indexed caller, |
||
| 13 |
address indexed receiver, |
||
| 14 |
address indexed tokenIn, |
||
| 15 |
uint256 amountDeposited, |
||
| 16 |
uint256 amountSyOut |
||
| 17 |
); |
||
| 18 | |||
| 19 |
event Redeem( |
||
| 20 |
address indexed caller, |
||
| 21 |
address indexed receiver, |
||
| 22 |
address indexed tokenOut, |
||
| 23 |
uint256 amountSyToRedeem, |
||
| 24 |
uint256 amountTokenOut |
||
| 25 |
); |
||
| 26 | |||
| 27 |
constructor( |
||
| 28 |
MockERC20 _yieldToken |
||
| 29 |
✓ 1
|
) MockERC20("MockERC5115Tester", "SY5115", 18) {
|
|
| 30 |
✓ 1
|
yieldToken = _yieldToken; |
|
| 31 |
✓ 1
|
tokensIn.push(address(_yieldToken)); |
|
| 32 |
✓ 1
|
tokensOut.push(address(_yieldToken)); |
|
| 33 |
} |
||
| 34 | |||
| 35 |
function deposit( |
||
| 36 |
address receiver, |
||
| 37 |
address tokenIn, |
||
| 38 |
uint256 amountTokenToDeposit, |
||
| 39 |
uint256 minSharesOut, |
||
| 40 |
bool depositFromInternalBalance |
||
| 41 |
) public virtual returns (uint256 amountSharesOut) {
|
||
| 42 |
amountSharesOut = previewDeposit(tokenIn, amountTokenToDeposit); |
||
| 43 | |||
| 44 |
MockERC20(tokenIn).transferFrom( |
||
| 45 |
msg.sender, |
||
| 46 |
address(this), |
||
| 47 |
amountTokenToDeposit |
||
| 48 |
); |
||
| 49 |
_mint(receiver, amountSharesOut); |
||
| 50 | |||
| 51 |
emit Deposit( |
||
| 52 |
msg.sender, |
||
| 53 |
receiver, |
||
| 54 |
tokenIn, |
||
| 55 |
amountTokenToDeposit, |
||
| 56 |
amountSharesOut |
||
| 57 |
); |
||
| 58 |
} |
||
| 59 | |||
| 60 |
function exchangeRate() public view virtual returns (uint256) {
|
||
| 61 |
uint256 supply = totalSupply; |
||
| 62 |
if (supply == 0) return 1e18; |
||
| 63 |
return (yieldToken.balanceOf(address(this)) * 1e18) / supply; |
||
| 64 |
} |
||
| 65 | |||
| 66 |
function getTokensIn() public view virtual returns (address[] memory) {
|
||
| 67 |
return tokensIn; |
||
| 68 |
} |
||
| 69 | |||
| 70 |
function getTokensOut() public view virtual returns (address[] memory) {
|
||
| 71 |
return tokensOut; |
||
| 72 |
} |
||
| 73 | |||
| 74 |
function previewDeposit( |
||
| 75 |
address tokenIn, |
||
| 76 |
uint256 amountTokenToDeposit |
||
| 77 |
✓ 842
|
) public view virtual returns (uint256 amountSharesOut) {
|
|
| 78 |
✓ 842
|
require(tokenIn == address(yieldToken), "Invalid token"); |
|
| 79 |
✓ 711
|
uint256 supply = totalSupply; |
|
| 80 |
✓ 711
|
if (supply == 0) {
|
|
| 81 |
✓ 711
|
return amountTokenToDeposit; |
|
| 82 |
} |
||
| 83 |
return |
||
| 84 |
(amountTokenToDeposit * supply) / |
||
| 85 |
yieldToken.balanceOf(address(this)); |
||
| 86 |
} |
||
| 87 | |||
| 88 |
function previewRedeem( |
||
| 89 |
address tokenOut, |
||
| 90 |
uint256 amountSharesToRedeem |
||
| 91 |
) public view virtual returns (uint256 amountTokenOut) {
|
||
| 92 |
require(tokenOut == address(yieldToken), "Invalid token"); |
||
| 93 |
uint256 supply = totalSupply; |
||
| 94 |
if (supply == 0) {
|
||
| 95 |
return amountSharesToRedeem; |
||
| 96 |
} |
||
| 97 |
return |
||
| 98 |
(amountSharesToRedeem * yieldToken.balanceOf(address(this))) / |
||
| 99 |
supply; |
||
| 100 |
} |
||
| 101 |
} |
||
| 102 | |||
| 103 |
enum RevertType {
|
||
| 104 |
NONE, |
||
| 105 |
THROW, |
||
| 106 |
OOG, |
||
| 107 |
RETURN_BOMB, |
||
| 108 |
REVERT_BOMB |
||
| 109 |
} |
||
| 110 | |||
| 111 |
contract MockERC5115Tester is ERC5115 {
|
||
| 112 |
RevertType public revertBehaviour; |
||
| 113 |
uint256 public totalLosses; |
||
| 114 |
uint256 public totalGains; |
||
| 115 |
uint256 public lossOnWithdraw; |
||
| 116 |
✓ 1
|
uint256 public MAX_BPS = 10_000; |
|
| 117 | |||
| 118 |
✓ 1
|
constructor(address _yieldToken) ERC5115(MockERC20(_yieldToken)) {}
|
|
| 119 | |||
| 120 |
function deposit( |
||
| 121 |
address receiver, |
||
| 122 |
address tokenIn, |
||
| 123 |
uint256 amountTokenToDeposit, |
||
| 124 |
uint256 minSharesOut, |
||
| 125 |
bool depositFromInternalBalance |
||
| 126 |
) public override returns (uint256 amountSharesOut) {
|
||
| 127 |
_performRevertBehaviour(revertBehaviour); |
||
| 128 |
return |
||
| 129 |
super.deposit( |
||
| 130 |
receiver, |
||
| 131 |
tokenIn, |
||
| 132 |
amountTokenToDeposit, |
||
| 133 |
minSharesOut, |
||
| 134 |
depositFromInternalBalance |
||
| 135 |
); |
||
| 136 |
} |
||
| 137 | |||
| 138 |
function redeem( |
||
| 139 |
address receiver, |
||
| 140 |
uint256 amountSharesToRedeem, |
||
| 141 |
address tokenOut, |
||
| 142 |
uint256 minTokenOut, |
||
| 143 |
bool burnFromInternalBalance |
||
| 144 |
) public virtual returns (uint256 amountTokenOut) {
|
||
| 145 |
amountTokenOut = previewRedeem(tokenOut, amountSharesToRedeem); |
||
| 146 | |||
| 147 |
_burn(msg.sender, amountSharesToRedeem); |
||
| 148 |
uint256 lossyAmountTokenOut = amountTokenOut - |
||
| 149 |
((amountTokenOut * lossOnWithdraw) / MAX_BPS); |
||
| 150 |
MockERC20(tokenOut).transfer(receiver, lossyAmountTokenOut); |
||
| 151 | |||
| 152 |
emit Redeem( |
||
| 153 |
msg.sender, |
||
| 154 |
receiver, |
||
| 155 |
tokenOut, |
||
| 156 |
amountSharesToRedeem, |
||
| 157 |
amountTokenOut |
||
| 158 |
); |
||
| 159 |
} |
||
| 160 | |||
| 161 |
function _performRevertBehaviour(RevertType action) internal pure {
|
||
| 162 |
if (action == RevertType.THROW) {
|
||
| 163 |
revert("A normal Revert");
|
||
| 164 |
} |
||
| 165 | |||
| 166 |
if (action == RevertType.OOG) {
|
||
| 167 |
uint256 i; |
||
| 168 |
while (true) {
|
||
| 169 |
++i; |
||
| 170 |
} |
||
| 171 |
} |
||
| 172 | |||
| 173 |
if (action == RevertType.RETURN_BOMB) {
|
||
| 174 |
uint256 _bytes = 2_000_000; |
||
| 175 |
assembly {
|
||
| 176 |
return(0, _bytes) |
||
| 177 |
} |
||
| 178 |
} |
||
| 179 | |||
| 180 |
if (action == RevertType.REVERT_BOMB) {
|
||
| 181 |
uint256 _bytes = 2_000_000; |
||
| 182 |
assembly {
|
||
| 183 |
revert(0, _bytes) |
||
| 184 |
} |
||
| 185 |
} |
||
| 186 | |||
| 187 |
return; // NONE |
||
| 188 |
} |
||
| 189 | |||
| 190 |
function setRevertBehavior(RevertType rt) public {
|
||
| 191 |
revertBehaviour = rt; |
||
| 192 |
} |
||
| 193 | |||
| 194 |
function simulateLoss(uint256 lossAmount) external {
|
||
| 195 |
✓ 26.3K
|
MockERC20(yieldToken).transfer(address(0xbeef), lossAmount); |
|
| 196 |
✓ 434
|
totalLosses += lossAmount; |
|
| 197 |
} |
||
| 198 | |||
| 199 |
function simulateGain(uint256 gainAmount) external {
|
||
| 200 |
✓ 23.8K
|
MockERC20(yieldToken).transferFrom( |
|
| 201 |
✓ 23.8K
|
msg.sender, |
|
| 202 |
✓ 23.8K
|
address(this), |
|
| 203 |
gainAmount |
||
| 204 |
); |
||
| 205 |
✓ 4.1K
|
totalGains += gainAmount; |
|
| 206 |
} |
||
| 207 | |||
| 208 |
/// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
||
| 209 |
function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
|
||
| 210 |
✓ 30.4K
|
_lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
|
| 211 |
✓ 30.4K
|
lossOnWithdraw = _lossOnWithdraw; |
|
| 212 |
} |
||
| 213 | |||
| 214 |
function increaseYield(uint256 increasePercentageFP4) public {
|
||
| 215 |
require(increasePercentageFP4 <= 10000, "Invalid percentage"); |
||
| 216 |
uint256 amount = (yieldToken.balanceOf(address(this)) * |
||
| 217 |
increasePercentageFP4) / 10000; |
||
| 218 |
MockERC20(yieldToken).transferFrom(msg.sender, address(this), amount); |
||
| 219 |
} |
||
| 220 | |||
| 221 |
function decreaseYield(uint256 decreasePercentageFP4) public {
|
||
| 222 |
require(decreasePercentageFP4 <= 10000, "Invalid percentage"); |
||
| 223 |
uint256 amount = (yieldToken.balanceOf(address(this)) * |
||
| 224 |
decreasePercentageFP4) / 10000; |
||
| 225 |
MockERC20(yieldToken).transfer(address(0xbeef), amount); |
||
| 226 |
totalLosses += amount; |
||
| 227 |
} |
||
| 228 |
} |
||
| 229 |
8.3%
test/recon/mocks/MockERC5115YieldSourceOracle.sol
Lines covered: 4 / 48 (8.3%)
| 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 |
✓ 1
|
contract MockERC5115YieldSourceOracle is IYieldSourceOracle {
|
|
| 10 |
mapping(address => bool) public validAssetMap; |
||
| 11 |
|
||
| 12 |
function setValidAsset(address asset, bool isValid) external {
|
||
| 13 |
✓ 131.0K
|
validAssetMap[asset] = isValid; |
|
| 14 |
} |
||
| 15 |
|
||
| 16 |
function decimals(address) external pure returns (uint8) {
|
||
| 17 |
// ERC5115 always uses 18 decimals |
||
| 18 |
return 18; |
||
| 19 |
} |
||
| 20 |
|
||
| 21 |
function getShareOutput( |
||
| 22 |
address yieldSourceAddress, |
||
| 23 |
address assetIn, |
||
| 24 |
uint256 assetsIn |
||
| 25 |
✓ 23.6K
|
) external view returns (uint256) {
|
|
| 26 |
✓ 23.6K
|
return MockERC5115Tester(yieldSourceAddress).previewDeposit(assetIn, assetsIn); |
|
| 27 |
} |
||
| 28 |
|
||
| 29 |
function getAssetOutput( |
||
| 30 |
address yieldSourceAddress, |
||
| 31 |
address assetOut, |
||
| 32 |
uint256 sharesIn |
||
| 33 |
) public view returns (uint256) {
|
||
| 34 |
return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
||
| 35 |
} |
||
| 36 |
|
||
| 37 |
function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
|
||
| 38 |
return MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
||
| 39 |
} |
||
| 40 |
|
||
| 41 |
function getBalanceOfOwner( |
||
| 42 |
address yieldSourceAddress, |
||
| 43 |
address ownerOfShares |
||
| 44 |
) external view returns (uint256) {
|
||
| 45 |
return MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
||
| 46 |
} |
||
| 47 |
|
||
| 48 |
function getTVLByOwnerOfShares( |
||
| 49 |
address yieldSourceAddress, |
||
| 50 |
address ownerOfShares |
||
| 51 |
) external view returns (uint256) {
|
||
| 52 |
uint256 shares = MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares); |
||
| 53 |
uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
||
| 54 |
return (shares * exchangeRate) / 1e18; |
||
| 55 |
} |
||
| 56 |
|
||
| 57 |
function getTVL(address yieldSourceAddress) external view returns (uint256) {
|
||
| 58 |
uint256 totalShares = MockERC5115Tester(yieldSourceAddress).totalSupply(); |
||
| 59 |
uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate(); |
||
| 60 |
return (totalShares * exchangeRate) / 1e18; |
||
| 61 |
} |
||
| 62 |
|
||
| 63 |
function getPricePerShareMultiple( |
||
| 64 |
address[] memory yieldSourceAddresses |
||
| 65 |
) external view returns (uint256[] memory) {
|
||
| 66 |
uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
||
| 67 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 68 |
prices[i] = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
||
| 69 |
} |
||
| 70 |
return prices; |
||
| 71 |
} |
||
| 72 |
|
||
| 73 |
function getTVLByOwnerOfSharesMultiple( |
||
| 74 |
address[] memory yieldSourceAddresses, |
||
| 75 |
address[][] memory ownersOfShares |
||
| 76 |
) external view returns (uint256[][] memory) {
|
||
| 77 |
uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
||
| 78 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 79 |
result[i] = new uint256[](ownersOfShares[i].length); |
||
| 80 |
uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
||
| 81 |
for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
|
||
| 82 |
uint256 shares = MockERC5115Tester(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]); |
||
| 83 |
result[i][j] = (shares * exchangeRate) / 1e18; |
||
| 84 |
} |
||
| 85 |
} |
||
| 86 |
return result; |
||
| 87 |
} |
||
| 88 |
|
||
| 89 |
function getTVLMultiple( |
||
| 90 |
address[] memory yieldSourceAddresses |
||
| 91 |
) external view returns (uint256[] memory) {
|
||
| 92 |
uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
||
| 93 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 94 |
uint256 totalShares = MockERC5115Tester(yieldSourceAddresses[i]).totalSupply(); |
||
| 95 |
uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate(); |
||
| 96 |
tvls[i] = (totalShares * exchangeRate) / 1e18; |
||
| 97 |
} |
||
| 98 |
return tvls; |
||
| 99 |
} |
||
| 100 |
|
||
| 101 |
function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
|
||
| 102 |
return validAssetMap[asset]; |
||
| 103 |
} |
||
| 104 |
|
||
| 105 |
function isValidUnderlyingAssets( |
||
| 106 |
address[] memory, |
||
| 107 |
address[] memory assets |
||
| 108 |
) external view returns (bool[] memory) {
|
||
| 109 |
bool[] memory validities = new bool[](assets.length); |
||
| 110 |
for (uint256 i = 0; i < assets.length; i++) {
|
||
| 111 |
validities[i] = validAssetMap[assets[i]]; |
||
| 112 |
} |
||
| 113 |
return validities; |
||
| 114 |
} |
||
| 115 |
|
||
| 116 |
function getAssetOutputWithFees( |
||
| 117 |
bytes32, |
||
| 118 |
address yieldSourceAddress, |
||
| 119 |
address assetOut, |
||
| 120 |
address, |
||
| 121 |
uint256 sharesIn |
||
| 122 |
) external view returns (uint256) {
|
||
| 123 |
return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn); |
||
| 124 |
} |
||
| 125 |
} |
69.3%
test/recon/mocks/MockERC7540Tester.sol
Lines covered: 97 / 140 (69.3%)
| 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 |
✓ 32.3K
|
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 |
✓ 1
|
asset = _asset; |
|
| 26 |
} |
||
| 27 | |||
| 28 |
function share() external view returns (address shareTokenAddress) {
|
||
| 29 |
✓ 25.8K
|
return address(this); |
|
| 30 |
} |
||
| 31 | |||
| 32 |
✓ 34.0K
|
function totalAssets() public view virtual returns (uint256) {
|
|
| 33 |
✓ 34.0K
|
return asset.balanceOf(address(this)); |
|
| 34 |
} |
||
| 35 | |||
| 36 |
function convertToShares( |
||
| 37 |
uint256 assets |
||
| 38 |
) public view virtual returns (uint256) {
|
||
| 39 |
uint256 supply = totalSupply; |
||
| 40 |
return supply == 0 ? assets : (assets * supply) / totalAssets(); |
||
| 41 |
} |
||
| 42 | |||
| 43 |
function convertToAssets( |
||
| 44 |
uint256 shares |
||
| 45 |
✓ 78.3K
|
) public view virtual returns (uint256) {
|
|
| 46 |
✓ 78.3K
|
uint256 supply = totalSupply; |
|
| 47 |
✓ 109.1K
|
return supply == 0 ? shares : (shares * totalAssets()) / supply; |
|
| 48 |
} |
||
| 49 | |||
| 50 |
function maxDeposit(address) public pure virtual returns (uint256) {
|
||
| 51 |
✓ 9.5K
|
return type(uint256).max; |
|
| 52 |
} |
||
| 53 | |||
| 54 |
function maxMint(address) public pure virtual returns (uint256) {
|
||
| 55 |
return type(uint256).max; |
||
| 56 |
} |
||
| 57 | |||
| 58 |
✓ 3.5K
|
function maxWithdraw(address owner) public view virtual returns (uint256) {
|
|
| 59 |
✓ 3.5K
|
return convertToAssets(balanceOf[owner]); |
|
| 60 |
} |
||
| 61 | |||
| 62 |
✓ 3.2K
|
function maxRedeem(address owner) public view virtual returns (uint256) {
|
|
| 63 |
✓ 3.2K
|
return balanceOf[owner]; |
|
| 64 |
} |
||
| 65 | |||
| 66 |
function previewDeposit( |
||
| 67 |
uint256 assets |
||
| 68 |
✓ 13.3K
|
) public view virtual returns (uint256) {
|
|
| 69 |
✓ 13.3K
|
return convertToShares(assets); |
|
| 70 |
} |
||
| 71 | |||
| 72 |
function previewMint(uint256 shares) public view virtual returns (uint256) {
|
||
| 73 |
uint256 supply = totalSupply; |
||
| 74 |
return supply == 0 ? shares : (shares * totalAssets()) / supply; |
||
| 75 |
} |
||
| 76 | |||
| 77 |
function previewWithdraw( |
||
| 78 |
uint256 assets |
||
| 79 |
✓ 56.1K
|
) public view virtual returns (uint256) {
|
|
| 80 |
✓ 56.1K
|
uint256 supply = totalSupply; |
|
| 81 |
✓ 56.1K
|
return supply == 0 ? assets : (assets * supply) / totalAssets(); |
|
| 82 |
} |
||
| 83 | |||
| 84 |
function previewRedeem( |
||
| 85 |
uint256 shares |
||
| 86 |
✓ 27.0K
|
) public view virtual returns (uint256) {
|
|
| 87 |
✓ 27.0K
|
return convertToAssets(shares); |
|
| 88 |
} |
||
| 89 | |||
| 90 |
function deposit( |
||
| 91 |
uint256 assets, |
||
| 92 |
address receiver |
||
| 93 |
✓ 5.4K
|
) public virtual returns (uint256 shares) {
|
|
| 94 |
✓ 5.4K
|
shares = previewDeposit(assets); |
|
| 95 |
✓ 5.3K
|
asset.transferFrom(msg.sender, address(this), assets); |
|
| 96 |
✓ 1.5K
|
_mint(receiver, shares); |
|
| 97 |
✓ 1.5K
|
emit Deposit(msg.sender, receiver, assets, shares); |
|
| 98 |
} |
||
| 99 | |||
| 100 |
function mint( |
||
| 101 |
uint256 shares, |
||
| 102 |
address receiver |
||
| 103 |
✓ 36.6K
|
) public virtual returns (uint256 assets) {
|
|
| 104 |
✓ 36.6K
|
assets = previewMint(shares); |
|
| 105 |
✓ 35.9K
|
asset.transferFrom(msg.sender, address(this), assets); |
|
| 106 |
✓ 19.2K
|
_mint(receiver, shares); |
|
| 107 |
✓ 16.0K
|
emit Deposit(msg.sender, receiver, assets, shares); |
|
| 108 |
} |
||
| 109 |
} |
||
| 110 | |||
| 111 |
contract MockERC7540Tester is ERC7575, IERC165 {
|
||
| 112 |
event DepositRequest( |
||
| 113 |
address indexed controller, |
||
| 114 |
address indexed owner, |
||
| 115 |
uint256 indexed requestId, |
||
| 116 |
address sender, |
||
| 117 |
uint256 assets |
||
| 118 |
); |
||
| 119 |
event RedeemRequest( |
||
| 120 |
address indexed controller, |
||
| 121 |
address indexed owner, |
||
| 122 |
uint256 indexed requestId, |
||
| 123 |
address sender, |
||
| 124 |
uint256 shares |
||
| 125 |
); |
||
| 126 |
event OperatorSet( |
||
| 127 |
address indexed controller, |
||
| 128 |
address indexed operator, |
||
| 129 |
bool approved |
||
| 130 |
); |
||
| 131 | |||
| 132 |
struct DepositRequestStruct {
|
||
| 133 |
uint256 assets; |
||
| 134 |
address controller; |
||
| 135 |
address owner; |
||
| 136 |
bool fulfilled; |
||
| 137 |
bool canceled; |
||
| 138 |
} |
||
| 139 | |||
| 140 |
struct RedeemRequestStruct {
|
||
| 141 |
uint256 shares; |
||
| 142 |
address controller; |
||
| 143 |
address owner; |
||
| 144 |
bool fulfilled; |
||
| 145 |
bool canceled; |
||
| 146 |
} |
||
| 147 | |||
| 148 |
✓ 1
|
uint256 private _nextRequestId = 1; |
|
| 149 | |||
| 150 |
mapping(uint256 => DepositRequestStruct) public depositRequests; |
||
| 151 |
mapping(uint256 => RedeemRequestStruct) public redeemRequests; |
||
| 152 |
mapping(address => mapping(address => bool)) public operators; |
||
| 153 |
mapping(uint256 => bool) public pendingCancelDeposit; |
||
| 154 |
mapping(uint256 => bool) public pendingCancelRedeem; |
||
| 155 | |||
| 156 |
✓ 1
|
uint256 public yieldMultiplier = 10000; // 100% in basis points |
|
| 157 |
✓ 25.8K
|
uint256 private constant MAX_BPS = 10000; |
|
| 158 |
uint256 public totalLosses; |
||
| 159 |
uint256 public totalGains; |
||
| 160 |
uint256 public lossOnWithdraw; |
||
| 161 | |||
| 162 |
✓ 1
|
constructor(address _asset) ERC7575(MockERC20(_asset)) {}
|
|
| 163 | |||
| 164 |
// Operator Management |
||
| 165 |
function setOperator( |
||
| 166 |
address operator, |
||
| 167 |
bool approved |
||
| 168 |
✓ 13.9K
|
) external returns (bool) {
|
|
| 169 |
✓ 13.9K
|
operators[msg.sender][operator] = approved; |
|
| 170 |
✓ 13.9K
|
emit OperatorSet(msg.sender, operator, approved); |
|
| 171 |
return true; |
||
| 172 |
} |
||
| 173 | |||
| 174 |
function isOperator( |
||
| 175 |
address controller, |
||
| 176 |
address operator |
||
| 177 |
) external view returns (bool) {
|
||
| 178 |
return operators[controller][operator]; |
||
| 179 |
} |
||
| 180 | |||
| 181 |
// Async Deposit Flow |
||
| 182 |
function requestDeposit( |
||
| 183 |
uint256 assets, |
||
| 184 |
address controller, |
||
| 185 |
address owner |
||
| 186 |
✓ 23.2K
|
) external returns (uint256 requestId) {
|
|
| 187 |
✓ 23.2K
|
requestId = _nextRequestId++; |
|
| 188 |
✓ 23.2K
|
depositRequests[requestId] = DepositRequestStruct({
|
|
| 189 |
✓ 23.2K
|
assets: assets, |
|
| 190 |
controller: controller, |
||
| 191 |
owner: owner, |
||
| 192 |
fulfilled: false, |
||
| 193 |
canceled: false |
||
| 194 |
}); |
||
| 195 | |||
| 196 |
✓ 23.2K
|
asset.transferFrom(msg.sender, address(this), assets); |
|
| 197 |
✓ 7.1K
|
emit DepositRequest(controller, owner, requestId, msg.sender, assets); |
|
| 198 |
} |
||
| 199 | |||
| 200 |
function pendingDepositRequest( |
||
| 201 |
uint256 requestId, |
||
| 202 |
address controller |
||
| 203 |
) external view returns (uint256) {
|
||
| 204 |
DepositRequestStruct storage request = depositRequests[requestId]; |
||
| 205 |
if ( |
||
| 206 |
request.controller != controller || |
||
| 207 |
request.fulfilled || |
||
| 208 |
request.canceled |
||
| 209 |
) {
|
||
| 210 |
✓ 8.0K
|
return 0; |
|
| 211 |
} |
||
| 212 |
return request.assets; |
||
| 213 |
} |
||
| 214 | |||
| 215 |
function claimableDepositRequest( |
||
| 216 |
uint256 requestId, |
||
| 217 |
address controller |
||
| 218 |
) external view returns (uint256) {
|
||
| 219 |
DepositRequestStruct storage request = depositRequests[requestId]; |
||
| 220 |
if ( |
||
| 221 |
request.controller != controller || |
||
| 222 |
request.fulfilled || |
||
| 223 |
request.canceled |
||
| 224 |
) {
|
||
| 225 |
return 0; |
||
| 226 |
} |
||
| 227 |
return request.assets; |
||
| 228 |
} |
||
| 229 | |||
| 230 |
function pendingCancelDepositRequest( |
||
| 231 |
uint256 requestId, |
||
| 232 |
address controller |
||
| 233 |
) external view returns (bool) {
|
||
| 234 |
DepositRequestStruct storage request = depositRequests[requestId]; |
||
| 235 |
return |
||
| 236 |
request.controller == controller && pendingCancelDeposit[requestId]; |
||
| 237 |
} |
||
| 238 | |||
| 239 |
// Override deposit to handle async requests |
||
| 240 |
function deposit( |
||
| 241 |
uint256 assets, |
||
| 242 |
address receiver, |
||
| 243 |
address controller |
||
| 244 |
✓ 16.5K
|
) public returns (uint256 shares) {
|
|
| 245 |
require( |
||
| 246 |
✓ 16.5K
|
msg.sender == controller || operators[controller][msg.sender], |
|
| 247 |
"Not authorized" |
||
| 248 |
); |
||
| 249 | |||
| 250 |
// Find and fulfill a matching deposit request |
||
| 251 |
✓ 1.3K
|
for (uint256 i = 1; i < _nextRequestId; i++) {
|
|
| 252 |
✓ 391
|
DepositRequestStruct storage request = depositRequests[i]; |
|
| 253 |
if ( |
||
| 254 |
✓ 391
|
request.controller == controller && |
|
| 255 |
!request.fulfilled && |
||
| 256 |
!request.canceled && |
||
| 257 |
request.assets >= assets |
||
| 258 |
) {
|
||
| 259 |
shares = previewDeposit(assets); |
||
| 260 |
request.fulfilled = true; |
||
| 261 |
_mint(receiver, shares); |
||
| 262 | |||
| 263 |
// Refund excess assets if any |
||
| 264 |
if (request.assets > assets) {
|
||
| 265 |
asset.transfer(controller, request.assets - assets); |
||
| 266 |
} |
||
| 267 | |||
| 268 |
emit Deposit(msg.sender, receiver, assets, shares); |
||
| 269 |
return shares; |
||
| 270 |
} |
||
| 271 |
} |
||
| 272 | |||
| 273 |
// Fallback to synchronous deposit if no matching request |
||
| 274 |
✓ 922
|
return super.deposit(assets, receiver); |
|
| 275 |
} |
||
| 276 | |||
| 277 |
function withdraw( |
||
| 278 |
uint256 assets, |
||
| 279 |
address receiver, |
||
| 280 |
address owner |
||
| 281 |
✓ 25.3K
|
) public returns (uint256 shares) {
|
|
| 282 |
✓ 25.3K
|
shares = previewWithdraw(assets); |
|
| 283 |
✓ 24.2K
|
if (msg.sender != owner) {
|
|
| 284 |
✓ 23.9K
|
allowance[owner][msg.sender] -= shares; |
|
| 285 |
} |
||
| 286 |
✓ 637
|
_burn(owner, shares); |
|
| 287 |
✓ 401
|
uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
|
| 288 | |||
| 289 |
✓ 401
|
asset.transfer(receiver, lossyAssets); |
|
| 290 |
✓ 401
|
emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
|
| 291 |
} |
||
| 292 | |||
| 293 |
function redeem( |
||
| 294 |
uint256 shares, |
||
| 295 |
address receiver, |
||
| 296 |
address owner |
||
| 297 |
✓ 27.0K
|
) public returns (uint256 assets) {
|
|
| 298 |
✓ 27.0K
|
assets = previewRedeem(shares); |
|
| 299 |
✓ 26.4K
|
if (msg.sender != owner) {
|
|
| 300 |
✓ 19.9K
|
allowance[owner][msg.sender] -= shares; |
|
| 301 |
} |
||
| 302 |
✓ 7.0K
|
_burn(owner, shares); |
|
| 303 |
✓ 2.1K
|
uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS); |
|
| 304 | |||
| 305 |
✓ 2.1K
|
asset.transfer(receiver, lossyAssets); |
|
| 306 |
✓ 2.1K
|
emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares); |
|
| 307 |
} |
||
| 308 | |||
| 309 |
// Async Redeem Flow |
||
| 310 |
function requestRedeem( |
||
| 311 |
uint256 shares, |
||
| 312 |
address controller, |
||
| 313 |
address owner |
||
| 314 |
✓ 25.9K
|
) external returns (uint256 requestId) {
|
|
| 315 |
✓ 25.9K
|
requestId = _nextRequestId++; |
|
| 316 |
✓ 25.9K
|
redeemRequests[requestId] = RedeemRequestStruct({
|
|
| 317 |
shares: shares, |
||
| 318 |
controller: controller, |
||
| 319 |
owner: owner, |
||
| 320 |
fulfilled: false, |
||
| 321 |
canceled: false |
||
| 322 |
}); |
||
| 323 | |||
| 324 |
✓ 25.9K
|
emit RedeemRequest(controller, owner, requestId, msg.sender, shares); |
|
| 325 |
} |
||
| 326 | |||
| 327 |
function pendingRedeemRequest( |
||
| 328 |
uint256 requestId, |
||
| 329 |
address controller |
||
| 330 |
) external view returns (uint256) {
|
||
| 331 |
RedeemRequestStruct storage request = redeemRequests[requestId]; |
||
| 332 |
if ( |
||
| 333 |
request.controller != controller || |
||
| 334 |
request.fulfilled || |
||
| 335 |
request.canceled |
||
| 336 |
) {
|
||
| 337 |
return 0; |
||
| 338 |
} |
||
| 339 |
return request.shares; |
||
| 340 |
} |
||
| 341 | |||
| 342 |
function claimableRedeemRequest( |
||
| 343 |
uint256 requestId, |
||
| 344 |
address controller |
||
| 345 |
✓ 8.0K
|
) external view returns (uint256) {
|
|
| 346 |
✓ 8.0K
|
RedeemRequestStruct storage request = redeemRequests[requestId]; |
|
| 347 |
if ( |
||
| 348 |
✓ 8.0K
|
request.controller != controller || |
|
| 349 |
request.fulfilled || |
||
| 350 |
request.canceled |
||
| 351 |
) {
|
||
| 352 |
return 0; |
||
| 353 |
} |
||
| 354 |
return request.shares; |
||
| 355 |
} |
||
| 356 | |||
| 357 |
function pendingCancelRedeemRequest( |
||
| 358 |
uint256 requestId, |
||
| 359 |
address controller |
||
| 360 |
) external view returns (bool) {
|
||
| 361 |
RedeemRequestStruct storage request = redeemRequests[requestId]; |
||
| 362 |
return |
||
| 363 |
request.controller == controller && pendingCancelRedeem[requestId]; |
||
| 364 |
} |
||
| 365 | |||
| 366 |
// Cancel Operations |
||
| 367 |
function cancelDepositRequest( |
||
| 368 |
uint256 requestId, |
||
| 369 |
address controller |
||
| 370 |
) external {
|
||
| 371 |
require( |
||
| 372 |
✓ 21.9K
|
msg.sender == controller || operators[controller][msg.sender], |
|
| 373 |
"Not authorized" |
||
| 374 |
); |
||
| 375 |
✓ 664
|
DepositRequestStruct storage request = depositRequests[requestId]; |
|
| 376 |
require( |
||
| 377 |
✓ 664
|
request.controller == controller && !request.fulfilled, |
|
| 378 |
"Invalid request" |
||
| 379 |
); |
||
| 380 | |||
| 381 |
pendingCancelDeposit[requestId] = true; |
||
| 382 |
} |
||
| 383 | |||
| 384 |
function claimCancelDepositRequest( |
||
| 385 |
uint256 requestId, |
||
| 386 |
address receiver, |
||
| 387 |
address controller |
||
| 388 |
✓ 14.1K
|
) external returns (uint256 assets) {
|
|
| 389 |
✓ 14.1K
|
DepositRequestStruct storage request = depositRequests[requestId]; |
|
| 390 | |||
| 391 |
✓ 14.1K
|
assets = request.assets; |
|
| 392 |
✓ 14.1K
|
request.canceled = true; |
|
| 393 |
✓ 14.1K
|
pendingCancelDeposit[requestId] = false; |
|
| 394 | |||
| 395 |
✓ 14.1K
|
asset.transfer(receiver, assets); |
|
| 396 |
} |
||
| 397 | |||
| 398 |
function cancelRedeemRequest( |
||
| 399 |
uint256 requestId, |
||
| 400 |
address controller |
||
| 401 |
) external {
|
||
| 402 |
✓ 27.5K
|
RedeemRequestStruct storage request = redeemRequests[requestId]; |
|
| 403 | |||
| 404 |
✓ 27.5K
|
pendingCancelRedeem[requestId] = true; |
|
| 405 |
} |
||
| 406 | |||
| 407 |
function claimCancelRedeemRequest( |
||
| 408 |
uint256 requestId, |
||
| 409 |
address receiver, |
||
| 410 |
address controller |
||
| 411 |
✓ 26.7K
|
) external returns (uint256 shares) {
|
|
| 412 |
✓ 26.7K
|
RedeemRequestStruct storage request = redeemRequests[requestId]; |
|
| 413 | |||
| 414 |
✓ 26.7K
|
shares = request.shares; |
|
| 415 |
✓ 26.7K
|
request.canceled = true; |
|
| 416 |
✓ 26.7K
|
pendingCancelRedeem[requestId] = false; |
|
| 417 | |||
| 418 |
✓ 26.7K
|
_mint(receiver, shares); |
|
| 419 |
} |
||
| 420 | |||
| 421 |
// Placeholder functions for Centrifuge compatibility |
||
| 422 |
function poolId() external pure returns (uint64) {
|
||
| 423 |
return 1; |
||
| 424 |
} |
||
| 425 | |||
| 426 |
function trancheId() external pure returns (bytes16) {
|
||
| 427 |
return bytes16(uint128(1)); |
||
| 428 |
} |
||
| 429 | |||
| 430 |
// Yield simulation functions (similar to MockERC4626Tester) |
||
| 431 |
// Primary way that 7540 vaults receive losses is on rounding in redemptions so we just simulate a loss that reduces total asset balance |
||
| 432 |
function increaseYield(uint256 increasePercentageFP4) external {
|
||
| 433 |
uint256 amount = (totalAssets() * increasePercentageFP4) / MAX_BPS; |
||
| 434 |
MockERC20(asset).transferFrom(msg.sender, address(this), amount); |
||
| 435 |
} |
||
| 436 | |||
| 437 |
function decreaseYield(uint256 decreasePercentageFP4) external {
|
||
| 438 |
uint256 amount = (totalAssets() * decreasePercentageFP4) / MAX_BPS; |
||
| 439 |
MockERC20(asset).transfer(address(0xbeef), amount); |
||
| 440 |
} |
||
| 441 | |||
| 442 |
function simulateGain(uint256 gainAmount) external {
|
||
| 443 |
✓ 32.9K
|
MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount); |
|
| 444 |
✓ 7.9K
|
totalGains += gainAmount; |
|
| 445 |
} |
||
| 446 | |||
| 447 |
function simulateLoss(uint256 lossAmount) external {
|
||
| 448 |
✓ 25.0K
|
MockERC20(asset).transfer(address(0xbeef), lossAmount); |
|
| 449 |
✓ 624
|
totalLosses += lossAmount; |
|
| 450 |
} |
||
| 451 | |||
| 452 |
/// @dev Set the loss on withdraw as percentage of the assets being withdrawn |
||
| 453 |
function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
|
||
| 454 |
✓ 25.8K
|
_lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100% |
|
| 455 |
✓ 25.8K
|
lossOnWithdraw = _lossOnWithdraw; |
|
| 456 |
} |
||
| 457 | |||
| 458 |
/// @notice ERC165 interface detection |
||
| 459 |
function supportsInterface( |
||
| 460 |
bytes4 interfaceId |
||
| 461 |
) external pure override returns (bool) {
|
||
| 462 |
return interfaceId == type(IERC165).interfaceId; |
||
| 463 |
} |
||
| 464 |
} |
||
| 465 |
8.0%
test/recon/mocks/MockERC7540YieldSourceOracle.sol
Lines covered: 4 / 50 (8.0%)
| 1 |
// SPDX-License-Identifier: MIT |
||
| 2 |
pragma solidity ^0.8.30; |
||
| 3 | |||
| 4 |
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
|
||
| 5 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 6 |
import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
|
||
| 7 |
import {MockERC7540Tester} from "./MockERC7540Tester.sol";
|
||
| 8 | |||
| 9 |
/// @title MockERC7540YieldSourceOracle |
||
| 10 |
/// @notice Mock oracle for ERC7540 (Asynchronous Tokenized Vaults) in testing |
||
| 11 |
✓ 1
|
contract MockERC7540YieldSourceOracle is IYieldSourceOracle {
|
|
| 12 |
mapping(address => bool) public validAssetMap; |
||
| 13 |
|
||
| 14 |
function setValidAsset(address asset, bool isValid) external {
|
||
| 15 |
✓ 121.8K
|
validAssetMap[asset] = isValid; |
|
| 16 |
} |
||
| 17 |
|
||
| 18 |
function decimals(address yieldSourceAddress) external view returns (uint8) {
|
||
| 19 |
address share = MockERC7540Tester(yieldSourceAddress).share(); |
||
| 20 |
return IERC20Metadata(share).decimals(); |
||
| 21 |
} |
||
| 22 |
|
||
| 23 |
function getShareOutput( |
||
| 24 |
address yieldSourceAddress, |
||
| 25 |
address, |
||
| 26 |
uint256 assetsIn |
||
| 27 |
✓ 80.5K
|
) external view returns (uint256) {
|
|
| 28 |
✓ 80.5K
|
return MockERC7540Tester(yieldSourceAddress).convertToShares(assetsIn); |
|
| 29 |
} |
||
| 30 |
|
||
| 31 |
function getAssetOutput( |
||
| 32 |
address yieldSourceAddress, |
||
| 33 |
address, |
||
| 34 |
uint256 sharesIn |
||
| 35 |
) public view returns (uint256) {
|
||
| 36 |
return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
||
| 37 |
} |
||
| 38 |
|
||
| 39 |
function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
|
||
| 40 |
address share = MockERC7540Tester(yieldSourceAddress).share(); |
||
| 41 |
uint256 _decimals = IERC20Metadata(share).decimals(); |
||
| 42 |
return MockERC7540Tester(yieldSourceAddress).convertToAssets(10 ** _decimals); |
||
| 43 |
} |
||
| 44 |
|
||
| 45 |
function getBalanceOfOwner( |
||
| 46 |
address yieldSourceAddress, |
||
| 47 |
address ownerOfShares |
||
| 48 |
) external view returns (uint256) {
|
||
| 49 |
return IERC20(MockERC7540Tester(yieldSourceAddress).share()).balanceOf(ownerOfShares); |
||
| 50 |
} |
||
| 51 |
|
||
| 52 |
function getTVLByOwnerOfShares( |
||
| 53 |
address yieldSourceAddress, |
||
| 54 |
address ownerOfShares |
||
| 55 |
) external view returns (uint256) {
|
||
| 56 |
address share = MockERC7540Tester(yieldSourceAddress).share(); |
||
| 57 |
uint256 shares = IERC20(share).balanceOf(ownerOfShares); |
||
| 58 |
return MockERC7540Tester(yieldSourceAddress).convertToAssets(shares); |
||
| 59 |
} |
||
| 60 |
|
||
| 61 |
function getTVL(address yieldSourceAddress) external view returns (uint256) {
|
||
| 62 |
return MockERC7540Tester(yieldSourceAddress).totalAssets(); |
||
| 63 |
} |
||
| 64 |
|
||
| 65 |
function getPricePerShareMultiple( |
||
| 66 |
address[] memory yieldSourceAddresses |
||
| 67 |
) external view returns (uint256[] memory) {
|
||
| 68 |
uint256[] memory prices = new uint256[](yieldSourceAddresses.length); |
||
| 69 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 70 |
address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
||
| 71 |
uint256 _decimals = IERC20Metadata(share).decimals(); |
||
| 72 |
prices[i] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(10 ** _decimals); |
||
| 73 |
} |
||
| 74 |
return prices; |
||
| 75 |
} |
||
| 76 |
|
||
| 77 |
function getTVLByOwnerOfSharesMultiple( |
||
| 78 |
address[] memory yieldSourceAddresses, |
||
| 79 |
address[][] memory ownersOfShares |
||
| 80 |
) external view returns (uint256[][] memory) {
|
||
| 81 |
uint256[][] memory result = new uint256[][](yieldSourceAddresses.length); |
||
| 82 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 83 |
result[i] = new uint256[](ownersOfShares[i].length); |
||
| 84 |
address share = MockERC7540Tester(yieldSourceAddresses[i]).share(); |
||
| 85 |
for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
|
||
| 86 |
uint256 shares = IERC20(share).balanceOf(ownersOfShares[i][j]); |
||
| 87 |
result[i][j] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(shares); |
||
| 88 |
} |
||
| 89 |
} |
||
| 90 |
return result; |
||
| 91 |
} |
||
| 92 |
|
||
| 93 |
function getTVLMultiple( |
||
| 94 |
address[] memory yieldSourceAddresses |
||
| 95 |
) external view returns (uint256[] memory) {
|
||
| 96 |
uint256[] memory tvls = new uint256[](yieldSourceAddresses.length); |
||
| 97 |
for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
|
||
| 98 |
tvls[i] = MockERC7540Tester(yieldSourceAddresses[i]).totalAssets(); |
||
| 99 |
} |
||
| 100 |
return tvls; |
||
| 101 |
} |
||
| 102 |
|
||
| 103 |
function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
|
||
| 104 |
return validAssetMap[asset]; |
||
| 105 |
} |
||
| 106 |
|
||
| 107 |
function isValidUnderlyingAssets( |
||
| 108 |
address[] memory, |
||
| 109 |
address[] memory assets |
||
| 110 |
) external view returns (bool[] memory) {
|
||
| 111 |
bool[] memory validities = new bool[](assets.length); |
||
| 112 |
for (uint256 i = 0; i < assets.length; i++) {
|
||
| 113 |
validities[i] = validAssetMap[assets[i]]; |
||
| 114 |
} |
||
| 115 |
return validities; |
||
| 116 |
} |
||
| 117 |
|
||
| 118 |
function getAssetOutputWithFees( |
||
| 119 |
bytes32, |
||
| 120 |
address yieldSourceAddress, |
||
| 121 |
address, |
||
| 122 |
address, |
||
| 123 |
uint256 sharesIn |
||
| 124 |
) external view returns (uint256) {
|
||
| 125 |
return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn); |
||
| 126 |
} |
||
| 127 |
} |
96.1%
test/recon/targets/AdminTargets.sol
Lines covered: 171 / 178 (96.1%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// External dependencies |
||
| 5 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 6 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 7 |
import {Panic} from "@recon/Panic.sol";
|
||
| 8 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 9 | |||
| 10 |
// System dependencies |
||
| 11 |
import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 12 |
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
||
| 13 | |||
| 14 |
// Test dependencies |
||
| 15 |
import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
|
||
| 16 |
import {BeforeAfter, OpType} from "../BeforeAfter.sol";
|
||
| 17 |
import {Properties} from "../Properties.sol";
|
||
| 18 | |||
| 19 |
abstract contract AdminTargets is BaseTargetFunctions, Properties {
|
||
| 20 |
enum HookType {
|
||
| 21 |
// ERC4626 Hooks |
||
| 22 |
ApproveAndDeposit4626, |
||
| 23 |
Deposit4626, |
||
| 24 |
Redeem4626, |
||
| 25 |
// ERC5115 Hooks |
||
| 26 |
ApproveAndDeposit5115, |
||
| 27 |
Deposit5115, |
||
| 28 |
Redeem5115, |
||
| 29 |
// ERC7540 Hooks |
||
| 30 |
Deposit7540, |
||
| 31 |
Redeem7540, |
||
| 32 |
RequestDeposit7540, |
||
| 33 |
RequestRedeem7540, |
||
| 34 |
ApproveAndRequestDeposit7540, |
||
| 35 |
CancelDepositRequest7540, |
||
| 36 |
CancelRedeemRequest7540, |
||
| 37 |
ClaimCancelDepositRequest7540, |
||
| 38 |
ClaimCancelRedeemRequest7540, |
||
| 39 |
Withdraw7540, |
||
| 40 |
// Super Vault Hooks |
||
| 41 |
CancelRedeem, |
||
| 42 |
SuperVaultWithdraw7540 |
||
| 43 |
} |
||
| 44 | |||
| 45 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 46 |
function superVaultStrategy_executeHooks_clamped( |
||
| 47 |
uint256[] memory hookTypeInts, |
||
| 48 |
uint256[] memory amountsToInvest, |
||
| 49 |
bool[] memory usePrevHookAmounts |
||
| 50 |
) public payable {
|
||
| 51 |
// Limit the number of hooks to 10 maximum |
||
| 52 |
✓ 111.5K
|
uint256 numHooks = hookTypeInts.length; |
|
| 53 |
✓ 111.5K
|
if (numHooks > 10) {
|
|
| 54 |
✓ 102.7K
|
numHooks = 10; |
|
| 55 |
} |
||
| 56 | |||
| 57 |
// Ensure all arrays have the same length |
||
| 58 |
✓ 111.5K
|
if (amountsToInvest.length < numHooks) {
|
|
| 59 |
✓ 8.3K
|
numHooks = amountsToInvest.length; |
|
| 60 |
} |
||
| 61 |
✓ 111.5K
|
if (usePrevHookAmounts.length < numHooks) {
|
|
| 62 |
✓ 8.9K
|
numHooks = usePrevHookAmounts.length; |
|
| 63 |
} |
||
| 64 | |||
| 65 |
// Return early if no hooks to execute |
||
| 66 |
✓ 111.5K
|
if (numHooks == 0) {
|
|
| 67 |
✓ 2.3K
|
return; |
|
| 68 |
} |
||
| 69 | |||
| 70 |
// Create ExecuteArgs for the hooks |
||
| 71 |
✓ 109.2K
|
ISuperVaultStrategy.ExecuteArgs memory executeArgs = ISuperVaultStrategy |
|
| 72 |
.ExecuteArgs({
|
||
| 73 |
✓ 109.2K
|
hooks: new address[](numHooks), |
|
| 74 |
✓ 109.2K
|
hookCalldata: new bytes[](numHooks), |
|
| 75 |
✓ 109.2K
|
expectedAssetsOrSharesOut: new uint256[](numHooks), |
|
| 76 |
✓ 109.2K
|
globalProofs: new bytes32[][](numHooks), |
|
| 77 |
✓ 109.2K
|
strategyProofs: new bytes32[][](numHooks) |
|
| 78 |
}); |
||
| 79 | |||
| 80 |
// Process each hook |
||
| 81 |
✓ 109.2K
|
uint256 totalAmountToDeposit; |
|
| 82 |
✓ 1.1M
|
for (uint256 i = 0; i < numHooks; i++) {
|
|
| 83 |
// Convert integer to enum (will wrap around if > max enum value) |
||
| 84 |
✓ 981.3K
|
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 |
✓ 981.3K
|
uint256 clampedAmount = amountsToInvest[i] % |
|
| 88 |
✓ 981.3K
|
(MockERC20(superVault.asset()).balanceOf( |
|
| 89 |
✓ 981.3K
|
address(superVaultStrategy) |
|
| 90 |
✓ 981.3K
|
) + 1); |
|
| 91 | |||
| 92 |
// Get the hook address and calldata |
||
| 93 |
( |
||
| 94 |
✓ 981.3K
|
address hookAddress, |
|
| 95 |
✓ 981.3K
|
bytes memory hookCalldata |
|
| 96 |
✓ 981.3K
|
) = _getHookAddressAndCalldata( |
|
| 97 |
✓ 981.3K
|
hookType, |
|
| 98 |
✓ 981.3K
|
clampedAmount, |
|
| 99 |
✓ 981.3K
|
usePrevHookAmounts[i] |
|
| 100 |
); |
||
| 101 | |||
| 102 |
✓ 981.3K
|
executeArgs.hooks[i] = hookAddress; |
|
| 103 |
✓ 981.3K
|
executeArgs.hookCalldata[i] = hookCalldata; |
|
| 104 |
✓ 981.3K
|
executeArgs.expectedAssetsOrSharesOut[i] = clampedAmount; |
|
| 105 |
✓ 981.3K
|
executeArgs.globalProofs[i] = new bytes32[](1); |
|
| 106 |
✓ 981.3K
|
executeArgs.strategyProofs[i] = new bytes32[](1); |
|
| 107 | |||
| 108 |
✓ 981.3K
|
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 |
✓ 109.2K
|
if (_claimableMoreThanInvested(totalAmountToDeposit)) return; |
|
| 113 | |||
| 114 |
// Execute all hooks |
||
| 115 |
✓ 102.2K
|
this.superVaultStrategy_executeHooks{value: msg.value}(executeArgs);
|
|
| 116 |
} |
||
| 117 | |||
| 118 |
function _getHookAddressAndCalldata( |
||
| 119 |
HookType hookType, |
||
| 120 |
uint256 amountToInvest, |
||
| 121 |
bool usePrevHookAmount |
||
| 122 |
✓ 981.3K
|
) internal view returns (address hookAddress, bytes memory hookCalldata) {
|
|
| 123 |
✓ 981.3K
|
if (hookType == HookType.ApproveAndDeposit4626) {
|
|
| 124 |
✓ 130.9K
|
hookAddress = address(approveAndDeposit4626Hook); |
|
| 125 |
hookCalldata = abi.encodePacked( |
||
| 126 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 127 |
✓ 130.9K
|
_getYieldSource(), // Address of the yield source |
|
| 128 |
✓ 130.9K
|
superVault.asset(), // Address of the token to approve and deposit |
|
| 129 |
✓ 130.9K
|
amountToInvest, // Amount to deposit |
|
| 130 |
✓ 130.9K
|
usePrevHookAmount |
|
| 131 |
); |
||
| 132 |
✓ 850.4K
|
} else if (hookType == HookType.Deposit4626) {
|
|
| 133 |
✓ 57.7K
|
hookAddress = address(deposit4626Hook); |
|
| 134 |
hookCalldata = abi.encodePacked( |
||
| 135 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 136 |
✓ 57.7K
|
_getYieldSource(), // Address of the yield source |
|
| 137 |
✓ 57.7K
|
amountToInvest, // Amount to deposit |
|
| 138 |
✓ 57.7K
|
usePrevHookAmount |
|
| 139 |
); |
||
| 140 |
✓ 792.7K
|
} else if (hookType == HookType.Redeem4626) {
|
|
| 141 |
✓ 69.5K
|
hookAddress = address(redeem4626Hook); |
|
| 142 |
hookCalldata = abi.encodePacked( |
||
| 143 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 144 |
✓ 69.5K
|
_getYieldSource(), // Address of the yield source |
|
| 145 |
amountToInvest, // Amount to redeem |
||
| 146 |
✓ 200.5K
|
address(superVaultStrategy), // Receiver |
|
| 147 |
address(superVaultStrategy) // Owner |
||
| 148 |
); |
||
| 149 |
✓ 723.1K
|
} else if (hookType == HookType.ApproveAndDeposit5115) {
|
|
| 150 |
✓ 53.1K
|
hookAddress = address(approveAndDeposit5115Hook); |
|
| 151 |
hookCalldata = abi.encodePacked( |
||
| 152 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 153 |
✓ 53.1K
|
_getYieldSource(), // Address of the yield source |
|
| 154 |
✓ 53.1K
|
superVault.asset(), // Address of the token to approve and deposit |
|
| 155 |
✓ 53.1K
|
bytes32(0), // tokenId (for ERC5115) |
|
| 156 |
amountToInvest, // Amount to deposit |
||
| 157 |
usePrevHookAmount |
||
| 158 |
); |
||
| 159 |
✓ 670.1K
|
} else if (hookType == HookType.Deposit5115) {
|
|
| 160 |
✓ 59.3K
|
hookAddress = address(deposit5115Hook); |
|
| 161 |
hookCalldata = abi.encodePacked( |
||
| 162 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 163 |
✓ 59.3K
|
_getYieldSource(), // Address of the yield source |
|
| 164 |
bytes32(0), // tokenId (for ERC5115) |
||
| 165 |
amountToInvest, // Amount to deposit |
||
| 166 |
usePrevHookAmount |
||
| 167 |
); |
||
| 168 |
✓ 610.7K
|
} else if (hookType == HookType.Redeem5115) {
|
|
| 169 |
✓ 53.0K
|
hookAddress = address(redeem5115Hook); |
|
| 170 |
hookCalldata = abi.encodePacked( |
||
| 171 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 172 |
✓ 53.0K
|
_getYieldSource(), // Address of the yield source |
|
| 173 |
✓ 53.0K
|
bytes32(0), // tokenId (for ERC5115) |
|
| 174 |
amountToInvest, // Amount to redeem |
||
| 175 |
✓ 53.0K
|
address(superVaultStrategy), // Receiver |
|
| 176 |
address(superVaultStrategy) // Owner |
||
| 177 |
); |
||
| 178 |
✓ 557.7K
|
} else if (hookType == HookType.Deposit7540) {
|
|
| 179 |
✓ 45.7K
|
hookAddress = address(deposit7540Hook); |
|
| 180 |
hookCalldata = abi.encodePacked( |
||
| 181 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 182 |
✓ 45.7K
|
_getYieldSource(), // Address of the yield source |
|
| 183 |
amountToInvest, // Amount to deposit |
||
| 184 |
address(superVaultStrategy), // Receiver |
||
| 185 |
address(superVaultStrategy) // Controller |
||
| 186 |
); |
||
| 187 |
✓ 512.0K
|
} else if (hookType == HookType.Redeem7540) {
|
|
| 188 |
✓ 52.6K
|
hookAddress = address(redeem7540Hook); |
|
| 189 |
hookCalldata = abi.encodePacked( |
||
| 190 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 191 |
✓ 52.6K
|
_getYieldSource(), // Address of the yield source |
|
| 192 |
✓ 102.9K
|
amountToInvest, // Amount to redeem |
|
| 193 |
✓ 102.9K
|
address(superVaultStrategy), // Receiver |
|
| 194 |
address(superVaultStrategy), // Owner |
||
| 195 |
address(superVaultStrategy) // Controller |
||
| 196 |
); |
||
| 197 |
✓ 459.5K
|
} else if (hookType == HookType.RequestDeposit7540) {
|
|
| 198 |
✓ 49.4K
|
hookAddress = address(requestDeposit7540Hook); |
|
| 199 |
hookCalldata = abi.encodePacked( |
||
| 200 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 201 |
✓ 49.4K
|
_getYieldSource(), // Address of the yield source |
|
| 202 |
amountToInvest, // Amount to request deposit |
||
| 203 |
address(superVaultStrategy), // Owner |
||
| 204 |
address(superVaultStrategy) // Controller |
||
| 205 |
); |
||
| 206 |
✓ 410.1K
|
} else if (hookType == HookType.RequestRedeem7540) {
|
|
| 207 |
✓ 35.8K
|
hookAddress = address(requestRedeem7540Hook); |
|
| 208 |
hookCalldata = abi.encodePacked( |
||
| 209 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 210 |
✓ 35.8K
|
_getYieldSource(), // Address of the yield source |
|
| 211 |
amountToInvest, // Amount to request redeem |
||
| 212 |
address(superVaultStrategy), // Owner |
||
| 213 |
address(superVaultStrategy) // Controller |
||
| 214 |
); |
||
| 215 |
✓ 374.2K
|
} else if (hookType == HookType.ApproveAndRequestDeposit7540) {
|
|
| 216 |
✓ 45.6K
|
hookAddress = address(approveAndRequestDeposit7540Hook); |
|
| 217 |
hookCalldata = abi.encodePacked( |
||
| 218 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 219 |
✓ 45.6K
|
_getYieldSource(), // Address of the yield source |
|
| 220 |
✓ 45.6K
|
superVault.asset(), // Address of the token to approve |
|
| 221 |
amountToInvest, // Amount to request deposit |
||
| 222 |
✓ 45.6K
|
address(superVaultStrategy), // Owner |
|
| 223 |
address(superVaultStrategy) // Controller |
||
| 224 |
); |
||
| 225 |
✓ 328.6K
|
} else if (hookType == HookType.CancelDepositRequest7540) {
|
|
| 226 |
✓ 78.1K
|
hookAddress = address(cancelDepositRequest7540Hook); |
|
| 227 |
hookCalldata = abi.encodePacked( |
||
| 228 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 229 |
✓ 78.1K
|
_getYieldSource(), // Address of the yield source |
|
| 230 |
✓ 189.4K
|
address(superVaultStrategy) // Controller |
|
| 231 |
); |
||
| 232 |
✓ 250.5K
|
} else if (hookType == HookType.CancelRedeemRequest7540) {
|
|
| 233 |
✓ 49.1K
|
hookAddress = address(cancelRedeemRequest7540Hook); |
|
| 234 |
hookCalldata = abi.encodePacked( |
||
| 235 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 236 |
✓ 49.1K
|
_getYieldSource(), // Address of the yield source |
|
| 237 |
address(superVaultStrategy) // Controller |
||
| 238 |
); |
||
| 239 |
✓ 201.4K
|
} else if (hookType == HookType.ClaimCancelDepositRequest7540) {
|
|
| 240 |
✓ 41.6K
|
hookAddress = address(claimCancelDepositRequest7540Hook); |
|
| 241 |
hookCalldata = abi.encodePacked( |
||
| 242 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 243 |
✓ 41.6K
|
_getYieldSource(), // Address of the yield source |
|
| 244 |
✓ 88.9K
|
address(superVaultStrategy), // Receiver |
|
| 245 |
address(superVaultStrategy) // Controller |
||
| 246 |
); |
||
| 247 |
✓ 159.9K
|
} else if (hookType == HookType.ClaimCancelRedeemRequest7540) {
|
|
| 248 |
✓ 47.4K
|
hookAddress = address(claimCancelRedeemRequest7540Hook); |
|
| 249 |
hookCalldata = abi.encodePacked( |
||
| 250 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 251 |
✓ 47.4K
|
_getYieldSource(), // Address of the yield source |
|
| 252 |
address(superVaultStrategy), // Receiver |
||
| 253 |
address(superVaultStrategy) // Controller |
||
| 254 |
); |
||
| 255 |
✓ 112.5K
|
} else if (hookType == HookType.Withdraw7540) {
|
|
| 256 |
✓ 50.3K
|
hookAddress = address(withdraw7540Hook); |
|
| 257 |
hookCalldata = abi.encodePacked( |
||
| 258 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 259 |
✓ 50.3K
|
_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 |
✓ 62.2K
|
} else if (hookType == HookType.CancelRedeem) {
|
|
| 266 |
✓ 62.2K
|
hookAddress = address(cancelRedeemHook); |
|
| 267 |
hookCalldata = abi.encodePacked( |
||
| 268 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 269 |
✓ 62.2K
|
_getYieldSource(), // Address of the yield source |
|
| 270 |
address(superVaultStrategy) // Controller |
||
| 271 |
); |
||
| 272 |
} else if (hookType == HookType.SuperVaultWithdraw7540) {
|
||
| 273 |
hookAddress = address(superVaultWithdraw7540Hook); |
||
| 274 |
hookCalldata = abi.encodePacked( |
||
| 275 |
bytes32(0), // yieldSourceOracleId placeholder |
||
| 276 |
_getYieldSource(), // Address of the yield source |
||
| 277 |
amountToInvest, // Amount to withdraw |
||
| 278 |
address(superVaultStrategy), // Receiver |
||
| 279 |
address(superVaultStrategy), // Owner |
||
| 280 |
address(superVaultStrategy) // Controller |
||
| 281 |
); |
||
| 282 |
} |
||
| 283 |
} |
||
| 284 | |||
| 285 |
function superVaultStrategy_fulfillRedeemRequests_clamped( |
||
| 286 |
uint256 redeemAmount |
||
| 287 |
) public {
|
||
| 288 |
// Find a controller that has pending redeem requests |
||
| 289 |
✓ 282.5K
|
address selectedController = _getActor(); |
|
| 290 |
✓ 282.5K
|
uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
|
| 291 |
selectedController |
||
| 292 |
); |
||
| 293 | |||
| 294 |
// Clamp using the actor's pending amount |
||
| 295 |
✓ 282.5K
|
uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
|
| 296 | |||
| 297 |
✓ 282.5K
|
address[] memory controllers = new address[](1); |
|
| 298 |
✓ 282.5K
|
controllers[0] = selectedController; |
|
| 299 | |||
| 300 |
// Determine yield source type from currently active yield source |
||
| 301 |
✓ 688.6K
|
YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress( |
|
| 302 |
✓ 282.5K
|
_getYieldSource() |
|
| 303 |
); |
||
| 304 |
✓ 282.5K
|
address redeemHook = _getRedeemHookForType(activeYieldSourceType); |
|
| 305 | |||
| 306 |
// Create realistic hook calldata for redeem operation |
||
| 307 |
✓ 282.5K
|
bytes memory redeemHookCalldata; |
|
| 308 | |||
| 309 |
if ( |
||
| 310 |
✓ 282.5K
|
activeYieldSourceType == YieldSourceType.ERC4626 || |
|
| 311 |
✓ 51.8K
|
activeYieldSourceType == YieldSourceType.ERC5115 |
|
| 312 |
) {
|
||
| 313 |
// ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
||
| 314 |
redeemHookCalldata = abi.encodePacked( |
||
| 315 |
✓ 251.0K
|
bytes32(0), // yieldSourceOracleId placeholder |
|
| 316 |
✓ 251.0K
|
_getYieldSource(), // Current active yield source |
|
| 317 |
✓ 251.0K
|
address(superVaultStrategy), // Owner (strategy owns the yield source shares) |
|
| 318 |
✓ 251.0K
|
actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
|
| 319 |
false // Don't use previous hook amount |
||
| 320 |
); |
||
| 321 |
} else {
|
||
| 322 |
// ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
||
| 323 |
redeemHookCalldata = abi.encodePacked( |
||
| 324 |
✓ 31.6K
|
bytes32(0), // yieldSourceOracleId placeholder |
|
| 325 |
✓ 31.6K
|
_getYieldSource(), // Current active yield source |
|
| 326 |
✓ 31.6K
|
actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
|
| 327 |
✓ 31.6K
|
false // Don't use previous hook amount |
|
| 328 |
); |
||
| 329 |
} |
||
| 330 | |||
| 331 |
// Create arrays for FulfillArgs |
||
| 332 |
✓ 282.5K
|
address[] memory hooks = new address[](1); |
|
| 333 |
✓ 282.5K
|
hooks[0] = redeemHook; |
|
| 334 | |||
| 335 |
✓ 282.5K
|
bytes[] memory hookCalldata = new bytes[](1); |
|
| 336 |
✓ 282.5K
|
hookCalldata[0] = redeemHookCalldata; |
|
| 337 | |||
| 338 |
✓ 282.5K
|
uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
|
| 339 |
✓ 282.5K
|
expectedAssetsOrSharesOut[0] = 1; // @audit Allow max losse amount matching the actual redeem |
|
| 340 | |||
| 341 |
✓ 282.5K
|
bytes32[][] memory globalProofs = new bytes32[][](1); |
|
| 342 |
✓ 282.5K
|
globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
|
| 343 | |||
| 344 |
✓ 282.5K
|
bytes32[][] memory strategyProofs = new bytes32[][](1); |
|
| 345 |
✓ 282.5K
|
strategyProofs[0] = new bytes32[](0); // Empty proof |
|
| 346 | |||
| 347 |
// Create the FulfillArgs struct |
||
| 348 |
✓ 282.5K
|
ISuperVaultStrategy.FulfillArgs memory fulfillArgs = ISuperVaultStrategy |
|
| 349 |
.FulfillArgs({
|
||
| 350 |
✓ 282.5K
|
controllers: controllers, |
|
| 351 |
✓ 282.5K
|
hooks: hooks, |
|
| 352 |
✓ 282.5K
|
hookCalldata: hookCalldata, |
|
| 353 |
✓ 282.5K
|
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 354 |
✓ 282.5K
|
globalProofs: globalProofs, |
|
| 355 |
✓ 282.5K
|
strategyProofs: strategyProofs |
|
| 356 |
}); |
||
| 357 | |||
| 358 |
// Execute the function |
||
| 359 |
✓ 282.5K
|
superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT(fulfillArgs); |
|
| 360 |
} |
||
| 361 | |||
| 362 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 363 | |||
| 364 |
function superVaultStrategy_executeHooks( |
||
| 365 |
ISuperVaultStrategy.ExecuteArgs memory args |
||
| 366 |
) public payable asAdmin {
|
||
| 367 |
✓ 233.7K
|
superVaultStrategy.executeHooks{value: msg.value}(args);
|
|
| 368 | |||
| 369 |
executeHooksSuccess = true; |
||
| 370 |
} |
||
| 371 | |||
| 372 |
/// @dev Property: superVaultStrategy does not incur loss on fulfillment |
||
| 373 |
function superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT( |
||
| 374 |
ISuperVaultStrategy.FulfillArgs memory args |
||
| 375 |
✓ 420.6K
|
) public updateGhostsWithOpType(OpType.FULFILL) {
|
|
| 376 |
✓ 420.6K
|
uint256 summedExpectedAssets; |
|
| 377 |
✓ 1.1M
|
for (uint256 i; i < args.expectedAssetsOrSharesOut.length; i++) {
|
|
| 378 |
✓ 827.8K
|
summedExpectedAssets += args.expectedAssetsOrSharesOut[i]; |
|
| 379 |
} |
||
| 380 | |||
| 381 |
// no need to prank because called as admin address(this) |
||
| 382 |
✓ 290.3K
|
superVaultStrategy.fulfillRedeemRequests(args); |
|
| 383 | |||
| 384 |
✓ 98.6K
|
uint256 assetBalanceAfter = MockERC20(superVault.asset()).balanceOf( |
|
| 385 |
✓ 98.6K
|
address(superVaultStrategy) |
|
| 386 |
); |
||
| 387 | |||
| 388 |
✓ 98.6K
|
gte( |
|
| 389 |
✓ 98.6K
|
assetBalanceAfter, |
|
| 390 |
✓ 98.6K
|
summedExpectedAssets, |
|
| 391 |
✓ 98.6K
|
ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT |
|
| 392 |
); |
||
| 393 |
} |
||
| 394 | |||
| 395 |
// Functions that require SuperGovernor access |
||
| 396 |
/// @dev removed because we're bypassing hook validation |
||
| 397 |
// function superVaultAggregator_setHooksRootUpdateTimelock( |
||
| 398 |
// uint256 newTimelock |
||
| 399 |
// ) public asAdmin {
|
||
| 400 |
// superVaultAggregator.setHooksRootUpdateTimelock(newTimelock); |
||
| 401 |
// } |
||
| 402 | |||
| 403 |
/// @dev removed because we're bypassing hook validation |
||
| 404 |
// function superVaultAggregator_proposeGlobalHooksRoot( |
||
| 405 |
// bytes32 newRoot |
||
| 406 |
// ) public asAdmin {
|
||
| 407 |
// superVaultAggregator.proposeGlobalHooksRoot(newRoot); |
||
| 408 |
// } |
||
| 409 | |||
| 410 |
/// @dev removed because we're bypassing hook validation |
||
| 411 |
// function superVaultAggregator_executeGlobalHooksRootUpdate() |
||
| 412 |
// public |
||
| 413 |
// asAdmin |
||
| 414 |
// {
|
||
| 415 |
// superVaultAggregator.executeGlobalHooksRootUpdate(); |
||
| 416 |
// } |
||
| 417 | |||
| 418 |
/// @dev removed because we're bypassing hook validation |
||
| 419 |
// function superVaultAggregator_setGlobalHooksRootVetoStatus( |
||
| 420 |
// bool vetoed |
||
| 421 |
// ) public asAdmin {
|
||
| 422 |
// superVaultAggregator.setGlobalHooksRootVetoStatus(vetoed); |
||
| 423 |
// } |
||
| 424 | |||
| 425 |
/// @dev removed because we're bypassing hook validation |
||
| 426 |
// function superVaultAggregator_setStrategyHooksRootVetoStatus( |
||
| 427 |
// address strategy, |
||
| 428 |
// bool vetoed |
||
| 429 |
// ) public asAdmin {
|
||
| 430 |
// superVaultAggregator.setStrategyHooksRootVetoStatus(strategy, vetoed); |
||
| 431 |
// } |
||
| 432 | |||
| 433 |
function superVaultAggregator_changePrimaryManager( |
||
| 434 |
address strategy, |
||
| 435 |
address newManager |
||
| 436 |
) public asAdmin {
|
||
| 437 |
✓ 141.2K
|
superVaultAggregator.changePrimaryManager(strategy, newManager); |
|
| 438 |
} |
||
| 439 | |||
| 440 |
/// @dev won't achieve coverage until issue outlined here is resolved: https://github.com/Recon-Fuzz/superform-review/issues/5 |
||
| 441 |
function superVaultAggregator_slashStake( |
||
| 442 |
address manager, |
||
| 443 |
uint256 amount |
||
| 444 |
) public asAdmin {
|
||
| 445 |
✓ 124.2K
|
superVaultAggregator.slashStake(manager, amount); |
|
| 446 |
} |
||
| 447 | |||
| 448 |
/// Helpers |
||
| 449 | |||
| 450 |
function _requestedSharesForControllers( |
||
| 451 |
address[] memory controllers |
||
| 452 |
) internal returns (uint256) {
|
||
| 453 |
uint256 totalRequested; |
||
| 454 |
for (uint256 i; i < controllers.length; i++) {
|
||
| 455 |
totalRequested += superVault.pendingRedeemRequest( |
||
| 456 |
0, |
||
| 457 |
controllers[i] |
||
| 458 |
); |
||
| 459 |
} |
||
| 460 | |||
| 461 |
return totalRequested; |
||
| 462 |
} |
||
| 463 | |||
| 464 |
function _sumSuperVaultValsForControllers( |
||
| 465 |
address[] memory controllers |
||
| 466 |
) |
||
| 467 |
internal |
||
| 468 |
view |
||
| 469 |
returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis) |
||
| 470 |
{
|
||
| 471 |
for (uint256 i; i < controllers.length; i++) {
|
||
| 472 |
sumAccumulatorShares += superVaultStrategy |
||
| 473 |
.getSuperVaultState(controllers[i]) |
||
| 474 |
.accumulatorShares; |
||
| 475 |
sumAccumulatorCostBasis += superVaultStrategy |
||
| 476 |
.getSuperVaultState(controllers[i]) |
||
| 477 |
.accumulatorCostBasis; |
||
| 478 |
} |
||
| 479 |
} |
||
| 480 | |||
| 481 |
function _claimableMoreThanInvested( |
||
| 482 |
uint256 totalAmountToDeposit |
||
| 483 |
✓ 109.2K
|
) internal returns (bool) {
|
|
| 484 |
✓ 109.2K
|
address[] memory actors = _getActors(); |
|
| 485 |
✓ 109.2K
|
uint256 totalClaimable; |
|
| 486 |
✓ 436.8K
|
for (uint256 i; i < actors.length; i++) {
|
|
| 487 |
✓ 327.6K
|
uint256 claimableRedemptions = superVault.claimableRedeemRequest( |
|
| 488 |
0, |
||
| 489 |
✓ 327.6K
|
actors[i] |
|
| 490 |
); |
||
| 491 |
✓ 327.6K
|
uint256 claimableRedemptionsAsAssets = superVault.convertToAssets( |
|
| 492 |
claimableRedemptions |
||
| 493 |
); |
||
| 494 |
✓ 327.6K
|
totalClaimable += claimableRedemptionsAsAssets; |
|
| 495 |
} |
||
| 496 | |||
| 497 |
✓ 109.2K
|
uint256 currentStrategyBalance = MockERC20(superVault.asset()) |
|
| 498 |
✓ 109.2K
|
.balanceOf(address(superVaultStrategy)); |
|
| 499 | |||
| 500 |
// Don't allow investing more than the claimable amount |
||
| 501 |
✓ 109.2K
|
if (totalAmountToDeposit > totalClaimable) {
|
|
| 502 |
✓ 7.0K
|
return true; |
|
| 503 |
} |
||
| 504 | |||
| 505 |
// Ensure strategy has sufficient assets remaining after investment to cover claimable amounts |
||
| 506 |
✓ 102.2K
|
uint256 remainingStrategyBalance = currentStrategyBalance - |
|
| 507 |
✓ 102.2K
|
totalAmountToDeposit; |
|
| 508 |
✓ 102.2K
|
if (remainingStrategyBalance < totalClaimable) {
|
|
| 509 |
return true; |
||
| 510 |
} |
||
| 511 | |||
| 512 |
✓ 102.2K
|
return false; |
|
| 513 |
} |
||
| 514 |
} |
||
| 515 |
89.6%
test/recon/targets/DoomsdayTargets.sol
Lines covered: 310 / 346 (89.6%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 6 |
import {Panic} from "@recon/Panic.sol";
|
||
| 7 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 8 |
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
| 9 | |||
| 10 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 11 |
import {Properties} from "../Properties.sol";
|
||
| 12 |
import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 13 |
import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
|
||
| 14 |
import {MockERC4626Tester} from "test/recon/mocks/MockERC4626Tester.sol";
|
||
| 15 |
import {MockERC5115Tester} from "test/recon/mocks/MockERC5115Tester.sol";
|
||
| 16 |
import {MockERC7540Tester} from "test/recon/mocks/MockERC7540Tester.sol";
|
||
| 17 | |||
| 18 |
abstract contract DoomsdayTargets is BaseTargetFunctions, Properties {
|
||
| 19 |
/// @dev Property: previewDeposit and deposit equivalence |
||
| 20 |
function doomsday_previewDepositEquivalence_ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE( |
||
| 21 |
uint256 assets |
||
| 22 |
) public stateless {
|
||
| 23 |
✓ 152.6K
|
uint256 previewDepositShares = superVault.previewDeposit(assets); |
|
| 24 | |||
| 25 |
✓ 149.8K
|
vm.prank(_getActor()); |
|
| 26 |
✓ 149.8K
|
uint256 sharesActualDeposit = superVault.deposit(assets, _getActor()); |
|
| 27 | |||
| 28 |
✓ 11.3K
|
eq( |
|
| 29 |
✓ 11.3K
|
previewDepositShares, |
|
| 30 |
✓ 11.3K
|
sharesActualDeposit, |
|
| 31 |
✓ 11.3K
|
ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE |
|
| 32 |
); |
||
| 33 |
} |
||
| 34 | |||
| 35 |
/// @dev Property: previewMint and mint equivalence |
||
| 36 |
function doomsday_previewMintEquivalence_ASSERTION_PREVIEW_MINT_EQUIVALENCE(uint256 shares) public stateless {
|
||
| 37 |
✓ 112.8K
|
uint256 previewMintAssets = superVault.previewMint(shares); |
|
| 38 | |||
| 39 |
✓ 79.8K
|
vm.prank(_getActor()); |
|
| 40 |
✓ 79.8K
|
uint256 assetsActualMint = superVault.mint(shares, _getActor()); |
|
| 41 | |||
| 42 |
✓ 15.1K
|
eq( |
|
| 43 |
✓ 15.1K
|
previewMintAssets, |
|
| 44 |
✓ 15.1K
|
assetsActualMint, |
|
| 45 |
✓ 15.1K
|
ASSERTION_PREVIEW_MINT_EQUIVALENCE |
|
| 46 |
); |
||
| 47 |
} |
||
| 48 | |||
| 49 |
/// @dev Property: mint/redeem doesn't cause loss to user |
||
| 50 |
function doomsday_mintRedeemSymmetrical_ASSERTION_MINT_REDEEM_SYMMETRICAL( |
||
| 51 |
uint256 sharesToMint |
||
| 52 |
) public stateless {
|
||
| 53 |
// skip if there's been any gain because it complicates the assertion checking |
||
| 54 |
// NOTE: removed because was previously checking that user doesn't gain only from minting/redeeming |
||
| 55 |
// if (MockERC4626Tester(_getYieldSource()).totalGains() > 0) {
|
||
| 56 |
// return; |
||
| 57 |
// } |
||
| 58 |
✓ 149.7K
|
address asset = superVault.asset(); |
|
| 59 |
✓ 149.7K
|
uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
|
| 60 |
✓ 149.7K
|
uint256 feeRecipientBalanceBefore = MockERC20(asset).balanceOf( |
|
| 61 |
✓ 149.7K
|
feeRecipient |
|
| 62 |
); |
||
| 63 | |||
| 64 |
// 1. Mint |
||
| 65 |
✓ 149.7K
|
vm.prank(_getActor()); |
|
| 66 |
✓ 149.7K
|
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 |
✓ 30.9K
|
uint256 strategyAssetBalance = MockERC20(asset).balanceOf( |
|
| 72 |
✓ 30.9K
|
address(superVaultStrategy) |
|
| 73 |
); |
||
| 74 |
✓ 30.9K
|
if (strategyAssetBalance > 0) {
|
|
| 75 |
✓ 30.9K
|
ISuperVaultStrategy.ExecuteArgs |
|
| 76 |
✓ 30.9K
|
memory depositArgs = _createExecuteDepositArgs( |
|
| 77 |
✓ 30.9K
|
strategyAssetBalance |
|
| 78 |
); |
||
| 79 | |||
| 80 |
✓ 30.9K
|
superVaultStrategy.executeHooks(depositArgs); |
|
| 81 |
} |
||
| 82 | |||
| 83 |
// 3. Request Redemption |
||
| 84 |
✓ 14.3K
|
uint256 userShares = superVault.balanceOf(_getActor()); |
|
| 85 |
// get shares of strategy in the deposited yield source |
||
| 86 |
✓ 14.3K
|
uint256 shares = _getSuperVaultStrategyShares(); |
|
| 87 | |||
| 88 |
✓ 14.3K
|
vm.prank(_getActor()); |
|
| 89 |
✓ 14.3K
|
superVault.requestRedeem(shares, _getActor(), _getActor()); |
|
| 90 | |||
| 91 |
✓ 13.8K
|
uint256 feeBalanceBefore = MockERC20(superVault.asset()).balanceOf( |
|
| 92 |
✓ 13.8K
|
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 |
✓ 13.8K
|
ISuperVaultStrategy.FulfillArgs |
|
| 98 |
✓ 13.8K
|
memory fulfillArgs = _createFulfillRedeemFromStrategyArgs(shares); |
|
| 99 | |||
| 100 |
// called by admin address(this) |
||
| 101 |
✓ 13.8K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 102 | |||
| 103 |
// 5. Claim Redemption |
||
| 104 |
✓ 11.8K
|
uint256 sharesToRedeem = superVault.maxRedeem(_getActor()); |
|
| 105 | |||
| 106 |
✓ 11.8K
|
vm.prank(_getActor()); |
|
| 107 |
✓ 11.8K
|
superVault.redeem(sharesToRedeem, _getActor(), _getActor()); |
|
| 108 | |||
| 109 |
✓ 10.6K
|
uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor()); |
|
| 110 | |||
| 111 |
✓ 10.6K
|
uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost |
|
| 112 | |||
| 113 |
✓ 10.6K
|
uint256 feeRecipientBalanceAfter = MockERC20(superVault.asset()) |
|
| 114 |
✓ 10.6K
|
.balanceOf(feeRecipient); |
|
| 115 |
✓ 10.6K
|
uint256 feeDelta = feeRecipientBalanceAfter - feeRecipientBalanceBefore; |
|
| 116 | |||
| 117 |
// 6. Check that user didn't lose assets |
||
| 118 |
✓ 10.6K
|
gte( |
|
| 119 |
✓ 10.6K
|
balanceAfter + TOLERANCE + feeDelta, |
|
| 120 |
✓ 10.6K
|
balanceBefore, |
|
| 121 |
✓ 10.6K
|
ASSERTION_MINT_REDEEM_SYMMETRICAL |
|
| 122 |
); |
||
| 123 |
} |
||
| 124 | |||
| 125 |
/// @dev Property: deposit/withdraw doesn't cause loss to user |
||
| 126 |
function doomsday_depositWithdrawSymmetrical_ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL( |
||
| 127 |
uint256 assetsToDeposit |
||
| 128 |
✓ 138.3K
|
) 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 |
✓ 138.3K
|
address asset = superVault.asset(); |
|
| 135 |
✓ 138.3K
|
uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor()); |
|
| 136 | |||
| 137 |
// 1. Deposit |
||
| 138 |
✓ 138.3K
|
vm.prank(_getActor()); |
|
| 139 |
✓ 138.3K
|
superVault.deposit(assetsToDeposit, _getActor()); |
|
| 140 | |||
| 141 |
// 2. Request Withdrawal (through redemption in ERC7540) |
||
| 142 |
✓ 33.4K
|
uint256 shares = superVault.balanceOf(_getActor()); |
|
| 143 |
✓ 33.4K
|
vm.prank(_getActor()); |
|
| 144 |
✓ 33.4K
|
superVault.requestRedeem(shares, _getActor(), _getActor()); |
|
| 145 | |||
| 146 |
// 3. Fulfill Withdrawal |
||
| 147 |
✓ 33.4K
|
ISuperVaultStrategy.FulfillArgs |
|
| 148 |
✓ 33.4K
|
memory fulfillArgs = _createFulfillRedeemArgs(shares); |
|
| 149 |
// fulfills as admin (address(this)) |
||
| 150 |
✓ 33.4K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 151 | |||
| 152 |
// 4. Claim Withdrawal |
||
| 153 |
✓ 4.4K
|
uint256 withdrawableAssets = superVault.maxWithdraw(_getActor()); |
|
| 154 |
✓ 4.4K
|
vm.prank(_getActor()); |
|
| 155 |
✓ 4.4K
|
superVault.withdraw(withdrawableAssets, _getActor(), _getActor()); |
|
| 156 | |||
| 157 |
✓ 4.4K
|
uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor()); |
|
| 158 | |||
| 159 |
✓ 4.4K
|
uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost |
|
| 160 |
// 5. Check that user didn't lose assets |
||
| 161 |
✓ 4.4K
|
gte( |
|
| 162 |
✓ 4.4K
|
balanceAfter + TOLERANCE, |
|
| 163 |
✓ 4.4K
|
balanceBefore, |
|
| 164 |
✓ 4.4K
|
ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL |
|
| 165 |
); |
||
| 166 | |||
| 167 |
✓ 4.4K
|
return (balanceAfter, balanceBefore); |
|
| 168 |
} |
||
| 169 | |||
| 170 |
/// @dev Property: maxRedeem is reset to 0 after full redemption |
||
| 171 |
/// @dev Property: redeeming maxRedeem shouldn't revert |
||
| 172 |
function doomsday_maxRedeemResetsAfterFullRedemption_ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION( |
||
| 173 |
uint256 sharesToMint |
||
| 174 |
) public stateless {
|
||
| 175 |
// 1. Deposit to get shares |
||
| 176 |
✓ 126.5K
|
vm.prank(_getActor()); |
|
| 177 |
✓ 126.5K
|
superVault.mint(sharesToMint, _getActor()); |
|
| 178 | |||
| 179 |
// redeem all user shares |
||
| 180 |
✓ 26.7K
|
uint256 shares = superVault.balanceOf(_getActor()); |
|
| 181 | |||
| 182 |
// 2. Request full redemption |
||
| 183 |
✓ 26.7K
|
vm.prank(_getActor()); |
|
| 184 |
✓ 26.7K
|
superVault.requestRedeem(shares, _getActor(), _getActor()); |
|
| 185 | |||
| 186 |
// 3. Fulfill the redemption request |
||
| 187 |
✓ 26.7K
|
ISuperVaultStrategy.FulfillArgs |
|
| 188 |
✓ 26.7K
|
memory fulfillArgs = _createFulfillRedeemArgs(shares); |
|
| 189 |
// fulfill as address(this) |
||
| 190 |
✓ 26.7K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 191 | |||
| 192 |
// 4. Check maxRedeem before claiming |
||
| 193 |
✓ 8.0K
|
uint256 maxRedeemBeforeClaim = superVault.maxRedeem(_getActor()); |
|
| 194 | |||
| 195 |
// 5. Claim the full redemption |
||
| 196 |
✓ 8.0K
|
vm.prank(_getActor()); |
|
| 197 |
✓ 8.0K
|
try superVault.redeem(maxRedeemBeforeClaim, _getActor(), _getActor()) {
|
|
| 198 |
// 6. Check maxRedeem is reset to 0 after full redemption |
||
| 199 |
✓ 8.0K
|
uint256 maxRedeemAfterClaim = superVault.maxRedeem(_getActor()); |
|
| 200 |
✓ 8.0K
|
eq( |
|
| 201 |
✓ 8.0K
|
maxRedeemAfterClaim, |
|
| 202 |
✓ 8.0K
|
0, |
|
| 203 |
✓ 8.0K
|
ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION |
|
| 204 |
); |
||
| 205 |
} catch {
|
||
| 206 |
if (maxRedeemBeforeClaim > 0) {
|
||
| 207 |
t(false, ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION); |
||
| 208 |
} |
||
| 209 |
} |
||
| 210 |
} |
||
| 211 | |||
| 212 |
/// @dev Property: maxWithdraw is reset to 0 after full withdrawal |
||
| 213 |
function doomsday_maxWithdrawResetsAfterFullWithdrawal_ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL( |
||
| 214 |
uint256 assetsToDeposit |
||
| 215 |
) public stateless {
|
||
| 216 |
// 1. Deposit to get shares |
||
| 217 |
✓ 142.8K
|
vm.prank(_getActor()); |
|
| 218 |
✓ 142.8K
|
superVault.deposit(assetsToDeposit, _getActor()); |
|
| 219 | |||
| 220 |
✓ 22.2K
|
uint256 shares = superVault.balanceOf(_getActor()); |
|
| 221 | |||
| 222 |
// 2. Request redemption of all shares |
||
| 223 |
✓ 22.2K
|
vm.prank(_getActor()); |
|
| 224 |
✓ 22.2K
|
superVault.requestRedeem(shares, _getActor(), _getActor()); |
|
| 225 | |||
| 226 |
// 3. Fulfill the redemption request |
||
| 227 |
✓ 22.2K
|
ISuperVaultStrategy.FulfillArgs |
|
| 228 |
✓ 22.2K
|
memory fulfillArgs = _createFulfillRedeemArgs(shares); |
|
| 229 |
// called as admin address(this) |
||
| 230 |
✓ 22.2K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 231 | |||
| 232 |
// 4. Check maxWithdraw after fulfillment and use that value |
||
| 233 |
✓ 1.6K
|
uint256 maxWithdrawBefore = superVault.maxWithdraw(_getActor()); |
|
| 234 | |||
| 235 |
// 5. Withdraw the exact amount returned by maxWithdraw |
||
| 236 |
✓ 1.6K
|
vm.prank(_getActor()); |
|
| 237 |
✓ 1.6K
|
try superVault.withdraw(maxWithdrawBefore, _getActor(), _getActor()) {
|
|
| 238 |
// 6. Check maxWithdraw is reset to 0 after full withdrawal |
||
| 239 |
✓ 1.6K
|
uint256 maxWithdrawAfter = superVault.maxWithdraw(_getActor()); |
|
| 240 |
✓ 1.6K
|
eq( |
|
| 241 |
✓ 1.6K
|
maxWithdrawAfter, |
|
| 242 |
✓ 1.6K
|
0, |
|
| 243 |
✓ 1.6K
|
ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL |
|
| 244 |
); |
||
| 245 |
} catch {
|
||
| 246 |
t(false, ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL); |
||
| 247 |
} |
||
| 248 |
} |
||
| 249 | |||
| 250 |
/// @dev Property: fulfillRedeemRequests doesn't redeem more than requested for multiple actors |
||
| 251 |
function doomsday_fulfillDoesntOverRedeemMultipleActors_ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS( |
||
| 252 |
uint256[3] memory sharesToMint, |
||
| 253 |
uint256[3] memory actorIndexes |
||
| 254 |
) public stateless {
|
||
| 255 |
✓ 130.7K
|
address[] memory actors = _getActors(); |
|
| 256 |
✓ 130.7K
|
if (actors.length < 3) return; // Need at least 3 actors for this test |
|
| 257 | |||
| 258 |
// Arrays to track actors and their requests |
||
| 259 |
✓ 130.7K
|
address[] memory testActors = new address[](3); |
|
| 260 |
✓ 130.7K
|
uint256[] memory requestedShares = new uint256[](3); |
|
| 261 |
✓ 130.7K
|
uint256[] memory sharesBefore = new uint256[](3); |
|
| 262 | |||
| 263 |
// 1. Setup: Each actor deposits and requests redemption |
||
| 264 |
✓ 130.7K
|
uint256 totalRequestedShares; |
|
| 265 |
✓ 152.5K
|
for (uint256 i = 0; i < 3; i++) {
|
|
| 266 |
// Get unique actor |
||
| 267 |
✓ 150.7K
|
testActors[i] = actors[actorIndexes[i] % actors.length]; |
|
| 268 | |||
| 269 |
// Mint shares for this actor |
||
| 270 |
✓ 150.7K
|
if (sharesToMint[i] > 0) {
|
|
| 271 |
✓ 148.4K
|
vm.prank(testActors[i]); |
|
| 272 |
✓ 148.4K
|
superVault.mint(sharesToMint[i], testActors[i]); |
|
| 273 |
} |
||
| 274 | |||
| 275 |
// Get actual share balance |
||
| 276 |
✓ 21.8K
|
sharesBefore[i] = superVault.maxRedeem(testActors[i]); |
|
| 277 |
✓ 21.8K
|
requestedShares[i] = sharesBefore[i]; |
|
| 278 | |||
| 279 |
// Request redemption of all shares |
||
| 280 |
✓ 21.8K
|
if (requestedShares[i] > 0) {
|
|
| 281 |
✓ 210
|
vm.prank(testActors[i]); |
|
| 282 |
✓ 210
|
superVault.requestRedeem( |
|
| 283 |
✓ 210
|
requestedShares[i], |
|
| 284 |
✓ 210
|
testActors[i], |
|
| 285 |
✓ 210
|
testActors[i] |
|
| 286 |
); |
||
| 287 |
} |
||
| 288 |
✓ 21.8K
|
totalRequestedShares += requestedShares[i]; |
|
| 289 |
} |
||
| 290 | |||
| 291 |
// 2. Create multi-actor FulfillArgs |
||
| 292 |
✓ 1.7K
|
ISuperVaultStrategy.FulfillArgs |
|
| 293 |
✓ 1.7K
|
memory fulfillArgs = _createMultiActorFulfillArgs( |
|
| 294 |
✓ 1.7K
|
testActors, |
|
| 295 |
✓ 1.7K
|
requestedShares |
|
| 296 |
); |
||
| 297 | |||
| 298 |
// 3. Calculate total pending before |
||
| 299 |
✓ 1.7K
|
uint256 totalPendingBefore; |
|
| 300 |
✓ 7.0K
|
for (uint256 i = 0; i < 3; i++) {
|
|
| 301 |
✓ 5.2K
|
totalPendingBefore += superVault.pendingRedeemRequest( |
|
| 302 |
0, |
||
| 303 |
✓ 5.2K
|
testActors[i] |
|
| 304 |
); |
||
| 305 |
} |
||
| 306 | |||
| 307 |
// 4. Fulfill all redemption requests at once |
||
| 308 |
// fulfill as address(this) |
||
| 309 |
✓ 1.7K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 310 | |||
| 311 |
// 5. Calculate total pending after |
||
| 312 |
uint256 totalPendingAfter; |
||
| 313 |
for (uint256 i = 0; i < 3; i++) {
|
||
| 314 |
totalPendingAfter += superVault.pendingRedeemRequest( |
||
| 315 |
0, |
||
| 316 |
testActors[i] |
||
| 317 |
); |
||
| 318 |
} |
||
| 319 | |||
| 320 |
lte( |
||
| 321 |
totalPendingBefore - totalPendingAfter, |
||
| 322 |
totalRequestedShares, |
||
| 323 |
ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS |
||
| 324 |
); |
||
| 325 |
} |
||
| 326 | |||
| 327 |
/// @dev Property: primary manager can always be replaced by governance via `changePrimaryManager` |
||
| 328 |
function doomsday_primaryManagerAlwaysChangeable_ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE() public stateless {
|
||
| 329 |
✓ 134.7K
|
address strategy = address(superVaultStrategy); |
|
| 330 |
✓ 134.7K
|
address newManager = _getActor(); |
|
| 331 | |||
| 332 |
// Since address(this) has SUPER_GOVERNOR_ROLE, this should always succeed |
||
| 333 |
✓ 134.7K
|
vm.prank(address(this)); |
|
| 334 |
✓ 134.7K
|
try superGovernor.changePrimaryManager(strategy, newManager) {
|
|
| 335 |
// Call succeeded - this is expected behavior |
||
| 336 |
} catch (bytes memory err) {
|
||
| 337 |
bool expectedError; |
||
| 338 |
expectedError = checkError(err, "MANAGER_TAKEOVERS_FROZEN()"); // custom error |
||
| 339 |
t( |
||
| 340 |
!expectedError, |
||
| 341 |
ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE |
||
| 342 |
); |
||
| 343 |
} |
||
| 344 |
} |
||
| 345 | |||
| 346 |
/// @dev Property: all users can withdraw (solvency) |
||
| 347 |
// NOTE: if withdrawing from a given strategy via fulfillRedeemRequests fails, it can be expected that one of the YieldSourceTargets would be called to switch the yield source |
||
| 348 |
// this should allow fulfillments to eventually succeed so we don't need to sort through all yield sources that have currently been deposited into before fulfilling |
||
| 349 |
function doomsday_allUsersCanWithdraw_ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED() public stateless {
|
||
| 350 |
✓ 122.1K
|
address[] memory actors = _getActors(); |
|
| 351 |
✓ 122.1K
|
bool paused = superVaultAggregator.isStrategyPaused( |
|
| 352 |
✓ 122.1K
|
address(superVaultStrategy) |
|
| 353 |
); |
||
| 354 | |||
| 355 |
// request redemption for all actors |
||
| 356 |
✓ 138.4K
|
for (uint256 i; i < actors.length; i++) {
|
|
| 357 |
✓ 136.9K
|
uint256 redeemableShares = superVault.balanceOf(actors[i]); |
|
| 358 | |||
| 359 |
✓ 136.9K
|
vm.prank(actors[i]); |
|
| 360 |
✓ 136.9K
|
superVault.requestRedeem(redeemableShares, actors[i], actors[i]); |
|
| 361 |
} |
||
| 362 | |||
| 363 |
// fulfill redemption for all actors |
||
| 364 |
✓ 1.6K
|
for (uint256 i; i < actors.length; i++) {
|
|
| 365 |
✓ 1.6K
|
uint256 redeemableShares = superVault.pendingRedeemRequest( |
|
| 366 |
0, |
||
| 367 |
✓ 1.6K
|
actors[i] |
|
| 368 |
); |
||
| 369 | |||
| 370 |
// switch the actor |
||
| 371 |
✓ 1.6K
|
_switchActor(i); |
|
| 372 | |||
| 373 |
✓ 1.6K
|
ISuperVaultStrategy.FulfillArgs |
|
| 374 |
✓ 1.6K
|
memory fulfillArgs = _fulfillRedeemRequestsArgs( |
|
| 375 |
✓ 1.6K
|
redeemableShares |
|
| 376 |
); |
||
| 377 |
✓ 1.6K
|
superVaultStrategy.fulfillRedeemRequests(fulfillArgs); |
|
| 378 |
} |
||
| 379 | |||
| 380 |
// try to withdraw max possible for all actors |
||
| 381 |
for (uint256 i; i < actors.length; i++) {
|
||
| 382 |
uint256 withdrawable = superVault.maxWithdraw(actors[i]); |
||
| 383 | |||
| 384 |
if (withdrawable > 0 && !paused) {
|
||
| 385 |
vm.prank(actors[i]); |
||
| 386 |
try |
||
| 387 |
superVault.withdraw(withdrawable, actors[i], actors[i]) |
||
| 388 |
{} catch {
|
||
| 389 |
// if user can't maxWithdraw there's most likely an insolvency issue related to the TOLERANCE_CONSTANT |
||
| 390 |
t( |
||
| 391 |
false, |
||
| 392 |
ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED |
||
| 393 |
); |
||
| 394 |
} |
||
| 395 |
} |
||
| 396 |
} |
||
| 397 |
} |
||
| 398 | |||
| 399 |
/// @dev Property: Claiming redemptions should never revert with INVALID_REDEEM_CLAIM |
||
| 400 |
function doomsday_redemptionsNeverReverts_ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM( |
||
| 401 |
uint256 shares |
||
| 402 |
) public asActor stateless {
|
||
| 403 |
✓ 154.5K
|
try superVault.redeem(shares, _getActor(), _getActor()) {} catch (
|
|
| 404 |
bytes memory err |
||
| 405 |
) {
|
||
| 406 |
✓ 154.5K
|
bool unexpectedError = checkError(err, "INVALID_REDEEM_CLAIM()"); |
|
| 407 |
✓ 154.5K
|
t( |
|
| 408 |
✓ 154.5K
|
!unexpectedError, |
|
| 409 |
✓ 154.5K
|
ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM |
|
| 410 |
); |
||
| 411 |
} |
||
| 412 |
} |
||
| 413 | |||
| 414 |
/// @dev Get shares for SuperVaultStrategy in the current yield source |
||
| 415 |
✓ 14.3K
|
function _getSuperVaultStrategyShares() internal view returns (uint256) {
|
|
| 416 |
✓ 14.3K
|
address yieldSource = _getYieldSource(); |
|
| 417 |
✓ 14.3K
|
YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
|
| 418 |
✓ 14.3K
|
yieldSource |
|
| 419 |
); |
||
| 420 | |||
| 421 |
✓ 14.3K
|
if (sourceType == YieldSourceType.ERC4626) {
|
|
| 422 |
return |
||
| 423 |
✓ 14.3K
|
MockERC4626Tester(yieldSource).balanceOf( |
|
| 424 |
✓ 14.3K
|
address(superVaultStrategy) |
|
| 425 |
); |
||
| 426 |
} else if (sourceType == YieldSourceType.ERC5115) {
|
||
| 427 |
return |
||
| 428 |
MockERC5115Tester(yieldSource).balanceOf( |
||
| 429 |
address(superVaultStrategy) |
||
| 430 |
); |
||
| 431 |
} else if (sourceType == YieldSourceType.ERC7540) {
|
||
| 432 |
return |
||
| 433 |
MockERC7540Tester(yieldSource).balanceOf( |
||
| 434 |
address(superVaultStrategy) |
||
| 435 |
); |
||
| 436 |
} else {
|
||
| 437 |
revert("Invalid yield source type");
|
||
| 438 |
} |
||
| 439 |
} |
||
| 440 | |||
| 441 |
// Helpers |
||
| 442 | |||
| 443 |
/// @dev Helper function to clamp the values for the function call |
||
| 444 |
function _fulfillRedeemRequestsArgs( |
||
| 445 |
uint256 redeemAmount |
||
| 446 |
✓ 169.4K
|
) public returns (ISuperVaultStrategy.FulfillArgs memory fulfillArgs) {
|
|
| 447 |
// Find a controller that has pending redeem requests |
||
| 448 |
✓ 169.4K
|
address selectedController = _getActor(); |
|
| 449 |
✓ 169.4K
|
uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest( |
|
| 450 |
selectedController |
||
| 451 |
); |
||
| 452 | |||
| 453 |
// Clamp using the actor's pending amount |
||
| 454 |
✓ 169.4K
|
uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1); |
|
| 455 | |||
| 456 |
✓ 169.4K
|
address[] memory controllers = new address[](1); |
|
| 457 |
✓ 169.4K
|
controllers[0] = selectedController; |
|
| 458 | |||
| 459 |
// Determine yield source type from currently active yield source |
||
| 460 |
✓ 169.4K
|
YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress( |
|
| 461 |
✓ 169.4K
|
_getYieldSource() |
|
| 462 |
); |
||
| 463 |
✓ 169.4K
|
address redeemHook = _getRedeemHookForType(activeYieldSourceType); |
|
| 464 | |||
| 465 |
// Create realistic hook calldata for redeem operation |
||
| 466 |
✓ 169.4K
|
bytes memory redeemHookCalldata; |
|
| 467 | |||
| 468 |
if ( |
||
| 469 |
✓ 169.4K
|
activeYieldSourceType == YieldSourceType.ERC4626 || |
|
| 470 |
✓ 56.0K
|
activeYieldSourceType == YieldSourceType.ERC5115 |
|
| 471 |
) {
|
||
| 472 |
// ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
||
| 473 |
redeemHookCalldata = abi.encodePacked( |
||
| 474 |
✓ 146.2K
|
bytes32(0), // yieldSourceOracleId placeholder |
|
| 475 |
✓ 146.2K
|
_getYieldSource(), // Current active yield source |
|
| 476 |
✓ 146.2K
|
address(superVaultStrategy), // Owner (strategy owns the yield source shares) |
|
| 477 |
✓ 146.2K
|
actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
|
| 478 |
false // Don't use previous hook amount |
||
| 479 |
); |
||
| 480 |
} else {
|
||
| 481 |
// ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
||
| 482 |
redeemHookCalldata = abi.encodePacked( |
||
| 483 |
✓ 23.2K
|
bytes32(0), // yieldSourceOracleId placeholder |
|
| 484 |
✓ 23.2K
|
_getYieldSource(), // Current active yield source |
|
| 485 |
✓ 23.2K
|
actualRedeemAmount, // Amount to redeem (matches controller's pending request) |
|
| 486 |
✓ 23.2K
|
false // Don't use previous hook amount |
|
| 487 |
); |
||
| 488 |
} |
||
| 489 | |||
| 490 |
// Create arrays for FulfillArgs |
||
| 491 |
✓ 169.4K
|
address[] memory hooks = new address[](1); |
|
| 492 |
✓ 169.4K
|
hooks[0] = redeemHook; |
|
| 493 | |||
| 494 |
✓ 169.4K
|
bytes[] memory hookCalldata = new bytes[](1); |
|
| 495 |
✓ 169.4K
|
hookCalldata[0] = redeemHookCalldata; |
|
| 496 | |||
| 497 |
✓ 169.4K
|
uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
|
| 498 |
✓ 169.4K
|
expectedAssetsOrSharesOut[0] = actualRedeemAmount; // Expect amount matching the actual redeem |
|
| 499 | |||
| 500 |
✓ 169.4K
|
bytes32[][] memory globalProofs = new bytes32[][](1); |
|
| 501 |
✓ 169.4K
|
globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator |
|
| 502 | |||
| 503 |
✓ 169.4K
|
bytes32[][] memory strategyProofs = new bytes32[][](1); |
|
| 504 |
✓ 169.4K
|
strategyProofs[0] = new bytes32[](0); // Empty proof |
|
| 505 | |||
| 506 |
// Create the FulfillArgs struct |
||
| 507 |
✓ 169.4K
|
fulfillArgs = ISuperVaultStrategy.FulfillArgs({
|
|
| 508 |
controllers: controllers, |
||
| 509 |
hooks: hooks, |
||
| 510 |
hookCalldata: hookCalldata, |
||
| 511 |
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
||
| 512 |
globalProofs: globalProofs, |
||
| 513 |
strategyProofs: strategyProofs |
||
| 514 |
}); |
||
| 515 | |||
| 516 |
return fulfillArgs; |
||
| 517 |
} |
||
| 518 | |||
| 519 |
/// @dev Helper function to create FulfillArgs for multiple actors |
||
| 520 |
function _createMultiActorFulfillArgs( |
||
| 521 |
address[] memory controllers, |
||
| 522 |
uint256[] memory amounts |
||
| 523 |
✓ 1.7K
|
) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
|
|
| 524 |
✓ 1.7K
|
uint256 numActors = controllers.length; |
|
| 525 | |||
| 526 |
✓ 1.7K
|
address[] memory hooks = new address[](numActors); |
|
| 527 |
✓ 1.7K
|
bytes[] memory hookCalldata = new bytes[](numActors); |
|
| 528 |
✓ 1.7K
|
uint256[] memory expectedAssetsOrSharesOut = new uint256[](numActors); |
|
| 529 |
✓ 1.7K
|
bytes32[][] memory globalProofs = new bytes32[][](numActors); |
|
| 530 |
✓ 1.7K
|
bytes32[][] memory strategyProofs = new bytes32[][](numActors); |
|
| 531 | |||
| 532 |
✓ 7.0K
|
for (uint256 i = 0; i < numActors; i++) {
|
|
| 533 |
✓ 5.2K
|
hooks[i] = _getRedeemHookForType( |
|
| 534 |
✓ 5.2K
|
_getYieldSourceTypeFromAddress(_getYieldSource()) |
|
| 535 |
); |
||
| 536 | |||
| 537 |
if ( |
||
| 538 |
✓ 5.2K
|
_getYieldSourceTypeFromAddress(_getYieldSource()) == |
|
| 539 |
✓ 5.2K
|
YieldSourceType.ERC4626 |
|
| 540 |
) {
|
||
| 541 |
✓ 1.7K
|
hookCalldata[i] = abi.encodePacked( |
|
| 542 |
✓ 1.7K
|
bytes32(0), |
|
| 543 |
✓ 1.7K
|
_getYieldSource(), |
|
| 544 |
✓ 1.7K
|
address(superVaultStrategy), |
|
| 545 |
✓ 1.7K
|
amounts[i], |
|
| 546 |
✓ 1.7K
|
false |
|
| 547 |
); |
||
| 548 |
} else {
|
||
| 549 |
✓ 3.5K
|
hookCalldata[i] = abi.encodePacked( |
|
| 550 |
✓ 3.5K
|
bytes32(0), |
|
| 551 |
✓ 3.5K
|
_getYieldSource(), |
|
| 552 |
✓ 3.5K
|
amounts[i], |
|
| 553 |
✓ 3.5K
|
false |
|
| 554 |
); |
||
| 555 |
} |
||
| 556 | |||
| 557 |
✓ 5.2K
|
expectedAssetsOrSharesOut[i] = amounts[i]; |
|
| 558 |
✓ 5.2K
|
globalProofs[i] = new bytes32[](0); |
|
| 559 |
✓ 5.2K
|
strategyProofs[i] = new bytes32[](0); |
|
| 560 |
} |
||
| 561 | |||
| 562 |
return |
||
| 563 |
✓ 1.7K
|
ISuperVaultStrategy.FulfillArgs({
|
|
| 564 |
controllers: controllers, |
||
| 565 |
hooks: hooks, |
||
| 566 |
hookCalldata: hookCalldata, |
||
| 567 |
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
||
| 568 |
globalProofs: globalProofs, |
||
| 569 |
strategyProofs: strategyProofs |
||
| 570 |
}); |
||
| 571 |
} |
||
| 572 | |||
| 573 |
/// @dev Helper function to create FulfillArgs for redeem requests |
||
| 574 |
function _createFulfillRedeemArgs( |
||
| 575 |
uint256 amount |
||
| 576 |
✓ 82.3K
|
) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
|
|
| 577 |
✓ 82.3K
|
address[] memory controllers = new address[](1); |
|
| 578 |
✓ 82.3K
|
controllers[0] = _getActor(); |
|
| 579 | |||
| 580 |
✓ 82.3K
|
address[] memory hooks = new address[](1); |
|
| 581 |
✓ 101.3K
|
hooks[0] = _getRedeemHookForType( |
|
| 582 |
✓ 82.3K
|
_getYieldSourceTypeFromAddress(_getYieldSource()) |
|
| 583 |
); |
||
| 584 | |||
| 585 |
✓ 82.3K
|
bytes[] memory hookCalldata = new bytes[](1); |
|
| 586 |
if ( |
||
| 587 |
✓ 82.3K
|
_getYieldSourceTypeFromAddress(_getYieldSource()) == |
|
| 588 |
✓ 82.3K
|
YieldSourceType.ERC4626 |
|
| 589 |
) {
|
||
| 590 |
✓ 55.8K
|
hookCalldata[0] = abi.encodePacked( |
|
| 591 |
✓ 55.8K
|
bytes32(0), |
|
| 592 |
✓ 55.8K
|
_getYieldSource(), |
|
| 593 |
✓ 55.8K
|
address(superVaultStrategy), |
|
| 594 |
✓ 55.8K
|
amount, |
|
| 595 |
false |
||
| 596 |
); |
||
| 597 |
} else {
|
||
| 598 |
✓ 26.5K
|
hookCalldata[0] = abi.encodePacked( |
|
| 599 |
✓ 26.5K
|
bytes32(0), |
|
| 600 |
✓ 26.5K
|
_getYieldSource(), |
|
| 601 |
✓ 26.5K
|
amount, |
|
| 602 |
✓ 26.5K
|
false |
|
| 603 |
); |
||
| 604 |
} |
||
| 605 | |||
| 606 |
✓ 82.3K
|
uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
|
| 607 |
✓ 82.3K
|
expectedAssetsOrSharesOut[0] = amount; |
|
| 608 | |||
| 609 |
✓ 82.3K
|
bytes32[][] memory globalProofs = new bytes32[][](1); |
|
| 610 |
✓ 82.3K
|
globalProofs[0] = new bytes32[](0); |
|
| 611 | |||
| 612 |
✓ 82.3K
|
bytes32[][] memory strategyProofs = new bytes32[][](1); |
|
| 613 |
✓ 82.3K
|
strategyProofs[0] = new bytes32[](0); |
|
| 614 | |||
| 615 |
return |
||
| 616 |
✓ 82.3K
|
ISuperVaultStrategy.FulfillArgs({
|
|
| 617 |
controllers: controllers, |
||
| 618 |
hooks: hooks, |
||
| 619 |
hookCalldata: hookCalldata, |
||
| 620 |
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
||
| 621 |
globalProofs: globalProofs, |
||
| 622 |
strategyProofs: strategyProofs |
||
| 623 |
}); |
||
| 624 |
} |
||
| 625 | |||
| 626 |
/// @dev Helper function to create ExecuteArgs for depositing assets into yield strategy |
||
| 627 |
function _createExecuteDepositArgs( |
||
| 628 |
uint256 amount |
||
| 629 |
✓ 30.9K
|
) internal view returns (ISuperVaultStrategy.ExecuteArgs memory) {
|
|
| 630 |
✓ 30.9K
|
address[] memory hooks = new address[](1); |
|
| 631 |
✓ 30.9K
|
hooks[0] = _getApproveAndDepositHookForType( |
|
| 632 |
✓ 30.9K
|
_getYieldSourceTypeFromAddress(_getYieldSource()) |
|
| 633 |
); |
||
| 634 | |||
| 635 |
✓ 30.9K
|
bytes[] memory hookCalldata = new bytes[](1); |
|
| 636 |
✓ 30.9K
|
YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
|
| 637 |
✓ 30.9K
|
_getYieldSource() |
|
| 638 |
); |
||
| 639 | |||
| 640 |
✓ 30.9K
|
if (sourceType == YieldSourceType.ERC4626) {
|
|
| 641 |
// ERC4626 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, bool usePrevAmount |
||
| 642 |
✓ 24.6K
|
hookCalldata[0] = abi.encodePacked( |
|
| 643 |
✓ 21.8K
|
bytes32(0), // oracle ID placeholder |
|
| 644 |
✓ 21.8K
|
_getYieldSource(), |
|
| 645 |
✓ 21.8K
|
superVault.asset(), // token address |
|
| 646 |
✓ 21.8K
|
amount, |
|
| 647 |
✓ 21.8K
|
false // don't use previous amount |
|
| 648 |
); |
||
| 649 |
✓ 9.1K
|
} else if (sourceType == YieldSourceType.ERC5115) {
|
|
| 650 |
// ERC5115 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address receiver, uint256 id, bool usePrevAmount |
||
| 651 |
hookCalldata[0] = abi.encodePacked( |
||
| 652 |
✓ 2.8K
|
bytes32(0), |
|
| 653 |
✓ 2.8K
|
_getYieldSource(), |
|
| 654 |
✓ 2.8K
|
superVault.asset(), // token address |
|
| 655 |
amount, |
||
| 656 |
✓ 2.8K
|
address(superVaultStrategy), |
|
| 657 |
uint256(0), // sy ID for ERC5115 |
||
| 658 |
false |
||
| 659 |
); |
||
| 660 |
✓ 6.3K
|
} else if (sourceType == YieldSourceType.ERC7540) {
|
|
| 661 |
// ERC7540 requestDeposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address controller, bool usePrevAmount |
||
| 662 |
✓ 6.3K
|
hookCalldata[0] = abi.encodePacked( |
|
| 663 |
✓ 6.3K
|
bytes32(0), |
|
| 664 |
✓ 6.3K
|
_getYieldSource(), |
|
| 665 |
✓ 6.3K
|
superVault.asset(), // token address |
|
| 666 |
amount, |
||
| 667 |
✓ 6.3K
|
address(superVaultStrategy), |
|
| 668 |
false |
||
| 669 |
); |
||
| 670 |
} |
||
| 671 | |||
| 672 |
✓ 30.9K
|
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 |
✓ 30.9K
|
expectedAssetsOrSharesOut[0] = 1; // Minimum expected shares from deposit |
|
| 676 | |||
| 677 |
✓ 30.9K
|
bytes32[][] memory globalProofs = new bytes32[][](1); |
|
| 678 |
✓ 30.9K
|
globalProofs[0] = new bytes32[](0); |
|
| 679 | |||
| 680 |
✓ 30.9K
|
bytes32[][] memory strategyProofs = new bytes32[][](1); |
|
| 681 |
✓ 30.9K
|
strategyProofs[0] = new bytes32[](0); |
|
| 682 | |||
| 683 |
return |
||
| 684 |
ISuperVaultStrategy.ExecuteArgs({
|
||
| 685 |
✓ 30.9K
|
hooks: hooks, |
|
| 686 |
✓ 30.9K
|
hookCalldata: hookCalldata, |
|
| 687 |
✓ 30.9K
|
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
|
| 688 |
✓ 30.9K
|
globalProofs: globalProofs, |
|
| 689 |
✓ 30.9K
|
strategyProofs: strategyProofs |
|
| 690 |
}); |
||
| 691 |
} |
||
| 692 | |||
| 693 |
/// @dev Helper function to create FulfillArgs for redeeming from yield strategy |
||
| 694 |
function _createFulfillRedeemFromStrategyArgs( |
||
| 695 |
uint256 sharesToRedeem |
||
| 696 |
✓ 13.8K
|
) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
|
|
| 697 |
✓ 13.8K
|
address[] memory controllers = new address[](1); |
|
| 698 |
✓ 13.8K
|
controllers[0] = _getActor(); |
|
| 699 | |||
| 700 |
// Get the actual share balance that the SuperVaultStrategy holds in the yield source |
||
| 701 |
✓ 13.8K
|
address yieldSource = _getYieldSource(); |
|
| 702 |
✓ 13.8K
|
uint256 strategyYieldSourceShares = IERC20(yieldSource).balanceOf( |
|
| 703 |
✓ 13.8K
|
address(superVaultStrategy) |
|
| 704 |
); |
||
| 705 | |||
| 706 |
// Use the pending shares for the hook calldata to match what's expected by fulfillRedeemRequests |
||
| 707 |
✓ 13.8K
|
uint256 pendingShares = superVault.pendingRedeemRequest(0, _getActor()); |
|
| 708 | |||
| 709 |
// For debugging: ensure we don't try to redeem more than the strategy has |
||
| 710 |
✓ 13.8K
|
uint256 actualSharesToRedeem = strategyYieldSourceShares < pendingShares |
|
| 711 |
✓ 9
|
? strategyYieldSourceShares |
|
| 712 |
✓ 13.8K
|
: pendingShares; |
|
| 713 | |||
| 714 |
✓ 13.8K
|
address[] memory hooks = new address[](1); |
|
| 715 |
✓ 13.8K
|
hooks[0] = _getRedeemHookForType( |
|
| 716 |
✓ 13.8K
|
_getYieldSourceTypeFromAddress(yieldSource) |
|
| 717 |
); |
||
| 718 | |||
| 719 |
✓ 13.8K
|
bytes[] memory hookCalldata = new bytes[](1); |
|
| 720 |
✓ 13.8K
|
YieldSourceType sourceType = _getYieldSourceTypeFromAddress( |
|
| 721 |
✓ 13.8K
|
yieldSource |
|
| 722 |
); |
||
| 723 | |||
| 724 |
if ( |
||
| 725 |
✓ 13.8K
|
sourceType == YieldSourceType.ERC4626 || |
|
| 726 |
sourceType == YieldSourceType.ERC5115 |
||
| 727 |
) {
|
||
| 728 |
// For ERC4626/5115: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount |
||
| 729 |
// Use the calculated shares to redeem (limited by what strategy actually has) |
||
| 730 |
✓ 13.8K
|
hookCalldata[0] = abi.encodePacked( |
|
| 731 |
✓ 13.8K
|
bytes32(0), |
|
| 732 |
✓ 13.8K
|
yieldSource, |
|
| 733 |
✓ 13.8K
|
address(superVaultStrategy), // owner is the strategy |
|
| 734 |
✓ 13.8K
|
actualSharesToRedeem, // redeem calculated amount |
|
| 735 |
false |
||
| 736 |
); |
||
| 737 |
} else {
|
||
| 738 |
// For ERC7540: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount |
||
| 739 |
hookCalldata[0] = abi.encodePacked( |
||
| 740 |
bytes32(0), |
||
| 741 |
yieldSource, |
||
| 742 |
actualSharesToRedeem, // redeem calculated amount |
||
| 743 |
false |
||
| 744 |
); |
||
| 745 |
} |
||
| 746 | |||
| 747 |
// For fulfillment, we expect to get assets back from redeeming the shares |
||
| 748 |
// Use a conservative estimate for expected assets out |
||
| 749 |
✓ 13.8K
|
uint256[] memory expectedAssetsOrSharesOut = new uint256[](1); |
|
| 750 |
✓ 13.8K
|
expectedAssetsOrSharesOut[0] = actualSharesToRedeem > 0 ? 1 : 0; // Minimum 1 asset expected if redeeming anything |
|
| 751 | |||
| 752 |
✓ 13.8K
|
bytes32[][] memory globalProofs = new bytes32[][](1); |
|
| 753 |
✓ 13.8K
|
globalProofs[0] = new bytes32[](0); |
|
| 754 | |||
| 755 |
✓ 13.8K
|
bytes32[][] memory strategyProofs = new bytes32[][](1); |
|
| 756 |
✓ 13.8K
|
strategyProofs[0] = new bytes32[](0); |
|
| 757 | |||
| 758 |
return |
||
| 759 |
✓ 13.8K
|
ISuperVaultStrategy.FulfillArgs({
|
|
| 760 |
controllers: controllers, |
||
| 761 |
hooks: hooks, |
||
| 762 |
hookCalldata: hookCalldata, |
||
| 763 |
expectedAssetsOrSharesOut: expectedAssetsOrSharesOut, |
||
| 764 |
globalProofs: globalProofs, |
||
| 765 |
strategyProofs: strategyProofs |
||
| 766 |
}); |
||
| 767 |
} |
||
| 768 |
} |
||
| 769 |
100.0%
test/recon/targets/ManagersTargets.sol
Lines covered: 8 / 8 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 6 |
import {Properties} from "../Properties.sol";
|
||
| 7 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 8 | |||
| 9 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 10 | |||
| 11 |
// Target functions that are effectively inherited from the Actor and AssetManagers |
||
| 12 |
// Once properly standardized, managers will expose these by default |
||
| 13 |
// Keeping them out makes your project more custom |
||
| 14 |
abstract contract ManagersTargets is BaseTargetFunctions, Properties {
|
||
| 15 |
// == ACTOR HANDLERS == // |
||
| 16 | |||
| 17 |
/// @dev Start acting as another actor |
||
| 18 |
/// @dev Update ghosts here to make global property checks not fail falsely |
||
| 19 |
function switchActor(uint256 entropy) public updateGhosts {
|
||
| 20 |
✓ 136.6K
|
_switchActor(entropy); |
|
| 21 |
} |
||
| 22 | |||
| 23 |
/// @dev Starts using a new asset |
||
| 24 |
function switch_asset(uint256 entropy) public {
|
||
| 25 |
✓ 123.0K
|
_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 |
✓ 229.9K
|
function add_new_asset(uint8 decimals) public returns (address) {
|
|
| 30 |
✓ 229.9K
|
address newAsset = _newAsset(decimals); |
|
| 31 |
return newAsset; |
||
| 32 |
} |
||
| 33 | |||
| 34 |
/// @dev Switches the current vault based on the entropy |
||
| 35 |
/// @param entropy The entropy to choose a random vault in the array for switching |
||
| 36 |
function switch_vault(uint256 entropy) public {
|
||
| 37 |
✓ 117.8K
|
_switchVault(entropy); |
|
| 38 |
} |
||
| 39 | |||
| 40 |
/// @dev Deploy a new vault using the current asset and add it to the list of vaults, |
||
| 41 |
/// then set it as the current vault |
||
| 42 |
function add_new_vault() public {
|
||
| 43 |
✓ 146.6K
|
_newVault(superVault.asset()); |
|
| 44 |
} |
||
| 45 | |||
| 46 |
/// === GHOST UPDATING HANDLERS ===/// |
||
| 47 |
/// We `updateGhosts` cause you never know (e.g. donations) |
||
| 48 |
/// If you don't want to track donations, remove the `updateGhosts` |
||
| 49 | |||
| 50 |
/// @dev Approve to arbitrary address, uses Actor by default |
||
| 51 |
/// NOTE: You're almost always better off setting approvals in `Setup` |
||
| 52 |
function asset_approve( |
||
| 53 |
address to, |
||
| 54 |
uint128 amt |
||
| 55 |
) public updateGhosts asActor {
|
||
| 56 |
✓ 140.8K
|
MockERC20(superVault.asset()).approve(to, amt); |
|
| 57 |
} |
||
| 58 | |||
| 59 |
/// @dev Mint to arbitrary address, uses owner by default, even though MockERC20 doesn't check |
||
| 60 |
function asset_mint(address to, uint128 amt) public updateGhosts asAdmin {
|
||
| 61 |
✓ 153.1K
|
MockERC20(superVault.asset()).mint(to, amt); |
|
| 62 |
} |
||
| 63 |
} |
||
| 64 |
100.0%
test/recon/targets/OracleTargets.sol
Lines covered: 30 / 30 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// External dependencies |
||
| 5 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 6 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 7 |
import {Panic} from "@recon/Panic.sol";
|
||
| 8 | |||
| 9 |
// Source dependencies |
||
| 10 |
import {IECDSAPPSOracle} from "src/interfaces/oracles/IECDSAPPSOracle.sol";
|
||
| 11 |
import "test/mocks/MockYieldSourceOracle.sol"; |
||
| 12 |
import "../mocks/MockERC4626YieldSourceOracle.sol"; |
||
| 13 |
import "../mocks/MockERC5115YieldSourceOracle.sol"; |
||
| 14 |
import "../mocks/MockERC7540YieldSourceOracle.sol"; |
||
| 15 | |||
| 16 |
// Test suite dependencies |
||
| 17 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 18 |
import {Properties} from "../Properties.sol";
|
||
| 19 | |||
| 20 |
abstract contract OracleTargets is BaseTargetFunctions, Properties {
|
||
| 21 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 22 |
function yieldSourceOracle_setValidAsset_clamped() public {
|
||
| 23 |
✓ 154.6K
|
mockERC4626YieldSourceOracle_setValidAsset(superVault.asset(), true); |
|
| 24 |
} |
||
| 25 | |||
| 26 |
function mockERC4626YieldSourceOracle_setValidAsset( |
||
| 27 |
address asset, |
||
| 28 |
bool isValid |
||
| 29 |
) public asActor {
|
||
| 30 |
✓ 324.6K
|
MockERC4626YieldSourceOracle(address(erc4626YieldSourceOracle)) |
|
| 31 |
.setValidAsset(asset, isValid); |
||
| 32 |
} |
||
| 33 | |||
| 34 |
function mockERC5115YieldSourceOracle_setValidAsset( |
||
| 35 |
address asset, |
||
| 36 |
bool isValid |
||
| 37 |
) public asActor {
|
||
| 38 |
✓ 131.0K
|
MockERC5115YieldSourceOracle(address(erc5115YieldSourceOracle)) |
|
| 39 |
.setValidAsset(asset, isValid); |
||
| 40 |
} |
||
| 41 | |||
| 42 |
function mockERC7540YieldSourceOracle_setValidAsset( |
||
| 43 |
address asset, |
||
| 44 |
bool isValid |
||
| 45 |
) public asActor {
|
||
| 46 |
✓ 121.8K
|
MockERC7540YieldSourceOracle(address(erc7540YieldSourceOracle)) |
|
| 47 |
.setValidAsset(asset, isValid); |
||
| 48 |
} |
||
| 49 | |||
| 50 |
function ECDSAPPSOracle_updatePPS_clamped(uint256 pps) public {
|
||
| 51 |
✓ 124.0K
|
pps %= (100_000_000 * 1e18) + 1; // clamp to a reasonable max price |
|
| 52 | |||
| 53 |
✓ 124.0K
|
address[] memory strategies = new address[](1); |
|
| 54 |
✓ 124.0K
|
strategies[0] = address(superVaultStrategy); |
|
| 55 | |||
| 56 |
✓ 124.0K
|
bytes[][] memory proofsArray = new bytes[][](1); |
|
| 57 |
✓ 124.0K
|
proofsArray[0] = new bytes[](0); |
|
| 58 | |||
| 59 |
✓ 124.0K
|
uint256[] memory ppss = new uint256[](1); |
|
| 60 |
✓ 124.0K
|
ppss[0] = pps; |
|
| 61 | |||
| 62 |
✓ 124.0K
|
uint256[] memory ppsStdevs = new uint256[](1); |
|
| 63 |
✓ 124.0K
|
ppsStdevs[0] = 0; |
|
| 64 | |||
| 65 |
✓ 124.0K
|
uint256[] memory validatorSets = new uint256[](1); |
|
| 66 |
✓ 124.0K
|
validatorSets[0] = 0; |
|
| 67 | |||
| 68 |
✓ 124.0K
|
uint256[] memory totalValidators = new uint256[](1); |
|
| 69 |
✓ 124.0K
|
totalValidators[0] = 0; |
|
| 70 | |||
| 71 |
✓ 124.0K
|
uint256[] memory timestamps = new uint256[](1); |
|
| 72 |
✓ 124.0K
|
timestamps[0] = block.timestamp; |
|
| 73 | |||
| 74 |
✓ 124.0K
|
IECDSAPPSOracle.UpdatePPSArgs memory args = IECDSAPPSOracle |
|
| 75 |
.UpdatePPSArgs({
|
||
| 76 |
✓ 124.0K
|
strategies: strategies, |
|
| 77 |
✓ 124.0K
|
proofsArray: proofsArray, |
|
| 78 |
✓ 124.0K
|
ppss: ppss, |
|
| 79 |
✓ 124.0K
|
ppsStdevs: ppsStdevs, |
|
| 80 |
✓ 124.0K
|
validatorSets: validatorSets, |
|
| 81 |
✓ 124.0K
|
totalValidators: totalValidators, |
|
| 82 |
✓ 124.0K
|
timestamps: timestamps |
|
| 83 |
}); |
||
| 84 | |||
| 85 |
✓ 124.0K
|
ECDSAPPSOracle_updatePPS(args); |
|
| 86 |
} |
||
| 87 | |||
| 88 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 89 |
function ECDSAPPSOracle_updatePPS( |
||
| 90 |
IECDSAPPSOracle.UpdatePPSArgs memory args |
||
| 91 |
) public asActor {
|
||
| 92 |
✓ 260.5K
|
ECDSAPPSOracle.updatePPS(args); |
|
| 93 | |||
| 94 |
✓ 112.5K
|
hasUpdatedPPS = true; |
|
| 95 |
} |
||
| 96 |
} |
||
| 97 |
100.0%
test/recon/targets/SuperGovernorTargets.sol
Lines covered: 22 / 22 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 6 |
import {Properties} from "../Properties.sol";
|
||
| 7 |
// Chimera deps |
||
| 8 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 9 | |||
| 10 |
// Helpers |
||
| 11 |
import {Panic} from "@recon/Panic.sol";
|
||
| 12 | |||
| 13 |
import "src/SuperGovernor.sol"; |
||
| 14 | |||
| 15 |
abstract contract SuperGovernorTargets is BaseTargetFunctions, Properties {
|
||
| 16 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 17 |
function superGovernor_proposeGlobalHooksRoot_clamped( |
||
| 18 |
bytes32 newRoot |
||
| 19 |
) public {
|
||
| 20 |
✓ 142.7K
|
(bytes32 testRoot, bytes32[][] memory testProofs) = merkleHelper |
|
| 21 |
.generateTestHooksRoot( |
||
| 22 |
✓ 142.7K
|
address(approveAndDeposit4626Hook), |
|
| 23 |
✓ 142.7K
|
address(redeem4626Hook), |
|
| 24 |
✓ 142.7K
|
_getYieldSource(), |
|
| 25 |
✓ 142.7K
|
superVault.asset() |
|
| 26 |
); |
||
| 27 | |||
| 28 |
✓ 142.7K
|
superGovernor_proposeGlobalHooksRoot(testRoot); |
|
| 29 |
} |
||
| 30 | |||
| 31 |
function superGovernor_proposeUpkeepPaymentsChange_clamped() public {
|
||
| 32 |
✓ 149.3K
|
superGovernor_proposeUpkeepPaymentsChange(true); |
|
| 33 |
} |
||
| 34 | |||
| 35 |
function superGovernor_proposeFee_clamped( |
||
| 36 |
uint256 feeTypeAsUint, |
||
| 37 |
uint256 value |
||
| 38 |
) public {
|
||
| 39 |
✓ 102.9K
|
feeTypeAsUint %= 3; |
|
| 40 |
✓ 102.9K
|
superGovernor_proposeFee(FeeType(feeTypeAsUint), value); |
|
| 41 |
} |
||
| 42 | |||
| 43 |
function superGovernor_executeFeeUpdate_clamped( |
||
| 44 |
uint256 feeTypeAsUint |
||
| 45 |
) public {
|
||
| 46 |
✓ 134.7K
|
feeTypeAsUint %= 3; |
|
| 47 |
✓ 134.7K
|
superGovernor_executeFeeUpdate(FeeType(feeTypeAsUint)); |
|
| 48 |
} |
||
| 49 | |||
| 50 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 51 | |||
| 52 |
function superGovernor_proposeFee( |
||
| 53 |
FeeType feeType, |
||
| 54 |
uint256 value |
||
| 55 |
) public asAdmin {
|
||
| 56 |
✓ 119.2K
|
superGovernor.proposeFee(feeType, value); |
|
| 57 |
} |
||
| 58 | |||
| 59 |
function superGovernor_executeFeeUpdate(FeeType feeType) public asAdmin {
|
||
| 60 |
✓ 161.3K
|
superGovernor.executeFeeUpdate(feeType); |
|
| 61 |
} |
||
| 62 | |||
| 63 |
function superGovernor_proposeMinStaleness( |
||
| 64 |
uint256 newMinStaleness |
||
| 65 |
) public asAdmin {
|
||
| 66 |
✓ 118.6K
|
superGovernor.proposeMinStaleness(newMinStaleness); |
|
| 67 |
} |
||
| 68 | |||
| 69 |
function superGovernor_executeMinStalenesChange() public asAdmin {
|
||
| 70 |
✓ 126.5K
|
superGovernor.executeMinStalenesChange(); |
|
| 71 |
} |
||
| 72 | |||
| 73 |
function superGovernor_executeRemoveIncentiveTokens() public asAdmin {
|
||
| 74 |
✓ 148.3K
|
superGovernor.executeRemoveIncentiveTokens(); |
|
| 75 |
} |
||
| 76 | |||
| 77 |
function superGovernor_executeUpkeepClaim(uint256 amount) public asAdmin {
|
||
| 78 |
✓ 132.7K
|
superGovernor.executeUpkeepClaim(amount); |
|
| 79 |
} |
||
| 80 | |||
| 81 |
function superGovernor_proposeUpkeepPaymentsChange( |
||
| 82 |
bool enabled |
||
| 83 |
) public asAdmin {
|
||
| 84 |
✓ 274.4K
|
superGovernor.proposeUpkeepPaymentsChange(enabled); |
|
| 85 |
} |
||
| 86 | |||
| 87 |
function superGovernor_executeUpkeepPaymentsChange() public asAdmin {
|
||
| 88 |
✓ 141.4K
|
superGovernor.executeUpkeepPaymentsChange(); |
|
| 89 |
} |
||
| 90 | |||
| 91 |
function superGovernor_proposeAddIncentiveTokens( |
||
| 92 |
address[] memory tokens |
||
| 93 |
) public asAdmin {
|
||
| 94 |
✓ 122.9K
|
superGovernor.proposeAddIncentiveTokens(tokens); |
|
| 95 |
} |
||
| 96 | |||
| 97 |
function superGovernor_executeAddIncentiveTokens() public asAdmin {
|
||
| 98 |
✓ 142.2K
|
superGovernor.executeAddIncentiveTokens(); |
|
| 99 |
} |
||
| 100 | |||
| 101 |
function superGovernor_proposeGlobalHooksRoot( |
||
| 102 |
bytes32 newRoot |
||
| 103 |
) public asAdmin {
|
||
| 104 |
✓ 259.5K
|
superGovernor.proposeGlobalHooksRoot(newRoot); |
|
| 105 |
} |
||
| 106 |
} |
||
| 107 |
100.0%
test/recon/targets/SuperVaultAggregatorTargets.sol
Lines covered: 37 / 37 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 6 |
import {Panic} from "@recon/Panic.sol";
|
||
| 7 | |||
| 8 |
import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
|
||
| 9 |
import "src/SuperVault/SuperVaultAggregator.sol"; |
||
| 10 | |||
| 11 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 12 |
import {Properties} from "../Properties.sol";
|
||
| 13 | |||
| 14 |
abstract contract SuperVaultAggregatorTargets is |
||
| 15 |
BaseTargetFunctions, |
||
| 16 |
Properties |
||
| 17 |
{
|
||
| 18 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 19 |
function superVaultAggregator_proposeChangePrimaryManager_clamped() public {
|
||
| 20 |
superVaultAggregator_proposeChangePrimaryManager( |
||
| 21 |
✓ 144.4K
|
address(superVaultStrategy), |
|
| 22 |
✓ 144.4K
|
_getActor() |
|
| 23 |
); |
||
| 24 |
} |
||
| 25 | |||
| 26 |
function superVaultAggregator_executeChangePrimaryManager_clamped() public {
|
||
| 27 |
✓ 153.9K
|
superVaultAggregator_executeChangePrimaryManager( |
|
| 28 |
✓ 153.9K
|
address(superVaultStrategy) |
|
| 29 |
); |
||
| 30 |
} |
||
| 31 | |||
| 32 |
function superVaultAggregator_createVault_clamped( |
||
| 33 |
uint256 minUpdateInterval, |
||
| 34 |
uint256 maxStaleness, |
||
| 35 |
uint256 performanceFeeBps, |
||
| 36 |
uint256 managementFeeBps |
||
| 37 |
) public {
|
||
| 38 |
// Clamp values to reasonable ranges |
||
| 39 |
✓ 112.0K
|
minUpdateInterval = minUpdateInterval % 3601; // Max 1 hour |
|
| 40 |
✓ 112.0K
|
maxStaleness = (maxStaleness % 86400) + 301; // Between 5 minutes and 1 day |
|
| 41 |
✓ 112.0K
|
performanceFeeBps = performanceFeeBps % 9001; // Max 90% |
|
| 42 |
✓ 112.0K
|
managementFeeBps = managementFeeBps % 5001; // Max 50% |
|
| 43 | |||
| 44 |
// Create secondary managers array |
||
| 45 |
✓ 112.0K
|
address[] memory secondaryManagers = new address[](1); |
|
| 46 | |||
| 47 |
✓ 112.0K
|
ISuperVaultAggregator.VaultCreationParams |
|
| 48 |
memory params = ISuperVaultAggregator.VaultCreationParams({
|
||
| 49 |
✓ 112.0K
|
asset: _getAsset(), |
|
| 50 |
name: "SuperVault", |
||
| 51 |
symbol: "SV", |
||
| 52 |
✓ 112.0K
|
mainManager: address(this), |
|
| 53 |
✓ 112.0K
|
secondaryManagers: secondaryManagers, |
|
| 54 |
✓ 112.0K
|
minUpdateInterval: minUpdateInterval, |
|
| 55 |
✓ 112.0K
|
maxStaleness: maxStaleness, |
|
| 56 |
feeConfig: ISuperVaultStrategy.FeeConfig({
|
||
| 57 |
✓ 112.0K
|
performanceFeeBps: performanceFeeBps, |
|
| 58 |
✓ 112.0K
|
managementFeeBps: managementFeeBps, |
|
| 59 |
✓ 112.0K
|
recipient: address(this) |
|
| 60 |
}) |
||
| 61 |
}); |
||
| 62 | |||
| 63 |
✓ 112.0K
|
superVaultAggregator_createVault(params); |
|
| 64 |
} |
||
| 65 | |||
| 66 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 67 | |||
| 68 |
function superVaultAggregator_addAuthorizedCaller( |
||
| 69 |
address strategy, |
||
| 70 |
address caller |
||
| 71 |
) public asAdmin {
|
||
| 72 |
✓ 129.3K
|
superVaultAggregator.addAuthorizedCaller(strategy, caller); |
|
| 73 |
} |
||
| 74 | |||
| 75 |
function superVaultAggregator_addSecondaryManager( |
||
| 76 |
address strategy, |
||
| 77 |
address manager |
||
| 78 |
) public asActor {
|
||
| 79 |
✓ 127.4K
|
superVaultAggregator.addSecondaryManager(strategy, manager); |
|
| 80 |
} |
||
| 81 | |||
| 82 |
/// @dev removed because only callable by oracle |
||
| 83 |
// function superVaultAggregator_batchForwardPPS( |
||
| 84 |
// ISuperVaultAggregator.BatchForwardPPSArgs memory args |
||
| 85 |
// ) public asActor {
|
||
| 86 |
// superVaultAggregator.batchForwardPPS(args); |
||
| 87 |
// } |
||
| 88 | |||
| 89 |
/// @dev irrelevant for testing because we're bypassing hook validation |
||
| 90 |
// function superVaultAggregator_changeGlobalLeavesStatus( |
||
| 91 |
// bytes32[] memory leaves, |
||
| 92 |
// bool[] memory statuses, |
||
| 93 |
// address strategy |
||
| 94 |
// ) public asActor {
|
||
| 95 |
// superVaultAggregator.changeGlobalLeavesStatus( |
||
| 96 |
// leaves, |
||
| 97 |
// statuses, |
||
| 98 |
// strategy |
||
| 99 |
// ); |
||
| 100 |
// } |
||
| 101 | |||
| 102 |
function superVaultAggregator_claimUpkeep(uint256 amount) public asActor {
|
||
| 103 |
✓ 142.9K
|
superVaultAggregator.claimUpkeep(amount); |
|
| 104 |
} |
||
| 105 | |||
| 106 |
function superVaultAggregator_createVault( |
||
| 107 |
ISuperVaultAggregator.VaultCreationParams memory params |
||
| 108 |
) public asActor {
|
||
| 109 |
( |
||
| 110 |
✓ 232.6K
|
address _superVault, |
|
| 111 |
address _strategy, |
||
| 112 |
address _escrow |
||
| 113 |
✓ 232.6K
|
) = superVaultAggregator.createVault(params); |
|
| 114 | |||
| 115 |
✓ 109.4K
|
superVault = SuperVault(_superVault); |
|
| 116 |
✓ 109.4K
|
superVaultStrategy = SuperVaultStrategy(payable(_strategy)); |
|
| 117 |
✓ 109.4K
|
superVaultEscrow = SuperVaultEscrow(_escrow); |
|
| 118 | |||
| 119 |
✓ 109.4K
|
hasDeployedNewVault = true; |
|
| 120 |
} |
||
| 121 | |||
| 122 |
function superVaultAggregator_depositStake( |
||
| 123 |
address manager, |
||
| 124 |
uint256 amount |
||
| 125 |
) public asActor {
|
||
| 126 |
✓ 147.8K
|
superVaultAggregator.depositStake(manager, amount); |
|
| 127 |
} |
||
| 128 | |||
| 129 |
function superVaultAggregator_depositUpkeep(uint256 amount) public asActor {
|
||
| 130 |
✓ 121.9K
|
superVaultAggregator.depositUpkeep(_getActor(), amount); |
|
| 131 |
} |
||
| 132 | |||
| 133 |
function superVaultAggregator_executeChangePrimaryManager( |
||
| 134 |
address strategy |
||
| 135 |
) public asActor {
|
||
| 136 |
✓ 272.1K
|
superVaultAggregator.executeChangePrimaryManager(strategy); |
|
| 137 |
} |
||
| 138 | |||
| 139 |
/// @dev removed because we're bypassing hook validation |
||
| 140 |
// function superVaultAggregator_executeStrategyHooksRootUpdate( |
||
| 141 |
// address strategy |
||
| 142 |
// ) public asActor {
|
||
| 143 |
// superVaultAggregator.executeStrategyHooksRootUpdate(strategy); |
||
| 144 |
// } |
||
| 145 | |||
| 146 |
/// @dev removed because only callable by oracle |
||
| 147 |
// function superVaultAggregator_forwardPPS( |
||
| 148 |
// address updateAuthority, |
||
| 149 |
// ISuperVaultAggregator.ForwardPPSArgs memory args |
||
| 150 |
// ) public asActor {
|
||
| 151 |
// superVaultAggregator.forwardPPS(updateAuthority, args); |
||
| 152 |
// } |
||
| 153 | |||
| 154 |
function superVaultAggregator_proposeChangePrimaryManager( |
||
| 155 |
address strategy, |
||
| 156 |
address newManager |
||
| 157 |
) public asActor {
|
||
| 158 |
✓ 256.4K
|
superVaultAggregator.proposeChangePrimaryManager(strategy, newManager); |
|
| 159 |
} |
||
| 160 | |||
| 161 |
/// @dev removed because we're bypassing hook validation |
||
| 162 |
// function superVaultAggregator_proposeStrategyHooksRoot( |
||
| 163 |
// address strategy, |
||
| 164 |
// bytes32 newRoot |
||
| 165 |
// ) public asActor {
|
||
| 166 |
// superVaultAggregator.proposeStrategyHooksRoot(strategy, newRoot); |
||
| 167 |
// } |
||
| 168 | |||
| 169 |
function superVaultAggregator_removeAuthorizedCaller( |
||
| 170 |
address strategy, |
||
| 171 |
address caller |
||
| 172 |
) public asActor {
|
||
| 173 |
✓ 148.6K
|
superVaultAggregator.removeAuthorizedCaller(strategy, caller); |
|
| 174 |
} |
||
| 175 | |||
| 176 |
function superVaultAggregator_removeSecondaryManager( |
||
| 177 |
address strategy, |
||
| 178 |
address manager |
||
| 179 |
) public asActor {
|
||
| 180 |
✓ 110.7K
|
superVaultAggregator.removeSecondaryManager(strategy, manager); |
|
| 181 |
} |
||
| 182 | |||
| 183 |
function superVaultAggregator_updatePPSVerificationThresholds( |
||
| 184 |
address strategy, |
||
| 185 |
uint256 dispersionThreshold_, |
||
| 186 |
uint256 deviationThreshold_, |
||
| 187 |
uint256 mnThreshold_ |
||
| 188 |
) public asActor {
|
||
| 189 |
✓ 142.0K
|
superVaultAggregator.updatePPSVerificationThresholds( |
|
| 190 |
strategy, |
||
| 191 |
dispersionThreshold_, |
||
| 192 |
deviationThreshold_, |
||
| 193 |
mnThreshold_ |
||
| 194 |
); |
||
| 195 |
} |
||
| 196 | |||
| 197 |
function superVaultAggregator_withdrawStake(uint256 amount) public asActor {
|
||
| 198 |
✓ 160.6K
|
superVaultAggregator.withdrawStake(amount); |
|
| 199 |
} |
||
| 200 | |||
| 201 |
function superVaultAggregator_withdrawUpkeep( |
||
| 202 |
uint256 amount |
||
| 203 |
) public asActor {
|
||
| 204 |
✓ 120.3K
|
superVaultAggregator.withdrawUpkeep(amount); |
|
| 205 |
} |
||
| 206 |
} |
||
| 207 |
100.0%
test/recon/targets/SuperVaultEscrowTargets.sol
Lines covered: 3 / 3 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 |
import {BeforeAfter} from "../BeforeAfter.sol";
|
||
| 6 |
import {Properties} from "../Properties.sol";
|
||
| 7 |
// Chimera deps |
||
| 8 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 9 | |||
| 10 |
// Helpers |
||
| 11 |
import {Panic} from "@recon/Panic.sol";
|
||
| 12 | |||
| 13 |
import "src/SuperVault/SuperVaultEscrow.sol"; |
||
| 14 | |||
| 15 |
abstract contract SuperVaultEscrowTargets is |
||
| 16 |
BaseTargetFunctions, |
||
| 17 |
Properties |
||
| 18 |
{
|
||
| 19 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 20 | |||
| 21 | |||
| 22 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 23 | |||
| 24 |
function superVaultEscrow_escrowShares(address from, uint256 amount) public asActor {
|
||
| 25 |
✓ 96.2K
|
superVaultEscrow.escrowShares(from, amount); |
|
| 26 |
} |
||
| 27 | |||
| 28 |
function superVaultEscrow_initialize(address vaultAddress, address strategyAddress) public asActor {
|
||
| 29 |
✓ 129.3K
|
superVaultEscrow.initialize(vaultAddress, strategyAddress); |
|
| 30 |
} |
||
| 31 | |||
| 32 |
function superVaultEscrow_returnShares(address to, uint256 amount) public asActor {
|
||
| 33 |
✓ 127.0K
|
superVaultEscrow.returnShares(to, amount); |
|
| 34 |
} |
||
| 35 |
} |
100.0%
test/recon/targets/SuperVaultStrategyTargets.sol
Lines covered: 24 / 24 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 5 | |||
| 6 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 7 |
import {Panic} from "@recon/Panic.sol";
|
||
| 8 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 9 | |||
| 10 |
import "src/SuperVault/SuperVaultStrategy.sol"; |
||
| 11 | |||
| 12 |
import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
|
||
| 13 |
import {BeforeAfter, OpType} from "../BeforeAfter.sol";
|
||
| 14 |
import {Properties} from "../Properties.sol";
|
||
| 15 | |||
| 16 |
abstract contract SuperVaultStrategyTargets is BaseTargetFunctions, Properties {
|
||
| 17 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 18 | |||
| 19 |
/// @dev Clamps the action type to 0 to add a vault as a yield source |
||
| 20 |
function superVaultStrategy_manageYieldSource_clamped( |
||
| 21 |
uint256 sourceType |
||
| 22 |
) public {
|
||
| 23 |
✓ 300.6K
|
YieldSourceType clampedType = YieldSourceType(sourceType % 3); // 0=ERC4626, 1=ERC5115, 2=ERC7540 |
|
| 24 |
✓ 300.6K
|
address yieldSourceOracle = _getYieldSourceOracleForType(clampedType); |
|
| 25 | |||
| 26 |
✓ 300.6K
|
superVaultStrategy_manageYieldSource( |
|
| 27 |
✓ 300.6K
|
_getYieldSource(), |
|
| 28 |
✓ 300.6K
|
yieldSourceOracle, |
|
| 29 |
✓ 300.6K
|
0 |
|
| 30 |
); |
||
| 31 |
} |
||
| 32 | |||
| 33 |
function superVaultStrategy_handleOperations7540_clamped( |
||
| 34 |
uint256 operation, |
||
| 35 |
uint256 amount |
||
| 36 |
) public {
|
||
| 37 |
✓ 134.7K
|
operation %= 7; // clamp by the possible operation types in the enum |
|
| 38 |
✓ 111.6K
|
superVaultStrategy_handleOperations7540( |
|
| 39 |
✓ 134.7K
|
ISuperVaultStrategy.Operation(operation), |
|
| 40 |
_getActor(), |
||
| 41 |
_getActor(), |
||
| 42 |
✓ 111.6K
|
amount |
|
| 43 |
); |
||
| 44 |
} |
||
| 45 | |||
| 46 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 47 | |||
| 48 |
function superVaultStrategy_executeVaultFeeConfigUpdate() public asActor {
|
||
| 49 |
✓ 161.8K
|
superVaultStrategy.executeVaultFeeConfigUpdate(); |
|
| 50 |
} |
||
| 51 | |||
| 52 |
function superVaultStrategy_handleOperations4626Deposit( |
||
| 53 |
address controller, |
||
| 54 |
uint256 assetsGross |
||
| 55 |
) public asActor {
|
||
| 56 |
✓ 158.7K
|
superVaultStrategy.handleOperations4626Deposit(controller, assetsGross); |
|
| 57 |
} |
||
| 58 | |||
| 59 |
function superVaultStrategy_handleOperations4626Mint( |
||
| 60 |
address controller, |
||
| 61 |
uint256 sharesNet, |
||
| 62 |
uint256 assetsGross, |
||
| 63 |
uint256 assetsNet |
||
| 64 |
) public asActor {
|
||
| 65 |
✓ 128.9K
|
superVaultStrategy.handleOperations4626Mint( |
|
| 66 |
controller, |
||
| 67 |
sharesNet, |
||
| 68 |
assetsGross, |
||
| 69 |
assetsNet |
||
| 70 |
); |
||
| 71 |
} |
||
| 72 | |||
| 73 |
function superVaultStrategy_handleOperations7540( |
||
| 74 |
ISuperVaultStrategy.Operation operation, |
||
| 75 |
address controller, |
||
| 76 |
address receiver, |
||
| 77 |
uint256 amount |
||
| 78 |
) public asActor {
|
||
| 79 |
✓ 124.9K
|
superVaultStrategy.handleOperations7540( |
|
| 80 |
✓ 124.9K
|
operation, |
|
| 81 |
✓ 124.9K
|
controller, |
|
| 82 |
✓ 124.9K
|
receiver, |
|
| 83 |
✓ 124.9K
|
amount |
|
| 84 |
); |
||
| 85 |
} |
||
| 86 | |||
| 87 |
function superVaultStrategy_manageEmergencyWithdraw( |
||
| 88 |
uint8 action, |
||
| 89 |
address recipient, |
||
| 90 |
uint256 amount |
||
| 91 |
) public asActor {
|
||
| 92 |
✓ 138.6K
|
superVaultStrategy.manageEmergencyWithdraw(action, recipient, amount); |
|
| 93 |
} |
||
| 94 | |||
| 95 |
function superVaultStrategy_manageYieldSource( |
||
| 96 |
address source, |
||
| 97 |
address oracle, |
||
| 98 |
uint8 actionType |
||
| 99 |
) public asActor {
|
||
| 100 |
✓ 448.0K
|
superVaultStrategy.manageYieldSource(source, oracle, actionType); |
|
| 101 |
} |
||
| 102 | |||
| 103 |
function superVaultStrategy_manageYieldSources( |
||
| 104 |
address[] memory sources, |
||
| 105 |
address[] memory oracles, |
||
| 106 |
uint8[] memory actionTypes |
||
| 107 |
) public asActor {
|
||
| 108 |
✓ 121.5K
|
superVaultStrategy.manageYieldSources(sources, oracles, actionTypes); |
|
| 109 |
} |
||
| 110 | |||
| 111 |
function superVaultStrategy_moveAccumulatorOnTransfer( |
||
| 112 |
address from, |
||
| 113 |
address to, |
||
| 114 |
uint256 shares |
||
| 115 |
) public asActor {
|
||
| 116 |
✓ 124.6K
|
superVaultStrategy.moveAccumulatorOnTransfer(from, to, shares); |
|
| 117 |
} |
||
| 118 | |||
| 119 |
function superVaultStrategy_proposeVaultFeeConfigUpdate( |
||
| 120 |
uint256 performanceFeeBps, |
||
| 121 |
uint256 managementFeeBps, |
||
| 122 |
address recipient |
||
| 123 |
) public asActor {
|
||
| 124 |
✓ 199.7K
|
superVaultStrategy.proposeVaultFeeConfigUpdate( |
|
| 125 |
performanceFeeBps, |
||
| 126 |
managementFeeBps, |
||
| 127 |
recipient |
||
| 128 |
); |
||
| 129 |
} |
||
| 130 | |||
| 131 |
function superVaultStrategy_updateMaxPPSSlippage( |
||
| 132 |
uint256 maxSlippageBps |
||
| 133 |
) public asActor {
|
||
| 134 |
✓ 124.0K
|
superVaultStrategy.updateMaxPPSSlippage(maxSlippageBps); |
|
| 135 |
} |
||
| 136 |
} |
||
| 137 |
100.0%
test/recon/targets/SuperVaultTargets.sol
Lines covered: 103 / 103 (100.0%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// Recon deps |
||
| 5 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 6 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 7 |
import {Panic} from "@recon/Panic.sol";
|
||
| 8 |
import {MockERC20} from "@recon/MockERC20.sol";
|
||
| 9 | |||
| 10 |
import "src/SuperVault/SuperVault.sol"; |
||
| 11 | |||
| 12 |
import {BeforeAfter, OpType} from "test/recon/BeforeAfter.sol";
|
||
| 13 |
import {Properties} from "../Properties.sol";
|
||
| 14 | |||
| 15 |
/// @dev All receivers are inherently clamped to actors to make checking properties easier |
||
| 16 |
abstract contract SuperVaultTargets is BaseTargetFunctions, Properties {
|
||
| 17 |
/// CUSTOM TARGET FUNCTIONS - Add your own target functions here /// |
||
| 18 |
function superVault_requestRedeem_clamped(uint256 shares) public {
|
||
| 19 |
✓ 277.1K
|
shares %= superVault.balanceOf(_getActor()) + 1; |
|
| 20 | |||
| 21 |
✓ 277.1K
|
superVault_requestRedeem(shares); |
|
| 22 |
} |
||
| 23 | |||
| 24 |
function superVault_redeem_clamped(uint256 shares) public {
|
||
| 25 |
✓ 163.3K
|
uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
|
| 26 |
✓ 163.3K
|
uint256 claimableShares = superVault.convertToShares(claimableAssets); |
|
| 27 | |||
| 28 |
✓ 163.3K
|
shares %= claimableShares + 1; |
|
| 29 | |||
| 30 |
✓ 163.3K
|
superVault_redeem(shares); |
|
| 31 |
} |
||
| 32 | |||
| 33 |
function superVault_withdraw_clamped(uint256 assets) public {
|
||
| 34 |
✓ 151.6K
|
uint256 claimableAssets = superVault.maxWithdraw(_getActor()); |
|
| 35 |
✓ 151.6K
|
assets %= claimableAssets + 1; |
|
| 36 | |||
| 37 |
✓ 151.6K
|
superVault_withdraw(assets); |
|
| 38 |
} |
||
| 39 | |||
| 40 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 41 | |||
| 42 |
function superVault_approve(address spender, uint256 value) public asActor {
|
||
| 43 |
✓ 142.1K
|
superVault.approve(spender, value); |
|
| 44 |
} |
||
| 45 | |||
| 46 |
function superVault_burnShares(uint256 amount) public asActor {
|
||
| 47 |
✓ 133.2K
|
superVault.burnShares(amount); |
|
| 48 |
} |
||
| 49 | |||
| 50 |
/// @dev Property: pendingRedeemRequest should be 0 after a user calls cancelRedeem |
||
| 51 |
/// @dev Property: averageRequestPPS should be 0 after a user calls cancelRedeem |
||
| 52 |
/// @dev Property: user shouldn't receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem |
||
| 53 |
function superVault_cancelRedeem_ASSERTION_CANCEL_REDEEM_NO_OVERPAY() |
||
| 54 |
public |
||
| 55 |
✓ 143.6K
|
updateGhostsWithOpType(OpType.CANCEL) |
|
| 56 |
{
|
||
| 57 |
✓ 143.6K
|
uint256 pendingRedeemRequestsBefore = superVault.pendingRedeemRequest( |
|
| 58 |
0, |
||
| 59 |
✓ 143.6K
|
_getActor() |
|
| 60 |
); |
||
| 61 |
✓ 143.6K
|
uint256 pendingRedeemRequestsAsAssets = superVault.convertToAssets( |
|
| 62 |
pendingRedeemRequestsBefore |
||
| 63 |
); |
||
| 64 |
✓ 143.6K
|
uint256 balanceBefore = MockERC20(superVault.asset()).balanceOf( |
|
| 65 |
✓ 143.6K
|
_getActor() |
|
| 66 |
); |
||
| 67 | |||
| 68 |
✓ 143.6K
|
vm.prank(_getActor()); |
|
| 69 |
✓ 143.6K
|
superVault.cancelRedeem(_getActor()); |
|
| 70 | |||
| 71 |
✓ 1.3K
|
uint256 pendingRedeemRequestsAfter = superVault.pendingRedeemRequest( |
|
| 72 |
0, |
||
| 73 |
✓ 1.3K
|
_getActor() |
|
| 74 |
); |
||
| 75 |
✓ 1.3K
|
uint256 averageRequestPPS = superVaultStrategy |
|
| 76 |
✓ 1.3K
|
.getSuperVaultState(_getActor()) |
|
| 77 |
.averageRequestPPS; |
||
| 78 |
✓ 1.3K
|
uint256 balanceAfter = MockERC20(superVault.asset()).balanceOf( |
|
| 79 |
✓ 1.3K
|
_getActor() |
|
| 80 |
); |
||
| 81 | |||
| 82 |
// Checks |
||
| 83 |
✓ 1.3K
|
eq( |
|
| 84 |
✓ 1.3K
|
pendingRedeemRequestsAfter, |
|
| 85 |
✓ 1.3K
|
0, |
|
| 86 |
✓ 1.3K
|
ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
|
| 87 |
); |
||
| 88 |
✓ 1.3K
|
eq( |
|
| 89 |
✓ 1.3K
|
averageRequestPPS, |
|
| 90 |
✓ 1.3K
|
0, |
|
| 91 |
✓ 1.3K
|
ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
|
| 92 |
); |
||
| 93 |
✓ 1.3K
|
lte( |
|
| 94 |
✓ 1.3K
|
balanceAfter - balanceBefore, |
|
| 95 |
✓ 1.3K
|
pendingRedeemRequestsAsAssets, |
|
| 96 |
✓ 1.3K
|
ASSERTION_CANCEL_REDEEM_NO_OVERPAY |
|
| 97 |
); |
||
| 98 |
} |
||
| 99 | |||
| 100 |
/// @dev Property: previewDeposit returns the correct amounts compared to executing a deposit |
||
| 101 |
function superVault_deposit_ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION( |
||
| 102 |
uint256 assets |
||
| 103 |
✓ 131.6K
|
) public updateGhostsWithOpType(OpType.ADD) {
|
|
| 104 |
✓ 131.6K
|
uint256 previewShares = superVault.previewDeposit(assets); |
|
| 105 | |||
| 106 |
✓ 128.8K
|
vm.prank(_getActor()); |
|
| 107 |
✓ 128.8K
|
uint256 shares = superVault.deposit(assets, _getActor()); |
|
| 108 | |||
| 109 |
✓ 15.9K
|
eq( |
|
| 110 |
✓ 15.9K
|
previewShares, |
|
| 111 |
✓ 15.9K
|
shares, |
|
| 112 |
✓ 15.9K
|
ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION |
|
| 113 |
); |
||
| 114 |
} |
||
| 115 | |||
| 116 |
/// @dev Property: previewMint returns the correct amounts compared to executing a mint |
||
| 117 |
function superVault_mint_ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION( |
||
| 118 |
uint256 shares |
||
| 119 |
✓ 313.6K
|
) public updateGhostsWithOpType(OpType.ADD) {
|
|
| 120 |
✓ 313.6K
|
uint256 previewMint = superVault.previewMint(shares); |
|
| 121 | |||
| 122 |
✓ 268.8K
|
vm.prank(_getActor()); |
|
| 123 |
✓ 268.8K
|
uint256 assets = superVault.mint(shares, _getActor()); |
|
| 124 | |||
| 125 |
✓ 178.5K
|
eq( |
|
| 126 |
✓ 178.5K
|
assets, |
|
| 127 |
✓ 178.5K
|
previewMint, |
|
| 128 |
✓ 178.5K
|
ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION |
|
| 129 |
); |
||
| 130 |
} |
||
| 131 | |||
| 132 |
function superVault_invalidateNonce(bytes32 nonce) public asActor {
|
||
| 133 |
✓ 119.1K
|
superVault.invalidateNonce(nonce); |
|
| 134 |
} |
||
| 135 | |||
| 136 |
function superVault_redeem( |
||
| 137 |
uint256 shares |
||
| 138 |
✓ 305.3K
|
) public updateGhostsWithOpType(OpType.REMOVE) asActor {
|
|
| 139 |
✓ 305.3K
|
superVault.redeem(shares, _getActor(), _getActor()); |
|
| 140 |
} |
||
| 141 | |||
| 142 |
function superVault_withdraw( |
||
| 143 |
uint256 assets |
||
| 144 |
✓ 278.5K
|
) public updateGhostsWithOpType(OpType.REMOVE) asActor {
|
|
| 145 |
✓ 278.5K
|
superVault.withdraw(assets, _getActor(), _getActor()); |
|
| 146 |
} |
||
| 147 | |||
| 148 |
function superVault_requestRedeem( |
||
| 149 |
uint256 shares |
||
| 150 |
✓ 427.6K
|
) public updateGhostsWithOpType(OpType.REQUEST) asActor {
|
|
| 151 |
✓ 1.0M
|
superVault.requestRedeem(shares, _getActor(), _getActor()); |
|
| 152 |
} |
||
| 153 | |||
| 154 |
function superVault_setOperator( |
||
| 155 |
uint256 entropy, |
||
| 156 |
bool approved |
||
| 157 |
) public asActor {
|
||
| 158 |
✓ 135.4K
|
address operator = _getRandomActor(entropy); |
|
| 159 |
✓ 135.4K
|
superVault.setOperator(operator, approved); |
|
| 160 |
} |
||
| 161 | |||
| 162 |
/// @dev Propery: _update should never revert |
||
| 163 |
/// @dev Property: Transfers of shares should transfer the exact amount of accumulatorShares to the recipient |
||
| 164 |
/// @dev Property: Transfers of shares should transfer the exact amount of accumulatorCostBasis to the recipient |
||
| 165 |
// NOTE: _update only gets called on transfer of Vault shares |
||
| 166 |
function superVault_transfer_ASSERTION_TRANSFER_SHARES_CONSERVED( |
||
| 167 |
uint256 entropy, |
||
| 168 |
uint256 value |
||
| 169 |
✓ 142.9K
|
) public updateGhostsWithOpType(OpType.TRANSFER) {
|
|
| 170 |
✓ 142.9K
|
address to = _getRandomActor(entropy); |
|
| 171 |
✓ 142.9K
|
ISuperVaultStrategy.SuperVaultState |
|
| 172 |
✓ 142.9K
|
memory stateSenderBefore = superVaultStrategy.getSuperVaultState( |
|
| 173 |
✓ 142.9K
|
_getActor() |
|
| 174 |
); |
||
| 175 |
✓ 142.9K
|
ISuperVaultStrategy.SuperVaultState |
|
| 176 |
✓ 142.9K
|
memory stateRecipientBefore = superVaultStrategy.getSuperVaultState( |
|
| 177 |
to |
||
| 178 |
); |
||
| 179 | |||
| 180 |
✓ 142.9K
|
vm.prank(_getActor()); |
|
| 181 |
✓ 142.9K
|
try superVault.transfer(to, value) {
|
|
| 182 |
✓ 6.2K
|
ISuperVaultStrategy.SuperVaultState |
|
| 183 |
✓ 6.2K
|
memory stateSenderAfter = superVaultStrategy.getSuperVaultState( |
|
| 184 |
✓ 6.2K
|
_getActor() |
|
| 185 |
); |
||
| 186 |
✓ 6.2K
|
ISuperVaultStrategy.SuperVaultState |
|
| 187 |
✓ 6.2K
|
memory stateRecipientAfter = superVaultStrategy |
|
| 188 |
.getSuperVaultState(to); |
||
| 189 | |||
| 190 |
✓ 12.4K
|
eq( |
|
| 191 |
✓ 6.2K
|
stateSenderBefore.accumulatorShares - |
|
| 192 |
✓ 6.2K
|
stateSenderAfter.accumulatorShares, |
|
| 193 |
✓ 6.2K
|
stateRecipientAfter.accumulatorShares - |
|
| 194 |
✓ 6.2K
|
stateRecipientBefore.accumulatorShares, |
|
| 195 |
✓ 12.4K
|
ASSERTION_TRANSFER_SHARES_CONSERVED |
|
| 196 |
); |
||
| 197 |
eq( |
||
| 198 |
✓ 6.2K
|
stateSenderBefore.accumulatorCostBasis - |
|
| 199 |
✓ 6.2K
|
stateSenderAfter.accumulatorCostBasis, |
|
| 200 |
✓ 6.2K
|
stateRecipientAfter.accumulatorCostBasis - |
|
| 201 |
✓ 6.2K
|
stateRecipientBefore.accumulatorCostBasis, |
|
| 202 |
ASSERTION_TRANSFER_SHARES_CONSERVED |
||
| 203 |
); |
||
| 204 |
} catch (bytes memory err) {
|
||
| 205 |
✓ 136.7K
|
bool expectedError; |
|
| 206 |
✓ 136.7K
|
expectedError = checkError( |
|
| 207 |
✓ 136.7K
|
err, |
|
| 208 |
"ERC20InsufficientBalance(address,uint256,uint256)" |
||
| 209 |
); |
||
| 210 |
✓ 136.7K
|
t(expectedError, ASSERTION_TRANSFER_SHARES_CONSERVED); |
|
| 211 |
} |
||
| 212 |
} |
||
| 213 | |||
| 214 |
/// @dev Propery: _update should never revert |
||
| 215 |
// NOTE: _update only gets called on transfer of Vault shares |
||
| 216 |
function superVault_transferFrom_ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM( |
||
| 217 |
uint256 entropyFrom, |
||
| 218 |
uint256 entropyTo, |
||
| 219 |
uint256 value |
||
| 220 |
✓ 141.7K
|
) public updateGhostsWithOpType(OpType.TRANSFER) {
|
|
| 221 |
✓ 141.7K
|
address from = _getRandomActor(entropyFrom); |
|
| 222 |
✓ 141.7K
|
address to = _getRandomActor(entropyTo); |
|
| 223 | |||
| 224 |
✓ 141.7K
|
vm.prank(_getActor()); |
|
| 225 |
✓ 141.7K
|
try superVault.transferFrom(from, to, value) {} catch (
|
|
| 226 |
bytes memory err |
||
| 227 |
) {
|
||
| 228 |
✓ 140.1K
|
bool expectedError; |
|
| 229 |
expectedError = |
||
| 230 |
✓ 140.1K
|
checkError( |
|
| 231 |
✓ 140.1K
|
err, |
|
| 232 |
"ERC20InsufficientBalance(address,uint256,uint256)" |
||
| 233 |
) || |
||
| 234 |
✓ 139.8K
|
checkError( |
|
| 235 |
✓ 139.8K
|
err, |
|
| 236 |
"ERC20InsufficientAllowance(address,uint256,uint256)" |
||
| 237 |
); |
||
| 238 |
✓ 140.1K
|
t( |
|
| 239 |
✓ 140.1K
|
expectedError, |
|
| 240 |
✓ 140.1K
|
ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM |
|
| 241 |
); |
||
| 242 |
} |
||
| 243 |
} |
||
| 244 | |||
| 245 |
/// @dev removed because signature components not fuzzable |
||
| 246 |
// function superVault_authorizeOperator( |
||
| 247 |
// address controller, |
||
| 248 |
// address operator, |
||
| 249 |
// bool approved, |
||
| 250 |
// bytes32 nonce, |
||
| 251 |
// uint256 deadline, |
||
| 252 |
// bytes memory signature |
||
| 253 |
// ) public asActor {
|
||
| 254 |
// superVault.authorizeOperator( |
||
| 255 |
// controller, |
||
| 256 |
// operator, |
||
| 257 |
// approved, |
||
| 258 |
// nonce, |
||
| 259 |
// deadline, |
||
| 260 |
// signature |
||
| 261 |
// ); |
||
| 262 |
// } |
||
| 263 |
} |
||
| 264 |
93.2%
test/recon/targets/YieldSourceTargets.sol
Lines covered: 138 / 148 (93.2%)
| 1 |
// SPDX-License-Identifier: GPL-2.0 |
||
| 2 |
pragma solidity ^0.8.0; |
||
| 3 | |||
| 4 |
// Chimera deps |
||
| 5 |
import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
|
||
| 6 |
import {vm} from "@chimera/Hevm.sol";
|
||
| 7 | |||
| 8 |
// Helpers |
||
| 9 |
import {Panic} from "@recon/Panic.sol";
|
||
| 10 | |||
| 11 |
import {Properties} from "../Properties.sol";
|
||
| 12 |
import {MockERC4626Tester, FunctionType, RevertType} from "../mocks/MockERC4626Tester.sol";
|
||
| 13 |
import {MockERC5115Tester, RevertType as RevertType5115} from "../mocks/MockERC5115Tester.sol";
|
||
| 14 |
import {MockERC7540Tester} from "../mocks/MockERC7540Tester.sol";
|
||
| 15 |
import {YieldSourceType} from "../managers/YieldManager.sol";
|
||
| 16 | |||
| 17 |
/// @dev Target functions for yield source testers which are used as yield sources in SuperVaultStrategy |
||
| 18 |
abstract contract YieldSourceTargets is BaseTargetFunctions, Properties {
|
||
| 19 |
/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE /// |
||
| 20 | |||
| 21 |
/// ERC20 functions (available across all types as they inherit from MockERC20) /// |
||
| 22 |
function yieldSource_approve( |
||
| 23 |
address spender, |
||
| 24 |
uint256 value |
||
| 25 |
) public asActor {
|
||
| 26 |
✓ 152.9K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 27 |
✓ 152.9K
|
address yieldSource = _getYieldSource(); |
|
| 28 | |||
| 29 |
✓ 152.9K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 30 |
✓ 53.3K
|
MockERC4626Tester(yieldSource).approve(spender, value); |
|
| 31 |
✓ 99.6K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 32 |
MockERC5115Tester(yieldSource).approve(spender, value); |
||
| 33 |
✓ 99.6K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 34 |
✓ 99.6K
|
MockERC7540Tester(yieldSource).approve(spender, value); |
|
| 35 |
} |
||
| 36 |
} |
||
| 37 | |||
| 38 |
function yieldSource_setDecimalsOffset( |
||
| 39 |
uint8 targetDecimalsOffset |
||
| 40 |
) public asActor {
|
||
| 41 |
✓ 122.4K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 42 |
✓ 122.4K
|
address yieldSource = _getYieldSource(); |
|
| 43 | |||
| 44 |
✓ 122.4K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 45 |
✓ 39.2K
|
MockERC4626Tester(yieldSource).setDecimalsOffset( |
|
| 46 |
targetDecimalsOffset |
||
| 47 |
); |
||
| 48 |
} |
||
| 49 |
// Note: ERC5115 and ERC7540 don't have setDecimalsOffset function |
||
| 50 |
} |
||
| 51 | |||
| 52 |
function yieldSource_transfer(address to, uint256 value) public asActor {
|
||
| 53 |
✓ 125.4K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 54 |
✓ 125.4K
|
address yieldSource = _getYieldSource(); |
|
| 55 | |||
| 56 |
✓ 125.4K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 57 |
✓ 31.6K
|
MockERC4626Tester(yieldSource).transfer(to, value); |
|
| 58 |
✓ 93.7K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 59 |
MockERC5115Tester(yieldSource).transfer(to, value); |
||
| 60 |
✓ 93.7K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 61 |
✓ 93.7K
|
MockERC7540Tester(yieldSource).transfer(to, value); |
|
| 62 |
} |
||
| 63 |
} |
||
| 64 | |||
| 65 |
function yieldSource_transferFrom( |
||
| 66 |
address from, |
||
| 67 |
address to, |
||
| 68 |
uint256 value |
||
| 69 |
) public asActor {
|
||
| 70 |
✓ 134.2K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 71 |
✓ 134.2K
|
address yieldSource = _getYieldSource(); |
|
| 72 | |||
| 73 |
✓ 134.2K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 74 |
✓ 33.1K
|
MockERC4626Tester(yieldSource).transferFrom(from, to, value); |
|
| 75 |
✓ 101.1K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 76 |
MockERC5115Tester(yieldSource).transferFrom(from, to, value); |
||
| 77 |
✓ 101.1K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 78 |
✓ 101.1K
|
MockERC7540Tester(yieldSource).transferFrom(from, to, value); |
|
| 79 |
} |
||
| 80 |
} |
||
| 81 | |||
| 82 |
/// Core Vault Functions (ERC4626/ERC4626-like) /// |
||
| 83 |
function yieldSource_deposit( |
||
| 84 |
uint256 assets, |
||
| 85 |
address receiver |
||
| 86 |
) public asActor {
|
||
| 87 |
✓ 108.3K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 88 |
✓ 108.3K
|
address yieldSource = _getYieldSource(); |
|
| 89 | |||
| 90 |
✓ 108.3K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 91 |
✓ 31.9K
|
MockERC4626Tester(yieldSource).deposit(assets, receiver); |
|
| 92 |
} |
||
| 93 |
// Note: ERC5115 has different deposit signature, handled separately |
||
| 94 |
// Note: ERC7540 has different deposit signature, handled separately |
||
| 95 |
} |
||
| 96 | |||
| 97 |
function yieldSource_mint(uint256 shares, address receiver) public asActor {
|
||
| 98 |
✓ 291.1K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 99 |
✓ 291.1K
|
address yieldSource = _getYieldSource(); |
|
| 100 | |||
| 101 |
✓ 291.1K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 102 |
✓ 37.2K
|
MockERC4626Tester(yieldSource).mint(shares, receiver); |
|
| 103 |
✓ 253.8K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 104 |
✓ 253.8K
|
MockERC7540Tester(yieldSource).mint(shares, receiver); |
|
| 105 |
} |
||
| 106 |
// Note: ERC5115 doesn't have mint function |
||
| 107 |
} |
||
| 108 | |||
| 109 |
function yieldSource_withdraw( |
||
| 110 |
uint256 assets, |
||
| 111 |
address receiver, |
||
| 112 |
address owner |
||
| 113 |
) public asActor {
|
||
| 114 |
✓ 138.9K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 115 |
✓ 138.9K
|
address yieldSource = _getYieldSource(); |
|
| 116 | |||
| 117 |
✓ 138.9K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 118 |
✓ 32.0K
|
MockERC4626Tester(yieldSource).withdraw(assets, receiver, owner); |
|
| 119 |
✓ 106.8K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 120 |
✓ 106.8K
|
MockERC7540Tester(yieldSource).withdraw(assets, receiver, owner); |
|
| 121 |
} |
||
| 122 |
// Note: ERC5115 doesn't have withdraw function |
||
| 123 |
} |
||
| 124 | |||
| 125 |
function yieldSource_redeem( |
||
| 126 |
uint256 shares, |
||
| 127 |
address receiver, |
||
| 128 |
address owner |
||
| 129 |
) public asActor {
|
||
| 130 |
✓ 109.5K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 131 |
✓ 109.5K
|
address yieldSource = _getYieldSource(); |
|
| 132 | |||
| 133 |
✓ 109.5K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 134 |
✓ 28.0K
|
MockERC4626Tester(yieldSource).redeem(shares, receiver, owner); |
|
| 135 |
✓ 81.5K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 136 |
✓ 81.5K
|
MockERC7540Tester(yieldSource).redeem(shares, receiver, owner); |
|
| 137 |
} |
||
| 138 |
// Note: ERC5115 has different redeem signature, handled separately |
||
| 139 |
} |
||
| 140 | |||
| 141 |
/// ERC5115-specific functions /// |
||
| 142 |
function yieldSource_deposit5115( |
||
| 143 |
address receiver, |
||
| 144 |
address tokenIn, |
||
| 145 |
uint256 amountTokenToDeposit, |
||
| 146 |
uint256 minSharesOut, |
||
| 147 |
bool depositFromInternalBalance |
||
| 148 |
) public asActor {
|
||
| 149 |
✓ 124.1K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 150 |
✓ 124.1K
|
address yieldSource = _getYieldSource(); |
|
| 151 | |||
| 152 |
✓ 124.1K
|
if (currentType == YieldSourceType.ERC5115) {
|
|
| 153 |
MockERC5115Tester(yieldSource).deposit( |
||
| 154 |
receiver, |
||
| 155 |
tokenIn, |
||
| 156 |
amountTokenToDeposit, |
||
| 157 |
minSharesOut, |
||
| 158 |
depositFromInternalBalance |
||
| 159 |
); |
||
| 160 |
} |
||
| 161 |
} |
||
| 162 | |||
| 163 |
function yieldSource_redeem5115( |
||
| 164 |
address receiver, |
||
| 165 |
uint256 amountSharesToRedeem, |
||
| 166 |
address tokenOut, |
||
| 167 |
uint256 minTokenOut, |
||
| 168 |
bool burnFromInternalBalance |
||
| 169 |
) public asActor {
|
||
| 170 |
✓ 143.5K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 171 |
✓ 143.5K
|
address yieldSource = _getYieldSource(); |
|
| 172 | |||
| 173 |
✓ 143.5K
|
if (currentType == YieldSourceType.ERC5115) {
|
|
| 174 |
MockERC5115Tester(yieldSource).redeem( |
||
| 175 |
receiver, |
||
| 176 |
amountSharesToRedeem, |
||
| 177 |
tokenOut, |
||
| 178 |
minTokenOut, |
||
| 179 |
burnFromInternalBalance |
||
| 180 |
); |
||
| 181 |
} |
||
| 182 |
} |
||
| 183 | |||
| 184 |
/// ERC7540-specific functions /// |
||
| 185 |
function yieldSource_setOperator( |
||
| 186 |
address operator, |
||
| 187 |
bool approved |
||
| 188 |
) public asActor {
|
||
| 189 |
✓ 114.8K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 190 |
✓ 114.8K
|
address yieldSource = _getYieldSource(); |
|
| 191 | |||
| 192 |
✓ 114.8K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 193 |
✓ 72.8K
|
MockERC7540Tester(yieldSource).setOperator(operator, approved); |
|
| 194 |
} |
||
| 195 |
} |
||
| 196 | |||
| 197 |
function yieldSource_requestDeposit( |
||
| 198 |
uint256 assets, |
||
| 199 |
address controller, |
||
| 200 |
address owner |
||
| 201 |
) public asActor {
|
||
| 202 |
✓ 133.3K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 203 |
✓ 133.3K
|
address yieldSource = _getYieldSource(); |
|
| 204 | |||
| 205 |
✓ 133.3K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 206 |
✓ 95.6K
|
MockERC7540Tester(yieldSource).requestDeposit( |
|
| 207 |
✓ 95.6K
|
assets, |
|
| 208 |
✓ 95.6K
|
controller, |
|
| 209 |
✓ 95.6K
|
owner |
|
| 210 |
); |
||
| 211 |
} |
||
| 212 |
} |
||
| 213 | |||
| 214 |
function yieldSource_requestRedeem( |
||
| 215 |
uint256 shares, |
||
| 216 |
address controller, |
||
| 217 |
address owner |
||
| 218 |
) public asActor {
|
||
| 219 |
✓ 144.7K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 220 |
✓ 144.7K
|
address yieldSource = _getYieldSource(); |
|
| 221 | |||
| 222 |
✓ 144.7K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 223 |
✓ 111.0K
|
MockERC7540Tester(yieldSource).requestRedeem( |
|
| 224 |
✓ 111.0K
|
shares, |
|
| 225 |
✓ 111.0K
|
controller, |
|
| 226 |
✓ 111.0K
|
owner |
|
| 227 |
); |
||
| 228 |
} |
||
| 229 |
} |
||
| 230 | |||
| 231 |
function yieldSource_cancelDepositRequest( |
||
| 232 |
uint256 requestId, |
||
| 233 |
address controller |
||
| 234 |
) public asActor {
|
||
| 235 |
✓ 143.0K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 236 |
✓ 143.0K
|
address yieldSource = _getYieldSource(); |
|
| 237 | |||
| 238 |
✓ 143.0K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 239 |
✓ 105.2K
|
MockERC7540Tester(yieldSource).cancelDepositRequest( |
|
| 240 |
✓ 105.2K
|
requestId, |
|
| 241 |
✓ 105.2K
|
controller |
|
| 242 |
); |
||
| 243 |
} |
||
| 244 |
} |
||
| 245 | |||
| 246 |
function yieldSource_cancelRedeemRequest( |
||
| 247 |
uint256 requestId, |
||
| 248 |
address controller |
||
| 249 |
) public asActor {
|
||
| 250 |
✓ 145.4K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 251 |
✓ 145.4K
|
address yieldSource = _getYieldSource(); |
|
| 252 | |||
| 253 |
✓ 145.4K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 254 |
✓ 102.9K
|
MockERC7540Tester(yieldSource).cancelRedeemRequest( |
|
| 255 |
✓ 102.9K
|
requestId, |
|
| 256 |
✓ 102.9K
|
controller |
|
| 257 |
); |
||
| 258 |
} |
||
| 259 |
} |
||
| 260 | |||
| 261 |
function yieldSource_claimCancelDepositRequest( |
||
| 262 |
uint256 requestId, |
||
| 263 |
address receiver, |
||
| 264 |
address controller |
||
| 265 |
) public asActor {
|
||
| 266 |
✓ 123.2K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 267 |
✓ 123.2K
|
address yieldSource = _getYieldSource(); |
|
| 268 | |||
| 269 |
✓ 123.2K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 270 |
✓ 89.3K
|
MockERC7540Tester(yieldSource).claimCancelDepositRequest( |
|
| 271 |
✓ 89.3K
|
requestId, |
|
| 272 |
✓ 89.3K
|
receiver, |
|
| 273 |
✓ 89.3K
|
controller |
|
| 274 |
); |
||
| 275 |
} |
||
| 276 |
} |
||
| 277 | |||
| 278 |
function yieldSource_claimCancelRedeemRequest( |
||
| 279 |
uint256 requestId, |
||
| 280 |
address receiver, |
||
| 281 |
address controller |
||
| 282 |
) public asActor {
|
||
| 283 |
✓ 162.4K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 284 |
✓ 162.4K
|
address yieldSource = _getYieldSource(); |
|
| 285 | |||
| 286 |
✓ 162.4K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 287 |
✓ 116.1K
|
MockERC7540Tester(yieldSource).claimCancelRedeemRequest( |
|
| 288 |
✓ 116.1K
|
requestId, |
|
| 289 |
✓ 116.1K
|
receiver, |
|
| 290 |
✓ 116.1K
|
controller |
|
| 291 |
); |
||
| 292 |
} |
||
| 293 |
} |
||
| 294 | |||
| 295 |
function yieldSource_deposit7540( |
||
| 296 |
uint256 assets, |
||
| 297 |
address receiver, |
||
| 298 |
address controller |
||
| 299 |
) public asActor {
|
||
| 300 |
✓ 204.0K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 301 |
✓ 204.0K
|
address yieldSource = _getYieldSource(); |
|
| 302 | |||
| 303 |
✓ 204.0K
|
if (currentType == YieldSourceType.ERC7540) {
|
|
| 304 |
✓ 153.3K
|
MockERC7540Tester(yieldSource).deposit( |
|
| 305 |
✓ 153.3K
|
assets, |
|
| 306 |
✓ 153.3K
|
receiver, |
|
| 307 |
✓ 153.3K
|
controller |
|
| 308 |
); |
||
| 309 |
} |
||
| 310 |
} |
||
| 311 | |||
| 312 |
/// Testing-specific functions (ERC4626 only) /// |
||
| 313 |
function yieldSource_setRevertBehavior4626( |
||
| 314 |
uint8 functionType, |
||
| 315 |
uint8 revertType |
||
| 316 |
) public asActor {
|
||
| 317 |
✓ 115.3K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 318 |
✓ 115.3K
|
address yieldSource = _getYieldSource(); |
|
| 319 | |||
| 320 |
✓ 115.3K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 321 |
✓ 37.0K
|
MockERC4626Tester(yieldSource).setRevertBehavior( |
|
| 322 |
✓ 37.0K
|
FunctionType(functionType), |
|
| 323 |
✓ 5.0K
|
RevertType(revertType) |
|
| 324 |
); |
||
| 325 |
} |
||
| 326 |
} |
||
| 327 | |||
| 328 |
/// Testing-specific functions (ERC5115 only) /// |
||
| 329 |
function yieldSource_setRevertBehavior5115( |
||
| 330 |
uint8 revertType |
||
| 331 |
) public asActor {
|
||
| 332 |
✓ 142.8K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 333 |
✓ 142.8K
|
address yieldSource = _getYieldSource(); |
|
| 334 | |||
| 335 |
✓ 142.8K
|
if (currentType == YieldSourceType.ERC5115) {
|
|
| 336 |
MockERC5115Tester(yieldSource).setRevertBehavior( |
||
| 337 |
RevertType5115(revertType) |
||
| 338 |
); |
||
| 339 |
} |
||
| 340 |
} |
||
| 341 | |||
| 342 |
/// Common yield manipulation functions /// |
||
| 343 | |||
| 344 |
function yieldSource_simulateLoss(uint256 lossAmount) public {
|
||
| 345 |
✓ 137.3K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 346 |
✓ 137.3K
|
address yieldSource = _getYieldSource(); |
|
| 347 | |||
| 348 |
✓ 137.3K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 349 |
✓ 46.6K
|
MockERC4626Tester(yieldSource).simulateLoss(lossAmount); |
|
| 350 |
✓ 90.7K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 351 |
MockERC5115Tester(yieldSource).simulateLoss(lossAmount); |
||
| 352 |
✓ 90.7K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 353 |
✓ 90.7K
|
MockERC7540Tester(yieldSource).simulateLoss(lossAmount); |
|
| 354 |
} |
||
| 355 |
} |
||
| 356 | |||
| 357 |
function yieldSource_simulateGain(uint256 gainAmount) public {
|
||
| 358 |
✓ 128.4K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 359 |
✓ 128.4K
|
address yieldSource = _getYieldSource(); |
|
| 360 | |||
| 361 |
✓ 128.4K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 362 |
✓ 27.2K
|
MockERC4626Tester(yieldSource).simulateGain(gainAmount); |
|
| 363 |
✓ 101.3K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 364 |
MockERC5115Tester(yieldSource).simulateGain(gainAmount); |
||
| 365 |
✓ 101.3K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 366 |
✓ 101.3K
|
MockERC7540Tester(yieldSource).simulateGain(gainAmount); |
|
| 367 |
} |
||
| 368 |
} |
||
| 369 | |||
| 370 |
function yieldSource_setLossOnWithdraw( |
||
| 371 |
uint256 lossOnWithdraw |
||
| 372 |
) public asActor {
|
||
| 373 |
✓ 126.0K
|
YieldSourceType currentType = _getCurrentYieldSourceType(); |
|
| 374 |
✓ 126.0K
|
address yieldSource = _getYieldSource(); |
|
| 375 | |||
| 376 |
✓ 126.0K
|
if (currentType == YieldSourceType.ERC4626) {
|
|
| 377 |
✓ 41.2K
|
MockERC4626Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
|
| 378 |
✓ 84.8K
|
} else if (currentType == YieldSourceType.ERC5115) {
|
|
| 379 |
MockERC5115Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
||
| 380 |
✓ 84.8K
|
} else if (currentType == YieldSourceType.ERC7540) {
|
|
| 381 |
✓ 84.8K
|
MockERC7540Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw); |
|
| 382 |
} |
||
| 383 |
} |
||
| 384 | |||
| 385 |
/// Yield source management functions /// |
||
| 386 |
function yieldSource_switchToERC4626() public {
|
||
| 387 |
✓ 160.7K
|
_switchYieldSource(0); // Switch to first yield source (ERC4626) |
|
| 388 |
} |
||
| 389 | |||
| 390 |
function yieldSource_switchToERC5115() public {
|
||
| 391 |
✓ 121.0K
|
_switchYieldSource(1); // Switch to second yield source (ERC5115) |
|
| 392 |
} |
||
| 393 | |||
| 394 |
function yieldSource_switchToERC7540() public {
|
||
| 395 |
✓ 134.9K
|
_switchYieldSource(2); // Switch to third yield source (ERC7540) |
|
| 396 |
} |
||
| 397 | |||
| 398 |
function yieldSource_switchRandom(uint256 entropy) public {
|
||
| 399 |
// Randomly switch between the three deployed yield sources |
||
| 400 |
✓ 251.3K
|
_switchYieldSource(entropy); |
|
| 401 |
} |
||
| 402 |
} |
||
| 403 |