From 92549be85d8067b36f79d7e5889ba58948b644c5 Mon Sep 17 00:00:00 2001 From: rndquu Date: Thu, 25 Apr 2024 15:44:44 +0300 Subject: [PATCH] feat: add plain pool migration --- README.md | 7 ++ packages/contracts/.env.example | 7 ++ .../Deploy001_Diamond_Dollar_Governance.s.sol | 59 ++++++++-------- .../Deploy001_Diamond_Dollar_Governance.s.sol | 68 +++++++++++++------ .../interfaces/ICurveStableSwapFactoryNG.sol | 45 +++++++++++- .../dollar/interfaces/ICurveStableSwapNG.sol | 8 ++- .../mocks/MockCurveStableSwapMetaNG.sol | 2 +- .../dollar/mocks/MockCurveStableSwapNG.sol | 9 +++ 8 files changed, 152 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index fb53a4aaa..d40e0ab93 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,13 @@ COLLATERAL_TOKEN_ADDRESS="0x5f98805A4E8be255a32880FDeC7F6728C6568bA0" # Collateral token price feed address from chainlink. # By default set to LUSD/USD price feed deployed on ethereum mainnet. +# This price feed is used in 2 cases: +# 1) To calculate collateral price in USD +# 2) To calculate Dollar price in USD +# Since collateral token (LUSD) is the same one used in Curve's plain pool (LUSD-Dollar) +# we share the same price feed in: +# 1) `LibUbiquityPool.setCollateralChainLinkPriceFeed()` (to calculate collateral price in USD) +# 2) `LibUbiquityPool.setStableUsdChainLinkPriceFeed()` (to calculate Dollar price in USD) # - mainnet: uses already deployed LUSD/USD chainlink price feed # - testnet/anvil: deploys LUSD/USD chainlink price feed from scratch COLLATERAL_TOKEN_CHAINLINK_PRICE_FEED_ADDRESS="0x3D7aE7E594f2f2091Ad8798313450130d0Aba3a0" diff --git a/packages/contracts/.env.example b/packages/contracts/.env.example index 8c76dc042..98ef10783 100644 --- a/packages/contracts/.env.example +++ b/packages/contracts/.env.example @@ -11,6 +11,13 @@ COLLATERAL_TOKEN_ADDRESS="0x5f98805A4E8be255a32880FDeC7F6728C6568bA0" # Collateral token price feed address from chainlink. # By default set to LUSD/USD price feed deployed on ethereum mainnet. +# This price feed is used in 2 cases: +# 1) To calculate collateral price in USD +# 2) To calculate Dollar price in USD +# Since collateral token (LUSD) is the same one used in Curve's plain pool (LUSD-Dollar) +# we share the same price feed in: +# 1) `LibUbiquityPool.setCollateralChainLinkPriceFeed()` (to calculate collateral price in USD) +# 2) `LibUbiquityPool.setStableUsdChainLinkPriceFeed()` (to calculate Dollar price in USD) # - mainnet: uses already deployed LUSD/USD chainlink price feed # - testnet/anvil: deploys LUSD/USD chainlink price feed from scratch COLLATERAL_TOKEN_CHAINLINK_PRICE_FEED_ADDRESS="0x3D7aE7E594f2f2091Ad8798313450130d0Aba3a0" diff --git a/packages/contracts/migrations/development/Deploy001_Diamond_Dollar_Governance.s.sol b/packages/contracts/migrations/development/Deploy001_Diamond_Dollar_Governance.s.sol index d43bd448d..90a770249 100644 --- a/packages/contracts/migrations/development/Deploy001_Diamond_Dollar_Governance.s.sol +++ b/packages/contracts/migrations/development/Deploy001_Diamond_Dollar_Governance.s.sol @@ -16,7 +16,7 @@ import {DiamondLoupeFacet} from "../../src/dollar/facets/DiamondLoupeFacet.sol"; import {ManagerFacet} from "../../src/dollar/facets/ManagerFacet.sol"; import {OwnershipFacet} from "../../src/dollar/facets/OwnershipFacet.sol"; import {UbiquityPoolFacet} from "../../src/dollar/facets/UbiquityPoolFacet.sol"; -import {ICurveStableSwapMetaNG} from "../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; +import {ICurveStableSwapNG} from "../../src/dollar/interfaces/ICurveStableSwapNG.sol"; import {ICurveTwocryptoOptimized} from "../../src/dollar/interfaces/ICurveTwocryptoOptimized.sol"; import {IDiamondCut} from "../../src/dollar/interfaces/IDiamondCut.sol"; import {IDiamondLoupe} from "../../src/dollar/interfaces/IDiamondLoupe.sol"; @@ -26,7 +26,7 @@ import {LibAccessControl} from "../../src/dollar/libraries/LibAccessControl.sol" import {AppStorage, LibAppStorage, Modifiers} from "../../src/dollar/libraries/LibAppStorage.sol"; import {LibDiamond} from "../../src/dollar/libraries/LibDiamond.sol"; import {MockChainLinkFeed} from "../../src/dollar/mocks/MockChainLinkFeed.sol"; -import {MockCurveStableSwapMetaNG} from "../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; +import {MockCurveStableSwapNG} from "../../src/dollar/mocks/MockCurveStableSwapNG.sol"; import {MockCurveTwocryptoOptimized} from "../../src/dollar/mocks/MockCurveTwocryptoOptimized.sol"; import {MockERC20} from "../../src/dollar/mocks/MockERC20.sol"; import {DiamondTestHelper} from "../../test/helpers/DiamondTestHelper.sol"; @@ -129,8 +129,7 @@ contract Deploy001_Diamond_Dollar_Governance is Script, DiamondTestHelper { // oracle related contracts AggregatorV3Interface chainLinkPriceFeedEth; // chainlink ETH/USD price feed AggregatorV3Interface chainLinkPriceFeedLusd; // chainlink LUSD/USD price feed - IERC20 curveTriPoolLpToken; // Curve's 3CRV-LP token - ICurveStableSwapMetaNG curveDollarMetaPool; // Curve's Dollar-3CRVLP metapool + ICurveStableSwapNG curveStableDollarPlainPool; // Curve's LUSD-Dollar plain pool ICurveTwocryptoOptimized curveGovernanceEthPool; // Curve's Governance-WETH crypto pool // collateral ERC20 token used in UbiquityPoolFacet @@ -408,22 +407,22 @@ contract Deploy001_Diamond_Dollar_Governance is Script, DiamondTestHelper { * - oracle related contracts * - Governance token related contracts * - * @dev Ubiquity protocol supports 4 oracles: - * 1. Curve's Dollar-3CRVLP metapool to fetch Dollar prices - * 2. Chainlink's price feed (used in UbiquityPool) to fetch collateral token prices in USD - * 3. Chainlink's price feed (used in UbiquityPool) to fetch ETH/USD price - * 4. Curve's Governance-WETH crypto pool to fetch Governance/ETH price + * @dev Ubiquity protocol supports 5 oracles: + * 1. Curve's LUSD-Dollar plain pool to fetch Dollar prices + * 2. Chainlink's price feed (used in UbiquityPool) to fetch LUSD/USD price (for getting Dollar price in USD) + * 3. Chainlink's price feed (used in UbiquityPool) to fetch collateral token prices in USD (for getting collateral price in USD) + * 4. Chainlink's price feed (used in UbiquityPool) to fetch ETH/USD price + * 5. Curve's Governance-WETH crypto pool to fetch Governance/ETH price * * There are 2 migrations (deployment scripts): * 1. Development (for usage in testnet and local anvil instance) * 2. Mainnet (for production usage in mainnet) * * Development migration deploys (for ease of debugging) mocks of: - * - Chainlink collateral price feed contract * - Chainlink ETH/USD price feed contract - * - 3CRVLP ERC20 token + * - Chainlink LUSD/USD price feed contract (for getting Dollar and collateral prices in USD) * - WETH token - * - Curve's Dollar-3CRVLP metapool contract + * - Curve's LUSD-Dollar plain pool contract * - Curve's Governance-WETH crypto pool contract */ function afterRun() public virtual { @@ -464,7 +463,7 @@ contract Deploy001_Diamond_Dollar_Governance is Script, DiamondTestHelper { 1 // answered in round ); - // set price feed address and threshold in seconds + // set collateral price feed address and threshold in seconds ubiquityPoolFacet.setCollateralChainLinkPriceFeed( address(collateralToken), // collateral token address address(chainLinkPriceFeedLusd), // price feed address @@ -474,41 +473,45 @@ contract Deploy001_Diamond_Dollar_Governance is Script, DiamondTestHelper { // fetch latest prices from chainlink for collateral with index 0 ubiquityPoolFacet.updateChainLinkCollateralPrice(0); + // set Stable/USD price feed address and threshold in seconds + ubiquityPoolFacet.setStableUsdChainLinkPriceFeed( + address(chainLinkPriceFeedLusd), // price feed address + CHAINLINK_PRICE_FEED_THRESHOLD // price feed staleness threshold in seconds + ); + // stop sending admin transactions vm.stopBroadcast(); //========================================= - // Curve's Dollar-3CRVLP metapool deploy + // Curve's LUSD-Dollar plain pool deploy //========================================= // start sending owner transactions vm.startBroadcast(ownerPrivateKey); - // deploy mock 3CRV-LP token - curveTriPoolLpToken = new MockERC20( - "Curve.fi DAI/USDC/USDT", - "3Crv", - 18 - ); - - // deploy mock Curve's Dollar-3CRVLP metapool - curveDollarMetaPool = new MockCurveStableSwapMetaNG( - address(dollarToken), - address(curveTriPoolLpToken) + // Deploy mock Curve's LUSD-Dollar plain pool. + // Since we're using LUSD both as collateral and Dollar token pair + // in Curve's plain pool we don't deploy another mock of the "stable" coin + // paired to Dollar and simply use collateral token (i.e. LUSD). + curveStableDollarPlainPool = new MockCurveStableSwapNG( + address(collateralToken), + address(dollarToken) ); // stop sending owner transactions vm.stopBroadcast(); //======================================== - // Curve's Dollar-3CRVLP metapool setup + // Curve's LUSD-Dollar plain pool setup //======================================== // start sending admin transactions vm.startBroadcast(adminPrivateKey); - // set curve's metapool in manager facet - managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); + // set curve's plain pool in manager facet + managerFacet.setStableSwapPlainPoolAddress( + address(curveStableDollarPlainPool) + ); // stop sending admin transactions vm.stopBroadcast(); diff --git a/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar_Governance.s.sol b/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar_Governance.s.sol index 1a15e0816..eb00a449d 100644 --- a/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar_Governance.s.sol +++ b/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar_Governance.s.sol @@ -55,11 +55,12 @@ contract Deploy001_Diamond_Dollar_Governance is * we need to use already deployed contracts while `Deploy001_Diamond_Dollar_Governance_Development` * deploys all oracle and Governance token related contracts from scratch for ease of debugging. * - * @dev Ubiquity protocol supports 4 oracles: - * 1. Curve's Dollar-3CRVLP metapool to fetch Dollar prices - * 2. Chainlink's price feed (used in UbiquityPool) to fetch collateral token prices in USD - * 3. Chainlink's price feed (used in UbiquityPool) to fetch ETH/USD price - * 4. Curve's Governance-WETH crypto pool to fetch Governance/ETH price + * @dev Ubiquity protocol supports 5 oracles: + * 1. Curve's LUSD-Dollar plain pool to fetch Dollar prices + * 2. Chainlink's price feed (used in UbiquityPool) to fetch LUSD/USD price (for getting Dollar price in USD) + * 3. Chainlink's price feed (used in UbiquityPool) to fetch collateral token prices in USD (for getting collateral price in USD) + * 4. Chainlink's price feed (used in UbiquityPool) to fetch ETH/USD price + * 5. Curve's Governance-WETH crypto pool to fetch Governance/ETH price * * There are 2 migrations (deployment scripts): * 1. Development (for usage in testnet and local anvil instance) @@ -67,6 +68,7 @@ contract Deploy001_Diamond_Dollar_Governance is * * Mainnet (i.e. production) migration uses already deployed contracts for: * - Chainlink collateral price feed contract + * - Chainlink Stable/USD price feed contract (here "Stable" refers to the LUSD token from Curve's LUSD-Dollar plain pool) * - UbiquityAlgorithmicDollarManager contract * - UbiquityGovernance token contract * - Chainlink ETH/USD price feed @@ -104,7 +106,7 @@ contract Deploy001_Diamond_Dollar_Governance is chainlinkPriceFeedAddressLusd ); - // set price feed + // set collateral price feed ubiquityPoolFacet.setCollateralChainLinkPriceFeed( address(collateralToken), // collateral token address address(chainLinkPriceFeedLusd), // price feed address @@ -114,46 +116,68 @@ contract Deploy001_Diamond_Dollar_Governance is // fetch latest prices from chainlink for collateral with index 0 ubiquityPoolFacet.updateChainLinkCollateralPrice(0); + // set Stable/Dollar price feed + ubiquityPoolFacet.setStableUsdChainLinkPriceFeed( + address(chainLinkPriceFeedLusd), // price feed address + CHAINLINK_PRICE_FEED_THRESHOLD // price feed staleness threshold in seconds + ); + // stop sending admin transactions vm.stopBroadcast(); //========================================= - // Curve's Dollar-3CRVLP metapool deploy + // Curve's LUSD-Dollar plain pool deploy //========================================= // start sending owner transactions vm.startBroadcast(ownerPrivateKey); - // deploy Curve Dollar-3CRV metapool - address curveDollarMetaPoolAddress = ICurveStableSwapFactoryNG( + // prepare parameters + address[] memory plainPoolCoins = new address[](2); + plainPoolCoins[0] = address(collateralToken); + plainPoolCoins[1] = address(dollarToken); + + uint8[] memory plainPoolAssetTypes = new uint8[](2); + plainPoolAssetTypes[0] = 0; + plainPoolAssetTypes[1] = 0; + + bytes4[] memory plainPoolMethodIds = new bytes4[](2); + plainPoolMethodIds[0] = bytes4(""); + plainPoolMethodIds[1] = bytes4(""); + + address[] memory plainPoolTokenOracleAddresses = new address[](2); + plainPoolTokenOracleAddresses[0] = address(0); + plainPoolTokenOracleAddresses[1] = address(0); + + // deploy Curve LUSD-Dollar plain pool + address curveDollarPlainPoolAddress = ICurveStableSwapFactoryNG( 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - ).deploy_metapool( - 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7, // Curve 3pool (DAI-USDT-USDC) address - "Dollar/3CRV", // pool name - "Dollar3CRV", // LP token symbol - address(dollarToken), // main token + ).deploy_plain_pool( + "LUSD/Dollar", // pool name + "LUSDDollar", // LP token symbol + plainPoolCoins, // coins used in the pool 100, // amplification coefficient - 40000000, // trade fee, 0.04% + 4000000, // trade fee, 0.04% 20000000000, // off-peg fee multiplier 2597, // moving average time value, 2597 = 1800 seconds - 0, // metapool implementation index - 0, // asset type - "", // method id for oracle asset type (not applicable for Dollar) - address(0) // token oracle address (not applicable for Dollar) + 0, // plain pool implementation index + plainPoolAssetTypes, // asset types + plainPoolMethodIds, // method ids for oracle asset type (not applicable for Dollar) + plainPoolTokenOracleAddresses // token oracle addresses (not applicable for Dollar) ); // stop sending owner transactions vm.stopBroadcast(); //======================================== - // Curve's Dollar-3CRVLP metapool setup + // Curve's LUSD-Dollar plain pool setup //======================================== // start sending admin transactions vm.startBroadcast(adminPrivateKey); - // set curve's metapool in manager facet - managerFacet.setStableSwapMetaPoolAddress(curveDollarMetaPoolAddress); + // set curve's plain pool in manager facet + managerFacet.setStableSwapPlainPoolAddress(curveDollarPlainPoolAddress); // stop sending admin transactions vm.stopBroadcast(); diff --git a/packages/contracts/src/dollar/interfaces/ICurveStableSwapFactoryNG.sol b/packages/contracts/src/dollar/interfaces/ICurveStableSwapFactoryNG.sol index 2dc241cbc..ef0c377da 100644 --- a/packages/contracts/src/dollar/interfaces/ICurveStableSwapFactoryNG.sol +++ b/packages/contracts/src/dollar/interfaces/ICurveStableSwapFactoryNG.sol @@ -19,7 +19,7 @@ interface ICurveStableSwapFactoryNG { * @param _A Amplification coefficient. If set to 0 then bonding curve acts like Uniswap. Any >0 value * makes the bonding curve to swap at 1:1 constant price, the more `_A` the longer the constant price period. * Curve recommends set it to 100 for crypto collateralizard stablecoins. This parameter can be updated later. - * @param _fee Trade fee, given as an integer with 1e10 precision, ex: 40000000 = 0.04% fee + * @param _fee Trade fee, given as an integer with 1e10 precision, ex: 4000000 = 0.04% fee * @param _offpeg_fee_multiplier Off-peg multiplier. Curve recommends set it to `20000000000`. This parameter can be updated * later. More info: https://docs.curve.fi/stableswap-exchange/stableswap-ng/pools/overview/#dynamic-fees * @param _ma_exp_time MA time; set as time_in_seconds / ln(2), ex: 866 = 600 seconds, 2597 = 1800 seconds. @@ -54,4 +54,47 @@ interface ICurveStableSwapFactoryNG { bytes4 _method_id, address _oracle ) external returns (address); + + /** + * @notice Deploys a new plain pool + * @param _name Name of the new plain pool, ex: "LUSD/Dollar" + * @param _symbol Symbol for the new pool's LP token, ex: "LUSDDollar" + * @param _coins Array of addresses of the coins being used in the pool + * @param _A Amplification coefficient. If set to 0 then bonding curve acts like Uniswap. Any >0 value + * makes the bonding curve to swap at 1:1 constant price, the more `_A` the longer the constant price period. + * Curve recommends set it to 100 for crypto collateralizard stablecoins. This parameter can be updated later. + * @param _fee Trade fee, given as an integer with 1e10 precision, ex: 4000000 = 0.04% fee + * @param _offpeg_fee_multiplier Off-peg multiplier. Curve recommends set it to `20000000000`. This parameter can be updated + * later. More info: https://docs.curve.fi/stableswap-exchange/stableswap-ng/pools/overview/#dynamic-fees + * @param _ma_exp_time MA time; set as time_in_seconds / ln(2), ex: 866 = 600 seconds, 2597 = 1800 seconds. + * This parameter can be updated later. + * @param _implementation_idx Index of the plain pool implementation to use. Can be retrieved + * via `ICurveStableSwapFactoryNG.pool_implementations()`. There is only 1 plain pool implementation right now + * so use index `0`. + * @param _asset_types Asset types of the pool tokens as an integer. Available asset type indexes: + * - 0: Standard ERC20 token with no additional features + * - 1: Oracle - token with rate oracle (e.g. wstETH) + * - 2: Rebasing - token with rebase (e.g. stETH) + * - 3: ERC4626 - token with convertToAssets method (e.g. sDAI) + * Both Dollar and LUSD are standard ERC20 tokens so we should use asset types with index `0`. + * @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of + * the oracle addresses that give rate oracles. This is applied only to asset type `1` (Oracle). + * For Dollar token deployment set empty. + * @param _oracles Array of rate oracle addresses. This is applied only to asset type `1` (Oracle). + * For Dollar token deployment set empty address. + * @return Deployed plain pool address + */ + function deploy_plain_pool( + string memory _name, + string memory _symbol, + address[] memory _coins, + uint256 _A, + uint256 _fee, + uint256 _offpeg_fee_multiplier, + uint256 _ma_exp_time, + uint256 _implementation_idx, + uint8[] memory _asset_types, + bytes4[] memory _method_ids, + address[] memory _oracles + ) external returns (address); } diff --git a/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol b/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol index e6c548478..95b31f1c5 100644 --- a/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol +++ b/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol @@ -6,4 +6,10 @@ import {ICurveStableSwapMetaNG} from "./ICurveStableSwapMetaNG.sol"; /** * @notice Curve's interface for plain pool which contains only USD pegged assets */ -interface ICurveStableSwapNG is ICurveStableSwapMetaNG {} +interface ICurveStableSwapNG is ICurveStableSwapMetaNG { + function add_liquidity( + uint256[] memory _amounts, + uint256 _min_mint_amount, + address _receiver + ) external returns (uint256); +} diff --git a/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol b/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol index adca18713..7cc0092b8 100644 --- a/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol +++ b/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol @@ -19,7 +19,7 @@ contract MockCurveStableSwapMetaNG is ICurveStableSwapMetaNG, MockERC20 { uint256[2] memory _amounts, uint256 _min_mint_amount, address _receiver - ) external returns (uint256 result) { + ) public returns (uint256 result) { mint( _receiver, _min_mint_amount == 0 diff --git a/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol b/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol index d95f996b9..9fe6d1c18 100644 --- a/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol +++ b/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol @@ -12,4 +12,13 @@ contract MockCurveStableSwapNG is address _token0, address _token1 ) MockCurveStableSwapMetaNG(_token0, _token1) {} + + function add_liquidity( + uint256[] memory _amounts, + uint256 _min_mint_amount, + address _receiver + ) external returns (uint256 result) { + uint256[2] memory amounts = [_amounts[0], _amounts[1]]; + return add_liquidity(amounts, _min_mint_amount, _receiver); + } }