diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 16e171e4..da04e745 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -55,7 +55,7 @@ jobs: uses: codespell-project/actions-codespell@v2.0 with: check_filenames: true - ignore_words_list: we + ignore_words_list: we,amountIn skip: ./.git,./lib,./certora validate-links: diff --git a/deployment-config/00_Default.json b/deployment-config/00_Default.json index 077ea22a..7bddcccb 100644 --- a/deployment-config/00_Default.json +++ b/deployment-config/00_Default.json @@ -1,5 +1,5 @@ { "initialDefaultAdmin": "0x94544835Cf97c631f101c5f538787fE14E2E04f6", - "protocol": "0x0000000000417626Ef34D62C4DC189b021603f2F", - "ilkAddress": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110" -} + "protocol": "0xE5a5F3A6C88B894710992e1C2626be0DEB99566E", + "ilkAddress": "0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A" +} \ No newline at end of file diff --git a/deployment-config/02_DeployInterestRateModule.json b/deployment-config/02_DeployInterestRateModule.json index 83825312..ce557a35 100644 --- a/deployment-config/02_DeployInterestRateModule.json +++ b/deployment-config/02_DeployInterestRateModule.json @@ -9,5 +9,5 @@ "adjustedAboveKinkSlope": "0", "minimumAboveKinkSlope": "64011036758000000000" }, - "yieldOracleAddress": "0x437CC840e234C2127f54CD59B0B18aF59c586760" + "yieldOracleAddress": "0x2CAe5eD3b35654499EE605cD66A9b14a0d053773" } diff --git a/deployment-config/04_DeployIonPool.json b/deployment-config/04_DeployIonPool.json index 76777f23..57ebb657 100644 --- a/deployment-config/04_DeployIonPool.json +++ b/deployment-config/04_DeployIonPool.json @@ -1,11 +1,10 @@ { - "underlying": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "treasury": "0x0000000000417626Ef34D62C4DC189b021603f2F", + "underlying": "0x4200000000000000000000000000000000000006", + "treasury": "0xE5a5F3A6C88B894710992e1C2626be0DEB99566E", "decimals": "18", - "name": "Ion ezETH WETH Token", - "symbol": "iezETH-WETH", - "whitelist": "0x7E317f99aA313669AaCDd8dB3927ff3aCB562dAD", - "interestRateModule": "0xCcfD0fDEE103B4b4e45B5D8934540070219A6653", - "ionImpl": "0x77ca0d4b78d8b4f3c71e20f8c8771c4cb7abe201", - "salt": "0x413a3e110b6efc009e045a000000000000000000000000000000000000000000" + "name": "Ion weETH WETH Token", + "symbol": "iweETH-WETH", + "whitelist": "0xf98248f0dA9D51d827A3C42d608acF65c77BD76A", + "interestRateModule": "0x7BC91582b10c3ce83be1918daE5136B59FC55e01", + "salt": "0xa53bcb7572e19b03e4aae7000000000000000000000000000000000000000000" } diff --git a/deployment-config/05_DeployInitialReserveAndSpotOracles.json b/deployment-config/05_DeployInitialReserveAndSpotOracles.json index 1a20f5ae..ced04132 100644 --- a/deployment-config/05_DeployInitialReserveAndSpotOracles.json +++ b/deployment-config/05_DeployInitialReserveAndSpotOracles.json @@ -1,5 +1,6 @@ { "maxChange": "30000000000000000000000000", "ltv": "930000000000000000000000000", - "maxTimeFromLastUpdate": "87000" + "maxTimeFromLastUpdate": "87000", + "gracePeriod": "3600" } diff --git a/deployment-config/06_SetupCollateral.json b/deployment-config/06_SetupCollateral.json index ec512619..2e00c3d2 100644 --- a/deployment-config/06_SetupCollateral.json +++ b/deployment-config/06_SetupCollateral.json @@ -1,6 +1,6 @@ { - "ionPool": "0x00000000008a3A77bd91bC738Ed2Efaa262c3763", - "spotOracle": "0x08fEDD981732c2a0EceCd4c70a41eA3AC454A6FD", - "debtCeiling": "0", + "ionPool": "0x00000000000fA8e0FD26b4554d067CF1856De7F5", + "spotOracle": "0x5D83953248cbF1FF723978FbD3490D9a2385A52d", + "debtCeiling": "000000000000000000000000000000000000000000000000", "dust": "4000000000000000000000000000000000000000000000" } diff --git a/deployment-config/07_DeployGemJoin.json b/deployment-config/07_DeployGemJoin.json index 4e20ed02..81a8102e 100644 --- a/deployment-config/07_DeployGemJoin.json +++ b/deployment-config/07_DeployGemJoin.json @@ -1,3 +1,3 @@ { - "ionPool": "0x00000000008a3A77bd91bC738Ed2Efaa262c3763" + "ionPool": "0x00000000000fA8e0FD26b4554d067CF1856De7F5" } diff --git a/deployment-config/08_DeployHandlers.json b/deployment-config/08_DeployHandlers.json index cdeac87c..e060d270 100644 --- a/deployment-config/08_DeployHandlers.json +++ b/deployment-config/08_DeployHandlers.json @@ -1,5 +1,5 @@ { - "ionPool": "0x00000000008a3A77bd91bC738Ed2Efaa262c3763", - "gemJoin": "0xe3692b2E55Eb2494cA73610c3b027F53815CCD39", - "whitelist": "0x7E317f99aA313669AaCDd8dB3927ff3aCB562dAD" + "ionPool": "0x00000000000fA8e0FD26b4554d067CF1856De7F5", + "gemJoin": "0xe21ae2d45dEDF8dEE2D854774a904d33b8700E78", + "whitelist": "0xf98248f0dA9D51d827A3C42d608acF65c77BD76A" } diff --git a/deployment-config/09_DeployLiquidation.json b/deployment-config/09_DeployLiquidation.json index 9bb0b3eb..005845d5 100644 --- a/deployment-config/09_DeployLiquidation.json +++ b/deployment-config/09_DeployLiquidation.json @@ -3,7 +3,7 @@ "liquidationThreshold": "960000000000000000000000000", "maxDiscount": "200000000000000000000000000", "reserveFactor": "10000000000000000000000000", - "ionPool": "0x00000000008a3A77bd91bC738Ed2Efaa262c3763", - "reserveOracle": "0x3239396B740cD6BBABb42196A03f7B77fA7102C9", - "salt": "0xb54f84bfabde3501ca221f000000000000000000000000000000000000000000" + "ionPool": "0x00000000000fA8e0FD26b4554d067CF1856De7F5", + "reserveOracle": "0x39c66dEAA7BA7576ed1498C5A0601454740e386C", + "salt": "0x7dbc99ede0e74803aa149e000000000000000000000000000000000000000000" } diff --git a/deployment-config/10_AdminTransfer.json b/deployment-config/10_AdminTransfer.json index 9ccb7b42..6525b770 100644 --- a/deployment-config/10_AdminTransfer.json +++ b/deployment-config/10_AdminTransfer.json @@ -1,8 +1,8 @@ { - "ionPool": "0x00000000008a3A77bd91bC738Ed2Efaa262c3763", - "yieldOracle": "0x437CC840e234C2127f54CD59B0B18aF59c586760", - "whitelist": "0x7E317f99aA313669AaCDd8dB3927ff3aCB562dAD", - "proxyAdmin": "91192b4d46d77d013bdee650ad38452ed1da0175", - "liquidation": "0x0000000000d8858E1A9B373582A691dB992C23CA", - "gemJoin": "0xe3692b2E55Eb2494cA73610c3b027F53815CCD39" + "ionPool": "0x00000000000fA8e0FD26b4554d067CF1856De7F5", + "yieldOracle": "0x2CAe5eD3b35654499EE605cD66A9b14a0d053773", + "whitelist": "0xf98248f0dA9D51d827A3C42d608acF65c77BD76A", + "proxyAdmin": "0xe559662C42FF6460BE7c7f0C67aBA789Fde53861", + "liquidation": "0x00000000009229776762B5e6b865a06afeB4444c", + "gemJoin": "0xe21ae2d45dEDF8dEE2D854774a904d33b8700E78" } diff --git a/foundry.toml b/foundry.toml index 5a4c56ba..f7be527d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -14,7 +14,7 @@ remappings = ["@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "forge-safe/=lib/forge-safe/src/"] fs_permissions = [{ access = "read-write", path = "./"}] optimizer = true -optimizer_runs = 300 +optimizer_runs = 200 no_match_contract = "Echidna" no_match_path = "script/**/*.sol" gas_reports = ["IonHandler", "IonRegistry", "TransparentUpgradeableProxy", "IonPool", "WstEthHandler", "EthXHandler", "SwEthHandler", "GemJoin", "InterestRate", "Whitelist", "WeEthHandler"] diff --git a/script/deploy-test/03_DeployWhitelist.t.sol b/script/deploy-test/03_DeployWhitelist.t.sol index f77c6bab..449e6c33 100644 --- a/script/deploy-test/03_DeployWhitelist.t.sol +++ b/script/deploy-test/03_DeployWhitelist.t.sol @@ -8,10 +8,10 @@ import { Whitelist } from "../../src/Whitelist.sol"; contract DeployWhitelistTest is DeployTestBase, DeployWhitelistScript { function checkState(Whitelist whitelist) public { assert(address(whitelist).code.length > 0); - // assertEq(whitelist.owner(), initialDefaultAdmin, "initial owner"); + assertEq(whitelist.owner(), initialDefaultAdmin, "initial owner"); assertEq(whitelist.lendersRoot(), lenderRoot, "lendersRoot"); assertEq(whitelist.borrowersRoot(0), borrowerRoots[0], "borrowersRoot(0)"); - assertEq(whitelist.pendingOwner(), protocol, "pendingOwner"); + assertEq(whitelist.pendingOwner(), address(0), "pendingOwner should be zero"); vm.startPrank(initialDefaultAdmin); whitelist.transferOwnership(protocol); diff --git a/script/deploy-test/10_AdminTransfer.t.sol b/script/deploy-test/10_AdminTransfer.t.sol index 3b53aeaf..083ae411 100644 --- a/script/deploy-test/10_AdminTransfer.t.sol +++ b/script/deploy-test/10_AdminTransfer.t.sol @@ -16,7 +16,7 @@ contract DeployAdminTransferTest is DeployTestBase, AdminTransferScript { assertLe(addressSchedule, block.timestamp, "address schedule"); // assertEq(yieldOracle.pendingOwner(), protocol, "yield oracle pending owner"); - // assertEq(whitelist.pendingOwner(), protocol, "whitelist pending owner"); + assertEq(whitelist.pendingOwner(), protocol, "whitelist pending owner"); assertEq(proxyAdmin.pendingOwner(), protocol, "proxy admin pending owner"); assertTrue(ionPool.hasRole(ionPool.GEM_JOIN_ROLE(), address(gemJoin)), "gem join role"); assertTrue(ionPool.hasRole(ionPool.LIQUIDATOR_ROLE(), address(liquidation)), "gem join role"); diff --git a/script/deploy/04_DeployIonPool.s.sol b/script/deploy/04_DeployIonPool.s.sol index 5d49a662..cdcc2dac 100644 --- a/script/deploy/04_DeployIonPool.s.sol +++ b/script/deploy/04_DeployIonPool.s.sol @@ -31,20 +31,21 @@ contract DeployIonPoolScript is DeployScript { bytes32 salt = config.readBytes32(".salt"); function createX() public returns (IonPool ionImpl, IonPool ionPool) { - ionImpl = IonPool(config.readAddress(".ionImpl")); + // ionImpl = IonPool(config.readAddress(".ionImpl")); _validateInterface(IERC20(underlying)); _validateInterface(interestRateModule); _validateInterface(whitelist); // ionImpl = IonPool(); - _validateInterfaceIonPool(ionImpl); - // if (deployCreate2) { - // ionImpl = new IonPool{ salt: DEFAULT_SALT }(); - // } else { - // ionImpl = new IonPool(); - // } + if (deployCreate2) { + ionImpl = new IonPool{ salt: DEFAULT_SALT }(); + } else { + ionImpl = new IonPool(); + } + + _validateInterfaceIonPool(ionImpl); bytes memory initCode = type(TransparentUpgradeableProxy).creationCode; diff --git a/script/deploy/05_DeployInitialReserveAndSpotOracles.s.sol b/script/deploy/05_DeployInitialReserveAndSpotOracles.s.sol index 03d4ef1a..5bae94fe 100644 --- a/script/deploy/05_DeployInitialReserveAndSpotOracles.s.sol +++ b/script/deploy/05_DeployInitialReserveAndSpotOracles.s.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.21; import { DeployScript } from "../Deploy.s.sol"; import { RAY } from "../../src/libraries/math/WadRayMath.sol"; -import { EzEthWethReserveOracle } from "./../../src/oracles/reserve/lrt/EzEthWethReserveOracle.sol"; -import { EzEthWethSpotOracle } from "./../../src/oracles/spot/lrt/EzEthWethSpotOracle.sol"; +import { WeEthWethReserveOracle } from "./../../src/oracles/reserve/lrt/WeEthWethReserveOracle.sol"; +import { WeEthWethSpotOracle } from "./../../src/oracles/spot/lrt/WeEthWethSpotOracle.sol"; import { stdJson as StdJson } from "forge-std/StdJson.sol"; @@ -15,6 +15,7 @@ contract DeployInitialReserveAndSpotOraclesScript is DeployScript { string config = vm.readFile(configPath); uint256 maxChange = config.readUint(".maxChange"); + uint256 gracePeriod = config.readUint(".gracePeriod"); uint256 ltv = config.readUint(".ltv"); function run() public broadcast returns (address reserveOracle, address spotOracle) { @@ -28,13 +29,22 @@ contract DeployInitialReserveAndSpotOraclesScript is DeployScript { uint256 maxTimeFromLastUpdate = config.readUint(".maxTimeFromLastUpdate"); if (deployCreate2) { - reserveOracle = address(new EzEthWethReserveOracle{ salt: DEFAULT_SALT }(0, new address[](3), 0, maxChange)); + reserveOracle = address( + new WeEthWethReserveOracle{ salt: DEFAULT_SALT }( + 0, new address[](3), 0, maxChange, maxTimeFromLastUpdate, gracePeriod + ) + ); spotOracle = address( - new EzEthWethSpotOracle{ salt: DEFAULT_SALT }(ltv, address(reserveOracle), maxTimeFromLastUpdate) + new WeEthWethSpotOracle{ salt: DEFAULT_SALT }( + ltv, address(reserveOracle), maxTimeFromLastUpdate, gracePeriod + ) ); } else { - reserveOracle = address(new EzEthWethReserveOracle(0, new address[](3), 0, maxChange)); - spotOracle = address(new EzEthWethSpotOracle(ltv, address(reserveOracle), maxTimeFromLastUpdate)); + reserveOracle = address( + new WeEthWethReserveOracle(0, new address[](3), 0, maxChange, maxTimeFromLastUpdate, gracePeriod) + ); + spotOracle = + address(new WeEthWethSpotOracle(ltv, address(reserveOracle), maxTimeFromLastUpdate, gracePeriod)); } } } diff --git a/script/deploy/08_DeployHandlers.s.sol b/script/deploy/08_DeployHandlers.s.sol index 5304e753..b915ef70 100644 --- a/script/deploy/08_DeployHandlers.s.sol +++ b/script/deploy/08_DeployHandlers.s.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; -import { MAINNET_WSTETH_WETH_UNISWAP, EZETH_WETH_BALANCER_POOL_ID } from "../../src/Constants.sol"; +import { BASE_WETH, BASE_WEETH_WETH_BALANCER_POOL_ID, BASE_WSTETH_WETH_UNISWAP } from "../../src/Constants.sol"; import { DeployScript } from "../Deploy.s.sol"; import { IonPool } from "../../src/IonPool.sol"; import { GemJoin } from "../../src/join/GemJoin.sol"; import { Whitelist } from "../../src/Whitelist.sol"; import { IonHandlerBase } from "../../src/flash/IonHandlerBase.sol"; -import { EzEthWethHandler } from "./../../src/flash/lrt/EzEthWethHandler.sol"; +import { WeEthWethHandler } from "./../../src/flash/lrt/WeEthWethHandler.sol"; import { stdJson as StdJson } from "forge-std/StdJson.sol"; // NOTE: Different handlers will have different constructor parameters. @@ -28,12 +28,24 @@ contract DeployHandlersScript is DeployScript { _validateInterfaceIonPool(ionPool); if (deployCreate2) { - handler = new EzEthWethHandler{ salt: DEFAULT_SALT }( - ILK_INDEX_ZERO, ionPool, gemJoin, whitelist, MAINNET_WSTETH_WETH_UNISWAP, EZETH_WETH_BALANCER_POOL_ID + handler = new WeEthWethHandler{ salt: DEFAULT_SALT }( + ILK_INDEX_ZERO, + ionPool, + gemJoin, + whitelist, + BASE_WSTETH_WETH_UNISWAP, + BASE_WEETH_WETH_BALANCER_POOL_ID, + BASE_WETH ); } else { - handler = new EzEthWethHandler{ salt: DEFAULT_SALT }( - ILK_INDEX_ZERO, ionPool, gemJoin, whitelist, MAINNET_WSTETH_WETH_UNISWAP, EZETH_WETH_BALANCER_POOL_ID + handler = new WeEthWethHandler{ salt: DEFAULT_SALT }( + ILK_INDEX_ZERO, + ionPool, + gemJoin, + whitelist, + BASE_WSTETH_WETH_UNISWAP, + BASE_WEETH_WETH_BALANCER_POOL_ID, + BASE_WETH ); } } diff --git a/script/deploy/10_AdminTransfer.s.sol b/script/deploy/10_AdminTransfer.s.sol index 105a2e20..791ce8cc 100644 --- a/script/deploy/10_AdminTransfer.s.sol +++ b/script/deploy/10_AdminTransfer.s.sol @@ -49,7 +49,7 @@ contract AdminTransferScript is DeployScript { ionPool.beginDefaultAdminTransfer(protocol); // yieldOracle.transferOwnership(protocol); - // whitelist.transferOwnership(protocol); + whitelist.transferOwnership(protocol); proxyAdmin.transferOwnership(protocol); } } diff --git a/src/Constants.sol b/src/Constants.sol index ff78aba7..bb61368d 100644 --- a/src/Constants.sol +++ b/src/Constants.sol @@ -20,6 +20,7 @@ import { IRenzoOracle, IRestakeManager } from "./interfaces/ProviderInterfaces.sol"; +import { IWETH9 } from "./interfaces/IWETH9.sol"; import { IRedstonePriceFeed } from "./interfaces/IRedstone.sol"; import { IChainlink } from "./interfaces/IChainlink.sol"; import { ICreateX } from "./interfaces/ICreateX.sol"; @@ -27,6 +28,7 @@ import { ICreateX } from "./interfaces/ICreateX.sol"; import { IPMarketV3 } from "pendle-core-v2-public/interfaces/IPMarketV3.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; uint8 constant REDSTONE_DECIMALS = 8; @@ -53,6 +55,8 @@ IEtherFiLiquidityPool constant ETHER_FI_LIQUIDITY_POOL_ADDRESS = IWeEth constant WEETH_ADDRESS = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee); IRedstonePriceFeed constant REDSTONE_WEETH_ETH_PRICE_FEED = IRedstonePriceFeed(0x8751F736E94F6CD167e8C5B97E245680FbD9CC36); +IChainlink constant BASE_WEETH_ETH_PRICE_CHAINLINK = IChainlink(0xFC1415403EbB0c693f9a7844b92aD2Ff24775C65); +IChainlink constant BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK = IChainlink(0x35e9D7001819Ea3B39Da906aE6b06A62cfe2c181); // rsETH IRedstonePriceFeed constant REDSTONE_RSETH_ETH_PRICE_FEED = @@ -97,3 +101,12 @@ IPMarketV3 constant PT_RSWETH_POOL = IPMarketV3(0x1729981345aa5CaCdc19eA9eeffea9 // CreateX ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + +// --- BASE --- + +// EtherFi +bytes32 constant BASE_WEETH_WETH_BALANCER_POOL_ID = 0xab99a3e856deb448ed99713dfce62f937e2d4d74000000000000000000000118; +IUniswapV3Pool constant BASE_WSTETH_WETH_UNISWAP = IUniswapV3Pool(0x20E068D76f9E90b90604500B84c7e19dCB923e7e); +IChainlink constant BASE_SEQUENCER_UPTIME_FEED = IChainlink(0xBCF85224fc0756B9Fa45aA7892530B47e10b6433); +IERC20 constant BASE_WEETH = IERC20(0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A); +IWETH9 constant BASE_WETH = IWETH9(0x4200000000000000000000000000000000000006); diff --git a/src/flash/BalancerFlashloanDirectMintHandler.sol b/src/flash/BalancerFlashloanDirectMintHandler.sol index 9278e7f4..db02ed96 100644 --- a/src/flash/BalancerFlashloanDirectMintHandler.sol +++ b/src/flash/BalancerFlashloanDirectMintHandler.sol @@ -14,7 +14,9 @@ IVault constant VAULT = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); /** * @notice This contract allows for easy creation of leverage positions through - * Balancer flashloans and LST mints through the LST provider. + * Balancer flashloans and LST mints through the LST provider. This contract + * should be used when the base asset of the market is `WETH` and when the asset + * required to mint the collateral is also `WETH`. * * @dev There are a couple things to consider here from a security perspective. The * first one is that the flashloan callback must only be callable from the diff --git a/src/flash/IonHandlerBase.sol b/src/flash/IonHandlerBase.sol index f1edfad4..c0c573a1 100644 --- a/src/flash/IonHandlerBase.sol +++ b/src/flash/IonHandlerBase.sol @@ -6,7 +6,6 @@ import { IWETH9 } from "../interfaces/IWETH9.sol"; import { GemJoin } from "../join/GemJoin.sol"; import { WadRayMath, RAY } from "../libraries/math/WadRayMath.sol"; import { Whitelist } from "../Whitelist.sol"; -import { WETH_ADDRESS } from "../Constants.sol"; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -70,9 +69,8 @@ abstract contract IonHandlerBase { } IERC20 public immutable BASE; - // Will keep WETH for compatibility with other strategies. But this should - // be removed eventually to remove dependence on WETH as a base asset. IWETH9 public immutable WETH; + uint8 public immutable ILK_INDEX; IonPool public immutable POOL; GemJoin public immutable JOIN; @@ -86,14 +84,14 @@ abstract contract IonHandlerBase { * @param _gemJoin the `GemJoin` associated with the `ilkIndex` of this * contract. * @param _whitelist the `Whitelist` module address. + * @param _weth The WETH address of the chain. */ - constructor(uint8 _ilkIndex, IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist) { + constructor(uint8 _ilkIndex, IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, IWETH9 _weth) { POOL = _ionPool; ILK_INDEX = _ilkIndex; BASE = IERC20(_ionPool.underlying()); - IWETH9 _weth = WETH_ADDRESS; WETH = _weth; address ilkAddress = POOL.getIlkAddress(_ilkIndex); @@ -262,6 +260,6 @@ abstract contract IonHandlerBase { * @dev To allow unwrapping of WETH into ETH. */ receive() external payable { - if (msg.sender != address(WETH_ADDRESS)) revert CannotSendEthToContract(); + if (msg.sender != address(WETH)) revert CannotSendEthToContract(); } } diff --git a/src/flash/PtHandler.sol b/src/flash/PtHandler.sol index 63794d8a..a8bcdb9f 100644 --- a/src/flash/PtHandler.sol +++ b/src/flash/PtHandler.sol @@ -5,6 +5,7 @@ import { IonPool } from "../IonPool.sol"; import { GemJoin } from "../join/GemJoin.sol"; import { Whitelist } from "../Whitelist.sol"; import { IonHandlerBase } from "./IonHandlerBase.sol"; +import { IWETH9 } from "./../interfaces/IWETH9.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -47,9 +48,10 @@ contract PtHandler is IonHandlerBase, IPMarketSwapCallback { IonPool pool, GemJoin join, Whitelist whitelist, - IPMarketV3 _market + IPMarketV3 _market, + IWETH9 _weth ) - IonHandlerBase(0, pool, join, whitelist) + IonHandlerBase(0, pool, join, whitelist, _weth) { if (!pool.hasRole(pool.GEM_JOIN_ROLE(), address(join))) revert InvalidGemJoin(address(join)); diff --git a/src/flash/UniswapFlashloanBalancerSwapHandler.sol b/src/flash/UniswapFlashloanBalancerSwapHandler.sol index dabc3011..1c5ecb36 100644 --- a/src/flash/UniswapFlashloanBalancerSwapHandler.sol +++ b/src/flash/UniswapFlashloanBalancerSwapHandler.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.21; import { IWETH9 } from "../interfaces/IWETH9.sol"; import { IonHandlerBase } from "./IonHandlerBase.sol"; -import { IWETH9 } from "../interfaces/IWETH9.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import { IUniswapV3FlashCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol"; @@ -22,6 +21,9 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s * rates. DEXes also provide an avenue for atomic deleveraging since the LST -> * ETH exchange can be made. * + * This contract is used when the Balancer has a collateral asset <> base asset + * pool, and the Uniswap has a base asset flashloan. + * * NOTE: Uniswap flashloans do charge a small fee. * * @dev Some tokens only have liquidity on Balancer. Due to the reentrancy lock @@ -53,16 +55,17 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback * swaps. */ constructor(IUniswapV3Pool _flashloanPool, bytes32 _balancerPoolId) { - address weth = address(WETH); - IERC20(weth).approve(address(VAULT), type(uint256).max); + BASE.approve(address(VAULT), type(uint256).max); IERC20(address(LST_TOKEN)).approve(address(VAULT), type(uint256).max); FLASHLOAN_POOL = _flashloanPool; address token0 = IUniswapV3Pool(_flashloanPool).token0(); address token1 = IUniswapV3Pool(_flashloanPool).token1(); - bool _wethIsToken0 = token0 == weth; - bool _wethIsToken1 = token1 == weth; + // The naming convention uses `_weth`, but this terminology refers to + // the `BASE` token of the underlying IonPool. + bool _wethIsToken0 = token0 == address(BASE); + bool _wethIsToken1 = token1 == address(BASE); if (!_wethIsToken0 && !_wethIsToken1) revert WethNotInPoolPair(_flashloanPool); @@ -123,7 +126,7 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback // the value from the flashloan. uint256 wethIn = _simulateGivenOutBalancerSwap({ fundManagement: fundManagement, - assetIn: address(WETH), + assetIn: address(BASE), assetOut: address(LST_TOKEN), amountOut: amountToLeverage }); @@ -257,7 +260,7 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback IVault.SingleSwap memory balancerSwap = IVault.SingleSwap({ poolId: bytes32(BALANCER_POOL_ID), kind: IVault.SwapKind.GIVEN_OUT, - assetIn: IAsset(address(WETH)), + assetIn: IAsset(address(BASE)), assetOut: IAsset(address(LST_TOKEN)), amount: amountToLeverage, userData: "" @@ -275,7 +278,7 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback uint256 totalCollateral = flashCallbackData.initialDeposit + amountToLeverage; _depositAndBorrow(user, address(this), totalCollateral, wethToRepay, AmountToBorrow.IS_MIN); - WETH.safeTransfer(msg.sender, wethToRepay); + BASE.safeTransfer(msg.sender, wethToRepay); } else { // When deleveraging uint256 totalRepayment = flashCallbackData.wethFlashloaned + fee; @@ -283,7 +286,7 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback uint256 collateralIn = _simulateGivenOutBalancerSwap({ fundManagement: fundManagement, assetIn: address(LST_TOKEN), - assetOut: address(WETH), + assetOut: address(BASE), amountOut: totalRepayment }); @@ -298,14 +301,14 @@ abstract contract UniswapFlashloanBalancerSwapHandler is IUniswapV3FlashCallback poolId: bytes32(BALANCER_POOL_ID), kind: IVault.SwapKind.GIVEN_OUT, assetIn: IAsset(address(LST_TOKEN)), - assetOut: IAsset(address(WETH)), + assetOut: IAsset(address(BASE)), amount: totalRepayment, userData: "" }); VAULT.swap(balancerSwap, fundManagement, type(uint256).max, block.timestamp + 1); - WETH.safeTransfer(msg.sender, totalRepayment); + BASE.safeTransfer(msg.sender, totalRepayment); } } diff --git a/src/flash/UniswapFlashswapHandler.sol b/src/flash/UniswapFlashswapHandler.sol index 462568e8..e37dac89 100644 --- a/src/flash/UniswapFlashswapHandler.sol +++ b/src/flash/UniswapFlashswapHandler.sol @@ -22,6 +22,10 @@ import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/ca * @dev When using the `UniswapFlashSwapHandler`, the `IUniswapV3Pool pool` fed to the * constructor should be the WETH/[LST] pool. * + * This flow can be used in case when the UniswapV3 Pool has a collateral <> + * base asset pair. However, the current version of this contract always assumes + * that the base asset is `WETH`. + * * Unlike Balancer flashloans, there is no concern here that somebody else could * initiate a flashswap, then direct the callback to be called on this contract. * Uniswap enforces that callback is only called on `msg.sender`. diff --git a/src/flash/lrt/EzEthHandler.sol b/src/flash/lrt/EzEthHandler.sol index 7dc0596a..a9ff3e3f 100644 --- a/src/flash/lrt/EzEthHandler.sol +++ b/src/flash/lrt/EzEthHandler.sol @@ -9,6 +9,8 @@ import { IonHandlerBase } from "../IonHandlerBase.sol"; import { RenzoLibrary } from "./../../libraries/lrt/RenzoLibrary.sol"; import { WETH_ADDRESS } from "../../Constants.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; + import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; /** @@ -30,9 +32,10 @@ contract EzEthHandler is UniswapFlashswapDirectMintHandlerWithDust { IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _wstEthUniswapPool + IUniswapV3Pool _wstEthUniswapPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapDirectMintHandlerWithDust(_wstEthUniswapPool, WETH_ADDRESS) { } diff --git a/src/flash/lrt/EzEthWethHandler.sol b/src/flash/lrt/EzEthWethHandler.sol index 63138e23..821e8025 100644 --- a/src/flash/lrt/EzEthWethHandler.sol +++ b/src/flash/lrt/EzEthWethHandler.sol @@ -5,14 +5,15 @@ import { IonPool } from "../../IonPool.sol"; import { GemJoin } from "../../join/GemJoin.sol"; import { Whitelist } from "../../Whitelist.sol"; import { IonHandlerBase } from "../IonHandlerBase.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { UniswapFlashloanBalancerSwapHandler } from "./../UniswapFlashloanBalancerSwapHandler.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; - /** * @notice Handler for the ezETH collateral in the ezETH/WETH market. * * @custom:security-contact security@molecularlabs.io */ + contract EzEthWethHandler is UniswapFlashloanBalancerSwapHandler { /** * @notice Creates a new `EzEthWethHandler` instance. @@ -29,9 +30,10 @@ contract EzEthWethHandler is UniswapFlashloanBalancerSwapHandler { GemJoin _gemJoin, Whitelist _whitelist, IUniswapV3Pool _wstEthUniswapPool, - bytes32 _balancerPoolId + bytes32 _balancerPoolId, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashloanBalancerSwapHandler(_wstEthUniswapPool, _balancerPoolId) { } } diff --git a/src/flash/lrt/RsEthHandler.sol b/src/flash/lrt/RsEthHandler.sol index 9c556b46..b886d10b 100644 --- a/src/flash/lrt/RsEthHandler.sol +++ b/src/flash/lrt/RsEthHandler.sol @@ -10,6 +10,8 @@ import { IonHandlerBase } from "../IonHandlerBase.sol"; import { RSETH, WETH_ADDRESS } from "../../Constants.sol"; import { KelpDaoLibrary } from "../../libraries/lrt/KelpDaoLibrary.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; + import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; /** @@ -33,9 +35,10 @@ contract RsEthHandler is UniswapFlashswapDirectMintHandler { IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _wstEthUniswapPool + IUniswapV3Pool _wstEthUniswapPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapDirectMintHandler(_wstEthUniswapPool, WETH_ADDRESS) { } diff --git a/src/flash/lrt/RswEthHandler.sol b/src/flash/lrt/RswEthHandler.sol index 802fe0e0..48f5ac42 100644 --- a/src/flash/lrt/RswEthHandler.sol +++ b/src/flash/lrt/RswEthHandler.sol @@ -9,6 +9,7 @@ import { UniswapFlashswapDirectMintHandler } from "../UniswapFlashswapDirectMint import { IonHandlerBase } from "../IonHandlerBase.sol"; import { RSWETH, WETH_ADDRESS } from "../../Constants.sol"; import { RestakedSwellLibrary } from "../../libraries/lrt/RestakedSwellLibrary.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -33,9 +34,10 @@ contract RswEthHandler is UniswapFlashswapDirectMintHandler { IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _wstEthUniswapPool + IUniswapV3Pool _wstEthUniswapPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapDirectMintHandler(_wstEthUniswapPool, WETH_ADDRESS) { } diff --git a/src/flash/lrt/WeEthHandler.sol b/src/flash/lrt/WeEthHandler.sol index 5972c6f4..4fd4314d 100644 --- a/src/flash/lrt/WeEthHandler.sol +++ b/src/flash/lrt/WeEthHandler.sol @@ -9,6 +9,7 @@ import { UniswapFlashswapDirectMintHandler } from "../UniswapFlashswapDirectMint import { IonHandlerBase } from "../IonHandlerBase.sol"; import { EtherFiLibrary } from "../../libraries/lrt/EtherFiLibrary.sol"; import { WEETH_ADDRESS, WETH_ADDRESS, EETH_ADDRESS } from "../../Constants.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -33,9 +34,10 @@ contract WeEthHandler is UniswapFlashswapDirectMintHandler { IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _wstEthUniswapPool + IUniswapV3Pool _wstEthUniswapPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapDirectMintHandler(_wstEthUniswapPool, WETH_ADDRESS) { EETH_ADDRESS.approve(address(WEETH_ADDRESS), type(uint256).max); diff --git a/src/flash/lrt/WeEthWethHandler.sol b/src/flash/lrt/WeEthWethHandler.sol new file mode 100644 index 00000000..1156e4a1 --- /dev/null +++ b/src/flash/lrt/WeEthWethHandler.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import { IonPool } from "../../IonPool.sol"; +import { GemJoin } from "../../join/GemJoin.sol"; +import { Whitelist } from "../../Whitelist.sol"; +import { IonHandlerBase } from "../IonHandlerBase.sol"; +import { UniswapFlashloanBalancerSwapHandler } from "./../UniswapFlashloanBalancerSwapHandler.sol"; +import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; + +/** + * @notice Handler for the weETH collateral in the weETH/WETH market. + * + * @custom:security-contact security@molecularlabs.io + */ +contract WeEthWethHandler is UniswapFlashloanBalancerSwapHandler { + /** + * @notice Creates a new `WeEthWethHandler` instance. + * @param _ilkIndex Ilk index of the pool. + * @param _ionPool address. + * @param _gemJoin address. + * @param _whitelist address. + * @param _wstEthUniswapPool address of the wstETH/WETH Uniswap pool + * @param _balancerPoolId Balancer pool ID for the weETH/WETH pool. + */ + constructor( + uint8 _ilkIndex, + IonPool _ionPool, + GemJoin _gemJoin, + Whitelist _whitelist, + IUniswapV3Pool _wstEthUniswapPool, + bytes32 _balancerPoolId, + IWETH9 _weth + ) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) + UniswapFlashloanBalancerSwapHandler(_wstEthUniswapPool, _balancerPoolId) + { } +} diff --git a/src/flash/lst/EthXHandler.sol b/src/flash/lst/EthXHandler.sol index 8218ae43..0964d553 100644 --- a/src/flash/lst/EthXHandler.sol +++ b/src/flash/lst/EthXHandler.sol @@ -10,6 +10,7 @@ import { IonHandlerBase } from "../IonHandlerBase.sol"; import { UniswapFlashloanBalancerSwapHandler } from "../UniswapFlashloanBalancerSwapHandler.sol"; import { BalancerFlashloanDirectMintHandler } from "../BalancerFlashloanDirectMintHandler.sol"; import { UniswapFlashswapHandler } from "../UniswapFlashswapHandler.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -47,10 +48,11 @@ contract EthXHandler is Whitelist _whitelist, IUniswapV3Pool _wstEthUniswapPool, IUniswapV3Pool _ethXUniswapPool, - bytes32 _balancerPoolId + bytes32 _balancerPoolId, + IWETH9 _weth ) UniswapFlashloanBalancerSwapHandler(_wstEthUniswapPool, _balancerPoolId) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapHandler(_ethXUniswapPool, false) { STADER_DEPOSIT = _staderDeposit; diff --git a/src/flash/lst/SwEthHandler.sol b/src/flash/lst/SwEthHandler.sol index 7fab6a37..dd63e8e5 100644 --- a/src/flash/lst/SwEthHandler.sol +++ b/src/flash/lst/SwEthHandler.sol @@ -10,6 +10,7 @@ import { ISwEth } from "../../interfaces/ProviderInterfaces.sol"; import { SwellLibrary } from "../../libraries/lst/SwellLibrary.sol"; import { WadRayMath } from "../../libraries/math/WadRayMath.sol"; import { Whitelist } from "../../Whitelist.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -35,9 +36,10 @@ contract SwEthHandler is UniswapFlashswapHandler, BalancerFlashloanDirectMintHan IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _swEthPool + IUniswapV3Pool _swEthPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapHandler(_swEthPool, true) { } diff --git a/src/flash/lst/WstEthHandler.sol b/src/flash/lst/WstEthHandler.sol index 24e7db7d..1a4155db 100644 --- a/src/flash/lst/WstEthHandler.sol +++ b/src/flash/lst/WstEthHandler.sol @@ -9,6 +9,7 @@ import { BalancerFlashloanDirectMintHandler } from "../BalancerFlashloanDirectMi import { IWstEth } from "../../interfaces/ProviderInterfaces.sol"; import { LidoLibrary } from "../../libraries/lst/LidoLibrary.sol"; import { Whitelist } from "../../Whitelist.sol"; +import { IWETH9 } from "./../../interfaces/IWETH9.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -36,9 +37,10 @@ contract WstEthHandler is UniswapFlashswapHandler, BalancerFlashloanDirectMintHa IonPool _ionPool, GemJoin _gemJoin, Whitelist _whitelist, - IUniswapV3Pool _wstEthUniswapPool + IUniswapV3Pool _wstEthUniswapPool, + IWETH9 _weth ) - IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist) + IonHandlerBase(_ilkIndex, _ionPool, _gemJoin, _whitelist, _weth) UniswapFlashswapHandler(_wstEthUniswapPool, false) { // NOTE: approves wstETH contract infinite approval to move this contract's stEth diff --git a/src/oracles/reserve/lrt/WeEthWethReserveOracle.sol b/src/oracles/reserve/lrt/WeEthWethReserveOracle.sol new file mode 100644 index 00000000..8d6df5c3 --- /dev/null +++ b/src/oracles/reserve/lrt/WeEthWethReserveOracle.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { ReserveOracle } from "../ReserveOracle.sol"; +import { BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK, BASE_SEQUENCER_UPTIME_FEED } from "../../../Constants.sol"; +import { WadRayMath } from "../../../libraries/math/WadRayMath.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +/** + * @notice Reserve oracle for weETH. + * + * @custom:security-contact security@molecularlabs.io + */ +contract WeEthWethReserveOracle is ReserveOracle { + using WadRayMath for uint256; + using SafeCast for int256; + + error SequencerDown(); + error GracePeriodNotOver(); + error MaxTimeFromLastUpdateExceeded(uint256, uint256); + + uint256 public immutable MAX_TIME_FROM_LAST_UPDATE; // seconds + uint256 public immutable GRACE_PERIOD; + + /** + * @notice Creates a new `WeEthWethReserveOracle` instance. Provides + * the amount of WETH equal to one weETH. + * ETH / eETH is 1 since eETH is rebasing. Depeg here would reflect in eETH / wETH + * exchange rate. + * @dev The value of weETH denominated in WETH by the provider. + * @param _ilkIndex of weETH. + * @param _feeds List of alternative data sources for the weETH exchange rate. + * @param _quorum The amount of alternative data sources to aggregate. + * @param _maxChange Maximum percent change between exchange rate updates. [RAY] + */ + constructor( + uint8 _ilkIndex, + address[] memory _feeds, + uint8 _quorum, + uint256 _maxChange, + uint256 _maxTimeFromLastUpdate, + uint256 _gracePeriod + ) + ReserveOracle(_ilkIndex, _feeds, _quorum, _maxChange) + { + MAX_TIME_FROM_LAST_UPDATE = _maxTimeFromLastUpdate; + GRACE_PERIOD = _gracePeriod; + _initializeExchangeRate(); + } + + /** + * @notice Returns the exchange rate between WETH and weETH. + * @return Exchange rate between WETH and weETH. + */ + function _getProtocolExchangeRate() internal view override returns (uint256) { + ( + /*uint80 roundID*/ + , + int256 answer, + uint256 startedAt, + /*uint256 updatedAt*/ + , + /*uint80 answeredInRound*/ + ) = BASE_SEQUENCER_UPTIME_FEED.latestRoundData(); + + if (answer == 1) revert SequencerDown(); + if (block.timestamp - startedAt <= GRACE_PERIOD) revert GracePeriodNotOver(); + + (, int256 ethPerWeEth,, uint256 ethPerWeEthUpdatedAt,) = + BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + + if (block.timestamp - ethPerWeEthUpdatedAt > MAX_TIME_FROM_LAST_UPDATE) { + revert MaxTimeFromLastUpdateExceeded(block.timestamp, ethPerWeEthUpdatedAt); + } else { + return ethPerWeEth.toUint256(); // [WAD] + } + } +} diff --git a/src/oracles/spot/lrt/WeEthWethSpotOracle.sol b/src/oracles/spot/lrt/WeEthWethSpotOracle.sol new file mode 100644 index 00000000..1b155772 --- /dev/null +++ b/src/oracles/spot/lrt/WeEthWethSpotOracle.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.21; + +import { SpotOracle } from "../../../oracles/spot/SpotOracle.sol"; +import { WadRayMath } from "../../../libraries/math/WadRayMath.sol"; +import { BASE_WEETH_ETH_PRICE_CHAINLINK, BASE_SEQUENCER_UPTIME_FEED } from "../../../Constants.sol"; + +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +/** + * @notice The weETH spot oracle denominated in WETH + * + * @custom:security-contact security@molecularlabs.io + */ +contract WeEthWethSpotOracle is SpotOracle { + using WadRayMath for uint256; + using SafeCast for int256; + + error SequencerDown(); + error GracePeriodNotOver(); + + /** + * @notice The maximum delay for the oracle update in seconds before the + * data is considered stale. + */ + uint256 public immutable MAX_TIME_FROM_LAST_UPDATE; // seconds + + /** + * @notice Amount of time to wait after the sequencer restarts. + */ + uint256 public immutable GRACE_PERIOD; + + /** + * @notice Creates a new `WeEthWethSpotOracle` instance. + * @param _ltv The loan to value ratio for the weETH/WETH market. + * @param _reserveOracle The associated reserve oracle. + * @param _maxTimeFromLastUpdate The maximum delay for the oracle update in seconds + */ + constructor( + uint256 _ltv, + address _reserveOracle, + uint256 _maxTimeFromLastUpdate, + uint256 _gracePeriod + ) + SpotOracle(_ltv, _reserveOracle) + { + MAX_TIME_FROM_LAST_UPDATE = _maxTimeFromLastUpdate; + GRACE_PERIOD = _gracePeriod; + } + + /** + * @notice Gets the price of weETH in WETH. + * @dev Redstone oracle returns ETH per weETH with 8 decimals. This + * @return wethPerWeEth price of weETH in WETH. [WAD] + */ + function getPrice() public view override returns (uint256) { + ( + /*uint80 roundID*/ + , + int256 answer, + uint256 startedAt, + /*uint256 updatedAt*/ + , + /*uint80 answeredInRound*/ + ) = BASE_SEQUENCER_UPTIME_FEED.latestRoundData(); + + if (answer == 1) revert SequencerDown(); + if (block.timestamp - startedAt <= GRACE_PERIOD) revert GracePeriodNotOver(); + + ( + /*uint80 roundID*/ + , + int256 ethPerWeEth, + /*uint startedAt*/ + , + uint256 ethPerWeEthUpdatedAt, + /*uint80 answeredInRound*/ + ) = BASE_WEETH_ETH_PRICE_CHAINLINK.latestRoundData(); // [WAD] + + if (block.timestamp - ethPerWeEthUpdatedAt > MAX_TIME_FROM_LAST_UPDATE) { + return 0; // collateral valuation is zero if oracle data is stale + } else { + return ethPerWeEth.toUint256(); // [wad] + } + } +} diff --git a/src/periphery/IonLens.sol b/src/periphery/IonLens.sol index 37bfc3b2..3ae6c5b8 100644 --- a/src/periphery/IonLens.sol +++ b/src/periphery/IonLens.sol @@ -51,7 +51,6 @@ contract IonLens is IIonLens { bytes4 private constant EXTSLOAD_SELECTOR = 0x1e2eaeaf; error SloadFailed(); - error InvalidFieldSlot(); constructor() { IonPoolStorage storage $ = _getIonPoolStorage(); diff --git a/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandler.t.sol b/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandler.t.sol index 0f5ece4c..7e24e92e 100644 --- a/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandler.t.sol +++ b/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandler.t.sol @@ -42,11 +42,18 @@ abstract contract UniswapFlashswapDirectMintHandler_Test is LrtHandler_ForkBase assertLe( ionPool.normalizedDebt(_getIlkIndex(), address(this)).rayMulUp(ionPool.rate(_getIlkIndex())), - maxResultingDebt + roundingError + maxResultingDebt + roundingError, + "resulting debt" + ); + assertEq( + IERC20(address(_getCollaterals()[_getIlkIndex()])).balanceOf(address(_getTypedUFDMHandler())), + 0, + "collateral balanceOf" + ); + assertLe(IERC20(_getUnderlying()).balanceOf(address(_getTypedUFDMHandler())), roundingError, "rounding error"); + assertEq( + ionPool.collateral(_getIlkIndex(), address(this)), resultingAdditionalCollateral, "resulting collateral" ); - assertEq(IERC20(address(_getCollaterals()[_getIlkIndex()])).balanceOf(address(_getTypedUFDMHandler())), 0); - assertLe(IERC20(_getUnderlying()).balanceOf(address(_getTypedUFDMHandler())), roundingError); - assertEq(ionPool.collateral(_getIlkIndex(), address(this)), resultingAdditionalCollateral); } function testFork_RevertWhen_UntrustedCallerCallsFlashswapCallback() external { diff --git a/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandlerWithDust.t.sol b/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandlerWithDust.t.sol index 7dae9fa0..611f42e0 100644 --- a/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandlerWithDust.t.sol +++ b/test/fork/concrete/handlers-base/UniswapFlashswapDirectMintHandlerWithDust.t.sol @@ -46,8 +46,12 @@ abstract contract UniswapFlashswapDirectMintHandlerWithDust_Test is LrtHandler_F maxResultingDebt + roundingError, "max resulting debt upper bound with rounding error" ); - assertEq(IERC20(address(_getCollaterals()[_getIlkIndex()])).balanceOf(address(_getTypedUFDMHandler())), 0); - assertLe(IERC20(_getUnderlying()).balanceOf(address(_getTypedUFDMHandler())), roundingError); + assertEq( + IERC20(address(_getCollaterals()[_getIlkIndex()])).balanceOf(address(_getTypedUFDMHandler())), + 0, + "collateral balanceOf" + ); + assertLe(IERC20(_getUnderlying()).balanceOf(address(_getTypedUFDMHandler())), roundingError, "rounding error"); // TODO: bound this with a max dust bound assertGt( ionPool.collateral(_getIlkIndex(), address(this)), diff --git a/test/fork/concrete/lrt/EzEthHandler.t.sol b/test/fork/concrete/lrt/EzEthHandler.t.sol index d965e26e..8f6ed8df 100644 --- a/test/fork/concrete/lrt/EzEthHandler.t.sol +++ b/test/fork/concrete/lrt/EzEthHandler.t.sol @@ -5,7 +5,7 @@ import { RenzoLibrary } from "./../../../../src/libraries/lrt/RenzoLibrary.sol"; import { LrtHandler_ForkBase } from "../../../helpers/handlers/LrtHandlerForkBase.sol"; import { EzEthHandler } from "./../../../../src/flash/lrt/EzEthHandler.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; -import { RENZO_RESTAKE_MANAGER, EZETH } from "../../../../src/Constants.sol"; +import { RENZO_RESTAKE_MANAGER, EZETH, WETH_ADDRESS } from "../../../../src/Constants.sol"; import { IIonPool } from "./../../../../src/interfaces/IIonPool.sol"; import { IProviderLibraryExposed } from "../../../helpers/IProviderLibraryExposed.sol"; @@ -31,7 +31,9 @@ abstract contract EzEthHandler_ForkBase is LrtHandler_ForkBase { function setUp() public virtual override { super.setUp(); - ezEthHandler = new EzEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL); + ezEthHandler = new EzEthHandler( + ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL, WETH_ADDRESS + ); EZETH.approve(address(ezEthHandler), type(uint256).max); diff --git a/test/fork/concrete/lrt/EzEthWethHandler.t.sol b/test/fork/concrete/lrt/EzEthWethHandler.t.sol index 95f710c6..19c9ab9d 100644 --- a/test/fork/concrete/lrt/EzEthWethHandler.t.sol +++ b/test/fork/concrete/lrt/EzEthWethHandler.t.sol @@ -24,7 +24,8 @@ contract EzEthWethHandler_ForkTest is UniswapFlashloanBalancerSwapHandler_Test { gemJoins[ILK_INDEX], Whitelist(whitelist), MAINNET_WSTETH_WETH_UNISWAP, - EZETH_WETH_BALANCER_POOL_ID + EZETH_WETH_BALANCER_POOL_ID, + WETH_ADDRESS ); EZETH.approve(address(handler), type(uint256).max); diff --git a/test/fork/concrete/lrt/ReserveOracle.t.sol b/test/fork/concrete/lrt/ReserveOracle.t.sol index bb68a269..f7aa41c3 100644 --- a/test/fork/concrete/lrt/ReserveOracle.t.sol +++ b/test/fork/concrete/lrt/ReserveOracle.t.sol @@ -6,6 +6,7 @@ import { ReserveOracle } from "../../../../src/oracles/reserve/ReserveOracle.sol import { RsEthWstEthReserveOracle } from "../../../../src/oracles/reserve/lrt/RsEthWstEthReserveOracle.sol"; import { RswEthWstEthReserveOracle } from "../../../../src/oracles/reserve/lrt/RswEthWstEthReserveOracle.sol"; import { EzEthWethReserveOracle } from "./../../../../src/oracles/reserve/lrt/EzEthWethReserveOracle.sol"; +import { WeEthWethReserveOracle } from "./../../../../src/oracles/reserve/lrt/WeEthWethReserveOracle.sol"; import { WadRayMath } from "../../../../src/libraries/math/WadRayMath.sol"; import { UPDATE_COOLDOWN } from "../../../../src/oracles/reserve/ReserveOracle.sol"; import { @@ -16,7 +17,10 @@ import { ETHX_ADDRESS, RSWETH, EZETH, - RENZO_RESTAKE_MANAGER + RENZO_RESTAKE_MANAGER, + BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK, + ETHER_FI_LIQUIDITY_POOL_ADDRESS, + WEETH_ADDRESS } from "../../../../src/Constants.sol"; import { ReserveOracleSharedSetup } from "../../../helpers/ReserveOracleSharedSetup.sol"; import { StdStorage, stdStorage } from "../../../../lib/forge-safe/lib/forge-std/src/StdStorage.sol"; @@ -27,7 +31,9 @@ import { EzEthWstEthReserveOracle } from "./../../../../src/oracles/reserve/lrt/ import { ReserveOracleSharedSetup } from "../../../helpers/ReserveOracleSharedSetup.sol"; -import { ETHER_FI_LIQUIDITY_POOL_ADDRESS, WEETH_ADDRESS } from "../../../../src/Constants.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import { console2 } from "forge-std/console2.sol"; uint256 constant LTV = 0.9e27; uint256 constant MAX_CHANGE = 0.03e27; @@ -403,3 +409,82 @@ contract EzEthWethReserveOracle_ForkTest is MockEzEth { return totalTVL.wadDivDown(totalSupply); } } + +contract MockChainlink { + using SafeCast for uint256; + + uint256 public exchangeRate; + + function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) { + return (0, exchangeRate.toInt256(), 0, block.timestamp, 0); + } + + function setExchangeRate(uint256 _exchangeRate) external returns (uint256) { + exchangeRate = _exchangeRate; + } +} + +contract WeEthWethReserveOracle_ForkTest is ReserveOracle_ForkTest { + using SafeCast for int256; + + error MaxTimeFromLastUpdateExceeded(uint256, uint256); + + uint256 public immutable MAX_TIME_FROM_LAST_UPDATE = 87_000; // seconds + uint256 public immutable GRACE_PERIOD = 3600; + + function setUp() public override { + super.setUp(); + reserveOracle = new WeEthWethReserveOracle( + ILK_INDEX, emptyFeeds, QUORUM, MAX_CHANGE, MAX_TIME_FROM_LAST_UPDATE, GRACE_PERIOD + ); + } + + function _getForkRpc() internal override returns (string memory) { + return vm.envString("BASE_MAINNET_RPC_URL"); + } + + function _convertToEth(uint256 amt) internal view override returns (uint256) { + return amt; + } + + function _getProtocolExchangeRate() internal view override returns (uint256) { + (, int256 ethPerWeEth,, uint256 ethPerWeEthUpdatedAt,) = + BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + if (block.timestamp - ethPerWeEthUpdatedAt > MAX_TIME_FROM_LAST_UPDATE) { + revert MaxTimeFromLastUpdateExceeded(block.timestamp, ethPerWeEthUpdatedAt); + } else { + return ethPerWeEth.toUint256(); // [WAD] + } + } + + // --- Slashing Scenario --- + function _increaseExchangeRate() internal override returns (uint256 newPrice) { + // Replace the Chainlink contract that returns the exchange rate with a + // new dummy contract that returns a higher exchange rate. + (, int256 prevExchangeRate,,,) = BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + + MockChainlink chainlink = new MockChainlink(); + + vm.etch(address(BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK), address(chainlink).code); + + MockChainlink(address(BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK)).setExchangeRate(1.8e18); + + (, int256 newExchangeRate,,,) = BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + + require(newExchangeRate > prevExchangeRate, "price should increase"); + } + + function _decreaseExchangeRate() internal override returns (uint256 newPrice) { + (, int256 prevExchangeRate,,,) = BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + + MockChainlink chainlink = new MockChainlink(); + + vm.etch(address(BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK), address(chainlink).code); + + MockChainlink(address(BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK)).setExchangeRate(0.5e18); + + (, int256 newExchangeRate,,,) = BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK.latestRoundData(); + + require(newExchangeRate < prevExchangeRate, "price should decrease"); + } +} diff --git a/test/fork/concrete/lrt/RsEthHandler.t.sol b/test/fork/concrete/lrt/RsEthHandler.t.sol index fb73f2e0..3cf8f862 100644 --- a/test/fork/concrete/lrt/RsEthHandler.t.sol +++ b/test/fork/concrete/lrt/RsEthHandler.t.sol @@ -5,7 +5,7 @@ import { IRsEth } from "../../../../src/interfaces/ProviderInterfaces.sol"; import { KelpDaoLibrary } from "../../../../src/libraries/lrt/KelpDaoLibrary.sol"; import { RsEthHandler } from "../../../../src/flash/lrt/RsEthHandler.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; -import { RSETH, RSETH_LRT_DEPOSIT_POOL } from "../../../../src/Constants.sol"; +import { RSETH, RSETH_LRT_DEPOSIT_POOL, WETH_ADDRESS } from "../../../../src/Constants.sol"; import { LrtHandler_ForkBase } from "../../../helpers/handlers/LrtHandlerForkBase.sol"; import { IProviderLibraryExposed } from "../../../helpers/IProviderLibraryExposed.sol"; @@ -38,7 +38,9 @@ abstract contract RsEthHandler_ForkBase is LrtHandler_ForkBase { function setUp() public virtual override { super.setUp(); - rsEthHandler = new RsEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL); + rsEthHandler = new RsEthHandler( + ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL, WETH_ADDRESS + ); RSETH.approve(address(rsEthHandler), type(uint256).max); diff --git a/test/fork/concrete/lrt/RswEthHandler.t.sol b/test/fork/concrete/lrt/RswEthHandler.t.sol index 99359c8e..680a3077 100644 --- a/test/fork/concrete/lrt/RswEthHandler.t.sol +++ b/test/fork/concrete/lrt/RswEthHandler.t.sol @@ -5,7 +5,7 @@ import { IRswEth } from "../../../../src/interfaces/ProviderInterfaces.sol"; import { RestakedSwellLibrary } from "../../../../src/libraries/lrt/RestakedSwellLibrary.sol"; import { RswEthHandler } from "../../../../src/flash/lrt/RswEthHandler.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; -import { RSWETH } from "../../../../src/Constants.sol"; +import { RSWETH, WETH_ADDRESS } from "../../../../src/Constants.sol"; import { LrtHandler_ForkBase } from "../../../helpers/handlers/LrtHandlerForkBase.sol"; import { IIonPool } from "./../../../../src/interfaces/IIonPool.sol"; @@ -39,7 +39,9 @@ abstract contract RswEthHandler_ForkBase is LrtHandler_ForkBase { function setUp() public virtual override { super.setUp(); - rswEthHandler = new RswEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL); + rswEthHandler = new RswEthHandler( + ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL, WETH_ADDRESS + ); RSWETH.approve(address(rswEthHandler), type(uint256).max); diff --git a/test/fork/concrete/lrt/SpotOracle.t.sol b/test/fork/concrete/lrt/SpotOracle.t.sol index 03698022..c28f96f4 100644 --- a/test/fork/concrete/lrt/SpotOracle.t.sol +++ b/test/fork/concrete/lrt/SpotOracle.t.sol @@ -14,6 +14,9 @@ import { EzEthWstEthReserveOracle } from "./../../../../src/oracles/reserve/lrt/ import { EzEthWstEthSpotOracle } from "./../../../../src/oracles/spot/lrt/EzEthWstEthSpotOracle.sol"; import { EzEthWethReserveOracle } from "./../../../../src/oracles/reserve/lrt/EzEthWethReserveOracle.sol"; import { EzEthWethSpotOracle } from "./../../../../src/oracles/spot/lrt/EzEthWethSpotOracle.sol"; +import { WeEthWethReserveOracle } from "./../../../../src/oracles/reserve/lrt/WeEthWethReserveOracle.sol"; +import { WeEthWethSpotOracle } from "./../../../../src/oracles/spot/lrt/WeEthWethSpotOracle.sol"; + import { WadRayMath } from "../../../../src/libraries/math/WadRayMath.sol"; import { Math } from "openzeppelin-contracts/contracts/utils/math/Math.sol"; @@ -137,3 +140,21 @@ contract EzEthWethSpotOracle_ForkTest is SpotOracle_ForkTest { spotOracle = new EzEthWethSpotOracle(MAX_LTV, address(reserveOracle), MAX_TIME_FROM_LAST_UPDATE); } } + +contract WeEthWethSpotOracle_ForkTest is SpotOracle_ForkTest { + uint256 constant GRACE_PERIOD = 3600; + uint256 constant MAX_TIME_FROM_LAST_UPDATE = 87_000; + uint256 constant MAX_LTV = 0.8e27; + + function setUp() public override { + super.setUp(); + reserveOracle = new WeEthWethReserveOracle( + ILK_INDEX, emptyFeeds, QUORUM, DEFAULT_MAX_CHANGE, MAX_TIME_FROM_LAST_UPDATE, GRACE_PERIOD + ); + spotOracle = new WeEthWethSpotOracle(MAX_LTV, address(reserveOracle), MAX_TIME_FROM_LAST_UPDATE, GRACE_PERIOD); + } + + function _getForkRpc() internal override returns (string memory) { + return vm.envString("BASE_MAINNET_RPC_URL"); + } +} diff --git a/test/fork/concrete/lrt/WeEthHandler.t.sol b/test/fork/concrete/lrt/WeEthHandler.t.sol index 66ffe3bd..6f4b4c0c 100644 --- a/test/fork/concrete/lrt/WeEthHandler.t.sol +++ b/test/fork/concrete/lrt/WeEthHandler.t.sol @@ -6,7 +6,7 @@ import { EtherFiLibrary } from "../../../../src/libraries/lrt/EtherFiLibrary.sol import { LrtHandler_ForkBase } from "../../../helpers/handlers/LrtHandlerForkBase.sol"; import { WeEthHandler } from "../../../../src/flash/lrt/WeEthHandler.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; -import { WEETH_ADDRESS, EETH_ADDRESS } from "../../../../src/Constants.sol"; +import { WEETH_ADDRESS, EETH_ADDRESS, WETH_ADDRESS } from "../../../../src/Constants.sol"; import { IProviderLibraryExposed } from "../../../helpers/IProviderLibraryExposed.sol"; import { UniswapFlashswapDirectMintHandler_Test } from "../handlers-base/UniswapFlashswapDirectMintHandler.t.sol"; @@ -38,7 +38,9 @@ abstract contract WeEthHandler_ForkBase is LrtHandler_ForkBase { function setUp() public virtual override { super.setUp(); - weEthHandler = new WeEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL); + weEthHandler = new WeEthHandler( + ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL, WETH_ADDRESS + ); WEETH_ADDRESS.approve(address(weEthHandler), type(uint256).max); diff --git a/test/fork/concrete/lrt/WeEthWethHandler.t.sol b/test/fork/concrete/lrt/WeEthWethHandler.t.sol new file mode 100644 index 00000000..674d4bf3 --- /dev/null +++ b/test/fork/concrete/lrt/WeEthWethHandler.t.sol @@ -0,0 +1,88 @@ +import { WeEthWethHandler } from "./../../../../src/flash/lrt/WeEthWethHandler.sol"; +import { Whitelist } from "./../../../../src/Whitelist.sol"; +import { + BASE_WSTETH_WETH_UNISWAP, + BASE_WEETH_WETH_BALANCER_POOL_ID, + BASE_WETH, + BASE_WEETH, + BASE_WEETH_ETH_PRICE_CHAINLINK, + WETH_ADDRESS +} from "./../../../../src/Constants.sol"; +import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; + +import { UniswapFlashloanBalancerSwapHandler_Test } from + "./../../concrete/handlers-base/UniswapFlashloanBalancerSwapHandler.t.sol"; +import { IProviderLibraryExposed } from "./../../../helpers/IProviderLibraryExposed.sol"; +import { SafeCast } from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; + +using SafeCast for int256; + +contract WeEthWethHandler_ForkTest is UniswapFlashloanBalancerSwapHandler_Test { + WeEthWethHandler handler; + uint8 immutable ILK_INDEX = 0; + + function setUp() public virtual override { + super.setUp(); + handler = new WeEthWethHandler( + ILK_INDEX, + ionPool, + gemJoins[ILK_INDEX], + Whitelist(whitelist), + BASE_WSTETH_WETH_UNISWAP, + BASE_WEETH_WETH_BALANCER_POOL_ID, + WETH_ADDRESS + ); + + BASE_WEETH.approve(address(handler), type(uint256).max); + + // Remove debt ceiling for this test + for (uint8 i = 0; i < lens.ilkCount(iIonPool); i++) { + ionPool.updateIlkDebtCeiling(i, type(uint256).max); + } + + deal(address(BASE_WEETH), address(this), INITIAL_BORROWER_COLLATERAL_BALANCE); + } + + function _getCollaterals() internal view override returns (IERC20[] memory _collaterals) { + _collaterals = new IERC20[](1); + _collaterals[0] = BASE_WEETH; + } + + function _getHandler() internal view override returns (address) { + return address(handler); + } + + function _getIlkIndex() internal pure override returns (uint8) { + return ILK_INDEX; + } + + function _getUnderlying() internal pure override returns (address) { + return address(BASE_WETH); + } + + function _getInitialSpotPrice() internal view override returns (uint256) { + (, int256 ethPerWeEth,,,) = BASE_WEETH_ETH_PRICE_CHAINLINK.latestRoundData(); // [WAD] + return ethPerWeEth.toUint256(); + } + + // NOTE Should be unused + function _getProviderLibrary() internal view override returns (IProviderLibraryExposed) { + return IProviderLibraryExposed(address(0)); + } + + function _getDepositContracts() internal view override returns (address[] memory) { + return new address[](1); + } + + function _getForkRpc() internal view override returns (string memory) { + return vm.envString("BASE_MAINNET_RPC_URL"); + } +} + +contract WeEthWethHandler_WithRateChange_ForkTest is WeEthWethHandler_ForkTest { + function setUp() public virtual override { + super.setUp(); + + ionPool.setRate(ILK_INDEX, 3.5708923502395e27); + } +} diff --git a/test/fork/concrete/lst/SwEthHandler.t.sol b/test/fork/concrete/lst/SwEthHandler.t.sol index 9f8c778c..b17a6b00 100644 --- a/test/fork/concrete/lst/SwEthHandler.t.sol +++ b/test/fork/concrete/lst/SwEthHandler.t.sol @@ -5,6 +5,7 @@ import { ISwEth } from "../../../../src/interfaces/ProviderInterfaces.sol"; import { SwEthHandler } from "../../../../src/flash//lst/SwEthHandler.sol"; import { SwellLibrary } from "../../../../src/libraries/lst/SwellLibrary.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; +import { WETH_ADDRESS } from "../../../../src/Constants.sol"; import { LstHandler_ForkBase } from "../../../helpers/handlers/LstHandlerForkBase.sol"; import { IProviderLibraryExposed } from "../../../helpers/IProviderLibraryExposed.sol"; @@ -39,7 +40,8 @@ abstract contract SwEthHandler_ForkBase is LstHandler_ForkBase { function setUp() public virtual override { super.setUp(); - swEthHandler = new SwEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), SWETH_ETH_POOL); + swEthHandler = + new SwEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), SWETH_ETH_POOL, WETH_ADDRESS); IERC20(address(MAINNET_SWELL)).approve(address(swEthHandler), type(uint256).max); diff --git a/test/fork/concrete/lst/WstEthHandler.t.sol b/test/fork/concrete/lst/WstEthHandler.t.sol index b2bde8d3..e349b7f7 100644 --- a/test/fork/concrete/lst/WstEthHandler.t.sol +++ b/test/fork/concrete/lst/WstEthHandler.t.sol @@ -7,6 +7,7 @@ import { WadRayMath, RAY } from "../../../../src/libraries/math/WadRayMath.sol"; import { LidoLibrary } from "../../../../src/libraries/lst/LidoLibrary.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; import { IonHandlerBase } from "../../../../src/flash/IonHandlerBase.sol"; +import { WETH_ADDRESS } from "../../../../src/Constants.sol"; import { BalancerFlashloanDirectMintHandler_Test } from "../handlers-base/BalancerFlashloanDirectMintHandler.t.sol"; import { UniswapFlashswapHandler_Test } from "../handlers-base/UniswapFlashswapHandler.t.sol"; @@ -43,7 +44,9 @@ abstract contract WstEthHandler_ForkBase is LstHandler_ForkBase { function setUp() public virtual override { super.setUp(); - wstEthHandler = new WstEthHandler(ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL); + wstEthHandler = new WstEthHandler( + ilkIndex, ionPool, gemJoins[ilkIndex], Whitelist(whitelist), WSTETH_WETH_POOL, WETH_ADDRESS + ); IERC20(address(MAINNET_WSTETH)).approve(address(wstEthHandler), type(uint256).max); diff --git a/test/fork/concrete/pendle/PtWeEthHandler.t.sol b/test/fork/concrete/pendle/PtWeEthHandler.t.sol index 8e23bf0f..7e2a444e 100644 --- a/test/fork/concrete/pendle/PtWeEthHandler.t.sol +++ b/test/fork/concrete/pendle/PtWeEthHandler.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; -import { WEETH_ADDRESS, PT_WEETH_POOL } from "../../../../src/Constants.sol"; +import { WEETH_ADDRESS, PT_WEETH_POOL, WETH_ADDRESS } from "../../../../src/Constants.sol"; import { PtHandler } from "../../../../src/flash/PtHandler.sol"; import { Whitelist } from "../../../../src/Whitelist.sol"; import { IWeEth } from "../../../../src/interfaces/ProviderInterfaces.sol"; @@ -28,7 +28,8 @@ abstract contract PtWeEthHandler_ForkBase is PtHandler_ForkBase { pool: ionPool, join: gemJoins[ilkIndex], whitelist: Whitelist(whitelist), - _market: PT_WEETH_POOL + _market: PT_WEETH_POOL, + _weth: WETH_ADDRESS }); IERC20 _pt = ptHandler.PT(); diff --git a/test/helpers/ReserveOracleSharedSetup.sol b/test/helpers/ReserveOracleSharedSetup.sol index fb12c9b6..5f3925a1 100644 --- a/test/helpers/ReserveOracleSharedSetup.sol +++ b/test/helpers/ReserveOracleSharedSetup.sol @@ -60,15 +60,19 @@ contract ReserveOracleSharedSetup is IonPoolSharedSetup { function setUp() public virtual override { if (blockNumber == 0) { - vm.createSelectFork(MAINNET_RPC_URL); + vm.createSelectFork(_getForkRpc()); } else { - mainnetFork = vm.createSelectFork(MAINNET_RPC_URL, blockNumber); + mainnetFork = vm.createSelectFork(_getForkRpc(), blockNumber); } super.setUp(); mockToken = new ERC20PresetMinterPauser("Mock LST", "mLST"); } + function _getForkRpc() internal virtual returns (string memory) { + return vm.envString("MAINNET_ARCHIVE_RPC_URL"); + } + function changeStaderOracleExchangeRate( uint256 totalEthBalance, uint256 totalSupply diff --git a/test/helpers/handlers/IonHandlerForkBase.sol b/test/helpers/handlers/IonHandlerForkBase.sol index cf5f527c..da0bbc26 100644 --- a/test/helpers/handlers/IonHandlerForkBase.sol +++ b/test/helpers/handlers/IonHandlerForkBase.sol @@ -12,6 +12,8 @@ import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import { WETH_ADDRESS } from "./../../../src/Constants.sol"; + abstract contract IonHandler_ForkBase is IonPoolSharedSetup { uint256 constant INITIAL_THIS_UNDERLYING_BALANCE = 20e18; @@ -23,15 +25,15 @@ abstract contract IonHandler_ForkBase is IonPoolSharedSetup { AggregatorV2V3Interface constant STETH_ETH_CHAINLINK = AggregatorV2V3Interface(0x86392dC19c0b719886221c78AB11eb8Cf5c52812); - IWETH9 constant weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IWETH9 internal weth = IWETH9(_getUnderlying()); uint256 forkBlock = 0; bytes32[] borrowerWhitelistProof; function setUp() public virtual override { - if (forkBlock == 0) vm.createSelectFork(vm.envString("MAINNET_ARCHIVE_RPC_URL")); - else vm.createSelectFork(vm.envString("MAINNET_ARCHIVE_RPC_URL"), forkBlock); + if (forkBlock == 0) vm.createSelectFork(_getForkRpc()); + else vm.createSelectFork(_getForkRpc(), forkBlock); super.setUp(); vm.deal(lender1, INITIAL_LENDER_UNDERLYING_BALANCE); @@ -58,8 +60,12 @@ abstract contract IonHandler_ForkBase is IonPoolSharedSetup { } } + /** + * The asset that the lenders deposit into the IonPool. By default, the test + * assumes the base asset is WETH. + */ function _getUnderlying() internal pure virtual override returns (address) { - return address(weth); + return address(WETH_ADDRESS); } function _getDebtCeiling(uint8) internal pure override returns (uint256) { @@ -72,5 +78,9 @@ abstract contract IonHandler_ForkBase is IonPoolSharedSetup { function _getHandler() internal view virtual returns (address); + function _getForkRpc() internal view virtual returns (string memory) { + return vm.envString("MAINNET_ARCHIVE_RPC_URL"); + } + receive() external payable { } } diff --git a/test/helpers/handlers/LrtHandlerForkBase.sol b/test/helpers/handlers/LrtHandlerForkBase.sol index 10a37a9d..28afb975 100644 --- a/test/helpers/handlers/LrtHandlerForkBase.sol +++ b/test/helpers/handlers/LrtHandlerForkBase.sol @@ -24,36 +24,40 @@ abstract contract LrtHandler_ForkBase is IonHandler_ForkBase { } config.distributionFactors[0] = 1e4; - if (forkBlock == 0) vm.createSelectFork(vm.envString("MAINNET_ARCHIVE_RPC_URL")); - else vm.createSelectFork(vm.envString("MAINNET_ARCHIVE_RPC_URL"), forkBlock); + if (forkBlock == 0) vm.createSelectFork(_getForkRpc()); + else vm.createSelectFork(_getForkRpc(), forkBlock); IonPoolSharedSetup.setUp(); - (, int256 stEthSpot,,,) = STETH_ETH_CHAINLINK.latestRoundData(); - uint256 wstEthInEthSpot = MAINNET_WSTETH.getStETHByWstETH(uint256(stEthSpot)); - - // ETH / weETH [8 decimals] - (, int256 answer,,,) = REDSTONE_WEETH_ETH_PRICE_FEED.latestRoundData(); - - // wstETH / weETH [18 decimals] - uint256 weEthWstEthSpot = uint256(answer).scaleUpToWad(8).wadDivDown(wstEthInEthSpot); - - spotOracles[0].setPrice(weEthWstEthSpot); + spotOracles[0].setPrice(_getInitialSpotPrice()); vm.deal(lender1, INITIAL_LENDER_UNDERLYING_BALANCE); vm.deal(lender2, INITIAL_LENDER_UNDERLYING_BALANCE); vm.startPrank(lender1); - uint256 amount = WSTETH_ADDRESS.depositForLst(INITIAL_LENDER_UNDERLYING_BALANCE); // TODO remove - WSTETH_ADDRESS.approve(address(ionPool), type(uint256).max); // TODO remove + + // uint256 amount = WSTETH_ADDRESS.depositForLst(INITIAL_LENDER_UNDERLYING_BALANCE); // TODO remove + // WSTETH_ADDRESS.approve(address(ionPool), type(uint256).max); // TODO remove deal(address(ionPool.underlying()), lender1, INITIAL_LENDER_UNDERLYING_BALANCE); ionPool.underlying().approve(address(ionPool), type(uint256).max); - ionPool.supply(lender1, amount, emptyProof); + ionPool.supply(lender1, INITIAL_LENDER_UNDERLYING_BALANCE, emptyProof); vm.stopPrank(); } function _getUnderlying() internal pure virtual override returns (address) { return address(WSTETH_ADDRESS); } + + function _getInitialSpotPrice() internal view virtual returns (uint256) { + (, int256 stEthSpot,,,) = STETH_ETH_CHAINLINK.latestRoundData(); + uint256 wstEthInEthSpot = MAINNET_WSTETH.getStETHByWstETH(uint256(stEthSpot)); + + // ETH / weETH [8 decimals] + (, int256 answer,,,) = REDSTONE_WEETH_ETH_PRICE_FEED.latestRoundData(); + + // wstETH / weETH [18 decimals] + uint256 weEthWstEthSpot = uint256(answer).scaleUpToWad(8).wadDivDown(wstEthInEthSpot); + return weEthWstEthSpot; + } }