Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base Market Deployments #110

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion deployment-config/02_DeployInterestRateModule.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"adjustedAboveKinkSlope": "0",
"minimumAboveKinkSlope": "64011036758000000000"
},
"yieldOracleAddress": "0x437CC840e234C2127f54CD59B0B18aF59c586760"
"yieldOracleAddress": "0x05f5Fe49e6298169056C1F9B1ea8D8e12A59eE55"
}
15 changes: 7 additions & 8 deletions deployment-config/04_DeployIonPool.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"underlying": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"treasury": "0x0000000000417626Ef34D62C4DC189b021603f2F",
"underlying": "0x4200000000000000000000000000000000000006",
"treasury": "0x94544835Cf97c631f101c5f538787fE14E2E04f6",
"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": "0xE2aA4636448CDB61A25388B23BA10f9675A9b4d9",
"interestRateModule": "0x643e2674e38234b67DD8F1CE4a9e5F645870F6c1",
"salt": "0xa53bcb7572e19b03e4aae7000000000000000000000000000000000000000000"
}
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
15 changes: 8 additions & 7 deletions script/deploy/04_DeployIonPool.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions src/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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);

jpick713 marked this conversation as resolved.
Show resolved Hide resolved
// rsETH
IRedstonePriceFeed constant REDSTONE_RSETH_ETH_PRICE_FEED =
Expand Down
60 changes: 60 additions & 0 deletions src/oracles/reserve/lrt/WeEthWethReserveOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { ReserveOracle } from "../ReserveOracle.sol";
import { BASE_WEETH_ETH_EXCHANGE_RATE_CHAINLINK } 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 MaxTimeFromLastUpdateExceeded(uint256, uint256);

uint256 public immutable MAX_TIME_FROM_LAST_UPDATE; // seconds

/**
* @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
)
ReserveOracle(_ilkIndex, _feeds, _quorum, _maxChange)
{
MAX_TIME_FROM_LAST_UPDATE = _maxTimeFromLastUpdate;
_initializeExchangeRate();
}

/**
* @notice Returns the exchange rate between WETH and weETH.
* @return Exchange rate between WETH and weETH.
*/
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]
}
}
}
51 changes: 51 additions & 0 deletions src/oracles/spot/lrt/WeEthWethSpotOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 } 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;

uint256 public immutable MAX_TIME_FROM_LAST_UPDATE; // seconds

/**
* @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
)
SpotOracle(_ltv, _reserveOracle)
{
MAX_TIME_FROM_LAST_UPDATE = _maxTimeFromLastUpdate;
}

/**
* @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) {
(, int256 ethPerWeEth,, uint256 ethPerWeEthUpdatedAt,) = 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]
}
}
}
86 changes: 84 additions & 2 deletions test/fork/concrete/lrt/ReserveOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -403,3 +409,79 @@ 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

function setUp() public override {
super.setUp();
reserveOracle = new WeEthWethReserveOracle(ILK_INDEX, emptyFeeds, QUORUM, MAX_CHANGE, MAX_TIME_FROM_LAST_UPDATE);
}

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");
}
}
19 changes: 19 additions & 0 deletions test/fork/concrete/lrt/SpotOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -137,3 +140,19 @@ 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 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);
spotOracle = new WeEthWethSpotOracle(MAX_LTV, address(reserveOracle), MAX_TIME_FROM_LAST_UPDATE);
}

function _getForkRpc() internal override returns (string memory) {
return vm.envString("BASE_MAINNET_RPC_URL");
}
}
8 changes: 6 additions & 2 deletions test/helpers/ReserveOracleSharedSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading