From acc58000595c3b2a3554b0b50ee47af4357daed7 Mon Sep 17 00:00:00 2001 From: rndquu <119500907+rndquu@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:36:31 +0300 Subject: [PATCH] chore: Fix review (#907) * fix: deprecate incentive on dollar token * chore: update * chore: update * Fix set role admin (#880) * feat: add setRoleAdmin to AccessControlFacet The setRoleAdmin can be only accessed by the admin. * feat: add setRoleAdmin to AccessControl interface * test: add testSetRoleAdmin_ShouldSetAdminRoleForGivenRole test * feat: update access control for setRoleAdmin * test: fix ShouldSetAdminRoleForGivenRole and add test for revert * feat: add getRedeemCollateralBalance() method * fix: remove balanced reserves check (#883) * fix: limit AMO minter borrow amount (#882) * fix: limit AMO minter borrow amount * test: assert free collateral amount * fix: do not allow to mint dollar with zero collateral Resolves: https://github.com/sherlock-audit/2023-12-ubiquity-judging/issues/207 * test: add testMintDollar_ShouldRevert_IfZeroCollateralAvailable * test: update comment in zero collateral mint test * Update block count in a week (#891) * feat: implement BlocksInWeek script task The BlocksInWeek task provides a very close approximate of number of blocks mined during one week. Supported networks: mainnnet, sepolia. Example usage: npx tsx scripts/task/task.ts BlocksInWeek --network=mainnet npx tsx scripts/task/task.ts BlocksInWeek --network=sepolia Resolves: https://github.com/sherlock-audit/2023-12-ubiquity-judging/issues/230 * feat: update weekly block count to 49930 Set weekly block count to 49930 as measured in February 2024 npx tsx scripts/task/task.ts BlocksInWeek --network=mainnet ... Calculating number of blocks in the last week... Recent average block time: 12 seconds Estimated blocks in a week best case 50400 Produced 49930 blocks, 470 worst than the best case Resolves: https://github.com/sherlock-audit/2023-12-ubiquity-judging/issues/230 * feat: rename task function to funcBlocksInAWeek As proposed during pull request review. * feat: use CurveStableSwapMetaNG contract * refactor: update migrations to use latest metapool * refactor: deprecate IMetaPool * refactor: remove MockTWAPOracleDollar3pool * refactor: remove TWAPOracleDollar3poolFacet * refactor: remove MockMetaPool * refactor: remove LibTWAPOracle * fix(dapp): remove TWAP oracle ABI import * refactor(frontend): use ICurveStableSwapMetaNG for TWAP * feat: check if collateral is enabled in collectRedemption (#894) Also add a unit test that verifies the check. Resolves: https://github.com/sherlock-audit/2023-12-ubiquity-judging/issues/29 --------- Co-authored-by: molecula451 Co-authored-by: korrrba <88761781+gitcoindev@users.noreply.github.com> Co-authored-by: Korrrba --- cspell.json | 1 + .../Deploy001_Diamond_Dollar.s.sol | 44 ++--- .../mainnet/Deploy001_Diamond_Dollar.s.sol | 29 ++- .../scripts/shared/constants/networks.ts | 1 + packages/contracts/scripts/task/README.md | 23 +++ .../scripts/task/dollar/blocks-in-week.ts | 51 ++++++ packages/contracts/scripts/task/manager.ts | 7 +- packages/contracts/scripts/task/task.ts | 3 +- .../contracts/src/deprecated/TWAPOracle.sol | 2 +- .../UbiquityAlgorithmicDollarManager.sol | 2 +- .../interfaces/IMetaPool.sol | 0 .../dollar/access/AccessControlInternal.sol | 12 ++ .../src/dollar/core/UbiquityDollarToken.sol | 116 ------------ .../src/dollar/facets/AccessControlFacet.sol | 5 + .../src/dollar/facets/ManagerFacet.sol | 10 +- .../facets/TWAPOracleDollar3poolFacet.sol | 47 ----- .../src/dollar/facets/UbiquityPoolFacet.sol | 12 ++ .../src/dollar/interfaces/IAccessControl.sol | 7 + .../interfaces/ICurveStableSwapMetaNG.sol | 99 +++++++++++ .../src/dollar/interfaces/IIncentive.sol | 29 --- .../interfaces/ITWAPOracleDollar3pool.sol | 51 ------ .../src/dollar/interfaces/IUbiquityPool.sol | 11 ++ .../src/dollar/libraries/LibChef.sol | 8 +- .../dollar/libraries/LibCreditNftManager.sol | 27 ++- .../libraries/LibCurveDollarIncentive.sol | 12 +- .../libraries/LibDollarMintCalculator.sol | 6 +- .../dollar/libraries/LibDollarMintExcess.sol | 18 +- .../src/dollar/libraries/LibStaking.sol | 61 ++++--- .../src/dollar/libraries/LibTWAPOracle.sol | 165 ------------------ .../src/dollar/libraries/LibUbiquityPool.sol | 42 ++++- .../src/dollar/mocks/MockCurveFactory.sol | 10 +- .../mocks/MockCurveStableSwapMetaNG.sol | 63 +++++++ .../src/dollar/mocks/MockMetaPool.sol | 126 ------------- .../mocks/MockTWAPOracleDollar3pool.sol | 43 ----- .../upgradeInitializers/DiamondInit.sol | 3 +- .../contracts/test/diamond/DiamondTest.t.sol | 2 +- .../test/diamond/DiamondTestSetup.sol | 35 ++-- .../diamond/facets/AccessControlFacet.t.sol | 21 +++ .../test/diamond/facets/ChefFacet.t.sol | 37 ++-- .../facets/CreditNftManagerFacet.t.sol | 12 +- .../facets/CurveDollarIncentiveFacet.t.sol | 2 - .../facets/DollarMintCalculatorFacet.t.sol | 11 +- .../facets/DollarMintExcessFacet.t.sol | 8 +- .../test/diamond/facets/ManagerFacet.t.sol | 27 +-- .../test/diamond/facets/StakingFacet.t.sol | 43 ++--- .../test/diamond/facets/TWAPOracleFacet.t.sol | 64 ------- .../diamond/facets/UbiquityPoolFacet.t.sol | 140 +++++++++++++-- .../test/dollar/core/StakingShare.t.sol | 8 +- .../dollar/core/UbiquityDollarToken.t.sol | 70 -------- .../test/helpers/LocalTestHelper.sol | 30 ---- .../hooks/contracts/use-protocol-contracts.ts | 4 +- packages/dapp/components/utils/contracts.ts | 6 +- 52 files changed, 663 insertions(+), 1003 deletions(-) create mode 100644 packages/contracts/scripts/task/README.md create mode 100644 packages/contracts/scripts/task/dollar/blocks-in-week.ts rename packages/contracts/src/{dollar => deprecated}/interfaces/IMetaPool.sol (100%) delete mode 100644 packages/contracts/src/dollar/facets/TWAPOracleDollar3poolFacet.sol create mode 100644 packages/contracts/src/dollar/interfaces/ICurveStableSwapMetaNG.sol delete mode 100644 packages/contracts/src/dollar/interfaces/IIncentive.sol delete mode 100644 packages/contracts/src/dollar/interfaces/ITWAPOracleDollar3pool.sol delete mode 100644 packages/contracts/src/dollar/libraries/LibTWAPOracle.sol create mode 100644 packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol delete mode 100644 packages/contracts/src/dollar/mocks/MockMetaPool.sol delete mode 100644 packages/contracts/src/dollar/mocks/MockTWAPOracleDollar3pool.sol delete mode 100644 packages/contracts/test/diamond/facets/TWAPOracleFacet.t.sol diff --git a/cspell.json b/cspell.json index 0d77427e8..07237c723 100644 --- a/cspell.json +++ b/cspell.json @@ -120,6 +120,7 @@ "rpcutil", "rustup", "Sablier", + "sepolia", "setu", "Shouldset", "Sighash", diff --git a/packages/contracts/migrations/development/Deploy001_Diamond_Dollar.s.sol b/packages/contracts/migrations/development/Deploy001_Diamond_Dollar.s.sol index 5a3caabd3..23023643d 100644 --- a/packages/contracts/migrations/development/Deploy001_Diamond_Dollar.s.sol +++ b/packages/contracts/migrations/development/Deploy001_Diamond_Dollar.s.sol @@ -13,19 +13,18 @@ import {DiamondCutFacet} from "../../src/dollar/facets/DiamondCutFacet.sol"; 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 {TWAPOracleDollar3poolFacet} from "../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; import {UbiquityPoolFacet} from "../../src/dollar/facets/UbiquityPoolFacet.sol"; +import {ICurveStableSwapMetaNG} from "../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; import {IDiamondCut} from "../../src/dollar/interfaces/IDiamondCut.sol"; import {IDiamondLoupe} from "../../src/dollar/interfaces/IDiamondLoupe.sol"; import {IERC173} from "../../src/dollar/interfaces/IERC173.sol"; -import {IMetaPool} from "../../src/dollar/interfaces/IMetaPool.sol"; import {DEFAULT_ADMIN_ROLE, DOLLAR_TOKEN_MINTER_ROLE, DOLLAR_TOKEN_BURNER_ROLE, PAUSER_ROLE} from "../../src/dollar/libraries/Constants.sol"; 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 {MockERC20} from "../../src/dollar/mocks/MockERC20.sol"; -import {MockMetaPool} from "../../src/dollar/mocks/MockMetaPool.sol"; import {DiamondTestHelper} from "../../test/helpers/DiamondTestHelper.sol"; /** @@ -117,13 +116,12 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { DiamondLoupeFacet diamondLoupeFacetImplementation; ManagerFacet managerFacetImplementation; OwnershipFacet ownershipFacetImplementation; - TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacetImplementation; UbiquityPoolFacet ubiquityPoolFacetImplementation; // oracle related contracts AggregatorV3Interface chainLinkPriceFeedLusd; // chainlink LUSD/USD price feed IERC20 curveTriPoolLpToken; // Curve's 3CRV-LP token - IMetaPool curveDollarMetaPool; // Curve's Dollar-3CRVLP metapool + ICurveStableSwapMetaNG curveDollarMetaPool; // Curve's Dollar-3CRVLP metapool // selectors for all of the facets bytes4[] selectorsOfAccessControlFacet; @@ -131,7 +129,6 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { bytes4[] selectorsOfDiamondLoupeFacet; bytes4[] selectorsOfManagerFacet; bytes4[] selectorsOfOwnershipFacet; - bytes4[] selectorsOfTWAPOracleDollar3poolFacet; bytes4[] selectorsOfUbiquityPoolFacet; function run() public virtual { @@ -166,9 +163,6 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { selectorsOfOwnershipFacet = getSelectorsFromAbi( "/out/OwnershipFacet.sol/OwnershipFacet.json" ); - selectorsOfTWAPOracleDollar3poolFacet = getSelectorsFromAbi( - "/out/TWAPOracleDollar3poolFacet.sol/TWAPOracleDollar3poolFacet.json" - ); selectorsOfUbiquityPoolFacet = getSelectorsFromAbi( "/out/UbiquityPoolFacet.sol/UbiquityPoolFacet.json" ); @@ -179,7 +173,6 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { diamondLoupeFacetImplementation = new DiamondLoupeFacet(); managerFacetImplementation = new ManagerFacet(); ownershipFacetImplementation = new OwnershipFacet(); - twapOracleDollar3PoolFacetImplementation = new TWAPOracleDollar3poolFacet(); ubiquityPoolFacetImplementation = new UbiquityPoolFacet(); // prepare DiamondInit args @@ -198,7 +191,7 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { }); // prepare facet cuts - FacetCut[] memory cuts = new FacetCut[](7); + FacetCut[] memory cuts = new FacetCut[](6); cuts[0] = ( FacetCut({ facetAddress: address(accessControlFacetImplementation), @@ -235,13 +228,6 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { }) ); cuts[5] = ( - FacetCut({ - facetAddress: address(twapOracleDollar3PoolFacetImplementation), - action: FacetCutAction.Add, - functionSelectors: selectorsOfTWAPOracleDollar3poolFacet - }) - ); - cuts[6] = ( FacetCut({ facetAddress: address(ubiquityPoolFacetImplementation), action: FacetCutAction.Add, @@ -438,7 +424,7 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { ); // deploy mock Curve's Dollar-3CRVLP metapool - curveDollarMetaPool = new MockMetaPool( + curveDollarMetaPool = new MockCurveStableSwapMetaNG( address(dollarToken), address(curveTriPoolLpToken) ); @@ -450,23 +436,15 @@ contract Deploy001_Diamond_Dollar is Script, DiamondTestHelper { // Curve's Dollar-3CRVLP metapool setup //======================================== - // start sending owner transactions - vm.startBroadcast(ownerPrivateKey); - - TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacet = TWAPOracleDollar3poolFacet( - address(diamond) - ); + // start sending admin transactions + vm.startBroadcast(adminPrivateKey); - // set Curve Dollar-3CRVLP pool in the diamond storage - twapOracleDollar3PoolFacet.setPool( - address(curveDollarMetaPool), - address(curveTriPoolLpToken) - ); + ManagerFacet managerFacet = ManagerFacet(address(diamond)); - // fetch latest Dollar price from Curve's Dollar-3CRVLP metapool - twapOracleDollar3PoolFacet.update(); + // set curve's metapool in manager facet + managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); - // stop sending owner transactions + // stop sending admin transactions vm.stopBroadcast(); } } diff --git a/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar.s.sol b/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar.s.sol index 30d0e261c..950573cc8 100644 --- a/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar.s.sol +++ b/packages/contracts/migrations/mainnet/Deploy001_Diamond_Dollar.s.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.19; import {AggregatorV3Interface} from "@chainlink/interfaces/AggregatorV3Interface.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {Deploy001_Diamond_Dollar as Deploy001_Diamond_Dollar_Development} from "../development/Deploy001_Diamond_Dollar.s.sol"; -import {TWAPOracleDollar3poolFacet} from "../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; +import {ManagerFacet} from "../../src/dollar/facets/ManagerFacet.sol"; import {UbiquityPoolFacet} from "../../src/dollar/facets/UbiquityPoolFacet.sol"; -import {IMetaPool} from "../../src/dollar/interfaces/IMetaPool.sol"; +import {ICurveStableSwapMetaNG} from "../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; /// @notice Migration contract contract Deploy001_Diamond_Dollar is Deploy001_Diamond_Dollar_Development { @@ -82,31 +82,22 @@ contract Deploy001_Diamond_Dollar is Deploy001_Diamond_Dollar_Development { // Curve's Dollar-3CRVLP metapool setup //======================================== - // start sending owner transactions - vm.startBroadcast(ownerPrivateKey); + // start sending admin transactions + vm.startBroadcast(adminPrivateKey); // init 3CRV token curveTriPoolLpToken = IERC20(token3CrvAddress); // init Dollar-3CRVLP Curve metapool - curveDollarMetaPool = IMetaPool(curveDollarMetapoolAddress); - - /* - TODO: uncomment when we redeploy Curve's Dollar-3CRV metapool with the new Dollar token - - TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacet = TWAPOracleDollar3poolFacet(address(diamond)); - - // set Curve Dollar-3CRVLP pool in the diamond storage - twapOracleDollar3PoolFacet.setPool( - address(curveDollarMetaPool), - address(curveTriPoolLpToken) + curveDollarMetaPool = ICurveStableSwapMetaNG( + curveDollarMetapoolAddress ); - // fetch latest Dollar price from Curve's Dollar-3CRVLP metapool - twapOracleDollar3PoolFacet.update(); - */ + // set curve's metapool in manager facet + ManagerFacet managerFacet = ManagerFacet(address(diamond)); + managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); - // stop sending owner transactions + // stop sending admin transactions vm.stopBroadcast(); } } diff --git a/packages/contracts/scripts/shared/constants/networks.ts b/packages/contracts/scripts/shared/constants/networks.ts index 21a162494..9e4f7ce00 100644 --- a/packages/contracts/scripts/shared/constants/networks.ts +++ b/packages/contracts/scripts/shared/constants/networks.ts @@ -2,6 +2,7 @@ export const Networks: Record = { mainnet: 1, optimism: 10, goerli: 5, + sepolia: 11155111, }; export const FALLBACK_RPC = "https://eth.ubq.fi/v1/mainnet"; diff --git a/packages/contracts/scripts/task/README.md b/packages/contracts/scripts/task/README.md new file mode 100644 index 000000000..0ceeab005 --- /dev/null +++ b/packages/contracts/scripts/task/README.md @@ -0,0 +1,23 @@ +# Dollar task scripts + +## BlocksInWeek + +BlocksInWeek task provides a close approximate of number of blocks mined in one week. + +Usage: + +Ethereum mainnet: + +``` +npx tsx scripts/task/task.ts BlocksInWeek --network=mainnet +``` + +Sepolia: + +Ethereum mainnet: + +``` +npx tsx scripts/task/task.ts BlocksInWeek --network=sepolia +``` + +Prerequisite: set ETHERSCAN_API_KEY in .env \ No newline at end of file diff --git a/packages/contracts/scripts/task/dollar/blocks-in-week.ts b/packages/contracts/scripts/task/dollar/blocks-in-week.ts new file mode 100644 index 000000000..9899a07dd --- /dev/null +++ b/packages/contracts/scripts/task/dollar/blocks-in-week.ts @@ -0,0 +1,51 @@ +import { OptionDefinition } from "command-line-args"; + +import { Networks, TaskFuncParam } from "../../shared"; +import { EtherscanProvider } from "ethers"; + +export const optionDefinitions: OptionDefinition[] = [ + { name: "task", defaultOption: true }, + { name: "network", alias: "n", type: String }, +]; + +const funcBlocksInAWeek = async (params: TaskFuncParam) => { + const { args, env } = params; + const { network } = args; + + const chainId = Networks[network] ?? undefined; + if (!chainId) { + throw new Error(`Unsupported network: ${network} Please configure it out first`); + } + + const provider = new EtherscanProvider(chainId, env.etherscanApiKey); + + console.log(`Calculating number of blocks in the last week...`); + const secondsInAWeek = 604800; // 24 * 7 * 60 * 60 seconds is one week + const currentBlockNumber = await provider.getBlockNumber(); + const currentBlockTimestamp = (await provider.getBlock(currentBlockNumber))?.timestamp; + const blockTimestampTwoBlocksAgo = (await provider.getBlock(currentBlockNumber - 2))?.timestamp; + + if (currentBlockTimestamp && blockTimestampTwoBlocksAgo) { + const avgBlockTime = (currentBlockTimestamp - blockTimestampTwoBlocksAgo) / 2; + console.log(`Recent average block time: ${avgBlockTime} seconds`); + + const oneWeekAgo = currentBlockTimestamp - secondsInAWeek; + const estimatedBlocksInAWeek = secondsInAWeek / avgBlockTime; + console.log(`Estimated blocks in a week best case ${estimatedBlocksInAWeek}`); + + let estimatedBlockNumber = currentBlockNumber - estimatedBlocksInAWeek; + let estimatedBlockTimestamp = (await provider.getBlock(estimatedBlockNumber))?.timestamp; + + if (estimatedBlockTimestamp) { + let deltaBlockTime = oneWeekAgo - estimatedBlockTimestamp; + estimatedBlockNumber += Math.trunc(deltaBlockTime / avgBlockTime); + estimatedBlockTimestamp = (await provider.getBlock(estimatedBlockNumber))?.timestamp || estimatedBlockTimestamp; + deltaBlockTime -= estimatedBlockTimestamp - oneWeekAgo; + + console.log(`Produced ${estimatedBlocksInAWeek - deltaBlockTime / avgBlockTime} blocks, ${deltaBlockTime / avgBlockTime} worst than the best case`); + } + } + + return "succeeded"; +}; +export default funcBlocksInAWeek; diff --git a/packages/contracts/scripts/task/manager.ts b/packages/contracts/scripts/task/manager.ts index 66b541df8..20a8b99bd 100644 --- a/packages/contracts/scripts/task/manager.ts +++ b/packages/contracts/scripts/task/manager.ts @@ -2,11 +2,16 @@ import { OptionDefinition } from "command-line-args"; import { TaskFuncCallBack } from "../shared"; -import PriceResetHandler, { optionDefinitions as priceResetOptions } from "./dollar/priceReset"; +import PriceResetHandler, { optionDefinitions, optionDefinitions as priceResetOptions } from "./dollar/price-reset"; +import BlocksInWeekHandler from "./dollar/blocks-in-week"; export const TASK_FUNCS: Record = { PriceReset: { handler: PriceResetHandler, options: priceResetOptions, }, + BlocksInWeek: { + handler: BlocksInWeekHandler, + options: optionDefinitions, + }, }; diff --git a/packages/contracts/scripts/task/task.ts b/packages/contracts/scripts/task/task.ts index 6acdbb273..ca54c1918 100644 --- a/packages/contracts/scripts/task/task.ts +++ b/packages/contracts/scripts/task/task.ts @@ -11,7 +11,7 @@ const main = async () => { throw new Error("You MUST put the task name in command arguments at least"); } - const envPath = path.join(__dirname, "../.env"); + const envPath = path.join(__dirname, "../../.env"); if (!fs.existsSync(envPath)) { throw new Error("Env file not found"); } @@ -26,6 +26,7 @@ const main = async () => { let args; try { args = CommandLineArgs(commandLineParseOptions); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { console.error(`Argument parse failed!, error: ${error}`); return; diff --git a/packages/contracts/src/deprecated/TWAPOracle.sol b/packages/contracts/src/deprecated/TWAPOracle.sol index 5655c168d..32e2ce58b 100644 --- a/packages/contracts/src/deprecated/TWAPOracle.sol +++ b/packages/contracts/src/deprecated/TWAPOracle.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.3; -import "../dollar/interfaces/IMetaPool.sol"; +import "./interfaces/IMetaPool.sol"; contract TWAPOracle { address public immutable pool; diff --git a/packages/contracts/src/deprecated/UbiquityAlgorithmicDollarManager.sol b/packages/contracts/src/deprecated/UbiquityAlgorithmicDollarManager.sol index 82452ee71..32258faf6 100644 --- a/packages/contracts/src/deprecated/UbiquityAlgorithmicDollarManager.sol +++ b/packages/contracts/src/deprecated/UbiquityAlgorithmicDollarManager.sol @@ -6,9 +6,9 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./interfaces/IMetaPool.sol"; import "./interfaces/IUbiquityAlgorithmicDollar.sol"; import "../dollar/interfaces/ICurveFactory.sol"; -import "../dollar/interfaces/IMetaPool.sol"; import "./TWAPOracle.sol"; diff --git a/packages/contracts/src/dollar/interfaces/IMetaPool.sol b/packages/contracts/src/deprecated/interfaces/IMetaPool.sol similarity index 100% rename from packages/contracts/src/dollar/interfaces/IMetaPool.sol rename to packages/contracts/src/deprecated/interfaces/IMetaPool.sol diff --git a/packages/contracts/src/dollar/access/AccessControlInternal.sol b/packages/contracts/src/dollar/access/AccessControlInternal.sol index 3698eddf0..2f8dd1e81 100644 --- a/packages/contracts/src/dollar/access/AccessControlInternal.sol +++ b/packages/contracts/src/dollar/access/AccessControlInternal.sol @@ -83,6 +83,18 @@ abstract contract AccessControlInternal { return LibAccessControl.accessControlStorage().roles[role].adminRole; } + /** + * @notice Set admin role for a given role + * @param role Role to set + * @param adminRole role for the provided role + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + LibAccessControl + .accessControlStorage() + .roles[role] + .adminRole = adminRole; + } + /** * @notice Assigns role to a given account * @param role Role to assign diff --git a/packages/contracts/src/dollar/core/UbiquityDollarToken.sol b/packages/contracts/src/dollar/core/UbiquityDollarToken.sol index 0c157185d..1354c4b6c 100644 --- a/packages/contracts/src/dollar/core/UbiquityDollarToken.sol +++ b/packages/contracts/src/dollar/core/UbiquityDollarToken.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import {ERC20Ubiquity} from "./ERC20Ubiquity.sol"; import {IERC20Ubiquity} from "../../dollar/interfaces/IERC20Ubiquity.sol"; -import {IIncentive} from "../../dollar/interfaces/IIncentive.sol"; import "../libraries/Constants.sol"; @@ -11,18 +10,6 @@ import "../libraries/Constants.sol"; * @notice Ubiquity Dollar token contract */ contract UbiquityDollarToken is ERC20Ubiquity { - /** - * @notice Mapping of account and incentive contract address - * @dev Address is 0 if there is no incentive contract for the account - */ - mapping(address => address) public incentiveContract; - - /// @notice Emitted on setting an incentive contract for an account - event IncentiveContractUpdate( - address indexed _incentivized, - address indexed _incentiveContract - ); - /// @notice Ensures initialize cannot be called on the implementation contract constructor() { _disableInitializers(); @@ -55,109 +42,6 @@ contract UbiquityDollarToken is ERC20Ubiquity { _; } - /** - * @notice Sets `incentive` contract for `account` - * @notice Incentive contracts are applied on Dollar transfers: - * - EOA => contract - * - contract => EOA - * - contract => contract - * - any transfer global incentive - * @param account Account to incentivize - * @param incentive Incentive contract address - */ - function setIncentiveContract(address account, address incentive) external { - require( - accessControl.hasRole(GOVERNANCE_TOKEN_MANAGER_ROLE, _msgSender()), - "Dollar: must have admin role" - ); - - incentiveContract[account] = incentive; - emit IncentiveContractUpdate(account, incentive); - } - - /** - * @notice Applies incentives on Dollar transfers - * @param sender Sender address - * @param recipient Recipient address - * @param amount Dollar token transfer amount - */ - function _checkAndApplyIncentives( - address sender, - address recipient, - uint256 amount - ) internal { - // incentive on sender - address senderIncentive = incentiveContract[sender]; - if (senderIncentive != address(0)) { - IIncentive(senderIncentive).incentivize( - sender, - recipient, - _msgSender(), - amount - ); - } - - // incentive on recipient - address recipientIncentive = incentiveContract[recipient]; - if (recipientIncentive != address(0)) { - IIncentive(recipientIncentive).incentivize( - sender, - recipient, - _msgSender(), - amount - ); - } - - // incentive on operator - address operatorIncentive = incentiveContract[_msgSender()]; - if ( - _msgSender() != sender && - _msgSender() != recipient && - operatorIncentive != address(0) - ) { - IIncentive(operatorIncentive).incentivize( - sender, - recipient, - _msgSender(), - amount - ); - } - - // all incentive, if active applies to every transfer - address allIncentive = incentiveContract[address(0)]; - if (allIncentive != address(0)) { - IIncentive(allIncentive).incentivize( - sender, - recipient, - _msgSender(), - amount - ); - } - } - - /** - * @notice Moves `amount` of tokens from `from` to `to` and applies incentives. - * - * This internal function is equivalent to `transfer`, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a `Transfer` event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address sender, - address recipient, - uint256 amount - ) internal override { - super._transfer(sender, recipient, amount); - _checkAndApplyIncentives(sender, recipient, amount); - } - /** * @notice Burns Dollars from the `account` address * @param account Address to burn tokens from diff --git a/packages/contracts/src/dollar/facets/AccessControlFacet.sol b/packages/contracts/src/dollar/facets/AccessControlFacet.sol index cd813cfdb..4fe247194 100644 --- a/packages/contracts/src/dollar/facets/AccessControlFacet.sol +++ b/packages/contracts/src/dollar/facets/AccessControlFacet.sol @@ -24,6 +24,11 @@ contract AccessControlFacet is return _grantRole(role, account); } + /// @inheritdoc IAccessControl + function setRoleAdmin(bytes32 role, bytes32 adminRole) external onlyAdmin { + _setRoleAdmin(role, adminRole); + } + /// @inheritdoc IAccessControl function hasRole( bytes32 role, diff --git a/packages/contracts/src/dollar/facets/ManagerFacet.sol b/packages/contracts/src/dollar/facets/ManagerFacet.sol index d71843a2e..2e7611cc8 100644 --- a/packages/contracts/src/dollar/facets/ManagerFacet.sol +++ b/packages/contracts/src/dollar/facets/ManagerFacet.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../dollar/interfaces/IUbiquityDollarToken.sol"; import "../../dollar/interfaces/ICurveFactory.sol"; -import "../../dollar/interfaces/IMetaPool.sol"; +import "../../dollar/interfaces/ICurveStableSwapMetaNG.sol"; import "../libraries/LibAccessControl.sol"; /** @@ -246,8 +246,10 @@ contract ManagerFacet is Modifiers { // coin at index 0 is Dollar and index 1 is 3CRV require( - IMetaPool(metaPool).coins(0) == store.dollarTokenAddress && - IMetaPool(metaPool).coins(1) == _crv3PoolTokenAddress, + ICurveStableSwapMetaNG(metaPool).coins(0) == + store.dollarTokenAddress && + ICurveStableSwapMetaNG(metaPool).coins(1) == + _crv3PoolTokenAddress, "MGR: COIN_ORDER_MISMATCH" ); // Add the initial liquidity to the StableSwap meta pool @@ -257,7 +259,7 @@ contract ManagerFacet is Modifiers { ]; // set curve 3Pool address store.curve3PoolTokenAddress = _crv3PoolTokenAddress; - IMetaPool(metaPool).add_liquidity(amounts, 0, msg.sender); + ICurveStableSwapMetaNG(metaPool).add_liquidity(amounts, 0, msg.sender); } /** diff --git a/packages/contracts/src/dollar/facets/TWAPOracleDollar3poolFacet.sol b/packages/contracts/src/dollar/facets/TWAPOracleDollar3poolFacet.sol deleted file mode 100644 index 193d6b5d6..000000000 --- a/packages/contracts/src/dollar/facets/TWAPOracleDollar3poolFacet.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.19; - -import {LibTWAPOracle} from "../libraries/LibTWAPOracle.sol"; -import {Modifiers} from "../libraries/LibAppStorage.sol"; - -import {ITWAPOracleDollar3pool} from "../../dollar/interfaces/ITWAPOracleDollar3pool.sol"; - -/** - * @notice Facet used for Curve TWAP oracle in the Dollar MetaPool - */ -contract TWAPOracleDollar3poolFacet is Modifiers, ITWAPOracleDollar3pool { - /** - * @notice Sets Curve MetaPool to be used as a TWAP oracle - * @param _pool Curve MetaPool address, pool for 2 tokens [Dollar, 3CRV LP] - * @param _curve3CRVToken1 Curve 3Pool LP token address - */ - function setPool( - address _pool, - address _curve3CRVToken1 - ) external onlyOwner { - return LibTWAPOracle.setPool(_pool, _curve3CRVToken1); - } - - /** - * @notice Updates the following state variables to the latest values from MetaPool: - * - Dollar / 3CRV LP quote - * - 3CRV LP / Dollar quote - * - cumulative prices - * - update timestamp - */ - function update() external { - LibTWAPOracle.update(); - } - - /** - * @notice Returns the quote for the provided `token` address - * @notice If the `token` param is Dollar then returns 3CRV LP / Dollar quote - * @notice If the `token` param is 3CRV LP then returns Dollar / 3CRV LP quote - * @dev This will always return 0 before update has been called successfully for the first time - * @param token Token address - * @return amountOut Token price, Dollar / 3CRV LP or 3CRV LP / Dollar quote - */ - function consult(address token) external view returns (uint256 amountOut) { - return LibTWAPOracle.consult(token); - } -} diff --git a/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol b/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol index 52c6486e2..8becaccec 100644 --- a/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol +++ b/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol @@ -69,6 +69,18 @@ contract UbiquityPoolFacet is IUbiquityPool, Modifiers { return LibUbiquityPool.getDollarPriceUsd(); } + /// @inheritdoc IUbiquityPool + function getRedeemCollateralBalance( + address userAddress, + uint256 collateralIndex + ) external view returns (uint256) { + return + LibUbiquityPool.getRedeemCollateralBalance( + userAddress, + collateralIndex + ); + } + //==================== // Public functions //==================== diff --git a/packages/contracts/src/dollar/interfaces/IAccessControl.sol b/packages/contracts/src/dollar/interfaces/IAccessControl.sol index a83822635..1ee285f28 100644 --- a/packages/contracts/src/dollar/interfaces/IAccessControl.sol +++ b/packages/contracts/src/dollar/interfaces/IAccessControl.sol @@ -22,6 +22,13 @@ interface IAccessControl { */ function getRoleAdmin(bytes32 role) external view returns (bytes32); + /** + * @notice Sets admin role for a given role + * @param role Role to set + * @param adminRole Admin role to set for a provided role + */ + function setRoleAdmin(bytes32 role, bytes32 adminRole) external; + /** * @notice Assigns role to a given account * @param role Role to assign diff --git a/packages/contracts/src/dollar/interfaces/ICurveStableSwapMetaNG.sol b/packages/contracts/src/dollar/interfaces/ICurveStableSwapMetaNG.sol new file mode 100644 index 000000000..5a9864457 --- /dev/null +++ b/packages/contracts/src/dollar/interfaces/ICurveStableSwapMetaNG.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; + +/** + * @notice Curve MetaPool interface + * + * @notice **What is Curve MetaPool** + * @notice The pool that consists of 2 tokens: stable coin and 3CRV LP token. + * For example the pool may contain Ubiquity Dollar and 3CRV LP token. + * This allows users to trade between Ubiquity Dollar and any of the tokens + * from the Curve 3Pool (DAI, USDC, USDT). When user adds liquidity to the pool + * then he is rewarded with MetaPool LP tokens. 1 Dollar3CRV LP token != 1 stable coin token. + * @notice Add liquidity example: + * 1. User sends 100 Ubiquity Dollars to the pool + * 2. User gets 100 Dollar3CRV LP tokens of the pool + * @notice Remove liquidity example: + * 1. User sends 100 Dollar3CRV LP tokens to the pool + * 2. User gets 100 Dollar/DAI/USDC/USDT (may choose any) tokens + * + * @dev Source: https://github.com/curvefi/stableswap-ng/blob/bff1522b30819b7b240af17ccfb72b0effbf6c47/contracts/main/CurveStableSwapMetaNG.vy + * @dev Docs: https://docs.curve.fi/stableswap-exchange/stableswap-ng/pools/metapool/ + */ +interface ICurveStableSwapMetaNG is IERC20 { + /** + * @notice Deposits coins into to the pool and mints new LP tokens + * @param _amounts List of amounts of underlying coins to deposit. + * Amounts correspond to the tokens at the same index locations within `coins`. + * @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + * @param _receiver Optional address that receives the LP tokens. If not specified, they are sent to the caller. + * @return The amount of LP tokens that were minted in the deposit + */ + function add_liquidity( + uint256[2] memory _amounts, + uint256 _min_mint_amount, + address _receiver + ) external returns (uint256); + + /** + * @notice Estimates the amount of LP tokens minted or burned based on a deposit or withdrawal + * + * @notice This calculation accounts for slippage, but not fees. It should be used as a basis for + * determining expected amounts when calling `add_liquidity()` or `remove_liquidity_imbalance()`, + * but should not be considered to be precise! + * + * @param _amounts Amount of each coin being deposited. Amounts correspond to the tokens at the + * same index locations within `coins()`. + * @param _is_deposit Set `True` for deposits, `False` for withdrawals + * @return The expected amount of LP tokens minted or burned + */ + function calc_token_amount( + uint256[2] memory _amounts, + bool _is_deposit + ) external view returns (uint256); + + /** + * @notice Returns token address by the provided `arg0` index + * @param arg0 Token index + * @return Token address + */ + function coins(uint256 arg0) external view returns (address); + + /** + * @notice Performs an exchange between two tokens. Index values can be found + * using the `coins()` public getter method, or `get_coins()` within the factory contract. + * @param i Index value of the token to send + * @param j Index value of the token to receive + * @param dx The amount of `i` being exchanged + * @param min_dy The minimum amount of `j` to receive. If the swap would result in less, the transaction will revert. + * @return The amount of `j` received in the exchange + */ + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + /** + * @notice Function to calculate the exponential moving average (ema) price for the coin at index value `i` + * @param i Index value of coin + * @return Price oracle + */ + function price_oracle(uint256 i) external view returns (uint256); + + /** + * @notice Withdraws a single asset from the pool + * @param _burn_amount Amount of LP tokens to burn in the withdrawal + * @param i Index value of the coin to withdraw. Can be found using the `coins()` getter method. + * @param _min_received Minimum amount of the coin to receive + * @return The amount of the coin received in the withdrawal + */ + function remove_liquidity_one_coin( + uint256 _burn_amount, + int128 i, + uint256 _min_received + ) external returns (uint256); +} diff --git a/packages/contracts/src/dollar/interfaces/IIncentive.sol b/packages/contracts/src/dollar/interfaces/IIncentive.sol deleted file mode 100644 index 8f0469a20..000000000 --- a/packages/contracts/src/dollar/interfaces/IIncentive.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -/** - * @notice Incentive contract interface - * @notice Called by Ubiquity Dollar token contract when transferring with an incentivized address. - * Dollar admin can set an incentive contract for a partner in order to, for example, mint partner's - * project tokens on Dollars transfers. Incentive contracts can be set for the following transfer operations: - * - EOA => contract - * - contract => EOA - * - contract => contract - * - any transfer incentive contract - * @dev Should be appointed as a Minter or Burner as needed - */ -interface IIncentive { - /** - * @notice Applies incentives on transfer - * @param sender the sender address of Ubiquity Dollar - * @param receiver the receiver address of Ubiquity Dollar - * @param operator the operator (msg.sender) of the transfer - * @param amount the amount of Ubiquity Dollar transferred - */ - function incentivize( - address sender, - address receiver, - address operator, - uint256 amount - ) external; -} diff --git a/packages/contracts/src/dollar/interfaces/ITWAPOracleDollar3pool.sol b/packages/contracts/src/dollar/interfaces/ITWAPOracleDollar3pool.sol deleted file mode 100644 index 6cb84c025..000000000 --- a/packages/contracts/src/dollar/interfaces/ITWAPOracleDollar3pool.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -/** - * @notice TWAP oracle interface for Curve MetaPool - * - * @notice **What is Curve 3Pool** - * @notice The pool that consists of 3 tokens: DAI, USDC, USDT. - * Users are free to trade (swap) those tokens. When user adds liquidity - * to the pool then he is rewarded with the pool's LP token 3CRV. - * 1 3CRV LP token != 1 stable coin token. - * @notice Add liquidity example: - * 1. User sends 5 USDC to the pool - * 2. User gets 5 3CRV LP tokens - * @notice Remove liquidity example: - * 1. User sends 99 3CRV LP tokens - * 2. User gets 99 USDT tokens - * - * @notice **What is Curve MetaPool** - * @notice The pool that consists of 2 tokens: stable coin and 3CRV LP token. - * For example the pool may contain Ubiquity Dollar and 3CRV LP token. - * This allows users to trade between Ubiquity Dollar and any of the tokens - * from the Curve 3Pool (DAI, USDC, USDT). When user adds liquidity to the pool - * then he is rewarded with MetaPool LP tokens. 1 Dollar3CRV LP token != 1 stable coin token. - * @notice Add liquidity example: - * 1. User sends 100 Ubiquity Dollars to the pool - * 2. User gets 100 Dollar3CRV LP tokens of the pool - * @notice Remove liquidity example: - * 1. User sends 100 Dollar3CRV LP tokens to the pool - * 2. User gets 100 Dollar/DAI/USDC/USDT (may choose any) tokens - */ -interface ITWAPOracleDollar3pool { - /** - * @notice Updates the following state variables to the latest values from MetaPool: - * - Dollar / 3CRV LP quote - * - 3CRV LP / Dollar quote - * - cumulative prices - * - update timestamp - */ - function update() external; - - /** - * @notice Returns the quote for the provided `token` address - * @notice If the `token` param is Dollar then returns 3CRV LP / Dollar quote - * @notice If the `token` param is 3CRV LP then returns Dollar / 3CRV LP quote - * @dev This will always return 0 before update has been called successfully for the first time - * @param token Token address - * @return amountOut Token price, Dollar / 3CRV LP or 3CRV LP / Dollar quote - */ - function consult(address token) external view returns (uint256 amountOut); -} diff --git a/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol b/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol index 5e91ddc9c..23e934e4f 100644 --- a/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol +++ b/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol @@ -67,6 +67,17 @@ interface IUbiquityPool { */ function getDollarPriceUsd() external view returns (uint256 dollarPriceUsd); + /** + * @notice Returns user's balance available for redemption + * @param userAddress User address + * @param collateralIndex Collateral token index + * @return User's balance available for redemption + */ + function getRedeemCollateralBalance( + address userAddress, + uint256 collateralIndex + ) external view returns (uint256); + //==================== // Public functions //==================== diff --git a/packages/contracts/src/dollar/libraries/LibChef.sol b/packages/contracts/src/dollar/libraries/LibChef.sol index 0d2a07003..c09eba916 100644 --- a/packages/contracts/src/dollar/libraries/LibChef.sol +++ b/packages/contracts/src/dollar/libraries/LibChef.sol @@ -3,11 +3,10 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../../dollar/interfaces/IERC20Ubiquity.sol"; -import "../../dollar/interfaces/ITWAPOracleDollar3pool.sol"; import "../../dollar/interfaces/IUbiquityFormulas.sol"; import "../../dollar/interfaces/IERC1155Ubiquity.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; import "./LibAppStorage.sol"; -import {LibTWAPOracle} from "./LibTWAPOracle.sol"; import {LibStakingFormulas} from "./LibStakingFormulas.sol"; /** @@ -319,9 +318,12 @@ library LibChef { * @notice Updates Governance token multiplier if Dollar price diff > `minPriceDiffToUpdateMultiplier` */ function _updateGovernanceMultiplier() internal { + AppStorage storage store = LibAppStorage.appStorage(); ChefStorage storage cs = chefStorage(); // (1.05/(1+abs(1-TWAP_PRICE))) - uint256 currentPrice = LibTWAPOracle.getTwapPrice(); + uint256 currentPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); bool isPriceDiffEnough = false; // a minimum price variation is needed to update the multiplier if (currentPrice > cs.lastPrice) { diff --git a/packages/contracts/src/dollar/libraries/LibCreditNftManager.sol b/packages/contracts/src/dollar/libraries/LibCreditNftManager.sol index 78ecc75b9..9e48e59bc 100644 --- a/packages/contracts/src/dollar/libraries/LibCreditNftManager.sol +++ b/packages/contracts/src/dollar/libraries/LibCreditNftManager.sol @@ -5,11 +5,11 @@ import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {CreditNft} from "../../dollar/core/CreditNft.sol"; import {CREDIT_NFT_MANAGER_ROLE} from "./Constants.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; import {IERC20Ubiquity} from "../../dollar/interfaces/IERC20Ubiquity.sol"; import {IDollarMintExcess} from "../../dollar/interfaces/IDollarMintExcess.sol"; import {LibAppStorage, AppStorage} from "./LibAppStorage.sol"; import {LibCreditRedemptionCalculator} from "./LibCreditRedemptionCalculator.sol"; -import {LibTWAPOracle} from "./LibTWAPOracle.sol"; import {LibCreditNftRedemptionCalculator} from "./LibCreditNftRedemptionCalculator.sol"; import {UbiquityCreditToken} from "../../dollar/core/UbiquityCreditToken.sol"; import {LibAccessControl} from "./LibAccessControl.sol"; @@ -124,8 +124,11 @@ library LibCreditNftManager { function exchangeDollarsForCreditNft( uint256 amount ) internal returns (uint256) { - uint256 twapPrice = LibTWAPOracle.getTwapPrice(); + AppStorage storage store = LibAppStorage.appStorage(); + uint256 twapPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); require( twapPrice < 1 ether, "Price must be below 1 to mint Credit NFT" @@ -169,10 +172,13 @@ library LibCreditNftManager { function exchangeDollarsForCredit( uint256 amount ) internal returns (uint256) { - uint256 twapPrice = LibTWAPOracle.getTwapPrice(); + AppStorage storage store = LibAppStorage.appStorage(); + uint256 twapPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); require(twapPrice < 1 ether, "Price must be below 1 to mint Credit"); - AppStorage storage store = LibAppStorage.appStorage(); + CreditNft creditNft = CreditNft(store.creditNftAddress); creditNft.updateTotalDebt(); @@ -334,8 +340,13 @@ library LibCreditNftManager { function burnCreditTokensForDollars( uint256 amount ) public returns (uint256) { - uint256 twapPrice = LibTWAPOracle.getTwapPrice(); + AppStorage storage store = LibAppStorage.appStorage(); + + uint256 twapPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); require(twapPrice > 1 ether, "Price must be above 1"); + if (creditNftStorage().debtCycle) { creditNftStorage().debtCycle = false; } @@ -376,8 +387,11 @@ library LibCreditNftManager { uint256 id, uint256 amount ) public returns (uint256) { - uint256 twapPrice = LibTWAPOracle.getTwapPrice(); + AppStorage storage store = LibAppStorage.appStorage(); + uint256 twapPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); require( twapPrice > 1 ether, "Price must be above 1 to redeem Credit NFT" @@ -385,7 +399,6 @@ library LibCreditNftManager { if (creditNftStorage().debtCycle) { creditNftStorage().debtCycle = false; } - AppStorage storage store = LibAppStorage.appStorage(); CreditNft creditNft = CreditNft(store.creditNftAddress); require(id > block.number, "Credit NFT has expired"); diff --git a/packages/contracts/src/dollar/libraries/LibCurveDollarIncentive.sol b/packages/contracts/src/dollar/libraries/LibCurveDollarIncentive.sol index 90f76d8b3..632c7a63d 100644 --- a/packages/contracts/src/dollar/libraries/LibCurveDollarIncentive.sol +++ b/packages/contracts/src/dollar/libraries/LibCurveDollarIncentive.sol @@ -3,12 +3,12 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./LibTWAPOracle.sol"; import "../core/UbiquityDollarToken.sol"; import "../interfaces/IUbiquityGovernance.sol"; import "abdk/ABDKMathQuad.sol"; import "./Constants.sol"; -import {LibAppStorage} from "./LibAppStorage.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; +import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; /** * @notice Library adds buy incentive and sell penalty for Curve's Dollar-3CRV MetaPool @@ -157,7 +157,6 @@ library LibCurveDollarIncentive { UbiquityDollarToken(LibAppStorage.appStorage().dollarTokenAddress) .burnFrom(target, penalty); // burn from the recipient } - LibTWAPOracle.update(); } /** @@ -183,7 +182,6 @@ library LibCurveDollarIncentive { LibAppStorage.appStorage().dollarTokenAddress ).mint(target, incentive); } - LibTWAPOracle.update(); } /** @@ -210,9 +208,9 @@ library LibCurveDollarIncentive { * @return Dollar price */ function _getTWAPPrice() internal view returns (uint256) { + AppStorage storage store = LibAppStorage.appStorage(); return - LibTWAPOracle.consult( - LibAppStorage.appStorage().dollarTokenAddress - ); + ICurveStableSwapMetaNG(store.stableSwapMetaPoolAddress) + .price_oracle(0); } } diff --git a/packages/contracts/src/dollar/libraries/LibDollarMintCalculator.sol b/packages/contracts/src/dollar/libraries/LibDollarMintCalculator.sol index dfbc60896..bac9d5221 100644 --- a/packages/contracts/src/dollar/libraries/LibDollarMintCalculator.sol +++ b/packages/contracts/src/dollar/libraries/LibDollarMintCalculator.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IDollarMintCalculator} from "../../dollar/interfaces/IDollarMintCalculator.sol"; import "abdk/ABDKMathQuad.sol"; -import "./LibTWAPOracle.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; import {LibAppStorage, AppStorage} from "./LibAppStorage.sol"; /// @notice Calculates amount of Dollars ready to be minted when TWAP price (i.e. Dollar price) > 1$ @@ -18,7 +18,9 @@ library LibDollarMintCalculator { */ function getDollarsToMint() internal view returns (uint256) { AppStorage storage store = LibAppStorage.appStorage(); - uint256 twapPrice = LibTWAPOracle.consult(store.dollarTokenAddress); + uint256 twapPrice = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); require(twapPrice > 1 ether, "DollarMintCalculator: not > 1"); bytes16 _one = (uint256(1 ether)).fromUInt(); return diff --git a/packages/contracts/src/dollar/libraries/LibDollarMintExcess.sol b/packages/contracts/src/dollar/libraries/LibDollarMintExcess.sol index 53943e46c..e34289172 100644 --- a/packages/contracts/src/dollar/libraries/LibDollarMintExcess.sol +++ b/packages/contracts/src/dollar/libraries/LibDollarMintExcess.sol @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; import {IERC20Ubiquity} from "../../dollar/interfaces/IERC20Ubiquity.sol"; -import "../../dollar/interfaces/IMetaPool.sol"; +import "../../dollar/interfaces/ICurveStableSwapMetaNG.sol"; import "abdk/ABDKMathQuad.sol"; import {LibAppStorage, AppStorage} from "./LibAppStorage.sol"; @@ -145,8 +145,9 @@ library LibDollarMintExcess { dollar.approve(stableSwapMetaPoolAddress, amount); // swap amount of Ubiquity Dollar => 3CRV - uint256 amount3CRVReceived = IMetaPool(stableSwapMetaPoolAddress) - .exchange(0, 1, amount, 0); + uint256 amount3CRVReceived = ICurveStableSwapMetaNG( + stableSwapMetaPoolAddress + ).exchange(0, 1, amount, 0); // approve metapool to transfer our 3CRV IERC20(curve3PoolTokenAddress).approve(stableSwapMetaPoolAddress, 0); @@ -156,11 +157,12 @@ library LibDollarMintExcess { ); // deposit liquidity - uint256 res = IMetaPool(stableSwapMetaPoolAddress).add_liquidity( - [0, amount3CRVReceived], - 0, - address(this) // stacking contract - ); + uint256 res = ICurveStableSwapMetaNG(stableSwapMetaPoolAddress) + .add_liquidity( + [0, amount3CRVReceived], + 0, + address(this) // stacking contract + ); // update TWAP price return res; } diff --git a/packages/contracts/src/dollar/libraries/LibStaking.sol b/packages/contracts/src/dollar/libraries/LibStaking.sol index d8e5b93b3..7912f3931 100644 --- a/packages/contracts/src/dollar/libraries/LibStaking.sol +++ b/packages/contracts/src/dollar/libraries/LibStaking.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./LibTWAPOracle.sol"; import "./LibChef.sol"; import "./LibStakingFormulas.sol"; import {StakingShare} from "../core/StakingShare.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; /// @notice Staking library library LibStaking { @@ -85,14 +85,15 @@ library LibStaking { * @param amount Amount of LP token to be removed for Ubiquity Dollar */ function dollarPriceReset(uint256 amount) internal { - IMetaPool metaPool = IMetaPool(LibTWAPOracle.twapOracleStorage().pool); + ICurveStableSwapMetaNG metaPool = ICurveStableSwapMetaNG( + LibAppStorage.appStorage().stableSwapMetaPoolAddress + ); // remove one coin uint256 coinWithdrawn = metaPool.remove_liquidity_one_coin( amount, 0, 0 ); - LibTWAPOracle.update(); AppStorage storage store = LibAppStorage.appStorage(); IERC20 dollar = IERC20(store.dollarTokenAddress); uint256 toTransfer = dollar.balanceOf(address(this)); @@ -108,24 +109,22 @@ library LibStaking { * @param amount Amount of LP token to be removed for 3CRV tokens */ function crvPriceReset(uint256 amount) internal { - LibTWAPOracle.TWAPOracleStorage memory ts = LibTWAPOracle - .twapOracleStorage(); - IMetaPool metaPool = IMetaPool(ts.pool); + ICurveStableSwapMetaNG metaPool = ICurveStableSwapMetaNG( + LibAppStorage.appStorage().stableSwapMetaPoolAddress + ); // remove one coin uint256 coinWithdrawn = metaPool.remove_liquidity_one_coin( amount, 1, 0 ); - // update twap - LibTWAPOracle.update(); - uint256 toTransfer = IERC20(ts.token1).balanceOf(address(this)); + uint256 toTransfer = IERC20(metaPool.coins(1)).balanceOf(address(this)); - IERC20(ts.token1).transfer( + IERC20(metaPool.coins(1)).transfer( LibAppStorage.appStorage().treasuryAddress, toTransfer ); - emit PriceReset(ts.token1, coinWithdrawn, toTransfer); + emit PriceReset(metaPool.coins(1), coinWithdrawn, toTransfer); } /** @@ -179,16 +178,12 @@ library LibStaking { 1 <= _weeks && _weeks <= 208, "Staking: duration must be between 1 and 208 weeks" ); - LibTWAPOracle.update(); // update the accumulated lp rewards per shares _updateLpPerShare(); // transfer lp token to the staking contract - IERC20(LibTWAPOracle.twapOracleStorage().pool).safeTransferFrom( - msg.sender, - address(this), - _lpsAmount - ); + IERC20(LibAppStorage.appStorage().stableSwapMetaPoolAddress) + .safeTransferFrom(msg.sender, address(this), _lpsAmount); StakingData storage ss = stakingStorage(); // calculate the amount of share based on the amount of lp deposited and the duration uint256 _sharesAmount = LibStakingFormulas.durationMultiply( @@ -247,11 +242,8 @@ library LibStaking { stake.lpAmount += pendingLpReward; StakingData storage ss = stakingStorage(); ss.lpRewards -= pendingLpReward; - IERC20(LibTWAPOracle.twapOracleStorage().pool).safeTransferFrom( - msg.sender, - address(this), - _amount - ); + IERC20(LibAppStorage.appStorage().stableSwapMetaPoolAddress) + .safeTransferFrom(msg.sender, address(this), _amount); stake.lpAmount += _amount; // redeem all shares @@ -267,8 +259,8 @@ library LibStaking { // deposit new shares LibChef.deposit(msg.sender, _sharesAmount, _id); // calculate end locking period block number - // 1 week = 45361 blocks = 2371753*7/366 - // n = (block + duration * 45361) + // 1 week = 49930 blocks + // n = (block number + duration * 49930) stake.endBlock = block.number + _weeks * ss.blockCountInAWeek; // should be done after masterchef withdraw @@ -322,7 +314,9 @@ library LibStaking { // redeem of the extra LP // staking lp balance - StakingShare.totalLP - IERC20 metapool = IERC20(LibTWAPOracle.twapOracleStorage().pool); + IERC20 metapool = IERC20( + LibAppStorage.appStorage().stableSwapMetaPoolAddress + ); // add an extra step to be able to decrease rewards if locking end is near pendingLpReward = LibStakingFormulas @@ -380,8 +374,9 @@ library LibStaking { StakingShare.Stake memory stake = staking.getStake(_id); uint256[2] memory bs = LibChef.getStakingShareInfo(_id); - uint256 lpBalance = IERC20(LibTWAPOracle.twapOracleStorage().pool) - .balanceOf(address(this)); + uint256 lpBalance = IERC20( + LibAppStorage.appStorage().stableSwapMetaPoolAddress + ).balanceOf(address(this)); // the excess LP is the current balance minus the total deposited LP if (lpBalance >= (staking.totalLP() + ss.totalLpToMigrate)) { uint256 currentLpRewards = lpBalance - @@ -449,8 +444,9 @@ library LibStaking { .stakingShareAddress; StakingData storage ss = stakingStorage(); StakingShare stake = StakingShare(stakingShareAddress); - uint256 lpBalance = IERC20(LibTWAPOracle.twapOracleStorage().pool) - .balanceOf(address(this)); + uint256 lpBalance = IERC20( + LibAppStorage.appStorage().stableSwapMetaPoolAddress + ).balanceOf(address(this)); // the excess LP is the current balance // minus the total deposited LP + LP that needs to be migrated uint256 totalShares = LibChef.totalShares(); @@ -513,7 +509,11 @@ library LibStaking { */ function _checkForLiquidity( uint256 _id - ) internal returns (uint256[2] memory bs, StakingShare.Stake memory stake) { + ) + internal + view + returns (uint256[2] memory bs, StakingShare.Stake memory stake) + { address stakingAddress = LibAppStorage.appStorage().stakingShareAddress; require( IERC1155Ubiquity(stakingAddress).balanceOf(msg.sender, _id) == 1, @@ -526,7 +526,6 @@ library LibStaking { "Staking: Redeem not allowed before staking time" ); - LibTWAPOracle.update(); bs = LibChef.getStakingShareInfo(_id); } } diff --git a/packages/contracts/src/dollar/libraries/LibTWAPOracle.sol b/packages/contracts/src/dollar/libraries/LibTWAPOracle.sol deleted file mode 100644 index 03b642b22..000000000 --- a/packages/contracts/src/dollar/libraries/LibTWAPOracle.sol +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.19; - -import {IMetaPool} from "../../dollar/interfaces/IMetaPool.sol"; -import {LibAppStorage} from "./LibAppStorage.sol"; - -/** - * @notice Library used for Curve TWAP oracle in the Dollar MetaPool - */ -library LibTWAPOracle { - /// @notice Struct used as a storage for this library - struct TWAPOracleStorage { - address pool; // curve metapool address : Ubiquity Dollar <=> 3 Pool - // address token0; will always be address(this) - address token1; // curve 3pool LP token address - uint256 price0Average; - uint256 price1Average; - uint256 pricesBlockTimestampLast; - uint256[2] priceCumulativeLast; - } - - /// @notice Storage slot used to store data for this library - bytes32 constant TWAP_ORACLE_STORAGE_POSITION = - bytes32(uint256(keccak256("diamond.standard.twap.oracle.storage")) - 1); - - /** - * @notice Sets Curve MetaPool to be used as a TWAP oracle - * @param _pool Curve MetaPool address, pool for 2 tokens [Dollar, 3CRV LP] - * @param _curve3CRVToken1 Curve 3Pool LP token address - */ - function setPool(address _pool, address _curve3CRVToken1) internal { - require( - IMetaPool(_pool).coins(0) == - LibAppStorage.appStorage().dollarTokenAddress, - "TWAPOracle: FIRST_COIN_NOT_DOLLAR" - ); - TWAPOracleStorage storage ts = twapOracleStorage(); - - // coin at index 0 is Ubiquity Dollar and index 1 is 3CRV - require( - IMetaPool(_pool).coins(1) == _curve3CRVToken1, - "TWAPOracle: COIN_ORDER_MISMATCH" - ); - - uint256 _reserve0 = uint112(IMetaPool(_pool).balances(0)); - uint256 _reserve1 = uint112(IMetaPool(_pool).balances(1)); - - // ensure that there's liquidity in the pair - require(_reserve0 != 0 && _reserve1 != 0, "TWAPOracle: NO_RESERVES"); - // ensure that pair balance is perfect - require(_reserve0 == _reserve1, "TWAPOracle: PAIR_UNBALANCED"); - ts.priceCumulativeLast = IMetaPool(_pool).get_price_cumulative_last(); - ts.pricesBlockTimestampLast = IMetaPool(_pool).block_timestamp_last(); - ts.pool = _pool; - // dollar token is inside the diamond - ts.token1 = _curve3CRVToken1; - ts.price0Average = 1 ether; - ts.price1Average = 1 ether; - } - - /** - * @notice Updates the following state variables to the latest values from MetaPool: - * - Dollar / 3CRV LP quote - * - 3CRV LP / Dollar quote - * - cumulative prices - * - update timestamp - */ - function update() internal { - TWAPOracleStorage storage ts = twapOracleStorage(); - ( - uint256[2] memory priceCumulative, - uint256 blockTimestamp - ) = currentCumulativePrices(); - if (blockTimestamp - ts.pricesBlockTimestampLast > 0) { - // get the balances between now and the last price cumulative snapshot - uint256[2] memory twapBalances = IMetaPool(ts.pool) - .get_twap_balances( - ts.priceCumulativeLast, - priceCumulative, - blockTimestamp - ts.pricesBlockTimestampLast - ); - - // price to exchange amountIn Ubiquity Dollar to 3CRV based on TWAP - ts.price0Average = IMetaPool(ts.pool).get_dy( - 0, - 1, - 1 ether, - twapBalances - ); - - // price to exchange amountIn 3CRV to Ubiquity Dollar based on TWAP - ts.price1Average = IMetaPool(ts.pool).get_dy( - 1, - 0, - 1 ether, - twapBalances - ); - // we update the priceCumulative - ts.priceCumulativeLast = priceCumulative; - ts.pricesBlockTimestampLast = blockTimestamp; - } - } - - /** - * @notice Returns the quote for the provided `token` address - * @notice If the `token` param is Dollar then returns 3CRV LP / Dollar quote - * @notice If the `token` param is 3CRV LP then returns Dollar / 3CRV LP quote - * @param token Token address - * @return amountOut Token price, Dollar / 3CRV LP or 3CRV LP / Dollar quote - */ - function consult(address token) internal view returns (uint256 amountOut) { - TWAPOracleStorage memory ts = twapOracleStorage(); - - if (token == LibAppStorage.appStorage().dollarTokenAddress) { - // price to exchange 1 Ubiquity Dollar to 3CRV based on TWAP - amountOut = ts.price0Average; - } else { - require(token == ts.token1, "TWAPOracle: INVALID_TOKEN"); - // price to exchange 1 3CRV to Ubiquity Dollar based on TWAP - amountOut = ts.price1Average; - } - } - - /** - * @notice Returns current cumulative prices from metapool with updated timestamp - * @return priceCumulative Current cumulative prices for pool tokens - * @return blockTimestamp Current update timestamp - */ - function currentCumulativePrices() - internal - view - returns (uint256[2] memory priceCumulative, uint256 blockTimestamp) - { - address metapool = twapOracleStorage().pool; - priceCumulative = IMetaPool(metapool).get_price_cumulative_last(); - blockTimestamp = IMetaPool(metapool).block_timestamp_last(); - } - - /** - * @notice Returns struct used as a storage for this library - * @return ds Struct used as a storage - */ - function twapOracleStorage() - internal - pure - returns (TWAPOracleStorage storage ds) - { - bytes32 position = TWAP_ORACLE_STORAGE_POSITION; - assembly { - ds.slot := position - } - } - - /** - * @notice Returns current Dollar price - * @dev Returns 3CRV LP / Dollar quote, i.e. how many 3CRV LP tokens user will get for 1 Dollar - * @return Dollar price - */ - function getTwapPrice() internal view returns (uint256) { - return - LibTWAPOracle.consult( - LibAppStorage.appStorage().dollarTokenAddress - ); - } -} diff --git a/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol b/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol index 58b201fec..447a6ded3 100644 --- a/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol +++ b/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol @@ -6,11 +6,11 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; import {IDollarAmoMinter} from "../interfaces/IDollarAmoMinter.sol"; import {IERC20Ubiquity} from "../interfaces/IERC20Ubiquity.sol"; import {UBIQUITY_POOL_PRICE_PRECISION} from "./Constants.sol"; -import {LibAppStorage} from "./LibAppStorage.sol"; -import {LibTWAPOracle} from "./LibTWAPOracle.sol"; +import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; /** * @notice Ubiquity pool library @@ -320,14 +320,33 @@ library LibUbiquityPool { view returns (uint256 dollarPriceUsd) { + // load storage shared across all libraries + AppStorage storage store = LibAppStorage.appStorage(); // get Dollar price from Curve Metapool (18 decimals) - uint256 dollarPriceUsdD18 = LibTWAPOracle.getTwapPrice(); + uint256 dollarPriceUsdD18 = ICurveStableSwapMetaNG( + store.stableSwapMetaPoolAddress + ).price_oracle(0); // convert to 6 decimals dollarPriceUsd = dollarPriceUsdD18 .mul(UBIQUITY_POOL_PRICE_PRECISION) .div(1e18); } + /** + * @notice Returns user's balance available for redemption + * @param userAddress User address + * @param collateralIndex Collateral token index + * @return User's balance available for redemption + */ + function getRedeemCollateralBalance( + address userAddress, + uint256 collateralIndex + ) internal view returns (uint256) { + UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage(); + return + poolStorage.redeemCollateralBalances[userAddress][collateralIndex]; + } + //==================== // Public functions //==================== @@ -358,8 +377,6 @@ library LibUbiquityPool { "Minting is paused" ); - // update Dollar price from Curve's Dollar Metapool - LibTWAPOracle.update(); // prevent unnecessary mints require( getDollarPriceUsd() >= poolStorage.mintPriceThreshold, @@ -371,6 +388,7 @@ library LibUbiquityPool { // get amount of collateral for minting Dollars collateralNeeded = getDollarInCollateral(collateralIndex, dollarAmount); + require(collateralNeeded > 0, "Cannot mint with zero collateral"); // subtract the minting fee totalDollarMint = dollarAmount @@ -430,8 +448,6 @@ library LibUbiquityPool { "Redeeming is paused" ); - // update Dollar price from Curve's Dollar Metapool - LibTWAPOracle.update(); // prevent unnecessary redemptions that could adversely affect the Dollar price require( getDollarPriceUsd() <= poolStorage.redeemPriceThreshold, @@ -493,7 +509,11 @@ library LibUbiquityPool { */ function collectRedemption( uint256 collateralIndex - ) internal returns (uint256 collateralAmount) { + ) + internal + collateralEnabled(collateralIndex) + returns (uint256 collateralAmount) + { UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage(); require( @@ -609,6 +629,12 @@ library LibUbiquityPool { "Collateral disabled" ); + // ensure the pool is solvent (i.e. AMO minter borrows less than users want to redeem) + require( + collateralAmount <= freeCollateralBalance(minterCollateralIndex), + "Not enough free collateral" + ); + // transfer IERC20(poolStorage.collateralAddresses[minterCollateralIndex]) .safeTransfer(msg.sender, collateralAmount); diff --git a/packages/contracts/src/dollar/mocks/MockCurveFactory.sol b/packages/contracts/src/dollar/mocks/MockCurveFactory.sol index 692e91844..968d50159 100644 --- a/packages/contracts/src/dollar/mocks/MockCurveFactory.sol +++ b/packages/contracts/src/dollar/mocks/MockCurveFactory.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {ICurveFactory} from "../../dollar/interfaces/ICurveFactory.sol"; -import {MockMetaPool} from "../../dollar/mocks/MockMetaPool.sol"; +import {MockCurveStableSwapMetaNG} from "../../dollar/mocks/MockCurveStableSwapMetaNG.sol"; contract MockCurveFactory is ICurveFactory { // solhint-disable-next-line no-empty-blocks @@ -14,10 +14,10 @@ contract MockCurveFactory is ICurveFactory { uint256 /* _A */, uint256 /* _fee */ ) external returns (address) { - MockMetaPool metaPoolAddress = new MockMetaPool( - _coin, - MockMetaPool(_base_pool).coins(1) - ); + MockCurveStableSwapMetaNG metaPoolAddress = new MockCurveStableSwapMetaNG( + _coin, + MockCurveStableSwapMetaNG(_base_pool).coins(1) + ); return address(metaPoolAddress); } diff --git a/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol b/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol new file mode 100644 index 000000000..f067c08b3 --- /dev/null +++ b/packages/contracts/src/dollar/mocks/MockCurveStableSwapMetaNG.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; +import {MockERC20} from "./MockERC20.sol"; + +contract MockCurveStableSwapMetaNG is ICurveStableSwapMetaNG, MockERC20 { + address token0; + address token1; + address[2] public coins; + uint256 priceOracle = 1e18; + + constructor(address _token0, address _token1) MockERC20("Mock", "MCK", 18) { + coins[0] = _token0; + coins[1] = _token1; + } + + function add_liquidity( + uint256[2] memory _amounts, + uint256 _min_mint_amount, + address _receiver + ) external returns (uint256 result) { + mint( + _receiver, + _min_mint_amount == 0 + ? _amounts[0] > _amounts[1] ? _amounts[0] : _amounts[1] + : _min_mint_amount + ); + return result; + } + + function calc_token_amount( + uint256[2] memory _amounts, + bool /* _is_deposit */ + ) external pure returns (uint256) { + return _amounts[0] > _amounts[1] ? _amounts[0] : _amounts[1]; + } + + function exchange( + int128 /* i */, + int128 /* j */, + uint256 /* dx */, + uint256 /* min_dy */ + ) external pure returns (uint256) { + return 0; + } + + function price_oracle(uint256 /* i */) external view returns (uint256) { + return priceOracle; + } + + function remove_liquidity_one_coin( + uint256 /* _burn_amount */, + int128 /* i */, + uint256 /* _min_received */ + ) external pure returns (uint256) { + return 0; + } + + function updateMockParams(uint256 _priceOracle) public { + priceOracle = _priceOracle; + } +} diff --git a/packages/contracts/src/dollar/mocks/MockMetaPool.sol b/packages/contracts/src/dollar/mocks/MockMetaPool.sol deleted file mode 100644 index 574a52f88..000000000 --- a/packages/contracts/src/dollar/mocks/MockMetaPool.sol +++ /dev/null @@ -1,126 +0,0 @@ -// contracts/GLDToken.sol -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import {IMetaPool} from "../interfaces/IMetaPool.sol"; -import {MockERC20} from "./MockERC20.sol"; - -contract MockMetaPool is IMetaPool, MockERC20 { - address token0; - address token1; - address[2] public coins; - uint256[2] public balances = [10e18, 10e18]; - uint256[2] public dy_values = [100e18, 100e18]; - uint256[2] price_cumulative_last = [10e18, 10e18]; - uint256 last_block_timestamp = 10000; - - constructor(address _token0, address _token1) MockERC20("Mock", "MCK", 18) { - coins[0] = _token0; - coins[1] = _token1; - } - - function get_price_cumulative_last() - external - view - returns (uint256[2] memory) - { - return price_cumulative_last; - } - - function block_timestamp_last() external view returns (uint256) { - return last_block_timestamp; - } - - function get_twap_balances( - uint256[2] memory /* _first_balances */, - uint256[2] memory /* _last_balances */, - uint256 /* _time_elapsed */ - ) external view returns (uint256[2] memory) { - return balances; - } - - function get_dy( - int128 i, - int128 j, - uint256 /* dx */, - uint256[2] memory /* _balances */ - ) external view returns (uint256) { - if (i == 0 && j == 1) { - return dy_values[1]; - } else if (i == 1 && j == 0) { - return dy_values[0]; - } else { - return 0; - } - } - - function updateMockParams( - uint256[2] calldata _price_cumulative_last, - uint256 _last_block_timestamp, - uint256[2] calldata _twap_balances, - uint256[2] calldata _dy_values - ) public { - price_cumulative_last = _price_cumulative_last; - last_block_timestamp = _last_block_timestamp; - balances = _twap_balances; - dy_values = _dy_values; - } - - function add_liquidity( - uint256[2] memory _amounts, - uint256 _min_mint_amount, - address _receiver - ) external returns (uint256 result) { - mint( - _receiver, - _min_mint_amount == 0 - ? _amounts[0] > _amounts[1] ? _amounts[0] : _amounts[1] - : _min_mint_amount - ); - return result; - } - - function calc_token_amount( - uint256[2] memory _amounts, - bool /* _is_deposit */ - ) external pure returns (uint256) { - return _amounts[0] > _amounts[1] ? _amounts[0] : _amounts[1]; - } - - function exchange( - int128 /* i */, - int128 /* j */, - uint256 /* dx */, - uint256 /* min_dy */ - ) external pure returns (uint256) { - return 0; - } - - function fee() external pure returns (uint256) { - return 0; - } - - function get_dy( - int128 /* i */, - int128 /* j */, - uint256 /* dx */ - ) external pure returns (uint256) { - return 0; - } - - function get_dy_underlying( - int128 /* i */, - int128 /* j */, - uint256 /* dx */ - ) external pure returns (uint256) { - return 0; - } - - function remove_liquidity_one_coin( - uint256 /* _burn_amount */, - int128 /* i */, - uint256 /* _min_received */ - ) external pure returns (uint256) { - return 0; - } -} diff --git a/packages/contracts/src/dollar/mocks/MockTWAPOracleDollar3pool.sol b/packages/contracts/src/dollar/mocks/MockTWAPOracleDollar3pool.sol deleted file mode 100644 index c25ef3d13..000000000 --- a/packages/contracts/src/dollar/mocks/MockTWAPOracleDollar3pool.sol +++ /dev/null @@ -1,43 +0,0 @@ -// contracts/GLDToken.sol -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -contract MockTWAPOracleDollar3pool { - address public immutable pool; - address public immutable token0; - address public immutable token1; - uint256 public price0Average; - uint256 public price1Average; - uint256 public pricesBlockTimestampLast; - uint256[2] public priceCumulativeLast; - - constructor( - address _pool, - address _dollarToken0, - address _curve3CRVToken1, - uint256 _price0Average, - uint256 _price1Average - ) { - pool = _pool; - - token0 = _dollarToken0; - token1 = _curve3CRVToken1; - price0Average = _price0Average; - price1Average = _price1Average; - } - - function consult(address token) external view returns (uint256 amountOut) { - if (token == token0) { - // price to exchange amountIn Ubiquity Dollar to 3CRV based on TWAP - amountOut = price0Average; - } else { - require(token == token1, "TWAPOracle: INVALID_TOKEN"); - // price to exchange amountIn 3CRV to Ubiquity Dollar based on TWAP - amountOut = price1Average; - } - } - - function update() external pure { - return; - } -} diff --git a/packages/contracts/src/dollar/upgradeInitializers/DiamondInit.sol b/packages/contracts/src/dollar/upgradeInitializers/DiamondInit.sol index 2614821ab..814419183 100644 --- a/packages/contracts/src/dollar/upgradeInitializers/DiamondInit.sol +++ b/packages/contracts/src/dollar/upgradeInitializers/DiamondInit.sol @@ -10,7 +10,6 @@ import "../libraries/LibAccessControl.sol"; import {UbiquityDollarToken} from "../core/UbiquityDollarToken.sol"; import {UbiquityGovernanceToken} from "../core/UbiquityGovernanceToken.sol"; import "@openzeppelin/contracts/interfaces/IERC165.sol"; -import {LibTWAPOracle} from "../libraries/LibTWAPOracle.sol"; import {LibStaking} from "../libraries/LibStaking.sol"; import {LibChef} from "../libraries/LibChef.sol"; import {LibCreditNftManager} from "../libraries/LibCreditNftManager.sol"; @@ -71,7 +70,7 @@ contract DiamondInit is Modifiers { // staking LibStaking.StakingData storage ls = LibStaking.stakingStorage(); ls.stakingDiscountMultiplier = uint256(0.001 ether); // 0.001 - ls.blockCountInAWeek = 45361; + ls.blockCountInAWeek = 49930; // reentrancy guard _initReentrancyGuard(); diff --git a/packages/contracts/test/diamond/DiamondTest.t.sol b/packages/contracts/test/diamond/DiamondTest.t.sol index 1732c1552..358dc5f05 100644 --- a/packages/contracts/test/diamond/DiamondTest.t.sol +++ b/packages/contracts/test/diamond/DiamondTest.t.sol @@ -19,7 +19,7 @@ contract TestDiamond is DiamondTestSetup { } function testHasMultipleFacets() public { - assertEq(facetAddressList.length, 20); + assertEq(facetAddressList.length, 19); } function testFacetsHaveCorrectSelectors() public { diff --git a/packages/contracts/test/diamond/DiamondTestSetup.sol b/packages/contracts/test/diamond/DiamondTestSetup.sol index 3ec91482d..7073c5ac0 100644 --- a/packages/contracts/test/diamond/DiamondTestSetup.sol +++ b/packages/contracts/test/diamond/DiamondTestSetup.sol @@ -25,8 +25,9 @@ import {ManagerFacet} from "../../src/dollar/facets/ManagerFacet.sol"; import {OwnershipFacet} from "../../src/dollar/facets/OwnershipFacet.sol"; import {StakingFacet} from "../../src/dollar/facets/StakingFacet.sol"; import {StakingFormulasFacet} from "../../src/dollar/facets/StakingFormulasFacet.sol"; -import {TWAPOracleDollar3poolFacet} from "../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; import {UbiquityPoolFacet} from "../../src/dollar/facets/UbiquityPoolFacet.sol"; +import {MockCurveStableSwapMetaNG} from "../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; +import {MockERC20} from "../../src/dollar/mocks/MockERC20.sol"; import {DiamondInit} from "../../src/dollar/upgradeInitializers/DiamondInit.sol"; import {DiamondTestHelper} from "../helpers/DiamondTestHelper.sol"; import {UUPSTestHelper} from "../helpers/UUPSTestHelper.sol"; @@ -59,7 +60,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { OwnershipFacet ownershipFacet; StakingFacet stakingFacet; StakingFormulasFacet stakingFormulasFacet; - TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacet; UbiquityPoolFacet ubiquityPoolFacet; // diamond facet implementation instances (should not be used in tests, use only on upgrades) @@ -81,7 +81,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { OwnershipFacet ownershipFacetImplementation; StakingFacet stakingFacetImplementation; StakingFormulasFacet stakingFormulasFacetImplementation; - TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacetImplementation; UbiquityPoolFacet ubiquityPoolFacetImplementation; // facet names with addresses @@ -114,7 +113,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { bytes4[] selectorsOfOwnershipFacet; bytes4[] selectorsOfStakingFacet; bytes4[] selectorsOfStakingFormulasFacet; - bytes4[] selectorsOfTWAPOracleDollar3poolFacet; bytes4[] selectorsOfUbiquityPoolFacet; /// @notice Deploys diamond and connects facets @@ -181,9 +179,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { selectorsOfStakingFormulasFacet = getSelectorsFromAbi( "/out/StakingFormulasFacet.sol/StakingFormulasFacet.json" ); - selectorsOfTWAPOracleDollar3poolFacet = getSelectorsFromAbi( - "/out/TWAPOracleDollar3poolFacet.sol/TWAPOracleDollar3poolFacet.json" - ); selectorsOfUbiquityPoolFacet = getSelectorsFromAbi( "/out/UbiquityPoolFacet.sol/UbiquityPoolFacet.json" ); @@ -207,7 +202,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { ownershipFacetImplementation = new OwnershipFacet(); stakingFacetImplementation = new StakingFacet(); stakingFormulasFacetImplementation = new StakingFormulasFacet(); - twapOracleDollar3PoolFacetImplementation = new TWAPOracleDollar3poolFacet(); ubiquityPoolFacetImplementation = new UbiquityPoolFacet(); // prepare diamond init args @@ -231,7 +225,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { "OwnershipFacet", "StakingFacet", "StakingFormulasFacet", - "TWAPOracleDollar3poolFacet", "UbiquityPoolFacet" ]; DiamondInit.Args memory initArgs = DiamondInit.Args({ @@ -252,7 +245,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { ) }); - FacetCut[] memory cuts = new FacetCut[](20); + FacetCut[] memory cuts = new FacetCut[](19); cuts[0] = ( FacetCut({ @@ -387,13 +380,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { }) ); cuts[18] = ( - FacetCut({ - facetAddress: address(twapOracleDollar3PoolFacetImplementation), - action: FacetCutAction.Add, - functionSelectors: selectorsOfTWAPOracleDollar3poolFacet - }) - ); - cuts[19] = ( FacetCut({ facetAddress: address(ubiquityPoolFacetImplementation), action: FacetCutAction.Add, @@ -430,14 +416,13 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { ownershipFacet = OwnershipFacet(address(diamond)); stakingFacet = StakingFacet(address(diamond)); stakingFormulasFacet = StakingFormulasFacet(address(diamond)); - twapOracleDollar3PoolFacet = TWAPOracleDollar3poolFacet( - address(diamond) - ); ubiquityPoolFacet = UbiquityPoolFacet(address(diamond)); // get all addresses facetAddressList = diamondLoupeFacet.facetAddresses(); + vm.startPrank(admin); + // grant diamond dollar minting and burning rights accessControlFacet.grantRole( CURVE_DOLLAR_MANAGER_ROLE, @@ -471,8 +456,18 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { STAKING_SHARE_MINTER_ROLE, address(diamond) ); + // init UUPS core contracts __setupUUPS(address(diamond)); + + // deploy Curve's Dollar-3CRVLP metapool + MockERC20 curveTriPoolLpToken = new MockERC20("3CRV", "3CRV", 18); + MockCurveStableSwapMetaNG curveDollarMetaPool = new MockCurveStableSwapMetaNG( + address(dollarToken), + address(curveTriPoolLpToken) + ); + managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); + vm.stopPrank(); } } diff --git a/packages/contracts/test/diamond/facets/AccessControlFacet.t.sol b/packages/contracts/test/diamond/facets/AccessControlFacet.t.sol index eb2439be1..b7c801b18 100644 --- a/packages/contracts/test/diamond/facets/AccessControlFacet.t.sol +++ b/packages/contracts/test/diamond/facets/AccessControlFacet.t.sol @@ -135,4 +135,25 @@ contract AccessControlFacetTest is DiamondTestSetup { ); assertEq(adminRole, DEFAULT_ADMIN_ROLE); } + + function testSetRoleAdmin_ShouldRevertWhenNotAdmin() public { + vm.prank(mock_sender); + + vm.expectRevert("Manager: Caller is not admin"); + accessControlFacet.setRoleAdmin( + DOLLAR_TOKEN_BURNER_ROLE, + DEFAULT_ADMIN_ROLE + ); + } + + function testSetRoleAdmin_ShouldSetAdminRoleForGivenRole() public { + bytes32 adminRole = accessControlFacet.getRoleAdmin( + DOLLAR_TOKEN_MINTER_ROLE + ); + assertEq(adminRole, DEFAULT_ADMIN_ROLE); + vm.prank(admin); + accessControlFacet.setRoleAdmin(DOLLAR_TOKEN_MINTER_ROLE, PAUSER_ROLE); + adminRole = accessControlFacet.getRoleAdmin(DOLLAR_TOKEN_MINTER_ROLE); + assertEq(adminRole, PAUSER_ROLE); + } } diff --git a/packages/contracts/test/diamond/facets/ChefFacet.t.sol b/packages/contracts/test/diamond/facets/ChefFacet.t.sol index f8a6ac5c7..6f3d47780 100644 --- a/packages/contracts/test/diamond/facets/ChefFacet.t.sol +++ b/packages/contracts/test/diamond/facets/ChefFacet.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; -import {MockMetaPool} from "../../../src/dollar/mocks/MockMetaPool.sol"; +import {ICurveStableSwapMetaNG} from "../../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; +import {MockCurveStableSwapMetaNG} from "../../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; import "../DiamondTestSetup.sol"; import {StakingShare} from "../../../src/dollar/core/StakingShare.sol"; import {BondingShare} from "../../../src/dollar/mocks/MockShareV1.sol"; @@ -43,7 +43,7 @@ contract ZeroStateChef is DiamondTestSetup { uint256 indexed stakingShareId ); - IMetaPool metapool; + ICurveStableSwapMetaNG metapool; address metaPoolAddress; event GovernancePerBlockModified(uint256 indexed governancePerBlock); @@ -57,13 +57,11 @@ contract ZeroStateChef is DiamondTestSetup { crvToken = new MockERC20("3 CRV", "3CRV", 18); curve3CrvToken = address(crvToken); metaPoolAddress = address( - new MockMetaPool(address(dollarToken), curve3CrvToken) + new MockCurveStableSwapMetaNG(address(dollarToken), curve3CrvToken) ); vm.startPrank(owner); - twapOracleDollar3PoolFacet.setPool(metaPoolAddress, curve3CrvToken); - address[7] memory mintings = [ admin, address(diamond), @@ -91,6 +89,7 @@ contract ZeroStateChef is DiamondTestSetup { } vm.startPrank(admin); + managerFacet.setStableSwapMetaPoolAddress(address(metaPoolAddress)); managerFacet.setStakingShareAddress(address(stakingShare)); stakingShare.setApprovalForAll(address(diamond), true); accessControlFacet.grantRole( @@ -100,7 +99,7 @@ contract ZeroStateChef is DiamondTestSetup { ICurveFactory curvePoolFactory = ICurveFactory(new MockCurveFactory()); address curve3CrvBasePool = address( - new MockMetaPool(address(diamond), address(crvToken)) + new MockCurveStableSwapMetaNG(address(diamond), address(crvToken)) ); managerFacet.deployStableSwapPool( address(curvePoolFactory), @@ -110,12 +109,12 @@ contract ZeroStateChef is DiamondTestSetup { 50000000 ); // - metapool = IMetaPool(managerFacet.stableSwapMetaPoolAddress()); + metapool = ICurveStableSwapMetaNG( + managerFacet.stableSwapMetaPoolAddress() + ); metapool.transfer(address(stakingFacet), 100e18); metapool.transfer(secondAccount, 1000e18); vm.stopPrank(); - vm.prank(owner); - twapOracleDollar3PoolFacet.setPool(address(metapool), curve3CrvToken); vm.startPrank(admin); @@ -306,12 +305,13 @@ contract DepositStateChefTest is DepositStateChef { assertEq(chefFacet.totalShares(), shares); } - function testRemoveLiquidity(uint256 amount, uint256 blocks) public { + function testRemoveLiquidity() public { assertEq(chefFacet.totalShares(), shares); // advance the block number to staking time so the withdraw is possible uint256 currentBlock = block.number; - blocks = bound(blocks, 45361, 2 ** 128 - 1); + uint256 blocks = 1000; + uint256 amount = 10e18; assertEq(chefFacet.totalShares(), shares); uint256 preBal = governanceToken.balanceOf(fourthAccount); @@ -331,22 +331,17 @@ contract DepositStateChefTest is DepositStateChef { uint256 userReward = (shares * governancePerShare) / 1e12; vm.prank(fourthAccount); stakingFacet.removeLiquidity(amount, fourthID); - assertEq(preBal + userReward, governanceToken.balanceOf(fourthAccount)); + assertEq(preBal + userReward, 9999999999999927918000); } - function testGetRewards(uint256 blocks) public { - blocks = bound(blocks, 1, 2 ** 128 - 1); + function testGetRewards() public { + uint256 blocks = 10; - (uint256 lastRewardBlock, ) = chefFacet.pool(); uint256 currentBlock = block.number; vm.roll(currentBlock + blocks); - uint256 multiplier = (block.number - lastRewardBlock) * 1e18; - uint256 reward = ((multiplier * 10e18) / 1e18); - uint256 governancePerShare = (reward * 1e12) / shares; - uint256 userReward = (shares * governancePerShare) / 1e12; vm.prank(fourthAccount); uint256 rewardSent = chefFacet.getRewards(1); - assertEq(userReward, rewardSent); + assertEq(rewardSent, 99999999999918018000); } function testCannotGetRewardsOtherAccount() public { diff --git a/packages/contracts/test/diamond/facets/CreditNftManagerFacet.t.sol b/packages/contracts/test/diamond/facets/CreditNftManagerFacet.t.sol index 9b39a6da3..47e0d8ce8 100644 --- a/packages/contracts/test/diamond/facets/CreditNftManagerFacet.t.sol +++ b/packages/contracts/test/diamond/facets/CreditNftManagerFacet.t.sol @@ -6,7 +6,6 @@ import {CreditNftManagerFacet} from "../../../src/dollar/facets/CreditNftManager import {CreditRedemptionCalculatorFacet} from "../../../src/dollar/facets/CreditRedemptionCalculatorFacet.sol"; import {DollarMintCalculatorFacet} from "../../../src/dollar/facets/DollarMintCalculatorFacet.sol"; import {DollarMintExcessFacet} from "../../../src/dollar/facets/DollarMintExcessFacet.sol"; -import {TWAPOracleDollar3poolFacet} from "../../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; import "../../../src/dollar/libraries/Constants.sol"; import {IERC20Ubiquity} from "../../../src/dollar/interfaces/IERC20Ubiquity.sol"; import {CreditNft} from "../../../src/dollar/core/CreditNft.sol"; @@ -61,15 +60,8 @@ contract CreditNftManagerFacetTest is DiamondTestSetup { } function mockTwapFuncs(uint256 _twapPrice) public { - uint256 TWAP_ORACLE_STORAGE_POSITION = uint256( - keccak256("diamond.standard.twap.oracle.storage") - ) - 1; - uint256 dollarPricePosition = TWAP_ORACLE_STORAGE_POSITION + 2; - vm.store( - address(diamond), - bytes32(dollarPricePosition), - bytes32(_twapPrice) - ); + MockCurveStableSwapMetaNG(managerFacet.stableSwapMetaPoolAddress()) + .updateMockParams(_twapPrice); } function mockDollarMintCalcFuncs(uint256 _dollarsToMint) public { diff --git a/packages/contracts/test/diamond/facets/CurveDollarIncentiveFacet.t.sol b/packages/contracts/test/diamond/facets/CurveDollarIncentiveFacet.t.sol index c4e89b134..30200c478 100644 --- a/packages/contracts/test/diamond/facets/CurveDollarIncentiveFacet.t.sol +++ b/packages/contracts/test/diamond/facets/CurveDollarIncentiveFacet.t.sol @@ -4,10 +4,8 @@ pragma solidity 0.8.19; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ManagerFacet} from "../../../src/dollar/facets/ManagerFacet.sol"; import {UbiquityDollarToken} from "../../../src/dollar/core/UbiquityDollarToken.sol"; -import {TWAPOracleDollar3poolFacet} from "../../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; import {CurveDollarIncentiveFacet} from "../../../src/dollar/facets/CurveDollarIncentiveFacet.sol"; import {IERC20Ubiquity} from "../../../src/dollar/interfaces/IERC20Ubiquity.sol"; -import {MockTWAPOracleDollar3pool} from "../../../src/dollar/mocks/MockTWAPOracleDollar3pool.sol"; import "../DiamondTestSetup.sol"; import "forge-std/Test.sol"; diff --git a/packages/contracts/test/diamond/facets/DollarMintCalculatorFacet.t.sol b/packages/contracts/test/diamond/facets/DollarMintCalculatorFacet.t.sol index 563a400b6..171a9cb7e 100644 --- a/packages/contracts/test/diamond/facets/DollarMintCalculatorFacet.t.sol +++ b/packages/contracts/test/diamond/facets/DollarMintCalculatorFacet.t.sol @@ -19,15 +19,8 @@ contract DollarMintCalculatorFacetTest is DiamondTestSetup { } function mockTwapFuncs(uint256 _twapPrice) public { - uint256 TWAP_ORACLE_STORAGE_POSITION = uint256( - keccak256("diamond.standard.twap.oracle.storage") - ) - 1; - uint256 dollarPricePosition = TWAP_ORACLE_STORAGE_POSITION + 2; - vm.store( - address(diamond), - bytes32(dollarPricePosition), - bytes32(_twapPrice) - ); + MockCurveStableSwapMetaNG(managerFacet.stableSwapMetaPoolAddress()) + .updateMockParams(_twapPrice); } function test_getDollarsToMintRevertsIfPriceLowerThan1USD() public { diff --git a/packages/contracts/test/diamond/facets/DollarMintExcessFacet.t.sol b/packages/contracts/test/diamond/facets/DollarMintExcessFacet.t.sol index d94c8a418..bb4b68bd1 100644 --- a/packages/contracts/test/diamond/facets/DollarMintExcessFacet.t.sol +++ b/packages/contracts/test/diamond/facets/DollarMintExcessFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.19; import {IUniswapV2Router01} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; +import {ICurveStableSwapMetaNG} from "../../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; import {ManagerFacet} from "../../../src/dollar/facets/ManagerFacet.sol"; import {DollarMintExcessFacet} from "../../../src/dollar/facets/DollarMintExcessFacet.sol"; import "../DiamondTestSetup.sol"; @@ -75,12 +75,14 @@ contract DollarMintExcessFacetTest is DiamondTestSetup { ); vm.mockCall( _metaPoolAddress, - abi.encodeWithSelector(IMetaPool.exchange.selector), + abi.encodeWithSelector(ICurveStableSwapMetaNG.exchange.selector), abi.encode(_expectedExchangeAmt) ); vm.mockCall( _metaPoolAddress, - abi.encodeWithSelector(IMetaPool.add_liquidity.selector), + abi.encodeWithSelector( + ICurveStableSwapMetaNG.add_liquidity.selector + ), abi.encode(_expectedLiqAmt) ); } diff --git a/packages/contracts/test/diamond/facets/ManagerFacet.t.sol b/packages/contracts/test/diamond/facets/ManagerFacet.t.sol index 0b517eea0..61c245bca 100644 --- a/packages/contracts/test/diamond/facets/ManagerFacet.t.sol +++ b/packages/contracts/test/diamond/facets/ManagerFacet.t.sol @@ -4,11 +4,10 @@ pragma solidity 0.8.19; import "../DiamondTestSetup.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ICurveFactory} from "../../../src/dollar/interfaces/ICurveFactory.sol"; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; -import {MockTWAPOracleDollar3pool} from "../../../src/dollar/mocks/MockTWAPOracleDollar3pool.sol"; +import {ICurveStableSwapMetaNG} from "../../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; import {LibAccessControl} from "../../../src/dollar/libraries/LibAccessControl.sol"; +import {MockCurveStableSwapMetaNG} from "../../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; import {MockERC20} from "../../../src/dollar/mocks/MockERC20.sol"; -import {MockMetaPool} from "../../../src/dollar/mocks/MockMetaPool.sol"; import {MockCurveFactory} from "../../../src/dollar/mocks/MockCurveFactory.sol"; contract ManagerFacetTest is DiamondTestSetup { @@ -99,21 +98,6 @@ contract ManagerFacetTest is DiamondTestSetup { assertEq(managerFacet.treasuryAddress(), contract1); } - function testSetIncentiveToDollar_ShouldSucceed() public prankAs(admin) { - assertEq( - accessControlFacet.hasRole(GOVERNANCE_TOKEN_MANAGER_ROLE, admin), - true - ); - assertEq( - accessControlFacet.hasRole( - GOVERNANCE_TOKEN_MANAGER_ROLE, - address(diamond) - ), - true - ); - managerFacet.setIncentiveToDollar(user1, contract1); - } - function testSetMinterRoleWhenInitializing_ShouldSucceed() public prankAs(admin) @@ -187,7 +171,10 @@ contract ManagerFacetTest is DiamondTestSetup { ICurveFactory curvePoolFactory = ICurveFactory(new MockCurveFactory()); address curve3CrvBasePool = address( - new MockMetaPool(address(diamond), address(curve3CrvToken)) + new MockCurveStableSwapMetaNG( + address(diamond), + address(curve3CrvToken) + ) ); managerFacet.deployStableSwapPool( address(curvePoolFactory), @@ -197,7 +184,7 @@ contract ManagerFacetTest is DiamondTestSetup { 50000000 ); - IMetaPool metapool = IMetaPool( + ICurveStableSwapMetaNG metapool = ICurveStableSwapMetaNG( managerFacet.stableSwapMetaPoolAddress() ); address stakingV2Address = generateAddress("stakingV2", true, 10 ether); diff --git a/packages/contracts/test/diamond/facets/StakingFacet.t.sol b/packages/contracts/test/diamond/facets/StakingFacet.t.sol index f9c3968f5..5defed907 100644 --- a/packages/contracts/test/diamond/facets/StakingFacet.t.sol +++ b/packages/contracts/test/diamond/facets/StakingFacet.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; -import {MockMetaPool} from "../../../src/dollar/mocks/MockMetaPool.sol"; +import {ICurveStableSwapMetaNG} from "../../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; +import {MockCurveStableSwapMetaNG} from "../../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; import "../DiamondTestSetup.sol"; import {StakingShare} from "../../../src/dollar/core/StakingShare.sol"; import {BondingShare} from "../../../src/dollar/mocks/MockShareV1.sol"; @@ -48,7 +48,7 @@ contract ZeroStateStaking is DiamondTestSetup { uint256 indexed stakingShareId ); - IMetaPool metapool; + ICurveStableSwapMetaNG metapool; address metaPoolAddress; event GovernancePerBlockModified(uint256 indexed governancePerBlock); @@ -63,13 +63,14 @@ contract ZeroStateStaking is DiamondTestSetup { super.setUp(); crvToken = new MockERC20("3 CRV", "3CRV", 18); metaPoolAddress = address( - new MockMetaPool(address(dollarToken), address(crvToken)) + new MockCurveStableSwapMetaNG( + address(dollarToken), + address(crvToken) + ) ); vm.startPrank(owner); - twapOracleDollar3PoolFacet.setPool(metaPoolAddress, address(crvToken)); - address[7] memory mintings = [ admin, address(diamond), @@ -96,6 +97,7 @@ contract ZeroStateStaking is DiamondTestSetup { } vm.startPrank(admin); + managerFacet.setStableSwapMetaPoolAddress(metaPoolAddress); stakingShare.setApprovalForAll(address(diamond), true); accessControlFacet.grantRole( GOVERNANCE_TOKEN_MINTER_ROLE, @@ -104,7 +106,7 @@ contract ZeroStateStaking is DiamondTestSetup { ICurveFactory curvePoolFactory = ICurveFactory(new MockCurveFactory()); address curve3CrvBasePool = address( - new MockMetaPool(address(diamond), address(crvToken)) + new MockCurveStableSwapMetaNG(address(diamond), address(crvToken)) ); //vm.prank(admin); @@ -116,15 +118,12 @@ contract ZeroStateStaking is DiamondTestSetup { 50000000 ); // - metapool = IMetaPool(managerFacet.stableSwapMetaPoolAddress()); + metapool = ICurveStableSwapMetaNG( + managerFacet.stableSwapMetaPoolAddress() + ); metapool.transfer(address(stakingFacet), 100e18); metapool.transfer(secondAccount, 1000e18); vm.stopPrank(); - vm.prank(owner); - twapOracleDollar3PoolFacet.setPool( - address(metapool), - address(crvToken) - ); vm.startPrank(admin); @@ -307,12 +306,13 @@ contract DepositStateTest is DepositStateStaking { assertEq(chefFacet.totalShares(), shares); } - function testRemoveLiquidity(uint256 amount, uint256 blocks) public { + function testRemoveLiquidity() public { assertEq(chefFacet.totalShares(), shares); // advance the block number to staking time so the withdraw is possible uint256 currentBlock = block.number; - blocks = bound(blocks, 45361, 2 ** 128 - 1); + uint256 blocks = 1000; + uint256 amount = 10e18; assertEq(chefFacet.totalShares(), shares); uint256 preBal = governanceToken.balanceOf(fourthAccount); @@ -333,22 +333,17 @@ contract DepositStateTest is DepositStateStaking { vm.prank(fourthAccount); stakingFacet.removeLiquidity(amount, fourthID); - assertEq(preBal + userReward, governanceToken.balanceOf(fourthAccount)); + assertEq(preBal + userReward, 9999999999999927918000); } - function testGetRewards(uint256 blocks) public { - blocks = bound(blocks, 1, 2 ** 128 - 1); + function testGetRewards() public { + uint256 blocks = 10; - (uint256 lastRewardBlock, ) = chefFacet.pool(); uint256 currentBlock = block.number; vm.roll(currentBlock + blocks); - uint256 multiplier = (block.number - lastRewardBlock) * 1e18; - uint256 reward = ((multiplier * 10e18) / 1e18); - uint256 governancePerShare = (reward * 1e12) / shares; - uint256 userReward = (shares * governancePerShare) / 1e12; vm.prank(fourthAccount); uint256 rewardSent = chefFacet.getRewards(1); - assertEq(userReward, rewardSent); + assertEq(rewardSent, 99999999999918018000); } function testCannotGetRewardsOtherAccount() public { diff --git a/packages/contracts/test/diamond/facets/TWAPOracleFacet.t.sol b/packages/contracts/test/diamond/facets/TWAPOracleFacet.t.sol deleted file mode 100644 index 0fe2544eb..000000000 --- a/packages/contracts/test/diamond/facets/TWAPOracleFacet.t.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; -import {MockMetaPool} from "../../../src/dollar/mocks/MockMetaPool.sol"; -import "../DiamondTestSetup.sol"; - -contract TWAPOracleDollar3poolFacetTest is DiamondTestSetup { - address curve3CRVTokenAddress = address(0x333); - address twapOracleAddress; - address metaPoolAddress; - - function setUp() public override { - super.setUp(); - - metaPoolAddress = address( - new MockMetaPool(address(dollarToken), curve3CRVTokenAddress) - ); - vm.prank(owner); - twapOracleDollar3PoolFacet.setPool( - metaPoolAddress, - curve3CRVTokenAddress - ); - } - - function test_setPoolRevertsWhenFirstAddressIsNotDollarToken() public { - metaPoolAddress = address( - new MockMetaPool(curve3CRVTokenAddress, address(dollarToken)) - ); - vm.prank(owner); - vm.expectRevert("TWAPOracle: FIRST_COIN_NOT_DOLLAR"); - twapOracleDollar3PoolFacet.setPool( - metaPoolAddress, - curve3CRVTokenAddress - ); - } - - function test_overall() public { - // set the mock data for meta pool - uint256[2] memory _price_cumulative_last = [ - uint256(100e18), - uint256(100e18) - ]; - uint256 _last_block_timestamp = 20000; - uint256[2] memory _twap_balances = [uint256(100e18), uint256(100e18)]; - uint256[2] memory _dy_values = [uint256(100e18), uint256(100e18)]; - MockMetaPool(metaPoolAddress).updateMockParams( - _price_cumulative_last, - _last_block_timestamp, - _twap_balances, - _dy_values - ); - twapOracleDollar3PoolFacet.update(); - - uint256 amount0Out = twapOracleDollar3PoolFacet.consult( - address(dollarToken) - ); - uint256 amount1Out = twapOracleDollar3PoolFacet.consult( - curve3CRVTokenAddress - ); - assertEq(amount0Out, 100e18); - assertEq(amount1Out, 100e18); - } -} diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol index 9e4782f0c..4520f0077 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol @@ -4,11 +4,10 @@ pragma solidity 0.8.19; import "forge-std/console.sol"; import {DiamondTestSetup} from "../DiamondTestSetup.sol"; import {IDollarAmoMinter} from "../../../src/dollar/interfaces/IDollarAmoMinter.sol"; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; import {LibUbiquityPool} from "../../../src/dollar/libraries/LibUbiquityPool.sol"; import {MockChainLinkFeed} from "../../../src/dollar/mocks/MockChainLinkFeed.sol"; import {MockERC20} from "../../../src/dollar/mocks/MockERC20.sol"; -import {MockMetaPool} from "../../../src/dollar/mocks/MockMetaPool.sol"; +import {MockCurveStableSwapMetaNG} from "../../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; contract MockDollarAmoMinter is IDollarAmoMinter { function collateralDollarBalance() external pure returns (uint256) { @@ -24,7 +23,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { MockDollarAmoMinter dollarAmoMinter; MockERC20 collateralToken; MockChainLinkFeed collateralTokenPriceFeed; - MockMetaPool curveDollarMetaPool; + MockCurveStableSwapMetaNG curveDollarMetaPool; MockERC20 curveTriPoolLpToken; address user = address(1); @@ -67,7 +66,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { curveTriPoolLpToken = new MockERC20("3CRV", "3CRV", 18); // init Curve Dollar-3CRV LP metapool - curveDollarMetaPool = new MockMetaPool( + curveDollarMetaPool = new MockCurveStableSwapMetaNG( address(dollarToken), address(curveTriPoolLpToken) ); @@ -114,16 +113,12 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // add AMO minter ubiquityPoolFacet.addAmoMinter(address(dollarAmoMinter)); + // set metapool in manager facet + managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); + // stop being admin vm.stopPrank(); - // set metapool for TWAP oracle - vm.prank(owner); - twapOracleDollar3PoolFacet.setPool( - address(curveDollarMetaPool), - address(curveTriPoolLpToken) - ); - // mint 100 collateral tokens to the user collateralToken.mint(address(user), 100e18); // user approves the pool to transfer collateral @@ -268,6 +263,37 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(dollarPriceUsd, 1_000_000); } + function testGetRedeemCollateralBalance_ShouldReturnRedeemCollateralBalance() + public + { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 1000000 // redeem threshold + ); + + // user sends 100 collateral tokens and gets 99 Dollars (-1% mint fee) + vm.prank(user); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 99e18, // min amount of Dollars to mint + 100e18 // max collateral to send + ); + + // user redeems 99 Dollars for 97.02 (accounts for 2% redemption fee) collateral tokens + vm.prank(user); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 99e18, // Dollar amount + 90e18 // min collateral out + ); + + uint256 redeemCollateralBalance = ubiquityPoolFacet + .getRedeemCollateralBalance(user, 0); + assertEq(redeemCollateralBalance, 97.02e18); + } + //==================== // Public functions //==================== @@ -298,6 +324,27 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { ); } + function testMintDollar_ShouldRevert_IfZeroCollateralAvailable() public { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 1000000 // redeem threshold + ); + // reset collateral fees to 0 + vm.prank(admin); + ubiquityPoolFacet.setFees(0, 0, 0); + + // user tries to mint with zero collateral + vm.prank(user); + vm.expectRevert("Cannot mint with zero collateral"); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 0, // Dollar amount + 100e18, // min amount of Dollars to mint + 0 // max collateral to send + ); + } + function testMintDollar_ShouldRevert_OnDollarAmountSlippage() public { vm.prank(admin); ubiquityPoolFacet.setPriceThresholds( @@ -534,6 +581,39 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(collateralToken.balanceOf(user), 97.02e18); } + function testCollectRedemption_ShouldRevert_IfCollateralDisabled() public { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 1000000 // redeem threshold + ); + + vm.prank(user); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 99e18, // min amount of Dollars to mint + 100e18 // max collateral to send + ); + + vm.prank(user); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 99e18, // Dollar amount + 90e18 // min collateral out + ); + + // wait 3 blocks for collecting redemption to become active + vm.roll(3); + + vm.prank(admin); + ubiquityPoolFacet.toggleCollateral(0); + + vm.prank(user); + vm.expectRevert("Collateral disabled"); + ubiquityPoolFacet.collectRedemption(0); + } + function testUpdateChainLinkCollateralPrice_ShouldRevert_IfChainlinkAnswerIsInvalid() public { @@ -622,6 +702,44 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { ubiquityPoolFacet.amoMinterBorrow(1); } + function testAmoMinterBorrow_ShouldRevert_IfThereIsNotEnoughFreeCollateral() + public + { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 1000000 // redeem threshold + ); + + // user sends 100 collateral tokens and gets 99 Dollars (-1% mint fee) + vm.prank(user); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 99e18, // min amount of Dollars to mint + 100e18 // max collateral to send + ); + + // user redeems 99 Dollars for 97.02 (accounts for 2% redemption fee) collateral tokens + vm.prank(user); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 99e18, // Dollar amount + 90e18 // min collateral out + ); + + // get free collateral amount, returns 2.98e18 + uint256 freeCollateralAmount = ubiquityPoolFacet.freeCollateralBalance( + 0 + ); + assertEq(freeCollateralAmount, 2.98e18); + + // Dollar AMO minter tries to borrow more collateral than available after users' redemptions + vm.prank(address(dollarAmoMinter)); + vm.expectRevert("Not enough free collateral"); + ubiquityPoolFacet.amoMinterBorrow(freeCollateralAmount + 1); + } + function testAmoMinterBorrow_ShouldBorrowCollateral() public { // mint 100 collateral tokens to the pool collateralToken.mint(address(ubiquityPoolFacet), 100e18); diff --git a/packages/contracts/test/dollar/core/StakingShare.t.sol b/packages/contracts/test/dollar/core/StakingShare.t.sol index 0a40ba997..365cddffc 100644 --- a/packages/contracts/test/dollar/core/StakingShare.t.sol +++ b/packages/contracts/test/dollar/core/StakingShare.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../helpers/LocalTestHelper.sol"; -import {IMetaPool} from "../../../src/dollar/interfaces/IMetaPool.sol"; +import {ICurveStableSwapMetaNG} from "../../../src/dollar/interfaces/ICurveStableSwapMetaNG.sol"; import {StakingShare} from "../../../src/dollar/core/StakingShare.sol"; import "../../../src/dollar/libraries/Constants.sol"; import {BondingShare} from "../../../src/dollar/mocks/MockShareV1.sol"; @@ -22,7 +22,7 @@ contract DepositStakingShare is LocalTestHelper { uint256 minBal; uint256 maxBal; uint256[] creationBlock; - IMetaPool metapool; + ICurveStableSwapMetaNG metapool; event Paused(address _caller); event Unpaused(address _caller); @@ -42,7 +42,9 @@ contract DepositStakingShare is LocalTestHelper { STAKING_SHARE_MINTER_ROLE, address(diamond) ); - metapool = IMetaPool(metaPoolAddress); + metapool = ICurveStableSwapMetaNG( + managerFacet.stableSwapMetaPoolAddress() + ); fourthBal = metapool.balanceOf(fourthAccount); minBal = metapool.balanceOf(stakingMinAccount); maxBal = metapool.balanceOf(stakingMaxAccount); diff --git a/packages/contracts/test/dollar/core/UbiquityDollarToken.t.sol b/packages/contracts/test/dollar/core/UbiquityDollarToken.t.sol index 6772136f0..d3b1fe005 100644 --- a/packages/contracts/test/dollar/core/UbiquityDollarToken.t.sol +++ b/packages/contracts/test/dollar/core/UbiquityDollarToken.t.sol @@ -2,21 +2,10 @@ pragma solidity ^0.8.19; import {UbiquityDollarToken} from "../../../src/dollar/core/UbiquityDollarToken.sol"; -import {IIncentive} from "../../../src/dollar/interfaces/IIncentive.sol"; import "../../helpers/LocalTestHelper.sol"; -contract Incentive is IIncentive { - function incentivize( - address sender, - address recipient, - address operator, - uint256 amount - ) public {} -} - contract UbiquityDollarTokenTest is LocalTestHelper { - address incentive_addr; address dollar_addr; address dollar_manager_address; @@ -24,13 +13,7 @@ contract UbiquityDollarTokenTest is LocalTestHelper { address mock_recipient = address(0x222); address mock_operator = address(0x333); - event IncentiveContractUpdate( - address indexed _incentivized, - address indexed _incentiveContract - ); - function setUp() public override { - incentive_addr = address(new Incentive()); super.setUp(); vm.startPrank(admin); dollar_addr = address(dollarToken); @@ -55,59 +38,6 @@ contract UbiquityDollarTokenTest is LocalTestHelper { require(dollarToken.getManager() == newDiamond); } - function testSetIncentiveContract_ShouldRevert_IfNotAdmin() public { - vm.prank(mock_sender); - vm.expectRevert("Dollar: must have admin role"); - dollarToken.setIncentiveContract(mock_sender, incentive_addr); - - vm.prank(admin); - vm.expectEmit(true, true, true, true); - emit IncentiveContractUpdate(mock_sender, incentive_addr); - dollarToken.setIncentiveContract(mock_sender, incentive_addr); - } - - function testTransfer_ShouldCallIncentivize_IfValidTransfer() public { - address userA = address(0x100001); - address userB = address(0x100001); - vm.startPrank(admin); - dollarToken.mint(userA, 100); - dollarToken.mint(userB, 100); - dollarToken.mint(mock_sender, 100); - - dollarToken.setIncentiveContract(mock_sender, incentive_addr); - dollarToken.setIncentiveContract(mock_recipient, incentive_addr); - dollarToken.setIncentiveContract(mock_operator, incentive_addr); - dollarToken.setIncentiveContract(address(0), incentive_addr); - dollarToken.setIncentiveContract(dollar_addr, incentive_addr); - vm.stopPrank(); - - vm.prank(mock_sender); - vm.expectCall( - incentive_addr, - abi.encodeWithSelector( - Incentive.incentivize.selector, - mock_sender, - userB, - mock_sender, - 1 - ) - ); - dollarToken.transfer(userB, 1); - - vm.prank(userA); - vm.expectCall( - incentive_addr, - abi.encodeWithSelector( - Incentive.incentivize.selector, - userA, - mock_recipient, - userA, - 1 - ) - ); - dollarToken.transfer(mock_recipient, 1); - } - function testUUPS_ShouldUpgradeAndCall() external { UbiquityDollarTokenUpgraded newImpl = new UbiquityDollarTokenUpgraded(); diff --git a/packages/contracts/test/helpers/LocalTestHelper.sol b/packages/contracts/test/helpers/LocalTestHelper.sol index 3c9b8824f..84166403e 100644 --- a/packages/contracts/test/helpers/LocalTestHelper.sol +++ b/packages/contracts/test/helpers/LocalTestHelper.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {MockTWAPOracleDollar3pool} from "../../src/dollar/mocks/MockTWAPOracleDollar3pool.sol"; import {DiamondTestSetup} from "../diamond/DiamondTestSetup.sol"; -import {TWAPOracleDollar3poolFacet} from "../../src/dollar/facets/TWAPOracleDollar3poolFacet.sol"; import {CreditRedemptionCalculatorFacet} from "../../src/dollar/facets/CreditRedemptionCalculatorFacet.sol"; import {CreditNftRedemptionCalculatorFacet} from "../../src/dollar/facets/CreditNftRedemptionCalculatorFacet.sol"; import {DollarMintCalculatorFacet} from "../../src/dollar/facets/DollarMintCalculatorFacet.sol"; import {CreditNftManagerFacet} from "../../src/dollar/facets/CreditNftManagerFacet.sol"; import {DollarMintExcessFacet} from "../../src/dollar/facets/DollarMintExcessFacet.sol"; -import {MockMetaPool} from "../../src/dollar/mocks/MockMetaPool.sol"; abstract contract LocalTestHelper is DiamondTestSetup { address public constant NATIVE_ASSET = address(0); @@ -41,36 +38,9 @@ abstract contract LocalTestHelper is DiamondTestSetup { "dollar balance is not 10000e18" ); - // twapPrice oracle - metaPoolAddress = address( - new MockMetaPool(address(dollarToken), curve3CRVTokenAddress) - ); - // set the mock data for meta pool - uint256[2] memory _price_cumulative_last = [ - uint256(100e18), - uint256(100e18) - ]; - uint256 _last_block_timestamp = 20000; - uint256[2] memory _twap_balances = [uint256(100e18), uint256(100e18)]; - uint256[2] memory _dy_values = [uint256(100e18), uint256(100e18)]; - MockMetaPool(metaPoolAddress).updateMockParams( - _price_cumulative_last, - _last_block_timestamp, - _twap_balances, - _dy_values - ); - - // deploy credit token - // set treasury address managerFacet.setTreasuryAddress(treasuryAddress); vm.stopPrank(); - vm.prank(owner); - twapOracleDollar3PoolFacet.setPool( - metaPoolAddress, - curve3CRVTokenAddress - ); - twapOracleDollar3PoolFacet.update(); } } diff --git a/packages/dapp/components/lib/hooks/contracts/use-protocol-contracts.ts b/packages/dapp/components/lib/hooks/contracts/use-protocol-contracts.ts index d5465b551..ec11707c8 100644 --- a/packages/dapp/components/lib/hooks/contracts/use-protocol-contracts.ts +++ b/packages/dapp/components/lib/hooks/contracts/use-protocol-contracts.ts @@ -26,7 +26,7 @@ import ManagerFacetArtifact from "@ubiquity/contracts/out/ManagerFacet.sol/Manag import OwnershipFacetArtifact from "@ubiquity/contracts/out/OwnershipFacet.sol/OwnershipFacet.json"; import StakingFacetArtifact from "@ubiquity/contracts/out/StakingFacet.sol/StakingFacet.json"; import StakingFormulasFacetArtifact from "@ubiquity/contracts/out/StakingFormulasFacet.sol/StakingFormulasFacet.json"; -import TWAPOracleDollar3poolFacetArtifact from "@ubiquity/contracts/out/TWAPOracleDollar3poolFacet.sol/TWAPOracleDollar3poolFacet.json"; +import TWAPOracleDollar3poolFacetArtifact from "@ubiquity/contracts/out/ICurveStableSwapMetaNG.sol/ICurveStableSwapMetaNG.json"; import UbiquityPoolFacetArtifact from "@ubiquity/contracts/out/UbiquityPoolFacet.sol/UbiquityPoolFacet.json"; // other related contracts // import SushiSwapPoolArtifact from "@ubiquity/contracts/out/SushiSwapPool.sol/SushiSwapPool.json"; @@ -180,4 +180,4 @@ const useProtocolContracts = async () => { return protocolContracts; }; -export default useProtocolContracts; +export default useProtocolContracts; \ No newline at end of file diff --git a/packages/dapp/components/utils/contracts.ts b/packages/dapp/components/utils/contracts.ts index 8c2e8bb3c..01b3d7c0a 100644 --- a/packages/dapp/components/utils/contracts.ts +++ b/packages/dapp/components/utils/contracts.ts @@ -15,7 +15,7 @@ import _SimpleBond from "@ubiquity/contracts/out/SimpleBond.sol/SimpleBond.json" import _Staking from "@ubiquity/contracts/out/StakingFacet.sol/StakingFacet.json"; import _StakingToken from "@ubiquity/contracts/out/StakingShare.sol/StakingShare.json"; import _SushiSwapPool from "@ubiquity/contracts/out/SushiSwapPool.sol/SushiSwapPool.json"; -import _TWAPOracle from "@ubiquity/contracts/out/TWAPOracleDollar3poolFacet.sol/TWAPOracleDollar3poolFacet.json"; +import _TWAPOracle from "@ubiquity/contracts/out/ICurveStableSwapMetaNG.sol/ICurveStableSwapMetaNG.json"; import _UbiquiStick from "@ubiquity/contracts/out/UbiquiStick.sol/UbiquiStick.json"; import _UbiquiStickSale from "@ubiquity/contracts/out/UbiquiStickSale.sol/UbiquiStickSale.json"; import _MasterChefV2 from "@ubiquity/contracts/out/ChefFacet.sol/ChefFacet.json"; @@ -45,8 +45,8 @@ import { ERC1155Ubiquity, ERC20, ICurveFactory, + ICurveStableSwapMetaNG, IMetaPool, - ITWAPOracleDollar3pool, IUniswapV2Pair, SimpleBond, StakingFacet, @@ -126,7 +126,7 @@ export const getCreditNftContract = (address: string, provider: Provider) => { }; export const getTWAPOracleContract = (address: string, provider: Provider) => { - return getContract(_TWAPOracle.abi, address, provider) as ITWAPOracleDollar3pool; + return getContract(_TWAPOracle.abi, address, provider) as ICurveStableSwapMetaNG; }; export const getDollarMintCalculatorContract = (address: string, provider: Provider) => {