From dd2719091787ff5623716df804ab7d4d1ba2f005 Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 6 Sep 2024 11:42:31 -0300 Subject: [PATCH 01/42] aerodrome initial draft --- common/configuration.ts | 3 + .../aerodrome/AerodromeGaugeWrapper.sol | 49 +++ .../assets/aerodrome/AerodromePoolTokens.sol | 231 +++++++++++ .../aerodrome/AerodromeStableCollateral.sol | 153 +++++++ .../assets/aerodrome/vendor/IAeroGauge.sol | 36 ++ .../assets/aerodrome/vendor/IAeroPool.sol | 39 ++ .../assets/aerodrome/vendor/IAeroRouter.sol | 59 +++ .../aerodrome/AerodromeGaugeWrapper.test.ts | 386 ++++++++++++++++++ .../aerodrome/constants.ts | 40 ++ .../aerodrome/helpers.ts | 76 ++++ 10 files changed, 1072 insertions(+) create mode 100644 contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol create mode 100644 contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol create mode 100644 contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol create mode 100644 contracts/plugins/assets/aerodrome/vendor/IAeroGauge.sol create mode 100644 contracts/plugins/assets/aerodrome/vendor/IAeroPool.sol create mode 100644 contracts/plugins/assets/aerodrome/vendor/IAeroRouter.sol create mode 100644 test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts create mode 100644 test/plugins/individual-collateral/aerodrome/constants.ts create mode 100644 test/plugins/individual-collateral/aerodrome/helpers.ts diff --git a/common/configuration.ts b/common/configuration.ts index 928d4b96a..91b59421c 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -516,6 +516,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', + AERO: '0x940181a94A35A4569E4529A3CDfB74e38FD98631', + eUSD: '0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -532,6 +534,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h + eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol b/contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol new file mode 100644 index 000000000..dc6ff7f5a --- /dev/null +++ b/contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../erc20/RewardableERC20Wrapper.sol"; +import "./vendor/IAeroGauge.sol"; + +// Note: Only supports AERO rewards. +contract AerodromeGaugeWrapper is RewardableERC20Wrapper { + using SafeERC20 for IERC20; + + IAeroGauge public immutable gauge; + + /// @param _lpToken The Aerodrome LP token, transferrable + constructor( + ERC20 _lpToken, + string memory _name, + string memory _symbol, + ERC20 _aero, + IAeroGauge _gauge + ) RewardableERC20Wrapper(_lpToken, _name, _symbol, _aero) { + require( + address(_aero) != address(0) && + address(_gauge) != address(0) && + address(_lpToken) != address(0), + "invalid address" + ); + + require(address(_aero) == address(_gauge.rewardToken()), "wrong Aero"); + + gauge = _gauge; + } + + // deposit an Aerodrome LP token + function _afterDeposit(uint256 _amount, address) internal override { + underlying.approve(address(gauge), _amount); + gauge.deposit(_amount); + } + + // withdraw to Aerodrome LP token + function _beforeWithdraw(uint256 _amount, address) internal override { + gauge.withdraw(_amount); + } + + // claim rewards - only supports AERO rewards + function _claimAssetRewards() internal virtual override { + gauge.getReward(address(this)); + } +} diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol new file mode 100644 index 000000000..62a04bed8 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: ISC +pragma solidity 0.8.19; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "contracts/plugins/assets/OracleLib.sol"; +import "contracts/libraries/Fixed.sol"; +import "./vendor/IAeroPool.sol"; + +/// Supports Aerodrome stable pools (2 tokens) +contract AerodromePoolTokens { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + error WrongIndex(uint8 maxLength); + error NoToken(uint8 tokenNumber); + + uint8 internal constant nTokens = 2; + + enum AeroPoolType { + Stable, + Volatile // not supported in this version + } + + // === State (Immutable) === + + IAeroPool public immutable pool; + + IERC20Metadata internal immutable token0; + IERC20Metadata internal immutable token1; + + // For each token, we maintain up to two feeds/timeouts/errors + // The data below would normally be a struct, but we want bytecode substitution + + AggregatorV3Interface internal immutable _t0feed0; + AggregatorV3Interface internal immutable _t0feed1; + uint48 internal immutable _t0timeout0; // {s} + uint48 internal immutable _t0timeout1; // {s} + uint192 internal immutable _t0error0; // {1} + uint192 internal immutable _t0error1; // {1} + + AggregatorV3Interface internal immutable _t1feed0; + AggregatorV3Interface internal immutable _t1feed1; + uint48 internal immutable _t1timeout0; // {s} + uint48 internal immutable _t1timeout1; // {s} + uint192 internal immutable _t1error0; // {1} + uint192 internal immutable _t1error1; // {1} + + // === Config === + + struct APTConfiguration { + IAeroPool pool; + AeroPoolType poolType; + AggregatorV3Interface[][] feeds; // row should multiply to give {UoA/ref}; max columns is 2 + uint48[][] oracleTimeouts; // {s} same order as feeds + uint192[][] oracleErrors; // {1} same order as feeds + } + + constructor(APTConfiguration memory config) { + require(maxFeedsLength(config.feeds) <= 2, "price feeds limited to 2"); + require( + config.feeds.length == nTokens && minFeedsLength(config.feeds) != 0, + "each token needs at least 1 price feed" + ); + require(address(config.pool) != address(0), "pool address is zero"); + + pool = config.pool; + + // Solidity does not support immutable arrays. This is a hack to get the equivalent of + // an immutable array so we do not have store the token feeds in the blockchain. This is + // a gas optimization since it is significantly more expensive to read and write on the + // blockchain than it is to use embedded values in the bytecode. + + // === Tokens === + + if (config.poolType != AeroPoolType.Stable) { + revert("invalid poolType"); + } + + token0 = IERC20Metadata(pool.token0()); + token1 = IERC20Metadata(pool.token1()); + + // === Feeds + timeouts === + // I know this section at-first looks verbose and silly, but it's actually well-justified: + // - immutable variables cannot be conditionally written to + // - a struct or an array would not be able to be immutable + // - immutable variables means values get in-lined in the bytecode + + // token0 + bool more = config.feeds[0].length != 0; + // untestable: + // more will always be true based on previous feeds validations + _t0feed0 = more ? config.feeds[0][0] : AggregatorV3Interface(address(0)); + _t0timeout0 = more && config.oracleTimeouts[0].length != 0 + ? config.oracleTimeouts[0][0] + : 0; + _t0error0 = more && config.oracleErrors[0].length != 0 ? config.oracleErrors[0][0] : 0; + if (more) { + require(address(_t0feed0) != address(0), "t0feed0 empty"); + require(_t0timeout0 != 0, "t0timeout0 zero"); + require(_t0error0 < FIX_ONE, "t0error0 too large"); + } + + more = config.feeds[0].length > 1; + _t0feed1 = more ? config.feeds[0][1] : AggregatorV3Interface(address(0)); + _t0timeout1 = more && config.oracleTimeouts[0].length > 1 ? config.oracleTimeouts[0][1] : 0; + _t0error1 = more && config.oracleErrors[0].length > 1 ? config.oracleErrors[0][1] : 0; + if (more) { + require(address(_t0feed1) != address(0), "t0feed1 empty"); + require(_t0timeout1 != 0, "t0timeout1 zero"); + require(_t0error1 < FIX_ONE, "t0error1 too large"); + } + + // token1 + // untestable: + // more will always be true based on previous feeds validations + more = config.feeds[1].length != 0; + _t1feed0 = more ? config.feeds[1][0] : AggregatorV3Interface(address(0)); + _t1timeout0 = more && config.oracleTimeouts[1].length != 0 + ? config.oracleTimeouts[1][0] + : 0; + _t1error0 = more && config.oracleErrors[1].length != 0 ? config.oracleErrors[1][0] : 0; + if (more) { + require(address(_t1feed0) != address(0), "t1feed0 empty"); + require(_t1timeout0 != 0, "t1timeout0 zero"); + require(_t1error0 < FIX_ONE, "t1error0 too large"); + } + + more = config.feeds[1].length > 1; + _t1feed1 = more ? config.feeds[1][1] : AggregatorV3Interface(address(0)); + _t1timeout1 = more && config.oracleTimeouts[1].length > 1 ? config.oracleTimeouts[1][1] : 0; + _t1error1 = more && config.oracleErrors[1].length > 1 ? config.oracleErrors[1][1] : 0; + if (more) { + require(address(_t1feed1) != address(0), "t1feed1 empty"); + require(_t1timeout1 != 0, "t1timeout1 zero"); + require(_t1error1 < FIX_ONE, "t1error1 too large"); + } + } + + /// @dev Warning: Can revert + /// @param index The index of the token: 0 or 1 + /// @return low {UoA/ref_index} + /// @return high {UoA/ref_index} + function tokenPrice(uint8 index) public view virtual returns (uint192 low, uint192 high) { + if (index >= nTokens) revert WrongIndex(nTokens - 1); + + // Use only 1 feed if 2nd feed not defined + // otherwise: multiply feeds together, e.g; {UoA/ref} = {UoA/target} * {target/ref} + uint192 x; + uint192 y = FIX_ONE; + uint192 xErr; // {1} + uint192 yErr; // {1} + // if only 1 feed: `y` is FIX_ONE and `yErr` is 0 + + if (index == 0) { + x = _t0feed0.price(_t0timeout0); + xErr = _t0error0; + if (address(_t0feed1) != address(0)) { + y = _t0feed1.price(_t0timeout1); + yErr = _t0error1; + } + } else { + x = _t1feed0.price(_t1timeout0); + xErr = _t1error0; + if (address(_t1feed1) != address(0)) { + y = _t1feed1.price(_t1timeout1); + yErr = _t1error1; + } + } + + return toRange(x, y, xErr, yErr); + } + + /// @param index The index of the token: 0 or 1 + /// @return [{ref_index}] + function tokenReserve(uint8 index) public view virtual returns (uint256) { + if (index >= nTokens) revert WrongIndex(nTokens - 1); + if (index == 0) return pool.reserve0(); + return pool.reserve1(); + } + + // === Internal === + + function maxPoolOracleTimeout() internal view virtual returns (uint48) { + return + uint48( + Math.max(Math.max(_t0timeout0, _t1timeout0), Math.max(_t0timeout1, _t1timeout1)) + ); + } + + // === Private === + + function getToken(uint8 index) private view returns (IERC20Metadata) { + // untestable: + // getToken is always called with a valid index + if (index >= nTokens) revert WrongIndex(nTokens - 1); + if (index == 0) return token0; + return token1; + } + + function minFeedsLength(AggregatorV3Interface[][] memory feeds) private pure returns (uint8) { + uint8 minLength = type(uint8).max; + for (uint8 i = 0; i < feeds.length; ++i) { + minLength = uint8(Math.min(minLength, feeds[i].length)); + } + return minLength; + } + + function maxFeedsLength(AggregatorV3Interface[][] memory feeds) private pure returns (uint8) { + uint8 maxLength; + for (uint8 i = 0; i < feeds.length; ++i) { + maxLength = uint8(Math.max(maxLength, feeds[i].length)); + } + return maxLength; + } + + /// x and y can be any two fixes that can be multiplied + /// @param xErr {1} error associated with x + /// @param yErr {1} error associated with y + /// returns low and high extremes of x * y, given errors + function toRange( + uint192 x, + uint192 y, + uint192 xErr, + uint192 yErr + ) private pure returns (uint192 low, uint192 high) { + low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); + high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); + } +} diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol new file mode 100644 index 000000000..81311a800 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "contracts/interfaces/IAsset.sol"; +import "contracts/libraries/Fixed.sol"; +import "contracts/plugins/assets/FiatCollateral.sol"; +import "../../../interfaces/IRewardable.sol"; +import "./AerodromePoolTokens.sol"; + +// This plugin only works on Base +IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); + +/** + * @title AerodromeStableCollateral + * This plugin contract is designed for Aerodrome stable pools + * Each token in the pool can have between 1 and 2 oracles per each token. + * + * tok = AerodromeStakingWrapper(stablePool) + * ref = 1e18 (fixed) + * tar = USD + * UoA = USD + * + */ +contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout + /// @dev No revenue hiding (refPerTok() == FIX_ONE) + /// @dev config.erc20 should be an AerodromeStakingWrapper + constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) + FiatCollateral(config) + AerodromePoolTokens(aptConfig) + { + require(config.defaultThreshold != 0, "defaultThreshold zero"); + maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); + } + + /// Can revert, used by other contract functions in order to catch errors + /// Should not return FIX_MAX for low + /// Should only return FIX_MAX for high if low is 0 + /// Should NOT be manipulable by MEV + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + virtual + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // get reserves + uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); + uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); + uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + uint192 sqrtK = (r0.sqrt()).mulDiv(r1.sqrt(), totalSupply); + + // get token prices + (uint192 p0_low, uint192 p0_high) = tokenPrice(0); + (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + + // {UoA/tok} + low = sqrtK.mul(2).mul(((p0_low.mul(p1_low)).sqrt())); + high = sqrtK.mul(2).mul(((p0_high.mul(p1_high)).sqrt())); + + assert(low <= high); //obviously true just by inspection + pegPrice = FIX_ONE; + } + + /// Should not revert + /// Refresh exchange rates and update default status. + /// Have to override to add custom default checks + function refresh() public virtual override { + CollateralStatus oldStatus = status(); + + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {UoA/tok} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high != FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + savedPegPrice = pegPrice; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + // untested: + // validated in other plugins, cost to test here is high + assert(low == 0); + } + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (low == 0 || _anyDepeggedInPool()) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); + } + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } + + /// Claim rewards earned by holding a balance of the ERC20 token + /// @custom:delegate-call + function claimRewards() external virtual override(Asset, IRewardable) { + uint256 aeroBal = AERO.balanceOf(address(this)); + IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(AERO, AERO.balanceOf(address(this)) - aeroBal); + } + + // === Internal === + + // Override this later to implement non-stable pools + function _anyDepeggedInPool() internal view virtual returns (bool) { + // Check reference token oracles + for (uint8 i = 0; i < nTokens; ++i) { + try this.tokenPrice(i) returns (uint192 low, uint192 high) { + // {UoA/tok} = {UoA/tok} + {UoA/tok} + uint192 mid = (low + high) / 2; + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (mid < pegBottom || mid > pegTop) return true; + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + // untested: + // pattern validated in other plugins, cost to test is high + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return true; + } + } + + return false; + } +} diff --git a/contracts/plugins/assets/aerodrome/vendor/IAeroGauge.sol b/contracts/plugins/assets/aerodrome/vendor/IAeroGauge.sol new file mode 100644 index 000000000..4611f5f50 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/vendor/IAeroGauge.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +interface IAeroGauge { + error ZeroAmount(); + + /// @notice Address of the pool LP token which is deposited (staked) for rewards + function stakingToken() external view returns (address); + + /// @notice Address of the token (AERO) rewarded to stakers + function rewardToken() external view returns (address); + + /// @notice Cached amount of rewardToken earned for an account + function rewards(address) external view returns (uint256); + + /// @notice Returns accrued balance to date from last claim / first deposit. + function earned(address _account) external view returns (uint256); + + /// @notice Retrieve rewards for an address. + /// @dev Throws if not called by same address or voter. + /// @param _account . + function getReward(address _account) external; + + /// @notice Deposit LP tokens into gauge for msg.sender + /// @param _amount . + function deposit(uint256 _amount) external; + + /// @notice Deposit LP tokens into gauge for any user + /// @param _amount . + /// @param _recipient Recipient to give balance to + function deposit(uint256 _amount, address _recipient) external; + + /// @notice Withdraw LP tokens for user + /// @param _amount . + function withdraw(uint256 _amount) external; +} diff --git a/contracts/plugins/assets/aerodrome/vendor/IAeroPool.sol b/contracts/plugins/assets/aerodrome/vendor/IAeroPool.sol new file mode 100644 index 000000000..7070453f6 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/vendor/IAeroPool.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +// solhint-disable func-param-name-mixedcase, func-name-mixedcase +interface IAeroPool is IERC20Metadata { + /// @notice Returns [token0, token1] + function tokens() external view returns (address, address); + + /// @notice Address of token in the pool with the lower address value + function token0() external view returns (address); + + /// @notice Address of token in the poool with the higher address value + function token1() external view returns (address); + + /// @notice Amount of token0 in pool + function reserve0() external view returns (uint256); + + /// @notice Amount of token1 in pool + function reserve1() external view returns (uint256); + + function stable() external view returns (bool); + + function mint(address to) external returns (uint256 liquidity); + + /// @notice Update reserves and, on the first call per block, price accumulators + /// @return _reserve0 . + /// @return _reserve1 . + /// @return _blockTimestampLast . + function getReserves() + external + view + returns ( + uint256 _reserve0, + uint256 _reserve1, + uint256 _blockTimestampLast + ); +} diff --git a/contracts/plugins/assets/aerodrome/vendor/IAeroRouter.sol b/contracts/plugins/assets/aerodrome/vendor/IAeroRouter.sol new file mode 100644 index 000000000..c183e56f9 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/vendor/IAeroRouter.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +interface IAeroRouter { + /// @notice Add liquidity of two tokens to a Pool + /// @param tokenA . + /// @param tokenB . + /// @param stable True if pool is stable, false if volatile + /// @param amountADesired Amount of tokenA desired to deposit + /// @param amountBDesired Amount of tokenB desired to deposit + /// @param amountAMin Minimum amount of tokenA to deposit + /// @param amountBMin Minimum amount of tokenB to deposit + /// @param to Recipient of liquidity token + /// @param deadline Deadline to receive liquidity + /// @return amountA Amount of tokenA to actually deposit + /// @return amountB Amount of tokenB to actually deposit + /// @return liquidity Amount of liquidity token returned from deposit + function addLiquidity( + address tokenA, + address tokenB, + bool stable, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + // **** REMOVE LIQUIDITY **** + + /// @notice Remove liquidity of two tokens from a Pool + /// @param tokenA . + /// @param tokenB . + /// @param stable True if pool is stable, false if volatile + /// @param liquidity Amount of liquidity to remove + /// @param amountAMin Minimum amount of tokenA to receive + /// @param amountBMin Minimum amount of tokenB to receive + /// @param to Recipient of tokens received + /// @param deadline Deadline to remove liquidity + /// @return amountA Amount of tokenA received + /// @return amountB Amount of tokenB received + function removeLiquidity( + address tokenA, + address tokenB, + bool stable, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); +} diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts new file mode 100644 index 000000000..954656fd8 --- /dev/null +++ b/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts @@ -0,0 +1,386 @@ +import { networkConfig } from '#/common/configuration' +import { useEnv } from '#/utils/env' +import hre, { BigNumber, ethers } from 'hardhat' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { makeWUSDCeUSD, mintLpToken, mintWrappedLpToken, resetFork } from './helpers' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { + IAeroPool, + ERC20Mock, + AerodromeGaugeWrapper__factory, + AerodromeGaugeWrapper, + IAeroGauge, +} from '@typechain/index' +import { expect } from 'chai' +import { ZERO_ADDRESS } from '#/common/constants' +import { + AERO, + eUSD, + AERO_USDC_eUSD_GAUGE, + AERO_USDC_eUSD_POOL, + AERO_USDC_eUSD_HOLDER, +} from './constants' +import { bn, fp } from '#/common/numbers' +import { getChainId } from '#/common/blockchain-utils' +import { advanceTime } from '#/test/utils/time' + +const describeFork = useEnv('FORK') ? describe : describe.skip + +const point1Pct = (value: BigNumber): BigNumber => { + return value.div(1000) +} +describeFork('Aerodrome Gauge Wrapper', () => { + let bob: SignerWithAddress + let charles: SignerWithAddress + let don: SignerWithAddress + let token0: ERC20Mock + let token1: ERC20Mock + let aero: ERC20Mock + let gauge: IAeroGauge + let wrapper: AerodromeGaugeWrapper + let lpToken: IAeroPool + let AerodromeGaugeWrapperFactory: AerodromeGaugeWrapper__factory + + let chainId: number + + before(async () => { + await resetFork() + + chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + AerodromeGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + }) + + beforeEach(async () => { + ;[, bob, charles, don] = await ethers.getSigners() + ;({ token0, token1, wrapper, lpToken } = await loadFixture(makeWUSDCeUSD)) + gauge = await ethers.getContractAt('IAeroGauge', AERO_USDC_eUSD_GAUGE) + }) + + describe('Deployment', () => { + it('reverts if deployed with a 0 address for AERO token or staking contract', async () => { + await expect( + AerodromeGaugeWrapperFactory.deploy( + AERO_USDC_eUSD_POOL, + await wrapper.name(), + await wrapper.symbol(), + ZERO_ADDRESS, + AERO_USDC_eUSD_GAUGE + ) + ).to.be.reverted + + await expect( + AerodromeGaugeWrapperFactory.deploy( + AERO_USDC_eUSD_POOL, + await wrapper.name(), + await wrapper.symbol(), + AERO, + ZERO_ADDRESS + ) + ).to.be.reverted + }) + + it('reverts if deployed with invalid pool', async () => { + await expect( + AerodromeGaugeWrapperFactory.deploy( + ZERO_ADDRESS, + await wrapper.name(), + await wrapper.symbol(), + AERO, + AERO_USDC_eUSD_GAUGE + ) + ).to.be.reverted + }) + + it('reverts if deployed with invalid AERO token', async () => { + const INVALID_AERO = eUSD // mock (any erc20) + await expect( + AerodromeGaugeWrapperFactory.deploy( + AERO_USDC_eUSD_POOL, + await wrapper.name(), + await wrapper.symbol(), + INVALID_AERO, + AERO_USDC_eUSD_GAUGE + ) + ).to.be.revertedWith('wrong Aero') + }) + }) + + describe('Deposit', () => { + const amount = fp('0.02') + + beforeEach(async () => { + await mintLpToken(gauge, lpToken, amount, AERO_USDC_eUSD_HOLDER, bob.address) + await lpToken.connect(bob).approve(wrapper.address, ethers.constants.MaxUint256) + }) + + it('deposits correct amount', async () => { + const balanceInLPPrev = await lpToken.balanceOf(bob.address) + + await wrapper.connect(bob).deposit(await lpToken.balanceOf(bob.address), bob.address) + + expect(await lpToken.balanceOf(bob.address)).to.equal(0) + expect(await wrapper.balanceOf(bob.address)).to.equal(balanceInLPPrev) + }) + + it('deposits less than available', async () => { + const depositAmount = await lpToken.balanceOf(bob.address).then((e) => e.div(2)) + + await wrapper.connect(bob).deposit(depositAmount, bob.address) + + expect(await lpToken.balanceOf(bob.address)).to.be.closeTo(depositAmount, 10) + expect(await wrapper.balanceOf(bob.address)).to.closeTo(depositAmount, 10) + }) + + it('has accurate balances when doing multiple deposits', async () => { + const depositAmount = await lpToken.balanceOf(bob.address) + await wrapper.connect(bob).deposit(depositAmount.mul(3).div(4), bob.address) + + await advanceTime(1000) + await wrapper.connect(bob).deposit(depositAmount.mul(1).div(4), bob.address) + + expect(await wrapper.balanceOf(bob.address)).to.closeTo(depositAmount, 10) + }) + + it('updates the totalSupply', async () => { + const totalSupplyBefore = await wrapper.totalSupply() + const expectedAmount = await lpToken.balanceOf(bob.address) + + await wrapper.connect(bob).deposit(expectedAmount, bob.address) + expect(await wrapper.totalSupply()).to.equal(totalSupplyBefore.add(expectedAmount)) + }) + + it('handles deposits with 0 amount', async () => { + const balanceInLPPrev = await lpToken.balanceOf(bob.address) + + await expect(wrapper.connect(bob).deposit(0, bob.address)).to.not.be.reverted + + expect(await lpToken.balanceOf(bob.address)).to.equal(balanceInLPPrev) + expect(await wrapper.balanceOf(bob.address)).to.equal(0) + }) + }) + + describe('Withdraw', () => { + const initAmt = fp('0.02') + + beforeEach(async () => { + await mintWrappedLpToken( + wrapper, + gauge, + lpToken, + initAmt, + AERO_USDC_eUSD_HOLDER, + bob, + bob.address + ) + await mintWrappedLpToken( + wrapper, + gauge, + lpToken, + initAmt, + AERO_USDC_eUSD_HOLDER, + charles, + charles.address + ) + }) + + it('withdraws to own account', async () => { + const initialBal = await wrapper.balanceOf(bob.address) + await wrapper.connect(bob).withdraw(await wrapper.balanceOf(bob.address), bob.address) + const finalBal = await wrapper.balanceOf(bob.address) + + expect(finalBal).to.closeTo(bn('0'), 10) + expect(await lpToken.balanceOf(bob.address)).to.closeTo(initialBal, 10) + }) + + it('withdraws all balance via multiple withdrawals', async () => { + const initialBalance = await wrapper.balanceOf(bob.address) + + const withdrawAmt = initialBalance.div(2) + await wrapper.connect(bob).withdraw(withdrawAmt, bob.address) + expect(await wrapper.balanceOf(bob.address)).to.closeTo(initialBalance.sub(withdrawAmt), 0) + + await advanceTime(1000) + + await wrapper.connect(bob).withdraw(withdrawAmt, bob.address) + expect(await wrapper.balanceOf(bob.address)).to.closeTo(bn('0'), 10) + }) + + it('handles complex withdrawal sequence', async () => { + let bobWithdrawn = bn('0') + let charlesWithdrawn = bn('0') + let donWithdrawn = bn('0') + + const firstWithdrawAmt = await wrapper.balanceOf(charles.address).then((e) => e.div(2)) + + charlesWithdrawn = charlesWithdrawn.add(firstWithdrawAmt) + + await wrapper.connect(charles).withdraw(firstWithdrawAmt, charles.address) + const newBalanceCharles = await lpToken.balanceOf(charles.address) + expect(newBalanceCharles).to.closeTo(firstWithdrawAmt, 10) + + // don deposits + await mintWrappedLpToken( + wrapper, + gauge, + lpToken, + initAmt, + AERO_USDC_eUSD_HOLDER, + don, + don.address + ) + + // bob withdraws SOME + bobWithdrawn = bobWithdrawn.add(bn('12345e6')) + await wrapper.connect(bob).withdraw(bn('12345e6'), bob.address) + + // don withdraws SOME + donWithdrawn = donWithdrawn.add(bn('123e6')) + await wrapper.connect(don).withdraw(bn('123e6'), don.address) + + // charles withdraws ALL + const charlesRemainingBalance = await wrapper.balanceOf(charles.address) + charlesWithdrawn = charlesWithdrawn.add(charlesRemainingBalance) + await wrapper.connect(charles).withdraw(charlesRemainingBalance, charles.address) + + // don withdraws ALL + const donRemainingBalance = await wrapper.balanceOf(don.address) + donWithdrawn = donWithdrawn.add(donRemainingBalance) + await wrapper.connect(don).withdraw(donRemainingBalance, don.address) + + // bob withdraws ALL + const bobRemainingBalance = await wrapper.balanceOf(bob.address) + bobWithdrawn = bobWithdrawn.add(bobRemainingBalance) + await wrapper.connect(bob).withdraw(bobRemainingBalance, bob.address) + + const bal = await wrapper.balanceOf(bob.address) + + expect(bal).to.closeTo(bn('0'), 10) + expect(await lpToken.balanceOf(bob.address)).to.closeTo(bobWithdrawn, 100) + expect(await lpToken.balanceOf(charles.address)).to.closeTo(charlesWithdrawn, 100) + expect(await lpToken.balanceOf(don.address)).to.closeTo(donWithdrawn, 100) + }) + + it('updates the totalSupply', async () => { + const totalSupplyBefore = await wrapper.totalSupply() + const withdrawAmt = bn('15000e6') + const expectedDiff = withdrawAmt + await wrapper.connect(bob).withdraw(withdrawAmt, bob.address) + + expect(await wrapper.totalSupply()).to.be.closeTo(totalSupplyBefore.sub(expectedDiff), 10) + }) + }) + + describe('Rewards', () => { + const initialAmount = fp('0.02') + + beforeEach(async () => { + aero = await ethers.getContractAt('ERC20Mock', AERO) + }) + + it('claims rewards from Aerodrome', async () => { + await mintWrappedLpToken( + wrapper, + gauge, + lpToken, + initialAmount, + AERO_USDC_eUSD_HOLDER, + bob, + bob.address + ) + + const initialAeroBal = await aero.balanceOf(wrapper.address) + + await advanceTime(1000) + + let expectedRewards = await gauge.earned(wrapper.address) + await wrapper.claimRewards() + expect(await gauge.earned(wrapper.address)).to.equal(0) // all claimed + + const updatedAeroBal = await aero.balanceOf(wrapper.address) + expect(updatedAeroBal).to.be.gt(initialAeroBal) + expect(updatedAeroBal.sub(initialAeroBal)).to.be.closeTo( + expectedRewards, + point1Pct(expectedRewards) + ) + + await advanceTime(1000) + + expectedRewards = await gauge.earned(wrapper.address) + await wrapper.claimRewards() + expect(await gauge.earned(wrapper.address)).to.equal(0) // all claimed + + const finalAeroBal = await aero.balanceOf(wrapper.address) + expect(finalAeroBal).to.be.gt(updatedAeroBal) + expect(finalAeroBal.sub(updatedAeroBal)).to.be.closeTo( + expectedRewards, + point1Pct(expectedRewards) + ) + }) + + it('distributes rewards to holder', async () => { + expect(await aero.balanceOf(bob.address)).to.equal(0) + expect(await aero.balanceOf(don.address)).to.equal(0) + + // deposit with bob + await mintWrappedLpToken( + wrapper, + gauge, + lpToken, + initialAmount, + AERO_USDC_eUSD_HOLDER, + bob, + bob.address + ) + + await advanceTime(1000) + + // sync rewards + await wrapper.connect(bob).claimRewards() + + let expectedRewardsBob = await wrapper.accumulatedRewards(bob.address) + + // bob can claim and get rewards + await wrapper.connect(bob).claimRewards() + expect(await aero.balanceOf(bob.address)).to.be.gt(0) + expect(await aero.balanceOf(bob.address)).to.be.closeTo( + expectedRewardsBob, + point1Pct(expectedRewardsBob) + ) + + // don does not have rewards + await wrapper.connect(don).claimRewards() + expect(await aero.balanceOf(don.address)).to.equal(0) + + // transfer some tokens to don + const balToTransfer = (await wrapper.balanceOf(bob.address)).div(2) + await wrapper.connect(bob).transfer(don.address, balToTransfer) + + await advanceTime(1000) + + // Now both have rewards + await wrapper.connect(bob).claimRewards() + expectedRewardsBob = await wrapper.accumulatedRewards(bob.address) + expect(await aero.balanceOf(bob.address)).to.be.closeTo( + expectedRewardsBob, + point1Pct(expectedRewardsBob) + ) + + // Don also gets rewards + await wrapper.connect(don).claimRewards() + const expectedRewardsDon = await wrapper.accumulatedRewards(don.address) + expect(await aero.balanceOf(don.address)).to.be.gt(0) + expect(await aero.balanceOf(don.address)).to.be.closeTo( + expectedRewardsDon, + point1Pct(expectedRewardsDon) + ) + }) + }) + + // TODO: Aerodrome exceptions? +}) diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts new file mode 100644 index 000000000..720b5f1fc --- /dev/null +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -0,0 +1,40 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Base Addresses +export const AERO_USDC_eUSD_GAUGE = '0x793F22aB88dC91793E5Ce6ADbd7E733B0BD4733e' +export const AERO_USDC_eUSD_POOL = '0x7A034374C89C463DD65D8C9BCfe63BcBCED41f4F' +export const AERO_USDC_eUSD_HOLDER = '0xB6C8ea53ABA64a4BdE857D3b25d9DEbD0B149a0a' + +export const AERODROME_ROUTER = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' + +// Tokens +export const USDC = networkConfig['8453'].tokens.USDC! +export const eUSD = networkConfig['8453'].tokens.eUSD! +export const AERO = networkConfig['8453'].tokens.AERO! + +// USDC +export const USDC_USD_FEED = networkConfig['8453'].chainlinkFeeds.USDC! +export const USDC_ORACLE_TIMEOUT = bn('86400') +export const USDC_ORACLE_ERROR = fp('0.003') +export const USDC_HOLDER = '0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A' + +// eUSD +export const eUSD_USD_FEED = networkConfig['8453'].chainlinkFeeds.eUSD! +export const eUSD_ORACLE_TIMEOUT = bn('86400') +export const eUSD_ORACLE_ERROR = fp('0.005') +export const eUSD_HOLDER = '0xb5E331615FdbA7DF49e05CdEACEb14Acdd5091c3' + +export const FORK_BLOCK = 19074000 + +// Common +export const FIX_ONE = 1n * 10n ** 18n +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const DEFAULT_THRESHOLD = fp('0.02') // 2% +export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h +export const MAX_TRADE_VOL = fp('1e6') + +export enum AerodromePoolType { + Stable, + Volatile, +} diff --git a/test/plugins/individual-collateral/aerodrome/helpers.ts b/test/plugins/individual-collateral/aerodrome/helpers.ts new file mode 100644 index 000000000..266515a55 --- /dev/null +++ b/test/plugins/individual-collateral/aerodrome/helpers.ts @@ -0,0 +1,76 @@ +import { ERC20Mock } from '@typechain/ERC20Mock' +import { + IAeroPool, + IAeroGauge, + AerodromeGaugeWrapper__factory, + AerodromeGaugeWrapper, +} from '@typechain/index' +import { ethers } from 'hardhat' +import { + USDC, + eUSD, + AERO, + AERO_USDC_eUSD_POOL, + AERO_USDC_eUSD_GAUGE, + FORK_BLOCK, +} from './constants' +import { getResetFork } from '../helpers' +import { whileImpersonating } from '#/test/utils/impersonation' + +interface WrappedAeroFixture { + token0: ERC20Mock + token1: ERC20Mock + wrapper: AerodromeGaugeWrapper + lpToken: IAeroPool +} + +export const makeWUSDCeUSD = async (sAMM_usdc_eUSD?: string): Promise => { + const lpToken = ( + await ethers.getContractAt('IAeroPool', sAMM_usdc_eUSD ?? AERO_USDC_eUSD_POOL) + ) + + const AerodromGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + + const wrapper = await AerodromGaugeWrapperFactory.deploy( + lpToken.address, + 'w' + (await lpToken.name()), + 'w' + (await lpToken.symbol()), + AERO, + AERO_USDC_eUSD_GAUGE + ) + const token0 = await ethers.getContractAt('ERC20Mock', USDC) + const token1 = await ethers.getContractAt('ERC20Mock', eUSD) + + return { token0, token1, wrapper, lpToken } +} + +export const mintLpToken = async ( + gauge: IAeroGauge, + lpToken: IAeroPool, + amount: BigNumberish, + holder: string, + recipient: string +) => { + await whileImpersonating(holder, async (signer) => { + await gauge.connect(signer).withdraw(amount) + await lpToken.connect(signer).transfer(recipient, amount) + }) +} + +export const mintWrappedLpToken = async ( + wrapper: AerodromeGaugeWrapper, + gauge: IAeroGauge, + lpToken: IAeroPool, + amount: BigNumberish, + holder: string, + user: SignerWithAddress, + recipient: string +) => { + await mintLpToken(gauge, lpToken, amount, holder, user.address) + await lpToken.connect(user).approve(wrapper.address, ethers.constants.MaxUint256) + await wrapper.connect(user).deposit(amount, recipient) +} + +export const resetFork = getResetFork(FORK_BLOCK) From f1dfdbc31fb84138315b0b06c16ae3bcec12ffa0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 6 Sep 2024 23:07:12 -0400 Subject: [PATCH 02/42] fix and tweak tryPrice --- .../plugins/assets/aerodrome/AerodromeStableCollateral.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 81311a800..27cecfb41 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -61,15 +61,15 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); - uint192 sqrtK = (r0.sqrt()).mulDiv(r1.sqrt(), totalSupply); + uint192 sqrtK = r0.mul(r1).sqrt(); // get token prices (uint192 p0_low, uint192 p0_high) = tokenPrice(0); (uint192 p1_low, uint192 p1_high) = tokenPrice(1); // {UoA/tok} - low = sqrtK.mul(2).mul(((p0_low.mul(p1_low)).sqrt())); - high = sqrtK.mul(2).mul(((p0_high.mul(p1_high)).sqrt())); + low = sqrtK.mulu(2).mulDiv(p0_low.mul(p1_low).sqrt(), totalSupply); + high = sqrtK.mulu(2).mulDiv(p0_high.mul(p1_high).sqrt(), totalSupply); assert(low <= high); //obviously true just by inspection pegPrice = FIX_ONE; From b1f10dad5d3c979858c9ba1b21c6fe34ff2dd6f4 Mon Sep 17 00:00:00 2001 From: Julian R Date: Wed, 11 Sep 2024 08:58:22 -0300 Subject: [PATCH 03/42] apply revenue hiding and scripts --- common/configuration.ts | 1 + .../assets/aerodrome/AerodromePoolTokens.sol | 21 + .../aerodrome/AerodromeStableCollateral.sol | 93 ++-- scripts/deploy.ts | 3 +- .../collaterals/deploy_aerodrome_usdc_eusd.ts | 139 ++++++ .../verify_aerodrome_usdc_eusd.ts | 103 +++++ scripts/verify_etherscan.ts | 3 +- .../AerodromeStableCollateral.test.ts | 435 ++++++++++++++++++ .../aerodrome/constants.ts | 3 +- .../aerodrome/helpers.ts | 27 ++ 10 files changed, 789 insertions(+), 39 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts create mode 100644 test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts diff --git a/common/configuration.ts b/common/configuration.ts index 91b59421c..58be27aed 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -143,6 +143,7 @@ export interface IPools { crvTriCrypto?: string crvMIM3Pool?: string sdUSDCUSDCPlus?: string + aeroUSDCeUSD?: string } interface INetworkConfig { diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 62a04bed8..426855954 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -180,6 +180,27 @@ contract AerodromePoolTokens { return pool.reserve1(); } + function sqrtK() public view virtual returns (uint192) { + uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); + uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); + return r0.mul(r1).sqrt(); + } + + /// @param index The index of the token: 0 or 1 + /// @return [address of chainlink feeds] + function tokenFeeds(uint8 index) public view virtual returns (AggregatorV3Interface[] memory) { + if (index >= nTokens) revert WrongIndex(nTokens - 1); + AggregatorV3Interface[] memory feeds = new AggregatorV3Interface[](2); + if (index == 0) { + feeds[0] = _t0feed0; + feeds[1] = _t0feed1; + } else { + feeds[0] = _t1feed0; + feeds[1] = _t1feed1; + } + return feeds; + } + // === Internal === function maxPoolOracleTimeout() internal view virtual returns (uint48) { diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 27cecfb41..6b0b0674c 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "contracts/interfaces/IAsset.sol"; import "contracts/libraries/Fixed.sol"; -import "contracts/plugins/assets/FiatCollateral.sol"; +import "contracts/plugins/assets/AppreciatingFiatCollateral.sol"; import "../../../interfaces/IRewardable.sol"; import "./AerodromePoolTokens.sol"; @@ -24,17 +24,18 @@ IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); * UoA = USD * */ -contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { +contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout /// @dev No revenue hiding (refPerTok() == FIX_ONE) /// @dev config.erc20 should be an AerodromeStakingWrapper - constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) - FiatCollateral(config) - AerodromePoolTokens(aptConfig) - { + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + APTConfiguration memory aptConfig + ) AppreciatingFiatCollateral(config, revenueHiding) AerodromePoolTokens(aptConfig) { require(config.defaultThreshold != 0, "defaultThreshold zero"); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } @@ -57,19 +58,15 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { uint192 pegPrice ) { - // get reserves - uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); - uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); - uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); - uint192 sqrtK = r0.mul(r1).sqrt(); - // get token prices (uint192 p0_low, uint192 p0_high) = tokenPrice(0); (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + // {UoA/tok} - low = sqrtK.mulu(2).mulDiv(p0_low.mul(p1_low).sqrt(), totalSupply); - high = sqrtK.mulu(2).mulDiv(p0_high.mul(p1_high).sqrt(), totalSupply); + low = sqrtK().mulu(2).mulDiv(p0_low.mul(p1_low).sqrt(), totalSupply); + high = sqrtK().mulu(2).mulDiv(p0_high.mul(p1_high).sqrt(), totalSupply); assert(low <= high); //obviously true just by inspection pegPrice = FIX_ONE; @@ -81,35 +78,53 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { function refresh() public virtual override { CollateralStatus oldStatus = status(); - // Check for soft default + save prices - try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { - // {UoA/tok}, {UoA/tok}, {UoA/tok} - // (0, 0) is a valid price; (0, FIX_MAX) is unpriced - - // Save prices if priced - if (high != FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - savedPegPrice = pegPrice; - lastSave = uint48(block.timestamp); - } else { - // must be unpriced - // untested: - // validated in other plugins, cost to test here is high - assert(low == 0); + // Check for hard default + try this.underlyingRefPerTok() returns (uint192 underlyingRefPerTok_) { + // {ref/tok} = {ref/tok} * {1} + uint192 hiddenReferencePrice = underlyingRefPerTok_.mul(revenueShowing); + + // uint192(<) is equivalent to Fix.lt + if (underlyingRefPerTok_ < exposedReferencePrice) { + exposedReferencePrice = underlyingRefPerTok_; + markStatus(CollateralStatus.DISABLED); + } else if (hiddenReferencePrice > exposedReferencePrice) { + exposedReferencePrice = hiddenReferencePrice; } - // If the price is below the default-threshold price, default eventually - // uint192(+/-) is the same as Fix.plus/minus - if (low == 0 || _anyDepeggedInPool()) { + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {UoA/tok} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high != FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + savedPegPrice = pegPrice; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + // untested: + // validated in other plugins, cost to test here is high + assert(low == 0); + } + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (low == 0 || _anyDepeggedInPool()) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string markStatus(CollateralStatus.IFFY); - } else { - markStatus(CollateralStatus.SOUND); } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string - markStatus(CollateralStatus.IFFY); + markStatus(CollateralStatus.DISABLED); } CollateralStatus newStatus = status(); @@ -126,6 +141,12 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { emit RewardsClaimed(AERO, AERO.balanceOf(address(this)) - aeroBal); } + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function underlyingRefPerTok() public view virtual override returns (uint192) { + uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + return sqrtK().mulu(2).mulDiv(FIX_ONE, totalSupply); + } + // === Internal === // Override this later to implement non-stable pools diff --git a/scripts/deploy.ts b/scripts/deploy.ts index cc2a51418..9f0387425 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -102,7 +102,8 @@ async function main() { 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', - 'phase2-assets/assets/deploy_stg.ts' + 'phase2-assets/assets/deploy_stg.ts', + 'phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts new file mode 100644 index 000000000..a0b866a57 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts @@ -0,0 +1,139 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { AerodromeStableCollateral, AerodromeGaugeWrapper, IAeroPool } from '../../../../typechain' +import { combinedError, revenueHiding } from '../../utils' +import { + AerodromePoolType, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + AERO_USDC_eUSD_POOL, + AERO_USDC_eUSD_GAUGE, + AERO, + eUSD_ORACLE_ERROR, + eUSD_ORACLE_TIMEOUT, + eUSD_USD_FEED, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Convex Stable Plugin: crvUSD-USDC + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Stable Pool for USDC-eUSD **************************/ + + let collateral: AerodromeStableCollateral + let wusdceusd: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeStableCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeStableCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_USDC_eUSD_POOL) + wusdceusd = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_USDC_eUSD_GAUGE + ) + ) + await wusdceusd.deployed() + + console.log( + `Deployed wrapper for Aerodrome Stable USDC-eUSD pool on ${hre.network.name} (${chainId}): ${wusdceusd.address} ` + ) + + const oracleError = combinedError(USDC_ORACLE_ERROR, eUSD_ORACLE_ERROR) // 0.3% & 0.5% + + collateral = await AerodromeStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wusdceusd.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + pool: AERO_USDC_eUSD_POOL, + poolType: AerodromePoolType.Stable, + feeds: [[USDC_USD_FEED], [eUSD_USD_FEED]], + oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [eUSD_ORACLE_TIMEOUT]], + oracleErrors: [[USDC_ORACLE_ERROR], [eUSD_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Stable Collateral for USDC-eUSD to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroUSDCeUSD = collateral.address + assetCollDeployments.erc20s.aeroUSDCeUSD = wusdceusd.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts b/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts new file mode 100644 index 000000000..26a0f81d2 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts @@ -0,0 +1,103 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError, revenueHiding } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_USDC_eUSD_POOL, + AERO_USDC_eUSD_GAUGE, + AERO, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + eUSD_ORACLE_ERROR, + eUSD_ORACLE_TIMEOUT, + eUSD_USD_FEED, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroUSDCeUSDPoolCollateral = await ethers.getContractAt( + 'AerodromeStableCollateral', + deployments.collateral.aeroUSDCeUSD as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_USDC_eUSD_POOL) + await verifyContract( + chainId, + await aeroUSDCeUSDPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_USDC_eUSD_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify USDC-eUSD plugin **************************/ + const oracleError = combinedError(USDC_ORACLE_ERROR, eUSD_ORACLE_ERROR) // 0.3% & 0.5% + await verifyContract( + chainId, + deployments.collateral.aeroUSDCeUSD, + [ + { + erc20: await aeroUSDCeUSDPoolCollateral.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + pool: AERO_USDC_eUSD_POOL, + poolType: AerodromePoolType.Stable, + feeds: [[USDC_USD_FEED], [eUSD_USD_FEED]], + oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [eUSD_ORACLE_TIMEOUT]], + oracleErrors: [[USDC_ORACLE_ERROR], [eUSD_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol:AerodromeStableCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 55023fd71..fa5cea47c 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -88,7 +88,8 @@ async function main() { 'collateral-plugins/verify_aave_v3_usdc.ts', 'collateral-plugins/verify_wsteth.ts', 'collateral-plugins/verify_cbeth.ts', - 'assets/verify_stg.ts' + 'assets/verify_stg.ts', + 'collateral-plugins/verify_aerodrome_usdc_eusd.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts new file mode 100644 index 000000000..99fe43794 --- /dev/null +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -0,0 +1,435 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + IAeroPool, + MockV3Aggregator, + MockV3Aggregator__factory, + AerodromeGaugeWrapper__factory, + TestICollateral, + AerodromeGaugeWrapper, + ERC20Mock, +} from '../../../../typechain' +import { networkConfig } from '../../../../common/configuration' +import { ZERO_ADDRESS } from '#/common/constants' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + AerodromePoolType, + USDC_USD_FEED, + USDC_HOLDER, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + AERO_USDC_eUSD_POOL, + AERO_USDC_eUSD_GAUGE, + AERO_USDC_eUSD_HOLDER, + AERO, + USDC, + eUSD, + eUSD_HOLDER, + eUSD_USD_FEED, + eUSD_ORACLE_ERROR, + eUSD_ORACLE_TIMEOUT, + ORACLE_ERROR, +} from './constants' +import { expectPrice } from '../../../utils/oracles' +import { mintWrappedLpToken, resetFork, getFeeds, pushAllFeedsForward } from './helpers' +import BigNumber from 'bignumber.js' + +/* + Define interfaces +*/ + +interface AeroPoolTokenConfig { + token: string + feeds: string[] + oracleTimeouts: BigNumberish[] + oracleErrors: BigNumberish[] + holder: string +} + +interface AeroStablePoolEnumeration { + testName: string + pool: string + gauge: string + holder: string + toleranceDivisor: BigNumber + amountScaleDivisor: BigNumber + tokens: AeroPoolTokenConfig[] + oracleTimeout: BigNumberish + oracleError: BigNumberish +} + +interface AeroStableCollateralOpts extends CollateralOpts { + pool?: string + poolType?: AerodromePoolType + gauge?: string + feeds?: string[][] + oracleTimeouts?: BigNumberish[][] + oracleErrors?: BigNumberish[][] +} + +interface AerodromeCollateralFixtureContext extends CollateralFixtureContext { + feeds?: string[][] +} + +// ==== + +const config = networkConfig['8453'] // use Base fork + +// Test all Aerodrome Stable pools +const all = [ + { + testName: 'Aerodrome - USDC/eUSD Stable', + pool: AERO_USDC_eUSD_POOL, + gauge: AERO_USDC_eUSD_GAUGE, + holder: AERO_USDC_eUSD_HOLDER, + tokens: [ + { + token: USDC, + feeds: [USDC_USD_FEED], + oracleTimeouts: [USDC_ORACLE_TIMEOUT], + oracleErrors: [USDC_ORACLE_ERROR], + holder: USDC_HOLDER, + }, + { + token: eUSD, + feeds: [eUSD_USD_FEED], + oracleTimeouts: [eUSD_ORACLE_TIMEOUT], + oracleErrors: [eUSD_ORACLE_ERROR], + holder: eUSD_HOLDER, + }, + ], + oracleTimeout: PRICE_TIMEOUT, // max + oracleError: ORACLE_ERROR, // combined + amountScaleDivisor: bn('1e2'), + toleranceDivisor: bn('1e2'), + }, +] + +all.forEach((curr: AeroStablePoolEnumeration) => { + const defaultCollateralOpts: AeroStableCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: curr.tokens[0].feeds[0], // unused but cannot be zero + oracleTimeout: curr.oracleTimeout, // max of oracleTimeouts + oracleError: curr.oracleError, // combined oracle error + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + pool: curr.pool, + poolType: AerodromePoolType.Stable, + gauge: curr.gauge, + feeds: [curr.tokens[0].feeds, curr.tokens[1].feeds], + oracleTimeouts: [curr.tokens[0].oracleTimeouts, curr.tokens[1].oracleTimeouts], + oracleErrors: [curr.tokens[0].oracleErrors, curr.tokens[1].oracleErrors], + } + + const deployCollateral = async ( + opts: AeroStableCollateralOpts = {} + ): Promise => { + let pool: IAeroPool + let wrapper: AerodromeGaugeWrapper + + if (!opts.erc20) { + const AerodromGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + + // Create wrapper + pool = await ethers.getContractAt('IAeroPool', curr.pool) + + wrapper = await AerodromGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + curr.gauge + ) + + opts.erc20 = wrapper.address + } + + opts = { ...defaultCollateralOpts, ...opts } + + const AeroStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'AerodromeStableCollateral' + ) + + const collateral = await AeroStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + bn(0), + { + pool: opts.pool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + }, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + // Push forward chainlink feeds + await pushAllFeedsForward(collateral) + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: AeroStableCollateralOpts = {} + ): Fixture => { + const collateralOpts = { ...defaultCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute both feeds + const token0Feed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.chainlinkFeed = token0Feed.address + + const token1Feed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[token0Feed.address], [token1Feed.address]] + + const pool = await ethers.getContractAt('IAeroPool', curr.pool) + + const AerodromeGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + + const wrapper = await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + curr.gauge + ) + + collateralOpts.erc20 = wrapper.address + + const collateral = await deployCollateral(collateralOpts) + const erc20 = await ethers.getContractAt( + 'AerodromeGaugeWrapper', + (await collateral.erc20()) as string + ) + + const rewardToken = await ethers.getContractAt('ERC20Mock', AERO) + + return { + alice, + collateral, + chainlinkFeed: token0Feed, + tok: erc20, + rewardToken, + } + } + + return makeCollateralFixtureContext + } + + /* + Define helper functions +*/ + + const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string + ) => { + const gauge = await ethers.getContractAt('IAeroGauge', curr.gauge) + const pool = await ethers.getContractAt('IAeroPool', curr.pool) + + await mintWrappedLpToken( + ctx.tok as AerodromeGaugeWrapper, + gauge, + pool, + amount, + curr.holder, + user, + recipient + ) + } + + const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const allFeeds = await getFeeds(ctx.collateral) + const initialPrices = await Promise.all(allFeeds.map((f) => f.latestRoundData())) + for (const [i, feed] of allFeeds.entries()) { + const nextAnswer = initialPrices[i].answer.sub( + initialPrices[i].answer.mul(pctDecrease).div(100) + ) + await feed.updateAnswer(nextAnswer) + } + } + + const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + // Update values in Oracles increase by 10% + const allFeeds = await getFeeds(ctx.collateral) + const initialPrices = await Promise.all(allFeeds.map((f) => f.latestRoundData())) + for (const [i, feed] of allFeeds.entries()) { + const nextAnswer = initialPrices[i].answer.add( + initialPrices[i].answer.mul(pctIncrease).div(100) + ) + await feed.updateAnswer(nextAnswer) + } + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} + + const collateralSpecificStatusTests = () => { + it('prices change as feed price changes', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const feed0 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const feed1 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const coll = await deployCollateral({ + pool: curr.pool, + gauge: curr.gauge, + feeds: [[feed0.address], [feed1.address]], + }) + + const initialRefPerTok = await coll.refPerTok() + const [low, high] = await coll.price() + + // Update values in Oracles increase by 10% + const allFeeds = await getFeeds(coll) + const initialPrices = await Promise.all(allFeeds.map((f) => f.latestRoundData())) + for (const [i, feed] of allFeeds.entries()) { + await feed.updateAnswer(initialPrices[i].answer.mul(110).div(100)).then((e) => e.wait()) + } + + const [newLow, newHigh] = await coll.price() + + // with 18 decimals of price precision a 1e-9 tolerance seems fine for a 10% change + // and without this kind of tolerance the Volatile pool tests fail due to small movements + expect(newLow).to.be.closeTo(low.mul(110).div(100), fp('1e-9')) + expect(newHigh).to.be.closeTo(high.mul(110).div(100), fp('1e-9')) + + // Check refPerTok remains the same + const finalRefPerTok = await coll.refPerTok() + expect(finalRefPerTok).to.equal(initialRefPerTok) + }) + + it('prices change as targetPerRef changes', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const feed0 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const feed1 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const coll = await deployCollateral({ + pool: curr.pool, + gauge: curr.gauge, + feeds: [[feed0.address], [feed1.address]], + }) + + const tok = await ethers.getContractAt('IERC20Metadata', await coll.erc20()) + const tempCtx = { collateral: coll, chainlinkFeed: feed0, tok } + + const oracleError = await coll.oracleError() + const expectedPrice = await getExpectedPrice(tempCtx) + await expectPrice(coll.address, expectedPrice, oracleError, true, curr.toleranceDivisor) + + // Get refPerTok initial values + const initialRefPerTok = await coll.refPerTok() + const [oldLow, oldHigh] = await coll.price() + + // Update values in Oracles increase by 10-20% + await increaseTargetPerRef(tempCtx, 20) + + // Check new prices -- increase expected + const newPrice = await getExpectedPrice(tempCtx) + await expectPrice(coll.address, newPrice, oracleError, true, curr.toleranceDivisor) + const [newLow, newHigh] = await coll.price() + expect(oldLow).to.be.lt(newLow) + expect(oldHigh).to.be.lt(newHigh) + + // Check refPerTok remains the same (because we have not refreshed) + const finalRefPerTok = await coll.refPerTok() + expect(finalRefPerTok).to.equal(initialRefPerTok) + }) + + // eslint-disable-next-line @typescript-eslint/no-empty-function + it.skip('prices change as refPerTok changes', async () => {}) + } + + const getExpectedPrice = async (ctx: CollateralFixtureContext) => { + // TODO: Improve use both feeds + const initRefPerTok = await ctx.collateral.refPerTok() + const decimals = await ctx.chainlinkFeed.decimals() + const initData = await ctx.chainlinkFeed.latestRoundData() + return initData.answer + .mul(bn(10).pow(18 - decimals)) + .mul(initRefPerTok) + .div(fp('1')) + } + + /* + Run the test suite + */ + + const emptyFn = () => { + return + } + + const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest: emptyFn, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it, + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it.skip, + itChecksPriceChanges: it.skip, + itChecksNonZeroDefaultThreshold: it, + itHasRevenueHiding: it.skip, + resetFork, + collateralName: curr.testName, + chainlinkDefaultAnswer: bn('1e8'), + itIsPricedByPeg: true, + toleranceDivisor: curr.toleranceDivisor, + amountScaleDivisor: curr.amountScaleDivisor, + targetNetwork: 'base', + } + + collateralTests(opts) +}) diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts index 720b5f1fc..7c468868b 100644 --- a/test/plugins/individual-collateral/aerodrome/constants.ts +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -25,10 +25,11 @@ export const eUSD_ORACLE_TIMEOUT = bn('86400') export const eUSD_ORACLE_ERROR = fp('0.005') export const eUSD_HOLDER = '0xb5E331615FdbA7DF49e05CdEACEb14Acdd5091c3' -export const FORK_BLOCK = 19074000 +export const FORK_BLOCK = 19074500 // Common export const FIX_ONE = 1n * 10n ** 18n +export const ORACLE_ERROR = fp('0.005') export const PRICE_TIMEOUT = bn('604800') // 1 week export const DEFAULT_THRESHOLD = fp('0.02') // 2% export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h diff --git a/test/plugins/individual-collateral/aerodrome/helpers.ts b/test/plugins/individual-collateral/aerodrome/helpers.ts index 266515a55..5a2de4312 100644 --- a/test/plugins/individual-collateral/aerodrome/helpers.ts +++ b/test/plugins/individual-collateral/aerodrome/helpers.ts @@ -4,6 +4,8 @@ import { IAeroGauge, AerodromeGaugeWrapper__factory, AerodromeGaugeWrapper, + TestICollateral, + MockV3Aggregator, } from '@typechain/index' import { ethers } from 'hardhat' import { @@ -15,7 +17,9 @@ import { FORK_BLOCK, } from './constants' import { getResetFork } from '../helpers' +import { pushOracleForward } from '../../../utils/oracles' import { whileImpersonating } from '#/test/utils/impersonation' +import { ZERO_ADDRESS } from '#/common/constants' interface WrappedAeroFixture { token0: ERC20Mock @@ -73,4 +77,27 @@ export const mintWrappedLpToken = async ( await wrapper.connect(user).deposit(amount, recipient) } +export const getFeeds = async (coll: TestICollateral): Promise => { + const aeroStableColl = await ethers.getContractAt('AerodromeStableCollateral', coll.address) + + const feedAddrs = (await aeroStableColl.tokenFeeds(0)).concat(await aeroStableColl.tokenFeeds(1)) + const feeds: MockV3Aggregator[] = [] + + for (const feedAddr of feedAddrs) { + if (feedAddr != ZERO_ADDRESS) { + const oracle = await ethers.getContractAt('MockV3Aggregator', feedAddr) + feeds.push(oracle) + } + } + + return feeds +} + +export const pushAllFeedsForward = async (coll: TestICollateral) => { + const feeds = await getFeeds(coll) + for (const oracle of feeds) { + await pushOracleForward(oracle.address) + } +} + export const resetFork = getResetFork(FORK_BLOCK) From 6b7be0e5edb50f93b393bad5b2bed2ccfa33ab9f Mon Sep 17 00:00:00 2001 From: Julian R Date: Thu, 12 Sep 2024 11:51:49 -0300 Subject: [PATCH 04/42] apply correct k formula for stable pools --- .../plugins/assets/aerodrome/AerodromePoolTokens.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 426855954..06a7d25c2 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -26,6 +26,7 @@ contract AerodromePoolTokens { // === State (Immutable) === IAeroPool public immutable pool; + AeroPoolType public immutable poolType; IERC20Metadata internal immutable token0; IERC20Metadata internal immutable token1; @@ -66,6 +67,7 @@ contract AerodromePoolTokens { require(address(config.pool) != address(0), "pool address is zero"); pool = config.pool; + poolType = config.poolType; // Solidity does not support immutable arrays. This is a hack to get the equivalent of // an immutable array so we do not have store the token feeds in the blockchain. This is @@ -181,8 +183,15 @@ contract AerodromePoolTokens { } function sqrtK() public view virtual returns (uint192) { + // x3y+y3x >= k for sAMM pools + // xy >= k for vAMM pools uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); + if (poolType == AeroPoolType.Stable) { + uint192 _a = r0.mul(r1); + uint192 _b = (r0.mul(r0)).plus(r1.mul(r1)); + return _a.mul(_b).sqrt(); + } return r0.mul(r1).sqrt(); } From f63fb4ed7851cb4921cd709bde15a377ffe4c768 Mon Sep 17 00:00:00 2001 From: Julian R Date: Thu, 19 Sep 2024 10:27:35 -0300 Subject: [PATCH 05/42] temp testing changes --- .../individual-collateral/collateralTests.ts | 23 ++++++++++++++----- .../individual-collateral/pluginTestTypes.ts | 3 +++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 8dbce3ee9..394588283 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -93,6 +93,7 @@ export default function fn( resetFork, collateralName, chainlinkDefaultAnswer, + amountScaleDivisor, toleranceDivisor, targetNetwork, } = fixtures @@ -172,7 +173,8 @@ export default function fn( describe('functions', () => { it('returns the correct bal (18 decimals)', async () => { const decimals = await ctx.tok.decimals() - const amount = bn('20').mul(bn(10).pow(decimals)) + const scaleDivisor = amountScaleDivisor ?? bn(1) + const amount = bn('20').mul(bn(10).pow(decimals)).div(scaleDivisor) await mintCollateralTo(ctx, amount, alice, alice.address) const aliceBal = await collateral.bal(alice.address) @@ -195,7 +197,10 @@ export default function fn( }) itClaimsRewards('claims rewards (via collateral.claimRewards())', async () => { - const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) + const scaleDivisor = amountScaleDivisor ?? bn(1) + const amount = bn('20') + .mul(bn(10).pow(await ctx.tok.decimals())) + .div(scaleDivisor) await mintCollateralTo(ctx, amount, alice, ctx.collateral.address) await advanceBlocks(1000) await advanceToTimestamp((await getLatestBlockTimestamp()) + 12000) @@ -626,7 +631,7 @@ export default function fn( }) }) - describe('integration tests', () => { + describe.only('integration tests', () => { const onBase = useEnv('FORK_NETWORK').toLowerCase() == 'base' const onArbitrum = useEnv('FORK_NETWORK').toLowerCase() == 'arbitrum' @@ -662,6 +667,8 @@ export default function fn( let govParams: IGovParams let govRoles: IGovRoles + let scaleDivisor: BigNumber + const config = { dist: { rTokenDist: bn(0), // 0% RToken @@ -715,6 +722,7 @@ export default function fn( throw new Error(`Missing network configuration for ${hre.network.name}`) } ;[, owner, addr1] = await ethers.getSigners() + scaleDivisor = amountScaleDivisor ?? bn(1) }) beforeEach(async () => { @@ -735,7 +743,7 @@ export default function fn( collateralERC20 = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) await mintCollateralTo( ctx, - toBNDecimals(fp('1'), await collateralERC20.decimals()), + toBNDecimals(fp('1').div(scaleDivisor), await collateralERC20.decimals()), addr1, addr1.address ) @@ -823,7 +831,10 @@ export default function fn( it('redeems', async () => { await rToken.connect(addr1).redeem(supply) expect(await rToken.totalSupply()).to.equal(0) - const initialCollBal = toBNDecimals(fp('1'), await collateralERC20.decimals()) + const initialCollBal = toBNDecimals( + fp('1').div(scaleDivisor), + await collateralERC20.decimals() + ) expect(await collateralERC20.balanceOf(addr1.address)).to.be.closeTo( initialCollBal, initialCollBal.div(bn('1e5')) // 1-part-in-100k @@ -867,7 +878,7 @@ export default function fn( const router = await (await ethers.getContractFactory('DutchTradeRouter')).deploy() await rsr.connect(addr1).approve(router.address, MAX_UINT256) // Send excess collateral to the RToken trader via forwardRevenue() - let mintAmt = toBNDecimals(fp('1e-6'), await collateralERC20.decimals()) + let mintAmt = toBNDecimals(fp('1e-6'), await collateralERC20.decimals()).div(scaleDivisor) mintAmt = mintAmt.gt('100000') ? mintAmt : bn('100000') // fewest tokens distributor will transfer await mintCollateralTo(ctx, mintAmt, addr1, backingManager.address) await backingManager.forwardRevenue([collateralERC20.address]) diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index aa70a23c1..837e5a258 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -121,6 +121,9 @@ export interface CollateralTestSuiteFixtures // the default answer that will come from the chainlink feed after deployment chainlinkDefaultAnswer: BigNumberish + // the scale divisor that will be used for amounts in tests + amountScaleDivisor?: BigNumber + // the default tolerance divisor that will be used in expectPrice checks toleranceDivisor?: BigNumber From 4ca20a4ff757a3c3722365bb6c7194b632ed53fd Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 20 Sep 2024 13:34:16 -0300 Subject: [PATCH 06/42] implement new function and set debugging --- .../assets/aerodrome/AerodromePoolTokens.sol | 13 -- .../aerodrome/AerodromeStableCollateral.sol | 125 ++++++++++-------- .../AerodromeStableCollateral.test.ts | 27 ++-- .../individual-collateral/collateralTests.ts | 2 +- 4 files changed, 86 insertions(+), 81 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 06a7d25c2..001422fe3 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -182,19 +182,6 @@ contract AerodromePoolTokens { return pool.reserve1(); } - function sqrtK() public view virtual returns (uint192) { - // x3y+y3x >= k for sAMM pools - // xy >= k for vAMM pools - uint192 r0 = shiftl_toFix(tokenReserve(0), -int8(token0.decimals()), FLOOR); - uint192 r1 = shiftl_toFix(tokenReserve(1), -int8(token1.decimals()), FLOOR); - if (poolType == AeroPoolType.Stable) { - uint192 _a = r0.mul(r1); - uint192 _b = (r0.mul(r0)).plus(r1.mul(r1)); - return _a.mul(_b).sqrt(); - } - return r0.mul(r1).sqrt(); - } - /// @param index The index of the token: 0 or 1 /// @return [address of chainlink feeds] function tokenFeeds(uint8 index) public view virtual returns (AggregatorV3Interface[] memory) { diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 6b0b0674c..d9c24d0e7 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -6,10 +6,12 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "contracts/interfaces/IAsset.sol"; import "contracts/libraries/Fixed.sol"; -import "contracts/plugins/assets/AppreciatingFiatCollateral.sol"; +import "contracts/plugins/assets/FiatCollateral.sol"; import "../../../interfaces/IRewardable.sol"; import "./AerodromePoolTokens.sol"; +import "hardhat/console.sol"; + // This plugin only works on Base IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); @@ -19,12 +21,12 @@ IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); * Each token in the pool can have between 1 and 2 oracles per each token. * * tok = AerodromeStakingWrapper(stablePool) - * ref = 1e18 (fixed) + * ref = toFix(2) * tar = USD * UoA = USD * */ -contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolTokens { +contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -33,9 +35,8 @@ contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolT /// @dev config.erc20 should be an AerodromeStakingWrapper constructor( CollateralConfig memory config, - uint192 revenueHiding, APTConfiguration memory aptConfig - ) AppreciatingFiatCollateral(config, revenueHiding) AerodromePoolTokens(aptConfig) { + ) FiatCollateral(config) AerodromePoolTokens(aptConfig) { require(config.defaultThreshold != 0, "defaultThreshold zero"); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } @@ -52,21 +53,50 @@ contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolT view virtual override - returns ( - uint192 low, - uint192 high, - uint192 pegPrice - ) + returns (uint192 low, uint192 high, uint192 pegPrice) { + uint256 r0 = tokenReserve(0); + uint256 r1 = tokenReserve(1); + + console.log("reserves0: %s", r0); + console.log("reserves1: %s", r1); + + // x3y+y3x >= k for sAMM pools + uint256 sqrtReserve = sqrt256(sqrt256(r0 * r1) * sqrt256(r0 * r0 + r1 * r1)); + + console.log("sqrtReserve: %s", sqrtReserve); + // get token prices (uint192 p0_low, uint192 p0_high) = tokenPrice(0); (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + console.log("p0_low: %s", p0_low); + console.log("p0_high: %s", p0_high); + console.log("p1_low: %s", p1_low); + console.log("p1_high: %s", p1_high); + uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); - // {UoA/tok} - low = sqrtK().mulu(2).mulDiv(p0_low.mul(p1_low).sqrt(), totalSupply); - high = sqrtK().mulu(2).mulDiv(p0_high.mul(p1_high).sqrt(), totalSupply); + console.log("total supply raw: %s", pool.totalSupply()); + console.log("total supply: %s", totalSupply); + + // low + uint256 ratioLow = ((1e18) * p0_low) / p1_low; + uint256 sqrtPriceLow = sqrt256( + sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) + ); + low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); + + console.log("low: %s", low); + + // high + uint256 ratioHigh = ((1e18) * p0_high) / p1_high; + uint256 sqrtPriceHigh = sqrt256( + sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) + ); + high = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply); + + console.log("high: %s", high); assert(low <= high); //obviously true just by inspection pegPrice = FIX_ONE; @@ -78,53 +108,35 @@ contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolT function refresh() public virtual override { CollateralStatus oldStatus = status(); - // Check for hard default - try this.underlyingRefPerTok() returns (uint192 underlyingRefPerTok_) { - // {ref/tok} = {ref/tok} * {1} - uint192 hiddenReferencePrice = underlyingRefPerTok_.mul(revenueShowing); - - // uint192(<) is equivalent to Fix.lt - if (underlyingRefPerTok_ < exposedReferencePrice) { - exposedReferencePrice = underlyingRefPerTok_; - markStatus(CollateralStatus.DISABLED); - } else if (hiddenReferencePrice > exposedReferencePrice) { - exposedReferencePrice = hiddenReferencePrice; + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {UoA/tok} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high != FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + savedPegPrice = pegPrice; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + // untested: + // validated in other plugins, cost to test here is high + assert(low == 0); } - // Check for soft default + save prices - try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { - // {UoA/tok}, {UoA/tok}, {UoA/tok} - // (0, 0) is a valid price; (0, FIX_MAX) is unpriced - - // Save prices if priced - if (high != FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - savedPegPrice = pegPrice; - lastSave = uint48(block.timestamp); - } else { - // must be unpriced - // untested: - // validated in other plugins, cost to test here is high - assert(low == 0); - } - - // If the price is below the default-threshold price, default eventually - // uint192(+/-) is the same as Fix.plus/minus - if (low == 0 || _anyDepeggedInPool()) { - markStatus(CollateralStatus.IFFY); - } else { - markStatus(CollateralStatus.SOUND); - } - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (low == 0 || _anyDepeggedInPool()) { markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string - markStatus(CollateralStatus.DISABLED); + markStatus(CollateralStatus.IFFY); } CollateralStatus newStatus = status(); @@ -142,9 +154,10 @@ contract AerodromeStableCollateral is AppreciatingFiatCollateral, AerodromePoolT } /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens - function underlyingRefPerTok() public view virtual override returns (uint192) { - uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); - return sqrtK().mulu(2).mulDiv(FIX_ONE, totalSupply); + function refPerTok() public view virtual override returns (uint192) { + // TODO: Review case of negative offset + uint8 decimalOffset = token0.decimals() + token1.decimals() - 18; + return toFix(2).mulu(10 ** decimalOffset); } // === Internal === diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index 99fe43794..5032b189c 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -175,7 +175,6 @@ all.forEach((curr: AeroStablePoolEnumeration) => { defaultThreshold: opts.defaultThreshold, delayUntilDefault: opts.delayUntilDefault, }, - bn(0), { pool: opts.pool, poolType: opts.poolType, @@ -334,7 +333,6 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const [newLow, newHigh] = await coll.price() // with 18 decimals of price precision a 1e-9 tolerance seems fine for a 10% change - // and without this kind of tolerance the Volatile pool tests fail due to small movements expect(newLow).to.be.closeTo(low.mul(110).div(100), fp('1e-9')) expect(newHigh).to.be.closeTo(high.mul(110).div(100), fp('1e-9')) @@ -375,22 +373,29 @@ all.forEach((curr: AeroStablePoolEnumeration) => { expect(oldLow).to.be.lt(newLow) expect(oldHigh).to.be.lt(newHigh) - // Check refPerTok remains the same (because we have not refreshed) + // Check refPerTok remains the same const finalRefPerTok = await coll.refPerTok() expect(finalRefPerTok).to.equal(initialRefPerTok) }) - - // eslint-disable-next-line @typescript-eslint/no-empty-function - it.skip('prices change as refPerTok changes', async () => {}) } const getExpectedPrice = async (ctx: CollateralFixtureContext) => { - // TODO: Improve use both feeds const initRefPerTok = await ctx.collateral.refPerTok() - const decimals = await ctx.chainlinkFeed.decimals() - const initData = await ctx.chainlinkFeed.latestRoundData() - return initData.answer - .mul(bn(10).pow(18 - decimals)) + const coll = await ethers.getContractAt('AerodromeStableCollateral', ctx.collateral.address) + + const feed0 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(0))[0]) + const decimals0 = await feed0.decimals() + const initData0 = await feed0.latestRoundData() + + const feed1 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(1))[0]) + const decimals1 = await feed1.decimals() + const initData1 = await feed1.latestRoundData() + + return initData0.answer + .mul(bn(10).pow(18 - decimals0)) + .mul(initData1.answer) + .mul(bn(10).pow(18 - decimals1)) + .div(fp('1')) .mul(initRefPerTok) .div(fp('1')) } diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 394588283..fa7ae3a1e 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -631,7 +631,7 @@ export default function fn( }) }) - describe.only('integration tests', () => { + describe('integration tests', () => { const onBase = useEnv('FORK_NETWORK').toLowerCase() == 'base' const onArbitrum = useEnv('FORK_NETWORK').toLowerCase() == 'arbitrum' From 00664266dbb5d3de37df47603e05a79fd7d4a919 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 20 Sep 2024 23:37:56 +0530 Subject: [PATCH 07/42] Tiny change --- .../assets/aerodrome/AerodromePoolTokens.sol | 8 +++++-- .../aerodrome/AerodromeStableCollateral.sol | 24 +++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 001422fe3..15bb4f15b 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -178,8 +178,12 @@ contract AerodromePoolTokens { /// @return [{ref_index}] function tokenReserve(uint8 index) public view virtual returns (uint256) { if (index >= nTokens) revert WrongIndex(nTokens - 1); - if (index == 0) return pool.reserve0(); - return pool.reserve1(); + // Maybe also cache token decimals as immutable? + IERC20Metadata tokenInterface = getToken(index); + if (index == 0) { + return shiftl_toFix(pool.reserve0(), -int8(tokenInterface.decimals()), FLOOR); + } + return shiftl_toFix(pool.reserve1(), -int8(tokenInterface.decimals()), FLOOR); } /// @param index The index of the token: 0 or 1 diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index d9c24d0e7..047f3fff1 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -33,10 +33,10 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout /// @dev No revenue hiding (refPerTok() == FIX_ONE) /// @dev config.erc20 should be an AerodromeStakingWrapper - constructor( - CollateralConfig memory config, - APTConfiguration memory aptConfig - ) FiatCollateral(config) AerodromePoolTokens(aptConfig) { + constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) + FiatCollateral(config) + AerodromePoolTokens(aptConfig) + { require(config.defaultThreshold != 0, "defaultThreshold zero"); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } @@ -53,7 +53,11 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { view virtual override - returns (uint192 low, uint192 high, uint192 pegPrice) + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) { uint256 r0 = tokenReserve(0); uint256 r1 = tokenReserve(1); @@ -80,12 +84,12 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { console.log("total supply raw: %s", pool.totalSupply()); console.log("total supply: %s", totalSupply); - // low - uint256 ratioLow = ((1e18) * p0_low) / p1_low; - uint256 sqrtPriceLow = sqrt256( + // low + uint256 ratioLow = ((1e18) * p0_low) / p1_low; + uint256 sqrtPriceLow = sqrt256( sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) ); - low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); + low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); console.log("low: %s", low); @@ -157,7 +161,7 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { function refPerTok() public view virtual override returns (uint192) { // TODO: Review case of negative offset uint8 decimalOffset = token0.decimals() + token1.decimals() - 18; - return toFix(2).mulu(10 ** decimalOffset); + return toFix(2).mulu(10**decimalOffset); } // === Internal === From 5870acdcc22dc4e7128db1bf5925540a92a8487c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 20 Sep 2024 21:59:09 -0400 Subject: [PATCH 08/42] refPerTok() --- .../assets/aerodrome/AerodromeStableCollateral.sol | 12 +++++++----- .../aerodrome/AerodromeStableCollateral.test.ts | 9 ++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 047f3fff1..bd812c8d5 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -21,7 +21,7 @@ IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); * Each token in the pool can have between 1 and 2 oracles per each token. * * tok = AerodromeStakingWrapper(stablePool) - * ref = toFix(2) + * ref = LP token /w shift * tar = USD * UoA = USD * @@ -38,6 +38,7 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { AerodromePoolTokens(aptConfig) { require(config.defaultThreshold != 0, "defaultThreshold zero"); + assert((token0.decimals() + token1.decimals()) % 2 == 0); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } @@ -65,7 +66,7 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { console.log("reserves0: %s", r0); console.log("reserves1: %s", r1); - // x3y+y3x >= k for sAMM pools + // xy^3 + yx^3 >= k for sAMM pools uint256 sqrtReserve = sqrt256(sqrt256(r0 * r1) * sqrt256(r0 * r0 + r1 * r1)); console.log("sqrtReserve: %s", sqrtReserve); @@ -104,6 +105,8 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { assert(low <= high); //obviously true just by inspection pegPrice = FIX_ONE; + + console.log("refPerTok: %s", refPerTok()); } /// Should not revert @@ -159,9 +162,8 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function refPerTok() public view virtual override returns (uint192) { - // TODO: Review case of negative offset - uint8 decimalOffset = token0.decimals() + token1.decimals() - 18; - return toFix(2).mulu(10**decimalOffset); + int8 shift = 18 - int8((token0.decimals() + token1.decimals()) / 2); + return shiftl_toFix(2, shift, FLOOR); } // === Internal === diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index 5032b189c..d8e01e80a 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -1,7 +1,7 @@ import collateralTests from '../collateralTests' import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' import { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' import { IAeroPool, MockV3Aggregator, @@ -40,7 +40,6 @@ import { } from './constants' import { expectPrice } from '../../../utils/oracles' import { mintWrappedLpToken, resetFork, getFeeds, pushAllFeedsForward } from './helpers' -import BigNumber from 'bignumber.js' /* Define interfaces @@ -84,7 +83,7 @@ interface AerodromeCollateralFixtureContext extends CollateralFixtureContext { const config = networkConfig['8453'] // use Base fork // Test all Aerodrome Stable pools -const all = [ +const all: AeroStablePoolEnumeration[] = [ { testName: 'Aerodrome - USDC/eUSD Stable', pool: AERO_USDC_eUSD_POOL, @@ -383,11 +382,11 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const initRefPerTok = await ctx.collateral.refPerTok() const coll = await ethers.getContractAt('AerodromeStableCollateral', ctx.collateral.address) - const feed0 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(0))[0]) + const feed0 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(0))[0]) const decimals0 = await feed0.decimals() const initData0 = await feed0.latestRoundData() - const feed1 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(1))[0]) + const feed1 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(1))[0]) const decimals1 = await feed1.decimals() const initData1 = await feed1.latestRoundData() From 3dd2b9d66d5a595747564af3afd222b1d8136d04 Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 23 Sep 2024 09:25:37 -0300 Subject: [PATCH 09/42] final test and script adjustments --- .../aerodrome/AerodromeStableCollateral.sol | 21 ------------------- .../collaterals/deploy_aerodrome_usdc_eusd.ts | 3 +-- .../verify_aerodrome_usdc_eusd.ts | 3 +-- .../AerodromeStableCollateral.test.ts | 11 +++++----- 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index bd812c8d5..bf7a03b45 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -10,8 +10,6 @@ import "contracts/plugins/assets/FiatCollateral.sol"; import "../../../interfaces/IRewardable.sol"; import "./AerodromePoolTokens.sol"; -import "hardhat/console.sol"; - // This plugin only works on Base IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); @@ -63,28 +61,15 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { uint256 r0 = tokenReserve(0); uint256 r1 = tokenReserve(1); - console.log("reserves0: %s", r0); - console.log("reserves1: %s", r1); - // xy^3 + yx^3 >= k for sAMM pools uint256 sqrtReserve = sqrt256(sqrt256(r0 * r1) * sqrt256(r0 * r0 + r1 * r1)); - console.log("sqrtReserve: %s", sqrtReserve); - // get token prices (uint192 p0_low, uint192 p0_high) = tokenPrice(0); (uint192 p1_low, uint192 p1_high) = tokenPrice(1); - console.log("p0_low: %s", p0_low); - console.log("p0_high: %s", p0_high); - console.log("p1_low: %s", p1_low); - console.log("p1_high: %s", p1_high); - uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); - console.log("total supply raw: %s", pool.totalSupply()); - console.log("total supply: %s", totalSupply); - // low uint256 ratioLow = ((1e18) * p0_low) / p1_low; uint256 sqrtPriceLow = sqrt256( @@ -92,8 +77,6 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { ); low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); - console.log("low: %s", low); - // high uint256 ratioHigh = ((1e18) * p0_high) / p1_high; uint256 sqrtPriceHigh = sqrt256( @@ -101,12 +84,8 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { ); high = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply); - console.log("high: %s", high); - assert(low <= high); //obviously true just by inspection pegPrice = FIX_ONE; - - console.log("refPerTok: %s", refPerTok()); } /// Should not revert diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts index a0b866a57..31b39c810 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts @@ -12,7 +12,7 @@ import { fileExists, } from '../../common' import { AerodromeStableCollateral, AerodromeGaugeWrapper, IAeroPool } from '../../../../typechain' -import { combinedError, revenueHiding } from '../../utils' +import { combinedError } from '../../utils' import { AerodromePoolType, DEFAULT_THRESHOLD, @@ -101,7 +101,6 @@ async function main() { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - revenueHiding.toString(), { pool: AERO_USDC_eUSD_POOL, poolType: AerodromePoolType.Stable, diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts b/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts index 26a0f81d2..0c0f43485 100644 --- a/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts +++ b/scripts/verification/collateral-plugins/verify_aerodrome_usdc_eusd.ts @@ -8,7 +8,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { combinedError, revenueHiding } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' import { IAeroPool } from '@typechain/IAeroPool' import { AerodromePoolType, @@ -83,7 +83,6 @@ async function main() { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - revenueHiding.toString(), { pool: AERO_USDC_eUSD_POOL, poolType: AerodromePoolType.Stable, diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index d8e01e80a..2727abd94 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -390,11 +390,12 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const decimals1 = await feed1.decimals() const initData1 = await feed1.latestRoundData() - return initData0.answer - .mul(bn(10).pow(18 - decimals0)) - .mul(initData1.answer) - .mul(bn(10).pow(18 - decimals1)) - .div(fp('1')) + const avgPrice = (initData0.answer + .mul(bn(10).pow(18 - decimals0))) + .add(initData1.answer.mul(bn(10).pow(18 - decimals1))) + .div(2) + + return avgPrice .mul(initRefPerTok) .div(fp('1')) } From 3cdb40b0cc3082e8e3e078b1ae0e3d596612ba03 Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 23 Sep 2024 09:27:32 -0300 Subject: [PATCH 10/42] fix lint --- .../aerodrome/AerodromeStableCollateral.test.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index 2727abd94..109191cdb 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -390,14 +390,12 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const decimals1 = await feed1.decimals() const initData1 = await feed1.latestRoundData() - const avgPrice = (initData0.answer - .mul(bn(10).pow(18 - decimals0))) - .add(initData1.answer.mul(bn(10).pow(18 - decimals1))) - .div(2) - - return avgPrice - .mul(initRefPerTok) - .div(fp('1')) + const avgPrice = initData0.answer + .mul(bn(10).pow(18 - decimals0)) + .add(initData1.answer.mul(bn(10).pow(18 - decimals1))) + .div(2) + + return avgPrice.mul(initRefPerTok).div(fp('1')) } /* From ed650aaae772c217204c807332d012bd43f20975 Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 23 Sep 2024 09:48:38 -0300 Subject: [PATCH 11/42] add readme --- contracts/plugins/assets/aerodrome/README.md | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 contracts/plugins/assets/aerodrome/README.md diff --git a/contracts/plugins/assets/aerodrome/README.md b/contracts/plugins/assets/aerodrome/README.md new file mode 100644 index 000000000..82cf963aa --- /dev/null +++ b/contracts/plugins/assets/aerodrome/README.md @@ -0,0 +1,45 @@ +# Aerodrome Stable Collateral Plugin + +[Aerodrome Finance](https://aerodrome.finance) is an AMM designed to serve as Base's central liquidity hub. This plugin enables the use of any Aerodrome Stable LP token as collateral within the Reserve Protocol. + +Aerodrome Finance offers two different liquidity pool types based on token pair needs, `Stable Pools` and `Volatile Pools`. + +Only `Stable Pools` are currently supported. These pools are designed for tokens which have little to no volatility, and use the current formula for pricing tokens: + +`x³y + y³x ≥ k` + +## Usage + +### Number of Tokens in The Pool + +All Aerodrome Pools are designed to support `2 (two)` tokens. So this field is harcoded and not provided as a configuration deployment parameter. + +### Multiple Price Feeds + +Some tokens require multiple price feeds since they do not have a direct price feed to USD. One example of this is WBTC. To support this, the plugin accepts a `tokensPriceFeeds` field in the configuration deployment parameter. This data structure is a `address[][]` and should have the same length as the number of coins in the Pool. The indices of these price feeds should also match the indices of the tokens in the pool. For example, if I am deploying a collateral plugin for the USDC/EUSD, I would need to pass something like `[[USDC_USD_FEED_ADDR], [EUSD_USD_FEED_ADDR]]` as `tokensPriceFeeds`. Since USDC has an index of 0 in the Aerodrome USDC/eUSD pool, the USDC price feed should be in index 0 in `tokensPriceFeeds`. + +### Wrapped Stake Token + +Since the Aerodrome LP Token needs to be staked in the Gauge to get rewards in AERO, we need to wrap it in another ERC20-token. This repo includes an `AerodromeGaugeStakingWrapper` contract that needs to be deployed and its address passed as the `erc20` configuration parameter. + +### Rewards + +Rewards come in the form of AERO tokens, which will be distributed once `claimRewards()` is called. + +AERO token: `https://basescan.org/token/0x940181a94a35a4569e4529a3cdfb74e38fd98631` + +## Implementation Notes + +### Immutable Arrays for Price Feeds + +Internally, all `tokensPriceFeeds` are stored as multiple separate immutable variables instead of just one array-type state variable for each. This is a gas-optimization done to avoid using SSTORE/SLOAD opcodes which are necessary but expensive operations when using state variables. Immutable variables, on the other hand, are embedded in the bytecode and are much cheaper to use which leads to more gas-efficient `price`, `strictPrice` and `refresh` functions. This work-around is necessary since Solidity does not yet support immutable arrays. + +### refPerTok + +Aerodrome Stable Pools do not appreciate in value over time, so `refPerTok()` will be constant for these plugins and will not change. This also means there are no hard default checks in place. + +## Implementation + +| `tok` | `ref` | `target` | `UoA` | +| :------------------: | :---------------: | :------: | :---: | +| Aero Staking Wrapper | LP token /w shift | USD | USD | From 773ed0d3f1d24dfe70255ee1643d2f5fd0db875f Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 23 Sep 2024 10:01:53 -0300 Subject: [PATCH 12/42] add aerodrome test in ci --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9234f2717..b29512609 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -159,7 +159,7 @@ jobs: restore-keys: | hardhat-network-fork-${{ runner.os }}- hardhat-network-fork- - - run: yarn hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,compoundv3,stargate,lido}/*.test.ts + - run: yarn hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,aerodrome,compoundv3,stargate,lido}/*.test.ts env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true From 4802b4d8d6c6e787c013e142fea4c824eb18a426 Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 23 Sep 2024 11:31:01 -0300 Subject: [PATCH 13/42] avoid gauge test on mainnet --- .../aerodrome/AerodromeGaugeWrapper.test.ts | 3 ++- test/plugins/individual-collateral/aerodrome/constants.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts index 954656fd8..652aa034f 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeGaugeWrapper.test.ts @@ -14,6 +14,7 @@ import { import { expect } from 'chai' import { ZERO_ADDRESS } from '#/common/constants' import { + forkNetwork, AERO, eUSD, AERO_USDC_eUSD_GAUGE, @@ -24,7 +25,7 @@ import { bn, fp } from '#/common/numbers' import { getChainId } from '#/common/blockchain-utils' import { advanceTime } from '#/test/utils/time' -const describeFork = useEnv('FORK') ? describe : describe.skip +const describeFork = useEnv('FORK') && forkNetwork == 'base' ? describe : describe.skip const point1Pct = (value: BigNumber): BigNumber => { return value.div(1000) diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts index 7c468868b..d8a6e7dad 100644 --- a/test/plugins/individual-collateral/aerodrome/constants.ts +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -1,5 +1,8 @@ import { bn, fp } from '../../../../common/numbers' import { networkConfig } from '../../../../common/configuration' +import { useEnv } from '#/utils/env' + +export const forkNetwork = useEnv('FORK_NETWORK') ?? 'base' // Base Addresses export const AERO_USDC_eUSD_GAUGE = '0x793F22aB88dC91793E5Ce6ADbd7E733B0BD4733e' From 4475c31becceaa4c18935ca1fb0ea84dd1dcef60 Mon Sep 17 00:00:00 2001 From: Julian R Date: Tue, 1 Oct 2024 10:37:20 -0300 Subject: [PATCH 14/42] add aero to config --- common/configuration.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/configuration.ts b/common/configuration.ts index 58be27aed..c1140aeaa 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -116,6 +116,9 @@ export interface ITokens { // Mountain USDM?: string wUSDM?: string + + // Aerodrome + AERO?: string } export type ITokensKeys = Array From f974bf33de4ba497884848b2464a48b092e98944 Mon Sep 17 00:00:00 2001 From: Julian R Date: Tue, 1 Oct 2024 10:37:46 -0300 Subject: [PATCH 15/42] add check for stable pools --- contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 15bb4f15b..882b32ee3 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -76,7 +76,7 @@ contract AerodromePoolTokens { // === Tokens === - if (config.poolType != AeroPoolType.Stable) { + if (config.poolType != AeroPoolType.Stable || !config.pool.stable()) { revert("invalid poolType"); } From 49b0216d5f3352b3bdc756b3d0e2fab94ccfd326 Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 4 Oct 2024 11:24:19 -0300 Subject: [PATCH 16/42] change pegPrice --- .../aerodrome/AerodromeStableCollateral.sol | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index bf7a03b45..57f5303bc 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -71,21 +71,29 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); // low - uint256 ratioLow = ((1e18) * p0_low) / p1_low; - uint256 sqrtPriceLow = sqrt256( - sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) - ); - low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); - + { + uint256 ratioLow = ((1e18) * p0_low) / p1_low; + uint256 sqrtPriceLow = sqrt256( + sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) + ); + low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); + } // high - uint256 ratioHigh = ((1e18) * p0_high) / p1_high; - uint256 sqrtPriceHigh = sqrt256( - sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) - ); - high = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply); - + { + uint256 ratioHigh = ((1e18) * p0_high) / p1_high; + uint256 sqrtPriceHigh = sqrt256( + sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) + ); + + high = _safeWrap( + ((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply + ); + } assert(low <= high); //obviously true just by inspection - pegPrice = FIX_ONE; + + // {target/ref} = {UoA/ref} = {UoA/tok} / ({ref/tok} + // {target/ref} and {UoA/ref} are the same since target == UoA + pegPrice = ((low + high) / 2).div(refPerTok()); } /// Should not revert From 434145d198bc6a80d0f8ed9d161ccf184d21d1ae Mon Sep 17 00:00:00 2001 From: Julian R Date: Mon, 7 Oct 2024 08:27:05 -0300 Subject: [PATCH 17/42] low high fix --- .../plugins/assets/aerodrome/AerodromeStableCollateral.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 57f5303bc..aab8b8f75 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -72,7 +72,7 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { // low { - uint256 ratioLow = ((1e18) * p0_low) / p1_low; + uint256 ratioLow = ((1e18) * p0_high) / p1_low; uint256 sqrtPriceLow = sqrt256( sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) ); @@ -80,7 +80,7 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { } // high { - uint256 ratioHigh = ((1e18) * p0_high) / p1_high; + uint256 ratioHigh = ((1e18) * p0_low) / p1_high; uint256 sqrtPriceHigh = sqrt256( sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) ); From b42b0c94a7ec5c817f97f7be4bd8347894431238 Mon Sep 17 00:00:00 2001 From: Patrick McKelvy Date: Wed, 16 Oct 2024 14:39:45 -0400 Subject: [PATCH 18/42] deployed. --- scripts/addresses/8453-tmp-assets-collateral.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 4602fbd2d..302dd7a47 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -11,7 +11,8 @@ "cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D", "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", - "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73" + "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", + "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -23,6 +24,7 @@ "saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759", "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", - "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452" + "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D" } -} +} \ No newline at end of file From 896ae3485c0704e6b10927e4588a12464646c164 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Oct 2024 15:38:29 -0700 Subject: [PATCH 19/42] init AerodromeVolatileCollateral --- .../aerodrome/AerodromeStableCollateral.sol | 79 +-------- .../aerodrome/AerodromeVolatileCollateral.sol | 164 ++++++++++++++++++ 2 files changed, 168 insertions(+), 75 deletions(-) create mode 100644 contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index aab8b8f75..c6389df43 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -1,17 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import "contracts/interfaces/IAsset.sol"; -import "contracts/libraries/Fixed.sol"; -import "contracts/plugins/assets/FiatCollateral.sol"; -import "../../../interfaces/IRewardable.sol"; -import "./AerodromePoolTokens.sol"; - -// This plugin only works on Base -IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); +import "./AerodromeVolatileCollateral.sol"; /** * @title AerodromeStableCollateral @@ -24,7 +14,7 @@ IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); * UoA = USD * */ -contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { +contract AerodromeStableCollateral is AerodromeVolatileCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -32,12 +22,9 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { /// @dev No revenue hiding (refPerTok() == FIX_ONE) /// @dev config.erc20 should be an AerodromeStakingWrapper constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) - FiatCollateral(config) - AerodromePoolTokens(aptConfig) + AerodromeVolatileCollateral(config, aptConfig) { require(config.defaultThreshold != 0, "defaultThreshold zero"); - assert((token0.decimals() + token1.decimals()) % 2 == 0); - maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } /// Can revert, used by other contract functions in order to catch errors @@ -96,67 +83,9 @@ contract AerodromeStableCollateral is FiatCollateral, AerodromePoolTokens { pegPrice = ((low + high) / 2).div(refPerTok()); } - /// Should not revert - /// Refresh exchange rates and update default status. - /// Have to override to add custom default checks - function refresh() public virtual override { - CollateralStatus oldStatus = status(); - - // Check for soft default + save prices - try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { - // {UoA/tok}, {UoA/tok}, {UoA/tok} - // (0, 0) is a valid price; (0, FIX_MAX) is unpriced - - // Save prices if priced - if (high != FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - savedPegPrice = pegPrice; - lastSave = uint48(block.timestamp); - } else { - // must be unpriced - // untested: - // validated in other plugins, cost to test here is high - assert(low == 0); - } - - // If the price is below the default-threshold price, default eventually - // uint192(+/-) is the same as Fix.plus/minus - if (low == 0 || _anyDepeggedInPool()) { - markStatus(CollateralStatus.IFFY); - } else { - markStatus(CollateralStatus.SOUND); - } - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - markStatus(CollateralStatus.IFFY); - } - - CollateralStatus newStatus = status(); - if (oldStatus != newStatus) { - emit CollateralStatusChanged(oldStatus, newStatus); - } - } - - /// Claim rewards earned by holding a balance of the ERC20 token - /// @custom:delegate-call - function claimRewards() external virtual override(Asset, IRewardable) { - uint256 aeroBal = AERO.balanceOf(address(this)); - IRewardable(address(erc20)).claimRewards(); - emit RewardsClaimed(AERO, AERO.balanceOf(address(this)) - aeroBal); - } - - /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens - function refPerTok() public view virtual override returns (uint192) { - int8 shift = 18 - int8((token0.decimals() + token1.decimals()) / 2); - return shiftl_toFix(2, shift, FLOOR); - } - // === Internal === - // Override this later to implement non-stable pools - function _anyDepeggedInPool() internal view virtual returns (bool) { + function _anyDepeggedInPool() internal view virtual override returns (bool) { // Check reference token oracles for (uint8 i = 0; i < nTokens; ++i) { try this.tokenPrice(i) returns (uint192 low, uint192 high) { diff --git a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol new file mode 100644 index 000000000..6b17fc9c8 --- /dev/null +++ b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "contracts/interfaces/IAsset.sol"; +import "contracts/libraries/Fixed.sol"; +import "contracts/plugins/assets/FiatCollateral.sol"; +import "../../../interfaces/IRewardable.sol"; +import "./AerodromePoolTokens.sol"; + +// This plugin only works on Base +IERC20 constant AERO = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631); + +/** + * @title AerodromeVolatileCollateral + * This plugin contract is designed for Aerodrome volatile pools + * Each token in the pool can have between 1 and 2 oracles per each token. + * + * There is no appreciation, only AERO rewards. + * + * tok = AerodromeStakingWrapper(volatilePool) + * ref = LP token /w shift + * tar = LP token /w shift + * UoA = USD + * + */ +contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimout + /// @dev No revenue hiding (refPerTok() == FIX_ONE) + /// @dev config.erc20 should be an AerodromeStakingWrapper + constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) + FiatCollateral(config) + AerodromePoolTokens(aptConfig) + { + require(config.defaultThreshold != 0, "defaultThreshold zero"); + assert((token0.decimals() + token1.decimals()) % 2 == 0); + maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); + } + + /// Can revert, used by other contract functions in order to catch errors + /// Should not return FIX_MAX for low + /// Should only return FIX_MAX for high if low is 0 + /// Should NOT be manipulable by MEV + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + virtual + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + uint256 r0 = tokenReserve(0); + uint256 r1 = tokenReserve(1); + + // x * y >= k for vAMM pools + uint256 sqrtReserve = sqrt256(r0 * r1); + + // get token prices + (uint192 p0_low, uint192 p0_high) = tokenPrice(0); + (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + + uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + + // low + { + uint256 ratioLow = ((1e18) * p0_high) / p1_low; + uint256 sqrtPriceLow = sqrt256( + sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) + ); + low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); + } + // high + { + uint256 ratioHigh = ((1e18) * p0_low) / p1_high; + uint256 sqrtPriceHigh = sqrt256( + sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) + ); + + high = _safeWrap( + ((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply + ); + } + assert(low <= high); // not obviously true just by inspection + + pegPrice = 0; // no default checks or issuance premium + } + + /// Should not revert + /// Refresh exchange rates and update default status. + /// Have to override to add custom default checks + function refresh() public virtual override { + CollateralStatus oldStatus = status(); + + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {UoA/tok} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high != FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + savedPegPrice = pegPrice; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + // untested: + // validated in other plugins, cost to test here is high + assert(low == 0); + } + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (low == 0 || _anyDepeggedInPool()) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); + } + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } + + /// Claim rewards earned by holding a balance of the ERC20 token + /// @custom:delegate-call + function claimRewards() external virtual override(Asset, IRewardable) { + uint256 aeroBal = AERO.balanceOf(address(this)); + IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(AERO, AERO.balanceOf(address(this)) - aeroBal); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function refPerTok() public view virtual override returns (uint192) { + int8 shift = 18 - int8((token0.decimals() + token1.decimals()) / 2); + return shiftl_toFix(2, shift, FLOOR); + } + + // === Internal === + + // Override this later to implement non-stable pools + function _anyDepeggedInPool() internal view virtual returns (bool) { + // TODO + // consider expanding plugin later to support ie WBTC peg checks + return false; + } +} From 7bad2154bd42a19b6841db06ec242af9146a3021 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Oct 2024 16:11:47 -0700 Subject: [PATCH 20/42] deployment + verification scripts --- common/configuration.ts | 6 + .../assets/aerodrome/AerodromePoolTokens.sol | 5 +- scripts/deploy.ts | 4 +- .../collaterals/deploy_aerodrome_mog_weth.ts | 140 ++++++++++++++++++ .../collaterals/deploy_aerodrome_weth_aero.ts | 140 ++++++++++++++++++ .../verify_aerodrome_weth_aero.ts | 100 +++++++++++++ scripts/verify_etherscan.ts | 3 +- .../aerodrome/constants.ts | 23 ++- 8 files changed, 417 insertions(+), 4 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts diff --git a/common/configuration.ts b/common/configuration.ts index c1140aeaa..df7840bfd 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -119,6 +119,7 @@ export interface ITokens { // Aerodrome AERO?: string + MOG?: string } export type ITokensKeys = Array @@ -147,6 +148,8 @@ export interface IPools { crvMIM3Pool?: string sdUSDCUSDCPlus?: string aeroUSDCeUSD?: string + aeroWETHAERO?: string + aeroMOGWETH?: string } interface INetworkConfig { @@ -522,6 +525,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', AERO: '0x940181a94A35A4569E4529A3CDfB74e38FD98631', eUSD: '0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4', + MOG: '0x2Da56AcB9Ea78330f947bD57C54119Debda7AF71', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -539,6 +543,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', + AERO: '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', // 0.5%, 24h + MOG: '0x4aeb6D15769EaD32D0c5Be2940F40c7CFf53801d', // 0.5%, 24h }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 882b32ee3..07fe5d12e 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -76,7 +76,10 @@ contract AerodromePoolTokens { // === Tokens === - if (config.poolType != AeroPoolType.Stable || !config.pool.stable()) { + if (config.poolType == AeroPoolType.Stable && !config.pool.stable()) { + revert("invalid poolType"); + } + if (config.poolType == AeroPoolType.Volatile && config.pool.stable()) { revert("invalid poolType"); } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 9f0387425..9918600ce 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -103,7 +103,9 @@ async function main() { 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', 'phase2-assets/assets/deploy_stg.ts', - 'phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts' + 'phase2-assets/collaterals/deploy_aerodrome_usdc_eusd.ts', + 'phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts', + 'phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts new file mode 100644 index 000000000..f16486a9d --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts @@ -0,0 +1,140 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + AerodromeVolatileCollateral, + AerodromeGaugeWrapper, + IAeroPool, +} from '../../../../typechain' +import { combinedError } from '../../utils' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_MOG_WETH_POOL, + AERO_MOG_WETH_GAUGE, + AERO, + MOG_USD_FEED, + MOG_ORACLE_TIMEOUT, + MOG_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Aerodrome volatile plugin: MOG-WETH vAMM + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Volatile Pool for MOG-WETH **************************/ + + let collateral: AerodromeVolatileCollateral + let wMogWeth: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeStableCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_MOG_WETH_POOL) + wMogWeth = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_MOG_WETH_GAUGE + ) + ) + await wMogWeth.deployed() + + console.log( + `Deployed wrapper for Aerodrome Volatile MOG-WETH pool on ${hre.network.name} (${chainId}): ${wMogWeth.address} ` + ) + + const oracleError = combinedError(MOG_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.5% + + collateral = await AerodromeStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wMogWeth.address, + targetName: ethers.utils.formatBytes32String('50%ETH50%MOG'), // lexicographical order for tokens + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: MOG_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', // 24h + }, + { + pool: AERO_MOG_WETH_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [MOG_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [MOG_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [MOG_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Volatile Collateral for MOG-WETH to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroMOGWETH = collateral.address + assetCollDeployments.erc20s.aeroMOGWETH = wMogWeth.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts new file mode 100644 index 000000000..302011790 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts @@ -0,0 +1,140 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + AerodromeVolatileCollateral, + AerodromeGaugeWrapper, + IAeroPool, +} from '../../../../typechain' +import { combinedError } from '../../utils' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_AERO_POOL, + AERO_WETH_AERO_GAUGE, + AERO, + AERO_USD_FEED, + AERO_ORACLE_TIMEOUT, + AERO_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Aerodrome volatile plugin: WETH-AERO vAMM + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Volatile Pool for WETH-AERO **************************/ + + let collateral: AerodromeVolatileCollateral + let wWethAero: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeStableCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_AERO_POOL) + wWethAero = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_AERO_GAUGE + ) + ) + await wWethAero.deployed() + + console.log( + `Deployed wrapper for Aerodrome Volatile WETH-AERO pool on ${hre.network.name} (${chainId}): ${wWethAero.address} ` + ) + + const oracleError = combinedError(AERO_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + + collateral = await AerodromeStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wWethAero.address, + targetName: ethers.utils.formatBytes32String('50%AERO50%ETH'), // lexicographical order for tokens + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: AERO_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', // 24h + }, + { + pool: AERO_WETH_AERO_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [AERO_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [AERO_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [AERO_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Volatile Collateral for WETH-AERO to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroWETHAERO = collateral.address + assetCollDeployments.erc20s.aeroWETHAERO = wWethAero.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts b/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts new file mode 100644 index 000000000..aff94384f --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts @@ -0,0 +1,100 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_AERO_POOL, + AERO_WETH_AERO_GAUGE, + AERO, + AERO_ORACLE_TIMEOUT, + AERO_USD_FEED, + AERO_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroWETHAEROPoolCollateral = await ethers.getContractAt( + 'AerodromeVolatileCollateral', + deployments.collateral.aeroWETHAERO as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_AERO_POOL) + await verifyContract( + chainId, + await aeroWETHAEROPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_AERO_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify USDC-eUSD plugin **************************/ + const oracleError = combinedError(AERO_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + await verifyContract( + chainId, + deployments.collateral.aeroWETHAERO, + [ + { + erc20: await aeroWETHAEROPoolCollateral.erc20(), + targetName: await aeroWETHAEROPoolCollateral.targetName(), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: AERO_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', + }, + { + pool: AERO_WETH_AERO_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [AERO_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [AERO_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [AERO_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol:AerodromeVolatileCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index fa5cea47c..f17c9d141 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -89,7 +89,8 @@ async function main() { 'collateral-plugins/verify_wsteth.ts', 'collateral-plugins/verify_cbeth.ts', 'assets/verify_stg.ts', - 'collateral-plugins/verify_aerodrome_usdc_eusd.ts' + 'collateral-plugins/verify_aerodrome_usdc_eusd.ts', + 'collateral-plugins/verify_aerodrome_weth_aero.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts index d8a6e7dad..03b0f87a6 100644 --- a/test/plugins/individual-collateral/aerodrome/constants.ts +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -9,6 +9,12 @@ export const AERO_USDC_eUSD_GAUGE = '0x793F22aB88dC91793E5Ce6ADbd7E733B0BD4733e' export const AERO_USDC_eUSD_POOL = '0x7A034374C89C463DD65D8C9BCfe63BcBCED41f4F' export const AERO_USDC_eUSD_HOLDER = '0xB6C8ea53ABA64a4BdE857D3b25d9DEbD0B149a0a' +export const AERO_WETH_AERO_GAUGE = '0x96a24aB830D4ec8b1F6f04Ceac104F1A3b211a01' +export const AERO_WETH_AERO_POOL = '0x7f670f78B17dEC44d5Ef68a48740b6f8849cc2e6' + +export const AERO_MOG_WETH_GAUGE = '0x8FCc385d8d7f3A2e087853a79531630Bf96575e8' +export const AERO_MOG_WETH_POOL = '0x4a311ac4563abc30e71d0631c88a6232c1309ac5' + export const AERODROME_ROUTER = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' // Tokens @@ -30,12 +36,27 @@ export const eUSD_HOLDER = '0xb5E331615FdbA7DF49e05CdEACEb14Acdd5091c3' export const FORK_BLOCK = 19074500 +// AERO +export const AERO_ORACLE_ERROR = fp('0.005') // 0.5% +export const AERO_ORACLE_TIMEOUT = bn('86400') // 24hr +export const AERO_USD_FEED = networkConfig['8453'].chainlinkFeeds.AERO! + +// ETH +export const ETH_ORACLE_ERROR = fp('0.0015') // 0.15% +export const ETH_ORACLE_TIMEOUT = bn('1200') // 20min +export const ETH_USD_FEED = networkConfig['8453'].chainlinkFeeds.ETHUSD! + +// MOG +export const MOG_ORACLE_ERROR = fp('0.005') // 0.5% +export const MOG_ORACLE_TIMEOUT = bn('86400') // 24hr +export const MOG_USD_FEED = networkConfig['8453'].chainlinkFeeds.MOG! + // Common export const FIX_ONE = 1n * 10n ** 18n export const ORACLE_ERROR = fp('0.005') export const PRICE_TIMEOUT = bn('604800') // 1 week export const DEFAULT_THRESHOLD = fp('0.02') // 2% -export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h +export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h CAREFUL THIS IS ONLY FOR RTOKEN POOLS export const MAX_TRADE_VOL = fp('1e6') export enum AerodromePoolType { From 86f4d3bc32761162b6e60f44b1c9229c04d3009d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Oct 2024 16:13:14 -0700 Subject: [PATCH 21/42] nit --- .../collateral-plugins/verify_aerodrome_weth_aero.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts b/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts index aff94384f..5d6f45cb4 100644 --- a/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts +++ b/scripts/verification/collateral-plugins/verify_aerodrome_weth_aero.ts @@ -64,7 +64,7 @@ async function main() { 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' ) - /******** Verify USDC-eUSD plugin **************************/ + /******** Verify WETH-AERO plugin **************************/ const oracleError = combinedError(AERO_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% await verifyContract( chainId, From feda2e1af98d8c9b7cb2debefb5be257679fd8bb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Oct 2024 17:07:57 -0700 Subject: [PATCH 22/42] fix --- .../plugins/assets/aerodrome/AerodromeVolatileCollateral.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol index 6b17fc9c8..5897c14fe 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol @@ -37,7 +37,6 @@ contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { FiatCollateral(config) AerodromePoolTokens(aptConfig) { - require(config.defaultThreshold != 0, "defaultThreshold zero"); assert((token0.decimals() + token1.decimals()) % 2 == 0); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } From 0d1943a86c204ea79a6408df130bbb0472c78bc8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Oct 2024 17:08:01 -0700 Subject: [PATCH 23/42] base addresses --- scripts/addresses/8453-tmp-assets-collateral.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 302dd7a47..ff7d01cb0 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -12,7 +12,9 @@ "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0" + "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", + "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", + "aeroMOGWETH": "0xb05e15145655eF793f5Ebf8389D3974729889fC0" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -25,6 +27,8 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D" + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", + "aeroMOGWETH": "0x6816CeeA904dDC412E4fB6937B0849A991bd249e" } } \ No newline at end of file From c3a0189a0cbb65e877182fc0c978ed34a31cc733 Mon Sep 17 00:00:00 2001 From: Julian R Date: Tue, 29 Oct 2024 13:01:52 -0300 Subject: [PATCH 24/42] deploy aero --- common/configuration.ts | 1 + .../addresses/8453-tmp-assets-collateral.json | 8 ++- .../8453-tmp-assets-collateral.json | 12 ++++ .../phase2-assets/assets/deploy_aero.ts | 71 +++++++++++++++++++ scripts/verification/assets/verify_aero.ts | 49 +++++++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json create mode 100644 scripts/deployment/phase2-assets/assets/deploy_aero.ts create mode 100644 scripts/verification/assets/verify_aero.ts diff --git a/common/configuration.ts b/common/configuration.ts index c1140aeaa..300f60650 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -538,6 +538,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h + AERO: '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', // 0.5%, 24h eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 302dd7a47..5d8c2b9cb 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -1,7 +1,8 @@ { "assets": { "COMP": "0xB8794Fb1CCd62bFe631293163F4A3fC2d22e37e0", - "STG": "0xEE527CC63122732532d0f1ad33Ec035D30f3050f" + "STG": "0xEE527CC63122732532d0f1ad33Ec035D30f3050f", + "AERO": "0x5D09F98B6fA59456E608bD20Ca806140884C3790" }, "collateral": { "DAI": "0x3E40840d0282C9F9cC7d17094b5239f87fcf18e5", @@ -25,6 +26,7 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D" + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631" } -} \ No newline at end of file +} diff --git a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json new file mode 100644 index 000000000..fefc3e876 --- /dev/null +++ b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json @@ -0,0 +1,12 @@ +{ + "assets": { + "AERO": "0x5D09F98B6fA59456E608bD20Ca806140884C3790" + }, + "collateral": { + "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0" + }, + "erc20s": { + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631" + } +} diff --git a/scripts/deployment/phase2-assets/assets/deploy_aero.ts b/scripts/deployment/phase2-assets/assets/deploy_aero.ts new file mode 100644 index 000000000..d2e467254 --- /dev/null +++ b/scripts/deployment/phase2-assets/assets/deploy_aero.ts @@ -0,0 +1,71 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { + getDeploymentFile, + getDeploymentFilename, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + fileExists, +} from '../../common' +import { priceTimeout } from '../../utils' +import { Asset } from '../../../../typechain' + +async function main() { + // ==== Read Configuration ==== + const [burner] = await hre.ethers.getSigners() + const chainId = await getChainId(hre) + + console.log(`Deploying AERO asset to network ${hre.network.name} (${chainId}) + with burner account: ${burner.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedAssets: string[] = [] + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + /******** Deploy AERO asset **************************/ + const { asset: aeroAsset } = await hre.run('deploy-asset', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.AERO, + oracleError: fp('0.005').toString(), // 0.5% + tokenAddress: networkConfig[chainId].tokens.AERO, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '86400', // 24 hr + }) + await (await ethers.getContractAt('Asset', aeroAsset)).refresh() + + assetCollDeployments.assets.AERO = aeroAsset + assetCollDeployments.erc20s.AERO = networkConfig[chainId].tokens.AERO + deployedAssets.push(aeroAsset.toString()) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + /**************************************************************/ + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed AERO asset to ${hre.network.name} (${chainId}): + New deployments: ${deployedAssets} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/assets/verify_aero.ts b/scripts/verification/assets/verify_aero.ts new file mode 100644 index 000000000..fa41a2bb4 --- /dev/null +++ b/scripts/verification/assets/verify_aero.ts @@ -0,0 +1,49 @@ +import hre from 'hardhat' + +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { + getAssetCollDeploymentFilename, + getDeploymentFile, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { fp } from '../../../common/numbers' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + deployments = getDeploymentFile(getAssetCollDeploymentFilename(chainId)) + + const asset = await hre.ethers.getContractAt('Asset', deployments.assets.AERO!) + + /** ******************** Verify AERO Asset ****************************************/ + await verifyContract( + chainId, + deployments.assets.AERO, + [ + (await asset.priceTimeout()).toString(), + await asset.chainlinkFeed(), + fp('0.005').toString(), + await asset.erc20(), + (await asset.maxTradeVolume()).toString(), + (await asset.oracleTimeout()).toString(), + ], + 'contracts/plugins/assets/Asset.sol:Asset' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) From 26ff39ae1b3fd4a503584a05370faf81933d1a9e Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 1 Nov 2024 09:58:00 -0300 Subject: [PATCH 25/42] adapt feed revert test --- .../AerodromeStableCollateral.test.ts | 29 +++++++++++- .../individual-collateral/collateralTests.ts | 44 ++++++++++--------- .../individual-collateral/pluginTestTypes.ts | 3 ++ 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index 109191cdb..bd6c9e518 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -6,13 +6,14 @@ import { IAeroPool, MockV3Aggregator, MockV3Aggregator__factory, + InvalidMockV3Aggregator, AerodromeGaugeWrapper__factory, TestICollateral, AerodromeGaugeWrapper, ERC20Mock, } from '../../../../typechain' import { networkConfig } from '../../../../common/configuration' -import { ZERO_ADDRESS } from '#/common/constants' +import { CollateralStatus, ZERO_ADDRESS } from '#/common/constants' import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -376,6 +377,31 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const finalRefPerTok = await coll.refPerTok() expect(finalRefPerTok).to.equal(initialRefPerTok) }) + + it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) + ) + + const invalidCollateral = await deployCollateral({ + pool: curr.pool, + gauge: curr.gauge, + feeds: [[invalidChainlinkFeed.address], [invalidChainlinkFeed.address]], + }) + + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + }) } const getExpectedPrice = async (ctx: CollateralFixtureContext) => { @@ -424,6 +450,7 @@ all.forEach((curr: AeroStablePoolEnumeration) => { itChecksRefPerTokDefault: it.skip, itChecksPriceChanges: it.skip, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it.skip, itHasRevenueHiding: it.skip, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index fa7ae3a1e..8ce9c5023 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -87,6 +87,7 @@ export default function fn( itChecksRefPerTokDefault, itChecksPriceChanges, itChecksNonZeroDefaultThreshold, + itChecksMainChainlinkOracleRevert, itHasRevenueHiding, itIsPricedByPeg, itHasOracleRefPerTok, @@ -394,29 +395,32 @@ export default function fn( ) // within 1-part-in-1-thousand }) - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) - ) + itChecksMainChainlinkOracleRevert( + 'reverts if Chainlink feed reverts or runs out of gas, maintains status', + async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) - const invalidCollateral = await deployCollateral({ - erc20: ctx.tok.address, - chainlinkFeed: invalidChainlinkFeed.address, - }) + const invalidCollateral = await deployCollateral({ + erc20: ctx.tok.address, + chainlinkFeed: invalidChainlinkFeed.address, + }) - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + } + ) it('decays price over priceTimeout period', async () => { await collateral.refresh() diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 837e5a258..d65722bf8 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -106,6 +106,9 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that check that defaultThreshold is not zero itChecksNonZeroDefaultThreshold: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that check when the main chainlink feed reverts (not always used) + itChecksMainChainlinkOracleRevert: Mocha.TestFunction | Mocha.PendingTestFunction + // does the peg price matter for the results of tryPrice()? itIsPricedByPeg?: boolean From fa9070e68236b7f9247b5a83a63ece8ac7413ab9 Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 1 Nov 2024 10:15:36 -0300 Subject: [PATCH 26/42] add new parameter --- .../ankr/AnkrEthCollateralTestSuite.test.ts | 1 + test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts | 1 + .../individual-collateral/cbeth/CBETHCollateralL2.test.ts | 1 + .../individual-collateral/compoundv3/CometTestSuite.test.ts | 1 + .../individual-collateral/dsr/SDaiCollateralTestSuite.test.ts | 1 + .../individual-collateral/ethena/USDeFiatCollateral.test.ts | 1 + test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts | 1 + .../flux-finance/FTokenFiatCollateral.test.ts | 1 + .../individual-collateral/frax-eth/SFrxEthTestSuite.test.ts | 1 + .../individual-collateral/frax/SFraxCollateralTestSuite.test.ts | 1 + .../individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts | 1 + .../individual-collateral/lido/LidoStakedEthTestSuite.test.ts | 1 + .../meta-morpho/MetaMorphoFiatCollateral.test.ts | 1 + .../meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts | 1 + .../morpho-aave/MorphoAAVEFiatCollateral.test.ts | 1 + .../morpho-aave/MorphoAAVENonFiatCollateral.test.ts | 1 + .../morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts | 1 + .../individual-collateral/mountain/USDMCollateral.test.ts | 1 + .../individual-collateral/pirex-eth/ApxEthCollateral.test.ts | 1 + .../rocket-eth/RethCollateralTestSuite.test.ts | 1 + .../stargate/StargateUSDCTestSuite.test.ts_DEPRECATED | 1 + 21 files changed, 21 insertions(+) diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index 8a3a07a83..ed9bae870 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -291,6 +291,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index cd35ee5c0..6dea339bc 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -248,6 +248,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index a4a9c3242..7e1a28744 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -279,6 +279,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateralL2', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index c0062e93d..5b5c7ba6f 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -384,6 +384,7 @@ allTests.forEach((curr: CTokenV3Enumeration) => { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, resetFork: getResetFork(getForkBlock(curr.tokenName)), diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 2919f0ebc..52549b704 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -215,6 +215,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, resetFork, collateralName: 'SDaiCollateral', diff --git a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts index 9624aaa70..6a6528d9f 100644 --- a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts @@ -211,6 +211,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, collateralName: 'USDe Fiat Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts index 10b4da87c..b3c380480 100644 --- a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts +++ b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts @@ -282,6 +282,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'Stader ETHx', diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 180a88935..eabfa1269 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -258,6 +258,7 @@ all.forEach((curr: FTokenEnumeration) => { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index b15e1df41..b25608382 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -311,6 +311,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it.skip, // implemented in this file itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'SFraxEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index 2ed9bc5e4..e9ec2b3e2 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -195,6 +195,7 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it.skip, diff --git a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts index 184b1090a..7ac7956e2 100644 --- a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts @@ -278,6 +278,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK_BASE), collateralName: 'L2LidoStakedETH', diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 366c8c81c..09bb47a51 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -269,6 +269,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'LidoStakedETH', diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts index f8138befb..05429c153 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts @@ -175,6 +175,7 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts index d243ec85a..44b009bd6 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -171,6 +171,7 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 9a9b94fad..27f971c04 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -368,6 +368,7 @@ const makeAaveFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 937ec99e7..e846ec7e8 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -229,6 +229,7 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 81404fe20..71100d2a3 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -229,6 +229,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', diff --git a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts index 570c60345..3605c491a 100644 --- a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts +++ b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts @@ -311,6 +311,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file collateralName: 'USDM Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts index db143ad60..e49008c64 100644 --- a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts +++ b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts @@ -378,6 +378,7 @@ const opts = { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file resetFork, collateralName: 'ApxETH', diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index f766a3bc0..48017d264 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -276,6 +276,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'RocketPoolETH', diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED index 009ffdf1a..f3e64455d 100644 --- a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED @@ -316,6 +316,7 @@ export const stableOpts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, From 6b183a74e4102a3a7a67906ed5657785d564590f Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 1 Nov 2024 10:33:20 -0300 Subject: [PATCH 27/42] add new param --- .../yearnv2/YearnV2CurveFiatCollateral.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts index 22017205a..9b34573b7 100644 --- a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts @@ -260,6 +260,7 @@ tests.forEach((test: CurveFiatTest) => { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, + itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itClaimsRewards: it.skip, isMetapool: false, From dc65ee37c19dd81476b8d9d215be1df0cab442da Mon Sep 17 00:00:00 2001 From: Julian R Date: Fri, 1 Nov 2024 10:45:57 -0300 Subject: [PATCH 28/42] add new param to aave test --- test/plugins/individual-collateral/aave-v3/common.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/aave-v3/common.ts b/test/plugins/individual-collateral/aave-v3/common.ts index 77e11f1be..f9f22f6a2 100644 --- a/test/plugins/individual-collateral/aave-v3/common.ts +++ b/test/plugins/individual-collateral/aave-v3/common.ts @@ -210,6 +210,7 @@ export const makeTests = (defaultCollateralOpts: CollateralParams, altParams: Al itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, + itChecksMainChainlinkOracleRevert: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, itChecksPriceChanges: it, From 329eae3d32defdab80e707b3f588757f336bec4c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 23:19:17 +0900 Subject: [PATCH 29/42] integration test AerodromeVolatileCollateral --- .../aerodrome/AerodromeVolatileCollateral.sol | 3 +- .../AerodromeVolatileCollateral.test.ts | 413 ++++++++++++++++++ .../aerodrome/constants.ts | 9 +- .../aerodrome/helpers.ts | 12 +- 4 files changed, 431 insertions(+), 6 deletions(-) create mode 100644 test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts diff --git a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol index 5897c14fe..6635f97b7 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol @@ -156,8 +156,7 @@ contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { // Override this later to implement non-stable pools function _anyDepeggedInPool() internal view virtual returns (bool) { - // TODO - // consider expanding plugin later to support ie WBTC peg checks + // TODO maybe expand plugin later to support peg checks, currently not worth it return false; } } diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts new file mode 100644 index 000000000..c684598c9 --- /dev/null +++ b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts @@ -0,0 +1,413 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' +import { + IAeroPool, + MockV3Aggregator, + MockV3Aggregator__factory, + AerodromeGaugeWrapper__factory, + TestICollateral, + AerodromeGaugeWrapper, + ERC20Mock, +} from '../../../../typechain' +import { ZERO_ADDRESS } from '#/common/constants' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + AerodromePoolType, + MOG_USD_FEED, + MOG_HOLDER, + MOG_ORACLE_ERROR, + MOG_ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + AERO_MOG_WETH_POOL, + AERO_MOG_WETH_GAUGE, + AERO_MOG_WETH_HOLDER, + AERO, + AERO_USD_FEED, + AERO_ORACLE_ERROR, + AERO_ORACLE_TIMEOUT, + AERO_HOLDER, + MOG, + WETH, + WETH_HOLDER, + AERO_WETH_AERO_POOL, + AERO_WETH_AERO_GAUGE, + AERO_WETH_AERO_HOLDER, + ETH_USD_FEED, + ETH_ORACLE_ERROR, + ETH_ORACLE_TIMEOUT, +} from './constants' +import { mintWrappedLpToken, resetFork, getFeeds, pushAllFeedsForward } from './helpers' + +/* + Define interfaces +*/ + +interface AeroPoolTokenConfig { + token: string + feeds: string[] + oracleTimeouts: BigNumberish[] + oracleErrors: BigNumberish[] + holder: string +} + +interface AeroVolatilePoolEnumeration { + testName: string + pool: string + gauge: string + holder: string + toleranceDivisor: BigNumber + amountScaleDivisor: BigNumber + tokens: AeroPoolTokenConfig[] + oracleTimeout: BigNumberish + oracleError: BigNumberish +} + +interface AeroVolatileCollateralOpts extends CollateralOpts { + pool?: string + poolType?: AerodromePoolType + gauge?: string + feeds?: string[][] + oracleTimeouts?: BigNumberish[][] + oracleErrors?: BigNumberish[][] +} + +interface AerodromeCollateralFixtureContext extends CollateralFixtureContext { + feeds?: string[][] +} + +// ==== + +// Test all Aerodrome Volatile pools +const all: AeroVolatilePoolEnumeration[] = [ + { + testName: 'Aerodrome - MOG/WETH Volatile', + pool: AERO_MOG_WETH_POOL, + gauge: AERO_MOG_WETH_GAUGE, + holder: AERO_MOG_WETH_HOLDER, + tokens: [ + { + token: MOG, + feeds: [MOG_USD_FEED], + oracleTimeouts: [MOG_ORACLE_TIMEOUT], + oracleErrors: [MOG_ORACLE_ERROR], + holder: MOG_HOLDER, + }, + { + token: WETH, + feeds: [ETH_USD_FEED], + oracleTimeouts: [ETH_ORACLE_TIMEOUT], + oracleErrors: [ETH_ORACLE_ERROR], + holder: WETH_HOLDER, + }, + ], + oracleTimeout: MOG_ORACLE_TIMEOUT, // max + oracleError: MOG_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined + amountScaleDivisor: bn('1'), + toleranceDivisor: bn('1e4'), + }, + { + testName: 'Aerodrome - WETH/AERO Volatile', + pool: AERO_WETH_AERO_POOL, + gauge: AERO_WETH_AERO_GAUGE, + holder: AERO_WETH_AERO_HOLDER, + tokens: [ + { + token: WETH, + feeds: [ETH_USD_FEED], + oracleTimeouts: [ETH_ORACLE_TIMEOUT], + oracleErrors: [ETH_ORACLE_ERROR], + holder: WETH_HOLDER, + }, + { + token: AERO, + feeds: [AERO_USD_FEED], + oracleTimeouts: [AERO_ORACLE_TIMEOUT], + oracleErrors: [AERO_ORACLE_ERROR], + holder: AERO_HOLDER, + }, + ], + oracleTimeout: AERO_ORACLE_TIMEOUT, // max + oracleError: AERO_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined + amountScaleDivisor: bn('1'), + toleranceDivisor: bn('1e4'), + }, +] + +all.forEach((curr: AeroVolatilePoolEnumeration) => { + const defaultCollateralOpts: AeroVolatileCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('ETH'), // good enough to test swapping out + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: curr.tokens[0].feeds[0], // unused but cannot be zero + oracleTimeout: curr.oracleTimeout, // max of oracleTimeouts + oracleError: curr.oracleError, // combined oracle error + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + pool: curr.pool, + poolType: AerodromePoolType.Volatile, + gauge: curr.gauge, + feeds: [curr.tokens[0].feeds, curr.tokens[1].feeds], + oracleTimeouts: [curr.tokens[0].oracleTimeouts, curr.tokens[1].oracleTimeouts], + oracleErrors: [curr.tokens[0].oracleErrors, curr.tokens[1].oracleErrors], + } + + const deployCollateral = async ( + opts: AeroVolatileCollateralOpts = {} + ): Promise => { + let pool: IAeroPool + let wrapper: AerodromeGaugeWrapper + + if (!opts.erc20) { + const AerodromGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + + // Create wrapper + pool = await ethers.getContractAt('IAeroPool', curr.pool) + + wrapper = await AerodromGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + curr.gauge + ) + + opts.erc20 = wrapper.address + } + + opts = { ...defaultCollateralOpts, ...opts } + opts.feeds![0][0] = opts.chainlinkFeed! + + const AeroVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + + const collateral = await AeroVolatileCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + { + pool: opts.pool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + }, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + // Push forward chainlink feeds + await pushAllFeedsForward(collateral) + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: AeroVolatileCollateralOpts = {} + ): Fixture => { + const collateralOpts = { ...defaultCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute both feeds + const token0Feed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.chainlinkFeed = token0Feed.address + + const token1Feed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[token0Feed.address], [token1Feed.address]] + + const pool = await ethers.getContractAt('IAeroPool', curr.pool) + + const AerodromeGaugeWrapperFactory = ( + await ethers.getContractFactory('AerodromeGaugeWrapper') + ) + + const wrapper = await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + curr.gauge + ) + + collateralOpts.erc20 = wrapper.address + + const collateral = await deployCollateral(collateralOpts) + const erc20 = await ethers.getContractAt( + 'AerodromeGaugeWrapper', + (await collateral.erc20()) as string + ) + + const rewardToken = await ethers.getContractAt('ERC20Mock', AERO) + + return { + alice, + collateral, + chainlinkFeed: token0Feed, + tok: erc20, + rewardToken, + } + } + + return makeCollateralFixtureContext + } + + /* + Define helper functions +*/ + + const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string + ) => { + const gauge = await ethers.getContractAt('IAeroGauge', curr.gauge) + const pool = await ethers.getContractAt('IAeroPool', curr.pool) + + await mintWrappedLpToken( + ctx.tok as AerodromeGaugeWrapper, + gauge, + pool, + amount, + curr.holder, + user, + recipient + ) + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const reduceTargetPerRef = async () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const increaseTargetPerRef = async () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const increaseRefPerTok = async () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const reduceRefPerTok = async () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} + + const collateralSpecificStatusTests = () => { + it('prices change as feed price changes', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const feed0 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const feed1 = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const coll = await deployCollateral({ + pool: curr.pool, + gauge: curr.gauge, + feeds: [[feed0.address], [feed1.address]], + }) + + const initialRefPerTok = await coll.refPerTok() + const [low, high] = await coll.price() + + // Update values in Oracles increase by 10% + const allFeeds = await getFeeds(coll) + const initialPrices = await Promise.all(allFeeds.map((f) => f.latestRoundData())) + for (const [i, feed] of allFeeds.entries()) { + await feed.updateAnswer(initialPrices[i].answer.mul(110).div(100)).then((e) => e.wait()) + } + + const [newLow, newHigh] = await coll.price() + + // with 18 decimals of price precision a 1e-9 tolerance seems fine for a 10% change + expect(newLow).to.be.closeTo(low.mul(110).div(100), fp('1e-9')) + expect(newHigh).to.be.closeTo(high.mul(110).div(100), fp('1e-9')) + + // Check refPerTok remains _exactly_ the same + const finalRefPerTok = await coll.refPerTok() + expect(finalRefPerTok).to.equal(initialRefPerTok) + }) + } + + const getExpectedPrice = async (ctx: CollateralFixtureContext) => { + const initRefPerTok = await ctx.collateral.refPerTok() + const coll = await ethers.getContractAt('AerodromeVolatileCollateral', ctx.collateral.address) + + const feed0 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(0))[0]) + const decimals0 = await feed0.decimals() + const initData0 = await feed0.latestRoundData() + + const feed1 = await ethers.getContractAt('MockV3Aggregator', (await coll.tokenFeeds(1))[0]) + const decimals1 = await feed1.decimals() + const initData1 = await feed1.latestRoundData() + + const avgPrice = initData0.answer + .mul(bn(10).pow(18 - decimals0)) + .add(initData1.answer.mul(bn(10).pow(18 - decimals1))) + .div(2) + + return avgPrice.mul(initRefPerTok).div(fp('1')) + } + + /* + Run the test suite + */ + + const emptyFn = () => { + return + } + + const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest: emptyFn, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it, + itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, + itChecksRefPerTokDefault: it.skip, + itChecksPriceChanges: it.skip, + itChecksNonZeroDefaultThreshold: it.skip, + itHasRevenueHiding: it.skip, + resetFork, + collateralName: curr.testName, + chainlinkDefaultAnswer: bn('1e8'), + itIsPricedByPeg: false, + toleranceDivisor: curr.toleranceDivisor, + amountScaleDivisor: curr.amountScaleDivisor, + targetNetwork: 'base', + } + + collateralTests(opts) +}) diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts index 03b0f87a6..2810d3e4e 100644 --- a/test/plugins/individual-collateral/aerodrome/constants.ts +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -7,20 +7,24 @@ export const forkNetwork = useEnv('FORK_NETWORK') ?? 'base' // Base Addresses export const AERO_USDC_eUSD_GAUGE = '0x793F22aB88dC91793E5Ce6ADbd7E733B0BD4733e' export const AERO_USDC_eUSD_POOL = '0x7A034374C89C463DD65D8C9BCfe63BcBCED41f4F' -export const AERO_USDC_eUSD_HOLDER = '0xB6C8ea53ABA64a4BdE857D3b25d9DEbD0B149a0a' +export const AERO_USDC_eUSD_HOLDER = '0xB6C8ea53ABA64a4BdE857D3b25d9DEbD0B149a0a' // for gauge export const AERO_WETH_AERO_GAUGE = '0x96a24aB830D4ec8b1F6f04Ceac104F1A3b211a01' export const AERO_WETH_AERO_POOL = '0x7f670f78B17dEC44d5Ef68a48740b6f8849cc2e6' +export const AERO_WETH_AERO_HOLDER = '0x9f2cB6b3A5BfE6A7D42c3702F628201616649C00' // for pool export const AERO_MOG_WETH_GAUGE = '0x8FCc385d8d7f3A2e087853a79531630Bf96575e8' export const AERO_MOG_WETH_POOL = '0x4a311ac4563abc30e71d0631c88a6232c1309ac5' +export const AERO_MOG_WETH_HOLDER = '0x76AbE28E4108eC1B56f429582087CFDdC757eAcc' // for pool export const AERODROME_ROUTER = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' // Tokens export const USDC = networkConfig['8453'].tokens.USDC! +export const WETH = networkConfig['8453'].tokens.WETH! export const eUSD = networkConfig['8453'].tokens.eUSD! export const AERO = networkConfig['8453'].tokens.AERO! +export const MOG = networkConfig['8453'].tokens.MOG! // USDC export const USDC_USD_FEED = networkConfig['8453'].chainlinkFeeds.USDC! @@ -40,16 +44,19 @@ export const FORK_BLOCK = 19074500 export const AERO_ORACLE_ERROR = fp('0.005') // 0.5% export const AERO_ORACLE_TIMEOUT = bn('86400') // 24hr export const AERO_USD_FEED = networkConfig['8453'].chainlinkFeeds.AERO! +export const AERO_HOLDER = '0x807877258B55BfEfaBDD469dA1C72731C5070839' // ETH export const ETH_ORACLE_ERROR = fp('0.0015') // 0.15% export const ETH_ORACLE_TIMEOUT = bn('1200') // 20min export const ETH_USD_FEED = networkConfig['8453'].chainlinkFeeds.ETHUSD! +export const WETH_HOLDER = '0x6446021F4E396dA3df4235C62537431372195D38' // MOG export const MOG_ORACLE_ERROR = fp('0.005') // 0.5% export const MOG_ORACLE_TIMEOUT = bn('86400') // 24hr export const MOG_USD_FEED = networkConfig['8453'].chainlinkFeeds.MOG! +export const MOG_HOLDER = '0xBaeD383EDE0e5d9d72430661f3285DAa77E9439F' // Common export const FIX_ONE = 1n * 10n ** 18n diff --git a/test/plugins/individual-collateral/aerodrome/helpers.ts b/test/plugins/individual-collateral/aerodrome/helpers.ts index 5a2de4312..882c99553 100644 --- a/test/plugins/individual-collateral/aerodrome/helpers.ts +++ b/test/plugins/individual-collateral/aerodrome/helpers.ts @@ -7,7 +7,9 @@ import { TestICollateral, MockV3Aggregator, } from '@typechain/index' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { ethers } from 'hardhat' +import { BigNumberish } from 'ethers' import { USDC, eUSD, @@ -58,7 +60,10 @@ export const mintLpToken = async ( recipient: string ) => { await whileImpersonating(holder, async (signer) => { - await gauge.connect(signer).withdraw(amount) + // holder can have lpToken OR gauge + if ((await lpToken.balanceOf(signer.address)).lt(amount)) { + await gauge.connect(signer).withdraw(amount) + } await lpToken.connect(signer).transfer(recipient, amount) }) } @@ -78,9 +83,10 @@ export const mintWrappedLpToken = async ( } export const getFeeds = async (coll: TestICollateral): Promise => { - const aeroStableColl = await ethers.getContractAt('AerodromeStableCollateral', coll.address) + const aeroColl = await ethers.getContractAt('AerodromeVolatileCollateral', coll.address) + // works for AerodromeStableCollateral too - const feedAddrs = (await aeroStableColl.tokenFeeds(0)).concat(await aeroStableColl.tokenFeeds(1)) + const feedAddrs = (await aeroColl.tokenFeeds(0)).concat(await aeroColl.tokenFeeds(1)) const feeds: MockV3Aggregator[] = [] for (const feedAddr of feedAddrs) { From fd016d104d73dafc8e878ea7c786032d8b1f2217 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 23:30:01 +0900 Subject: [PATCH 30/42] fix mog aero and add verify script --- .../addresses/8453-tmp-assets-collateral.json | 6 +- .../collaterals/deploy_aerodrome_mog_weth.ts | 6 +- .../verify_aerodrome_mog_weth.ts | 101 ++++++++++++++++++ scripts/verify_etherscan.ts | 1 + 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_mog_weth.ts diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index d5dfef3e1..1971ec2b0 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -15,7 +15,7 @@ "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", - "aeroMOGWETH": "0xb05e15145655eF793f5Ebf8389D3974729889fC0", + "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314", "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3" }, "erc20s": { @@ -31,8 +31,8 @@ "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", - "aeroMOGWETH": "0x6816CeeA904dDC412E4fB6937B0849A991bd249e", + "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631" } -} +} \ No newline at end of file diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts index f16486a9d..d01ec10af 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts @@ -106,9 +106,9 @@ async function main() { { pool: AERO_MOG_WETH_POOL, poolType: AerodromePoolType.Volatile, - feeds: [[ETH_USD_FEED], [MOG_USD_FEED]], - oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [MOG_ORACLE_TIMEOUT]], - oracleErrors: [[ETH_ORACLE_ERROR], [MOG_ORACLE_ERROR]], + feeds: [[MOG_USD_FEED], [ETH_USD_FEED]], + oracleTimeouts: [[MOG_ORACLE_TIMEOUT], [ETH_ORACLE_TIMEOUT]], + oracleErrors: [[MOG_ORACLE_ERROR], [ETH_ORACLE_ERROR]], } ) } else { diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_mog_weth.ts b/scripts/verification/collateral-plugins/verify_aerodrome_mog_weth.ts new file mode 100644 index 000000000..c3841c355 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_mog_weth.ts @@ -0,0 +1,101 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_MOG_WETH_POOL, + AERO_MOG_WETH_GAUGE, + AERO, + MOG_ORACLE_TIMEOUT, + MOG_ORACLE_ERROR, + MOG_USD_FEED, + AERO_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroMOGWETHPoolCollateral = await ethers.getContractAt( + 'AerodromeVolatileCollateral', + deployments.collateral.aeroMOGWETH as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_MOG_WETH_POOL) + await verifyContract( + chainId, + await aeroMOGWETHPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_MOG_WETH_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify WETH-AERO plugin **************************/ + const oracleError = combinedError(AERO_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + await verifyContract( + chainId, + deployments.collateral.aeroMOGWETH, + [ + { + erc20: await aeroMOGWETHPoolCollateral.erc20(), + targetName: await aeroMOGWETHPoolCollateral.targetName(), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: MOG_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', + }, + { + pool: AERO_MOG_WETH_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[MOG_USD_FEED], [ETH_USD_FEED]], + oracleTimeouts: [[MOG_ORACLE_TIMEOUT], [ETH_ORACLE_TIMEOUT]], + oracleErrors: [[MOG_ORACLE_ERROR], [ETH_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol:AerodromeVolatileCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 2a87d5500..028ab0e55 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -93,6 +93,7 @@ async function main() { 'assets/verify_stg.ts', 'collateral-plugins/verify_aerodrome_usdc_eusd.ts', 'collateral-plugins/verify_aerodrome_weth_aero.ts', + 'collateral-plugins/verify_aerodrome_mog_weth.ts', 'collateral-plugins/verify_morphoeUSD.ts' ) } else if (chainId == '42161' || chainId == '421614') { From 8dd168400430d28e7a23592a1878a86ee98f6f74 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 23:39:21 +0900 Subject: [PATCH 31/42] remove itChecksMainChainlinkOracleRevert --- .../individual-collateral/aave-v3/common.ts | 1 - .../AerodromeStableCollateral.test.ts | 2 +- .../ankr/AnkrEthCollateralTestSuite.test.ts | 1 - .../cbeth/CBETHCollateral.test.ts | 1 - .../cbeth/CBETHCollateralL2.test.ts | 1 - .../individual-collateral/collateralTests.ts | 52 ++++++++----------- .../compoundv3/CometTestSuite.test.ts | 1 - .../dsr/SDaiCollateralTestSuite.test.ts | 1 - .../ethena/USDeFiatCollateral.test.ts | 1 - .../ethx/ETHxCollateral.test.ts | 1 - .../flux-finance/FTokenFiatCollateral.test.ts | 1 - .../frax-eth/SFrxEthTestSuite.test.ts | 1 - .../frax/SFraxCollateralTestSuite.test.ts | 1 - .../lido/L2LidoStakedEthTestSuite.test.ts | 1 - .../lido/LidoStakedEthTestSuite.test.ts | 1 - .../MetaMorphoFiatCollateral.test.ts | 1 - ...etaMorphoSelfReferentialCollateral.test.ts | 1 - .../MorphoAAVEFiatCollateral.test.ts | 1 - .../MorphoAAVENonFiatCollateral.test.ts | 1 - ...orphoAAVESelfReferentialCollateral.test.ts | 1 - .../mountain/USDMCollateral.test.ts | 1 - .../pirex-eth/ApxEthCollateral.test.ts | 1 - .../individual-collateral/pluginTestTypes.ts | 3 -- .../RethCollateralTestSuite.test.ts | 1 - .../StargateUSDCTestSuite.test.ts_DEPRECATED | 1 - .../YearnV2CurveFiatCollateral.test.ts | 1 - 26 files changed, 22 insertions(+), 58 deletions(-) diff --git a/test/plugins/individual-collateral/aave-v3/common.ts b/test/plugins/individual-collateral/aave-v3/common.ts index f9f22f6a2..77e11f1be 100644 --- a/test/plugins/individual-collateral/aave-v3/common.ts +++ b/test/plugins/individual-collateral/aave-v3/common.ts @@ -210,7 +210,6 @@ export const makeTests = (defaultCollateralOpts: CollateralParams, altParams: Al itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, itChecksPriceChanges: it, diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index bd6c9e518..f0cd89c1f 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -158,6 +158,7 @@ all.forEach((curr: AeroStablePoolEnumeration) => { } opts = { ...defaultCollateralOpts, ...opts } + opts.feeds![0][0] = opts.chainlinkFeed! const AeroStableCollateralFactory: ContractFactory = await ethers.getContractFactory( 'AerodromeStableCollateral' @@ -450,7 +451,6 @@ all.forEach((curr: AeroStablePoolEnumeration) => { itChecksRefPerTokDefault: it.skip, itChecksPriceChanges: it.skip, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it.skip, itHasRevenueHiding: it.skip, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index ed9bae870..8a3a07a83 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -291,7 +291,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 6dea339bc..cd35ee5c0 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -248,7 +248,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index 7e1a28744..a4a9c3242 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -279,7 +279,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateralL2', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 1d2fb32df..c70651b06 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -16,13 +16,7 @@ import { } from './fixtures' import { expectInIndirectReceipt } from '../../../common/events' import { whileImpersonating } from '../../utils/impersonation' -import { - IConfig, - IGovParams, - IGovRoles, - IRTokenSetup, - networkConfig, -} from '../../../common/configuration' +import { IGovParams, IGovRoles, IRTokenSetup, networkConfig } from '../../../common/configuration' import { advanceBlocks, advanceTime, @@ -93,7 +87,6 @@ export default function fn( itChecksRefPerTokDefault, itChecksPriceChanges, itChecksNonZeroDefaultThreshold, - itChecksMainChainlinkOracleRevert, itHasRevenueHiding, itIsPricedByPeg, itHasOracleRefPerTok, @@ -401,32 +394,29 @@ export default function fn( ) // within 1-part-in-1-thousand }) - itChecksMainChainlinkOracleRevert( - 'reverts if Chainlink feed reverts or runs out of gas, maintains status', - async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) - ) + it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) - const invalidCollateral = await deployCollateral({ - erc20: ctx.tok.address, - chainlinkFeed: invalidChainlinkFeed.address, - }) + const invalidCollateral = await deployCollateral({ + erc20: ctx.tok.address, + chainlinkFeed: invalidChainlinkFeed.address, + }) - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - } - ) + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + }) it('decays price over priceTimeout period', async () => { await collateral.refresh() diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 5b5c7ba6f..c0062e93d 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -384,7 +384,6 @@ allTests.forEach((curr: CTokenV3Enumeration) => { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, resetFork: getResetFork(getForkBlock(curr.tokenName)), diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 52549b704..2919f0ebc 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -215,7 +215,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, resetFork, collateralName: 'SDaiCollateral', diff --git a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts index 6a6528d9f..9624aaa70 100644 --- a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts @@ -211,7 +211,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, collateralName: 'USDe Fiat Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts index b3c380480..10b4da87c 100644 --- a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts +++ b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts @@ -282,7 +282,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'Stader ETHx', diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index eabfa1269..180a88935 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -258,7 +258,6 @@ all.forEach((curr: FTokenEnumeration) => { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index b25608382..b15e1df41 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -311,7 +311,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it.skip, // implemented in this file itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'SFraxEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index e9ec2b3e2..2ed9bc5e4 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -195,7 +195,6 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it.skip, diff --git a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts index 7ac7956e2..184b1090a 100644 --- a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts @@ -278,7 +278,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK_BASE), collateralName: 'L2LidoStakedETH', diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 09bb47a51..366c8c81c 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -269,7 +269,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'LidoStakedETH', diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts index 05429c153..f8138befb 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts @@ -175,7 +175,6 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts index 44b009bd6..d243ec85a 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -171,7 +171,6 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 27f971c04..9a9b94fad 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -368,7 +368,6 @@ const makeAaveFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index e846ec7e8..937ec99e7 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -229,7 +229,6 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 71100d2a3..81404fe20 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -229,7 +229,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', diff --git a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts index 3605c491a..570c60345 100644 --- a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts +++ b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts @@ -311,7 +311,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file collateralName: 'USDM Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts index e49008c64..db143ad60 100644 --- a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts +++ b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts @@ -378,7 +378,6 @@ const opts = { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file resetFork, collateralName: 'ApxETH', diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index d65722bf8..837e5a258 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -106,9 +106,6 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that check that defaultThreshold is not zero itChecksNonZeroDefaultThreshold: Mocha.TestFunction | Mocha.PendingTestFunction - // toggle on or off: tests that check when the main chainlink feed reverts (not always used) - itChecksMainChainlinkOracleRevert: Mocha.TestFunction | Mocha.PendingTestFunction - // does the peg price matter for the results of tryPrice()? itIsPricedByPeg?: boolean diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index 48017d264..f766a3bc0 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -276,7 +276,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'RocketPoolETH', diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED index f3e64455d..009ffdf1a 100644 --- a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED @@ -316,7 +316,6 @@ export const stableOpts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, diff --git a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts index 9b34573b7..22017205a 100644 --- a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts @@ -260,7 +260,6 @@ tests.forEach((test: CurveFiatTest) => { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itClaimsRewards: it.skip, isMetapool: false, From 26427bf488142474da020dbf7f8aeab3c1af456c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 23:40:31 +0900 Subject: [PATCH 32/42] nit --- common/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/configuration.ts b/common/configuration.ts index 05e72d675..acd5d5079 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -544,7 +544,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h - eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', + eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', // 0.5%, 24h AERO: '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', // 0.5%, 24h MOG: '0x4aeb6D15769EaD32D0c5Be2940F40c7CFf53801d', // 0.5%, 24h }, From afb3b61fa1c70be10b731b074e5b35f5a4ad7282 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 6 Nov 2024 00:14:34 +0900 Subject: [PATCH 33/42] fix WETH/AERO tests --- .../aerodrome/AerodromeVolatileCollateral.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts index c684598c9..a80432ab2 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts @@ -135,7 +135,7 @@ const all: AeroVolatilePoolEnumeration[] = [ ], oracleTimeout: AERO_ORACLE_TIMEOUT, // max oracleError: AERO_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined - amountScaleDivisor: bn('1'), + amountScaleDivisor: bn('1e2'), toleranceDivisor: bn('1e4'), }, ] From c40b21dcc21a5231b35241a849f63bf860674137 Mon Sep 17 00:00:00 2001 From: Julian R Date: Tue, 5 Nov 2024 15:22:41 -0300 Subject: [PATCH 34/42] adapt tests --- .../aerodrome/AerodromeStableCollateral.test.ts | 3 +++ .../aerodrome/AerodromeVolatileCollateral.test.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts index f0cd89c1f..b66520f0d 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeStableCollateral.test.ts @@ -318,6 +318,7 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const coll = await deployCollateral({ pool: curr.pool, gauge: curr.gauge, + chainlinkFeed: feed0.address, feeds: [[feed0.address], [feed1.address]], }) @@ -350,6 +351,7 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const coll = await deployCollateral({ pool: curr.pool, gauge: curr.gauge, + chainlinkFeed: feed0.address, feeds: [[feed0.address], [feed1.address]], }) @@ -390,6 +392,7 @@ all.forEach((curr: AeroStablePoolEnumeration) => { const invalidCollateral = await deployCollateral({ pool: curr.pool, gauge: curr.gauge, + chainlinkFeed: invalidChainlinkFeed.address, feeds: [[invalidChainlinkFeed.address], [invalidChainlinkFeed.address]], }) diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts index a80432ab2..e0ffb245e 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts @@ -328,6 +328,7 @@ all.forEach((curr: AeroVolatilePoolEnumeration) => { const coll = await deployCollateral({ pool: curr.pool, gauge: curr.gauge, + chainlinkFeed: feed0.address, feeds: [[feed0.address], [feed1.address]], }) From 0afed2692f439725c881d2642bfd80dd3e6401d9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 6 Nov 2024 15:27:34 +0900 Subject: [PATCH 35/42] cleanup merge --- common/configuration.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index 12dc0dd8f..3a7843019 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -121,7 +121,7 @@ export interface ITokens { // Aerodrome AERO?: string - + MOG?: string } @@ -151,11 +151,8 @@ export interface IPools { crvMIM3Pool?: string sdUSDCUSDCPlus?: string aeroUSDCeUSD?: string -<<<<<<< HEAD aeroWETHAERO?: string aeroMOGWETH?: string -======= ->>>>>>> 4.0.0 } interface INetworkConfig { @@ -550,10 +547,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', // 0.5%, 24h AERO: '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', // 0.5%, 24h -<<<<<<< HEAD MOG: '0x4aeb6D15769EaD32D0c5Be2940F40c7CFf53801d', // 0.5%, 24h -======= ->>>>>>> 4.0.0 }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', From 5f912744f57cdedcf64757a34fcebb599fc3b31a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 6 Nov 2024 15:31:37 +0900 Subject: [PATCH 36/42] remove itChecksMainChainlinkOracleRevert --- .../individual-collateral/aave-v3/common.ts | 1 - .../ankr/AnkrEthCollateralTestSuite.test.ts | 1 - .../cbeth/CBETHCollateral.test.ts | 1 - .../cbeth/CBETHCollateralL2.test.ts | 1 - .../individual-collateral/collateralTests.ts | 44 +++++++++---------- .../compoundv3/CometTestSuite.test.ts | 1 - .../dsr/SDaiCollateralTestSuite.test.ts | 1 - .../ethena/USDeFiatCollateral.test.ts | 1 - .../ethx/ETHxCollateral.test.ts | 1 - .../flux-finance/FTokenFiatCollateral.test.ts | 1 - .../frax-eth/SFrxEthTestSuite.test.ts | 1 - .../frax/SFraxCollateralTestSuite.test.ts | 1 - .../lido/L2LidoStakedEthTestSuite.test.ts | 1 - .../lido/LidoStakedEthTestSuite.test.ts | 1 - .../MetaMorphoFiatCollateral.test.ts | 1 - ...etaMorphoSelfReferentialCollateral.test.ts | 1 - .../MorphoAAVEFiatCollateral.test.ts | 1 - .../MorphoAAVENonFiatCollateral.test.ts | 1 - ...orphoAAVESelfReferentialCollateral.test.ts | 1 - .../mountain/USDMCollateral.test.ts | 1 - .../pirex-eth/ApxEthCollateral.test.ts | 1 - .../individual-collateral/pluginTestTypes.ts | 3 -- .../RethCollateralTestSuite.test.ts | 1 - .../StargateUSDCTestSuite.test.ts_DEPRECATED | 1 - .../YearnV2CurveFiatCollateral.test.ts | 1 - 25 files changed, 20 insertions(+), 50 deletions(-) diff --git a/test/plugins/individual-collateral/aave-v3/common.ts b/test/plugins/individual-collateral/aave-v3/common.ts index f9f22f6a2..77e11f1be 100644 --- a/test/plugins/individual-collateral/aave-v3/common.ts +++ b/test/plugins/individual-collateral/aave-v3/common.ts @@ -210,7 +210,6 @@ export const makeTests = (defaultCollateralOpts: CollateralParams, altParams: Al itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, itChecksPriceChanges: it, diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index ed9bae870..8a3a07a83 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -291,7 +291,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 6dea339bc..cd35ee5c0 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -248,7 +248,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index 7e1a28744..a4a9c3242 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -279,7 +279,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'CBEthCollateralL2', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index bd6cab8e0..6db2039ed 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -87,7 +87,6 @@ export default function fn( itChecksRefPerTokDefault, itChecksPriceChanges, itChecksNonZeroDefaultThreshold, - itChecksMainChainlinkOracleRevert, itHasRevenueHiding, itIsPricedByPeg, itHasOracleRefPerTok, @@ -395,32 +394,29 @@ export default function fn( ) // within 1-part-in-1-thousand }) - itChecksMainChainlinkOracleRevert( - 'reverts if Chainlink feed reverts or runs out of gas, maintains status', - async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) - ) + it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) - const invalidCollateral = await deployCollateral({ - erc20: ctx.tok.address, - chainlinkFeed: invalidChainlinkFeed.address, - }) + const invalidCollateral = await deployCollateral({ + erc20: ctx.tok.address, + chainlinkFeed: invalidChainlinkFeed.address, + }) - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - } - ) + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + }) it('decays price over priceTimeout period', async () => { await collateral.refresh() diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 5b5c7ba6f..c0062e93d 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -384,7 +384,6 @@ allTests.forEach((curr: CTokenV3Enumeration) => { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, resetFork: getResetFork(getForkBlock(curr.tokenName)), diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 52549b704..2919f0ebc 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -215,7 +215,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, resetFork, collateralName: 'SDaiCollateral', diff --git a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts index 6a6528d9f..9624aaa70 100644 --- a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts @@ -211,7 +211,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, collateralName: 'USDe Fiat Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts index b3c380480..10b4da87c 100644 --- a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts +++ b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts @@ -282,7 +282,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'Stader ETHx', diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index eabfa1269..180a88935 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -258,7 +258,6 @@ all.forEach((curr: FTokenEnumeration) => { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index b25608382..b15e1df41 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -311,7 +311,6 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it.skip, // implemented in this file itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, resetFork, collateralName: 'SFraxEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index e9ec2b3e2..2ed9bc5e4 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -195,7 +195,6 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it.skip, diff --git a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts index 7ac7956e2..184b1090a 100644 --- a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts @@ -278,7 +278,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK_BASE), collateralName: 'L2LidoStakedETH', diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 09bb47a51..366c8c81c 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -269,7 +269,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'LidoStakedETH', diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts index 05429c153..f8138befb 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts @@ -175,7 +175,6 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts index 44b009bd6..d243ec85a 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -171,7 +171,6 @@ const makeFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), targetNetwork: defaultCollateralOpts.forkNetwork, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 27f971c04..9a9b94fad 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -368,7 +368,6 @@ const makeAaveFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index e846ec7e8..937ec99e7 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -229,7 +229,6 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 71100d2a3..81404fe20 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -229,7 +229,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', diff --git a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts index 3605c491a..570c60345 100644 --- a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts +++ b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts @@ -311,7 +311,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file collateralName: 'USDM Collateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts index e49008c64..db143ad60 100644 --- a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts +++ b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts @@ -378,7 +378,6 @@ const opts = { itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it.skip, // implemented in this file resetFork, collateralName: 'ApxETH', diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index d65722bf8..837e5a258 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -106,9 +106,6 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that check that defaultThreshold is not zero itChecksNonZeroDefaultThreshold: Mocha.TestFunction | Mocha.PendingTestFunction - // toggle on or off: tests that check when the main chainlink feed reverts (not always used) - itChecksMainChainlinkOracleRevert: Mocha.TestFunction | Mocha.PendingTestFunction - // does the peg price matter for the results of tryPrice()? itIsPricedByPeg?: boolean diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index 48017d264..f766a3bc0 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -276,7 +276,6 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, resetFork, collateralName: 'RocketPoolETH', diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED index f3e64455d..009ffdf1a 100644 --- a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts_DEPRECATED @@ -316,7 +316,6 @@ export const stableOpts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksNonZeroDefaultThreshold: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, diff --git a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts index 9b34573b7..22017205a 100644 --- a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts @@ -260,7 +260,6 @@ tests.forEach((test: CurveFiatTest) => { itChecksTargetPerRefDefault: it, itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, - itChecksMainChainlinkOracleRevert: it, itHasRevenueHiding: it, itClaimsRewards: it.skip, isMetapool: false, From bdbce4eb469b477bb8780490e61c7192e7790cc8 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:59:40 -0300 Subject: [PATCH 37/42] new deployed addresses for stable (#1228) --- scripts/addresses/8453-tmp-assets-collateral.json | 10 +++++----- .../base-4.0.0/8453-tmp-assets-collateral.json | 12 ++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index fbda4eb04..94fe70614 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -13,11 +13,11 @@ "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", + "aeroUSDCeUSD": "0x87eBB7e1B23f124100DCC98915F34cf72C1b4Db7", "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314", "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", - "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4" + "aeroUSDzUSDC": "0x02c4dae9D927beD679C72569ab9dB0290223F872" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -30,11 +30,11 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "aeroUSDCeUSD": "0x2A971f27580Ca3aFF3b48Be360D2CE1b0Cc6b206", "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" + "aeroUSDzUSDC": "0xD21Ec7ca3c856f64539F364870b95647Ba30a857" } -} +} \ No newline at end of file diff --git a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json index c72e62328..6e57630a0 100644 --- a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json @@ -4,13 +4,17 @@ }, "collateral": { "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", - "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", - "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4" + "aeroUSDCeUSD": "0x87eBB7e1B23f124100DCC98915F34cf72C1b4Db7", + "aeroUSDzUSDC": "0x02c4dae9D927beD679C72569ab9dB0290223F872", + "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", + "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314" }, "erc20s": { "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", - "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "aeroUSDCeUSD": "0x2A971f27580Ca3aFF3b48Be360D2CE1b0Cc6b206", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" + "aeroUSDzUSDC": "0xD21Ec7ca3c856f64539F364870b95647Ba30a857", + "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", + "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4" } } From f2f9fa67b3c661ec28f49c84a2b76c5604fe5c18 Mon Sep 17 00:00:00 2001 From: Julian R Date: Thu, 7 Nov 2024 16:03:00 -0300 Subject: [PATCH 38/42] rollback deployments --- scripts/addresses/8453-tmp-assets-collateral.json | 8 ++++---- .../addresses/base-4.0.0/8453-tmp-assets-collateral.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 94fe70614..c94fb446c 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -13,11 +13,11 @@ "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "aeroUSDCeUSD": "0x87eBB7e1B23f124100DCC98915F34cf72C1b4Db7", + "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314", "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", - "aeroUSDzUSDC": "0x02c4dae9D927beD679C72569ab9dB0290223F872" + "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -30,11 +30,11 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "aeroUSDCeUSD": "0x2A971f27580Ca3aFF3b48Be360D2CE1b0Cc6b206", + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "aeroUSDzUSDC": "0xD21Ec7ca3c856f64539F364870b95647Ba30a857" + "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" } } \ No newline at end of file diff --git a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json index 6e57630a0..dc44e70ed 100644 --- a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json @@ -4,16 +4,16 @@ }, "collateral": { "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", - "aeroUSDCeUSD": "0x87eBB7e1B23f124100DCC98915F34cf72C1b4Db7", - "aeroUSDzUSDC": "0x02c4dae9D927beD679C72569ab9dB0290223F872", + "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", + "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4", "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314" }, "erc20s": { "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", - "aeroUSDCeUSD": "0x2A971f27580Ca3aFF3b48Be360D2CE1b0Cc6b206", + "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "aeroUSDzUSDC": "0xD21Ec7ca3c856f64539F364870b95647Ba30a857", + "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61", "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4" } From 975b6fb667ceba28cd0082f30e1bd482b3fafcee Mon Sep 17 00:00:00 2001 From: Julian R Date: Thu, 7 Nov 2024 16:04:14 -0300 Subject: [PATCH 39/42] lint --- scripts/addresses/8453-tmp-assets-collateral.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index c94fb446c..fbda4eb04 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -37,4 +37,4 @@ "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" } -} \ No newline at end of file +} From b13ffa2a782763619c9c0b68a04666a1da1c8362 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 11 Nov 2024 13:52:40 +0700 Subject: [PATCH 40/42] fix AerodomeVolatileCollateral pricing --- .../assets/aerodrome/AerodromePoolTokens.sol | 4 +- .../aerodrome/AerodromeStableCollateral.sol | 25 +++++++------ .../aerodrome/AerodromeVolatileCollateral.sol | 37 +++++++------------ 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 07fe5d12e..2ec03470a 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -178,8 +178,8 @@ contract AerodromePoolTokens { } /// @param index The index of the token: 0 or 1 - /// @return [{ref_index}] - function tokenReserve(uint8 index) public view virtual returns (uint256) { + /// @return {ref_index} + function tokenReserve(uint8 index) public view virtual returns (uint192) { if (index >= nTokens) revert WrongIndex(nTokens - 1); // Maybe also cache token decimals as immutable? IERC20Metadata tokenInterface = getToken(index); diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index c6389df43..2410006c0 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -45,38 +45,39 @@ contract AerodromeStableCollateral is AerodromeVolatileCollateral { uint192 pegPrice ) { - uint256 r0 = tokenReserve(0); - uint256 r1 = tokenReserve(1); + uint256 r0 = tokenReserve(0); // {ref_0} upcast + uint256 r1 = tokenReserve(1); // {ref_1} upcast // xy^3 + yx^3 >= k for sAMM pools uint256 sqrtReserve = sqrt256(sqrt256(r0 * r1) * sqrt256(r0 * r0 + r1 * r1)); // get token prices - (uint192 p0_low, uint192 p0_high) = tokenPrice(0); - (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + (uint192 p0_low, uint192 p0_high) = tokenPrice(0); // {UoA/ref_0} + (uint192 p1_low, uint192 p1_high) = tokenPrice(1); // {UoA/ref_1} - uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + // all aero pools have 18 decimals + uint192 totalSupply = _safeWrap(pool.totalSupply()); // low { - uint256 ratioLow = ((1e18) * p0_high) / p1_low; + uint256 ratioLow = (1e18 * p0_high) / p1_low; uint256 sqrtPriceLow = sqrt256( - sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) + sqrt256(1e18 * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) ); - low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); + low = _safeWrap((((1e18 * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); } // high { - uint256 ratioHigh = ((1e18) * p0_low) / p1_high; + uint256 ratioHigh = (1e18 * p0_low) / p1_high; uint256 sqrtPriceHigh = sqrt256( - sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) + sqrt256(1e18 * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) ); high = _safeWrap( - ((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply + (((1e18 * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply ); } - assert(low <= high); //obviously true just by inspection + assert(low <= high); // not obviously true just by inspection // {target/ref} = {UoA/ref} = {UoA/tok} / ({ref/tok} // {target/ref} and {UoA/ref} are the same since target == UoA diff --git a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol index 6635f97b7..9b1b8a075 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol @@ -38,6 +38,7 @@ contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { AerodromePoolTokens(aptConfig) { assert((token0.decimals() + token1.decimals()) % 2 == 0); + assert(pool.decimals() == 18); maxOracleTimeout = uint48(Math.max(maxOracleTimeout, maxPoolOracleTimeout())); } @@ -59,37 +60,27 @@ contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { uint192 pegPrice ) { - uint256 r0 = tokenReserve(0); - uint256 r1 = tokenReserve(1); + uint192 r0 = tokenReserve(0); // {ref_0} + uint192 r1 = tokenReserve(1); // {ref_1} // x * y >= k for vAMM pools - uint256 sqrtReserve = sqrt256(r0 * r1); + uint192 sqrtReserve = r0.mul(r1, ROUND).sqrt(); // get token prices - (uint192 p0_low, uint192 p0_high) = tokenPrice(0); - (uint192 p1_low, uint192 p1_high) = tokenPrice(1); + (uint192 p0_low, uint192 p0_high) = tokenPrice(0); // {UoA/ref_0} + (uint192 p1_low, uint192 p1_high) = tokenPrice(1); // {UoA/ref_1} - uint192 totalSupply = shiftl_toFix(pool.totalSupply(), -int8(pool.decimals()), FLOOR); + // all aero pools have 18 decimals already + uint192 totalSupply = _safeWrap(pool.totalSupply()); // low - { - uint256 ratioLow = ((1e18) * p0_high) / p1_low; - uint256 sqrtPriceLow = sqrt256( - sqrt256((1e18) * ratioLow) * sqrt256(1e36 + ratioLow * ratioLow) - ); - low = _safeWrap(((((1e18) * sqrtReserve) / sqrtPriceLow) * p0_low * 2) / totalSupply); - } + uint192 sqrtPriceLow = p0_high.div(p1_low, CEIL).sqrt(); + low = sqrtReserve.mulDiv(p0_low, sqrtPriceLow, FLOOR).mulDiv(2e18, totalSupply, FLOOR); + // high - { - uint256 ratioHigh = ((1e18) * p0_low) / p1_high; - uint256 sqrtPriceHigh = sqrt256( - sqrt256((1e18) * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) - ); - - high = _safeWrap( - ((((1e18) * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply - ); - } + uint192 sqrtPriceHigh = p0_low.div(p1_high, FLOOR).sqrt(); + high = sqrtReserve.mulDiv(p0_high, sqrtPriceHigh, CEIL).mulDiv(2e18, totalSupply, CEIL); + assert(low <= high); // not obviously true just by inspection pegPrice = 0; // no default checks or issuance premium From 6af5d8301ff0d104c4a3bfe62244b3ab7655e4f9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 11 Nov 2024 14:00:12 +0700 Subject: [PATCH 41/42] addresses --- scripts/addresses/8453-tmp-assets-collateral.json | 10 +++++----- .../base-4.0.0/8453-tmp-assets-collateral.json | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index fbda4eb04..7951c5b66 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -14,8 +14,8 @@ "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", - "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", - "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314", + "aeroWETHAERO": "0x1bD9eAEB0eD1aB51dA1Ff8D437892488790bbde7", + "aeroMOGWETH": "0x6Ec09BD7d32F1023626AaEa1090e178d81C7701A", "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4" }, @@ -31,10 +31,10 @@ "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", - "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", - "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4", + "aeroWETHAERO": "0x3712DDCF9aE516dD01042948B76A76901a84CD36", + "aeroMOGWETH": "0xCcC18B21be01a37ebFa5C932eD09574752F88C15", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" } -} +} \ No newline at end of file diff --git a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json index dc44e70ed..e6cd64e24 100644 --- a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json @@ -6,15 +6,15 @@ "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4", - "aeroWETHAERO": "0x5f053DbcF37E31250F15787E1577F7A6A07d486B", - "aeroMOGWETH": "0xf8cC32E8159C0bED61f947251d6D07E577D21314" + "aeroWETHAERO": "0x1bD9eAEB0eD1aB51dA1Ff8D437892488790bbde7", + "aeroMOGWETH": "0x6Ec09BD7d32F1023626AaEa1090e178d81C7701A" }, "erc20s": { "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61", - "aeroWETHAERO": "0x0DefA46e5e7258c9a776ADd56e6875a9323B9998", - "aeroMOGWETH": "0x7600cDA692679dF4928A883a8F1c5687b2991DF4" + "aeroWETHAERO": "0x3712DDCF9aE516dD01042948B76A76901a84CD36", + "aeroMOGWETH": "0xCcC18B21be01a37ebFa5C932eD09574752F88C15" } } From ad37084f75daca92754e9114c21194d02e35c019 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:04:11 -0300 Subject: [PATCH 42/42] Fixes and new volatile plugins and addresses (#1230) --- common/configuration.ts | 12 ++ .../assets/aerodrome/AerodromePoolTokens.sol | 4 +- .../aerodrome/AerodromeStableCollateral.sol | 10 +- .../aerodrome/AerodromeVolatileCollateral.sol | 2 +- contracts/plugins/assets/aerodrome/README.md | 17 ++- .../addresses/8453-tmp-assets-collateral.json | 18 ++- scripts/addresses/8453-tmp-deployments.json | 2 +- .../8453-tmp-assets-collateral.json | 14 +- scripts/deploy.ts | 3 + .../collaterals/deploy_aerodrome_mog_weth.ts | 8 +- .../collaterals/deploy_aerodrome_weth_aero.ts | 6 +- .../deploy_aerodrome_weth_cbbtc.ts | 140 ++++++++++++++++++ .../deploy_aerodrome_weth_degen.ts | 140 ++++++++++++++++++ .../collaterals/deploy_aerodrome_weth_well.ts | 140 ++++++++++++++++++ .../verify_aerodrome_weth_cbbtc.ts | 100 +++++++++++++ .../verify_aerodrome_weth_degen.ts | 100 +++++++++++++ .../verify_aerodrome_weth_well.ts | 100 +++++++++++++ scripts/verify_etherscan.ts | 3 + .../AerodromeVolatileCollateral.test.ts | 102 +++++++++++++ .../aerodrome/constants.ts | 39 ++++- .../aerodrome/helpers.ts | 2 +- 21 files changed, 922 insertions(+), 40 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_cbbtc.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_degen.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_well.ts create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_weth_cbbtc.ts create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_weth_degen.ts create mode 100644 scripts/verification/collateral-plugins/verify_aerodrome_weth_well.ts diff --git a/common/configuration.ts b/common/configuration.ts index 609bbe3a6..6794a846f 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -124,6 +124,9 @@ export interface ITokens { MOG?: string USDz?: string + cbBTC?: string + WELL?: string + DEGEN?: string // Sky USDS?: string @@ -159,6 +162,9 @@ export interface IPools { aeroWETHAERO?: string aeroMOGWETH?: string aeroUSDzUSDC?: string + aeroWETHcbBTC?: string + aeroWETHWELL?: string + aeroWETHDEGEN?: string } interface INetworkConfig { @@ -541,6 +547,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { USDz: '0x04D5ddf5f3a8939889F11E97f8c4BB48317F1938', meUSD: '0xbb819D845b573B5D7C538F5b85057160cfb5f313', AERO: '0x940181a94A35A4569E4529A3CDfB74e38FD98631', + cbBTC: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', + WELL: '0xA88594D404727625A9437C3f886C7643872296AE', + DEGEN: '0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -561,6 +570,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { USDz: '0xe25969e2Fa633a0C027fAB8F30Fc9C6A90D60B48', // 0.5%, 24h AERO: '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', // 0.5%, 24h MOG: '0x4aeb6D15769EaD32D0c5Be2940F40c7CFf53801d', // 0.5%, 24h + cbBTC: '0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D', // 0.3%, 20 min + WELL: '0xc15d9944dAefE2dB03e53bef8DDA25a56832C5fe', // 0.5%, 24h + DEGEN: '0xE62BcE5D7CB9d16AB8b4D622538bc0A50A5799c2', // 0.5%, 24h }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol index 2ec03470a..fa62c625e 100644 --- a/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol +++ b/contracts/plugins/assets/aerodrome/AerodromePoolTokens.sol @@ -8,7 +8,7 @@ import "contracts/plugins/assets/OracleLib.sol"; import "contracts/libraries/Fixed.sol"; import "./vendor/IAeroPool.sol"; -/// Supports Aerodrome stable pools (2 tokens) +/// Supports Aerodrome stable and volatile pools (2 tokens) contract AerodromePoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -20,7 +20,7 @@ contract AerodromePoolTokens { enum AeroPoolType { Stable, - Volatile // not supported in this version + Volatile } // === State (Immutable) === diff --git a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol index 2410006c0..5c7965702 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeStableCollateral.sol @@ -19,7 +19,7 @@ contract AerodromeStableCollateral is AerodromeVolatileCollateral { using FixLib for uint192; /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout - /// @dev No revenue hiding (refPerTok() == FIX_ONE) + /// @dev No revenue hiding (refPerTok() is constant) /// @dev config.erc20 should be an AerodromeStakingWrapper constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) AerodromeVolatileCollateral(config, aptConfig) @@ -73,15 +73,11 @@ contract AerodromeStableCollateral is AerodromeVolatileCollateral { sqrt256(1e18 * ratioHigh) * sqrt256(1e36 + ratioHigh * ratioHigh) ); - high = _safeWrap( - (((1e18 * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply - ); + high = _safeWrap((((1e18 * sqrtReserve) / sqrtPriceHigh) * p0_high * 2) / totalSupply); } assert(low <= high); // not obviously true just by inspection - // {target/ref} = {UoA/ref} = {UoA/tok} / ({ref/tok} - // {target/ref} and {UoA/ref} are the same since target == UoA - pegPrice = ((low + high) / 2).div(refPerTok()); + pegPrice = 0; } // === Internal === diff --git a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol index 9b1b8a075..8b8e8bd9f 100644 --- a/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol +++ b/contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol @@ -31,7 +31,7 @@ contract AerodromeVolatileCollateral is FiatCollateral, AerodromePoolTokens { using FixLib for uint192; /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimout - /// @dev No revenue hiding (refPerTok() == FIX_ONE) + /// @dev No revenue hiding (refPerTok() is constant) /// @dev config.erc20 should be an AerodromeStakingWrapper constructor(CollateralConfig memory config, APTConfiguration memory aptConfig) FiatCollateral(config) diff --git a/contracts/plugins/assets/aerodrome/README.md b/contracts/plugins/assets/aerodrome/README.md index 82cf963aa..0eea2e013 100644 --- a/contracts/plugins/assets/aerodrome/README.md +++ b/contracts/plugins/assets/aerodrome/README.md @@ -1,12 +1,12 @@ -# Aerodrome Stable Collateral Plugin +# Aerodrome Collateral Plugins -[Aerodrome Finance](https://aerodrome.finance) is an AMM designed to serve as Base's central liquidity hub. This plugin enables the use of any Aerodrome Stable LP token as collateral within the Reserve Protocol. +[Aerodrome Finance](https://aerodrome.finance) is an AMM designed to serve as Base's central liquidity hub. This plugin enables the use of any Aerodrome Stable and Volatile LP token as collateral within the Reserve Protocol. Aerodrome Finance offers two different liquidity pool types based on token pair needs, `Stable Pools` and `Volatile Pools`. -Only `Stable Pools` are currently supported. These pools are designed for tokens which have little to no volatility, and use the current formula for pricing tokens: +`Stable Pools` are designed for tokens which have little to no volatility, and use the current formula for pricing tokens: `x³y + y³x ≥ k` -`x³y + y³x ≥ k` +`Volatile Pools` are designed for tokens with high price volatility, and use a generic AMM formula: `x × y ≥ k` ## Usage @@ -36,10 +36,11 @@ Internally, all `tokensPriceFeeds` are stored as multiple separate immutable var ### refPerTok -Aerodrome Stable Pools do not appreciate in value over time, so `refPerTok()` will be constant for these plugins and will not change. This also means there are no hard default checks in place. +Aerodrome Pools do not appreciate in value over time, so `refPerTok()` will be constant for these plugins and will not change. This also means there are no hard default checks in place. ## Implementation -| `tok` | `ref` | `target` | `UoA` | -| :------------------: | :---------------: | :------: | :---: | -| Aero Staking Wrapper | LP token /w shift | USD | USD | +| `tok` | `ref` | `target` | `UoA` | +| :------------------: | :---------------: | :--------------: | :---: | +| Aero Staking Wrapper | LP token /w shift | USD (Stable) | USD | +| Aero Staking Wrapper | LP token /w shift | cpAMM (Volatile) | USD | diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 7951c5b66..33e58ecf6 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -14,10 +14,13 @@ "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", - "aeroWETHAERO": "0x1bD9eAEB0eD1aB51dA1Ff8D437892488790bbde7", - "aeroMOGWETH": "0x6Ec09BD7d32F1023626AaEa1090e178d81C7701A", + "aeroWETHAERO": "0xaC23df4289e2FeaFb2851e63958C2AAF41DB4833", + "aeroMOGWETH": "0xfAa0cfc986041a3aF69207C6fCF35316D58fEA6a", "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", - "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4" + "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4", + "aeroWETHcbBTC": "0x5ABe1A8b04A2428589bAeF9486d12688d482D061", + "aeroWETHWELL": "0x7df3541FFeDAA956aE6136E64253f3749b055Be6", + "aeroWETHDEGEN": "0xc8E72288f717Da55b7231365daE17AcD91b59705" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -31,10 +34,13 @@ "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", - "aeroWETHAERO": "0x3712DDCF9aE516dD01042948B76A76901a84CD36", - "aeroMOGWETH": "0xCcC18B21be01a37ebFa5C932eD09574752F88C15", + "aeroWETHAERO": "0x65f2c1b253a3E45670aDD259C9688Edf1A3b814d", + "aeroMOGWETH": "0xfaAC26b279338dF8cF56B11A572617f674A2F69C", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61" + "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61", + "aeroWETHcbBTC": "0x4BD08a771CdAbA5333CAc6F20322eD7d72b6cBfA", + "aeroWETHWELL": "0x1F599F8657CAA38Ee825e4E2d64F695749E2a161", + "aeroWETHDEGEN": "0xA762F790a31654D9AeF7DE550A473A0F5621E4F1" } } \ No newline at end of file diff --git a/scripts/addresses/8453-tmp-deployments.json b/scripts/addresses/8453-tmp-deployments.json index 7d32446fa..b34350b2b 100644 --- a/scripts/addresses/8453-tmp-deployments.json +++ b/scripts/addresses/8453-tmp-deployments.json @@ -37,4 +37,4 @@ "stRSR": "0x4Cf200D7fA568611DD8B4BD85053ba9419982C7D" } } -} \ No newline at end of file +} diff --git a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json index e6cd64e24..36f009606 100644 --- a/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json @@ -6,15 +6,21 @@ "meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3", "aeroUSDCeUSD": "0x9216CD5cA133aBBd23cc6F873bB4a95A78032db0", "aeroUSDzUSDC": "0x8AAdfbea33146e28170F99D031B7747EAa87DDD4", - "aeroWETHAERO": "0x1bD9eAEB0eD1aB51dA1Ff8D437892488790bbde7", - "aeroMOGWETH": "0x6Ec09BD7d32F1023626AaEa1090e178d81C7701A" + "aeroWETHAERO": "0xaC23df4289e2FeaFb2851e63958C2AAF41DB4833", + "aeroMOGWETH": "0xfAa0cfc986041a3aF69207C6fCF35316D58fEA6a", + "aeroWETHcbBTC": "0x5ABe1A8b04A2428589bAeF9486d12688d482D061", + "aeroWETHWELL": "0x7df3541FFeDAA956aE6136E64253f3749b055Be6", + "aeroWETHDEGEN": "0xc8E72288f717Da55b7231365daE17AcD91b59705" }, "erc20s": { "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313", "aeroUSDCeUSD": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", "aeroUSDzUSDC": "0x246Df11B856E9fD6120494F168475e1b41321c61", - "aeroWETHAERO": "0x3712DDCF9aE516dD01042948B76A76901a84CD36", - "aeroMOGWETH": "0xCcC18B21be01a37ebFa5C932eD09574752F88C15" + "aeroWETHAERO": "0x65f2c1b253a3E45670aDD259C9688Edf1A3b814d", + "aeroMOGWETH": "0xfaAC26b279338dF8cF56B11A572617f674A2F69C", + "aeroWETHcbBTC": "0x4BD08a771CdAbA5333CAc6F20322eD7d72b6cBfA", + "aeroWETHWELL": "0x1F599F8657CAA38Ee825e4E2d64F695749E2a161", + "aeroWETHDEGEN": "0xA762F790a31654D9AeF7DE550A473A0F5621E4F1" } } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 7bb6cdb3a..1d1199fe4 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -115,6 +115,9 @@ async function main() { 'phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts', 'phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts', 'phase2-assets/collaterals/deploy_aerodrome_usdz_usdc.ts', + 'phase2-assets/collaterals/deploy_aerodrome_weth_cbbtc.ts', + 'phase2-assets/collaterals/deploy_aerodrome_weth_well.ts', + 'phase2-assets/collaterals/deploy_aerodrome_weth_degen.ts', 'phase2-assets/collaterals/deploy_morphoeUSD.ts' ) } else if (chainId == '42161' || chainId == '421614') { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts index d01ec10af..bafaccd84 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_mog_weth.ts @@ -65,7 +65,7 @@ async function main() { // Only for Base if (baseL2Chains.includes(hre.network.name)) { - const AerodromeStableCollateralFactory = await hre.ethers.getContractFactory( + const AerodromeVolatileCollateralFactory = await hre.ethers.getContractFactory( 'AerodromeVolatileCollateral' ) const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') @@ -87,14 +87,14 @@ async function main() { `Deployed wrapper for Aerodrome Volatile MOG-WETH pool on ${hre.network.name} (${chainId}): ${wMogWeth.address} ` ) - const oracleError = combinedError(MOG_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.5% + const oracleError = combinedError(MOG_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% - collateral = await AerodromeStableCollateralFactory.connect( + collateral = await AerodromeVolatileCollateralFactory.connect( deployer ).deploy( { erc20: wMogWeth.address, - targetName: ethers.utils.formatBytes32String('50%ETH50%MOG'), // lexicographical order for tokens + targetName: ethers.utils.formatBytes32String('MOG_ETH_AERODROME_cpAMM'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: oracleError.toString(), // unused but cannot be zero diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts index 302011790..010446fc9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_aero.ts @@ -65,7 +65,7 @@ async function main() { // Only for Base if (baseL2Chains.includes(hre.network.name)) { - const AerodromeStableCollateralFactory = await hre.ethers.getContractFactory( + const AerodromeVolatileCollateralFactory = await hre.ethers.getContractFactory( 'AerodromeVolatileCollateral' ) const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') @@ -89,12 +89,12 @@ async function main() { const oracleError = combinedError(AERO_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% - collateral = await AerodromeStableCollateralFactory.connect( + collateral = await AerodromeVolatileCollateralFactory.connect( deployer ).deploy( { erc20: wWethAero.address, - targetName: ethers.utils.formatBytes32String('50%AERO50%ETH'), // lexicographical order for tokens + targetName: ethers.utils.formatBytes32String('ETH_AERO_AERODROME_cpAMM'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: oracleError.toString(), // unused but cannot be zero diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_cbbtc.ts new file mode 100644 index 000000000..e55b10562 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_cbbtc.ts @@ -0,0 +1,140 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + AerodromeVolatileCollateral, + AerodromeGaugeWrapper, + IAeroPool, +} from '../../../../typechain' +import { combinedError } from '../../utils' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_cbBTC_POOL, + AERO_WETH_cbBTC_GAUGE, + AERO, + cbBTC_USD_FEED, + cbBTC_ORACLE_TIMEOUT, + cbBTC_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Aerodrome volatile plugin: WETH-cbBTCvAMM + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Volatile Pool for WETH-cbBTC **************************/ + + let collateral: AerodromeVolatileCollateral + let wWethcbBTC: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_cbBTC_POOL) + wWethcbBTC = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_cbBTC_GAUGE + ) + ) + await wWethcbBTC.deployed() + + console.log( + `Deployed wrapper for Aerodrome Volatile WETH-cbBTC pool on ${hre.network.name} (${chainId}): ${wWethcbBTC.address} ` + ) + + const oracleError = combinedError(cbBTC_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.3% & 0.15% + + collateral = await AerodromeVolatileCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wWethcbBTC.address, + targetName: ethers.utils.formatBytes32String('ETH_BTC_AERODROME_cpAMM'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: ETH_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', // 24h + }, + { + pool: AERO_WETH_cbBTC_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [cbBTC_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [cbBTC_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [cbBTC_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Volatile Collateral for WETH-cbBTC to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroWETHcbBTC = collateral.address + assetCollDeployments.erc20s.aeroWETHcbBTC = wWethcbBTC.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_degen.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_degen.ts new file mode 100644 index 000000000..cb48b58ae --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_degen.ts @@ -0,0 +1,140 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + AerodromeVolatileCollateral, + AerodromeGaugeWrapper, + IAeroPool, +} from '../../../../typechain' +import { combinedError } from '../../utils' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_DEGEN_POOL, + AERO_WETH_DEGEN_GAUGE, + AERO, + DEGEN_USD_FEED, + DEGEN_ORACLE_TIMEOUT, + DEGEN_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Aerodrome volatile plugin: WETH-DEGEN vAMM + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Volatile Pool for WETH-DEGEN **************************/ + + let collateral: AerodromeVolatileCollateral + let wWethDEGEN: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_DEGEN_POOL) + wWethDEGEN = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_DEGEN_GAUGE + ) + ) + await wWethDEGEN.deployed() + + console.log( + `Deployed wrapper for Aerodrome Volatile WETH-DEGEN pool on ${hre.network.name} (${chainId}): ${wWethDEGEN.address} ` + ) + + const oracleError = combinedError(DEGEN_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + + collateral = await AerodromeVolatileCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wWethDEGEN.address, + targetName: ethers.utils.formatBytes32String('ETH_DEGEN_AERODROME_cpAMM'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: DEGEN_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', // 24h + }, + { + pool: AERO_WETH_DEGEN_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [DEGEN_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [DEGEN_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [DEGEN_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Volatile Collateral for WETH-DEGEN to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroWETHDEGEN = collateral.address + assetCollDeployments.erc20s.aeroWETHDEGEN = wWethDEGEN.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_well.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_well.ts new file mode 100644 index 000000000..624b0961f --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aerodrome_weth_well.ts @@ -0,0 +1,140 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + AerodromeVolatileCollateral, + AerodromeGaugeWrapper, + IAeroPool, +} from '../../../../typechain' +import { combinedError } from '../../utils' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_WELL_POOL, + AERO_WETH_WELL_GAUGE, + AERO, + WELL_USD_FEED, + WELL_ORACLE_TIMEOUT, + WELL_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aerodrome/constants' + +// Aerodrome volatile plugin: WETH-WELL vAMM + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aerodrome Volatile Pool for WETH-WELL **************************/ + + let collateral: AerodromeVolatileCollateral + let wWethWELL: AerodromeGaugeWrapper + + // Only for Base + if (baseL2Chains.includes(hre.network.name)) { + const AerodromeVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'AerodromeVolatileCollateral' + ) + const AerodromeGaugeWrapperFactory = await ethers.getContractFactory('AerodromeGaugeWrapper') + + // Deploy gauge wrapper + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_WELL_POOL) + wWethWELL = ( + await AerodromeGaugeWrapperFactory.deploy( + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_WELL_GAUGE + ) + ) + await wWethWELL.deployed() + + console.log( + `Deployed wrapper for Aerodrome Volatile WETH-WELL pool on ${hre.network.name} (${chainId}): ${wWethWELL.address} ` + ) + + const oracleError = combinedError(WELL_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + + collateral = await AerodromeVolatileCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wWethWELL.address, + targetName: ethers.utils.formatBytes32String('ETH_WELL_AERODROME_cpAMM'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), // unused but cannot be zero + oracleTimeout: WELL_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', // 24h + }, + { + pool: AERO_WETH_WELL_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [WELL_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [WELL_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [WELL_ORACLE_ERROR]], + } + ) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aerodrome Volatile Collateral for WETH-WELL to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aeroWETHWELL = collateral.address + assetCollDeployments.erc20s.aeroWETHWELL = wWethWELL.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_weth_cbbtc.ts b/scripts/verification/collateral-plugins/verify_aerodrome_weth_cbbtc.ts new file mode 100644 index 000000000..061714f4f --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_weth_cbbtc.ts @@ -0,0 +1,100 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_cbBTC_POOL, + AERO_WETH_cbBTC_GAUGE, + AERO, + cbBTC_ORACLE_TIMEOUT, + cbBTC_USD_FEED, + cbBTC_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroWETHcbBTCPoolCollateral = await ethers.getContractAt( + 'AerodromeVolatileCollateral', + deployments.collateral.aeroWETHcbBTC as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_cbBTC_POOL) + await verifyContract( + chainId, + await aeroWETHcbBTCPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_cbBTC_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify WETH-cbBTC plugin **************************/ + const oracleError = combinedError(cbBTC_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.3% & 0.15% + await verifyContract( + chainId, + deployments.collateral.aeroWETHcbBTC, + [ + { + erc20: await aeroWETHcbBTCPoolCollateral.erc20(), + targetName: await aeroWETHcbBTCPoolCollateral.targetName(), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: ETH_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', + }, + { + pool: AERO_WETH_cbBTC_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [cbBTC_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [cbBTC_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [cbBTC_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol:AerodromeVolatileCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_weth_degen.ts b/scripts/verification/collateral-plugins/verify_aerodrome_weth_degen.ts new file mode 100644 index 000000000..533ef4d1e --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_weth_degen.ts @@ -0,0 +1,100 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_DEGEN_POOL, + AERO_WETH_DEGEN_GAUGE, + AERO, + DEGEN_ORACLE_TIMEOUT, + DEGEN_USD_FEED, + DEGEN_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroWETHDEGENPoolCollateral = await ethers.getContractAt( + 'AerodromeVolatileCollateral', + deployments.collateral.aeroWETHDEGEN as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_DEGEN_POOL) + await verifyContract( + chainId, + await aeroWETHDEGENPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_DEGEN_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify WETH-DEGEN plugin **************************/ + const oracleError = combinedError(DEGEN_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + await verifyContract( + chainId, + deployments.collateral.aeroWETHDEGEN, + [ + { + erc20: await aeroWETHDEGENPoolCollateral.erc20(), + targetName: await aeroWETHDEGENPoolCollateral.targetName(), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: DEGEN_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', + }, + { + pool: AERO_WETH_DEGEN_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [DEGEN_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [DEGEN_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [DEGEN_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol:AerodromeVolatileCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aerodrome_weth_well.ts b/scripts/verification/collateral-plugins/verify_aerodrome_weth_well.ts new file mode 100644 index 000000000..bf61166ec --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aerodrome_weth_well.ts @@ -0,0 +1,100 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { combinedError } from '../../deployment/utils' +import { IAeroPool } from '@typechain/IAeroPool' +import { + AerodromePoolType, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + AERO_WETH_WELL_POOL, + AERO_WETH_WELL_GAUGE, + AERO, + WELL_ORACLE_TIMEOUT, + WELL_USD_FEED, + WELL_ORACLE_ERROR, + ETH_USD_FEED, + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, +} from '../../../test/plugins/individual-collateral/aerodrome/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + // Only on base, aways use wrapper + if (baseL2Chains.includes(hre.network.name)) { + const aeroWETHWELLPoolCollateral = await ethers.getContractAt( + 'AerodromeVolatileCollateral', + deployments.collateral.aeroWETHWELL as string + ) + + /******** Verify Gauge Wrapper **************************/ + + const pool = await ethers.getContractAt('IAeroPool', AERO_WETH_WELL_POOL) + await verifyContract( + chainId, + await aeroWETHWELLPoolCollateral.erc20(), + [ + pool.address, + 'w' + (await pool.name()), + 'w' + (await pool.symbol()), + AERO, + AERO_WETH_WELL_GAUGE, + ], + 'contracts/plugins/assets/aerodrome/AerodromeGaugeWrapper.sol:AerodromeGaugeWrapper' + ) + + /******** Verify WETH-WELL plugin **************************/ + const oracleError = combinedError(WELL_ORACLE_ERROR, ETH_ORACLE_ERROR) // 0.5% & 0.15% + await verifyContract( + chainId, + deployments.collateral.aeroWETHWELL, + [ + { + erc20: await aeroWETHWELLPoolCollateral.erc20(), + targetName: await aeroWETHWELLPoolCollateral.targetName(), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: oracleError.toString(), + oracleTimeout: WELL_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: '0', + delayUntilDefault: '86400', + }, + { + pool: AERO_WETH_WELL_POOL, + poolType: AerodromePoolType.Volatile, + feeds: [[ETH_USD_FEED], [WELL_USD_FEED]], + oracleTimeouts: [[ETH_ORACLE_TIMEOUT], [WELL_ORACLE_TIMEOUT]], + oracleErrors: [[ETH_ORACLE_ERROR], [WELL_ORACLE_ERROR]], + }, + ], + 'contracts/plugins/assets/aerodrome/AerodromeVolatileCollateral.sol:AerodromeVolatileCollateral' + ) + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index a3eddd842..a8692249c 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -96,6 +96,9 @@ async function main() { 'collateral-plugins/verify_aerodrome_weth_aero.ts', 'collateral-plugins/verify_aerodrome_mog_weth.ts', 'collateral-plugins/verify_aerodrome_usdz_usdc.ts', + 'collateral-plugins/verify_aerodrome_weth_cbbtc.ts', + 'collateral-plugins/verify_aerodrome_weth_well.ts', + 'collateral-plugins/verify_aerodrome_weth_degen.ts', 'collateral-plugins/verify_morphoeUSD.ts' ) } else if (chainId == '42161' || chainId == '421614') { diff --git a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts index e0ffb245e..2269132ea 100644 --- a/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts +++ b/test/plugins/individual-collateral/aerodrome/AerodromeVolatileCollateral.test.ts @@ -39,6 +39,30 @@ import { AERO_WETH_AERO_POOL, AERO_WETH_AERO_GAUGE, AERO_WETH_AERO_HOLDER, + AERO_WETH_cbBTC_POOL, + AERO_WETH_cbBTC_GAUGE, + AERO_WETH_cbBTC_HOLDER, + AERO_WETH_WELL_POOL, + AERO_WETH_WELL_GAUGE, + AERO_WETH_WELL_HOLDER, + AERO_WETH_DEGEN_POOL, + AERO_WETH_DEGEN_GAUGE, + AERO_WETH_DEGEN_HOLDER, + cbBTC, + cbBTC_USD_FEED, + cbBTC_ORACLE_ERROR, + cbBTC_ORACLE_TIMEOUT, + cbBTC_HOLDER, + WELL, + WELL_USD_FEED, + WELL_ORACLE_ERROR, + WELL_ORACLE_TIMEOUT, + WELL_HOLDER, + DEGEN, + DEGEN_USD_FEED, + DEGEN_ORACLE_ERROR, + DEGEN_ORACLE_TIMEOUT, + DEGEN_HOLDER, ETH_USD_FEED, ETH_ORACLE_ERROR, ETH_ORACLE_TIMEOUT, @@ -138,6 +162,84 @@ const all: AeroVolatilePoolEnumeration[] = [ amountScaleDivisor: bn('1e2'), toleranceDivisor: bn('1e4'), }, + { + testName: 'Aerodrome - WETH/cbBTC Volatile', + pool: AERO_WETH_cbBTC_POOL, + gauge: AERO_WETH_cbBTC_GAUGE, + holder: AERO_WETH_cbBTC_HOLDER, + tokens: [ + { + token: WETH, + feeds: [ETH_USD_FEED], + oracleTimeouts: [ETH_ORACLE_TIMEOUT], + oracleErrors: [ETH_ORACLE_ERROR], + holder: WETH_HOLDER, + }, + { + token: cbBTC, + feeds: [cbBTC_USD_FEED], + oracleTimeouts: [cbBTC_ORACLE_TIMEOUT], + oracleErrors: [cbBTC_ORACLE_ERROR], + holder: cbBTC_HOLDER, + }, + ], + oracleTimeout: ETH_ORACLE_TIMEOUT, // max + oracleError: cbBTC_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined + amountScaleDivisor: bn('1e6'), + toleranceDivisor: bn('1e4'), + }, + { + testName: 'Aerodrome - WETH/WELL Volatile', + pool: AERO_WETH_WELL_POOL, + gauge: AERO_WETH_WELL_GAUGE, + holder: AERO_WETH_WELL_HOLDER, + tokens: [ + { + token: WETH, + feeds: [ETH_USD_FEED], + oracleTimeouts: [ETH_ORACLE_TIMEOUT], + oracleErrors: [ETH_ORACLE_ERROR], + holder: WETH_HOLDER, + }, + { + token: WELL, + feeds: [WELL_USD_FEED], + oracleTimeouts: [WELL_ORACLE_TIMEOUT], + oracleErrors: [WELL_ORACLE_ERROR], + holder: WELL_HOLDER, + }, + ], + oracleTimeout: WELL_ORACLE_TIMEOUT, // max + oracleError: WELL_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined + amountScaleDivisor: bn('1e2'), + toleranceDivisor: bn('1e4'), + }, + { + testName: 'Aerodrome - WETH/DEGN Volatile', + pool: AERO_WETH_DEGEN_POOL, + gauge: AERO_WETH_DEGEN_GAUGE, + holder: AERO_WETH_DEGEN_HOLDER, + tokens: [ + { + token: WETH, + feeds: [ETH_USD_FEED], + oracleTimeouts: [ETH_ORACLE_TIMEOUT], + oracleErrors: [ETH_ORACLE_ERROR], + holder: WETH_HOLDER, + }, + { + token: DEGEN, + feeds: [DEGEN_USD_FEED], + oracleTimeouts: [DEGEN_ORACLE_TIMEOUT], + oracleErrors: [DEGEN_ORACLE_ERROR], + holder: DEGEN_HOLDER, + }, + ], + oracleTimeout: DEGEN_ORACLE_TIMEOUT, // max + oracleError: DEGEN_ORACLE_ERROR.add(ETH_ORACLE_ERROR), // combined + amountScaleDivisor: bn('1e2'), + toleranceDivisor: bn('1e4'), + }, ] all.forEach((curr: AeroVolatilePoolEnumeration) => { diff --git a/test/plugins/individual-collateral/aerodrome/constants.ts b/test/plugins/individual-collateral/aerodrome/constants.ts index 1714de5f1..984cde0eb 100644 --- a/test/plugins/individual-collateral/aerodrome/constants.ts +++ b/test/plugins/individual-collateral/aerodrome/constants.ts @@ -7,7 +7,7 @@ export const forkNetwork = useEnv('FORK_NETWORK') ?? 'base' // Base Addresses export const AERO_USDC_eUSD_GAUGE = '0x793F22aB88dC91793E5Ce6ADbd7E733B0BD4733e' export const AERO_USDC_eUSD_POOL = '0x7A034374C89C463DD65D8C9BCfe63BcBCED41f4F' -export const AERO_USDC_eUSD_HOLDER = '0xB6C8ea53ABA64a4BdE857D3b25d9DEbD0B149a0a' // for gauge +export const AERO_USDC_eUSD_HOLDER = '0x5d823546463fD0Ac3b31DBA6AD38B56a39BFbf70' // for gauge export const AERO_WETH_AERO_GAUGE = '0x96a24aB830D4ec8b1F6f04Ceac104F1A3b211a01' export const AERO_WETH_AERO_POOL = '0x7f670f78B17dEC44d5Ef68a48740b6f8849cc2e6' @@ -21,6 +21,18 @@ export const AERO_USDz_USDC_GAUGE = '0xb7E4bBee04285F4B55d0A93b34E5dA95C3a7faf9' export const AERO_USDz_USDC_POOL = '0x6d0b9C9E92a3De30081563c3657B5258b3fFa38B' export const AERO_USDz_USDC_HOLDER = '0x4C3cB0D6273A27C68AB4B2F96DB211d8d75e98Da' +export const AERO_WETH_cbBTC_GAUGE = '0xAFdEBa12B6a870d6639d043030b4b49F9C7c62BB' +export const AERO_WETH_cbBTC_POOL = '0x2578365B3dfA7FfE60108e181EFb79FeDdec2319' +export const AERO_WETH_cbBTC_HOLDER = '0x106b9121d259fb46F1033BDBd29ad0C67650953C' + +export const AERO_WETH_WELL_GAUGE = '0x7b6964440b615aC1d31bc95681B133E112fB2684' +export const AERO_WETH_WELL_POOL = '0x89D0F320ac73dd7d9513FFC5bc58D1161452a657' +export const AERO_WETH_WELL_HOLDER = '0x5a6859C2f992B998837342d29911dD14E8DC2E1a' + +export const AERO_WETH_DEGEN_GAUGE = '0x86A1260AB9f758026Ce1a5830BdfF66DBcF736d5' +export const AERO_WETH_DEGEN_POOL = '0x2C4909355b0C036840819484c3A882A95659aBf3' +export const AERO_WETH_DEGEN_HOLDER = '0xC314990f1379fEe384Be9a74328C75Df8587aE77' + export const AERODROME_ROUTER = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' // Tokens @@ -30,6 +42,9 @@ export const eUSD = networkConfig['8453'].tokens.eUSD! export const USDz = networkConfig['8453'].tokens.USDz! export const AERO = networkConfig['8453'].tokens.AERO! export const MOG = networkConfig['8453'].tokens.MOG! +export const cbBTC = networkConfig['8453'].tokens.cbBTC! +export const WELL = networkConfig['8453'].tokens.WELL! +export const DEGEN = networkConfig['8453'].tokens.DEGEN! // USDC export const USDC_USD_FEED = networkConfig['8453'].chainlinkFeeds.USDC! @@ -49,7 +64,7 @@ export const USDz_ORACLE_TIMEOUT = bn('86400') export const USDz_ORACLE_ERROR = fp('0.005') export const USDz_HOLDER = '0xA87c9808C0eBE20a1427B5C769623c77201f6f4D' -export const FORK_BLOCK = 21668000 //19980400 +export const FORK_BLOCK = 22854038 //19980400 // AERO export const AERO_ORACLE_ERROR = fp('0.005') // 0.5% @@ -69,12 +84,30 @@ export const MOG_ORACLE_TIMEOUT = bn('86400') // 24hr export const MOG_USD_FEED = networkConfig['8453'].chainlinkFeeds.MOG! export const MOG_HOLDER = '0xBaeD383EDE0e5d9d72430661f3285DAa77E9439F' +// cbBTC +export const cbBTC_ORACLE_ERROR = fp('0.003') // 0.3% +export const cbBTC_ORACLE_TIMEOUT = bn('1200') // 20 min +export const cbBTC_USD_FEED = networkConfig['8453'].chainlinkFeeds.cbBTC! +export const cbBTC_HOLDER = '0xF877ACaFA28c19b96727966690b2f44d35aD5976' + +// WELL +export const WELL_ORACLE_ERROR = fp('0.005') // 0.5% +export const WELL_ORACLE_TIMEOUT = bn('86400') // 24hr +export const WELL_USD_FEED = networkConfig['8453'].chainlinkFeeds.WELL! +export const WELL_HOLDER = '0xe66E3A37C3274Ac24FE8590f7D84A2427194DC17' + +// DEGEN +export const DEGEN_ORACLE_ERROR = fp('0.005') // 0.5% +export const DEGEN_ORACLE_TIMEOUT = bn('86400') // 24hr +export const DEGEN_USD_FEED = networkConfig['8453'].chainlinkFeeds.DEGEN! +export const DEGEN_HOLDER = '0x06A19654e0872Ba71c2261EA691Ecf8a0c677156' + // Common export const FIX_ONE = 1n * 10n ** 18n export const ORACLE_ERROR = fp('0.005') export const PRICE_TIMEOUT = bn('604800') // 1 week export const DEFAULT_THRESHOLD = fp('0.02') // 2% -export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h CAREFUL THIS IS ONLY FOR RTOKEN POOLS +export const DELAY_UNTIL_DEFAULT = bn('259200') // 72h CAREFUL THIS IS ONLY FOR STABLE POOLS export const MAX_TRADE_VOL = fp('1e6') export enum AerodromePoolType { diff --git a/test/plugins/individual-collateral/aerodrome/helpers.ts b/test/plugins/individual-collateral/aerodrome/helpers.ts index abdf8ae9a..1edc8dfde 100644 --- a/test/plugins/individual-collateral/aerodrome/helpers.ts +++ b/test/plugins/individual-collateral/aerodrome/helpers.ts @@ -196,7 +196,7 @@ export const allStableTests: AeroStablePoolEnumeration[] = [ ], oracleTimeout: PRICE_TIMEOUT, // max oracleError: ORACLE_ERROR, // combined - amountScaleDivisor: bn('1e2'), + amountScaleDivisor: bn('1e3'), toleranceDivisor: bn('1e2'), fix: makeWUSDCeUSD, },