diff --git a/.gitignore b/.gitignore index 13c0e37712..8751aabb75 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ scripts/playground.ts # tenderly deployment/verification artifacts deployments/ backtests/ + +# output files +output.log diff --git a/.openzeppelin/base_8453.json b/.openzeppelin/base_8453.json index 0d90ea97cb..41bd14fe5a 100644 --- a/.openzeppelin/base_8453.json +++ b/.openzeppelin/base_8453.json @@ -3328,6 +3328,98 @@ } } } + }, + "d1e021b854c3f6ab5e998969c8df7e648a147f5001337ec8f2bb065c4ea04d6f": { + "address": "0x87F0ec2f8C9C595612eC3534c7517B55277B811e", + "txHash": "0xdee9e0e2c378668088fa6d185e47ed0a013ee62e86f6da11f9909e427dd494b3", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index d0dae943ca..ed9459b7e3 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -3747,7 +3747,10 @@ }, "t_enum(TradeKind)25002": { "label": "enum TradeKind", - "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)": { @@ -4040,7 +4043,11 @@ }, "t_enum(CollateralStatus)24460": { "label": "enum CollateralStatus", - "members": ["SOUND", "IFFY", "DISABLED"], + "members": [ + "SOUND", + "IFFY", + "DISABLED" + ], "numberOfBytes": "1" }, "t_mapping(t_bytes32,t_bytes32)": { @@ -6333,7 +6340,10 @@ }, "t_enum(TradeKind)17751": { "label": "enum TradeKind", - "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)11113,t_contract(ITrade)19704)": { @@ -6826,6 +6836,98 @@ } } } + }, + "e6946280d3c82dd717cab5378fad9380289483dbb5e3bb62b934ad7569d33c94": { + "address": "0xf1B06c2305445E34CF0147466352249724c2EAC1", + "txHash": "0x9ed3ac012f65ff06d34129d211a4b455be0a1d60a2677c16f2a8a2f163772fcd", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 94eac583c2..0f1082a921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,59 @@ # Changelog +# 3.3.0 + +This release improves how collateral plugins price LP tokens and moves reward claiming out to the asset plugin level. + +## Upgrade Steps + +Swapout all collateral plugins with appreciation. + +All collateral plugins should be upgraded. The compound-v2 ERC20 wrapper will be traded out for the raw underlying CToken, as well as aave-v3 USDC/USDCbC for canonical wrappers. + +### Core Protocol Contracts + +- `BackingManager` + `RevenueTrader` + - Change `claimRewards()` to delegatecall to the list of registered plugins + +## Plugins + +### Assets + +- aave-v3 + - On mainnet: switch from one-off USDC wrapper to canonical USDC wrapper + - On base: switch from one-off USDbC wrapper to canonical USDC wrapper +- compound-v2 + - Remove `CTokenWrapper` + - Add reward claiming logic to `claimRewards()` + - Emit `RewardsClaimed` event during `claimRewards()` +- compound-v3 + - Emit `RewardsClaimed` event during `claimRewards()` +- curve + - Make `price()` more resistant to manipulation by MEV + - Emit `RewardsClaimed` event during `claimRewards()` +- convex + - Make `price()` more resistant to manipulation by MEV + - Emit `RewardsClaimed` event during `claimRewards()` + - Add new `crvUSD-USDC` plugin +- morpho-aave + - Emit `RewardsClaimed` event during `claimRewards()` +- stargate + - Emit `RewardsClaimed` event during `claimRewards()` +- yearn-v2 + - Make `price()` more resistant to manipulation by MEV + +### Trading + +- `GnosisTrade` + - Add `version()` getter +- `DutchTrade` + - Add `version()` getter + +### Facades + +- `FacadeMonitor.sol` + - Update compound-v2 implemention to deal with with-wrappper and without-wrapper cases + # 3.2.0 This release makes bidding in dutch auctions easier for MEV searchers and gives new RTokens being deployed the option to enable a variable target basket, or to be "reweightable". An RToken that is not reweightable cannot have its target basket changed in terms of quantities of target units. diff --git a/common/configuration.ts b/common/configuration.ts index c795fd40f5..d900dc9fb3 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -28,7 +28,9 @@ export interface ITokens { aWBTC?: string aCRV?: string aEthUSDC?: string - aBasUSDbC?: string + saEthUSDC?: string + aBasUSDC?: string + saBasUSDC?: string aWETHv3?: string acbETHv3?: string cDAI?: string @@ -74,6 +76,10 @@ export interface ITokens { yvCurveUSDPcrvUSD?: string yvCurveUSDCcrvUSD?: string + pyUSD?: string + aEthPyUSD?: string + saEthPyUSD?: string + // Morpho Aave maUSDC?: string maUSDT?: string @@ -93,7 +99,9 @@ export interface IFeeds { } export interface IPools { + cvxCrvUSDUSDC?: string cvx3Pool?: string + cvxPayPool?: string cvxeUSDFRAXBP?: string cvxTriCrypto?: string cvxMIM3Pool?: string @@ -135,117 +143,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { tokens: {}, chainlinkFeeds: {}, }, - // Config used for Mainnet forking -- Mirrors mainnet - '31337': { - name: 'localhost', - tokens: { - DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F', - USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - BUSD: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', - USDP: '0x8E870D67F660D95d5be530380D0eC0bd388289E1', - TUSD: '0x0000000000085d4780B73119b644AE5ecd22b376', - sUSD: '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', - FRAX: '0x853d955aCEf822Db058eb8505911ED77F175b99e', - MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3', - crvUSD: '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E', - eUSD: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', - aDAI: '0x028171bCA77440897B824Ca71D1c56caC55b68A3', - aUSDC: '0xBcca60bB61934080951369a648Fb03DF4F96263C', - aUSDT: '0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811', - aBUSD: '0xA361718326c15715591c299427c62086F69923D9', - aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', - aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', - aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', - aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', - aEthUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', - cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', - cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', - cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', - cUSDP: '0x041171993284df560249B57358F931D9eB7b925D', - cETH: '0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5', - cWBTC: '0xccF4429DB6322D5C611ee964527D42E5d685DD6a', - fUSDC: '0x465a5a630482f3abD6d3b84B39B29b07214d19e5', - fUSDT: '0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7', - fFRAX: '0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B', - fDAI: '0xe2bA8693cE7474900A045757fe0efCa900F6530b', - AAVE: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', - stkAAVE: '0x4da27a545c0c5B758a6BA100e3a049001de870f5', - COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', - WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', - EURT: '0xC581b735A1688071A1746c968e0798D642EDE491', - RSR: '0x320623b8E4fF03373931769A31Fc52A4E78B5d70', - CRV: '0xD533a949740bb3306d119CC777fa900bA034cd52', - CVX: '0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B', - ankrETH: '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb', - frxETH: '0x5E8422345238F34275888049021821E8E08CAa1f', - sfrxETH: '0xac3E018457B222d93114458476f3E3416Abbe38F', - stETH: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', - wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', - rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', - cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', - ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', - sFRAX: '0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32', - sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', - cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', - STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', - sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', - sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', - sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', - MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', - astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', - yvCurveUSDPcrvUSD: '0xF56fB6cc29F0666BDD1662FEaAE2A3C935ee3469', - yvCurveUSDCcrvUSD: '0x7cA00559B978CFde81297849be6151d3ccB408A9', - }, - chainlinkFeeds: { - RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', - AAVE: '0x547a514d5e3769680Ce22B2361c10Ea13619e8a9', - COMP: '0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5', - DAI: '0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9', - USDC: '0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6', - USDT: '0x3E7d1eAB13ad0104d2750B8863b489D65364e32D', - BUSD: '0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A', - USDP: '0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3', - TUSD: '0xec746eCF986E2927Abd291a2A1716c940100f8Ba', - sUSD: '0xad35Bd71b9aFE6e4bDc266B345c198eaDEf9Ad94', - FRAX: '0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD', - MIM: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', - crvUSD: '0xEEf0C605546958c1f899b6fB336C20671f9cD49F', - ETH: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419', - WBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', - BTC: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c', - EURT: '0x01D391A48f4F7339aC64CA2c83a07C22F95F587a', - EUR: '0xb49f677943BC038e9857d61E7d053CaA2C1734C1', - CVX: '0xd962fC30A72A84cE50161031391756Bf2876Af5D', - CRV: '0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f', - stETHETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', // stETH/ETH - stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD - rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH - cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df', // frxETH/ETH - }, - AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', - AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', - AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', - AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', - AAVE_DATA_PROVIDER: '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', - FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', - COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', - GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', - EASY_AUCTION_OWNER: '0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe', - MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', - MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', - MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', - COMET_REWARDS: '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', - COMET_CONFIGURATOR: '0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3', - COMET_PROXY_ADMIN: '0x1EC63B5883C3481134FD50D5DAebc83Ecd2E8779', - COMET_EXT: '0x285617313887d43256F852cAE0Ee4de4b68D45B0', - AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', - AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', - STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', - CURVE_POOL_WETH_FRXETH: '0x9c3b46c0ceb5b9e304fcd6d88fc50f7dd24b31bc', - }, '1': { name: 'mainnet', tokens: { @@ -267,6 +164,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', aEthUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', + saEthUSDC: '0x093cB4f405924a0C468b43209d5E466F1dd0aC7d', // our wrapper cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', @@ -285,7 +183,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', EURT: '0xC581b735A1688071A1746c968e0798D642EDE491', - RSR: '0x320623b8e4ff03373931769a31fc52a4e78b5d70', + RSR: '0x320623b8E4fF03373931769A31Fc52A4E78B5d70', CRV: '0xD533a949740bb3306d119CC777fa900bA034cd52', CVX: '0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B', ankrETH: '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb', @@ -307,6 +205,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', yvCurveUSDPcrvUSD: '0xF56fB6cc29F0666BDD1662FEaAE2A3C935ee3469', yvCurveUSDCcrvUSD: '0x7cA00559B978CFde81297849be6151d3ccB408A9', + pyUSD: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', + aEthPyUSD: '0x0C0d01AbF3e6aDfcA0989eBbA9d6e85dD58EaB1E', + saEthPyUSD: '0x8d6E0402A3E3aD1b43575b05905F9468447013cF', // our wrapper }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -333,13 +234,18 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df', // frxETH/ETH + pyUSD: '0x8f1dF6D7F2db73eECE86a18b4381F4707b918FB1', }, + AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', + AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', AAVE_DATA_PROVIDER: '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', + EASY_AUCTION_OWNER: '0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe', MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', @@ -546,12 +452,15 @@ export const networkConfig: { [key: string]: INetworkConfig } = { tokens: { DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', USDbC: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', RSR: '0xaB36452DbAC151bE02b16Ca17d8919826072f64a', COMP: '0x9e1028F5F1D5eDE59748FFceE5532509976840E0', WETH: '0x4200000000000000000000000000000000000006', cbETH: '0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', cUSDbCv3: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', - aBasUSDbC: '0x0a1d576f3eFeF75b330424287a95A366e8281D54', + cUSDCv3: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + aBasUSDC: '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB', + saBasUSDC: '0x6AfFDe0bA2D1f8fde8da8f296e7EfC991D807515', // our wrapper aWETHv3: '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7', acbETHv3: '0xcf3D55c10DB69f28fD1A75Bd73f3D8A2d9c595ad', sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', @@ -578,19 +487,13 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', COMET_CONFIGURATOR: '0x45939657d1CA34A8FA39A924B71D28Fe8431e581', COMET_PROXY_ADMIN: '0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d', - COMET_EXT: '0x2F9E3953b2Ef89fA265f2a32ed9F80D00229125B', + COMET_EXT: '0x3bac64185786922292266AA92a58cf870D694E2a', AAVE_V3_POOL: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', AAVE_V3_INCENTIVES_CONTROLLER: '0xf9cc4F0D883F1a1eb2c253bdb46c254Ca51E1F44', STARGATE_STAKING_CONTRACT: '0x06Eb48763f117c7Be887296CDcdfad2E4092739C', }, } - -export const getNetworkConfig = (chainId: string) => { - if (!networkConfig[chainId]) { - throw new Error(`Configuration for network ${chainId} not available`) - } - return networkConfig[chainId] -} +networkConfig['31337'] = networkConfig['1'] export const developmentChains = ['hardhat', 'localhost'] diff --git a/contracts/facade/Facade.sol b/contracts/facade/Facade.sol new file mode 100644 index 0000000000..b2657bb4ff --- /dev/null +++ b/contracts/facade/Facade.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../interfaces/IFacade.sol"; + +/* + * @title Facade + * @notice A Facade delegates execution to facets (implementions) as a function of selector. + * IMPORTANT: The functions should be stateless! They cannot rely on storage. + */ +// slither-disable-start +contract Facade is IFacade, Ownable { + mapping(bytes4 => address) public facets; + + // solhint-disable-next-line no-empty-blocks + constructor() Ownable() {} + + // Save new facets to the Facade, forcefully + function save(address facet, bytes4[] memory selectors) external onlyOwner { + require(facet != address(0), "zero address"); + for (uint256 i = 0; i < selectors.length; i++) { + facets[selectors[i]] = facet; + emit SelectorSaved(facet, selectors[i]); + } + } + + // Find the facet for function that is called and execute the + // function if a facet is found and return any value. + fallback() external { + address facet = facets[msg.sig]; + require(facet != address(0), "facet does not exist"); + + // Execute external function from facet using delegatecall and return any value. + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/facade/FacadeMonitor.sol b/contracts/facade/FacadeMonitor.sol index e8221a1195..d14ad06a5c 100644 --- a/contracts/facade/FacadeMonitor.sol +++ b/contracts/facade/FacadeMonitor.sol @@ -9,7 +9,7 @@ import "../interfaces/IFacadeMonitor.sol"; import "../interfaces/IRToken.sol"; import "../libraries/Fixed.sol"; import "../p1/RToken.sol"; -import "../plugins/assets/compoundv2/CTokenWrapper.sol"; +import "../plugins/assets/compoundv2/DEPRECATED_CTokenWrapper.sol"; import "../plugins/assets/compoundv3/ICusdcV3Wrapper.sol"; import "../plugins/assets/stargate/StargateRewardableWrapper.sol"; import { StaticATokenV3LM } from "../plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol"; @@ -157,20 +157,18 @@ contract FacadeMonitor is Initializable, OwnableUpgradeable, UUPSUpgradeable, IF ); availableLiquidity = underlying.balanceOf(address(aToken)); } else if (collType == CollPluginType.COMPOUND_V2 || collType == CollPluginType.FLUX) { - ICToken cToken; - uint256 cTokenBal; - if (collType == CollPluginType.COMPOUND_V2) { - // CompoundV2 uses a vault to wrap the CToken - CTokenWrapper cTokenVault = CTokenWrapper(address(erc20)); - cToken = ICToken(address(cTokenVault.underlying())); - cTokenBal = cTokenVault.balanceOf(address(rToken.main().backingManager())); - } else { - // FLUX - Uses FToken directly (fork of CToken) - cToken = ICToken(address(erc20)); - cTokenBal = cToken.balanceOf(address(rToken.main().backingManager())); + // (1) OLD compound-v2 uses a wrapper + // (2) NEW compound-v2 does not use a wrapper + // (3) FLUX does not use a wrapper + ICToken cToken = ICToken(ICToken(address(erc20)).underlying()); // case (1) + + // solhint-disable-next-line no-empty-blocks + try cToken.underlying() returns (address) {} catch { + cToken = ICToken(address(erc20)); // case (2) or (3) } - IERC20 underlying = IERC20(cToken.underlying()); + uint256 cTokenBal = cToken.balanceOf(address(rToken.main().backingManager())); + IERC20 underlying = IERC20(cToken.underlying()); uint256 exchangeRate = cToken.exchangeRateStored(); backingBalance = (cTokenBal * exchangeRate) / 1e18; diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/facets/ActFacet.sol similarity index 89% rename from contracts/facade/FacadeAct.sol rename to contracts/facade/facets/ActFacet.sol index 45f32b4f7c..bbcf1cfec9 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/facets/ActFacet.sol @@ -4,24 +4,25 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; -import "../plugins/trading/DutchTrade.sol"; -import "../plugins/trading/GnosisTrade.sol"; -import "../interfaces/IBackingManager.sol"; -import "../interfaces/IFacadeAct.sol"; -import "../interfaces/IFacadeRead.sol"; +import "../../plugins/trading/DutchTrade.sol"; +import "../../plugins/trading/GnosisTrade.sol"; +import "../../interfaces/IActFacet.sol"; +import "../../interfaces/IBackingManager.sol"; /** - * @title Facade - * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. + * @title ActFacet + * @notice + * Facet to help batch compound actions that cannot be done from an EOA, solely. * Compatible with both 2.1.0 and ^3.0.0 RTokens. + * @custom:static-call - Use ethers callStatic() to get result after update; do not execute */ // slither-disable-start -contract FacadeAct is IFacadeAct, Multicall { +contract ActFacet is IActFacet, Multicall { using Address for address; using SafeERC20 for IERC20; using FixLib for uint192; - function claimRewards(IRToken rToken) public { + function claimRewards(IRToken rToken) external { IMain main = rToken.main(); main.backingManager().claimRewards(); main.rTokenTrader().claimRewards(); @@ -36,7 +37,6 @@ contract FacadeAct is IFacadeAct, Multicall { /// For each ERC20 in `toSettle`: /// - Settle any open ERC20 trades /// Then: - /// - Transfer any revenue for that ERC20 from the backingManager to revenueTrader /// - Call `revenueTrader.manageTokens(ERC20)` to start an auction function runRevenueAuctions( IRevenueTrader revenueTrader, @@ -51,10 +51,7 @@ contract FacadeAct is IFacadeAct, Multicall { // if 2.1.0, distribute tokenToBuy bytes1 majorVersion = bytes(revenueTrader.version())[0]; - if ( - toSettle.length > 0 && - (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) - ) { + if (toSettle.length > 0 && (majorVersion == bytes1("2") || majorVersion == bytes1("1"))) { address(revenueTrader).functionCall( abi.encodeWithSignature("manageToken(address)", revenueTrader.tokenToBuy()) ); @@ -218,10 +215,10 @@ contract FacadeAct is IFacadeAct, Multicall { function _settleTrade(ITrading trader, IERC20 toSettle) private { bytes1 majorVersion = bytes(trader.version())[0]; - if (majorVersion == MAJOR_VERSION_3) { + if (majorVersion == bytes1("3")) { // Settle auctions trader.settleTrade(toSettle); - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + } else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) { address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle)); } else { _revertUnrecognizedVersion(); @@ -231,10 +228,10 @@ contract FacadeAct is IFacadeAct, Multicall { function _forwardRevenue(IBackingManager bm, IERC20[] memory toStart) private { bytes1 majorVersion = bytes(bm.version())[0]; // Need to use try-catch here in order to still show revenueOverview when basket not ready - if (majorVersion == MAJOR_VERSION_3) { + if (majorVersion == bytes1("3")) { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(toStart) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + } else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) { // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", toStart) @@ -252,9 +249,9 @@ contract FacadeAct is IFacadeAct, Multicall { ) private { bytes1 majorVersion = bytes(revenueTrader.version())[0]; - if (majorVersion == MAJOR_VERSION_3) { + if (majorVersion == bytes1("3")) { revenueTrader.manageTokens(toStart, kinds); - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + } else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) { for (uint256 i = 0; i < toStart.length; ++i) { address(revenueTrader).functionCall( abi.encodeWithSignature("manageToken(address)", toStart[i]) @@ -268,10 +265,10 @@ contract FacadeAct is IFacadeAct, Multicall { function _rebalance(IBackingManager bm, TradeKind kind) private { bytes1 majorVersion = bytes(bm.version())[0]; - if (majorVersion == MAJOR_VERSION_3) { + if (majorVersion == bytes1("3")) { // solhint-disable-next-line no-empty-blocks try bm.rebalance(kind) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + } else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) { IERC20[] memory emptyERC20s = new IERC20[](0); // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(bm).call{ value: 0 }( diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/facets/ReadFacet.sol similarity index 97% rename from contracts/facade/FacadeRead.sol rename to contracts/facade/facets/ReadFacet.sol index 8ad96bc3b0..4c9b0dcffb 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/facets/ReadFacet.sol @@ -2,25 +2,26 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../plugins/trading/DutchTrade.sol"; -import "../interfaces/IAsset.sol"; -import "../interfaces/IAssetRegistry.sol"; -import "../interfaces/IFacadeRead.sol"; -import "../interfaces/IRToken.sol"; -import "../interfaces/IStRSR.sol"; -import "../libraries/Fixed.sol"; -import "../p1/BasketHandler.sol"; -import "../p1/RToken.sol"; -import "../p1/StRSRVotes.sol"; +import "../../plugins/trading/DutchTrade.sol"; +import "../../interfaces/IAsset.sol"; +import "../../interfaces/IAssetRegistry.sol"; +import "../../interfaces/IReadFacet.sol"; +import "../../interfaces/IRToken.sol"; +import "../../interfaces/IStRSR.sol"; +import "../../libraries/Fixed.sol"; +import "../../p1/BasketHandler.sol"; +import "../../p1/RToken.sol"; +import "../../p1/StRSRVotes.sol"; /** - * @title Facade - * @notice A UX-friendly layer for reading out the state of a ^3.0.0 RToken in summary views. + * @title ReadFacet + * @notice + * Facet for reading out the state of a ^3.0.0 RToken in summary views. * Backwards-compatible with 2.1.0 RTokens with the exception of `redeemCustom()`. * @custom:static-call - Use ethers callStatic() to get result after update; do not execute */ // slither-disable-start -contract FacadeRead is IFacadeRead { +contract ReadFacet is IReadFacet { using FixLib for uint192; // === Static Calls === diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IActFacet.sol similarity index 90% rename from contracts/interfaces/IFacadeAct.sol rename to contracts/interfaces/IActFacet.sol index eef569af4f..7cf5b00274 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IActFacet.sol @@ -6,21 +6,17 @@ import "../interfaces/IStRSRVotes.sol"; import "../interfaces/IRevenueTrader.sol"; import "../interfaces/IRToken.sol"; -bytes1 constant MAJOR_VERSION_1 = bytes1("1"); -bytes1 constant MAJOR_VERSION_2 = bytes1("2"); -bytes1 constant MAJOR_VERSION_3 = bytes1("3"); - /** - * @title IFacadeAct + * @title IActFacet * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. v */ -interface IFacadeAct { +interface IActFacet { /// Claims rewards from all places they can accrue. function claimRewards(IRToken rToken) external; /// To use this, first call: - /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - FacadeRead.revenueOverview(revenueTrader) + /// - IReadFacet.auctionsSettleable(revenueTrader) + /// - IReadFacet.revenueOverview(revenueTrader) /// If either arrays returned are non-empty, then can execute this function productively. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index b9b3c5beca..fef9a3491b 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IAssetRegistry.sol"; import "./IBasketHandler.sol"; -import "./IBroker.sol"; import "./IComponent.sol"; import "./IRToken.sol"; import "./IStRSR.sol"; diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index fcaeac2c10..f1049ac5c7 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "./IAsset.sol"; import "./IComponent.sol"; import "./IGnosis.sol"; diff --git a/contracts/interfaces/IFacade.sol b/contracts/interfaces/IFacade.sol new file mode 100644 index 0000000000..afd227acd3 --- /dev/null +++ b/contracts/interfaces/IFacade.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "./IActFacet.sol"; +import "./IReadFacet.sol"; + +interface IFacade { + event SelectorSaved(address indexed facet, bytes4 indexed selector); + + // Save new facet to the Facade, forcefully + function save(address facet, bytes4[] memory selectors) external; + + function facets(bytes4 selector) external view returns (address); +} + +// solhint-disable-next-line no-empty-blocks +interface TestIFacade is IFacade, IActFacet, IReadFacet { + +} diff --git a/contracts/interfaces/IFacadeMonitor.sol b/contracts/interfaces/IFacadeMonitor.sol index 6c4f6f8d2d..0794a8e2f9 100644 --- a/contracts/interfaces/IFacadeMonitor.sol +++ b/contracts/interfaces/IFacadeMonitor.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IRToken.sol"; /** diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index f282be1479..dcb6ce910a 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -7,9 +7,9 @@ import "./IAssetRegistry.sol"; import "./IBasketHandler.sol"; import "./IBackingManager.sol"; import "./IBroker.sol"; -import "./IGnosis.sol"; -import "./IFurnace.sol"; import "./IDistributor.sol"; +import "./IFurnace.sol"; +import "./IGnosis.sol"; import "./IRToken.sol"; import "./IRevenueTrader.sol"; import "./IStRSR.sol"; diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 9528ab2efd..faa09a6b5f 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -4,12 +4,10 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; // solhint-disable-next-line max-line-length import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../libraries/Fixed.sol"; import "../libraries/Throttle.sol"; -import "./IAsset.sol"; import "./IComponent.sol"; -import "./IMain.sol"; -import "./IRewardable.sol"; /** * @title IRToken diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IReadFacet.sol similarity index 99% rename from contracts/interfaces/IFacadeRead.sol rename to contracts/interfaces/IReadFacet.sol index 8a3918be06..94e90880db 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IReadFacet.sol @@ -6,12 +6,12 @@ import "./IRToken.sol"; import "./IStRSR.sol"; /** - * @title IFacadeRead + * @title IReadFacet * @notice A UX-friendly layer for read operations, especially those that first require refresh() * * - @custom:static-call - Use ethers callStatic() in order to get result after update v */ -interface IFacadeRead { +interface IReadFacet { // === Static Calls === /// @return How many RToken `account` can issue given current holdings diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 8ab78078e1..c8cea3f4bd 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; -import "./IBroker.sol"; import "./IComponent.sol"; import "./ITrading.sol"; diff --git a/contracts/interfaces/IRewardable.sol b/contracts/interfaces/IRewardable.sol index 48da999850..44cfa3352b 100644 --- a/contracts/interfaces/IRewardable.sol +++ b/contracts/interfaces/IRewardable.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "./IComponent.sol"; -import "./IMain.sol"; /** * @title IRewardable @@ -11,6 +9,8 @@ import "./IMain.sol"; */ interface IRewardable { /// Emitted whenever a reward token balance is claimed + /// @param erc20 The ERC20 of the reward token + /// @param amount {qTok} event RewardsClaimed(IERC20 indexed erc20, uint256 amount); /// Claim rewards earned by holding a balance of the ERC20 token diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index b0279ef220..a080765a68 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -6,7 +6,6 @@ import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20Metadat import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol"; import "../libraries/Fixed.sol"; import "./IComponent.sol"; -import "./IMain.sol"; /** * @title IStRSR diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index f9e95114f9..dddec70325 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "./IBroker.sol"; +import "./IVersioned.sol"; enum TradeStatus { NOT_STARTED, // before init() @@ -17,7 +18,7 @@ enum TradeStatus { * * Usage: if (canSettle()) settle() */ -interface ITrade { +interface ITrade is IVersioned { /// Complete the trade and transfer tokens back to the origin trader /// @return soldAmt {qSellTok} The quantity of tokens sold /// @return boughtAmt {qBuyTok} The quantity of tokens bought diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index b0bed9bad3..6fc380b6b3 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../libraries/Fixed.sol"; -import "./IAsset.sol"; import "./IComponent.sol"; import "./ITrade.sol"; import "./IRewardable.sol"; diff --git a/contracts/libraries/Allowance.sol b/contracts/libraries/Allowance.sol index c3e5f62e5d..bd5fea1bd8 100644 --- a/contracts/libraries/Allowance.sol +++ b/contracts/libraries/Allowance.sol @@ -23,6 +23,8 @@ library AllowanceLib { // 1. Set initial allowance to 0 token.approve(spender, 0); + // untestable: + // allowance should always be 0 if token behaves correctly require(token.allowance(address(this), spender) == 0, "allowance not 0"); if (value == 0) return; @@ -37,6 +39,8 @@ library AllowanceLib { // 3. Fall-back to setting a maximum allowance if (!success) { token.approve(spender, type(uint256).max); + // untestable: + // allowance should always be max value if token behaves correctly require(token.allowance(address(this), spender) >= value, "allowance missing"); } } diff --git a/contracts/mixins/ComponentRegistry.sol b/contracts/mixins/ComponentRegistry.sol index d8136c6270..ff3a29f7c7 100644 --- a/contracts/mixins/ComponentRegistry.sol +++ b/contracts/mixins/ComponentRegistry.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../interfaces/IMain.sol"; import "./Auth.sol"; /** diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index afc4915e0c..92871bc922 100644 --- a/contracts/mixins/Versioned.sol +++ b/contracts/mixins/Versioned.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant VERSION = "3.2.0"; +string constant VERSION = "3.3.0"; /** * @title Versioned diff --git a/contracts/p0/mixins/Rewardable.sol b/contracts/p0/mixins/Rewardable.sol index a2d0bcdbe6..a74a250016 100644 --- a/contracts/p0/mixins/Rewardable.sol +++ b/contracts/p0/mixins/Rewardable.sol @@ -19,9 +19,13 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { IERC20[] memory erc20s = reg.erc20s(); for (uint256 i = 0; i < erc20s.length; i++) { - // empty try/catch because not every erc20 will be wrapped & have a claimRewards func - // solhint-disable-next-line - try IRewardable(address(erc20s[i])).claimRewards() {} catch {} + IAsset asset = reg.toAsset(erc20s[i]); + + // Claim rewards via delegatecall + address(asset).functionDelegateCall( + abi.encodeWithSignature("claimRewards()"), + "rewards claim failed" + ); } } @@ -30,8 +34,12 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { /// @param erc20 The ERC20 to claimRewards on /// @custom:interaction CEI function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { - // empty try/catch because not every erc20 will be wrapped & have a claimRewards func - // solhint-disable-next-line - try IRewardable(address(erc20)).claimRewards() {} catch {} + IAsset asset = main.assetRegistry().toAsset(erc20); + + // Claim rewards via delegatecall + address(asset).functionDelegateCall( + abi.encodeWithSignature("claimRewards()"), + "rewards claim failed" + ); } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index c0809c214e..c842a982d5 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -635,7 +635,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require(ArrayLib.allUnique(erc20s), "contains duplicates"); } - // ==== FacadeRead views ==== + // ==== ReadFacet views ==== // Not used in-protocol; helpful for reconstructing state /// Get a reference basket in today's collateral tokens, by nonce diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 0111d25bc3..d29a30cd9d 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -158,6 +158,7 @@ contract BrokerP1 is ComponentP1, IBroker { dutchTradeDisabled[buy] = true; } } else { + // untestable: trade kind is either BATCH or DUTCH revert("unrecognized trade kind"); } } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index f8a15e4c63..04e6551662 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -124,10 +124,10 @@ contract DistributorP1 is ComponentP1, IDistributor { if (addrTo == FURNACE) { addrTo = furnaceAddr; - if (transferAmt > 0) accountRewards = true; + accountRewards = true; } else if (addrTo == ST_RSR) { addrTo = stRSRAddr; - if (transferAmt > 0) accountRewards = true; + accountRewards = true; } transfers[numTransfers] = Transfer({ addrTo: addrTo, amount: transferAmt }); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 998bdc951f..8ec6574e1c 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -56,6 +56,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // solhint-disable-next-line no-empty-blocks try this.distributeTokenToBuy() {} catch (bytes memory errData) { + // untested: + // OOG pattern tested in other contracts, cost to test here is high // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string } diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index 216935c2b9..e2daa9f2ee 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -363,6 +363,8 @@ library BasketLibP1 { ICollateral coll = assetRegistry.toColl(erc20s[i]); // reverts if unregistered (uint192 low, uint192 high) = coll.price(); // {UoA/tok} + // untestable: + // this function is only called if basket is SOUND require(low > 0 && high < FIX_MAX, "invalid price"); // {UoA/BU} += {target/BU} * {UoA/tok} / ({target/ref} * {ref/tok}) diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 8edb10f86c..3b83589e9d 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -2,9 +2,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../../interfaces/IAsset.sol"; -import "../../interfaces/IAssetRegistry.sol"; -import "../../interfaces/IBackingManager.sol"; import "../../libraries/Fixed.sol"; import "./TradeLib.sol"; diff --git a/contracts/p1/mixins/RewardableLib.sol b/contracts/p1/mixins/RewardableLib.sol index 58b34987b4..79cb91426f 100644 --- a/contracts/p1/mixins/RewardableLib.sol +++ b/contracts/p1/mixins/RewardableLib.sol @@ -19,25 +19,28 @@ library RewardableLibP1 { // === Used by Traders + RToken === /// Claim all rewards - /// @custom:interaction mostly CEI but see comments // actions: - // try erc20.claimRewards() for erc20 in erc20s + // do asset.delegatecall(abi.encodeWithSignature("claimRewards()")) for asset in assets function claimRewards(IAssetRegistry reg) internal { Registry memory registry = reg.getRegistry(); - for (uint256 i = 0; i < registry.erc20s.length; ++i) { - // empty try/catch because not every erc20 will be wrapped & have a claimRewards func - // solhint-disable-next-line - try IRewardable(address(registry.erc20s[i])).claimRewards() {} catch {} + uint256 len = registry.assets.length; + for (uint256 i = 0; i < len; ++i) { + // Claim rewards via delegatecall + address(registry.assets[i]).functionDelegateCall( + abi.encodeWithSignature("claimRewards()"), + "rewards claim failed" + ); } } /// Claim rewards for a single ERC20 - /// @custom:interaction mostly CEI but see comments // actions: - // try erc20.claimRewards() + // do asset.delegatecall(abi.encodeWithSignature("claimRewards()")) function claimRewardsSingle(IAsset asset) internal { - // empty try/catch because not every erc20 will be wrapped & have a claimRewards func - // solhint-disable-next-line - try IRewardable(address(asset.erc20())).claimRewards() {} catch {} + // Claim rewards via delegatecall + address(asset).functionDelegateCall( + abi.encodeWithSignature("claimRewards()"), + "rewards claim failed" + ); } } diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 8d3c8e01c9..89fa344945 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -3,10 +3,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; -import "../../interfaces/IAssetRegistry.sol"; -import "../../interfaces/ITrading.sol"; import "../../libraries/Fixed.sol"; -import "./RecollateralizationLib.sol"; struct TradeInfo { IAsset sell; diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index f2df97c40b..86a9a83b98 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -187,7 +187,7 @@ contract Asset is IAsset, VersionedAsset { // solhint-disable no-empty-blocks /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual {} // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/EURFiatCollateral.sol b/contracts/plugins/assets/EURFiatCollateral.sol index d22e3152a0..a25b2bcf6d 100644 --- a/contracts/plugins/assets/EURFiatCollateral.sol +++ b/contracts/plugins/assets/EURFiatCollateral.sol @@ -55,6 +55,8 @@ contract EURFiatCollateral is FiatCollateral { uint192 pricePerTarget = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); // div-by-zero later + // untestable: + // calls to price() on the feed never return zero if using OracleLib if (pricePerTarget == 0) { return (0, FIX_MAX, 0); } diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index e9487fe671..52d9d1be6b 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -134,7 +134,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // solhint-disable no-empty-blocks /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual {} // solhint-enable no-empty-blocks @@ -150,6 +150,8 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// @return updatedAt {s} The timestamp of the cache update function latestPrice() external returns (uint192 rTokenPrice, uint256 updatedAt) { // Situations that require an update, from most common to least common. + // untestable: + // basket and trade nonce checks, as first condition will always be true in these cases if ( cachedOracleData.cachedAtTime + ORACLE_TIMEOUT <= block.timestamp || // Cache Timeout cachedOracleData.cachedAtNonce != basketHandler.nonce() || // Basket nonce was updated diff --git a/contracts/plugins/assets/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol index 4b241f6ef3..2eb2857931 100644 --- a/contracts/plugins/assets/VersionedAsset.sol +++ b/contracts/plugins/assets/VersionedAsset.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "../../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant ASSET_VERSION = "3.2.0"; +string constant ASSET_VERSION = "3.3.0"; /** * @title VersionedAsset diff --git a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol index 8aa9d87eb1..240bbacf9f 100644 --- a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol +++ b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol @@ -34,9 +34,24 @@ contract AaveV3FiatCollateral is AppreciatingFiatCollateral { } /// Claim rewards earned by holding a balance of the ERC20 token - /// delegatecall - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override(Asset, IRewardable) { - StaticATokenV3LM(address(erc20)).claimRewards(); + StaticATokenV3LM erc20_ = StaticATokenV3LM(address(erc20)); + address[] memory rewardsList = erc20_.INCENTIVES_CONTROLLER().getRewardsList(); + uint256[] memory bals = new uint256[](rewardsList.length); + + uint256 len = rewardsList.length; + for (uint256 i = 0; i < len; i++) { + bals[i] = IERC20(rewardsList[i]).balanceOf(address(this)); + } + + IRewardable(address(erc20)).claimRewards(); + + for (uint256 i = 0; i < len; i++) { + emit RewardsClaimed( + IERC20(rewardsList[i]), + IERC20(rewardsList[i]).balanceOf(address(this)) - bals[i] + ); + } } } diff --git a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol index 439a711831..2a40884470 100644 --- a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol +++ b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol @@ -35,6 +35,8 @@ contract ATokenFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; + IERC20 private immutable stkAAVE; + // solhint-disable no-empty-blocks /// @param config.chainlinkFeed Feed units: {UoA/ref} @@ -43,6 +45,7 @@ contract ATokenFiatCollateral is AppreciatingFiatCollateral { AppreciatingFiatCollateral(config, revenueHiding) { require(config.defaultThreshold > 0, "defaultThreshold zero"); + stkAAVE = IStaticAToken(address(erc20)).REWARD_TOKEN(); } // solhint-enable no-empty-blocks @@ -54,8 +57,10 @@ contract ATokenFiatCollateral is AppreciatingFiatCollateral { } /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override(Asset, IRewardable) { + uint256 bal = stkAAVE.balanceOf(address(this)); IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(stkAAVE, stkAAVE.balanceOf(address(this)) - bal); } } diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index 9434ddada5..057b10bc70 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -12,7 +12,7 @@ import "./ICToken.sol"; * @title CTokenFiatCollateral * @notice Collateral plugin for a cToken of fiat collateral, like cUSDC or cUSDP * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} - * Also used for FluxFinance. Flexible enough to work with and without CTokenWrapper. + * Also used for FluxFinance. Should NOT use with an ERC20 wrapper. */ contract CTokenFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; @@ -22,30 +22,21 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; - ICToken public immutable cToken; // gas-optimization: access underlying cToken directly + IComptroller private immutable comptroller; - /// @param config.erc20 May be a CTokenWrapper or the cToken itself + IERC20 private immutable comp; // COMP token + + /// @param config.erc20 The CToken itself /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) { require(config.defaultThreshold > 0, "defaultThreshold zero"); - - ICToken _cToken = ICToken(address(config.erc20)); - address _underlying = _cToken.underlying(); - uint8 _referenceERC20Decimals; - - // _underlying might be a wrapper at this point, try to go one level further - try ICToken(_underlying).underlying() returns (address _mostUnderlying) { - _cToken = ICToken(_underlying); - _referenceERC20Decimals = IERC20Metadata(_mostUnderlying).decimals(); - } catch { - _referenceERC20Decimals = IERC20Metadata(_underlying).decimals(); - } - - cToken = _cToken; - referenceERC20Decimals = _referenceERC20Decimals; + address referenceERC20 = ICToken(address(config.erc20)).underlying(); + referenceERC20Decimals = IERC20Metadata(referenceERC20).decimals(); require(referenceERC20Decimals > 0, "referenceERC20Decimals missing"); + comptroller = ICToken(address(config.erc20)).comptroller(); + comp = IERC20(comptroller.getCompAddress()); } /// Refresh exchange rates and update default status. @@ -54,7 +45,7 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { // == Refresh == // Update the Compound Protocol // solhint-disable no-empty-blocks - try cToken.exchangeRateCurrent() {} catch (bytes memory errData) { + try ICToken(address(erc20)).exchangeRateCurrent() {} catch (bytes memory errData) { CollateralStatus oldStatus = status(); // see: docs/solidity-style.md#Catching-Empty-Data @@ -73,16 +64,20 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function underlyingRefPerTok() public view override returns (uint192) { - uint256 rate = cToken.exchangeRateStored(); + uint256 rate = ICToken(address(erc20)).exchangeRateStored(); int8 shiftLeft = 8 - int8(referenceERC20Decimals) - 18; return shiftl_toFix(rate, shiftLeft); } /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override(Asset, IRewardable) { - // solhint-ignore-next-line no-empty-blocks - try IRewardable(address(erc20)).claimRewards() {} catch {} - // erc20 may not be a CTokenWrapper + uint256 bal = comp.balanceOf(address(this)); + address[] memory holders = new address[](1); + address[] memory cTokens = new address[](1); + holders[0] = address(this); + cTokens[0] = address(erc20); + comptroller.claimComp(holders, cTokens, false, true); + emit RewardsClaimed(comp, comp.balanceOf(address(this)) - bal); } } diff --git a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol index 8c9818e0e4..21ec14d585 100644 --- a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol @@ -31,7 +31,6 @@ contract CTokenNonFiatCollateral is CTokenFiatCollateral { ) CTokenFiatCollateral(config, revenueHiding) { require(address(targetUnitChainlinkFeed_) != address(0), "missing targetUnit feed"); require(targetUnitOracleTimeout_ > 0, "targetUnitOracleTimeout zero"); - require(config.defaultThreshold > 0, "defaultThreshold zero"); targetUnitChainlinkFeed = targetUnitChainlinkFeed_; targetUnitOracleTimeout = targetUnitOracleTimeout_; maxOracleTimeout = uint48(Math.max(maxOracleTimeout, targetUnitOracleTimeout_)); diff --git a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol index 34fdd32856..fcbe5651c8 100644 --- a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol @@ -9,6 +9,7 @@ import "./ICToken.sol"; * @title CTokenSelfReferentialCollateral * @notice Collateral plugin for a cToken of unpegged collateral, such as cETH. * Expected: {tok} != {ref}, {ref} == {target}, {target} != {UoA} + * Should NOT use with an ERC20 wrapper. */ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; @@ -18,8 +19,11 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; - ICToken public immutable cToken; // gas-optimization: access underlying cToken directly + IComptroller private immutable comptroller; + IERC20 private immutable comp; // COMP token + + /// @param config.erc20 The CToken itself /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide /// @param referenceERC20Decimals_ The number of decimals in the reference token @@ -30,8 +34,9 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(config.defaultThreshold == 0, "default threshold not supported"); require(referenceERC20Decimals_ > 0, "referenceERC20Decimals missing"); - cToken = ICToken(address(RewardableERC20Wrapper(address(config.erc20)).underlying())); referenceERC20Decimals = referenceERC20Decimals_; + comptroller = ICToken(address(config.erc20)).comptroller(); + comp = IERC20(comptroller.getCompAddress()); } /// Can revert, used by other contract functions in order to catch errors @@ -65,7 +70,7 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { // == Refresh == // Update the Compound Protocol -- access cToken directly // solhint-disable no-empty-blocks - try cToken.exchangeRateCurrent() {} catch (bytes memory errData) { + try ICToken(address(erc20)).exchangeRateCurrent() {} catch (bytes memory errData) { CollateralStatus oldStatus = status(); // see: docs/solidity-style.md#Catching-Empty-Data @@ -84,14 +89,20 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function underlyingRefPerTok() public view override returns (uint192) { - uint256 rate = cToken.exchangeRateStored(); + uint256 rate = ICToken(address(erc20)).exchangeRateStored(); int8 shiftLeft = 8 - int8(referenceERC20Decimals) - 18; return shiftl_toFix(rate, shiftLeft); } /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override(Asset, IRewardable) { - IRewardable(address(erc20)).claimRewards(); + uint256 bal = comp.balanceOf(address(this)); + address[] memory holders = new address[](1); + address[] memory cTokens = new address[](1); + holders[0] = address(this); + cTokens[0] = address(erc20); + comptroller.claimComp(holders, cTokens, false, true); + emit RewardsClaimed(comp, comp.balanceOf(address(this)) - bal); } } diff --git a/contracts/plugins/assets/compoundv2/CTokenWrapper.sol b/contracts/plugins/assets/compoundv2/DEPRECATED_CTokenWrapper.sol similarity index 99% rename from contracts/plugins/assets/compoundv2/CTokenWrapper.sol rename to contracts/plugins/assets/compoundv2/DEPRECATED_CTokenWrapper.sol index 27b37d8382..c4c5a10d80 100644 --- a/contracts/plugins/assets/compoundv2/CTokenWrapper.sol +++ b/contracts/plugins/assets/compoundv2/DEPRECATED_CTokenWrapper.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../erc20/RewardableERC20Wrapper.sol"; import "./ICToken.sol"; +/// DEPRECATED contract CTokenWrapper is RewardableERC20Wrapper { using SafeERC20 for ERC20; diff --git a/contracts/plugins/assets/compoundv2/ICToken.sol b/contracts/plugins/assets/compoundv2/ICToken.sol index c83f9a3552..2fa76ec185 100644 --- a/contracts/plugins/assets/compoundv2/ICToken.sol +++ b/contracts/plugins/assets/compoundv2/ICToken.sol @@ -31,6 +31,8 @@ interface ICToken is IERC20Metadata { * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) */ function redeem(uint256 redeemTokens) external returns (uint256); + + function comptroller() external view returns (IComptroller); } interface TestICToken is ICToken { diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index 7ea45ec6be..c4792f6b73 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -22,27 +22,25 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; - IERC20 public immutable rewardERC20; IComet public immutable comet; - uint256 public immutable reservesThresholdIffy; // {qUSDC} uint8 public immutable cometDecimals; + IERC20 private immutable comp; /// @param config.chainlinkFeed Feed units: {UoA/ref} - constructor( - CollateralConfig memory config, - uint192 revenueHiding, - uint256 reservesThresholdIffy_ - ) AppreciatingFiatCollateral(config, revenueHiding) { + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { require(config.defaultThreshold > 0, "defaultThreshold zero"); - rewardERC20 = ICusdcV3Wrapper(address(config.erc20)).rewardERC20(); + comp = ICusdcV3Wrapper(address(config.erc20)).rewardERC20(); comet = IComet(address(ICusdcV3Wrapper(address(erc20)).underlyingComet())); - reservesThresholdIffy = reservesThresholdIffy_; cometDecimals = comet.decimals(); } - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external override(Asset, IRewardable) { + uint256 bal = comp.balanceOf(address(this)); IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(comp, comp.balanceOf(address(this)) - bal); } function underlyingRefPerTok() public view virtual override returns (uint192) { @@ -72,41 +70,34 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { exposedReferencePrice = hiddenReferencePrice; } - int256 cometReserves = comet.getReserves(); - if (cometReserves < 0) { - markStatus(CollateralStatus.DISABLED); - } else if (uint256(cometReserves) < reservesThresholdIffy) { - markStatus(CollateralStatus.IFFY); - } else { - // Check for soft default + save prices - try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { - // {UoA/tok}, {UoA/tok}, {target/ref} - // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {target/ref} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced - // Save prices if priced - if (high < FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - lastSave = uint48(block.timestamp); - } else { - // must be unpriced - // untested: - // validated in other plugins, cost to test here is high - assert(low == 0); - } + // Save prices if priced + if (high < FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + // untested: + // validated in other plugins, cost to test here is high + assert(low == 0); + } - // If the price is below the default-threshold price, default eventually - // uint192(+/-) is the same as Fix.plus/minus - if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { - markStatus(CollateralStatus.IFFY); - } else { - markStatus(CollateralStatus.SOUND); - } - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data diff --git a/contracts/plugins/assets/curve/CurveStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol index aff843a253..a623d41b3f 100644 --- a/contracts/plugins/assets/curve/CurveStableCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableCollateral.sol @@ -7,6 +7,7 @@ import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "contracts/interfaces/IAsset.sol"; import "contracts/libraries/Fixed.sol"; import "contracts/plugins/assets/AppreciatingFiatCollateral.sol"; +import "contracts/plugins/assets/erc20/RewardableERC20.sol"; import "../curve/PoolTokens.sol"; /** @@ -15,6 +16,7 @@ import "../curve/PoolTokens.sol"; * whether this LP token ends up staked in Curve, Convex, Frax, or somewhere else. * Each token in the pool can have between 1 and 2 oracles per each token. * Stable means only like-kind pools. + * Works for both CurveGaugeWrapper and ConvexStakingWrapper. * * tok = ConvexStakingWrapper(stablePlainPool) * ref = stablePlainPool pool invariant @@ -28,8 +30,14 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; + // I don't love hard-coding these, but I prefer it to dynamically reading from either + // a CurveGaugeWrapper or ConvexStakingWrapper. If we ever use this contract + // on something other than mainnet we'll have to change this. + IERC20 public constant CRV = IERC20(0xD533a949740bb3306d119CC777fa900bA034cd52); + IERC20 public constant CVX = IERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); + /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout - /// @dev config.erc20 should be a RewardableERC20 + /// @dev config.erc20 should be a CurveGaugeWrapper or ConvexStakingWrapper constructor( CollateralConfig memory config, uint192 revenueHiding, @@ -42,7 +50,7 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { /// Can revert, used by other contract functions in order to catch errors /// Should not return FIX_MAX for low /// Should only return FIX_MAX for high if low is 0 - /// @dev Override this when pricing is more complicated than just a single oracle + /// @dev Override this when pricing is more complicated than just a single pool /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return {target/ref} Unused. Always 0 @@ -57,6 +65,24 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { uint192 ) { + // Assumption: the pool is balanced + // + // This pricing method returns a MINIMUM when the pool is balanced. + // It IS possible to interact with the protocol within a sandwich to manipulate + // LP token price upwards. + // + // However: + // - Lots of manipulation is required; + // (StableSwap pools are not price sensitive until the edge of the curve) + // - The DutchTrade pricing curve accounts for small/medium amounts of manipulation + // - The manipulator is under competition in auctions, so cannot guarantee they + // are the beneficiary of the manipulation. + // + // To be more MEV-resistant requires not using spot balances at all, which means one-of: + // 1. A moving average metric (unavailable in the cases we care about) + // 2. Mapping oracle prices to expected pool balances using precise knowledge about + // the shape of the trading curve. (maybe we can do this in the future) + // {UoA} (uint192 aumLow, uint192 aumHigh) = totalBalancesValue(); @@ -136,9 +162,15 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { } /// Claim rewards earned by holding a balance of the ERC20 token - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override(Asset, IRewardable) { + // Plugin can be used with either Curve or Convex wrappers + // Here I prefer omitting any wrapper-specific logic at the cost of an additional event + uint256 crvBal = CRV.balanceOf(address(this)); + uint256 cvxBal = CVX.balanceOf(address(this)); IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(CRV, CRV.balanceOf(address(this)) - crvBal); + emit RewardsClaimed(CVX, CVX.balanceOf(address(this)) - cvxBal); } // === Internal === diff --git a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol index b743021b65..8f759b4e96 100644 --- a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol @@ -68,7 +68,6 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { /// Can revert, used by other contract functions in order to catch errors /// Should not return FIX_MAX for low /// Should only return FIX_MAX for high if low is 0 - /// @dev Override this when pricing is more complicated than just a single oracle /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return pegPrice {target/ref} The actual price observed in the peg @@ -83,6 +82,24 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { uint192 pegPrice ) { + // Assumption: the pool is balanced + // + // This pricing method returns a MINIMUM when the pool is balanced. + // It IS possible to interact with the protocol within a sandwich to manipulate + // LP token price upwards. + // + // However: + // - Lots of manipulation is required; + // (StableSwap pools are not price sensitive until the edge of the curve) + // - The DutchTrade pricing curve accounts for small/medium amounts of manipulation + // - The manipulator is under competition in auctions, so cannot guarantee they + // are the beneficiary of the manipulation. + // + // To be more MEV-resistant requires not using spot balances at all, which means one-of: + // 1. A moving average metric (unavailable in the cases we care about) + // 2. Mapping oracle prices to expected pool balances using precise knowledge about + // the shape of the trading curve. (maybe we can do this in the future) + // {UoA/pairedTok} (uint192 lowPaired, uint192 highPaired) = tryPairedPrice(); require(lowPaired != 0 && highPaired != FIX_MAX, "invalid price"); @@ -117,7 +134,7 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function underlyingRefPerTok() public view override returns (uint192) { - return _safeWrap(metapoolToken.get_virtual_price()); + return _safeWrap(metapoolToken.get_virtual_price()); // includes inner virtual price } // Check for defaults outside the pool diff --git a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol index 6653f450c2..cdd990a875 100644 --- a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol @@ -451,4 +451,4 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return IBooster(convexBooster).earmarkRewards(convexPoolId); } } -// slither-disable-end reentrancy-no-eth \ No newline at end of file +// slither-disable-end reentrancy-no-eth diff --git a/contracts/plugins/assets/frax-eth/README.md b/contracts/plugins/assets/frax-eth/README.md index 81ed35cd9b..e677103404 100644 --- a/contracts/plugins/assets/frax-eth/README.md +++ b/contracts/plugins/assets/frax-eth/README.md @@ -31,6 +31,7 @@ This function returns rate of `frxETH/sfrxETH`, getting from [pricePerShare()](h #### target-per-ref price {tar/ref} The targetPerRef price of `ETH/frxETH` is received from the frxETH/ETH FRAX-managed oracle ([details here](https://docs.frax.finance/frax-oracle/frax-oracle-overview)). + #### tryPrice This function uses `refPerTok` and the chainlink price of `USD/ETH` to return the current price range of the collateral. Once an oracle becomes available for `frxETH/ETH`, this function should be modified to use it and return the appropiate `pegPrice`. diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index 25b39749d9..b9b615b2bf 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -49,7 +49,7 @@ contract SFraxEthCollateral is AppreciatingFiatCollateral { /// Can revert, used by other contract functions in order to catch errors /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/ref} FIX_ONE until an oracle becomes available + /// @return pegPrice {target/ref} The actual price observed in the peg function tryPrice() external view diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index 96cb195a62..5b55214855 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; // solhint-disable-next-line max-line-length -import { AppreciatingFiatCollateral, CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; import { MorphoTokenisedDeposit } from "./MorphoTokenisedDeposit.sol"; import { OracleLib } from "../OracleLib.sol"; // solhint-disable-next-line max-line-length @@ -18,6 +18,7 @@ import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; contract MorphoFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; + IERC20Metadata private immutable morpho; uint256 private immutable oneShare; int8 private immutable refDecimals; @@ -30,6 +31,7 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { require(address(config.erc20) != address(0), "missing erc20"); require(config.defaultThreshold > 0, "defaultThreshold zero"); MorphoTokenisedDeposit vault = MorphoTokenisedDeposit(address(config.erc20)); + morpho = IERC20Metadata(address(vault.rewardToken())); oneShare = 10**vault.decimals(); refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); } @@ -42,4 +44,12 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { -refDecimals ); } + + /// Claim rewards earned by holding a balance of the ERC20 token + /// @custom:delegate-call + function claimRewards() external virtual override(Asset, IRewardable) { + uint256 bal = morpho.balanceOf(address(this)); + IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(morpho, morpho.balanceOf(address(this)) - bal); + } } diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol index b9b815ed48..7deb645d81 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -17,6 +17,9 @@ import "./StargateRewardableWrapper.sol"; contract StargatePoolFiatCollateral is AppreciatingFiatCollateral { IStargatePool private immutable pool; + IERC20 private immutable stg; + + /// @param config.erc20 StargateRewardableWrapper /// @param config.chainlinkFeed Feed units: {UoA/ref} // solhint-disable no-empty-blocks constructor(CollateralConfig memory config, uint192 revenueHiding) @@ -24,6 +27,7 @@ contract StargatePoolFiatCollateral is AppreciatingFiatCollateral { { require(config.defaultThreshold > 0, "defaultThreshold zero"); pool = StargateRewardableWrapper(address(config.erc20)).pool(); + stg = StargateRewardableWrapper(address(config.erc20)).rewardToken(); } /// @return _rate {ref/tok} Quantity of whole reference units per whole collateral tokens @@ -38,6 +42,8 @@ contract StargatePoolFiatCollateral is AppreciatingFiatCollateral { } function claimRewards() external override(Asset, IRewardable) { - StargateRewardableWrapper(address(erc20)).claimRewards(); + uint256 bal = stg.balanceOf(address(this)); + IRewardable(address(erc20)).claimRewards(); + emit RewardsClaimed(stg, stg.balanceOf(address(this)) - bal); } } diff --git a/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol index d3b0d1b836..7644f58759 100644 --- a/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol +++ b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol @@ -83,7 +83,7 @@ contract YearnV2CurveFiatCollateral is CurveStableCollateral { // solhint-disable no-empty-blocks - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external virtual override { // No rewards to claim, everything is part of the pricePerShare } diff --git a/contracts/plugins/mocks/CTokenMock.sol b/contracts/plugins/mocks/CTokenMock.sol index a0d4f0b562..0d31d5666e 100644 --- a/contracts/plugins/mocks/CTokenMock.sol +++ b/contracts/plugins/mocks/CTokenMock.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; +import "../assets/compoundv2/ICToken.sol"; import "./ERC20Mock.sol"; contract CTokenMock is ERC20Mock { @@ -11,15 +12,20 @@ contract CTokenMock is ERC20Mock { uint256 internal _exchangeRate; - bool public revertExchangeRate; + bool public revertExchangeRateCurrent; + bool public revertExchangeRateStored; + + IComptroller public immutable comptroller; constructor( string memory name, string memory symbol, - address underlyingToken + address underlyingToken, + IComptroller _comptroller ) ERC20Mock(name, symbol) { _underlyingToken = underlyingToken; _exchangeRate = _toExchangeRate(FIX_ONE); + comptroller = _comptroller; } function decimals() public pure override returns (uint8) { @@ -27,7 +33,7 @@ contract CTokenMock is ERC20Mock { } function exchangeRateCurrent() external returns (uint256) { - if (revertExchangeRate) { + if (revertExchangeRateCurrent) { revert("reverting exchange rate current"); } _exchangeRate = _exchangeRate; // just to avoid sol warning @@ -35,6 +41,9 @@ contract CTokenMock is ERC20Mock { } function exchangeRateStored() external view returns (uint256) { + if (revertExchangeRateStored) { + revert("reverting exchange rate stored"); + } return _exchangeRate; } @@ -54,7 +63,11 @@ contract CTokenMock is ERC20Mock { return fiatcoinRedemptionRate.shiftl(leftShift).mul_toUint(start); } - function setRevertExchangeRate(bool newVal) external { - revertExchangeRate = newVal; + function setRevertExchangeRateCurrent(bool newVal) external { + revertExchangeRateCurrent = newVal; + } + + function setRevertExchangeRateStored(bool newVal) external { + revertExchangeRateStored = newVal; } } diff --git a/contracts/plugins/mocks/CTokenWrapperMock.sol b/contracts/plugins/mocks/CTokenWrapperMock.sol deleted file mode 100644 index 78a93b44af..0000000000 --- a/contracts/plugins/mocks/CTokenWrapperMock.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "../assets/compoundv2/CTokenWrapper.sol"; -import "../assets/compoundv2/ICToken.sol"; -import "./CTokenMock.sol"; - -contract CTokenWrapperMock is ERC20Mock, IRewardable { - ERC20Mock public comp; - CTokenMock public underlying; - IComptroller public comptroller; - - bool public revertClaimRewards; - - constructor( - string memory _name, - string memory _symbol, - address _underlyingToken, - ERC20Mock _comp, - IComptroller _comptroller - ) ERC20Mock(_name, _symbol) { - underlying = new CTokenMock("cToken Mock", "cMOCK", _underlyingToken); - comp = _comp; - comptroller = _comptroller; - } - - function decimals() public pure override returns (uint8) { - return 8; - } - - function exchangeRateCurrent() external returns (uint256) { - return underlying.exchangeRateCurrent(); - } - - function exchangeRateStored() external view returns (uint256) { - return underlying.exchangeRateStored(); - } - - function claimRewards() external { - if (revertClaimRewards) { - revert("reverting claim rewards"); - } - uint256 oldBal = comp.balanceOf(msg.sender); - address[] memory holders = new address[](1); - address[] memory cTokens = new address[](1); - holders[0] = msg.sender; - cTokens[0] = address(underlying); - comptroller.claimComp(holders, cTokens, false, true); - emit RewardsClaimed(IERC20(address(comp)), comp.balanceOf(msg.sender) - oldBal); - } - - function setExchangeRate(uint192 fiatcoinRedemptionRate) external { - underlying.setExchangeRate(fiatcoinRedemptionRate); - } - - function setRevertClaimRewards(bool newVal) external { - revertClaimRewards = newVal; - } -} diff --git a/contracts/plugins/mocks/CometMock.sol b/contracts/plugins/mocks/CometMock.sol index 16556c9483..cfd14e1356 100644 --- a/contracts/plugins/mocks/CometMock.sol +++ b/contracts/plugins/mocks/CometMock.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.19; // prettier-ignore contract CometMock { - int256 internal _reserves; address public externalDelegate; struct TotalsBasic { @@ -27,19 +26,10 @@ contract CometMock { uint8 _reserved; } - constructor(int256 reserves_, address delegate) { - _reserves = reserves_; + constructor(address delegate) { externalDelegate = delegate; } - function setReserves(int256 amount) external { - _reserves = amount; - } - - function getReserves() public view returns (int256) { - return _reserves; - } - // solhint-disable-next-line no-empty-blocks function accrueAccount(address account) public {} diff --git a/contracts/plugins/mocks/DutchTradeCallbackReentrantTest.sol b/contracts/plugins/mocks/DutchTradeCallbackReentrantTest.sol index ebf3707347..0191078830 100644 --- a/contracts/plugins/mocks/DutchTradeCallbackReentrantTest.sol +++ b/contracts/plugins/mocks/DutchTradeCallbackReentrantTest.sol @@ -5,8 +5,6 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IDutchTradeCallee, TradeStatus, DutchTrade, ITrading } from "../trading/DutchTrade.sol"; -import "hardhat/console.sol"; - contract DutchTradeCallbackReentrantTest is IDutchTradeCallee { using SafeERC20 for IERC20; @@ -27,8 +25,6 @@ contract DutchTradeCallbackReentrantTest is IDutchTradeCallee { IERC20(buyToken).safeTransfer(msg.sender, buyAmount); - console.log("canSettle", _currentTrade.canSettle()); - _trader.settleTrade(_currentTrade.sell()); // _currentTrade.canSettle(); diff --git a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol index a1f8bdbb38..dfd5197c20 100644 --- a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol +++ b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol @@ -9,7 +9,7 @@ contract InvalidATokenFiatCollateralMock is ATokenFiatCollateral { {} /// Reverting claimRewards function - /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + /// @custom:delegate-call function claimRewards() external pure override { revert("claimRewards() error"); } diff --git a/contracts/plugins/mocks/InvalidChainlinkMock.sol b/contracts/plugins/mocks/InvalidChainlinkMock.sol index c2d0823e99..02246c64e7 100644 --- a/contracts/plugins/mocks/InvalidChainlinkMock.sol +++ b/contracts/plugins/mocks/InvalidChainlinkMock.sol @@ -11,10 +11,12 @@ import "./ChainlinkMock.sol"; */ contract InvalidMockV3Aggregator is MockV3Aggregator { bool public simplyRevert; + bool public revertWithExplicitError; - constructor(uint8 _decimals, int256 _initialAnswer) - MockV3Aggregator(_decimals, _initialAnswer) - {} + constructor( + uint8 _decimals, + int256 _initialAnswer + ) MockV3Aggregator(_decimals, _initialAnswer) {} function latestRoundData() external @@ -30,6 +32,8 @@ contract InvalidMockV3Aggregator is MockV3Aggregator { { if (simplyRevert) { revert(); // Revert with no reason + } else if (revertWithExplicitError) { + revert("oracle explicit error"); // Revert with explicit reason } else { // Run out of gas this.infiniteLoop{ gas: 10 }(); @@ -47,6 +51,10 @@ contract InvalidMockV3Aggregator is MockV3Aggregator { simplyRevert = on; } + function setRevertWithExplicitError(bool on) external { + revertWithExplicitError = on; + } + function infiniteLoop() external pure { uint256 i = 0; uint256[1] memory array; diff --git a/contracts/plugins/mocks/RevertingFacetMock.sol b/contracts/plugins/mocks/RevertingFacetMock.sol new file mode 100644 index 0000000000..772f6fbdb6 --- /dev/null +++ b/contracts/plugins/mocks/RevertingFacetMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +contract RevertingFacetMock { + constructor() {} + + fallback() external { + revert("RevertingFacetMock"); + } +} diff --git a/contracts/plugins/mocks/UnpricedPlugins.sol b/contracts/plugins/mocks/UnpricedPlugins.sol index cc2841f2e6..4d2d20bda8 100644 --- a/contracts/plugins/mocks/UnpricedPlugins.sol +++ b/contracts/plugins/mocks/UnpricedPlugins.sol @@ -27,23 +27,51 @@ contract UnpricedAssetMock is Asset { uint48 oracleTimeout_ ) Asset(priceTimeout_, chainlinkFeed_, oracleError_, erc20_, maxTradeVolume_, oracleTimeout_) {} + /// tryPrice: mock unpriced by returning (0, FIX_MAX) + function tryPrice() external view override returns (uint192 low, uint192 high, uint192) { + // If unpriced is marked, return 0, FIX_MAX + if (unpriced) return (0, FIX_MAX, 0); + + uint192 p = chainlinkFeed.price(oracleTimeout); // {UoA/tok} + uint192 delta = p.mul(oracleError, CEIL); + return (p - delta, p + delta, 0); + } + + function setUnpriced(bool on) external { + unpriced = on; + } +} + +contract UnpricedFiatCollateralMock is FiatCollateral { + using FixLib for uint192; + using OracleLib for AggregatorV3Interface; + + bool public unpriced = false; + + // solhint-disable no-empty-blocks + + constructor(CollateralConfig memory config) FiatCollateral(config) {} + /// tryPrice: mock unpriced by returning (0, FIX_MAX) function tryPrice() external view + virtual override - returns ( - uint192 low, - uint192 high, - uint192 - ) + returns (uint192 low, uint192 high, uint192 pegPrice) { // If unpriced is marked, return 0, FIX_MAX if (unpriced) return (0, FIX_MAX, 0); - uint192 p = chainlinkFeed.price(oracleTimeout); // {UoA/tok} - uint192 delta = p.mul(oracleError, CEIL); - return (p - delta, p + delta, 0); + // {target/ref} = {UoA/ref} / {UoA/target} (1) + pegPrice = chainlinkFeed.price(oracleTimeout); + + // {target/ref} = {target/ref} * {1} + uint192 err = pegPrice.mul(oracleError, CEIL); + + low = pegPrice - err; + high = pegPrice + err; + // assert(low <= high); obviously true just by inspection } function setUnpriced(bool on) external { @@ -62,9 +90,10 @@ contract UnpricedAppreciatingFiatCollateralMock is AppreciatingFiatCollateral { // solhint-disable no-empty-blocks /// @param config.chainlinkFeed Feed units: {UoA/ref} - constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) - {} + constructor( + CollateralConfig memory config, + uint192 revenueHiding + ) AppreciatingFiatCollateral(config, revenueHiding) {} /// tryPrice: mock unpriced by returning (0, FIX_MAX) function tryPrice() @@ -72,11 +101,7 @@ contract UnpricedAppreciatingFiatCollateralMock is AppreciatingFiatCollateral { view virtual override - returns ( - uint192 low, - uint192 high, - uint192 pegPrice - ) + returns (uint192 low, uint192 high, uint192 pegPrice) { // If unpriced is marked, return 0, FIX_MAX if (unpriced) return (0, FIX_MAX, 0); diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 972aeecfd7..01eb92a4d1 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -8,6 +8,7 @@ import "../../libraries/NetworkConfigLib.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/IBroker.sol"; import "../../interfaces/ITrade.sol"; +import "../../mixins/Versioned.sol"; interface IDutchTradeCallee { function dutchTradeCallback( @@ -83,7 +84,7 @@ uint192 constant ONE_POINT_FIVE = 150e16; // {1} 1.5 * 3. Wait until the desired block is reached (hopefully not in the first 20% of the auction) * 4. Call bid() */ -contract DutchTrade is ITrade { +contract DutchTrade is ITrade, Versioned { using FixLib for uint192; using SafeERC20 for IERC20Metadata; diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index c494ecee57..8245791468 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -9,11 +9,12 @@ import "../../libraries/Fixed.sol"; import "../../interfaces/IBroker.sol"; import "../../interfaces/IGnosis.sol"; import "../../interfaces/ITrade.sol"; +import "../../mixins/Versioned.sol"; // Modifications to this contract's state must only ever be made when status=PENDING! /// Trade contract against the Gnosis EasyAuction mechanism -contract GnosisTrade is ITrade { +contract GnosisTrade is ITrade, Versioned { using FixLib for uint192; using SafeERC20Upgradeable for IERC20Upgradeable; diff --git a/docs/plugin-addresses.md b/docs/plugin-addresses.md index 15c2b973f5..9d697e3cec 100644 --- a/docs/plugin-addresses.md +++ b/docs/plugin-addresses.md @@ -19,7 +19,6 @@ Following are the addresses and configuration parameters of collateral plugins d | [USDC](https://etherscan.io/address/0xBE9D23040fe22E8Bd8A88BF5101061557355cA04) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | | [USDT](https://etherscan.io/address/0x58D7bF13D3572b08dE5d96373b8097d94B1325ad) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0xdAC17F958D2ee523a2206206994597C13D831ec7](https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7) | | [USDP](https://etherscan.io/address/0x2f98bA77a8ca1c630255c4517b1b3878f6e60C89) | 2.0% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x8E870D67F660D95d5be530380D0eC0bd388289E1](https://etherscan.io/address/0x8E870D67F660D95d5be530380D0eC0bd388289E1) | -| [TUSD](https://etherscan.io/address/0x7F9999B2C9D310a5f48dfD070eb5129e1e8565E2) | 1.3% | 24 | [0xec746eCF986E2927Abd291a2A1716c940100f8Ba](https://etherscan.io/address/0xec746eCF986E2927Abd291a2A1716c940100f8Ba) | [0x0000000000085d4780B73119b644AE5ecd22b376](https://etherscan.io/address/0x0000000000085d4780B73119b644AE5ecd22b376) | | [BUSD](https://etherscan.io/address/0xCBcd605088D5A5Da9ceEb3618bc01BFB87387423) | 1.5% | 24 | [0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A](https://etherscan.io/address/0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A) | [0x4Fabb145d64652a948d72533023f6E7A623C7C53](https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53) | | [aDAI](https://etherscan.io/address/0x256b89658bD831CC40283F42e85B1fa8973Db0c9) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985](https://etherscan.io/address/0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985) | | [aUSDC](https://etherscan.io/address/0x7cd9ca6401f743b38b3b16ea314bbab8e9c1ac51) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x60C384e226b120d93f3e0F4C502957b2B9C32B15](https://etherscan.io/address/0x60C384e226b120d93f3e0F4C502957b2B9C32B15) | @@ -34,7 +33,6 @@ Following are the addresses and configuration parameters of collateral plugins d | [cETH](https://etherscan.io/address/0x357d4dB0c2179886334cC33B8528048F7E1D3Fe3) | 0.0% | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F](https://etherscan.io/address/0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F) | | [WBTC](https://etherscan.io/address/0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4) | 3.51% | 24 | [0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599](https://etherscan.io/address/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) | | [WETH](https://etherscan.io/address/0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3) | 0.0% | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2](https://etherscan.io/address/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) | -| [EURT](https://etherscan.io/address/0xEBD07CE38e2f46031c982136012472A4D24AE070) | 3.0% | 24 | [0x01D391A48f4F7339aC64CA2c83a07C22F95F587a](https://etherscan.io/address/0x01D391A48f4F7339aC64CA2c83a07C22F95F587a) | [0xC581b735A1688071A1746c968e0798D642EDE491](https://etherscan.io/address/0xC581b735A1688071A1746c968e0798D642EDE491) | | [wstETH](https://etherscan.io/address/0x29F2EB4A0D3dC211BB488E9aBe12740cafBCc49C) | 2.5% | 24 | [0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8](https://etherscan.io/address/0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8) | [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0](https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) | | [rETH](https://etherscan.io/address/0x1103851D1FCDD3f88096fbed812c8FF01949cF9d) | 4.51% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xae78736Cd615f374D3085123A210448E74Fc6393](https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393) | | [fUSDC](https://etherscan.io/address/0x1FFA5955D64Ee32cB1BF7104167b81bb085b0c8d) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x6D05CB2CB647B58189FA16f81784C05B4bcd4fe9](https://etherscan.io/address/0x6D05CB2CB647B58189FA16f81784C05B4bcd4fe9) | diff --git a/hardhat.config.ts b/hardhat.config.ts index 61bac4e916..6c5398b9f1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -25,7 +25,7 @@ const TENDERLY_RPC_URL = useEnv('TENDERLY_RPC_URL') const GOERLI_RPC_URL = useEnv('GOERLI_RPC_URL') const BASE_GOERLI_RPC_URL = useEnv('BASE_GOERLI_RPC_URL') const BASE_RPC_URL = useEnv('BASE_RPC_URL') -const MNEMONIC = useEnv('MNEMONIC') ?? 'test test test test test test test test test test test junk' +const MNEMONIC = useEnv('MNEMONIC') || 'test test test test test test test test test test test junk' const TIMEOUT = useEnv('SLOW') ? 6_000_000 : 600_000 const src_dir = `./contracts/${useEnv('PROTO')}` @@ -44,7 +44,7 @@ const config: HardhatUserConfig = { : undefined, gas: 0x1ffffffff, blockGasLimit: 0x1fffffffffffff, - allowUnlimitedContractSize: true + allowUnlimitedContractSize: true, }, localhost: { // network for long-lived mainnet forks @@ -53,6 +53,7 @@ const config: HardhatUserConfig = { gas: 0x1ffffffff, blockGasLimit: 0x1fffffffffffff, allowUnlimitedContractSize: true, + timeout: 0, }, goerli: { chainId: 5, @@ -110,10 +111,6 @@ const config: HardhatUserConfig = { version: '0.6.12', settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size }, - 'contracts/facade/FacadeRead.sol': { - version: '0.8.19', - settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size - }, }, }, @@ -139,7 +136,7 @@ const config: HardhatUserConfig = { etherscan: { apiKey: { mainnet: useEnv('ETHERSCAN_API_KEY'), - base: useEnv('BASESCAN_API_KEY') + base: useEnv('BASESCAN_API_KEY'), }, customChains: [ { diff --git a/scripts/addresses/base-3.3.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-3.3.0/8453-tmp-assets-collateral.json new file mode 100644 index 0000000000..c03a5cfea0 --- /dev/null +++ b/scripts/addresses/base-3.3.0/8453-tmp-assets-collateral.json @@ -0,0 +1,13 @@ +{ + "assets": {}, + "collateral": { + "saBasUSDC": "0xAe9795115c7E5Bee7d2017b92c41DECa66d81dcf", + "cUSDCv3": "0x36A43E13f0c8d8612AE3978A8E7A58BB58000923", + "USDC": "0x8b906361048D277452506d3f791020A1cA798aF3" + }, + "erc20s": { + "saBasUSDC": "0x6AfFDe0bA2D1f8fde8da8f296e7EfC991D807515", + "cUSDCv3": "0xA694f7177C6c839C951C74C797283B35D0A486c8", + "USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + } +} \ No newline at end of file diff --git a/scripts/addresses/base-3.3.0/8453-tmp-deployments.json b/scripts/addresses/base-3.3.0/8453-tmp-deployments.json new file mode 100644 index 0000000000..66dde25ef3 --- /dev/null +++ b/scripts/addresses/base-3.3.0/8453-tmp-deployments.json @@ -0,0 +1,38 @@ +{ + "prerequisites": { + "RSR": "0xaB36452DbAC151bE02b16Ca17d8919826072f64a", + "RSR_FEED": "0xAa98aE504658766Dfe11F31c5D95a0bdcABDe0b1", + "GNOSIS_EASY_AUCTION": "0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02" + }, + "tradingLib": "", + "cvxMiningLib": "", + "facade": "", + "facets": { + "actFacet": "", + "readFacet": "" + }, + "facadeWriteLib": "", + "basketLib": "", + "facadeWrite": "", + "deployer": "", + "rsrAsset": "", + "implementations": { + "main": "", + "trading": { + "gnosisTrade": "", + "dutchTrade": "0xd0Ff3aa130A34Eac0C448950CA8fe662330cB065" + }, + "components": { + "assetRegistry": "", + "backingManager": "", + "basketHandler": "", + "broker": "", + "distributor": "", + "furnace": "", + "rsrTrader": "", + "rTokenTrader": "", + "rToken": "", + "stRSR": "" + } + } +} diff --git a/scripts/addresses/mainnet-3.3.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-3.3.0/1-tmp-assets-collateral.json new file mode 100644 index 0000000000..99aaaa7cc6 --- /dev/null +++ b/scripts/addresses/mainnet-3.3.0/1-tmp-assets-collateral.json @@ -0,0 +1,36 @@ +{ + "assets": { + "COMP": "0x29dc6F79750020d77c6391629101BDC0F0D16ECB", + "CRV": "0x9257a1307a72603B7916d0c97fCABC6351C3482E", + "CVX": "0x4dA79d89482737381E90d2A7005b21cd11eAeE5C" + }, + "collateral": { + "aUSDC": "0x6E14943224d6E4F7607943512ba17DbBA9524B8e", + "aUSDT": "0x8AD3055286f4E59B399616Bd6BEfE24F64573928", + "wstETH": "0x3519918E2918b59f3b29bed16dC77174DEC6707b", + "rETH": "0xEdd8d4Cc0d0358a12f232fd72821d25d4EbE7704", + "cUSDCv3": "0xf0Fb23485057Fd88C80B9CEc8b433FdA47e0a07A", + "cvxeUSDFRAXBP": "0x5cD176b58a6FdBAa1aEFD0921935a730C62f03Ac", + "sDAI": "0x29EDbbbE7415cb8637e0F62D5d19dcB3A5bC3229", + "saEthUSDC": "0x05beee046A5C28844804E679aD5587046dBffbc0", + "cUSDT": "0x1269BFa56EcaE9D6d5003810D4a35bf8479376b8", + "saEthPyUSD": "0xe176A5ebFB873D5b3cf1909d0EdaE4FE095F5bc7", + "cvxPayPool": "0x426Ad39C7ccF2f3872aBB16c0291Eb40c0F44D23" + }, + "erc20s": { + "aUSDC": "0x60C384e226b120d93f3e0F4C502957b2B9C32B15", + "aUSDT": "0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9", + "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", + "cUSDCv3": "0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A", + "cvxeUSDFRAXBP": "0x8e33D5aC344f9F2fc1f2670D45194C280d4fBcF1", + "sDAI": "0x83f20f44975d03b1b09e64809b757c47f942beea", + "saEthUSDC": "0x093cB4f405924a0C468b43209d5E466F1dd0aC7d", + "cUSDT": "0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9", + "saEthPyUSD": "0x8d6E0402A3E3aD1b43575b05905F9468447013cF", + "cvxPayPool": "0x6Cd8b88Dd65B004A82C33276C7AD3Fd4F569e254", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B" + } +} \ No newline at end of file diff --git a/scripts/addresses/mainnet-3.3.0/1-tmp-deployments.json b/scripts/addresses/mainnet-3.3.0/1-tmp-deployments.json new file mode 100644 index 0000000000..25a8b16438 --- /dev/null +++ b/scripts/addresses/mainnet-3.3.0/1-tmp-deployments.json @@ -0,0 +1,37 @@ +{ + "prerequisites": { + "RSR": "0x320623b8e4ff03373931769a31fc52a4e78b5d70", + "RSR_FEED": "0x759bBC1be8F90eE6457C44abc7d443842a976d02", + "GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101" + }, + "tradingLib": "", + "basketLib": "", + "facadeWriteLib": "", + "facadeWrite": "", + "facade": "0x2C7ca56342177343A2954C250702Fd464f4d0613", + "facets": { + "actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB", + "readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526" + }, + "deployer": "", + "rsrAsset": "", + "implementations": { + "main": "", + "trading": { + "gnosisTrade": "0x803a52c5DAB69B78419bb160051071eF2F9Fd227", + "dutchTrade": "0x4eDEb80Ce684A890Dd58Ae0d9762C38731b11b99" + }, + "components": { + "assetRegistry": "", + "backingManager": "", + "basketHandler": "", + "broker": "", + "distributor": "", + "furnace": "", + "rsrTrader": "", + "rTokenTrader": "", + "rToken": "", + "stRSR": "" + } + } +} diff --git a/scripts/deploy.ts b/scripts/deploy.ts index e2916e7d00..9c69d0f456 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -25,20 +25,26 @@ async function main() { // Part 1/3 of the *overall* deployment process: Deploy all contracts // See `confirm.ts` for part 2 - // Phase 1- Implementations + // Phase 1 -- Implementations const scripts = [ - 'phase1-common/0_setup_deployments.ts', - 'phase1-common/1_deploy_libraries.ts', - 'phase1-common/2_deploy_implementations.ts', - 'phase1-common/3_deploy_rsrAsset.ts', - 'phase1-common/4_deploy_facade.ts', - 'phase1-common/5_deploy_deployer.ts', - 'phase1-common/6_deploy_facadeWrite.ts', - 'phase1-common/7_deploy_facadeAct.ts', + 'phase1-core/0_setup_deployments.ts', + 'phase1-core/1_deploy_libraries.ts', + 'phase1-core/2_deploy_implementations.ts', + 'phase1-core/3_deploy_rsrAsset.ts', + 'phase1-core/4_deploy_facade.ts', // comment this out before deployment to keep old Facade + 'phase1-core/5_deploy_deployer.ts', + 'phase1-core/6_deploy_facadeWrite.ts', ] // ============================================= + // Phase 1.5 -- Facets + // To update the existing Facade, add new facets to the below list + + scripts.push('phase1-facade/1_deploy_readFacet.ts', 'phase1-facade/2_deploy_actFacet.ts') + + // ============================================= + // Phase 2 - Assets/Collateral if (!baseL2Chains.includes(hre.network.name)) { scripts.push( @@ -47,12 +53,13 @@ async function main() { 'phase2-assets/assets/deploy_crv.ts', 'phase2-assets/assets/deploy_cvx.ts', 'phase2-assets/2_deploy_collateral.ts', - 'phase2-assets/collaterals/deploy_compound_v2_collateral.ts', 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', 'phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts', 'phase2-assets/collaterals/deploy_flux_finance_collateral.ts', 'phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts', - 'phase2-assets/collaterals/deploy_convex_stable_plugin.ts', + 'phase2-assets/collaterals/deploy_convex_3pool_collateral.ts', + 'phase2-assets/collaterals/deploy_convex_paypool_collateral.ts', + 'phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts', 'phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts', 'phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts', 'phase2-assets/collaterals/deploy_curve_stable_plugin.ts', @@ -62,9 +69,11 @@ async function main() { 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', 'phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts', 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', + 'phase2-assets/collaterals/deploy_aave_v3_pyusd.ts', 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts', 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts', - 'phase2-assets/collaterals/deploy_sfrax.ts' + 'phase2-assets/collaterals/deploy_sfrax.ts', + 'phase2-assets/collaterals/deploy_sfrax_eth.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -74,7 +83,7 @@ async function main() { 'phase2-assets/2_deploy_collateral.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', 'phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts', - 'phase2-assets/collaterals/deploy_aave_v3_usdbc.ts', + 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', 'phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts', 'phase2-assets/assets/deploy_stg.ts' ) diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index a67b131915..a4fba21eb4 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -9,15 +9,20 @@ export interface IPrerequisites { GNOSIS_EASY_AUCTION: string } +export interface IFacets { + actFacet: string + readFacet: string +} + export interface IDeployments { prerequisites: IPrerequisites tradingLib: string basketLib: string - facadeRead: string + facade: string + facets: IFacets facadeWriteLib: string cvxMiningLib: string facadeWrite: string - facadeAct: string deployer: string rsrAsset: string implementations: IImplementations diff --git a/scripts/deployment/phase1-common/0_setup_deployments.ts b/scripts/deployment/phase1-core/0_setup_deployments.ts similarity index 97% rename from scripts/deployment/phase1-common/0_setup_deployments.ts rename to scripts/deployment/phase1-core/0_setup_deployments.ts index d9c71ff769..9cddc68e88 100644 --- a/scripts/deployment/phase1-common/0_setup_deployments.ts +++ b/scripts/deployment/phase1-core/0_setup_deployments.ts @@ -55,8 +55,11 @@ async function main() { }, tradingLib: '', cvxMiningLib: '', - facadeRead: '', - facadeAct: '', + facade: '', + facets: { + actFacet: '', + readFacet: '', + }, facadeWriteLib: '', basketLib: '', facadeWrite: '', diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-core/1_deploy_libraries.ts similarity index 100% rename from scripts/deployment/phase1-common/1_deploy_libraries.ts rename to scripts/deployment/phase1-core/1_deploy_libraries.ts diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-core/2_deploy_implementations.ts similarity index 100% rename from scripts/deployment/phase1-common/2_deploy_implementations.ts rename to scripts/deployment/phase1-core/2_deploy_implementations.ts diff --git a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-core/3_deploy_rsrAsset.ts similarity index 94% rename from scripts/deployment/phase1-common/3_deploy_rsrAsset.ts rename to scripts/deployment/phase1-core/3_deploy_rsrAsset.ts index f33da05e81..5d749afa10 100644 --- a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts +++ b/scripts/deployment/phase1-core/3_deploy_rsrAsset.ts @@ -5,8 +5,8 @@ import { getChainId } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' import { ZERO_ADDRESS } from '../../../common/constants' import { fp } from '../../../common/numbers' -import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../../deployment/common' -import { priceTimeout, validateImplementations } from '../../deployment/utils' +import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' +import { priceTimeout, validateImplementations } from '../utils' import { Asset } from '../../../typechain' let rsrAsset: Asset diff --git a/scripts/deployment/phase1-common/4_deploy_facade.ts b/scripts/deployment/phase1-core/4_deploy_facade.ts similarity index 82% rename from scripts/deployment/phase1-common/4_deploy_facade.ts rename to scripts/deployment/phase1-core/4_deploy_facade.ts index 249012bfc5..c23b86c665 100644 --- a/scripts/deployment/phase1-common/4_deploy_facade.ts +++ b/scripts/deployment/phase1-core/4_deploy_facade.ts @@ -5,9 +5,9 @@ import { getChainId, isValidContract } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' import { validateImplementations } from '../utils' -import { FacadeRead } from '../../../typechain' +import { Facade } from '../../../typechain' -let facadeRead: FacadeRead +let facade: Facade async function main() { // ==== Read Configuration ==== @@ -35,16 +35,16 @@ async function main() { // ******************** Deploy Facade ****************************************/ - const FacadeFactory = await ethers.getContractFactory('FacadeRead') - facadeRead = await FacadeFactory.connect(burner).deploy() - await facadeRead.deployed() + const FacadeFactory = await ethers.getContractFactory('Facade') + facade = await FacadeFactory.connect(burner).deploy() + await facade.deployed() // Write temporary deployments file - deployments.facadeRead = facadeRead.address + deployments.facade = facade.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) console.log(`Deployed to ${hre.network.name} (${chainId}) - Facade: ${facadeRead.address} + Facade: ${facade.address} Deployment file: ${deploymentFilename}`) } diff --git a/scripts/deployment/phase1-common/5_deploy_deployer.ts b/scripts/deployment/phase1-core/5_deploy_deployer.ts similarity index 95% rename from scripts/deployment/phase1-common/5_deploy_deployer.ts rename to scripts/deployment/phase1-core/5_deploy_deployer.ts index af17f2dad2..63448e2539 100644 --- a/scripts/deployment/phase1-common/5_deploy_deployer.ts +++ b/scripts/deployment/phase1-core/5_deploy_deployer.ts @@ -29,9 +29,9 @@ async function main() { await validateImplementations(deployments) // Check previous step executed - if (!deployments.facadeRead) { + if (!deployments.facade) { throw new Error(`Missing deployed contracts in network ${hre.network.name}`) - } else if (!(await isValidContract(hre, deployments.facadeRead))) { + } else if (!(await isValidContract(hre, deployments.facade))) { throw new Error(`Facade contract not found in network ${hre.network.name}`) } diff --git a/scripts/deployment/phase1-common/6_deploy_facadeWrite.ts b/scripts/deployment/phase1-core/6_deploy_facadeWrite.ts similarity index 100% rename from scripts/deployment/phase1-common/6_deploy_facadeWrite.ts rename to scripts/deployment/phase1-core/6_deploy_facadeWrite.ts diff --git a/scripts/deployment/phase1-common/7_deploy_facadeAct.ts b/scripts/deployment/phase1-facade/1_deploy_readFacet.ts similarity index 54% rename from scripts/deployment/phase1-common/7_deploy_facadeAct.ts rename to scripts/deployment/phase1-facade/1_deploy_readFacet.ts index 51adf6a105..04e5ec5e53 100644 --- a/scripts/deployment/phase1-common/7_deploy_facadeAct.ts +++ b/scripts/deployment/phase1-facade/1_deploy_readFacet.ts @@ -4,10 +4,9 @@ import hre, { ethers } from 'hardhat' import { getChainId, isValidContract } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' -import { validateImplementations } from '../utils' -import { FacadeAct } from '../../../typechain' +import { ReadFacet } from '../../../typechain' -let facadeAct: FacadeAct +let readFacet: ReadFacet async function main() { // ==== Read Configuration ==== @@ -24,30 +23,40 @@ async function main() { const deploymentFilename = getDeploymentFilename(chainId) const deployments = getDeploymentFile(deploymentFilename) - await validateImplementations(deployments) - - // Check previous step executed - if (!deployments.rsrAsset) { + // Check facade exists + if (!deployments.facade) { throw new Error(`Missing deployed contracts in network ${hre.network.name}`) - } else if (!(await isValidContract(hre, deployments.rsrAsset))) { - throw new Error(`RSR Asset contract not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.facade))) { + throw new Error(`Facade contract not found in network ${hre.network.name}`) } - // ******************** Deploy FacadeAct ****************************************/ - - // Deploy FacadeAct - const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + // ******************** Deploy ReadFacet ****************************************/ - facadeAct = await FacadeActFactory.connect(burner).deploy() - await facadeAct.deployed() + // Deploy ReadFacet + const ReadFacetFactory = await ethers.getContractFactory('ReadFacet') + readFacet = await ReadFacetFactory.connect(burner).deploy() + await readFacet.deployed() // Write temporary deployments file - deployments.facadeAct = facadeAct.address + deployments.facets.readFacet = readFacet.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) console.log(`Deployed to ${hre.network.name} (${chainId}) - FacadeAct: ${facadeAct.address} + ReadFacet: ${readFacet.address} Deployment file: ${deploymentFilename}`) + + // ******************** Save to Facade ****************************************/ + + console.log('Configuring with Facade...') + + // Save ReadFacet to Facade + const facade = await ethers.getContractAt('Facade', deployments.facade) + await facade.save( + readFacet.address, + Object.entries(readFacet.functions).map(([fn]) => readFacet.interface.getSighash(fn)) + ) + + console.log('Finished saving to Facade') } main().catch((error) => { diff --git a/scripts/deployment/phase1-facade/2_deploy_actFacet.ts b/scripts/deployment/phase1-facade/2_deploy_actFacet.ts new file mode 100644 index 0000000000..97ee29342a --- /dev/null +++ b/scripts/deployment/phase1-facade/2_deploy_actFacet.ts @@ -0,0 +1,65 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' + +import { getChainId, isValidContract } from '../../../common/blockchain-utils' +import { networkConfig } from '../../../common/configuration' +import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' +import { ActFacet } from '../../../typechain' + +let actFacet: ActFacet + +async function main() { + // ==== Read Configuration ==== + const [burner] = await hre.ethers.getSigners() + const chainId = await getChainId(hre) + + console.log(`Deploying Facade to network ${hre.network.name} (${chainId}) + with burner account: ${burner.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + const deploymentFilename = getDeploymentFilename(chainId) + const deployments = getDeploymentFile(deploymentFilename) + + // Check facade exists + if (!deployments.facade) { + throw new Error(`Missing deployed contracts in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.facade))) { + throw new Error(`Facade contract not found in network ${hre.network.name}`) + } + + // ******************** Deploy ActFacet ****************************************/ + + // Deploy ActFacet + const ActFacetFactory = await ethers.getContractFactory('ActFacet') + actFacet = await ActFacetFactory.connect(burner).deploy() + await actFacet.deployed() + + // Write temporary deployments file + deployments.facets.actFacet = actFacet.address + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + + console.log(`Deployed to ${hre.network.name} (${chainId}) + ActFacet: ${actFacet.address} + Deployment file: ${deploymentFilename}`) + + // ******************** Save to Facade ****************************************/ + + console.log('Configuring with Facade...') + + // Save ReadFacet to Facade + const facade = await ethers.getContractAt('Facade', deployments.facade) + await facade.save( + actFacet.address, + Object.entries(actFacet.functions).map(([fn]) => actFacet.interface.getSighash(fn)) + ) + + console.log('Finished saving to Facade') +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index 5a58c3bea8..98f69a75ef 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -148,29 +148,6 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) } - /******** Deploy Fiat Collateral - TUSD **************************/ - if (networkConfig[chainId].tokens.TUSD && networkConfig[chainId].chainlinkFeeds.TUSD) { - const { collateral: tusdCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.TUSD, - oracleError: fp('0.003').toString(), // 0.3% - tokenAddress: networkConfig[chainId].tokens.TUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.013').toString(), // 1.3% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', tusdCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.TUSD = tusdCollateral - assetCollDeployments.erc20s.TUSD = networkConfig[chainId].tokens.TUSD - deployedCollateral.push(tusdCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - } /******** Deploy Fiat Collateral - BUSD **************************/ if (networkConfig[chainId].tokens.BUSD && networkConfig[chainId].chainlinkFeeds.BUSD) { const { collateral: busdCollateral } = await hre.run('deploy-fiat-collateral', { @@ -448,27 +425,11 @@ async function main() { /*** Compound V2 not available in Base L2s */ if (!baseL2Chains.includes(hre.network.name)) { /******** Deploy CToken Fiat Collateral - cDAI **************************/ - const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') - const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) - - const cDaiVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cDAI!, - `${await cDai.name()} Vault`, - `${await cDai.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cDaiVault.deployed() - - console.log( - `Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} ` - ) - const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, oracleError: fp('0.0025').toString(), // 0.25% - cToken: cDaiVault.address, + cToken: networkConfig[chainId].tokens.cDAI, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -481,32 +442,17 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cDAI = cDaiCollateral - assetCollDeployments.erc20s.cDAI = cDaiVault.address + assetCollDeployments.erc20s.cDAI = networkConfig[chainId].tokens.cDAI deployedCollateral.push(cDaiCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDC **************************/ - const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) - - const cUsdcVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDC!, - `${await cUsdc.name()} Vault`, - `${await cUsdc.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdcVault.deployed() - - console.log( - `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` - ) - const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdcVault.address, + cToken: networkConfig[chainId].tokens.cUSDC, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -519,32 +465,17 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDC = cUsdcCollateral - assetCollDeployments.erc20s.cUSDC = cUsdcVault.address + assetCollDeployments.erc20s.cUSDC = networkConfig[chainId].tokens.cUSDC deployedCollateral.push(cUsdcCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDT **************************/ - const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) - - const cUsdtVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDT!, - `${await cUsdt.name()} Vault`, - `${await cUsdt.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdtVault.deployed() - - console.log( - `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` - ) - const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdtVault.address, + cToken: networkConfig[chainId].tokens.cUSDT, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -557,32 +488,17 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDT = cUsdtCollateral - assetCollDeployments.erc20s.cUSDT = cUsdtVault.address + assetCollDeployments.erc20s.cUSDT = networkConfig[chainId].tokens.cUSDT deployedCollateral.push(cUsdtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDP **************************/ - const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) - - const cUsdpVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDP!, - `${await cUsdp.name()} Vault`, - `${await cUsdp.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdpVault.deployed() - - console.log( - `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` - ) - const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, oracleError: fp('0.01').toString(), // 1% - cToken: cUsdpVault.address, + cToken: networkConfig[chainId].tokens.cUSDP, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -595,33 +511,18 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDP = cUsdpCollateral - assetCollDeployments.erc20s.cUSDP = cUsdpVault.address + assetCollDeployments.erc20s.cUSDP = networkConfig[chainId].tokens.cUSDP deployedCollateral.push(cUsdpCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ - const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) - - const cWBTCVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cWBTC!, - `${await cWBTC.name()} Vault`, - `${await cWBTC.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cWBTCVault.deployed() - - console.log( - `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` - ) - const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { priceTimeout: priceTimeout.toString(), referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, combinedOracleError: combinedBTCWBTCError.toString(), - cToken: cWBTCVault.address, + cToken: networkConfig[chainId].tokens.cWBTC, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '86400', // 24 hr targetUnitOracleTimeout: '3600', // 1 hr @@ -635,34 +536,19 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cWBTC = cWBTCCollateral - assetCollDeployments.erc20s.cWBTC = cWBTCVault.address + assetCollDeployments.erc20s.cWBTC = networkConfig[chainId].tokens.cWBTC deployedCollateral.push(cWBTCCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Self-Referential Collateral - cETH **************************/ - const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) - - const cETHVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cETH!, - `${await cETH.name()} Vault`, - `${await cETH.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cETHVault.deployed() - - console.log( - `Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} ` - ) - const { collateral: cETHCollateral } = await hre.run( 'deploy-ctoken-selfreferential-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, oracleError: fp('0.005').toString(), // 0.5% - cToken: cETHVault.address, + cToken: networkConfig[chainId].tokens.cETH, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), @@ -675,7 +561,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cETH = cETHCollateral - assetCollDeployments.erc20s.cETH = cETHVault.address + assetCollDeployments.erc20s.cETH = networkConfig[chainId].tokens.cETH deployedCollateral.push(cETHCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -736,38 +622,6 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) } - /******** Deploy EUR Fiat Collateral - EURT **************************/ - const eurtError = fp('0.02') // 2% - - if ( - networkConfig[chainId].tokens.EURT && - networkConfig[chainId].chainlinkFeeds.EUR && - networkConfig[chainId].chainlinkFeeds.EURT - ) { - const { collateral: eurtCollateral } = await hre.run('deploy-eurfiat-collateral', { - priceTimeout: priceTimeout.toString(), - referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.EURT, - targetUnitFeed: networkConfig[chainId].chainlinkFeeds.EUR, - oracleError: eurtError.toString(), // 2% - tokenAddress: networkConfig[chainId].tokens.EURT, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetUnitOracleTimeout: '86400', // 24 hr - targetName: ethers.utils.formatBytes32String('EUR'), - defaultThreshold: fp('0.03').toString(), // 3% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', eurtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.EURT = eurtCollateral - assetCollDeployments.erc20s.EURT = networkConfig[chainId].tokens.EURT - deployedCollateral.push(eurtCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - } - console.log(`Deployed collateral to ${hre.network.name} (${chainId}) New deployments: ${deployedCollateral} Deployment file: ${assetCollDeploymentFilename}`) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_pyusd.ts similarity index 72% rename from scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts rename to scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_pyusd.ts index f930201a21..27944c095f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_pyusd.ts @@ -14,8 +14,13 @@ import { import { bn, fp } from '#/common/numbers' import { AaveV3FiatCollateral } from '../../../../typechain' import { priceTimeout, revenueHiding } from '../../utils' +import { + PYUSD_MAX_TRADE_VOLUME, + PYUSD_ORACLE_TIMEOUT, + PYUSD_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aave-v3/constants' -// This file specifically deploys Aave V3 USDC collateral +// This file specifically deploys Aave V3 pyUSD collateral on Mainnet async function main() { // ==== Read Configuration ==== @@ -30,9 +35,9 @@ async function main() { throw new Error(`Missing network configuration for ${hre.network.name}`) } - // Only exists on Base L2 - if (!baseL2Chains.includes(hre.network.name)) { - throw new Error(`Invalid network ${hre.network.name} - only available on Base`) + // Only exists on Mainnet + if (baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Mainnet`) } // Get phase1 deployment @@ -47,9 +52,11 @@ async function main() { const deployedCollateral: string[] = [] - /******** Deploy Aave V3 USDbC wrapper **************************/ - + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') const StaticATokenFactory = await hre.ethers.getContractFactory('StaticATokenV3LM') + + /******** Deploy Aave V3 pyUSD ERC20 **************************/ + const erc20 = await StaticATokenFactory.deploy( networkConfig[chainId].AAVE_V3_POOL!, networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! @@ -57,44 +64,39 @@ async function main() { await erc20.deployed() await ( await erc20.initialize( - networkConfig[chainId].tokens.aBasUSDbC!, - 'Static Aave Base USDbC', - 'saBasUSDbC' + networkConfig[chainId].tokens.aEthPyUSD!, + 'Static Aave Ethereum pyUSD', + 'saEthPyUSD' ) ).wait() - console.log( - `Deployed wrapper for Aave V3 USDbC on ${hre.network.name} (${chainId}): ${erc20.address} ` - ) - - /******** Deploy Aave V3 USDbC collateral plugin **************************/ + /******** Deploy Aave V3 pyUSD collateral plugin **************************/ - const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') const collateral = await CollateralFactory.connect(deployer).deploy( { priceTimeout: priceTimeout, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: fp('0.003'), // 3% + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.pyUSD!, + oracleError: PYUSD_ORACLE_ERROR, erc20: erc20.address, - maxTradeVolume: fp('1e6'), - oracleTimeout: '86400', // 24 hr + maxTradeVolume: PYUSD_MAX_TRADE_VOLUME, + oracleTimeout: PYUSD_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.013'), + defaultThreshold: fp('0.01').add(PYUSD_ORACLE_ERROR), delayUntilDefault: bn('86400'), }, revenueHiding ) - await collateral.deployed() await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) console.log( - `Deployed Aave V3 USDbC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + `Deployed Aave V3 pyUSD collateral to ${hre.network.name} (${chainId}): ${collateral.address}` ) - assetCollDeployments.collateral.aBasUSDbC = collateral.address - assetCollDeployments.erc20s.aBasUSDbC = erc20.address + assetCollDeployments.erc20s.saEthPyUSD = erc20.address + assetCollDeployments.collateral.saEthPyUSD = collateral.address + assetCollDeployments.erc20s.saEthPyUSD = networkConfig[chainId].tokens.saEthPyUSD! deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts index 2d4eb8112d..63f72128c0 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts @@ -14,6 +14,14 @@ import { import { bn, fp } from '#/common/numbers' import { AaveV3FiatCollateral } from '../../../../typechain' import { priceTimeout, revenueHiding } from '../../utils' +import { + USDC_MAINNET_MAX_TRADE_VOLUME, + USDC_MAINNET_ORACLE_TIMEOUT, + USDC_MAINNET_ORACLE_ERROR, + USDC_BASE_MAX_TRADE_VOLUME, + USDC_BASE_ORACLE_TIMEOUT, + USDC_BASE_ORACLE_ERROR, +} from '../../../../test/plugins/individual-collateral/aave-v3/constants' // This file specifically deploys Aave V3 USDC collateral @@ -30,11 +38,6 @@ async function main() { throw new Error(`Missing network configuration for ${hre.network.name}`) } - // Only exists on Mainnet - if (baseL2Chains.includes(hre.network.name)) { - throw new Error(`Invalid network ${hre.network.name} - only available on Mainnet`) - } - // Get phase1 deployment const phase1File = getDeploymentFilename(chainId) if (!fileExists(phase1File)) { @@ -47,57 +50,98 @@ async function main() { const deployedCollateral: string[] = [] - /******** Deploy Aave V3 USDC wrapper **************************/ + /******** Deploy Aave V3 USDC collateral plugin **************************/ + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') const StaticATokenFactory = await hre.ethers.getContractFactory('StaticATokenV3LM') const erc20 = await StaticATokenFactory.deploy( networkConfig[chainId].AAVE_V3_POOL!, networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! ) await erc20.deployed() - await ( - await erc20.initialize( - networkConfig[chainId].tokens.aEthUSDC!, - 'Static Aave Ethereum USDC', - 'saEthUSDC' + + // Mainnet + if (!baseL2Chains.includes(hre.network.name)) { + /******** Deploy Aave V3 USDC wrapper **************************/ + + await ( + await erc20.initialize( + networkConfig[chainId].tokens.aEthUSDC!, + 'Static Aave Ethereum USDC', + 'saEthUSDC' + ) + ).wait() + + console.log( + `Deployed wrapper for Aave V3 USDC on ${hre.network.name} (${chainId}): ${erc20.address} ` ) - ).wait() - console.log( - `Deployed wrapper for Aave V3 USDC on ${hre.network.name} (${chainId}): ${erc20.address} ` - ) + const collateral = await CollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: USDC_MAINNET_ORACLE_ERROR.toString(), + erc20: erc20.address, + maxTradeVolume: USDC_MAINNET_MAX_TRADE_VOLUME.toString(), + oracleTimeout: USDC_MAINNET_ORACLE_TIMEOUT.toString(), + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(USDC_MAINNET_ORACLE_ERROR).toString(), + delayUntilDefault: bn('86400').toString(), + }, + revenueHiding.toString() + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - /******** Deploy Aave V3 USDC collateral plugin **************************/ - const usdcOracleTimeout = '86400' // 24 hr - const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + console.log( + `Deployed Aave V3 USDC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) - const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') - const collateral = await CollateralFactory.connect(deployer).deploy( - { - priceTimeout: priceTimeout, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: usdcOracleError, - erc20: erc20.address, - maxTradeVolume: fp('1e6'), - oracleTimeout: usdcOracleTimeout, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01').add(usdcOracleError), - delayUntilDefault: bn('86400'), - }, - revenueHiding - ) - await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + assetCollDeployments.erc20s.saEthUSDC = erc20.address + assetCollDeployments.collateral.saEthUSDC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } else { + /******** Deploy Aave V3 USDC wrapper **************************/ + + await ( + await erc20.initialize( + networkConfig[chainId].tokens.aBasUSDC!, + 'Static Aave Base USDC', + 'saBasUSDC' + ) + ).wait() + + console.log( + `Deployed wrapper for Aave V3 USDC on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) - console.log( - `Deployed Aave V3 USDC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` - ) + const collateral = await CollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: USDC_BASE_ORACLE_ERROR.toString(), + erc20: erc20.address, + maxTradeVolume: USDC_BASE_MAX_TRADE_VOLUME.toString(), + oracleTimeout: USDC_BASE_ORACLE_TIMEOUT.toString(), + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(USDC_BASE_ORACLE_ERROR).toString(), + delayUntilDefault: bn('86400').toString(), + }, + revenueHiding.toString() + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - assetCollDeployments.collateral.aEthUSDC = collateral.address - assetCollDeployments.erc20s.aEthUSDC = erc20.address - deployedCollateral.push(collateral.address.toString()) + console.log( + `Deployed Aave V3 USDC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + assetCollDeployments.erc20s.saBasUSDC = erc20.address + assetCollDeployments.collateral.saBasUSDC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) console.log(`Deployed collateral to ${hre.network.name} (${chainId}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts deleted file mode 100644 index 89b2464c55..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts +++ /dev/null @@ -1,293 +0,0 @@ -import fs from 'fs' -import hre, { ethers } from 'hardhat' -import { expect } from 'chai' -import { getChainId } from '../../../../common/blockchain-utils' -import { baseL2Chains, networkConfig } from '../../../../common/configuration' -import { bn, fp } from '../../../../common/numbers' -import { CollateralStatus } from '../../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, - getDeploymentFilename, - fileExists, -} from '../../common' -import { combinedError, priceTimeout, revenueHiding } from '../../utils' -import { ICollateral } from '../../../../typechain' - -async function main() { - // ==== Read Configuration ==== - const [burner] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) - with burner account: ${burner.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Get phase1 deployment - const phase1File = getDeploymentFilename(chainId) - if (!fileExists(phase1File)) { - throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) - } - // Check previous step completed - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) - - // Get Oracle Lib address if previously deployed (can override with arbitrary address) - const deployedCollateral: string[] = [] - - let collateral: ICollateral - - const wbtcOracleError = fp('0.02') // 2% - const btcOracleError = fp('0.005') // 0.5% - const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) - - /*** Compound V2 not available in Base L2s */ - if (!baseL2Chains.includes(hre.network.name)) { - /******** Deploy CToken Fiat Collateral - cDAI **************************/ - const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') - const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) - - const cDaiVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cDAI!, - `${await cDai.name()} Vault`, - `${await cDai.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cDaiVault.deployed() - - console.log( - `Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} ` - ) - - const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cDaiVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cDAI = cDaiCollateral - assetCollDeployments.erc20s.cDAI = cDaiVault.address - deployedCollateral.push(cDaiCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDC **************************/ - const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) - - const cUsdcVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDC!, - `${await cUsdc.name()} Vault`, - `${await cUsdc.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdcVault.deployed() - - console.log( - `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` - ) - - const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdcVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDC = cUsdcCollateral - assetCollDeployments.erc20s.cUSDC = cUsdcVault.address - deployedCollateral.push(cUsdcCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDT **************************/ - const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) - - const cUsdtVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDT!, - `${await cUsdt.name()} Vault`, - `${await cUsdt.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdtVault.deployed() - - console.log( - `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` - ) - - const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdtVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDT = cUsdtCollateral - assetCollDeployments.erc20s.cUSDT = cUsdtVault.address - deployedCollateral.push(cUsdtCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDP **************************/ - const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) - - const cUsdpVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDP!, - `${await cUsdp.name()} Vault`, - `${await cUsdp.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdpVault.deployed() - - console.log( - `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` - ) - - const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, - oracleError: fp('0.01').toString(), // 1% - cToken: cUsdpVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.02').toString(), // 2% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDP = cUsdpCollateral - assetCollDeployments.erc20s.cUSDP = cUsdpVault.address - deployedCollateral.push(cUsdpCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ - const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) - - const cWBTCVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cWBTC!, - `${await cWBTC.name()} Vault`, - `${await cWBTC.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cWBTCVault.deployed() - - console.log( - `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` - ) - - const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { - priceTimeout: priceTimeout.toString(), - referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, - targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, - combinedOracleError: combinedBTCWBTCError.toString(), - cToken: cWBTCVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetUnitOracleTimeout: '3600', // 1 hr - targetName: hre.ethers.utils.formatBytes32String('BTC'), - defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cWBTC = cWBTCCollateral - assetCollDeployments.erc20s.cWBTC = cWBTCVault.address - deployedCollateral.push(cWBTCCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Self-Referential Collateral - cETH **************************/ - const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) - - const cETHVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cETH!, - `${await cETH.name()} Vault`, - `${await cETH.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cETHVault.deployed() - - console.log( - `Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} ` - ) - - const { collateral: cETHCollateral } = await hre.run( - 'deploy-ctoken-selfreferential-collateral', - { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: fp('0.005').toString(), // 0.5% - cToken: cETHVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr - targetName: hre.ethers.utils.formatBytes32String('ETH'), - revenueHiding: revenueHiding.toString(), - referenceERC20Decimals: '18', - } - ) - collateral = await ethers.getContractAt('ICollateral', cETHCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cETH = cETHCollateral - assetCollDeployments.erc20s.cETH = cETHVault.address - deployedCollateral.push(cETHCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - } - - console.log(`Deployed collateral to ${hre.network.name} (${chainId}) - New deployments: ${deployedCollateral} - Deployment file: ${assetCollDeploymentFilename}`) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_3pool_collateral.ts similarity index 94% rename from scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts rename to scripts/deployment/phase2-assets/collaterals/deploy_convex_3pool_collateral.ts index abd65d88b6..77fd4035b0 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_3pool_collateral.ts @@ -9,7 +9,6 @@ import { getDeploymentFile, getAssetCollDeploymentFilename, IAssetCollDeployments, - IDeployments, getDeploymentFilename, fileExists, } from '../../common' @@ -35,7 +34,7 @@ import { USDT_USD_FEED, } from '../../../../test/plugins/individual-collateral/curve/constants' -// This file specifically deploys Convex Stable Plugin for 3pool +// Convex Stable Plugin: 3pool async function main() { // ==== Read Configuration ==== @@ -55,8 +54,6 @@ async function main() { if (!fileExists(phase1File)) { throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) } - const deployments = getDeploymentFile(phase1File) - // Check previous step completed const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) @@ -106,7 +103,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) console.log( - `Deployed Convex Stable Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + `Deployed Convex Stable Collateral for 3Pool to ${hre.network.name} (${chainId}): ${collateral.address}` ) assetCollDeployments.collateral.cvx3Pool = collateral.address diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts new file mode 100644 index 0000000000..7062133c02 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts @@ -0,0 +1,119 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveStableCollateral } from '../../../../typechain' +import { revenueHiding } from '../../utils' +import { + CurvePoolType, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + crvUSD_USDC, + crvUSD_USDC_POOL_ID, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + crvUSD_ORACLE_ERROR, + crvUSD_ORACLE_TIMEOUT, + crvUSD_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Convex Stable Plugin: crvUSD-USDC + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Convex Stable Pool for crvUSD-USDC **************************/ + + const CurveStableCollateralFactory = await hre.ethers.getContractFactory('CurveStableCollateral') + const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + + const crvUsdUSDCPool = await ConvexStakingWrapperFactory.deploy() + await crvUsdUSDCPool.deployed() + await (await crvUsdUSDCPool.initialize(crvUSD_USDC_POOL_ID)).wait() + + console.log( + `Deployed wrapper for Convex Stable crvUSD-USDC pool on ${hre.network.name} (${chainId}): ${crvUsdUSDCPool.address} ` + ) + + const collateral = await CurveStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: crvUsdUSDCPool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: crvUSD_USDC, + poolType: CurvePoolType.Plain, + feeds: [[USDC_USD_FEED], [crvUSD_USD_FEED]], + oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [crvUSD_ORACLE_TIMEOUT]], + oracleErrors: [[USDC_ORACLE_ERROR], [crvUSD_ORACLE_ERROR]], + lpToken: crvUSD_USDC, + } + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Convex Stable Collateral for crvUSD-USDC to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.cvxCrvUSDUSDC = collateral.address + assetCollDeployments.erc20s.cvxCrvUSDUSDC = crvUsdUSDCPool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_paypool_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_paypool_collateral.ts new file mode 100644 index 0000000000..4b62b240db --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_paypool_collateral.ts @@ -0,0 +1,119 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveStableCollateral } from '../../../../typechain' +import { revenueHiding } from '../../utils' +import { + CurvePoolType, + pyUSD_ORACLE_ERROR, + pyUSD_ORACLE_TIMEOUT, + pyUSD_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + PayPool, + PayPool_POOL_ID, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Convex Stable Plugin: paypool + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Convex Stable Pool for 3pool **************************/ + + const CurveStableCollateralFactory = await hre.ethers.getContractFactory('CurveStableCollateral') + const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + + const payPool = await ConvexStakingWrapperFactory.deploy() + await payPool.deployed() + await (await payPool.initialize(PayPool_POOL_ID)).wait() + + console.log( + `Deployed wrapper for Convex Stable PayPool on ${hre.network.name} (${chainId}): ${payPool.address} ` + ) + + const collateral = await CurveStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: payPool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: PayPool, + poolType: CurvePoolType.Plain, + feeds: [[pyUSD_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [[pyUSD_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleErrors: [[pyUSD_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + lpToken: PayPool, + } + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Convex Stable Collateral for PayPool to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.cvxPayPool = collateral.address + assetCollDeployments.erc20s.cvxPayPool = payPool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts index 21e78893af..0b2184a842 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts @@ -76,8 +76,7 @@ async function main() { defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% delayUntilDefault: bn('86400').toString(), // 24h }, - revenueHiding.toString(), - bn('10000e6').toString() // $10k + revenueHiding.toString() ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts index a05ac5bbc7..50253f15f7 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts @@ -29,11 +29,6 @@ async function main() { throw new Error(`Missing network configuration for ${hre.network.name}`) } - // Only exists on Mainnet - if (baseL2Chains.includes(hre.network.name)) { - throw new Error(`Invalid network ${hre.network.name} - only available on Mainnet`) - } - // Get phase1 deployment const phase1File = getDeploymentFilename(chainId) if (!fileExists(phase1File)) { @@ -74,8 +69,7 @@ async function main() { defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h }, - revenueHiding.toString(), - bn('10000e6').toString() // $10k + revenueHiding.toString() ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_sfrax_eth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax_eth.ts new file mode 100644 index 0000000000..0b09d97cfb --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax_eth.ts @@ -0,0 +1,86 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, combinedError } from '../../utils' +import { SFraxEthCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy SFRAX ETH Collateral - sFraxETH **************************/ + let sFraxEthOracleAddress: string = networkConfig[chainId].CURVE_POOL_WETH_FRXETH! + const SFraxEthCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'SFraxEthCollateral' + ) + + const oracleError = combinedError(fp('0.005'), fp('0.0002')) // 0.5% & 0.02% + + const collateral = await SFraxEthCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: oracleError.toString(), + erc20: networkConfig[chainId].tokens.sfrxETH, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4').toString(), // revenueHiding = 0.01% + sFraxEthOracleAddress + ) + await collateral.deployed() + + console.log(`Deployed sFraxETH to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.sfrxETH = collateral.address + assetCollDeployments.erc20s.sfrxETH = networkConfig[chainId].tokens.sfrxETH + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index a8c2083e09..0ce4e3bf46 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -180,11 +180,12 @@ export const getEmptyDeployment = (): IDeployments => { }, tradingLib: '', basketLib: '', - facadeRead: '', + actFacet: '', + readFacet: '', + facade: '', facadeWriteLib: '', cvxMiningLib: '', facadeWrite: '', - facadeAct: '', deployer: '', rsrAsset: '', implementations: { diff --git a/scripts/verification/4_verify_facade.ts b/scripts/verification/4_verify_facade.ts index b75b623a3f..c66c2cfd66 100644 --- a/scripts/verification/4_verify_facade.ts +++ b/scripts/verification/4_verify_facade.ts @@ -21,19 +21,22 @@ async function main() { deployments = getDeploymentFile(getDeploymentFilename(chainId)) /** ******************** Verify FacadeRead ****************************************/ + await verifyContract(chainId, deployments.facade, [], 'contracts/facade/Facade.sol:Facade') + + /** ******************** Verify ReadFacet ****************************************/ await verifyContract( chainId, - deployments.facadeRead, + deployments.facets.readFacet, [], - 'contracts/facade/FacadeRead.sol:FacadeRead' + 'contracts/facade/facets/ReadFacet.sol:ReadFacet' ) - /** ******************** Verify FacadeAct ****************************************/ + /** ******************** Verify ActFacet ****************************************/ await verifyContract( chainId, - deployments.facadeAct, + deployments.facets.actFacet, [], - 'contracts/facade/FacadeAct.sol:FacadeAct' + 'contracts/facade/facets/ActFacet.sol:ActFacet' ) } diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index fbcfe84d23..41a4375322 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -9,7 +9,7 @@ import { IAssetCollDeployments, } from '../deployment/common' import { combinedError, priceTimeout, revenueHiding, verifyContract } from '../deployment/utils' -import { ATokenMock, ATokenFiatCollateral, ICToken, CTokenFiatCollateral } from '../../typechain' +import { ATokenMock, ATokenFiatCollateral } from '../../typechain' let deployments: IAssetCollDeployments @@ -115,25 +115,6 @@ async function main() { ], 'contracts/plugins/assets/aave/ATokenFiatCollateral.sol:ATokenFiatCollateral' ) - /******** Verify CTokenWrapper - cDAI **************************/ - const cToken: ICToken = ( - await ethers.getContractAt('ICToken', networkConfig[chainId].tokens.cDAI as string) - ) - const cTokenCollateral: CTokenFiatCollateral = ( - await ethers.getContractAt('CTokenFiatCollateral', deployments.collateral.cDAI as string) - ) - - await verifyContract( - chainId, - await cTokenCollateral.erc20(), - [ - cToken.address, - `${await cToken.name()} Vault`, - `${await cToken.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER!, - ], - 'contracts/plugins/assets/compoundv2/CTokenWrapper.sol:CTokenWrapper' - ) /********************** Verify CTokenFiatCollateral - cDAI ****************************************/ await verifyContract( chainId, diff --git a/scripts/verification/assets/verify_comp.ts b/scripts/verification/assets/verify_comp.ts new file mode 100644 index 0000000000..5642443c2c --- /dev/null +++ b/scripts/verification/assets/verify_comp.ts @@ -0,0 +1,49 @@ +import hre from 'hardhat' + +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { + getAssetCollDeploymentFilename, + getDeploymentFile, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { fp } from '../../../common/numbers' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + deployments = getDeploymentFile(getAssetCollDeploymentFilename(chainId)) + + const asset = await hre.ethers.getContractAt('Asset', deployments.assets.COMP!) + + /** ******************** Verify COMP Asset ****************************************/ + await verifyContract( + chainId, + deployments.assets.COMP, + [ + await asset.priceTimeout(), + await asset.chainlinkFeed(), + fp('0.01').toString(), // 1% + await asset.erc20(), + await asset.maxTradeVolume(), + await asset.oracleTimeout(), + ], + 'contracts/plugins/assets/Asset.sol:Asset' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts deleted file mode 100644 index 3a373573dc..0000000000 --- a/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts +++ /dev/null @@ -1,71 +0,0 @@ -import hre, { ethers } from 'hardhat' -import { getChainId } from '../../../common/blockchain-utils' -import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, -} from '../../deployment/common' -import { fp, bn } from '../../../common/numbers' -import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' - -let deployments: IAssetCollDeployments - -async function main() { - // ********** Read config ********** - const chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - if (developmentChains.includes(hre.network.name)) { - throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) - } - - // Only exists on Base L2 - if (!baseL2Chains.includes(hre.network.name)) { - throw new Error(`Invalid network ${hre.network.name} - only available on Base`) - } - - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - deployments = getDeploymentFile(assetCollDeploymentFilename) - - /******** Verify Wrapper **************************/ - const erc20 = await ethers.getContractAt( - 'StaticATokenV3LM', - deployments.erc20s.aBasUSDbC as string - ) - - await verifyContract( - chainId, - deployments.erc20s.aBasUSDbC, - [await erc20.POOL(), await erc20.INCENTIVES_CONTROLLER()], - 'contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol:StaticATokenV3LM' - ) - - /******** Verify Aave V3 USDbC plugin **************************/ - await verifyContract( - chainId, - deployments.collateral.aBasUSDbC, - [ - { - erc20: erc20.address, - targetName: ethers.utils.formatBytes32String('USD'), - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: fp('0.003').toString(), // 3% - oracleTimeout: '86400', // 24 hr - maxTradeVolume: fp('1e6').toString(), - defaultThreshold: fp('0.013').toString(), - delayUntilDefault: bn('86400').toString(), - }, - revenueHiding.toString(), - ], - 'contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol:AaveV3FiatCollateral' - ) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts index 3ffce9a0a5..e5579929a0 100644 --- a/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts +++ b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts @@ -6,7 +6,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { fp, bn } from '../../../common/numbers' +import { fp } from '../../../common/numbers' import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -25,37 +25,46 @@ async function main() { const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) deployments = getDeploymentFile(assetCollDeploymentFilename) - /******** Verify Wrapper **************************/ const erc20 = await ethers.getContractAt( - 'StaticATokenV3LM', - deployments.erc20s.aEthUSDC as string + 'ERC20Mock', + baseL2Chains.includes(hre.network.name) + ? deployments.erc20s.saBasUSDC! + : deployments.erc20s.saEthUSDC! + ) + const collateral = await ethers.getContractAt( + 'AaveV3FiatCollateral', + baseL2Chains.includes(hre.network.name) + ? deployments.collateral.saBasUSDC! + : deployments.collateral.saEthUSDC! ) + /******** Verify Aave V3 USDC ERC20 **************************/ await verifyContract( chainId, - deployments.erc20s.aEthUSDC, - [await erc20.POOL(), await erc20.INCENTIVES_CONTROLLER()], + erc20.address, + [networkConfig[chainId].AAVE_V3_POOL!, networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER!], 'contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol:StaticATokenV3LM' ) /******** Verify Aave V3 USDC plugin **************************/ - const usdcOracleTimeout = '86400' // 24 hr - const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + // Works for both Mainnet and Base await verifyContract( chainId, - deployments.collateral.aEthUSDC, + collateral.address, [ { - erc20: erc20.address, + erc20: await collateral.erc20(), targetName: ethers.utils.formatBytes32String('USD'), priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: usdcOracleError.toString(), - oracleTimeout: usdcOracleTimeout, // 24 hr - maxTradeVolume: fp('1e6').toString(), - defaultThreshold: fp('0.01').add(usdcOracleError).toString(), - delayUntilDefault: bn('86400').toString(), + chainlinkFeed: await collateral.chainlinkFeed(), + oracleError: await collateral.oracleError(), + oracleTimeout: await collateral.oracleTimeout(), + maxTradeVolume: await collateral.maxTradeVolume(), + defaultThreshold: fp('0.01') + .add(await collateral.oracleError()) + .toString(), + delayUntilDefault: await collateral.delayUntilDefault(), }, revenueHiding.toString(), ], diff --git a/scripts/verification/collateral-plugins/verify_convex_stable.ts b/scripts/verification/collateral-plugins/verify_convex_3pool.ts similarity index 94% rename from scripts/verification/collateral-plugins/verify_convex_stable.ts rename to scripts/verification/collateral-plugins/verify_convex_3pool.ts index 127ef39143..9058ab6878 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable.ts +++ b/scripts/verification/collateral-plugins/verify_convex_3pool.ts @@ -5,10 +5,8 @@ import { bn } from '../../../common/numbers' import { ONE_ADDRESS } from '../../../common/constants' import { getDeploymentFile, - getDeploymentFilename, getAssetCollDeploymentFilename, IAssetCollDeployments, - IDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' import { revenueHiding } from '../../deployment/utils' @@ -44,9 +42,6 @@ async function main() { throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) } - const deploymentFilename = getDeploymentFilename(chainId) - const coreDeployments = getDeploymentFile(deploymentFilename) - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) deployments = getDeploymentFile(assetCollDeploymentFilename) diff --git a/scripts/verification/collateral-plugins/verify_convex_crvusd_usdc.ts b/scripts/verification/collateral-plugins/verify_convex_crvusd_usdc.ts new file mode 100644 index 0000000000..17ea4877d2 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_convex_crvusd_usdc.ts @@ -0,0 +1,92 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' +import { + CurvePoolType, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + crvUSD_USDC, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + crvUSD_ORACLE_ERROR, + crvUSD_ORACLE_TIMEOUT, + crvUSD_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const crvUsdUSDCPoolCollateral = await ethers.getContractAt( + 'CurveStableCollateral', + deployments.collateral.cvxCrvUSDUSDC as string + ) + + /******** Verify ConvexStakingWrapper **************************/ + + await verifyContract( + chainId, + await crvUsdUSDCPoolCollateral.erc20(), + [], + 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper' + ) + + /******** Verify crvUSD-USDC plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.cvxCrvUSDUSDC, + [ + { + erc20: await crvUsdUSDCPoolCollateral.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: crvUSD_USDC, + poolType: CurvePoolType.Plain, + feeds: [[USDC_USD_FEED], [crvUSD_USD_FEED]], + oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [crvUSD_ORACLE_TIMEOUT]], + oracleErrors: [[USDC_ORACLE_ERROR], [crvUSD_ORACLE_ERROR]], + lpToken: crvUSD_USDC, + }, + ], + 'contracts/plugins/assets/curve/CurveStableCollateral.sol:CurveStableCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_convex_paypool.ts b/scripts/verification/collateral-plugins/verify_convex_paypool.ts new file mode 100644 index 0000000000..dc0e00f0cd --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_convex_paypool.ts @@ -0,0 +1,92 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' +import { + CurvePoolType, + pyUSD_ORACLE_ERROR, + pyUSD_ORACLE_TIMEOUT, + pyUSD_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + PayPool, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const w3PoolCollateral = await ethers.getContractAt( + 'CurveStableCollateral', + deployments.collateral.cvxPayPool as string + ) + + /******** Verify ConvexStakingWrapper **************************/ + + await verifyContract( + chainId, + await w3PoolCollateral.erc20(), + [], + 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper' + ) + + /******** Verify PayPool plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.cvxPayPool, + [ + { + erc20: await w3PoolCollateral.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: PayPool, + poolType: CurvePoolType.Plain, + feeds: [[pyUSD_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [[pyUSD_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleErrors: [[pyUSD_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + lpToken: PayPool, + }, + ], + 'contracts/plugins/assets/curve/CurveStableCollateral.sol:CurveStableCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts index 400c23d10e..12cc671525 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts @@ -48,6 +48,15 @@ async function main() { deployments.collateral.cvxeUSDFRAXBP as string ) + /******** Verify ConvexStakingWrapper **************************/ + + await verifyContract( + chainId, + await eUSDPlugin.erc20(), + [], + 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper' + ) + /******** Verify eUSD/fraxBP plugin **************************/ await verifyContract( chainId, diff --git a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts index d0eb672ef2..497b3e5be8 100644 --- a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts +++ b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts @@ -69,7 +69,6 @@ async function main() { delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding, - bn('10000e6'), // $10k ], 'contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol:CTokenV3Collateral' ) diff --git a/scripts/verification/collateral-plugins/verify_cusdcv3.ts b/scripts/verification/collateral-plugins/verify_cusdcv3.ts index 09a6eceb34..dac4d02c4a 100644 --- a/scripts/verification/collateral-plugins/verify_cusdcv3.ts +++ b/scripts/verification/collateral-plugins/verify_cusdcv3.ts @@ -64,7 +64,6 @@ async function main() { delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding, - bn('10000e6'), // $10k ], 'contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol:CTokenV3Collateral' ) diff --git a/scripts/verification/collateral-plugins/verify_sfrax_eth.ts b/scripts/verification/collateral-plugins/verify_sfrax_eth.ts new file mode 100644 index 0000000000..2cd777f81b --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_sfrax_eth.ts @@ -0,0 +1,56 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { priceTimeout, oracleTimeout, verifyContract, combinedError } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify sFRAX **************************/ + const oracleError = combinedError(fp('0.005'), fp('0.0002')) // 0.5% & 0.02% + + await verifyContract( + chainId, + deployments.collateral.sfrxETH, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: oracleError.toString(), + erc20: networkConfig[chainId].tokens.sfrxETH, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4').toString(), + networkConfig[chainId].CURVE_POOL_WETH_FRXETH!, + ], + 'contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol:SFraxEthCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index dd200b6248..49344e9aa4 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -49,7 +49,9 @@ async function main() { // Phase 2 - Individual Plugins if (!baseL2Chains.includes(hre.network.name)) { scripts.push( - 'collateral-plugins/verify_convex_stable.ts', + 'collateral-plugins/verify_convex_crvusd_usdc.ts', + 'collateral-plugins/verify_convex_3pool.ts', + 'collateral-plugins/verify_convex_paypool.ts', 'collateral-plugins/verify_convex_stable_metapool.ts', 'collateral-plugins/verify_convex_stable_rtoken_metapool.ts', 'collateral-plugins/verify_curve_stable.ts', @@ -64,14 +66,15 @@ async function main() { 'collateral-plugins/verify_aave_v3_usdc.ts', 'collateral-plugins/verify_yearn_v2_curve_usdc.ts', 'collateral-plugins/verify_yearn_v2_curve_usdp.ts', - 'collateral-plugins/verify_sfrax.ts' + 'collateral-plugins/verify_sfrax.ts', + 'collateral-plugins/verify_sfrax_eth.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains scripts.push( 'collateral-plugins/verify_cbeth.ts', 'collateral-plugins/verify_cusdbcv3.ts', - 'collateral-plugins/verify_aave_v3_usdbc', + 'collateral-plugins/verify_aave_v3_usdc.ts', 'collateral-plugins/verify_stargate_usdc', 'assets/verify_stg.ts' ) diff --git a/tasks/deployment/mock/deploy-mock-ctoken.ts b/tasks/deployment/mock/deploy-mock-ctoken.ts index aedc14c43e..c140adc2e9 100644 --- a/tasks/deployment/mock/deploy-mock-ctoken.ts +++ b/tasks/deployment/mock/deploy-mock-ctoken.ts @@ -6,6 +6,7 @@ task('deploy-mock-ctoken', 'Deploys a mock CToken') .addParam('name', 'Token name') .addParam('symbol', 'Token symbol') .addParam('erc20', 'Underlying ERC20 address') + .addParam('comptroller', 'The Comptroller') .addOptionalParam('noOutput', 'Suppress output', false, types.boolean) .setAction(async (params, hre) => { const [deployer] = await hre.ethers.getSigners() @@ -21,7 +22,7 @@ task('deploy-mock-ctoken', 'Deploys a mock CToken') const erc20 = ( await (await hre.ethers.getContractFactory('CTokenMock')) .connect(deployer) - .deploy(params.name, params.symbol, params.erc20) + .deploy(params.name, params.symbol, params.erc20, params.comptroller) ) await erc20.deployed() diff --git a/tasks/testing/mint-tokens.ts b/tasks/testing/mint-tokens.ts index fd11d63b4c..b823d51271 100644 --- a/tasks/testing/mint-tokens.ts +++ b/tasks/testing/mint-tokens.ts @@ -112,8 +112,8 @@ task('give-rsr', 'Mints RSR to an address on a tenderly fork') const rsrWhale = '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1' await whileImpersonating(hre, rsrWhale, async (signer) => { - await rsr.connect(signer).transfer(params.address, fp('100e6')) + await rsr.connect(signer).transfer(params.address, fp('1e9')) }) - console.log(`100m RSR sent to ${params.address}`) + console.log(`1B RSR sent to ${params.address}`) }) diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index 602c245d47..8b05f23458 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -2,7 +2,8 @@ import { networkConfig } from '#/common/configuration' export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.USDT!.toLowerCase()]: '0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503', - [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0x756D64Dc5eDb56740fC617628dC832DDBCfd373c', + [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0xAFAaDfa18D9d63d09F19a5445e29CEc601054C5e', + [networkConfig['1'].tokens.pyUSD!.toLowerCase()]: '0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb', [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: '0xb99CC7e10Fe0Acc68C50C7829F473d81e23249cc', [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: '0x0B6B712B0f3998961Cd3109341b00c905b16124A', @@ -13,17 +14,24 @@ export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: '0x97D868b5C2937355Bf89C5E5463d52016240fE86', + [networkConfig['1'].tokens.cUSDCv3!.toLowerCase()]: '0x7f714b13249BeD8fdE2ef3FBDfB18Ed525544B03', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // saUSDC ['0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // cUSDCVault - [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', [networkConfig['1'].tokens.WBTC!.toLowerCase()]: '0x8eb8a3b98659cce290402893d0123abb75e3ab28', [networkConfig['1'].tokens.stETH!.toLowerCase()]: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', [networkConfig['1'].tokens.WETH!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', [networkConfig['1'].tokens.DAI!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', [networkConfig['1'].tokens.CRV!.toLowerCase()]: '0xf977814e90da44bfa03b6295a0616a897441acec', + ['0xAEda92e6A3B1028edc139A4ae56Ec881f3064D4F'.toLowerCase()]: + '0x8605dc0C339a2e7e85EEA043bD29d42DA2c6D784', // cvxeUSDFRAXBP LP token + [networkConfig['1'].tokens.sDAI!.toLowerCase()]: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016', + ['0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f'.toLowerCase()]: + '0x3154Cf16ccdb4C6d922629664174b904d80F2C35', // eUSD + ['0xacdf0dba4b9839b96221a8487e9ca660a48212be'.toLowerCase()]: + '0x8a8434A5952aC2CF4927bbEa3ace255c6dd165CD', // hyUSD } export const collateralToUnderlying: { [key: string]: string } = { @@ -33,4 +41,6 @@ export const collateralToUnderlying: { [key: string]: string } = { networkConfig['1'].tokens.USDC!.toLowerCase(), [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: networkConfig['1'].tokens.USDT!.toLowerCase(), [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: networkConfig['1'].tokens.USDC!.toLowerCase(), + [networkConfig['1'].tokens.saEthUSDC!.toLowerCase()]: + networkConfig['1'].tokens.aEthUSDC!.toLowerCase(), } diff --git a/tasks/testing/upgrade-checker-utils/governance.ts b/tasks/testing/upgrade-checker-utils/governance.ts index 1003f1e0ee..37ca3c0157 100644 --- a/tasks/testing/upgrade-checker-utils/governance.ts +++ b/tasks/testing/upgrade-checker-utils/governance.ts @@ -7,118 +7,180 @@ import { BigNumber, PopulatedTransaction } from 'ethers' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { pushOraclesForward } from './oracles' -export const passAndExecuteProposal = async ( +const validatePropState = async (propState: ProposalState, expectedState: ProposalState) => { + if (propState !== expectedState) { + throw new Error( + `Proposal should be ${ProposalState[expectedState]} but was ${ProposalState[propState]}` + ) + } +} + +export const moveProposalToActive = async ( hre: HardhatRuntimeEnvironment, rtokenAddress: string, governorAddress: string, - proposalId: string, - proposal?: Proposal + proposalId: string ) => { - console.log(`\nPassing & executing proposal ${proposalId}...`) + console.log('Activating Proposal:', proposalId) + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const propState = await governor.state(proposalId) - // Check proposal state - let propState = await governor.state(proposalId) if (propState == ProposalState.Pending) { - console.log(`Prop ${proposalId} is PENDING, moving to ACTIVE...`) + console.log(`Proposal is PENDING, moving to ACTIVE...`) // Advance time to start voting const votingDelay = await governor.votingDelay() - await advanceBlocks(hre, votingDelay.add(1)) - - // Check proposal state - propState = await governor.state(proposalId) - if (propState != ProposalState.Active) { - throw new Error(`Proposal should be active but was ${propState}`) + await advanceBlocks(hre, votingDelay.add(2)) + } else { + if (propState == ProposalState.Active) { + console.log(`Proposal is already ${ProposalState[ProposalState.Active]}... skipping step.`) + } else { + throw Error(`Proposal should be ${ProposalState[ProposalState.Pending]} at this step.`) } } + await validatePropState(await governor.state(proposalId), ProposalState.Active) +} + +export const voteProposal = async ( + hre: HardhatRuntimeEnvironment, + rtokenAddress: string, + governorAddress: string, + proposalId: string, + proposal?: Proposal +) => { + console.log('Voting Proposal:', proposalId) + + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const propState = await governor.state(proposalId) + if (propState == ProposalState.Active) { - console.log(`Prop ${proposalId} is ACTIVE, moving to SUCCEEDED...`) - - // gather enough whale voters - let whales: Array = await getDelegates(rtokenAddress.toLowerCase()) - const startBlock = await governor.proposalSnapshot(proposalId) - const quorum = await governor.quorum(startBlock) - - let quorumNotReached = true - let currentVoteAmount = BigNumber.from(0) - let i = 0 - while (quorumNotReached) { - const whale = whales[i] - currentVoteAmount = currentVoteAmount.add(BigNumber.from(whale.delegatedVotesRaw)) - i += 1 - if (currentVoteAmount.gt(quorum)) { - quorumNotReached = false + console.log(`Proposal is ACTIVE, moving to SUCCEEDED...`) + + if (!proposal) { + // gather enough whale voters + let whales: Array = await getDelegates(rtokenAddress.toLowerCase()) + const startBlock = await governor.proposalSnapshot(proposalId) + const quorum = await governor.quorum(startBlock) + + let quorumNotReached = true + let currentVoteAmount = BigNumber.from(0) + let i = 0 + while (quorumNotReached) { + const whale = whales[i] + currentVoteAmount = currentVoteAmount.add(BigNumber.from(whale.delegatedVotesRaw)) + i += 1 + if (currentVoteAmount.gt(quorum)) { + quorumNotReached = false + } } - } - whales = whales.slice(0, i) + whales = whales.slice(0, i) - // cast enough votes to pass the proposal - for (const whale of whales) { - await whileImpersonating(hre, whale.address, async (signer) => { - await governor.connect(signer).castVote(proposalId, 1) - }) + // cast enough votes to pass the proposal + for (const whale of whales) { + await whileImpersonating(hre, whale.address, async (signer) => { + await governor.connect(signer).castVote(proposalId, 1) + }) + } + } else { + // Vote from testing account, on the assumption it is staked/delegated + const [tester] = await hre.ethers.getSigners() + await governor.connect(tester).castVote(proposalId, 1) } + } + + await validatePropState(await governor.state(proposalId), ProposalState.Active) +} +export const passProposal = async ( + hre: HardhatRuntimeEnvironment, + rtokenAddress: string, + governorAddress: string, + proposalId: string +) => { + console.log('Passing Proposal:', proposalId) + + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const propState = await governor.state(proposalId) + + if (propState == ProposalState.Active) { // Advance time till voting is complete const votingPeriod = await governor.votingPeriod() await advanceBlocks(hre, votingPeriod.add(1)) - - propState = await governor.state(proposalId) - // Finished voting - Check proposal state - if (propState != ProposalState.Succeeded) { - throw new Error('Proposal should have succeeded') - } } + await validatePropState(await governor.state(proposalId), ProposalState.Succeeded) +} + +export const executeProposal = async ( + hre: HardhatRuntimeEnvironment, + rtokenAddress: string, + governorAddress: string, + proposalId: string, + proposal?: Proposal, + extraAssets: string[] = [] +) => { + console.log('Executing Proposal:', proposalId) + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + + // Check proposal state + let propState = await governor.state(proposalId) + let descriptionHash: string if (propState == ProposalState.Succeeded) { - console.log(`Prop ${proposalId} is SUCCEEDED, moving to QUEUED...`) + console.log(`Proposal is SUCCEEDED, moving to QUEUED...`) if (!proposal) { - proposal = await getProposalDetails(`${governorAddress.toLowerCase()}-${proposalId}`) + proposal = await getProposalDetails(proposalId) } + descriptionHash = hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(proposal.description)) - // Queue propoal + // Queue proposal await governor.queue(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) // Check proposal state propState = await governor.state(proposalId) - if (propState != ProposalState.Queued) { - throw new Error('Proposal should be queued') - } + await validatePropState(propState, ProposalState.Queued) } if (propState == ProposalState.Queued) { - console.log(`Prop ${proposalId} is QUEUED, moving to EXECUTED...`) + console.log(`Proposal is QUEUED, moving to EXECUTED...`) if (!proposal) { proposal = await getProposalDetails(`${governorAddress.toLowerCase()}-${proposalId}`) } + descriptionHash = hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(proposal.description)) const timelock = await hre.ethers.getContractAt('TimelockController', await governor.timelock()) const minDelay = await timelock.getMinDelay() + console.log('Preparing execution...') // Advance time required by timelock await advanceTime(hre, minDelay.add(1).toString()) await advanceBlocks(hre, 1) - await pushOraclesForward(hre, rtokenAddress) + + /* + ** Executing proposals requires that the oracles aren't stale. + ** Make sure to specify any extra assets that may have been registered. + */ + await pushOraclesForward(hre, rtokenAddress, extraAssets) + + console.log('Executing now...') // Execute await governor.execute(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) - // Check proposal state propState = await governor.state(proposalId) - if (propState != ProposalState.Executed) { - throw new Error('Proposal should be executed') - } + await validatePropState(propState, ProposalState.Executed) + } else { + throw new Error('Proposal should be queued') } - console.log(`Prop ${proposalId} is EXECUTED.`) + console.log(`Proposal is EXECUTED.`) } export const stakeAndDelegateRsr = async ( @@ -141,7 +203,7 @@ export const stakeAndDelegateRsr = async ( export const buildProposal = (txs: Array, description: string): Proposal => { const targets = txs.map((tx: PopulatedTransaction) => tx.to!) - const values = txs.map((tx: PopulatedTransaction) => bn(0)) + const values = txs.map(() => bn(0)) const calldatas = txs.map((tx: PopulatedTransaction) => tx.data!) return { targets, @@ -162,7 +224,7 @@ export const proposeUpgrade = async ( rTokenAddress: string, governorAddress: string, proposalBuilder: ProposalBuilder -): Promise => { +) => { console.log(`\nGenerating and proposing proposal...`) const [tester] = await hre.ethers.getSigners() @@ -193,5 +255,8 @@ export const proposeUpgrade = async ( console.log('\nSuccessfully proposed!') console.log(`Proposal ID: ${resp.events![0].args!.proposalId}`) - return { ...proposal, proposalId: resp.events![0].args!.proposalId } + return { + ...proposal, + proposalId: resp.events![0].args!.proposalId as string, + } } diff --git a/tasks/testing/upgrade-checker-utils/logs.ts b/tasks/testing/upgrade-checker-utils/logs.ts index a601fdf2b3..b79dbedb33 100644 --- a/tasks/testing/upgrade-checker-utils/logs.ts +++ b/tasks/testing/upgrade-checker-utils/logs.ts @@ -1,10 +1,9 @@ -import { ITokens, networkConfig } from '#/common/configuration' +import { networkConfig } from '#/common/configuration' const tokens: { [key: string]: string } = { [networkConfig['1'].tokens.USDC!.toLowerCase()]: 'USDC', [networkConfig['1'].tokens.USDT!.toLowerCase()]: 'USDT', [networkConfig['1'].tokens.USDP!.toLowerCase()]: 'USDP', - [networkConfig['1'].tokens.TUSD!.toLowerCase()]: 'TUSD', [networkConfig['1'].tokens.BUSD!.toLowerCase()]: 'BUSD', [networkConfig['1'].tokens.FRAX!.toLowerCase()]: 'FRAX', [networkConfig['1'].tokens.MIM!.toLowerCase()]: 'MIM', @@ -30,7 +29,6 @@ const tokens: { [key: string]: string } = { [networkConfig['1'].tokens.COMP!.toLowerCase()]: 'COMP', [networkConfig['1'].tokens.WETH!.toLowerCase()]: 'WETH', [networkConfig['1'].tokens.WBTC!.toLowerCase()]: 'WBTC', - [networkConfig['1'].tokens.EURT!.toLowerCase()]: 'EURT', [networkConfig['1'].tokens.RSR!.toLowerCase()]: 'RSR', [networkConfig['1'].tokens.CRV!.toLowerCase()]: 'CRV', [networkConfig['1'].tokens.CVX!.toLowerCase()]: 'CVX', @@ -42,10 +40,19 @@ const tokens: { [key: string]: string } = { [networkConfig['1'].tokens.rETH!.toLowerCase()]: 'rETH', [networkConfig['1'].tokens.cUSDCv3!.toLowerCase()]: 'cUSDCv3', [networkConfig['1'].tokens.DAI!.toLowerCase()]: 'DAI', + [networkConfig['1'].tokens.aEthUSDC!.toLowerCase()]: 'aEthUSDC', + [networkConfig['1'].tokens.saEthUSDC!.toLowerCase()]: 'saEthUSDC', + [networkConfig['1'].tokens.aEthPyUSD!.toLowerCase()]: 'aEthPyUSD', + [networkConfig['1'].tokens.saEthPyUSD!.toLowerCase()]: 'saEthPyUSD', + [networkConfig['1'].tokens.cUSDCv3!.toLowerCase()]: 'cUSDCv3', + ['0xaa91d24c2f7dbb6487f61869cd8cd8afd5c5cab2'.toLowerCase()]: 'mrp-aUSDT', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: 'saUSDC', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: 'saUSDT', ['0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase()]: 'cUSDCVault', ['0x4Be33630F92661afD646081BC29079A38b879aA0'.toLowerCase()]: 'cUSDTVault', + ['0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A'.toLowerCase()]: 'wcUSDCv3', + ['0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5'.toLowerCase()]: 'stkcvxeUSDFRAXBP', + ['0x83f20f44975d03b1b09e64809b757c47f942beea'.toLowerCase()]: 'sDAI', } export const logToken = (tokenAddress: string) => { diff --git a/tasks/testing/upgrade-checker-utils/oracles.ts b/tasks/testing/upgrade-checker-utils/oracles.ts index aa5d536188..a4d1c72aac 100644 --- a/tasks/testing/upgrade-checker-utils/oracles.ts +++ b/tasks/testing/upgrade-checker-utils/oracles.ts @@ -1,7 +1,10 @@ -import { setCode } from '@nomicfoundation/hardhat-network-helpers' +/* eslint-disable no-empty */ +import { networkConfig } from '../../../common/configuration' import { EACAggregatorProxyMock } from '@typechain/EACAggregatorProxyMock' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { BigNumber } from 'ethers' +import { AggregatorV3Interface } from '@typechain/index' +import { ONE_ADDRESS } from '../../../common/constants' export const overrideOracle = async ( hre: HardhatRuntimeEnvironment, @@ -16,13 +19,20 @@ export const overrideOracle = async ( const initPrice = await oracle.latestRoundData() const mockOracleFactory = await hre.ethers.getContractFactory('EACAggregatorProxyMock') const mockOracle = await mockOracleFactory.deploy(aggregator, accessController, initPrice.answer) - const bytecode = await hre.network.provider.send('eth_getCode', [mockOracle.address]) - await setCode(oracleAddress, bytecode) + const bytecode = await hre.network.provider.send('eth_getCode', [mockOracle.address, 'latest']) + await hre.network.provider.request({ + method: 'hardhat_setCode', + params: [oracleAddress, bytecode], + }) return hre.ethers.getContractAt('EACAggregatorProxyMock', oracleAddress) } -export const pushOraclesForward = async (hre: HardhatRuntimeEnvironment, rTokenAddress: string) => { - console.log(`\nPushing oracles forward for RToken ${rTokenAddress}...`) +export const pushOraclesForward = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string, + extraAssets: string[] = [] +) => { + console.log(`🔃 Pushing Oracles forward for RToken: ${rTokenAddress}`) const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const assetRegistry = await hre.ethers.getContractAt( @@ -30,27 +40,86 @@ export const pushOraclesForward = async (hre: HardhatRuntimeEnvironment, rTokenA await main.assetRegistry() ) const registry = await assetRegistry.getRegistry() + for (const asset of registry.assets) { await pushOracleForward(hre, asset) } + + for (const asset of extraAssets) { + await pushOracleForward(hre, asset) + } } export const pushOracleForward = async (hre: HardhatRuntimeEnvironment, asset: string) => { - const assetContract = await hre.ethers.getContractAt('TestIAsset', asset) - let chainlinkFeed = '' + // Need to handle all oracle cases, ie targetUnitChainlinkFeed, PoolTokens, etc + const updateAnswer = async (chainlinkFeed: AggregatorV3Interface) => { + const initPrice = await chainlinkFeed.latestRoundData() + const oracle = await overrideOracle(hre, chainlinkFeed.address) + await oracle.updateAnswer(initPrice.answer) + + console.log('✅ Feed Updated:', chainlinkFeed.address) + } + + // chainlinkFeed + try { + const assetContract = await hre.ethers.getContractAt('TestIAsset', asset) + const feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContract.chainlinkFeed() + ) + if (feed.address != ONE_ADDRESS) await updateAnswer(feed) + } catch {} + + // targetUnitChainlinkFeed try { - chainlinkFeed = await assetContract.chainlinkFeed() - } catch { - console.log(`no chainlink oracle found. skipping RTokenAsset ${asset}...`) - return + const assetContractNonFiat = await hre.ethers.getContractAt('NonFiatCollateral', asset) + const feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContractNonFiat.targetUnitChainlinkFeed() + ) + await updateAnswer(feed) + } catch {} + + // targetPerRefChainlinkFeed, uoaPerTargetChainlinkFeed, refPerTokenChainlinkFeed + try { + const assetContractLido = await hre.ethers.getContractAt('L2LidoStakedEthCollateral', asset) + let feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContractLido.targetPerRefChainlinkFeed() + ) + await updateAnswer(feed) + feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContractLido.uoaPerTargetChainlinkFeed() + ) + await updateAnswer(feed) + feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContractLido.refPerTokenChainlinkFeed() + ) + await updateAnswer(feed) + } catch {} + + // targetPerTokChainlinkFeed + try { + const assetContractReth = await hre.ethers.getContractAt('RethCollateral', asset) + const feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContractReth.targetPerTokChainlinkFeed() + ) + await updateAnswer(feed) + } catch {} + + // TODO do better + // Problem: The feeds on PoolTokens are internal immutable. Not in storage nor are there getters. + // Workaround solution: hard-code oracles for FRAX for eUSDFRAXBP; USDC is registered as backup + if (asset == '0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122') { + const feed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + networkConfig['1'].chainlinkFeeds.FRAX! + ) + await updateAnswer(feed) } - const realChainlinkFeed = await hre.ethers.getContractAt( - 'AggregatorV3Interface', - await assetContract.chainlinkFeed() - ) - const initPrice = await realChainlinkFeed.latestRoundData() - const oracle = await overrideOracle(hre, realChainlinkFeed.address) - await oracle.updateAnswer(initPrice.answer) } export const setOraclePrice = async ( @@ -59,13 +128,6 @@ export const setOraclePrice = async ( value: BigNumber ) => { const assetContract = await hre.ethers.getContractAt('TestIAsset', asset) - let chainlinkFeed = '' - try { - chainlinkFeed = await assetContract.chainlinkFeed() - } catch { - console.log(`no chainlink oracle found. skipping RTokenAsset ${asset}...`) - return - } const realChainlinkFeed = await hre.ethers.getContractAt( 'AggregatorV3Interface', await assetContract.chainlinkFeed() diff --git a/tasks/testing/upgrade-checker-utils/rewards.ts b/tasks/testing/upgrade-checker-utils/rewards.ts index 22a291619a..46e694e373 100644 --- a/tasks/testing/upgrade-checker-utils/rewards.ts +++ b/tasks/testing/upgrade-checker-utils/rewards.ts @@ -33,12 +33,13 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr const rsrRatePre = await strsr.exchangeRate() const rewards = await claimRewards(backingManager) + console.log('rewards claimed', rewards) await backingManager.forwardRevenue(rewards) const comp = '0xc00e94Cb662C3520282E6f5717214004A7f26888' const compContract = await hre.ethers.getContractAt('ERC20Mock', comp) // fake enough rewards to trade - await whileImpersonating(hre, '0x2775b1c75658Be0F640272CCb8c72ac986009e38', async (compWhale) => { + await whileImpersonating(hre, '0x73AF3bcf944a6559933396c1577B257e2054D935', async (compWhale) => { await compContract.connect(compWhale).transfer(rsrTrader.address, fp('1e5')) }) diff --git a/tasks/testing/upgrade-checker-utils/rtokens.ts b/tasks/testing/upgrade-checker-utils/rtokens.ts index 8b76df7908..59db68913d 100644 --- a/tasks/testing/upgrade-checker-utils/rtokens.ts +++ b/tasks/testing/upgrade-checker-utils/rtokens.ts @@ -3,13 +3,14 @@ import { ONE_PERIOD, TradeKind } from '#/common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { BigNumber, ContractFactory } from 'ethers' import { formatEther } from 'ethers/lib/utils' -import { advanceTime } from '#/utils/time' +import { advanceBlocks, advanceTime } from '#/utils/time' import { fp } from '#/common/numbers' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { callAndGetNextTrade, runBatchTrade, runDutchTrade } from './trades' import { CollateralStatus } from '#/common/constants' -import { FacadeAct } from '@typechain/FacadeAct' -import { FacadeRead } from '@typechain/FacadeRead' +import { ActFacet } from '@typechain/ActFacet' +import { ReadFacet } from '@typechain/ReadFacet' +import { pushOraclesForward } from '../upgrade-checker-utils/oracles' type Balances = { [key: string]: BigNumber } @@ -44,8 +45,15 @@ export const redeemRTokens = async ( 'BasketHandlerP1', await main.basketHandler() ) + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) - const redeemQuote = await basketHandler.quote(redeemAmount, 0) + await assetRegistry.refresh() + const basketsNeeded = await rToken.basketsNeeded() + const totalSupply = await rToken.totalSupply() + const redeemQuote = await basketHandler.quote(redeemAmount.mul(basketsNeeded).div(totalSupply), 0) const expectedTokens = redeemQuote.erc20s const expectedBalances: Balances = {} let log = '' @@ -91,9 +99,9 @@ export const customRedeemRTokens = async ( console.log(`\nCustom Redeeming ${formatEther(redeemAmount)}...`) const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) - const FacadeReadFactory: ContractFactory = await hre.ethers.getContractFactory('FacadeRead') - const facadeRead = await FacadeReadFactory.deploy() - const redeemQuote = await facadeRead.callStatic.redeemCustom( + const ReadFacetFactory: ContractFactory = await hre.ethers.getContractFactory('ReadFacet') + const readFacet = await ReadFacetFactory.deploy() + const redeemQuote = await readFacet.callStatic.redeemCustom( rToken.address, redeemAmount, [basketNonce], @@ -118,7 +126,7 @@ export const customRedeemRTokens = async ( [basketNonce], [fp('1')], expectedTokens, - expectedQuantities.map((q) => q.mul(99).div(100)) + expectedQuantities.map((q: BigNumber) => q.mul(99).div(100)) ) const postRedeemRTokenBal = await rToken.balanceOf(user.address) const postRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) @@ -158,7 +166,8 @@ export const recollateralize = async ( } const recollateralizeBatch = async (hre: HardhatRuntimeEnvironment, rtokenAddress: string) => { - console.log(`\n\n* * * * * Recollateralizing (Batch) RToken ${rtokenAddress}...`) + console.log(`* * * * * Recollateralizing (Batch) RToken ${rtokenAddress}...`) + const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const backingManager = await hre.ethers.getContractAt( @@ -170,9 +179,9 @@ const recollateralizeBatch = async (hre: HardhatRuntimeEnvironment, rtokenAddres await main.basketHandler() ) - // Deploy FacadeAct - const FacadeActFactory: ContractFactory = await hre.ethers.getContractFactory('FacadeAct') - const facadeAct = await FacadeActFactory.deploy() + // Deploy ActFacet + const FacadeActFactory: ContractFactory = await hre.ethers.getContractFactory('ActFacet') + const facadeAct = await FacadeActFactory.deploy() // Move post trading delay await advanceTime(hre, (await backingManager.tradingDelay()) + 1) @@ -211,7 +220,10 @@ const recollateralizeBatch = async (hre: HardhatRuntimeEnvironment, rtokenAddres } const recollateralizeDutch = async (hre: HardhatRuntimeEnvironment, rtokenAddress: string) => { - console.log(`\n\n* * * * * Recollateralizing (Dutch) RToken ${rtokenAddress}...`) + console.log('*') + console.log(`* * * * * Recollateralizing RToken (Dutch): ${rtokenAddress}...`) + console.log('*') + const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) @@ -224,11 +236,8 @@ const recollateralizeDutch = async (hre: HardhatRuntimeEnvironment, rtokenAddres await main.basketHandler() ) - // Move post trading delay - await advanceTime(hre, (await backingManager.tradingDelay()) + 1) - let tradesRemain = false - let sellToken: string = '' + let sellToken = '' const [newTradeCreated, initialSellToken] = await callAndGetNextTrade( backingManager.rebalance(TradeKind.DUTCH_AUCTION), @@ -239,9 +248,12 @@ const recollateralizeDutch = async (hre: HardhatRuntimeEnvironment, rtokenAddres tradesRemain = true sellToken = initialSellToken - while (tradesRemain) { + for (let i = 0; tradesRemain; i++) { + // every other trade, push oracles forward (some oracles have 3600s timeout) + if (i % 2 == 1) await pushOraclesForward(hre, rtokenAddress, []) ;[tradesRemain, sellToken] = await runDutchTrade(hre, backingManager, sellToken) - await advanceTime(hre, ONE_PERIOD.toString()) + + await advanceBlocks(hre, 1) } } diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 6069ca478e..6e0ce64b47 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -1,6 +1,7 @@ -import { QUEUE_START, TradeKind, TradeStatus } from '#/common/constants' +import { MAX_UINT256, QUEUE_START, TradeKind, TradeStatus } from '#/common/constants' import { bn, fp } from '#/common/numbers' import { whileImpersonating } from '#/utils/impersonation' +import { networkConfig } from '../../../common/configuration' import { advanceBlocks, advanceTime, @@ -11,9 +12,9 @@ import { DutchTrade } from '@typechain/DutchTrade' import { GnosisTrade } from '@typechain/GnosisTrade' import { TestITrading } from '@typechain/TestITrading' import { BigNumber, ContractTransaction } from 'ethers' -import { Interface, LogDescription } from 'ethers/lib/utils' +import { LogDescription } from 'ethers/lib/utils' import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { collateralToUnderlying, whales } from './constants' +import { whales } from './constants' import { logToken } from './logs' export const runBatchTrade = async ( @@ -35,7 +36,9 @@ export const runBatchTrade = async ( } const buyTokenAddress = await trade.buy() - console.log(`Running trade: sell ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...`) + console.log( + `Running batch trade: sell ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...` + ) const endTime = await trade.endTime() const worstPrice = await trade.worstCasePrice() // trade.buy() per trade.sell() const auctionId = await trade.auctionId() @@ -91,8 +94,8 @@ export const runDutchTrade = async ( // buy & sell are from the perspective of the auction-starter // bid() flips it to be from the perspective of the trader - let tradesRemain: boolean = false - let newSellToken: string = '' + let tradesRemain = false + let newSellToken = '' const tradeAddr = await trader.trades(tradeToken) const trade = await hre.ethers.getContractAt('DutchTrade', tradeAddr) @@ -103,32 +106,46 @@ export const runDutchTrade = async ( } const buyTokenAddress = await trade.buy() - console.log(`Running trade: sell ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...`) + console.log('=========') + console.log( + `Running Dutch Trade: Selling ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...` + ) const endBlock = await trade.endBlock() - const whaleAddr = whales[buyTokenAddress.toLowerCase()] - - // Bid close to end block - await advanceBlocks(hre, endBlock.sub(await getLatestBlockNumber(hre)).sub(5)) + const [tester] = await hre.ethers.getSigners() + + // Bid near 1:1 point, which occurs at the 70% mark + const toAdvance = endBlock + .sub(await getLatestBlockNumber(hre)) + .mul(7) + .div(10) + await advanceBlocks(hre, toAdvance) const buyAmount = await trade.bidAmount(await getLatestBlockNumber(hre)) // Ensure funds available - await getTokens(hre, buyTokenAddress, buyAmount, whaleAddr) + await getTokens(hre, buyTokenAddress, buyAmount, tester.address) - await whileImpersonating(hre, whaleAddr, async (whale) => { - const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) - // Bid + const buyToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) + await buyToken.connect(tester).approve(router.address, MAX_UINT256) - ;[tradesRemain, newSellToken] = await callAndGetNextTrade( - router.bid(trade.address, await router.signer.getAddress()), - trader - ) - }) + // Bid + ;[tradesRemain, newSellToken] = await callAndGetNextTrade( + router.bid(trade.address, await router.signer.getAddress()), + trader + ) + + console.log( + 'Trade State:', + TradeStatus[await trade.status()], + await trade.canSettle(), + await trade.bidder(), + tester.address + ) if ( (await trade.canSettle()) || (await trade.status()) != TradeStatus.CLOSED || - (await trade.bidder()) != whaleAddr + (await trade.bidder()) != router.address ) { throw new Error(`Error settling Dutch Trade`) } @@ -143,26 +160,34 @@ export const callAndGetNextTrade = async ( tx: Promise, trader: TestITrading ): Promise<[boolean, string]> => { - let tradesRemain: boolean = false - let newSellToken: string = '' + let tradesRemain = false + let newSellToken = '' // Process transaction and get next trade const r = await tx const resp = await r.wait() - const iface: Interface = trader.interface + const iface = trader.interface + for (const event of resp.events!) { let parsedLog: LogDescription | undefined try { parsedLog = iface.parseLog(event) + // eslint-disable-next-line no-empty } catch {} + if (parsedLog && parsedLog.name == 'TradeStarted') { + // TODO: Improve this to include proper token details and parsing. + console.log( - `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( + ` + ====== Trade Started: Selling ${logToken(parsedLog.args.sell)} / Buying ${logToken( parsedLog.args.buy - )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ - parsedLog.args.sellAmount - }` + )} ====== + minBuyAmount: ${parsedLog.args.minBuyAmount} + sellAmount: ${parsedLog.args.sellAmount} + `.trim() ) + tradesRemain = true newSellToken = parsedLog.args.sell } @@ -170,6 +195,7 @@ export const callAndGetNextTrade = async ( return [tradesRemain, newSellToken] } + // impersonate the whale to provide the required tokens to recipient export const getTokens = async ( hre: HardhatRuntimeEnvironment, @@ -177,6 +203,7 @@ export const getTokens = async ( amount: BigNumber, recipient: string ) => { + console.log('Acquiring tokens...', tokenAddress) switch (tokenAddress) { case '0x60C384e226b120d93f3e0F4C502957b2B9C32B15': // saUSDC case '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9': // saUSDT @@ -192,44 +219,6 @@ export const getTokens = async ( } } -// mint regular cTokens for an amount of `underlying` -const mintCToken = async ( - hre: HardhatRuntimeEnvironment, - tokenAddress: string, - amount: BigNumber, - recipient: string -) => { - const collateral = await hre.ethers.getContractAt('ICToken', tokenAddress) - const underlying = await hre.ethers.getContractAt( - 'ERC20Mock', - collateralToUnderlying[tokenAddress.toLowerCase()] - ) - await whileImpersonating(hre, whales[tokenAddress.toLowerCase()], async (whaleSigner) => { - await underlying.connect(whaleSigner).approve(collateral.address, amount) - await collateral.connect(whaleSigner).mint(amount) - const bal = await collateral.balanceOf(whaleSigner.address) - await collateral.connect(whaleSigner).transfer(recipient, bal) - }) -} - -// mints staticAToken for an amount of `underlying` -const mintStaticAToken = async ( - hre: HardhatRuntimeEnvironment, - tokenAddress: string, - amount: BigNumber, - recipient: string -) => { - const collateral = await hre.ethers.getContractAt('StaticATokenLM', tokenAddress) - const underlying = await hre.ethers.getContractAt( - 'ERC20Mock', - collateralToUnderlying[tokenAddress.toLowerCase()] - ) - await whileImpersonating(hre, whales[tokenAddress.toLowerCase()], async (whaleSigner) => { - await underlying.connect(whaleSigner).approve(collateral.address, amount) - await collateral.connect(whaleSigner).deposit(recipient, amount, 0, true) - }) -} - // get a specific amount of wrapped cTokens const getCTokenVault = async ( hre: HardhatRuntimeEnvironment, @@ -283,7 +272,77 @@ const getERC20Tokens = async ( recipient: string ) => { const token = await hre.ethers.getContractAt('ERC20Mock', tokenAddress) - await whileImpersonating(hre, whales[token.address.toLowerCase()], async (whaleSigner) => { - await token.connect(whaleSigner).transfer(recipient, amount) - }) + + // special-cases for wrappers with 0 supply + const wcUSDCv3 = await hre.ethers.getContractAt( + 'CusdcV3Wrapper', + '0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A' + ) + const saEthUSDC = await hre.ethers.getContractAt( + 'IStaticATokenV3LM', + networkConfig['1'].tokens.saEthUSDC! + ) + const saEthPyUSD = await hre.ethers.getContractAt( + 'IStaticATokenV3LM', + networkConfig['1'].tokens.saEthPyUSD! + ) + const stkcvxeUSDFRAXBP = await hre.ethers.getContractAt( + 'ConvexStakingWrapper', + '0x8e33D5aC344f9F2fc1f2670D45194C280d4fBcF1' + ) + + if (tokenAddress == wcUSDCv3.address) { + await whileImpersonating( + hre, + whales[networkConfig['1'].tokens.cUSDCv3!.toLowerCase()], + async (whaleSigner) => { + const cUSDCv3 = await hre.ethers.getContractAt( + 'ERC20Mock', + networkConfig['1'].tokens.cUSDCv3! + ) + await cUSDCv3.connect(whaleSigner).approve(wcUSDCv3.address, 0) + await cUSDCv3.connect(whaleSigner).approve(wcUSDCv3.address, MAX_UINT256) + await wcUSDCv3.connect(whaleSigner).deposit(amount.mul(2)) + const bal = await wcUSDCv3.balanceOf(whaleSigner.address) + await wcUSDCv3.connect(whaleSigner).transfer(recipient, bal) + } + ) + } else if (tokenAddress == saEthUSDC.address) { + await whileImpersonating( + hre, + whales[networkConfig['1'].tokens.USDC!.toLowerCase()], + async (whaleSigner) => { + const USDC = await hre.ethers.getContractAt('ERC20Mock', networkConfig['1'].tokens.USDC!) + await USDC.connect(whaleSigner).approve(saEthUSDC.address, amount.mul(2)) + await saEthUSDC.connect(whaleSigner).deposit(amount.mul(2), whaleSigner.address, 0, true) + await token.connect(whaleSigner).transfer(recipient, amount) // saEthUSDC transfer + } + ) + } else if (tokenAddress == saEthPyUSD.address) { + await whileImpersonating( + hre, + whales[networkConfig['1'].tokens.pyUSD!.toLowerCase()], + async (whaleSigner) => { + const pyUSD = await hre.ethers.getContractAt('ERC20Mock', networkConfig['1'].tokens.pyUSD!) + await pyUSD.connect(whaleSigner).approve(saEthPyUSD.address, amount.mul(2)) + await saEthPyUSD.connect(whaleSigner).deposit(amount.mul(2), whaleSigner.address, 0, true) + await token.connect(whaleSigner).transfer(recipient, amount) // saEthPyUSD transfer + } + ) + } else if (tokenAddress == stkcvxeUSDFRAXBP.address) { + const lpTokenAddr = '0xaeda92e6a3b1028edc139a4ae56ec881f3064d4f' + + await whileImpersonating(hre, whales[lpTokenAddr], async (whaleSigner) => { + const lpToken = await hre.ethers.getContractAt('ERC20Mock', lpTokenAddr) + await lpToken.connect(whaleSigner).approve(stkcvxeUSDFRAXBP.address, amount.mul(2)) + await stkcvxeUSDFRAXBP.connect(whaleSigner).deposit(amount.mul(2), whaleSigner.address) + await token.connect(whaleSigner).transfer(recipient, amount) + }) + } else { + const addr = whales[token.address.toLowerCase()] + if (!addr) throw new Error('missing whale for ' + tokenAddress) + await whileImpersonating(hre, whales[token.address.toLowerCase()], async (whaleSigner) => { + await token.connect(whaleSigner).transfer(recipient, amount) + }) + } } diff --git a/tasks/testing/upgrade-checker-utils/upgrades/3_3_0_plugins.ts b/tasks/testing/upgrade-checker-utils/upgrades/3_3_0_plugins.ts new file mode 100644 index 0000000000..93cdc5ce88 --- /dev/null +++ b/tasks/testing/upgrade-checker-utils/upgrades/3_3_0_plugins.ts @@ -0,0 +1,207 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { expect } from 'chai' +import { ProposalBuilder, buildProposal } from '../governance' +import { Proposal } from '#/utils/subgraph' +import { networkConfig } from '#/common/configuration' +import { bn, fp, toBNDecimals } from '#/common/numbers' +import { CollateralStatus, TradeKind, ZERO_ADDRESS } from '#/common/constants' +import { setOraclePrice } from '../oracles' +import { whileImpersonating } from '#/utils/impersonation' +import { whales } from '../constants' +import { getTokens, runDutchTrade } from '../trades' +import { + advanceTime, + advanceToTimestamp, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '#/utils/time' + +export default async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string, + governorAddress: string +) => { + console.log('\n* * * * * Run checks for release 3.3.0...') + const [tester] = await hre.ethers.getSigners() + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('IMain', await rToken.main()) + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const timelockAddress = await governor.timelock() + const timelock = await hre.ethers.getContractAt('TimelockController', timelockAddress) + + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + const backingManager = await hre.ethers.getContractAt( + 'BackingManagerP1', + await main.backingManager() + ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + const furnace = await hre.ethers.getContractAt('FurnaceP1', await main.furnace()) + const rsrTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rsrTrader()) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) + const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) + + console.log('\n3.3.0 check succeeded!') +} + +const saUSDTCollateralAddr = '0x8AD3055286f4E59B399616Bd6BEfE24F64573928' +const saUSDCCollateralAddr = '0x6E14943224d6E4F7607943512ba17DbBA9524B8e' +const saEthUSDCCollateralAddr = '0x05beee046A5C28844804E679aD5587046dBffbc0' +const wcUSDCv3CollateralAddr = '0xf0Fb23485057Fd88C80B9CEc8b433FdA47e0a07A' +const cUSDTCollateralAddr = '0x1269BFa56EcaE9D6d5003810D4a35bf8479376b8' +const saEthPyUSDCollateralAddr = '0xe176A5ebFB873D5b3cf1909d0EdaE4FE095F5bc7' +const TUSDCollateralAddr = '0x7F9999B2C9D310a5f48dfD070eb5129e1e8565E2' +const cUSDCVaultCollateralAddr = '0x50a9d529ea175cde72525eaa809f5c3c47daa1bb' +const cUSDTVaultCollateralAddr = '0x5757fF814da66a2B4f9D11d48570d742e246CfD9' + +const saEthUSDCERC20Addr = '0x093cB4f405924a0C468b43209d5E466F1dd0aC7d' +const wcUSDCv3ERC20Addr = '0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A' +const cUSDTVaultERC20Addr = '0x4Be33630F92661afD646081BC29079A38b879aA0' +const saUSDTERC20Addr = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9' +const cUSDTERC20Addr = '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9' +const saEthPyUSDERC20Addr = '0x8d6E0402A3E3aD1b43575b05905F9468447013cF' + +const batchTradeImplAddr = '0x803a52c5DAB69B78419bb160051071eF2F9Fd227' +const dutchTradeImplAddr = '0x4eDEb80Ce684A890Dd58Ae0d9762C38731b11b99' + +export const proposal_3_3_0_step_1: ProposalBuilder = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string +): Promise => { + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + + // Build proposal + const txs = [ + await broker.populateTransaction.setDutchTradeImplementation(dutchTradeImplAddr), + await broker.populateTransaction.setBatchTradeImplementation(batchTradeImplAddr), + await assetRegistry.populateTransaction.swapRegistered(saUSDTCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(saUSDCCollateralAddr), + await assetRegistry.populateTransaction.register(saEthUSDCCollateralAddr), + await assetRegistry.populateTransaction.register(wcUSDCv3CollateralAddr), + await assetRegistry.populateTransaction.register(cUSDTCollateralAddr), + await assetRegistry.populateTransaction.register(saEthPyUSDCollateralAddr), + await basketHandler.populateTransaction.setPrimeBasket( + [saEthUSDCERC20Addr, wcUSDCv3ERC20Addr, cUSDTVaultERC20Addr, saUSDTERC20Addr], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ), + await basketHandler.populateTransaction.refreshBasket(), + await rToken.populateTransaction.setRedemptionThrottleParams({ + amtRate: bn('25e23'), + pctRate: bn('125000000000000000'), + }), + ] + + const description = 'Step 1/4 of eUSD 3.3.0 plugin upgrade.' + + return buildProposal(txs, description) +} + +export const proposal_3_3_0_step_2: ProposalBuilder = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string +): Promise => { + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + + // Build proposal + const txs = [ + await basketHandler.populateTransaction.setPrimeBasket( + [saEthUSDCERC20Addr, wcUSDCv3ERC20Addr, cUSDTERC20Addr, saUSDTERC20Addr], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ), + await basketHandler.populateTransaction.refreshBasket(), + ] + + const description = 'Step 2/4 of eUSD 3.3.0 plugin upgrade.' + + return buildProposal(txs, description) +} + +export const proposal_3_3_0_step_3: ProposalBuilder = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string +): Promise => { + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + + // Build proposal + const txs = [ + await basketHandler.populateTransaction.setPrimeBasket( + [saEthUSDCERC20Addr, wcUSDCv3ERC20Addr, cUSDTERC20Addr, saEthPyUSDERC20Addr], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ), + await basketHandler.populateTransaction.refreshBasket(), + ] + + const description = 'Step 3/4 of eUSD 3.3.0 plugin upgrade.' + + return buildProposal(txs, description) +} + +export const proposal_3_3_0_step_4: ProposalBuilder = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string +): Promise => { + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + + // Build proposal + const txs = [ + await rToken.populateTransaction.setIssuanceThrottleParams({ + amtRate: bn('2e24'), + pctRate: bn('100000000000000000'), + }), + await basketHandler.populateTransaction.setBackupConfig( + '0x5553440000000000000000000000000000000000000000000000000000000000', + bn('2000000000000000000'), + [ + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x8e870d67f660d95d5be530380d0ec0bd388289e1', + '0x6b175474e89094c44da98b954eedeac495271d0f', + ] + ), + await assetRegistry.populateTransaction.unregister(TUSDCollateralAddr), + await assetRegistry.populateTransaction.unregister(cUSDCVaultCollateralAddr), + await assetRegistry.populateTransaction.unregister(cUSDTVaultCollateralAddr), + await assetRegistry.populateTransaction.unregister(saUSDCCollateralAddr), + await assetRegistry.populateTransaction.unregister(saUSDTCollateralAddr), + ] + + const description = 'Step 4/4 of eUSD 3.3.0 plugin upgrade.' + + return buildProposal(txs, description) +} diff --git a/tasks/testing/upgrade-checker.ts b/tasks/testing/upgrade-checker.ts index 0cc0f5436d..057f4ee5f7 100644 --- a/tasks/testing/upgrade-checker.ts +++ b/tasks/testing/upgrade-checker.ts @@ -4,27 +4,30 @@ import { getChainId } from '../../common/blockchain-utils' import { whileImpersonating } from '#/utils/impersonation' import { useEnv } from '#/utils/env' import { expect } from 'chai' -import { resetFork } from '#/utils/chain' -import { bn, fp } from '#/common/numbers' -import { TradeKind } from '#/common/constants' +import { fp } from '#/common/numbers' +import { MAX_UINT256, TradeKind } from '#/common/constants' import { formatEther, formatUnits } from 'ethers/lib/utils' -import { pushOraclesForward } from './upgrade-checker-utils/oracles' -import { - recollateralize, - redeemRTokens, - customRedeemRTokens, -} from './upgrade-checker-utils/rtokens' +import { recollateralize, redeemRTokens } from './upgrade-checker-utils/rtokens' import { claimRsrRewards } from './upgrade-checker-utils/rewards' import { whales } from './upgrade-checker-utils/constants' -import runChecks3_0_0, { proposal_3_0_0 } from './upgrade-checker-utils/upgrades/3_0_0' +import { pushOraclesForward } from './upgrade-checker-utils/oracles' +import runChecks3_3_0, { + proposal_3_3_0_step_1, + proposal_3_3_0_step_2, + proposal_3_3_0_step_3, + proposal_3_3_0_step_4, +} from './upgrade-checker-utils/upgrades/3_3_0_plugins' import { - passAndExecuteProposal, + passProposal, + executeProposal, proposeUpgrade, stakeAndDelegateRsr, + moveProposalToActive, + voteProposal, } from './upgrade-checker-utils/governance' -import { advanceBlocks, advanceTime, getLatestBlockNumber } from '#/utils/time' +import { advanceTime, getLatestBlockNumber } from '#/utils/time' -// run script for eUSD (version 3.0.0) +// run script for eUSD (version 3.3.0) // npx hardhat upgrade-checker --rtoken 0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F --governor 0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6 /* @@ -42,14 +45,17 @@ import { advanceBlocks, advanceTime, getLatestBlockNumber } from '#/utils/time' 21-34 more points of work to make this more generic */ -task('upgrade-checker', 'Mints all the tokens to an address') +interface Params { + rtoken: string + governor: string + proposalId?: string +} + +task('upgrade-checker', 'Runs a proposal and confirms can fully rebalance + redeem + mint') .addParam('rtoken', 'the address of the RToken being upgraded') .addParam('governor', 'the address of the OWNER of the RToken being upgraded') - .addOptionalParam('proposalid', 'the ID of the governance proposal', undefined) - .setAction(async (params, hre) => { - await resetFork(hre, Number(useEnv('FORK_BLOCK'))) - const [tester] = await hre.ethers.getSigners() - + .addOptionalParam('proposalId', 'the ID of the governance proposal', undefined) + .setAction(async (params: Params, hre) => { const chainId = await getChainId(hre) // make sure config exists @@ -63,30 +69,112 @@ task('upgrade-checker', 'Mints all the tokens to an address') } // make sure subgraph is configured - if (!useEnv('SUBGRAPH_URL')) { + if (params.proposalId && !useEnv('SUBGRAPH_URL')) { throw new Error('SUBGRAPH_URL required for subgraph queries') } - console.log(`starting at block ${await getLatestBlockNumber(hre)}`) + console.log(`Network Block: ${await getLatestBlockNumber(hre)}`) + + await hre.run('propose', { + step: '1', + rtoken: params.rtoken, + governor: params.governor, + }) - // 1. Approve and execute the governance proposal - if (!params.proposalid) { - const proposal = await proposeUpgrade(hre, params.rtoken, params.governor, proposal_3_0_0) + await hre.run('recollateralize', { + rtoken: params.rtoken, + governor: params.governor, + }) - await passAndExecuteProposal( - hre, - params.rtoken, - params.governor, - proposal.proposalId!, - proposal - ) - } else { - await passAndExecuteProposal(hre, params.rtoken, params.governor, params.proposalid) + await hre.run('propose', { + step: '2', + rtoken: params.rtoken, + governor: params.governor, + }) + + await hre.run('recollateralize', { + rtoken: params.rtoken, + governor: params.governor, + }) + + await hre.run('propose', { + step: '3', + rtoken: params.rtoken, + governor: params.governor, + }) + + await hre.run('recollateralize', { + rtoken: params.rtoken, + governor: params.governor, + }) + + await hre.run('propose', { + step: '4', + rtoken: params.rtoken, + governor: params.governor, + }) + + const rToken = await hre.ethers.getContractAt('IRToken', params.rtoken) + const main = await hre.ethers.getContractAt('IMain', await rToken.main()) + const assetRegistry = await hre.ethers.getContractAt( + 'IAssetRegistry', + await main.assetRegistry() + ) + const basketHandler = await hre.ethers.getContractAt( + 'IBasketHandler', + await main.basketHandler() + ) + await assetRegistry.refresh() + if (!((await basketHandler.status()) == 0)) throw new Error('Basket is not SOUND') + if (!(await basketHandler.fullyCollateralized())) { + throw new Error('Basket is not fully collateralized') } + console.log('Basket is SOUND and fully collateralized!') + }) - // we pushed the chain forward, so we need to keep the rToken SOUND - await pushOraclesForward(hre, params.rtoken) +interface ProposeParams { + step: string + rtoken: string + governor: string + proposalId?: string +} +task('propose', 'propose a gov action') + .addParam('step', 'the step of the proposal') + .addParam('rtoken', 'the address of the RToken being upgraded') + .addParam('governor', 'the address of the OWNER of the RToken being upgraded') + .setAction(async (params: ProposeParams, hre) => { + const stepFunction = (() => { + console.log(`=========================== STEP ${params.step} ===============================`) + if (params.step === '1') { + return proposal_3_3_0_step_1 + } + if (params.step === '2') { + return proposal_3_3_0_step_2 + } + if (params.step === '3') { + return proposal_3_3_0_step_3 + } + if (params.step === '4') { + return proposal_3_3_0_step_4 + } + + throw Error('Invalid step') + })() + + const proposal = await proposeUpgrade(hre, params.rtoken, params.governor, stepFunction) + + await moveProposalToActive(hre, params.rtoken, params.governor, proposal.proposalId) + await voteProposal(hre, params.rtoken, params.governor, proposal.proposalId) + await passProposal(hre, params.rtoken, params.governor, proposal.proposalId) + await executeProposal(hre, params.rtoken, params.governor, proposal.proposalId, proposal) + }) + +task('recollateralize') + .addParam('rtoken', 'the address of the RToken being upgraded') + .addParam('governor', 'the address of the OWNER of the RToken being upgraded') + .setAction(async (params: Params, hre) => { + const [tester] = await hre.ethers.getSigners() const rToken = await hre.ethers.getContractAt('RTokenP1', params.rtoken) // 2. Bring back to fully collateralized @@ -99,143 +187,76 @@ task('upgrade-checker', 'Mints all the tokens to an address') 'BackingManagerP1', await main.backingManager() ) - const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + // const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) - // Move past trading delay + /* + recollateralize + */ await advanceTime(hre, (await backingManager.tradingDelay()) + 1) + await pushOraclesForward(hre, params.rtoken, []) + await recollateralize(hre, rToken.address, TradeKind.DUTCH_AUCTION).catch((e: Error) => { + if (e.message.includes('already collateralized')) { + console.log('Already Collateralized!') - await recollateralize( - hre, - rToken.address, - (await broker.dutchAuctionLength()) > 0 ? TradeKind.DUTCH_AUCTION : TradeKind.BATCH_AUCTION - ) - - // 3. Run various checks - const saUsdtAddress = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase() - const saUsdcAddress = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase() - const usdtAddress = networkConfig['1'].tokens.USDT! - const usdcAddress = networkConfig['1'].tokens.USDC! - const cUsdtAddress = networkConfig['1'].tokens.cUSDT! - const cUsdcAddress = networkConfig['1'].tokens.cUSDC! - const cUsdtVaultAddress = '0x4Be33630F92661afD646081BC29079A38b879aA0'.toLowerCase() - const cUsdcVaultAddress = '0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase() - - /* + return + } - mint + throw e + }) + if (!(await basketHandler.fullyCollateralized())) throw new Error('Failed to recollateralize') - this is another area that needs to be made general - for now, we just want to be able to test eUSD, so minting and redeeming eUSD is fine + // Give `tester` RTokens from a whale + const redeemAmt = fp('1e3') + await whileImpersonating(hre, whales[params.rtoken.toLowerCase()], async (whaleSigner) => { + await rToken.connect(whaleSigner).transfer(tester.address, redeemAmt) + }) + if (!(await rToken.balanceOf(tester.address)).gte(redeemAmt)) throw new Error('missing R') + /* + redeem */ + await redeemRTokens(hre, tester, params.rtoken, redeemAmt) - const initialBal = bn('2e11') - const issueAmount = fp('1e5') - const usdt = await hre.ethers.getContractAt('ERC20Mock', usdtAddress) - const usdc = await hre.ethers.getContractAt('ERC20Mock', usdcAddress) - const saUsdt = await hre.ethers.getContractAt('StaticATokenLM', saUsdtAddress) - const cUsdt = await hre.ethers.getContractAt('ICToken', cUsdtAddress) - const cUsdtVault = await hre.ethers.getContractAt('CTokenWrapper', cUsdtVaultAddress) - const saUsdc = await hre.ethers.getContractAt('StaticATokenLM', saUsdcAddress) - const cUsdc = await hre.ethers.getContractAt('ICToken', cUsdcAddress) - const cUsdcVault = await hre.ethers.getContractAt('CTokenWrapper', cUsdcVaultAddress) - - // get saUsdt - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDT!.toLowerCase()], - async (usdtSigner) => { - await usdt.connect(usdtSigner).approve(saUsdt.address, initialBal) - await saUsdt.connect(usdtSigner).deposit(tester.address, initialBal, 0, true) - } - ) - const saUsdtBal = await saUsdt.balanceOf(tester.address) - await saUsdt.connect(tester).approve(rToken.address, saUsdtBal) - - // get cUsdtVault - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDT!.toLowerCase()], - async (usdtSigner) => { - await usdt.connect(usdtSigner).approve(cUsdt.address, initialBal) - await cUsdt.connect(usdtSigner).mint(initialBal) - const bal = await cUsdt.balanceOf(usdtSigner.address) - await cUsdt.connect(usdtSigner).approve(cUsdtVault.address, bal) - await cUsdtVault.connect(usdtSigner).deposit(bal, tester.address) - } - ) - - const cUsdtVaultBal = await cUsdtVault.balanceOf(tester.address) - await cUsdtVault.connect(tester).approve(rToken.address, cUsdtVaultBal) + // 3. Run the 3.0.0 checks + await runChecks3_3_0(hre, params.rtoken, params.governor) - // get saUsdc - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDC!.toLowerCase()], - async (usdcSigner) => { - await usdc.connect(usdcSigner).approve(saUsdc.address, initialBal) - await saUsdc.connect(usdcSigner).deposit(tester.address, initialBal, 0, true) - } - ) - const saUsdcBal = await saUsdc.balanceOf(tester.address) - await saUsdc.connect(tester).approve(rToken.address, saUsdcBal) + /* + mint + */ - // get cUsdcVault - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDC!.toLowerCase()], - async (usdcSigner) => { - await usdc.connect(usdcSigner).approve(cUsdc.address, initialBal) - await cUsdc.connect(usdcSigner).mint(initialBal) - const bal = await cUsdc.balanceOf(usdcSigner.address) - await cUsdc.connect(usdcSigner).approve(cUsdcVault.address, bal) - await cUsdcVault.connect(usdcSigner).deposit(bal, tester.address) - } - ) - const cUsdcVaultBal = await cUsdcVault.balanceOf(tester.address) - await cUsdcVault.connect(tester).approve(rToken.address, cUsdcVaultBal) + const issueAmt = redeemAmt.div(2) + console.log(`\nIssuing ${formatEther(issueAmt)} RTokens...`) + const [erc20s] = await basketHandler.quote(fp('1'), 0) + for (const e of erc20s) { + const erc20 = await hre.ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + e + ) + await erc20.connect(tester).approve(rToken.address, MAX_UINT256) // max approval + } + const preBal = await rToken.balanceOf(tester.address) + await rToken.connect(tester).issue(issueAmt) - console.log(`\nIssuing ${formatEther(issueAmount)} RTokens...`) - await rToken.connect(tester).issue(issueAmount) const postIssueBal = await rToken.balanceOf(tester.address) - if (!postIssueBal.eq(issueAmount)) { + if (!postIssueBal.eq(preBal.add(issueAmt))) { throw new Error( `Did not issue the correct amount of RTokens. wanted: ${formatUnits( - issueAmount, + preBal.add(issueAmt), 'mwei' )} balance: ${formatUnits(postIssueBal, 'mwei')}` ) } - console.log('successfully minted RTokens') - - /* - - redeem - - */ - const redeemAmount = fp('5e4') - await redeemRTokens(hre, tester, params.rtoken, redeemAmount) - - // 3. Run the 3.0.0 checks - await pushOraclesForward(hre, params.rtoken) - await runChecks3_0_0(hre, params.rtoken, params.governor) - - // we pushed the chain forward, so we need to keep the rToken SOUND - await pushOraclesForward(hre, params.rtoken) + console.log('Successfully minted RTokens') /* - claim rewards - */ await claimRsrRewards(hre, params.rtoken) /* - staking/unstaking - */ // get RSR @@ -251,60 +272,74 @@ task('upgrade-checker', 'Mints all the tokens to an address') const balPrevRSR = await rsr.balanceOf(stRSR.address) const balPrevStRSR = await stRSR.balanceOf(tester.address) + const testerBal = await rsr.balanceOf(tester.address) await stakeAndDelegateRsr(hre, rToken.address, tester.address) - expect(await rsr.balanceOf(stRSR.address)).to.equal(balPrevRSR.add(stakeAmount)) + expect(await rsr.balanceOf(stRSR.address)).to.equal(balPrevRSR.add(testerBal)) expect(await stRSR.balanceOf(tester.address)).to.be.gt(balPrevStRSR) + }) - /* +task('eusd-q1-2024-test', 'Test deployed eUSD Proposals').setAction(async (_, hre) => { + console.log(`Network Block: ${await getLatestBlockNumber(hre)}`) - switch basket and recollateralize - using Batch Auctions - Also check for custom redemption + const RTokenAddress = '0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f' + const RTokenGovernor = '0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6' - */ + const ProposalIdOne = + '114052081659629247617665835769035094910371266951213483500173240902265689564540' + const ProposalIdTwo = + '84013999114211651083886802889501217056607481369823717462033802424606122383108' - // we pushed the chain forward, so we need to keep the rToken SOUND - await pushOraclesForward(hre, params.rtoken) - - const bas = await basketHandler.getPrimeBasket() - console.log(bas.erc20s) - - const prevNonce = await basketHandler.nonce() - const governor = await hre.ethers.getContractAt('Governance', params.governor) - const timelockAddress = await governor.timelock() - await whileImpersonating(hre, timelockAddress, async (tl) => { - await basketHandler - .connect(tl) - .setPrimeBasket([saUsdtAddress, cUsdtVaultAddress], [fp('0.5'), fp('0.5')]) - await basketHandler.connect(tl).refreshBasket() - const tradingDelay = await backingManager.tradingDelay() - await advanceBlocks(hre, tradingDelay / 12 + 1) - await advanceTime(hre, tradingDelay + 1) - }) + // Make sure both proposals are active. + await moveProposalToActive(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await moveProposalToActive(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) - const b = await basketHandler.getPrimeBasket() - console.log(b.erc20s) + await voteProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await voteProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) - /* - custom redemption - */ - // Cannot do normal redeem - expect(await basketHandler.fullyCollateralized()).to.equal(false) - await expect(rToken.connect(tester).redeem(redeemAmount)).to.be.revertedWith( - 'partial redemption; use redeemCustom' - ) + await passProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await passProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) - // Do custom redemption on previous basket - await customRedeemRTokens(hre, tester, params.rtoken, prevNonce, redeemAmount) + await executeProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await hre.run('recollateralize', { + rtoken: RTokenAddress, + governor: RTokenGovernor, + }) - // Recollateralize using Batch auctions - await recollateralize(hre, rToken.address, TradeKind.BATCH_AUCTION) + await executeProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) + await hre.run('recollateralize', { + rtoken: RTokenAddress, + governor: RTokenGovernor, }) +}) -task('propose', 'propose a gov action') - .addParam('rtoken', 'the address of the RToken being upgraded') - .addParam('governor', 'the address of the OWNER of the RToken being upgraded') - .setAction(async (params, hre) => { - await proposeUpgrade(hre, params.rtoken, params.governor, proposal_3_0_0) +task('hyusd-q1-2024-test', 'Test deployed hyUSD Proposals').setAction(async (_, hre) => { + console.log(`Network Block: ${await getLatestBlockNumber(hre)}`) + + const RTokenAddress = '0xaCdf0DBA4B9839b96221a8487e9ca660a48212be' + const RTokenGovernor = '0x22d7937438b4bBf02f6cA55E3831ABB94Bd0b6f1' + + const ProposalIdOne = + '12128108731947079972460039600592322347543776217408895065380983128537007111991' + const ProposalIdTwo = + '67602359440860478788595002306085984888572052896929999758442845286427134600253' + + // Make sure both proposals are active. + await moveProposalToActive(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await moveProposalToActive(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) + + await voteProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await voteProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) + + await passProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await passProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) + + await executeProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdOne) + await executeProposal(hre, RTokenAddress, RTokenGovernor, ProposalIdTwo) + + await hre.run('recollateralize', { + rtoken: RTokenAddress, + governor: RTokenGovernor, }) +}) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 3c534f1604..e880d7da80 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -46,6 +46,7 @@ import { ORACLE_TIMEOUT, PRICE_TIMEOUT, SLOW, + VERSION, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' import { @@ -1109,6 +1110,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.canSettle()).to.equal(false) }) + it('Should have version()', async () => { + expect(await trade.version()).to.equal(VERSION) + }) + it('Should initialize DutchTrade correctly - only once', async () => { // Fund trade and initialize await token0.connect(owner).mint(trade.address, amount) @@ -1555,6 +1560,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await setStorageAt(newTrade.address, 0, 0) }) + it('Should have version()', async () => { + expect(await newTrade.version()).to.equal(VERSION) + }) + it('Open Trade ', async () => { // Open from traders // Backing Manager diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index 39a2ecf8de..1b9e4ed390 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -9,7 +9,6 @@ import { bn } from '../common/numbers' import { Asset, ERC20Mock, - FacadeRead, GnosisMock, IAssetRegistry, RTokenAsset, @@ -18,6 +17,7 @@ import { TestIBroker, TestIDeployer, TestIDistributor, + TestIFacade, TestIFurnace, TestIMain, TestIRevenueTrader, @@ -48,7 +48,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { // Market / Facade let gnosis: GnosisMock let broker: TestIBroker - let facade: FacadeRead + let facade: TestIFacade // Core contracts let rToken: TestIRToken diff --git a/test/Facade.test.ts b/test/Facade.test.ts index f06031f2f1..c1b7456993 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -11,21 +11,22 @@ import { setOraclePrice } from './utils/oracles' import { disableBatchTrade, disableDutchTrade } from './utils/trades' import { whileImpersonating } from './utils/impersonation' import { + ActFacet, Asset, BackingManagerP1, BackingMgrCompatibleV1, BackingMgrCompatibleV2, BackingMgrInvalidVersion, ComptrollerMock, - CTokenWrapperMock, + CTokenMock, ERC20Mock, - FacadeAct, FacadeMonitor, FacadeMonitorV2, - FacadeRead, FacadeTest, MockV3Aggregator, + ReadFacet, RecollateralizationLibP1, + RevertingFacetMock, RevenueTraderCompatibleV1, RevenueTraderCompatibleV2, RevenueTraderInvalidVersion, @@ -36,6 +37,7 @@ import { IBasketHandler, TestIBackingManager, TestIBroker, + TestIFacade, TestIRevenueTrader, TestIMain, TestIStRSR, @@ -67,7 +69,7 @@ const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.ski const itP1 = IMPLEMENTATION == Implementation.P1 ? it : it.skip -describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { +describe('Facade + FacadeMonitor contracts', () => { let owner: SignerWithAddress let addr1: SignerWithAddress let addr2: SignerWithAddress @@ -78,7 +80,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cTokenVault: CTokenWrapperMock + let cToken: CTokenMock let aaveToken: ERC20Mock let compToken: ERC20Mock let compoundMock: ComptrollerMock @@ -92,10 +94,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { let cTokenAsset: Collateral // Facade - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest - let facadeAct: FacadeAct let facadeMonitor: FacadeMonitor + let readFacet: ReadFacet // Main let rToken: TestIRToken @@ -136,7 +138,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { basket, config, facade, - facadeAct, + readFacet, facadeTest, facadeMonitor, rToken, @@ -157,9 +159,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) ) - cTokenVault = ( - await ethers.getContractAt('CTokenWrapperMock', await cTokenAsset.erc20()) - ) + cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) // Factories used in tests RevenueTraderV2ImplFactory = await ethers.getContractFactory('RevenueTraderCompatibleV2') @@ -193,7 +193,28 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { }) }) - describe('FacadeRead + interactions with FacadeAct', () => { + describe('Facade', () => { + let selector: string + let revertingFacet: RevertingFacetMock + + beforeEach(async () => { + selector = readFacet.interface.getSighash('backingOverview(address)') + const factory = await ethers.getContractFactory('RevertingFacetMock') + revertingFacet = await factory.deploy() + }) + + it('Cannot save zero addr facets', async () => { + await expect(facade.save(ZERO_ADDRESS, [selector])).to.be.revertedWith('zero address') + }) + it('Can overwrite an entry', async () => { + await expect(facade.save(revertingFacet.address, [selector])) + .to.emit(facade, 'SelectorSaved') + .withArgs(revertingFacet.address, selector) + await expect(facade.backingOverview(rToken.address)).to.be.revertedWith('RevertingFacetMock') + }) + }) + + describe('ReadFacet + ActFacet', () => { let issueAmount: BigNumber const expectValidBasketBreakdown = async (rToken: TestIRToken) => { @@ -204,11 +225,11 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(erc20s[0]).to.equal(token.address) expect(erc20s[1]).to.equal(usdc.address) expect(erc20s[2]).to.equal(aToken.address) - expect(erc20s[3]).to.equal(cTokenVault.address) - expect(breakdown[0]).to.equal(fp('0.25')) - expect(breakdown[1]).to.equal(fp('0.25')) - expect(breakdown[2]).to.equal(fp('0.25')) - expect(breakdown[3]).to.equal(fp('0.25')) + expect(erc20s[3]).to.equal(cToken.address) + expect(breakdown[0]).to.be.closeTo(fp('0.25'), 10) + expect(breakdown[1]).to.be.closeTo(fp('0.25'), 10) + expect(breakdown[2]).to.be.closeTo(fp('0.25'), 10) + expect(breakdown[3]).to.be.closeTo(fp('0.25'), 10) expect(targets[0]).to.equal(ethers.utils.formatBytes32String('USD')) expect(targets[1]).to.equal(ethers.utils.formatBytes32String('USD')) expect(targets[2]).to.equal(ethers.utils.formatBytes32String('USD')) @@ -227,7 +248,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(addr1).approve(rToken.address, initialBal) await usdc.connect(addr1).approve(rToken.address, initialBal) await aToken.connect(addr1).approve(rToken.address, initialBal) - await cTokenVault.connect(addr1).approve(rToken.address, initialBal) + await cToken.connect(addr1).approve(rToken.address, initialBal) // Issue rTokens await rToken.connect(addr1).issue(issueAmount) @@ -295,7 +316,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(toks[0]).to.equal(token.address) expect(toks[1]).to.equal(usdc.address) expect(toks[2]).to.equal(aToken.address) - expect(toks[3]).to.equal(cTokenVault.address) + expect(toks[3]).to.equal(cToken.address) expect(quantities.length).to.equal(4) expect(quantities[0]).to.equal(issueAmount.div(4)) expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) @@ -316,7 +337,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(toks[0]).to.equal(token.address) expect(toks[1]).to.equal(usdc.address) expect(toks[2]).to.equal(aToken.address) - expect(toks[3]).to.equal(cTokenVault.address) + expect(toks[3]).to.equal(cToken.address) expect(quantities.length).to.equal(4) expect(quantities[0]).to.equal(issueAmount.div(4)) expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) @@ -346,7 +367,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(toks[0]).to.equal(token.address) expect(toks[1]).to.equal(usdc.address) expect(toks[2]).to.equal(aToken.address) - expect(toks[3]).to.equal(cTokenVault.address) + expect(toks[3]).to.equal(cToken.address) expect(quantities[0]).to.equal(issueAmount.div(4)) expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(quantities[2]).to.equal(issueAmount.div(4)) @@ -367,7 +388,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(toksCustom[0]).to.equal(token.address) expect(toksCustom[1]).to.equal(usdc.address) expect(toksCustom[2]).to.equal(aToken.address) - expect(toksCustom[3]).to.equal(cTokenVault.address) + expect(toksCustom[3]).to.equal(cToken.address) expect(quantitiesCustom[0]).to.equal(issueAmount.div(4)) expect(quantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(quantitiesCustom[2]).to.equal(issueAmount.div(4)) @@ -394,7 +415,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(newToksCustom[0]).to.equal(token.address) expect(newToksCustom[1]).to.equal(usdc.address) expect(newToksCustom[2]).to.equal(aToken.address) - expect(newToksCustom[3]).to.equal(cTokenVault.address) + expect(newToksCustom[3]).to.equal(cToken.address) expect(newQuantitiesCustom[0]).to.equal(issueAmount.div(4).div(2)) expect(newQuantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(newQuantitiesCustom[2]).to.equal(issueAmount.div(4)) @@ -413,7 +434,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(prevBasketTokens[0]).to.equal(token.address) expect(prevBasketTokens[1]).to.equal(usdc.address) expect(prevBasketTokens[2]).to.equal(aToken.address) - expect(prevBasketTokens[3]).to.equal(cTokenVault.address) + expect(prevBasketTokens[3]).to.equal(cToken.address) expect(prevBasketQuantities[0]).to.equal(issueAmount.div(4).div(2)) expect(prevBasketQuantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(prevBasketQuantities[2]).to.equal(issueAmount.div(4)) @@ -425,6 +446,27 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await expect(facade.callStatic.redeem(rToken.address, issueAmount)).to.be.revertedWith( 'frozen' ) + + await expect( + facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [await basketHandler.nonce()], + [fp('1')] + ) + ).to.be.revertedWith('frozen') + }) + + it('Should revert if portions do not sum to FIX_ONE in redeem custom', async function () { + const nonce = await basketHandler.nonce() + await expect( + facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [nonce, nonce], + [fp('0.5'), fp('0.5').add(1)] + ) + ).to.be.revertedWith('portions do not add up to FIX_ONE') }) it('Should return backingOverview correctly', async () => { @@ -568,8 +610,8 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await usdc.connect(addr1).transfer(rsrTrader.address, 2) await aToken.connect(addr1).transfer(rTokenTrader.address, 1) await aToken.connect(addr1).transfer(rsrTrader.address, 2) - await cTokenVault.connect(addr1).transfer(rTokenTrader.address, 1) - await cTokenVault.connect(addr1).transfer(rsrTrader.address, 2) + await cToken.connect(addr1).transfer(rTokenTrader.address, 1) + await cToken.connect(addr1).transfer(rsrTrader.address, 2) // Balances const [erc20s, balances, balancesNeededByBackingManager] = @@ -583,11 +625,9 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { if (erc20s[i] == token.address) bal = issueAmount.div(4) if (erc20s[i] == usdc.address) bal = issueAmount.div(4).div(bn('1e12')) if (erc20s[i] == aToken.address) bal = issueAmount.div(4) - if (erc20s[i] == cTokenVault.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) + if (erc20s[i] == cToken.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) - if ( - [token.address, usdc.address, aToken.address, cTokenVault.address].indexOf(erc20s[i]) >= 0 - ) { + if ([token.address, usdc.address, aToken.address, cToken.address].indexOf(erc20s[i]) >= 0) { expect(balances[i]).to.equal(bal.add(3)) // expect 3 more expect(balancesNeededByBackingManager[i]).to.equal(bal) } else { @@ -597,7 +637,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { } }) - it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { + it('Should return revenue + chain into ActFacet.runRevenueAuctions', async () => { // Set low to 0 == revenueOverview() should not revert const minTradeVolume = await rsrTrader.minTradeVolume() const auctionLength = await broker.dutchAuctionLength() @@ -614,8 +654,9 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(low).to.equal(0) // revenue - let [erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(rsrTrader.address) + let [erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenueOverview( + rsrTrader.address + ) expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s const erc20sToStart = [] @@ -642,10 +683,11 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { [rsrTrader.address, [], erc20sToStart, [TradeKind.DUTCH_AUCTION]] ) const data = funcSig.substring(0, 10) + args.slice(2) - await expect(facadeAct.multicall([data])).to.emit(rsrTrader, 'TradeStarted') + const facadeAsActFacet = await ethers.getContractAt('ActFacet', facade.address) + await expect(facadeAsActFacet.multicall([data])).to.emit(rsrTrader, 'TradeStarted') // Another call to revenueOverview should not propose any auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenueOverview( rsrTrader.address ) expect(canStart).to.eql(Array(8).fill(false)) @@ -662,7 +704,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(settleable[0]).to.equal(token.address) // Another call to revenueOverview should settle and propose new auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenueOverview( rsrTrader.address ) @@ -678,7 +720,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { } // Settle and start new auction - await facadeAct.runRevenueAuctions(rsrTrader.address, erc20sToStart, erc20sToStart, [ + await facade.runRevenueAuctions(rsrTrader.address, erc20sToStart, erc20sToStart, [ TradeKind.DUTCH_AUCTION, ]) @@ -686,7 +728,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) // Call revenueOverview, cannot open new auctions - ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenueOverview( rsrTrader.address ) expect(canStart).to.eql(Array(8).fill(false)) @@ -703,11 +745,11 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await BackingMgrInvalidVerImplFactory.deploy() ) - await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).not.to.be.reverted + await expect(facade.callStatic.revenueOverview(rsrTrader.address)).not.to.be.reverted await backingManager.connect(owner).upgradeTo(bckMgrInvalidVer.address) // Reverts due to invalid version when forwarding revenue - await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).to.be.revertedWith( + await expect(facade.callStatic.revenueOverview(rsrTrader.address)).to.be.revertedWith( 'unrecognized version' ) }) @@ -715,7 +757,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { it('Should return nextRecollateralizationAuction', async () => { // Confirm no auction to run yet - should not revert let [canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( + await facade.callStatic.nextRecollateralizationAuction( backingManager.address, TradeKind.DUTCH_AUCTION ) @@ -733,11 +775,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { const sellAmt: BigNumber = await token.balanceOf(backingManager.address) // Confirm nextRecollateralizationAuction is true - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.DUTCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -758,11 +799,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { }) // nextRecollateralizationAuction should return false (trade open) - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.DUTCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(false) expect(sell).to.equal(ZERO_ADDRESS) expect(buy).to.equal(ZERO_ADDRESS) @@ -773,11 +813,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // nextRecollateralizationAuction should return the next trade // In this case it will retry the same auction - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.DUTCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -807,7 +846,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Confirm no auction to run yet - should not revert let [canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( + await facade.callStatic.nextRecollateralizationAuction( backingManager.address, TradeKind.BATCH_AUCTION ) @@ -825,11 +864,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { const sellAmt: BigNumber = await token.balanceOf(backingManager.address) // Confirm nextRecollateralizationAuction is true - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.BATCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -853,11 +891,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await backingManager.connect(owner).upgradeTo(backingManagerV1.address) // nextRecollateralizationAuction should return false (trade open) - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.BATCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(false) expect(sell).to.equal(ZERO_ADDRESS) expect(buy).to.equal(ZERO_ADDRESS) @@ -868,11 +905,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // nextRecollateralizationAuction should return the next trade // In this case it will retry the same auction - ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address, - TradeKind.BATCH_AUCTION - ) + ;[canStart, sell, buy, sellAmount] = await facade.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -882,7 +918,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) await expect( - facadeAct.callStatic.nextRecollateralizationAuction( + facade.callStatic.nextRecollateralizationAuction( backingManager.address, TradeKind.BATCH_AUCTION ) @@ -912,7 +948,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Attempt to trigger recollateralization await expect( - facadeAct.callStatic.nextRecollateralizationAuction( + facade.callStatic.nextRecollateralizationAuction( backingManager.address, TradeKind.BATCH_AUCTION ) @@ -951,7 +987,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(erc20s[0]).to.equal(token.address) expect(erc20s[1]).to.equal(usdc.address) expect(erc20s[2]).to.equal(aToken.address) - expect(erc20s[3]).to.equal(cTokenVault.address) + expect(erc20s[3]).to.equal(cToken.address) expect(breakdown[0]).to.equal(fp('0')) // dai expect(breakdown[1]).to.equal(fp('1')) // usdc expect(breakdown[2]).to.equal(fp('0')) // adai @@ -1050,7 +1086,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(erc20s.length).to.equal(4) expect(targetNames.length).to.equal(4) expect(targetAmts.length).to.equal(4) - const expectedERC20s = [token.address, usdc.address, aToken.address, cTokenVault.address] + const expectedERC20s = [token.address, usdc.address, aToken.address, cToken.address] for (let i = 0; i < 4; i++) { expect(erc20s[i]).to.equal(expectedERC20s[i]) expect(targetNames[i]).to.equal(ethers.utils.formatBytes32String('USD')) @@ -1082,7 +1118,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(erc20s.length).to.equal(4) expect(targetNames.length).to.equal(4) expect(targetAmts.length).to.equal(4) - const expectedERC20s = [token.address, usdc.address, aToken.address, cTokenVault.address] + const expectedERC20s = [token.address, usdc.address, aToken.address, cToken.address] for (let i = 0; i < 4; i++) { expect(erc20s[i]).to.equal(expectedERC20s[i]) expect(targetNames[i]).to.equal(ethers.utils.formatBytes32String('USD')) @@ -1131,13 +1167,13 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(owner).mint(addr1.address, initialBal) await usdc.connect(owner).mint(addr1.address, initialBal) await aToken.connect(owner).mint(addr1.address, initialBal) - await cTokenVault.connect(owner).mint(addr1.address, initialBal) + await cToken.connect(owner).mint(addr1.address, initialBal) // Provide approvals await token.connect(addr1).approve(rToken.address, initialBal) await usdc.connect(addr1).approve(rToken.address, initialBal) await aToken.connect(addr1).approve(rToken.address, initialBal) - await cTokenVault.connect(addr1).approve(rToken.address, initialBal) + await cToken.connect(addr1).approve(rToken.address, initialBal) }) it('should return batch auctions disabled correctly', async () => { @@ -1454,7 +1490,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { }) // P1 only - describeP1('FacadeAct', () => { + describeP1('ActFacet on P1', () => { let issueAmount: BigNumber beforeEach(async () => { @@ -1463,12 +1499,12 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(owner).mint(addr1.address, initialBal) await usdc.connect(owner).mint(addr1.address, initialBal) await aToken.connect(owner).mint(addr1.address, initialBal) - await cTokenVault.connect(owner).mint(addr1.address, initialBal) + await cToken.connect(owner).mint(addr1.address, initialBal) await token.connect(owner).mint(addr2.address, initialBal) await usdc.connect(owner).mint(addr2.address, initialBal) await aToken.connect(owner).mint(addr2.address, initialBal) - await cTokenVault.connect(owner).mint(addr2.address, initialBal) + await cToken.connect(owner).mint(addr2.address, initialBal) // Mint RSR await rsr.connect(owner).mint(addr1.address, initialBal) @@ -1480,7 +1516,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(addr1).approve(rToken.address, initialBal) await usdc.connect(addr1).approve(rToken.address, initialBal) await aToken.connect(addr1).approve(rToken.address, initialBal) - await cTokenVault.connect(addr1).approve(rToken.address, initialBal) + await cToken.connect(addr1).approve(rToken.address, initialBal) // Issue rTokens await rToken.connect(addr1).issue(issueAmount) @@ -1511,7 +1547,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await compoundMock.setRewards(rsrTrader.address, rewardAmountCOMP) // Via Facade, claim rewards from backingManager - await expectEvents(facadeAct.claimRewards(rToken.address), [ + await expectEvents(facade.claimRewards(rToken.address), [ { contract: aToken, name: 'RewardsClaimed', @@ -1519,13 +1555,13 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { emitted: true, }, { - contract: aToken, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, { - contract: cTokenVault, + contract: rsrTrader, name: 'RewardsClaimed', args: [compToken.address, rewardAmountCOMP], emitted: true, @@ -1544,12 +1580,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Run revenue auctions await expect( - facadeAct.runRevenueAuctions( - rsrTrader.address, - [], - [token.address], - [TradeKind.DUTCH_AUCTION] - ) + facade.runRevenueAuctions(rsrTrader.address, [], [token.address], [TradeKind.DUTCH_AUCTION]) ) .to.emit(rsrTrader, 'TradeStarted') .withArgs(anyValue, token.address, rsr.address, anyValue, anyValue) @@ -1562,7 +1593,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Settle and start new auction - Will retry await expectEvents( - facadeAct.runRevenueAuctions( + facade.runRevenueAuctions( rsrTrader.address, [token.address], [token.address], @@ -1608,7 +1639,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Run revenue auctions await expect( - facadeAct.runRevenueAuctions( + facade.runRevenueAuctions( rTokenTrader.address, [], [token.address], @@ -1630,7 +1661,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Settle and start new auction - Will retry await expectEvents( - facadeAct.runRevenueAuctions( + facade.runRevenueAuctions( rTokenTrader.address, [token.address], [token.address], @@ -1661,7 +1692,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { // Settle and start new auction - Will retry again await expectEvents( - facadeAct.runRevenueAuctions( + facade.runRevenueAuctions( rTokenTrader.address, [token.address], [token.address], @@ -1700,24 +1731,14 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) await expect( - facadeAct.runRevenueAuctions( - rsrTrader.address, - [], - [token.address], - [TradeKind.DUTCH_AUCTION] - ) + facade.runRevenueAuctions(rsrTrader.address, [], [token.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('unrecognized version') // Also set BackingManager to invalid version await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) await expect( - facadeAct.runRevenueAuctions( - rsrTrader.address, - [], - [token.address], - [TradeKind.DUTCH_AUCTION] - ) + facade.runRevenueAuctions(rsrTrader.address, [], [token.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('unrecognized version') }) }) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 9176c71ac0..0175c1c45c 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -29,8 +29,8 @@ import snapshotGasCost from './utils/snapshotGasCost' import { Asset, CTokenFiatCollateral, + CTokenMock, ERC20Mock, - FacadeRead, FacadeTest, FacadeWrite, FiatCollateral, @@ -42,6 +42,7 @@ import { TestIBroker, TestIDeployer, TestIDistributor, + TestIFacade, TestIFurnace, TestIMain, TestIRevenueTrader, @@ -49,7 +50,6 @@ import { TestIRToken, TimelockController, USDCMock, - CTokenWrapperMock, } from '../typechain' import { Collateral, @@ -79,7 +79,7 @@ describe('FacadeWrite contract', () => { // Tokens let token: ERC20Mock let usdc: USDCMock - let cTokenVault: CTokenWrapperMock + let cToken: ERC20Mock let basket: Collateral[] // Aave / Comp @@ -103,7 +103,7 @@ describe('FacadeWrite contract', () => { let timelock: TimelockController // Facade - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let facadeWriteLibAddr: string @@ -144,9 +144,7 @@ describe('FacadeWrite contract', () => { token = await ethers.getContractAt('ERC20Mock', await tokenAsset.erc20()) usdc = await ethers.getContractAt('USDCMock', await usdcAsset.erc20()) - cTokenVault = ( - await ethers.getContractAt('CTokenWrapperMock', await cTokenAsset.erc20()) - ) + cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) // Deploy DFacadeWriteLib lib const facadeWriteLib = await (await ethers.getContractFactory('FacadeWriteLib')).deploy() @@ -567,7 +565,7 @@ describe('FacadeWrite contract', () => { // Check new state - backing updated expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) tokens = await facade.basketTokens(rToken.address) - expect(tokens).to.eql([token.address, cTokenVault.address]) + expect(tokens).to.eql([token.address, cToken.address]) }) it('Should setup roles correctly', async () => { diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 84d16f32c7..5883101e4b 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -6,7 +6,7 @@ import hre, { ethers, upgrades } from 'hardhat' import { IConfig, MAX_RATIO } from '../common/configuration' import { bn, fp } from '../common/numbers' import { - CTokenWrapperMock, + CTokenMock, ERC20Mock, StaticATokenMock, TestIFurnace, @@ -52,7 +52,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenWrapperMock + let token3: CTokenMock let collateral0: Collateral let collateral1: Collateral @@ -117,9 +117,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) - ) + token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) // Mint Tokens await mintCollaterals(owner, [addr1, addr2], initialBal, basket) diff --git a/test/Main.test.ts b/test/Main.test.ts index 385df4fa22..0d4ab419ef 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -37,9 +37,8 @@ import { BasketHandlerP1, CTokenFiatCollateral, DutchTrade, - CTokenWrapperMock, + CTokenMock, ERC20Mock, - FacadeRead, FacadeTest, FiatCollateral, GnosisMock, @@ -56,6 +55,7 @@ import { TestIBroker, TestIDeployer, TestIDistributor, + TestIFacade, TestIFurnace, TestIMain, TestIRevenueTrader, @@ -120,7 +120,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenWrapperMock + let token3: CTokenMock let backupToken1: ERC20Mock let backupToken2: ERC20Mock let collateral0: FiatCollateral @@ -141,7 +141,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let stRSR: TestIStRSR let furnace: TestIFurnace let main: TestIMain - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager @@ -185,7 +185,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3 = erc20s[collateral.indexOf(basket[3])] + token3 = erc20s[collateral.indexOf(basket[3])] backupToken1 = erc20s[2] // USDT backupCollateral1 = collateral[2] @@ -2195,6 +2195,22 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(tokAmts[1]).to.equal(fp('0.5')) }) + it('Should handle unpriced asset in normalization', async () => { + await indexBH.connect(owner).setPrimeBasket([token0.address], [fp('1')]) + await indexBH.connect(owner).refreshBasket() + + // Set Token0 to unpriced - stale oracle + await advanceTime(DECAY_DELAY.add(PRICE_TIMEOUT).toString()) + await expectUnpriced(collateral0.address) + + // Attempt to add EURO, basket is not SOUND + await expect( + indexBH + .connect(owner) + .setPrimeBasket([token0.address, eurToken.address], [fp('1'), fp('0.25')]) + ).to.be.revertedWith('unsound basket') + }) + describe('Custom Redemption', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator diff --git a/test/RToken.test.ts b/test/RToken.test.ts index a65730a057..a2e61bcfdb 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -23,7 +23,7 @@ import { TestIMain, TestIRToken, USDCMock, - CTokenWrapperMock, + CTokenMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -62,7 +62,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenWrapperMock + let token3: CTokenMock let tokens: ERC20Mock[] let collateral0: Collateral @@ -109,9 +109,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) - ) + token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) tokens = [token0, token1, token2, token3] // Mint initial balances diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 1d892c6c69..d284170037 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -19,9 +19,7 @@ import { ATokenFiatCollateral, CTokenMock, DutchTrade, - CTokenWrapperMock, ERC20Mock, - FacadeRead, FacadeTest, FiatCollateral, GnosisMock, @@ -32,6 +30,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, + TestIFacade, TestIMain, TestIRToken, TestIStRSR, @@ -88,7 +87,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3Vault: CTokenWrapperMock let token3: CTokenMock let backupToken1: ERC20Mock let backupToken2: ERC20Mock @@ -106,7 +104,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Contracts to retrieve after deploy let rToken: TestIRToken let stRSR: TestIStRSR - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager @@ -180,10 +178,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3Vault = ( - await ethers.getContractAt('CTokenWrapperMock', await basket[3].erc20()) - ) - token3 = await ethers.getContractAt('CTokenMock', await token3Vault.underlying()) + token3 = await ethers.getContractAt('CTokenMock', await basket[3].erc20()) // Set Aave revenue token await token2.setAaveToken(aaveToken.address) @@ -246,7 +241,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3Vault.connect(addr1).approve(rToken.address, initialBal) + await token3.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) @@ -472,7 +467,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(2), [ token0.address, - token3Vault.address, + token3.address, ]) // Check initial state @@ -1160,7 +1155,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) // Nothing occurs if we attempt to settle for a token that is not being traded - await expect(backingManager.settleTrade(token3Vault.address)).to.not.emit + await expect(backingManager.settleTrade(token3.address)).to.not.emit // Advance time till auction ended await advanceTime(config.batchAuctionLength.add(100).toString()) @@ -3446,7 +3441,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3Vault.connect(addr1).approve(rToken.address, initialBal) + await token3.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) @@ -3795,9 +3790,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - All balance will be redeemed const sellAmt0: BigNumber = await token0.balanceOf(backingManager.address) const sellAmt2: BigNumber = await token2.balanceOf(backingManager.address) - const sellAmt3: BigNumber = (await token3Vault.balanceOf(backingManager.address)).mul( - pow10(10) - ) // convert to 18 decimals for simplification + const sellAmt3: BigNumber = (await token3.balanceOf(backingManager.address)).mul(pow10(10)) // convert to 18 decimals for simplification const minBuyAmt0 = await toMinBuyAmt(sellAmt0, fp('0.8'), fp('1')) // Run auctions - Will start with token0 @@ -3907,7 +3900,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - token3Vault.address, + token3.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -3921,7 +3914,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Backup Token 1 Auction await expectTrade(backingManager, { - sell: token3Vault.address, + sell: token3.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), @@ -3963,7 +3956,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - token3Vault.address, + token3.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -4293,7 +4286,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Advance time till auction ended await advanceTime(config.batchAuctionLength.add(100).toString()) - // Run auctions - will end current and open a new auction for token3Vault + // Run auctions - will end current and open a new auction for token3 // We only need now about 11.8 tokens for Token0 to be fully collateralized // Will check values later in the test to ensure they are in this range await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4315,14 +4308,14 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Token0 Auction await expectTrade(backingManager, { - sell: token3Vault.address, + sell: token3.address, buy: token0.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) // Get Trade - const t3 = await getTrade(backingManager, token3Vault.address) + const t3 = await getTrade(backingManager, token3.address) const sellAmt3 = (await t3.initBal()).mul(pow10(10)) // convert to 18 decimals let minBuyAmt3 = await toMinBuyAmt(sellAmt3, fp('1').div(50), fp('1')) expect(minBuyAmt3).to.be.closeTo(fp('11.28'), fp('0.01')) @@ -4358,13 +4351,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeSettled', - args: [ - anyValue, - token3Vault.address, - token0.address, - toBNDecimals(sellAmt3, 8), - minBuyAmt3, - ], + args: [anyValue, token3.address, token0.address, toBNDecimals(sellAmt3, 8), minBuyAmt3], emitted: true, }, { @@ -4480,9 +4467,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - All balance will be redeemed const sellAmt0: BigNumber = await token0.balanceOf(backingManager.address) const sellAmt2: BigNumber = await token2.balanceOf(backingManager.address) - const sellAmt3: BigNumber = (await token3Vault.balanceOf(backingManager.address)).mul( - pow10(10) - ) // convert to 18 decimals for simplification + const sellAmt3: BigNumber = (await token3.balanceOf(backingManager.address)).mul(pow10(10)) // convert to 18 decimals for simplification // Run auctions - will start with token0 and backuptoken1 const minBuyAmt = await toMinBuyAmt(sellAmt0, fp('0.5'), fp('1')) @@ -4599,7 +4584,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - token3Vault.address, + token3.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -4613,7 +4598,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Backup Token 1 Auction await expectTrade(backingManager, { - sell: token3Vault.address, + sell: token3.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), @@ -4660,7 +4645,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - token3Vault.address, + token3.address, backupToken1.address, toBNDecimals(sellAmt3, 8), sellAmt3.div(50).div(2), @@ -4998,7 +4983,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3Vault.connect(addr1).approve(rToken.address, initialBal) + await token3.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 8887b4ee28..384617fbdb 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -23,7 +23,7 @@ import { ATokenFiatCollateral, Asset, CTokenFiatCollateral, - CTokenWrapperMock, + CTokenMock, ComptrollerMock, ERC20Mock, FacadeTest, @@ -97,7 +97,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenWrapperMock + let token3: CTokenMock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral @@ -206,9 +206,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) - ) + token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) // Mint initial balances initialBal = bn('1000000e18') @@ -979,13 +977,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, rewardAmountCOMP], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -1243,13 +1241,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1360,13 +1358,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Can also claim through Facade await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1520,13 +1518,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.setRewards(backingManager.address, rewardAmountAAVE) await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1725,13 +1723,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1926,13 +1924,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2232,13 +2230,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, rewardAmountCOMP], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -2382,13 +2380,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, rewardAmountCOMP], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -2506,13 +2504,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2569,13 +2567,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2834,13 +2832,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2949,13 +2947,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3047,13 +3045,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Claim rewards await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3101,13 +3099,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3157,13 +3155,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3285,6 +3283,40 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) + it('Should support custom destinations only', async () => { + // Set distribution all to a custom destination + await expect( + distributor + .connect(owner) + .setDistribution(other.address, { rTokenDist: bn(0), rsrDist: bn(1) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(other.address, bn(0), bn(1)) + + // No distribution to Furnace or StRSR + await expect( + distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(FURNACE_DEST, bn(0), bn(0)) + + await expect( + distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(STRSR_DEST, bn(0), bn(0)) + + const rsrBalInDestination = await rsr.balanceOf(other.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await rsrTrader.distributeTokenToBuy() + const expectedAmount = rsrBalInDestination.add(issueAmount) + expect(await rsr.balanceOf(other.address)).to.be.closeTo(expectedAmount, 100) + }) + it('Should claim but not sweep rewards to BackingManager from the Revenue Traders', async () => { rewardAmountAAVE = bn('0.5e18') @@ -3298,13 +3330,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue await expectEvents(rsrTrader.claimRewards(), [ { - contract: token3, + contract: rsrTrader, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: rsrTrader, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3339,13 +3371,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Claim and sweep rewards await expectEvents(backingManager.claimRewards(), [ { - contract: token3, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: token2, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -3364,14 +3396,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should not revert on invalid claim logic', async () => { - // Here the aToken is going to have an invalid claimRewards on its asset, - // while the cToken will have it on the ERC20 + it('Should be able to claimRewardsSingle to escape reverting claimRewards()', async () => { + // Here the aToken is going to have an invalid claimRewards on its asset // cToken rewardAmountCOMP = bn('0.5e18') await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - await token3.setRevertClaimRewards(true) // Setup a new aToken with invalid claim data const ATokenCollateralFactory = await ethers.getContractFactory( @@ -3412,8 +3442,14 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // AAVE Rewards await token2.setRewards(backingManager.address, rewardAmountAAVE) - // Claim and sweep rewards -- should succeed - await expect(backingManager.claimRewards()).not.to.be.reverted + // Claim and sweep rewards -- fails + await expect(backingManager.claimRewards()).to.be.reverted + + // But can claimRewardsSingle + await expect(backingManager.claimRewardsSingle(token3.address)).to.emit( + backingManager, + 'RewardsClaimed' + ) }) context('DutchTrade', () => { @@ -3498,7 +3534,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await router.connect(addr1).bid(trade.address, addr1.address) expect(await trade.bidder()).to.equal(router.address) - // Cannot bid once is settled + + // Noone can bid again on the trade directly + await expect( + trade.connect(addr1).bidWithCallback(new Uint8Array(0)) + ).to.be.revertedWith('bid already received') + + // Cannot bid once is settled via router await expect( router.connect(addr1).bid(trade.address, addr1.address) ).to.be.revertedWith('trade not open') diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index de63aea75b..111faaefd4 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -11,7 +11,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenWrapperMock, + CTokenMock, ERC20Mock, FacadeTest, FiatCollateral, @@ -76,7 +76,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, let ERC20Mock: ContractFactory let ATokenMockFactory: ContractFactory - let CTokenWrapperMockFactory: ContractFactory + let CTokenMockFactory: ContractFactory let ATokenCollateralFactory: ContractFactory let CTokenCollateralFactory: ContractFactory @@ -110,7 +110,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, ERC20Mock = await ethers.getContractFactory('ERC20Mock') ATokenMockFactory = await ethers.getContractFactory('StaticATokenMock') - CTokenWrapperMockFactory = await ethers.getContractFactory('CTokenWrapperMock') + CTokenMockFactory = await ethers.getContractFactory('CTokenMock') ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -157,16 +157,15 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, return erc20 } - const prepCToken = async (index: number): Promise => { + const prepCToken = async (index: number): Promise => { const underlying: ERC20Mock = ( await ERC20Mock.deploy(`ERC20_NAME:${index}`, `ERC20_SYM:${index}`) ) - const erc20: CTokenWrapperMock = ( - await CTokenWrapperMockFactory.deploy( + const erc20: CTokenMock = ( + await CTokenMockFactory.deploy( `CToken_NAME:${index}`, `CToken_SYM:${index}`, underlying.address, - compToken.address, compoundMock.address ) ) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 81629b3426..7ee2f4d048 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -12,17 +12,17 @@ import { expectEvents } from '../common/events' import { ERC20Mock, ERC1271Mock, - FacadeRead, StRSRP0, StRSRP1Votes, StaticATokenMock, IAssetRegistry, TestIBackingManager, TestIBasketHandler, + TestIFacade, TestIMain, TestIRToken, TestIStRSR, - CTokenWrapperMock, + CTokenMock, } from '../typechain' import { IConfig, MAX_RATIO, MAX_UNSTAKING_DELAY } from '../common/configuration' import { CollateralStatus, MAX_UINT256, ONE_PERIOD, ZERO_ADDRESS } from '../common/constants' @@ -70,7 +70,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { let backingManager: TestIBackingManager let basketHandler: TestIBasketHandler let rToken: TestIRToken - let facade: FacadeRead + let facade: TestIFacade let assetRegistry: IAssetRegistry // StRSR @@ -80,7 +80,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenWrapperMock + let token3: CTokenMock let collateral0: Collateral let collateral1: Collateral let collateral2: Collateral @@ -179,9 +179,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) - ) + token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) }) describe('Deployment #fast', () => { diff --git a/test/fixtures.ts b/test/fixtures.ts index 07e8ce3a15..2c6fc0df1c 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -29,13 +29,13 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenSelfReferentialCollateral, - CTokenWrapperMock, + CTokenMock, ERC20Mock, DeployerP0, DeployerP1, DutchTrade, - FacadeRead, - FacadeAct, + ReadFacet, + ActFacet, FacadeMonitor, FacadeTest, DistributorP1, @@ -58,6 +58,7 @@ import { TestIBroker, TestIDeployer, TestIDistributor, + TestIFacade, TestIFurnace, TestIMain, TestIRevenueTrader, @@ -93,7 +94,7 @@ export const ORACLE_ERROR = fp('0.01') // 1% oracle error export const REVENUE_HIDING = fp('0') // no revenue hiding by default; test individually // This will have to be updated on each release -export const VERSION = '3.2.0' +export const VERSION = '3.3.0' export type Collateral = | FiatCollateral @@ -165,7 +166,6 @@ async function gnosisFixture(): Promise { } async function collateralFixture( - compToken: ERC20Mock, comptroller: ComptrollerMock, aaveToken: ERC20Mock, config: IConfig @@ -173,9 +173,7 @@ async function collateralFixture( const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const USDC: ContractFactory = await ethers.getContractFactory('USDCMock') const ATokenMockFactory: ContractFactory = await ethers.getContractFactory('StaticATokenMock') - const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenWrapperMock' - ) + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') const CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -248,15 +246,13 @@ async function collateralFixture( const makeCTokenCollateral = async ( symbol: string, referenceERC20: ERC20Mock, - chainlinkAddr: string, - compToken: ERC20Mock - ): Promise<[CTokenWrapperMock, CTokenFiatCollateral]> => { - const erc20: CTokenWrapperMock = ( - await CTokenWrapperMockFactory.deploy( + chainlinkAddr: string + ): Promise<[CTokenMock, CTokenFiatCollateral]> => { + const erc20: CTokenMock = ( + await CTokenMockFactory.deploy( symbol + ' Token', symbol, referenceERC20.address, - compToken.address, comptroller.address ) ) @@ -311,19 +307,9 @@ async function collateralFixture( const usdc = await makeSixDecimalCollateral('USDC') const usdt = await makeVanillaCollateral('USDT') const busd = await makeVanillaCollateral('BUSD') - const cdai = await makeCTokenCollateral('cDAI', dai[0], await dai[1].chainlinkFeed(), compToken) - const cusdc = await makeCTokenCollateral( - 'cUSDC', - usdc[0], - await usdc[1].chainlinkFeed(), - compToken - ) - const cusdt = await makeCTokenCollateral( - 'cUSDT', - usdt[0], - await usdt[1].chainlinkFeed(), - compToken - ) + const cdai = await makeCTokenCollateral('cDAI', dai[0], await dai[1].chainlinkFeed()) + const cusdc = await makeCTokenCollateral('cUSDC', usdc[0], await usdc[1].chainlinkFeed()) + const cusdt = await makeCTokenCollateral('cUSDT', usdt[0], await usdt[1].chainlinkFeed()) const adai = await makeATokenCollateral('aDAI', dai[0], await dai[1].chainlinkFeed(), aaveToken) const ausdc = await makeATokenCollateral( 'aUSDC', @@ -422,8 +408,9 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt rTokenAsset: RTokenAsset furnace: TestIFurnace stRSR: TestIStRSR - facade: FacadeRead - facadeAct: FacadeAct + facade: TestIFacade + readFacet: ReadFacet + actFacet: ActFacet facadeTest: FacadeTest facadeMonitor: FacadeMonitor broker: TestIBroker @@ -492,14 +479,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const TradingLibFactory: ContractFactory = await ethers.getContractFactory('TradingLibP0') const tradingLib: TradingLibP0 = await TradingLibFactory.deploy() - // Deploy FacadeRead - const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') - const facade = await FacadeReadFactory.deploy() - - // Deploy FacadeAct - const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') - const facadeAct = await FacadeActFactory.deploy() - // Deploy FacadeTest const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') const facadeTest = await FacadeTestFactory.deploy() @@ -708,7 +687,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = // Deploy collateral for Main const { erc20s, collateral, basket, basketsNeededAmts, bySymbol } = await collateralFixture( - compToken, compoundMock, aaveToken, config @@ -753,6 +731,26 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = await main.connect(owner).grantRole(SHORT_FREEZER, owner.address) await main.connect(owner).grantRole(LONG_FREEZER, owner.address) + // Deploy Facade + const FacadeFactory: ContractFactory = await ethers.getContractFactory('Facade') + const facade = await ethers.getContractAt('TestIFacade', (await FacadeFactory.deploy()).address) + + // Save ReadFacet to Facade + const ReadFacetFactory: ContractFactory = await ethers.getContractFactory('ReadFacet') + const readFacet = await ReadFacetFactory.deploy() + await facade.save( + readFacet.address, + Object.entries(readFacet.functions).map(([fn]) => readFacet.interface.getSighash(fn)) + ) + + // Save ActFacet to Facade + const ActFacetFactory: ContractFactory = await ethers.getContractFactory('ActFacet') + const actFacet = await ActFacetFactory.deploy() + await facade.save( + actFacet.address, + Object.entries(actFacet.functions).map(([fn]) => actFacet.interface.getSighash(fn)) + ) + return { rsr, rsrAsset, @@ -782,7 +780,8 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = gnosis, easyAuction, facade, - facadeAct, + readFacet, + actFacet, facadeTest, facadeMonitor, rsrTrader, diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index cab2fd3fb5..91cea90b07 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -38,7 +38,6 @@ import { CTokenSelfReferentialCollateral, ERC20Mock, EURFiatCollateral, - FacadeRead, FacadeTest, FiatCollateral, IAToken, @@ -51,11 +50,11 @@ import { StaticATokenLM, TestIBackingManager, TestIBasketHandler, + TestIFacade, TestIMain, TestIRToken, USDCMock, WETH9, - CTokenWrapper, } from '../../typechain' import { useEnv } from '#/utils/env' @@ -119,20 +118,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let stataUsdp: StaticATokenLM let cDai: CTokenMock - let cDaiVault: CTokenWrapper let cUsdc: CTokenMock - let cUsdcVault: CTokenWrapper let cUsdt: CTokenMock - let cUsdtVault: CTokenWrapper let cUsdp: CTokenMock - let cUsdpVault: CTokenWrapper let wbtc: ERC20Mock let cWBTC: CTokenMock - let cWBTCVault: CTokenWrapper let weth: ERC20Mock let cETH: CTokenMock - let cETHVault: CTokenWrapper let eurt: ERC20Mock let daiCollateral: FiatCollateral @@ -163,7 +156,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let rToken: TestIRToken let rTokenAsset: RTokenAsset let main: TestIMain - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager @@ -237,25 +230,19 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, busd = erc20s[3] // BUSD usdp = erc20s[4] // USDP tusd = erc20s[5] // TUSD - cDaiVault = erc20s[6] // cDAI - cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.underlying()) // cDAI - cUsdcVault = erc20s[7] // cUSDC - cUsdc = await ethers.getContractAt('CTokenMock', await cUsdcVault.underlying()) // cUSDC - cUsdtVault = erc20s[8] // cUSDT - cUsdt = await ethers.getContractAt('CTokenMock', await cUsdtVault.underlying()) // cUSDT - cUsdpVault = erc20s[9] // cUSDT - cUsdp = await ethers.getContractAt('CTokenMock', await cUsdpVault.underlying()) // cUSDT + cDai = erc20s[6] // cDAI + cUsdc = erc20s[7] // cUSDC + cUsdt = erc20s[8] // cUSDT + cUsdp = erc20s[9] // cUSDT stataDai = erc20s[10] // static aDAI stataUsdc = erc20s[11] // static aUSDC stataUsdt = erc20s[12] // static aUSDT stataBusd = erc20s[13] // static aBUSD stataUsdp = erc20s[14] // static aUSDP wbtc = erc20s[15] // wBTC - cWBTCVault = erc20s[16] // cWBTC - cWBTC = await ethers.getContractAt('CTokenMock', await cWBTCVault.underlying()) // cWBTC + cWBTC = erc20s[16] // cWBTC weth = erc20s[17] // wETH - cETHVault = erc20s[18] // cETH - cETH = await ethers.getContractAt('CTokenMock', await cETHVault.underlying()) // cETH + cETH = erc20s[18] // cETH eurt = erc20s[19] // eurt // Get plain aTokens @@ -347,8 +334,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // cDAI await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) - await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) }) // Setup balances for USDT @@ -368,12 +353,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await cWBTC .connect(cwbtcSigner) .transfer(addr1.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) - await cWBTC - .connect(addr1) - .approve(cWBTCVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) - await cWBTCVault - .connect(addr1) - .deposit(toBNDecimals(initialBalBtcEth, 8).mul(1000), addr1.address) }) // WETH @@ -386,12 +365,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await cETH .connect(cethSigner) .transfer(addr1.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) - await cETH - .connect(addr1) - .approve(cETHVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) - await cETHVault - .connect(addr1) - .deposit(toBNDecimals(initialBalBtcEth, 8).mul(1000), addr1.address) }) //EURT @@ -412,7 +385,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await basketHandler .connect(owner) .setPrimeBasket( - [dai.address, stataDai.address, cDaiVault.address], + [dai.address, stataDai.address, cDai.address], [fp('0.25'), fp('0.25'), fp('0.5')] ) await basketHandler.connect(owner).refreshBasket() @@ -540,7 +513,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenFiatCollateral pegPrice: BigNumber @@ -553,7 +525,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: dai, tokenAddress: networkConfig[chainId].tokens.DAI || '', cToken: cDai, - cTokenVault: cDaiVault, cTokenAddress: networkConfig[chainId].tokens.cDAI || '', cTokenCollateral: cDaiCollateral, pegPrice: fp('1'), @@ -563,7 +534,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdc, tokenAddress: networkConfig[chainId].tokens.USDC || '', cToken: cUsdc, - cTokenVault: cUsdcVault, cTokenAddress: networkConfig[chainId].tokens.cUSDC || '', cTokenCollateral: cUsdcCollateral, pegPrice: fp('1.0003994'), @@ -573,7 +543,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdt, tokenAddress: networkConfig[chainId].tokens.USDT || '', cToken: cUsdt, - cTokenVault: cUsdtVault, cTokenAddress: networkConfig[chainId].tokens.cUSDT || '', cTokenCollateral: cUsdtCollateral, pegPrice: fp('0.99934692'), @@ -583,7 +552,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdp, tokenAddress: networkConfig[chainId].tokens.USDP || '', cToken: cUsdp, - cTokenVault: cUsdpVault, cTokenAddress: networkConfig[chainId].tokens.cUSDP || '', cTokenCollateral: cUsdpCollateral, pegPrice: fp('0.99995491'), @@ -598,10 +566,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(8) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cToken.address) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String('USD') ) @@ -622,13 +588,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e4') ) - // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) - - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( @@ -729,15 +690,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e5') ) - // TODO: deprecate await expect(atkInf.aTokenCollateral.claimRewards()) .to.emit(atkInf.stataToken, 'RewardsClaimed') .withArgs(aaveToken.address, 0) - await expect(atkInf.stataToken['claimRewards()']()) - .to.emit(atkInf.stataToken, 'RewardsClaimed') - .withArgs(aaveToken.address, 0) - // Check StaticAToken expect(await atkInf.stataToken.name()).to.equal( 'Static Aave interest bearing ' + (await atkInf.token.symbol()) @@ -829,7 +785,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenNonFiatCollateral targetPrice: BigNumber @@ -844,7 +799,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: wbtc, tokenAddress: networkConfig[chainId].tokens.WBTC || '', cToken: cWBTC, - cTokenVault: cWBTCVault, cTokenAddress: networkConfig[chainId].tokens.cWBTC || '', cTokenCollateral: cWBTCCollateral, targetPrice: fp('31311.5'), // approx price June 6, 2022 @@ -861,10 +815,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(8) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -889,13 +841,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, true ) - // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) - - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( @@ -960,7 +907,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenSelfReferentialCollateral price: BigNumber @@ -974,7 +920,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: weth, tokenAddress: networkConfig[chainId].tokens.WETH || '', cToken: cETH, - cTokenVault: cETHVault, cTokenAddress: networkConfig[chainId].tokens.cETH || '', cTokenCollateral: cETHCollateral, price: fp('1859.17'), // approx price June 6, 2022 @@ -990,10 +935,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(8) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -1015,13 +958,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e5') ) - // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) - - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( @@ -1263,7 +1201,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -1287,7 +1225,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -1505,7 +1443,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cWBTCVault.address, + erc20: cWBTC.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1534,7 +1472,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cWBTCVault.address, + erc20: cWBTC.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1662,7 +1600,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cETHVault.address, + erc20: cETH.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), @@ -1692,7 +1630,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cETHVault.address, + erc20: cETH.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), @@ -1845,7 +1783,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const backing = await facade.basketTokens(rToken.address) expect(backing[0]).to.equal(dai.address) expect(backing[1]).to.equal(stataDai.address) - expect(backing[2]).to.equal(cDaiVault.address) + expect(backing[2]).to.equal(cDai.address) expect(backing.length).to.equal(3) @@ -1859,9 +1797,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const issueAmount: BigNumber = bn('10000e18') await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -1880,22 +1816,18 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances before expect(await dai.balanceOf(backingManager.address)).to.equal(0) expect(await stataDai.balanceOf(backingManager.address)).to.equal(0) - expect(await cDaiVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cDai.balanceOf(backingManager.address)).to.equal(0) expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) const initialBalAToken = initialBal.mul(9321).div(10000) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDaiVault.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBal, 8).mul(100) - ) + expect(await cDai.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBal, 8).mul(100)) // Provide approvals await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1913,7 +1845,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, fp('1') ) const requiredCTokens: BigNumber = bn('227116e8') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo( requiredCTokens, bn('1e8') ) @@ -1924,7 +1856,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, initialBalAToken.sub(issueAmtAToken), fp('1.5') ) - expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cDai.balanceOf(addr1.address)).to.be.closeTo( toBNDecimals(initialBal, 8).mul(100).sub(requiredCTokens), bn('1e8') ) @@ -1949,12 +1881,12 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances after - Backing Manager is empty expect(await dai.balanceOf(backingManager.address)).to.equal(0) expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), fp('0.01')) - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e15')) + expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e15')) // Check funds returned to user expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cDai.balanceOf(addr1.address)).to.be.closeTo( toBNDecimals(initialBal, 8).mul(100), bn('1e16') ) @@ -1973,9 +1905,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals for issuances await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') @@ -1986,7 +1916,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Store Balances after issuance const balanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) const balanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) + const balanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) // Check rates and prices const [aDaiPriceLow1, aDaiPriceHigh1] = await aDaiCollateral.price() // ~1.07546 @@ -2118,7 +2048,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances - Fewer ATokens and cTokens should have been sent to the user const newBalanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) const newBalanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) + const newBalanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) // Check received tokens represent ~10K in value at current prices expect(newBalanceAddr1Dai.sub(balanceAddr1Dai)).to.equal(issueAmount.div(4)) // = 2.5K (25% of basket) @@ -2131,7 +2061,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, fp('219.64'), // ~= 260 usd in value fp('0.01') ) - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo( bn('75331e8'), bn('5e16') ) // ~= 2481 usd in value @@ -2169,9 +2099,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -2222,13 +2150,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Approve all balances for user await wbtc.connect(addr1).approve(rToken.address, await wbtc.balanceOf(addr1.address)) - await cWBTCVault - .connect(addr1) - .approve(rToken.address, await cWBTCVault.balanceOf(addr1.address)) + await cWBTC.connect(addr1).approve(rToken.address, await cWBTC.balanceOf(addr1.address)) await weth.connect(addr1).approve(rToken.address, await weth.balanceOf(addr1.address)) - await cETHVault - .connect(addr1) - .approve(rToken.address, await cETHVault.balanceOf(addr1.address)) + await cETH.connect(addr1).approve(rToken.address, await cETH.balanceOf(addr1.address)) await eurt.connect(addr1).approve(rToken.address, await eurt.balanceOf(addr1.address)) }) @@ -2268,9 +2192,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await basketHandler.fullyCollateralized()).to.equal(true) const backing = await facade.basketTokens(rToken.address) expect(backing[0]).to.equal(wbtc.address) - expect(backing[1]).to.equal(cWBTCVault.address) + expect(backing[1]).to.equal(cWBTC.address) expect(backing[2]).to.equal(weth.address) - expect(backing[3]).to.equal(cETHVault.address) + expect(backing[3]).to.equal(cETH.address) expect(backing[4]).to.equal(eurt.address) expect(backing.length).to.equal(5) @@ -2294,17 +2218,17 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances before expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cWBTC.balanceOf(backingManager.address)).to.equal(0) expect(await weth.balanceOf(backingManager.address)).to.equal(0) - expect(await cETHVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cETH.balanceOf(backingManager.address)).to.equal(0) expect(await eurt.balanceOf(backingManager.address)).to.equal(0) expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) - expect(await cWBTCVault.balanceOf(addr1.address)).to.equal( + expect(await cWBTC.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth, 8).mul(1000) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) - expect(await cETHVault.balanceOf(addr1.address)).to.equal( + expect(await cETH.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth, 8).mul(1000) ) expect(await eurt.balanceOf(addr1.address)).to.equal( @@ -2318,13 +2242,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check Balances after expect(await wbtc.balanceOf(backingManager.address)).to.equal(toBNDecimals(issueAmount, 8)) //1 full units const requiredCWBTC: BigNumber = toBNDecimals(fp('49.85'), 8) // approx 49.5 cWBTC needed (~1 wbtc / 0.02006) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cWBTC.balanceOf(backingManager.address)).to.be.closeTo( requiredCWBTC, point1Pct(requiredCWBTC) ) expect(await weth.balanceOf(backingManager.address)).to.equal(issueAmount) //1 full units const requiredCETH: BigNumber = toBNDecimals(fp('49.8'), 8) // approx 49.8 cETH needed (~1 weth / 0.02020) - expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo( requiredCETH, point1Pct(requiredCETH) ) @@ -2335,13 +2259,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, toBNDecimals(initialBalBtcEth.sub(issueAmount), 8) ) const expectedcWBTCBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCWBTC) - expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( expectedcWBTCBalance, point1Pct(expectedcWBTCBalance) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth.sub(issueAmount)) const expectedcETHBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCETH) - expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( expectedcETHBalance, point1Pct(expectedcETHBalance) ) @@ -2369,19 +2293,19 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances after - Backing Manager is empty expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) + expect(await cWBTC.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) expect(await weth.balanceOf(backingManager.address)).to.equal(0) - expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) + expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) expect(await eurt.balanceOf(backingManager.address)).to.equal(0) // Check funds returned to user expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) - expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( toBNDecimals(initialBalBtcEth, 8).mul(1000), bn('10e9') ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) - expect(await cETHVault.balanceOf(addr1.address)).to.be.closeTo( + expect(await cETH.balanceOf(addr1.address)).to.be.closeTo( toBNDecimals(initialBalBtcEth, 8).mul(1000), bn('10e9') ) @@ -2402,7 +2326,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await expectEvents(backingManager.claimRewards(), [ { - contract: cWBTCVault, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, @@ -2426,7 +2350,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await advanceTime(8000) // Claim rewards - await expect(backingManager.claimRewards()).to.emit(cWBTCVault, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') // Check rewards both in COMP const rewardsCOMP1: BigNumber = await compToken.balanceOf(backingManager.address) @@ -2436,7 +2360,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await advanceTime(3600) // Get additional rewards - await expect(backingManager.claimRewards()).to.emit(cWBTCVault, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') const rewardsCOMP2: BigNumber = await compToken.balanceOf(backingManager.address) expect(rewardsCOMP2.sub(rewardsCOMP1)).to.be.gt(0) @@ -2604,10 +2528,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, stataDai = ( await ethers.getContractAt('StaticATokenLM', await aDaiCollateral.erc20()) ) - cDaiVault = ( - await ethers.getContractAt('CTokenWrapper', await cDaiCollateral.erc20()) - ) - cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.underlying()) + cDai = await ethers.getContractAt('CTokenMock', await cDaiCollateral.erc20()) // Get plain aToken aDai = ( @@ -2633,8 +2554,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // cDAI await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) - await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) }) }) @@ -2668,7 +2587,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals for issuances await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 6311deabc1..8ba38a3e62 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -14,6 +14,7 @@ import { advanceTime } from '../utils/time' import { bn, fp } from '../../common/numbers' import { AaveLendingPoolMock, + ActFacet, Asset, AssetRegistryP1, ATokenFiatCollateral, @@ -33,8 +34,6 @@ import { EasyAuction, ERC20Mock, EURFiatCollateral, - FacadeRead, - FacadeAct, FacadeTest, FiatCollateral, FurnaceP1, @@ -43,6 +42,7 @@ import { IERC20Metadata, MainP1, NonFiatCollateral, + ReadFacet, RevenueTraderP1, RTokenAsset, RTokenP1, @@ -54,6 +54,7 @@ import { TestIBroker, TestIDeployer, TestIDistributor, + TestIFacade, TestIFurnace, TestIMain, TestIRevenueTrader, @@ -154,7 +155,6 @@ interface CollateralFixture { } export async function collateralFixture( - comptroller: ComptrollerMock, aaveLendingPool: AaveLendingPoolMock, config: IConfig ): Promise { @@ -163,8 +163,6 @@ export async function collateralFixture( throw new Error(`Missing network configuration for ${hre.network.name}`) } - const CTokenWrapperFactory = await ethers.getContractFactory('CTokenWrapper') - const StaticATokenFactory: ContractFactory = await ethers.getContractFactory('StaticATokenLM') const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') @@ -213,18 +211,12 @@ export async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenWrapperFactory.deploy( - erc20.address, - `${await erc20.name()} Vault`, - `${await erc20.symbol()}-VAULT`, - comptroller.address - ) const coll = await CTokenCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: chainlinkAddr, oracleError: ORACLE_ERROR, - erc20: vault.address, + erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -234,7 +226,7 @@ export async function collateralFixture( REVENUE_HIDING ) await coll.refresh() - return [vault, coll] + return [erc20, coll] } const makeATokenCollateral = async ( @@ -309,18 +301,12 @@ export async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenWrapperFactory.deploy( - erc20.address, - `${await erc20.name()} Vault`, - `${await erc20.symbol()}-VAULT`, - comptroller.address - ) const coll = await CTokenNonFiatCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracleAddr, oracleError: ORACLE_ERROR, - erc20: vault.address, + erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), @@ -332,7 +318,7 @@ export async function collateralFixture( REVENUE_HIDING ) await coll.refresh() - return [vault, coll] + return [erc20, coll] } const makeSelfReferentialCollateral = async ( @@ -365,19 +351,13 @@ export async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenWrapperFactory.deploy( - erc20.address, - `${await erc20.name()} Vault`, - `${await erc20.symbol()}-VAULT`, - comptroller.address - ) const coll = ( await CTokenSelfReferentialCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: chainlinkAddr, oracleError: ORACLE_ERROR, - erc20: vault.address, + erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), @@ -389,7 +369,7 @@ export async function collateralFixture( ) ) await coll.refresh() - return [vault, coll] + return [erc20, coll] } const makeEURFiatCollateral = async ( @@ -608,8 +588,7 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt rTokenAsset: RTokenAsset furnace: TestIFurnace stRSR: TestIStRSR - facade: FacadeRead - facadeAct: FacadeAct + facade: TestIFacade facadeTest: FacadeTest facadeMonitor: FacadeMonitor broker: TestIBroker @@ -678,13 +657,25 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = AAVE_V2_DATA_PROVIDER_ADDR: networkConfig[chainId].AAVE_DATA_PROVIDER ?? ZERO_ADDRESS, } - // Deploy FacadeRead - const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') - const facade = await FacadeReadFactory.deploy() + // Deploy Facade + const FacadeFactory: ContractFactory = await ethers.getContractFactory('Facade') + const facade = await ethers.getContractAt('TestIFacade', (await FacadeFactory.deploy()).address) + + // Save ReadFacet to Facade + const ReadFacetFactory: ContractFactory = await ethers.getContractFactory('ReadFacet') + const readFacet = await ReadFacetFactory.deploy() + await facade.save( + readFacet.address, + Object.entries(readFacet.functions).map(([fn]) => readFacet.interface.getSighash(fn)) + ) - // Deploy FacadeAct - const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') - const facadeAct = await FacadeActFactory.deploy() + // Save ActFacet to Facade + const ActFacetFactory: ContractFactory = await ethers.getContractFactory('ActFacet') + const actFacet = await ActFacetFactory.deploy() + await facade.save( + actFacet.address, + Object.entries(actFacet.functions).map(([fn]) => actFacet.interface.getSighash(fn)) + ) // Deploy FacadeTest const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') @@ -876,7 +867,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = // Deploy collateral for Main const { erc20s, collateral, basket, basketsNeededAmts } = await collateralFixture( - compoundMock, aaveMock, config ) @@ -947,7 +937,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = broker, easyAuction, facade, - facadeAct, facadeTest, facadeMonitor, rsrTrader, diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index f5b5dff068..0a5c8a0c73 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -6,7 +6,7 @@ const forkBlockNumber = { 'flux-finance': 16836855, // Ethereum 'mainnet-2.0': 17522362, // Ethereum 'facade-monitor': 18742016, // Ethereum - default: 18522901, // Ethereum + default: 19275770, // Ethereum } export default forkBlockNumber diff --git a/test/integration/mainnet-test/FacadeActVersion.test.ts b/test/integration/mainnet-test/FacadeActVersion.test.ts index 0fadf15743..1ff6300e68 100644 --- a/test/integration/mainnet-test/FacadeActVersion.test.ts +++ b/test/integration/mainnet-test/FacadeActVersion.test.ts @@ -6,7 +6,7 @@ import { IMPLEMENTATION } from '../../fixtures' import { getChainId } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' import forkBlockNumber from '../fork-block-numbers' -import { FacadeAct, RevenueTraderP1 } from '../../../typechain' +import { ActFacet, RevenueTraderP1 } from '../../../typechain' import { useEnv } from '#/utils/env' import { getLatestBlockTimestamp, setNextBlockTimestamp } from '../../utils/time' import { ONE_PERIOD } from '#/common/constants' @@ -18,10 +18,10 @@ const FACADE_ACT_ADDR = '0xeaCaF85eA2df99e56053FD0250330C148D582547' // V3 const SELL_TOKEN_ADDR = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15' // aUSDC describeFork( - `FacadeAct - Settle Auctions - Mainnet Check - Mainnet Forking P${IMPLEMENTATION}`, + `ActFacet - Settle Auctions - Mainnet Check - Mainnet Forking P${IMPLEMENTATION}`, function () { - let facadeAct: FacadeAct - let newFacadeAct: FacadeAct + let facadeAct: ActFacet + let newFacadeAct: ActFacet let revenueTrader: RevenueTraderP1 let chainId: number @@ -43,7 +43,7 @@ describeFork( }) } - describe('FacadeAct', () => { + describe('ActFacet', () => { before(async () => { await setup(forkBlockNumber['mainnet-2.0']) @@ -60,7 +60,7 @@ describeFork( snap = await evmSnapshot() // Get contracts - facadeAct = await ethers.getContractAt('FacadeAct', FACADE_ACT_ADDR) + facadeAct = await ethers.getContractAt('ActFacet', FACADE_ACT_ADDR) revenueTrader = ( await ethers.getContractAt('RevenueTraderP1', REVENUE_TRADER_ADDR) ) @@ -76,7 +76,7 @@ describeFork( expect(await revenueTrader.tradesOpen()).to.equal(0) }) - it('Should fail with deployed FacadeAct', async () => { + it('Should fail with deployed ActFacet', async () => { expect(await revenueTrader.tradesOpen()).to.equal(1) await expect( facadeAct.runRevenueAuctions(revenueTrader.address, [SELL_TOKEN_ADDR], [], [1]) @@ -84,10 +84,10 @@ describeFork( expect(await revenueTrader.tradesOpen()).to.equal(1) }) - it('Should work with fixed FacadeAct', async () => { + it('Should work with fixed ActFacet', async () => { expect(await revenueTrader.tradesOpen()).to.equal(1) - const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + const FacadeActFactory = await ethers.getContractFactory('ActFacet') newFacadeAct = await FacadeActFactory.deploy() await newFacadeAct.runRevenueAuctions(revenueTrader.address, [SELL_TOKEN_ADDR], [], [1]) @@ -95,8 +95,8 @@ describeFork( expect(await revenueTrader.tradesOpen()).to.equal(0) }) - it('Fixed FacadeAct should return right revenueOverview', async () => { - const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + it('Fixed ActFacet should return right revenueOverview', async () => { + const FacadeActFactory = await ethers.getContractFactory('ActFacet') await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) newFacadeAct = await FacadeActFactory.deploy() @@ -142,8 +142,8 @@ describeFork( } }) - it('Fixed FacadeAct should run revenue auctions', async () => { - const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + it('Fixed ActFacet should run revenue auctions', async () => { + const FacadeActFactory = await ethers.getContractFactory('ActFacet') newFacadeAct = await FacadeActFactory.deploy() expect(await revenueTrader.tradesOpen()).to.equal(1) diff --git a/test/libraries/Fixed.test.ts b/test/libraries/Fixed.test.ts index 3f313e2c0f..8fa2451627 100644 --- a/test/libraries/Fixed.test.ts +++ b/test/libraries/Fixed.test.ts @@ -738,22 +738,15 @@ describe('In FixLib,', () => { }) }) describe('powu', () => { - it('correctly exponentiates inside its range', () => { + context('correctly exponentiates inside its range', () => { // prettier-ignore const table = [ - [fp(1.0), bn(1), fp(1.0)], - [fp(1.0), bn(15), fp(1.0)], - [fp(2), bn(7), fp(128)], - [fp(2), bn(63), fp('9223372036854775808')], - [fp(2), bn(64), fp('18446744073709551616')], - [fp(1.5), bn(7), fp(17.0859375)], - [fp(1.1), bn(4), fp('1.4641')], - [fp(1.1), bn(5), fp('1.61051')], - [fp(0.23), bn(3), fp('0.012167')], - [bn(1), bn(2), bn(0)], - [fp('1e-9'), bn(2), fp('1e-18')], - [fp(0.1), bn(17), fp('1e-17')], - [fp(10), bn(19), fp('1e19')] + [fp('1'), bn('1'), fp('1')], + [fp('1'), bn('15'), fp('1')], + [fp('0.23'), bn('3'), fp('0.012167')], + [bn('1'), bn('2'), bn('0')], + [fp('1e-9'), bn('2'), fp('1e-18')], + [fp('0.1'), bn('17'), fp('1e-17')], ] for (const [a, b, c] of table) { @@ -763,32 +756,11 @@ describe('In FixLib,', () => { } }) - it('correctly exponentiates at the extremes of its range', () => { + context('fails outside its range', () => { const table = [ - [MAX_UINT192, bn(1), MAX_UINT192], - [MIN_UINT192, bn(1), MIN_UINT192], - [MIN_UINT192, bn(0), fp(1)], - [fp(0), bn(0), fp(1.0)], - [fp(987.0), bn(0), fp(1.0)], - [fp(1.0), bn(2).pow(32).sub(1), fp(1.0)], - [fp(2), bn(131), fp(bn(2).pow(131))], - ] - - for (const [a, b, c] of table) { - it(`powu(${shortString(a)}, ${shortString(b)}) == ${shortString(c)}`, async () => { - expect(await caller.powu(a, b)).to.equal(c) - }) - } - }) - it('fails outside its range', () => { - const table = [ - [fp(10), bn(40)], + [fp('1.0001'), bn(1)], + [fp('2'), bn(1)], [MAX_UINT192, bn(2)], - [fp('8e19'), bn(2)], - [fp('1.9e13'), bn(3)], - [fp('9e9'), bn(4)], - [fp('9.2e8'), bn(5)], - [fp(2), bn(191)], ] for (const [a, b] of table) { diff --git a/test/monitor/FacadeMonitor.test.ts b/test/monitor/FacadeMonitor.test.ts index 45b7bf1d22..a9dcaffdc2 100644 --- a/test/monitor/FacadeMonitor.test.ts +++ b/test/monitor/FacadeMonitor.test.ts @@ -35,7 +35,6 @@ import { TestICToken, TestIRToken, USDCMock, - CTokenWrapper, StaticATokenV3LM, CusdcV3Wrapper, CometInterface, @@ -89,7 +88,6 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, let fUsdc: TestICToken let weth: IWETH let cDai: TestICToken - let cDaiVault: CTokenWrapper let cusdcV3: CometInterface let daiCollateral: FiatCollateral let aDaiCollateral: ATokenFiatCollateral @@ -153,8 +151,6 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Get tokens dai = erc20s[0] // DAI - cDaiVault = erc20s[6] // cDAI - cDai = await ethers.getContractAt('TestICToken', await cDaiVault.underlying()) // cDAI stataDai = erc20s[10] // static aDAI // Get plain aTokens @@ -198,6 +194,10 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await ethers.getContractAt('CometInterface', networkConfig[chainId].tokens.cUSDCv3 || '') ) + cDai = ( + await ethers.getContractAt('TestICToken', networkConfig[chainId].tokens.cDAI || '') + ) + sUsdc = ( await ethers.getContractAt('IStargatePool', networkConfig[chainId].tokens.sUSDC || '') ) @@ -229,8 +229,6 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Fund user with cDAI await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) - await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) }) // Fund user with cUSDCV3 @@ -676,14 +674,12 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, beforeEach(async () => { // Setup basket - await basketHandler.connect(owner).setPrimeBasket([cDaiVault.address], [fp('1')]) + await basketHandler.connect(owner).setPrimeBasket([cDai.address], [fp('1')]) await basketHandler.connect(owner).refreshBasket() await advanceTime(Number(config.warmupPeriod) + 1) // Provide approvals - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Advance time significantly - Recharge throttle await advanceTime(100000) @@ -724,17 +720,16 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.equal(fp('1')) // Confirm all can be redeemed expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) + const bmBalanceAmt = await cDai.balanceOf(backingManager.address) await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) + await cDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) expect(await cDai.balanceOf(addr2.address)).to.equal(bmBalanceAmt) await expect(cDai.connect(addr2).redeem(bmBalanceAmt)).to.not.be.reverted @@ -748,7 +743,7 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.equal(fp('1')) @@ -760,7 +755,7 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.be.closeTo(fp('0.80'), fp('0.01')) @@ -773,17 +768,16 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.be.closeTo(fp('0.40'), fp('0.01')) // Confirm we cannot redeem full balance expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) + const bmBalanceAmt = await cDai.balanceOf(backingManager.address) await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) + await cDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) await expect(cDai.connect(addr2).redeem(bmBalanceAmt)).to.be.reverted expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) @@ -797,7 +791,7 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.equal(fp('1')) @@ -808,17 +802,16 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, await facadeMonitor.backingReedemable( rToken.address, CollPluginType.COMPOUND_V2, - cDaiVault.address + cDai.address ) ).to.be.closeTo(fp('0'), fp('0.01')) // Confirm we cannot redeem anything, not even 1% expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) + const bmBalanceAmt = await cDai.balanceOf(backingManager.address) await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) + await cDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) expect(await cDai.balanceOf(addr2.address)).to.equal(bmBalanceAmt) await expect(cDai.connect(addr2).redeem((await cDai.balanceOf(addr2.address)).div(100))).to @@ -864,8 +857,7 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h }, - fp('1e-6'), - bn('10000e6').toString() // $10k + fp('1e-6') ) // Register and update collateral @@ -1205,7 +1197,6 @@ describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, maUSDC = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, - rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.USDC!, poolToken: networkConfig[chainId].tokens.aUSDC!, rewardToken: networkConfig[chainId].tokens.MORPHO!, diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 029a34a683..218ac6189b 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -26,7 +26,7 @@ import { Asset, ATokenFiatCollateral, CTokenFiatCollateral, - CTokenWrapperMock, + CTokenMock, ERC20Mock, FiatCollateral, GnosisMock, @@ -72,7 +72,7 @@ describe('Assets contracts #fast', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenWrapperMock + let cToken: CTokenMock // Assets let collateral0: FiatCollateral @@ -140,9 +140,7 @@ describe('Assets contracts #fast', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - cToken = ( - await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) - ) + cToken = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) await rsr.connect(wallet).mint(wallet.address, amt) await compToken.connect(wallet).mint(wallet.address, amt) @@ -466,6 +464,70 @@ describe('Assets contracts #fast', () => { await expect(rTokenAsset.price()).to.be.reverted }) + it('Should return latestPrice() for RTokenAsset correctly', async () => { + // Confirm current price $1 + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) + + // Latest Price returns current price + let rTokenPriceInfo = await rTokenAsset.callStatic.latestPrice() + expect(rTokenPriceInfo.rTokenPrice).to.equal(fp('1')) + expect(rTokenPriceInfo.updatedAt).to.be.lte(await getLatestBlockTimestamp()) + + // Perform actual call to cache data + await rTokenAsset.latestPrice() + let latestUpdate = await getLatestBlockTimestamp() + + // Calling again is noop + await rTokenAsset.latestPrice() + rTokenPriceInfo = await rTokenAsset.callStatic.latestPrice() + expect(rTokenPriceInfo.rTokenPrice).to.equal(fp('1')) + expect(rTokenPriceInfo.updatedAt).to.be.eq(latestUpdate) // did not refresh again + + // Will refresh if basket changes + await basketHandler + .connect(wallet) + .setPrimeBasket([token.address, usdc.address], [fp('0.5'), fp('0.5')]) + await basketHandler.connect(wallet).refreshBasket() + + // Perform actual call and check values + await rTokenAsset.latestPrice() + latestUpdate = await getLatestBlockTimestamp() + rTokenPriceInfo = await rTokenAsset.callStatic.latestPrice() + expect(rTokenPriceInfo.rTokenPrice).to.be.closeTo(fp('1'), fp('0.01')) // remains close + expect(rTokenPriceInfo.updatedAt).to.be.eq(latestUpdate) // refreshed + + // Perform trade (changes trade nonce) + await backingManager.rebalance(TradeKind.BATCH_AUCTION) + + // Perform actual call and check values + await rTokenAsset.latestPrice() + latestUpdate = await getLatestBlockTimestamp() + rTokenPriceInfo = await rTokenAsset.callStatic.latestPrice() + expect(rTokenPriceInfo.rTokenPrice).to.be.closeTo(fp('1'), fp('0.01')) // remains close + expect(rTokenPriceInfo.updatedAt).to.be.eq(latestUpdate) // refreshed + + // Calling again is noop + await rTokenAsset.latestPrice() + + // Settle trade + await advanceTime(config.batchAuctionLength.add(100).toString()) + await rTokenAsset.latestPrice() // update cache + await backingManager.settleTrade(aToken.address) + + // Perform actual call and check values + await rTokenAsset.latestPrice() + latestUpdate = await getLatestBlockTimestamp() + rTokenPriceInfo = await rTokenAsset.callStatic.latestPrice() + expect(rTokenPriceInfo.rTokenPrice).to.be.closeTo(fp('1'), fp('0.01')) // remains close + expect(rTokenPriceInfo.updatedAt).to.be.eq(latestUpdate) // refreshed + }) + it('Regression test -- Should handle unpriced collateral for RTokenAsset', async () => { // https://github.com/code-423n4/2023-07-reserve-findings/issues/20 @@ -493,6 +555,9 @@ describe('Assets contracts #fast', () => { // Check RToken is unpriced await expectUnpriced(rTokenAsset.address) + + // Oracle price update should revert + await expect(rTokenAsset.forceUpdatePrice()).to.be.revertedWith('invalid price') }) it('Regression test -- RTokenAsset.refresh() should refresh everything', async () => { @@ -760,6 +825,31 @@ describe('Assets contracts #fast', () => { await expect(invalidRSRAsset.refresh()).to.be.reverted }) + it('Bubbles error up if Chainlink feed reverts for explicit reason', async () => { + // Applies to all collateral as well + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed: InvalidMockV3Aggregator = ( + await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) + ) + + const invalidRSRAsset: Asset = ( + await AssetFactory.deploy( + PRICE_TIMEOUT, + invalidChainlinkFeed.address, + ORACLE_ERROR, + rsr.address, + config.rTokenMaxTradeVolume, + ORACLE_TIMEOUT + ) + ) + + // Reverting with reason + await invalidChainlinkFeed.setRevertWithExplicitError(true) + await expect(invalidRSRAsset.tryPrice()).to.be.revertedWith('oracle explicit error') + }) + it('Should handle price decay correctly', async () => { await rsrAsset.refresh() diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 29e37b53f4..22ffdbc792 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -8,11 +8,11 @@ import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../common/constan import { bn, fp } from '../../common/numbers' import { ATokenFiatCollateral, + BadERC20, ComptrollerMock, CTokenFiatCollateral, CTokenNonFiatCollateral, CTokenMock, - CTokenWrapperMock, CTokenSelfReferentialCollateral, ERC20Mock, EURFiatCollateral, @@ -72,7 +72,7 @@ describe('Collateral contracts', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenWrapperMock + let cToken: CTokenMock let aaveToken: ERC20Mock let compToken: ERC20Mock @@ -133,9 +133,7 @@ describe('Collateral contracts', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenCollateral.erc20()) ) - cToken = ( - await ethers.getContractAt('CTokenWrapperMock', await cTokenCollateral.erc20()) - ) + cToken = await ethers.getContractAt('CTokenMock', await cTokenCollateral.erc20()) await token.connect(owner).mint(owner.address, amt) await usdc.connect(owner).mint(owner.address, amt.div(bn('1e12'))) @@ -241,7 +239,7 @@ describe('Collateral contracts', () => { ) await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) await expect(cTokenCollateral.claimRewards()) - .to.emit(cToken, 'RewardsClaimed') + .to.emit(cTokenCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) }) }) @@ -405,6 +403,41 @@ describe('Collateral contracts', () => { ).to.be.revertedWith('delayUntilDefault too long') }) + it('Should not allow missing referenceERC20Decimals', async () => { + // CTokenFiatCollateral with decimals = 0 in underlying + const token0decimals: BadERC20 = await ( + await ethers.getContractFactory('BadERC20') + ).deploy('Bad ERC20', 'BERC20') + await token0decimals.setDecimals(0) + + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const cToken0Dec: CTokenMock = ( + await CTokenMockFactory.deploy( + '0 Decimal Token', + '0 Decimal Token', + token0decimals.address, + compoundMock.address + ) + ) + + await expect( + CTokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await tokenCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cToken0Dec.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('referenceERC20Decimals missing') + }) + it('Should not allow out of range oracle error', async () => { // === Begin zero oracle error checks === await expect( @@ -819,6 +852,57 @@ describe('Collateral contracts', () => { expect(await unpricedAppFiatCollateral.lastSave()).to.equal(currBlockTimestamp) }) + it('Should not save prices if try/price returns unpriced - Fiat Collateral', async () => { + const UnpricedFiatFactory = await ethers.getContractFactory('UnpricedFiatCollateralMock') + const unpricedFiatCollateral: UnpricedFiatCollateralMock = ( + await UnpricedFiatFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await tokenCollateral.chainlinkFeed(), // reuse - mock + oracleError: ORACLE_ERROR, + erc20: token.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }) + ) + + // Save prices + await unpricedFiatCollateral.refresh() + + // Check initial prices + let currBlockTimestamp: number = await getLatestBlockTimestamp() + await expectPrice(unpricedFiatCollateral.address, fp('1'), ORACLE_ERROR, true) + let [lowPrice, highPrice] = await unpricedFiatCollateral.price() + expect(await unpricedFiatCollateral.savedLowPrice()).to.equal(lowPrice) + expect(await unpricedFiatCollateral.savedHighPrice()).to.equal(highPrice) + expect(await unpricedFiatCollateral.lastSave()).to.be.equal(currBlockTimestamp) + + // Refresh saved prices + await unpricedFiatCollateral.refresh() + + // Check values remain but timestamp was updated + await expectPrice(unpricedFiatCollateral.address, fp('1'), ORACLE_ERROR, true) + ;[lowPrice, highPrice] = await unpricedFiatCollateral.price() + expect(await unpricedFiatCollateral.savedLowPrice()).to.equal(lowPrice) + expect(await unpricedFiatCollateral.savedHighPrice()).to.equal(highPrice) + currBlockTimestamp = await getLatestBlockTimestamp() + expect(await unpricedFiatCollateral.lastSave()).to.equal(currBlockTimestamp) + + // Set as unpriced so it returns 0,FIX MAX in try/price + await unpricedFiatCollateral.setUnpriced(true) + + // Check that now is unpriced + await expectUnpriced(unpricedFiatCollateral.address) + + // Refreshing would not save the new rates + await unpricedFiatCollateral.refresh() + expect(await unpricedFiatCollateral.savedLowPrice()).to.equal(lowPrice) + expect(await unpricedFiatCollateral.savedHighPrice()).to.equal(highPrice) + expect(await unpricedFiatCollateral.lastSave()).to.equal(currBlockTimestamp) + }) + it('lotPrice (deprecated) is equal to price()', async () => { for (const coll of [tokenCollateral, usdcCollateral, aTokenCollateral, cTokenCollateral]) { const lotPrice = await coll.lotPrice() @@ -978,6 +1062,9 @@ describe('Collateral contracts', () => { .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) expect(await coll.status()).to.equal(CollateralStatus.DISABLED) expect(await coll.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Refresh is a noop if DISABLED + await expect(coll.refresh()).to.not.emit(coll, 'CollateralStatusChanged') } }) @@ -1021,10 +1108,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cToken.underlying()) - ) - await cTokenErc20Mock.setRevertExchangeRate(true) + await cToken.setRevertExchangeRateCurrent(true) // Refresh - should not revert - Sets DISABLED await expect(cTokenCollateral.refresh()) @@ -1043,6 +1127,43 @@ describe('Collateral contracts', () => { const [newLow, newHigh] = await cTokenCollateral.price() expect(newLow).to.equal(currLow) expect(newHigh).to.equal(currHigh) + + // Refresh is a noop if already DISABLED + await expect(cTokenCollateral.refresh()).to.not.emit( + cTokenCollateral, + 'CollateralStatusChanged' + ) + }) + + it('CTokens - Enters DISABLED state when underlyingRefPerTok reverts', async () => { + const [currLow, currHigh] = await cTokenCollateral.price() + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Make cToken revert on underlyingRefPerTok + await cToken.setRevertExchangeRateStored(true) + await expect(cToken.exchangeRateStored()).to.be.reverted + await expect(cTokenCollateral.underlyingRefPerTok()).to.be.reverted + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenCollateral.refresh()) + .to.emit(cTokenCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Price remains the same + await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + + // Refresh is a noop if already DISABLED + await expect(cTokenCollateral.refresh()).to.not.emit( + cTokenCollateral, + 'CollateralStatusChanged' + ) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status - Fiat', async () => { @@ -1445,7 +1566,7 @@ describe('Collateral contracts', () => { let CTokenNonFiatFactory: ContractFactory let cTokenNonFiatCollateral: CTokenNonFiatCollateral let nonFiatToken: ERC20Mock - let cNonFiatTokenVault: CTokenWrapperMock + let cNonFiatToken: CTokenMock let targetUnitOracle: MockV3Aggregator let referenceUnitOracle: MockV3Aggregator @@ -1461,15 +1582,9 @@ describe('Collateral contracts', () => { await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) // 1 WBTC/BTC ) // cToken - cNonFiatTokenVault = await ( - await ethers.getContractFactory('CTokenWrapperMock') - ).deploy( - 'cWBTC Token', - 'cWBTC', - nonFiatToken.address, - compToken.address, - compoundMock.address - ) + cNonFiatToken = await ( + await ethers.getContractFactory('CTokenMock') + ).deploy('cWBTC Token', 'cWBTC', nonFiatToken.address, compoundMock.address) CTokenNonFiatFactory = await ethers.getContractFactory('CTokenNonFiatCollateral') @@ -1478,7 +1593,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1492,7 +1607,7 @@ describe('Collateral contracts', () => { await cTokenNonFiatCollateral.refresh() // Mint some tokens - await cNonFiatTokenVault.connect(owner).mint(owner.address, amt.div(bn('1e10'))) + await cNonFiatToken.connect(owner).mint(owner.address, amt.div(bn('1e10'))) }) it('Should not allow missing delayUntilDefault', async () => { @@ -1502,7 +1617,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1523,7 +1638,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1544,7 +1659,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ZERO_ADDRESS, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1565,7 +1680,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1586,7 +1701,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1607,7 +1722,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1629,8 +1744,8 @@ describe('Collateral contracts', () => { ) expect(await cTokenNonFiatCollateral.chainlinkFeed()).to.equal(referenceUnitOracle.address) expect(await cTokenNonFiatCollateral.referenceERC20Decimals()).to.equal(18) - expect(await cTokenNonFiatCollateral.erc20()).to.equal(cNonFiatTokenVault.address) - expect(await cNonFiatTokenVault.decimals()).to.equal(8) + expect(await cTokenNonFiatCollateral.erc20()).to.equal(cNonFiatToken.address) + expect(await cNonFiatToken.decimals()).to.equal(8) expect(await cTokenNonFiatCollateral.targetName()).to.equal( ethers.utils.formatBytes32String('BTC') ) @@ -1654,7 +1769,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) // 0.02 of 20k await expect(cTokenNonFiatCollateral.claimRewards()) - .to.emit(cNonFiatTokenVault, 'RewardsClaimed') + .to.emit(cTokenNonFiatCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) }) @@ -1664,7 +1779,7 @@ describe('Collateral contracts', () => { expect(await cTokenNonFiatCollateral.refPerTok()).to.equal(fp('0.02')) // Increase rate to double - await cNonFiatTokenVault.setExchangeRate(fp(2)) + await cNonFiatToken.setExchangeRate(fp(2)) await cTokenNonFiatCollateral.refresh() // RefPerTok also doubles in this case @@ -1720,49 +1835,14 @@ describe('Collateral contracts', () => { }) it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { - const currRate = await cNonFiatTokenVault.exchangeRateStored() - const [currLow, currHigh] = await cTokenNonFiatCollateral.price() - - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) - - // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cNonFiatTokenVault.underlying()) - ) - await cTokenErc20Mock.setRevertExchangeRate(true) - - // Refresh - should not revert - Sets DISABLED - await expect(cTokenNonFiatCollateral.refresh()) - .to.emit(cTokenNonFiatCollateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) - const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await cTokenNonFiatCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) - - // Exchange rate stored is still accessible - expect(await cNonFiatTokenVault.exchangeRateStored()).to.equal(currRate) - - // Price remains the same - await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) - const [newLow, newHigh] = await cTokenNonFiatCollateral.price() - expect(newLow).to.equal(currLow) - expect(newHigh).to.equal(currHigh) - }) - - it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { - const currRate = await cNonFiatTokenVault.exchangeRateStored() + const currRate = await cNonFiatToken.exchangeRateStored() const [currLow, currHigh] = await cTokenNonFiatCollateral.price() expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cNonFiatTokenVault.underlying()) - ) - await cTokenErc20Mock.setRevertExchangeRate(true) + await cNonFiatToken.setRevertExchangeRateCurrent(true) // Refresh - should not revert - Sets DISABLED await expect(cTokenNonFiatCollateral.refresh()) @@ -1774,45 +1854,19 @@ describe('Collateral contracts', () => { expect(await cTokenNonFiatCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) // Exchange rate stored is still accessible - expect(await cNonFiatTokenVault.exchangeRateStored()).to.equal(currRate) + expect(await cNonFiatToken.exchangeRateStored()).to.equal(currRate) // Price remains the same await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) const [newLow, newHigh] = await cTokenNonFiatCollateral.price() expect(newLow).to.equal(currLow) expect(newHigh).to.equal(currHigh) - }) - - it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { - const currRate = await cNonFiatTokenVault.exchangeRateStored() - const [currLow, currHigh] = await cTokenNonFiatCollateral.price() - - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) - // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cNonFiatTokenVault.underlying()) + // Refresh is a noop if DISABLED + await expect(cTokenNonFiatCollateral.refresh()).to.not.emit( + cTokenNonFiatCollateral, + 'CollateralStatusChanged' ) - await cTokenErc20Mock.setRevertExchangeRate(true) - - // Refresh - should not revert - Sets DISABLED - await expect(cTokenNonFiatCollateral.refresh()) - .to.emit(cTokenNonFiatCollateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) - const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await cTokenNonFiatCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) - - // Exchange rate stored is still accessible - expect(await cNonFiatTokenVault.exchangeRateStored()).to.equal(currRate) - - // Price remains the same - await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) - const [newLow, newHigh] = await cTokenNonFiatCollateral.price() - expect(newLow).to.equal(currLow) - expect(newHigh).to.equal(currHigh) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { @@ -1826,7 +1880,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: invalidChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1855,7 +1909,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatTokenVault.address, + erc20: cNonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -2028,7 +2082,7 @@ describe('Collateral contracts', () => { let CTokenSelfReferentialFactory: ContractFactory let cTokenSelfReferentialCollateral: CTokenSelfReferentialCollateral let selfRefToken: WETH9 - let cSelfRefToken: CTokenWrapperMock + let cSelfRefToken: CTokenMock let chainlinkFeed: MockV3Aggregator beforeEach(async () => { @@ -2039,8 +2093,8 @@ describe('Collateral contracts', () => { // cToken Self Ref cSelfRefToken = await ( - await ethers.getContractFactory('CTokenWrapperMock') - ).deploy('cETH Token', 'cETH', selfRefToken.address, compToken.address, compoundMock.address) + await ethers.getContractFactory('CTokenMock') + ).deploy('cETH Token', 'cETH', selfRefToken.address, compoundMock.address) CTokenSelfReferentialFactory = await ethers.getContractFactory( 'CTokenSelfReferentialCollateral' @@ -2156,7 +2210,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) await expect(cTokenSelfReferentialCollateral.claimRewards()) - .to.emit(cSelfRefToken, 'RewardsClaimed') + .to.emit(cTokenSelfReferentialCollateral, 'RewardsClaimed') .withArgs(compToken.address, 0) }) @@ -2212,10 +2266,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cSelfRefToken.underlying()) - ) - await cTokenErc20Mock.setRevertExchangeRate(true) + await cSelfRefToken.setRevertExchangeRateCurrent(true) // Refresh - should not revert - Sets DISABLED await expect(cTokenSelfReferentialCollateral.refresh()) @@ -2234,70 +2285,12 @@ describe('Collateral contracts', () => { const [newLow, newHigh] = await cTokenSelfReferentialCollateral.price() expect(newLow).to.equal(currLow) expect(newHigh).to.equal(currHigh) - }) - - it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { - const currRate = await cSelfRefToken.exchangeRateStored() - const [currLow, currHigh] = await cTokenSelfReferentialCollateral.price() - - expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) - await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) - // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cSelfRefToken.underlying()) - ) - await cTokenErc20Mock.setRevertExchangeRate(true) - - // Refresh - should not revert - Sets DISABLED - await expect(cTokenSelfReferentialCollateral.refresh()) - .to.emit(cTokenSelfReferentialCollateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - - expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) - const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await cTokenSelfReferentialCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) - - // Exchange rate stored is still accessible - expect(await cSelfRefToken.exchangeRateStored()).to.equal(currRate) - - // Price remains the same - await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) - const [newLow, newHigh] = await cTokenSelfReferentialCollateral.price() - expect(newLow).to.equal(currLow) - expect(newHigh).to.equal(currHigh) - }) - - it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { - const currRate = await cSelfRefToken.exchangeRateStored() - const [currLow, currHigh] = await cTokenSelfReferentialCollateral.price() - - expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) - await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) - - // Make cToken revert on exchangeRateCurrent() - const cTokenErc20Mock = ( - await ethers.getContractAt('CTokenMock', await cSelfRefToken.underlying()) + // Refresh is a noop if DISABLED + await expect(cTokenSelfReferentialCollateral.refresh()).to.not.emit( + cTokenSelfReferentialCollateral, + 'CollateralStatusChanged' ) - await cTokenErc20Mock.setRevertExchangeRate(true) - - // Refresh - should not revert - Sets DISABLED - await expect(cTokenSelfReferentialCollateral.refresh()) - .to.emit(cTokenSelfReferentialCollateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - - expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) - const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await cTokenSelfReferentialCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) - - // Exchange rate stored is still accessible - expect(await cSelfRefToken.exchangeRateStored()).to.equal(currRate) - - // Price remains the same - await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) - const [newLow, newHigh] = await cTokenSelfReferentialCollateral.price() - expect(newLow).to.equal(currLow) - expect(newHigh).to.equal(currHigh) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { diff --git a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts index b2628df278..6e1df0f318 100644 --- a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -1,228 +1,96 @@ -import collateralTests from '../collateralTests' -import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' import { ethers } from 'hardhat' -import { BigNumberish, BigNumber } from 'ethers' -import { - TestICollateral, - AaveV3FiatCollateral__factory, - IERC20Metadata, - MockStaticATokenV3LM, -} from '@typechain/index' import { bn, fp } from '#/common/numbers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { noop } from 'lodash' import { PRICE_TIMEOUT } from '#/test/fixtures' -import { resetFork } from './helpers' -import { whileImpersonating } from '#/test/utils/impersonation' -import { pushOracleForward } from '../../../utils/oracles' +import { makeTests } from './common' +import { networkConfig } from '#/common/configuration' import { - forkNetwork, - AUSDC_V3, - AAVE_V3_USDC_POOL, - AAVE_V3_INCENTIVES_CONTROLLER, - USDC_USD_PRICE_FEED, - USDC_HOLDER, + PYUSD_MAX_TRADE_VOLUME, + PYUSD_ORACLE_TIMEOUT, + PYUSD_ORACLE_ERROR, + USDC_MAINNET_MAX_TRADE_VOLUME, + USDC_MAINNET_ORACLE_TIMEOUT, + USDC_MAINNET_ORACLE_ERROR, + USDC_BASE_MAX_TRADE_VOLUME, + USDC_BASE_ORACLE_TIMEOUT, + USDC_BASE_ORACLE_ERROR, } from './constants' -interface AaveV3FiatCollateralFixtureContext extends CollateralFixtureContext { - staticWrapper: MockStaticATokenV3LM - baseToken: IERC20Metadata -} - /* - Define deployment functions -*/ - -type CollateralParams = Parameters[0] & { - revenueHiding?: BigNumberish -} - -// This defines options for the Aave V3 USDC Market -export const defaultCollateralOpts: CollateralParams = { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: USDC_USD_PRICE_FEED, - oracleError: fp('0.0025'), - erc20: '', // to be set - maxTradeVolume: fp('1e6'), - oracleTimeout: bn('86400'), - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125'), - delayUntilDefault: bn('86400'), -} - -export const deployCollateral = async (opts: Partial = {}) => { - const combinedOpts = { ...defaultCollateralOpts, ...opts } - const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') - - if (!combinedOpts.erc20 || combinedOpts.erc20 === '') { - const V3LMFactory = await ethers.getContractFactory('MockStaticATokenV3LM') - const staticWrapper = await V3LMFactory.deploy(AAVE_V3_USDC_POOL, AAVE_V3_INCENTIVES_CONTROLLER) - await staticWrapper.deployed() - await staticWrapper.initialize(AUSDC_V3, 'Static Aave Ethereum USDC', 'saEthUSDC') - - combinedOpts.erc20 = staticWrapper.address + ** Static AToken Factory for Aave V3 + ** Mainnet: 0x411D79b8cC43384FDE66CaBf9b6a17180c842511 + ** --> https://github.com/bgd-labs/aave-address-book/blob/main/src/AaveV3Ethereum.sol#L86 + ** Base: 0x940F9a5d5F9ED264990D0eaee1F3DD60B4Cb9A22 + ** --> https://github.com/bgd-labs/aave-address-book/blob/main/src/AaveV3Base.sol#L78 + */ + +// Mainnet - USDC +makeTests( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds['USDC']!, + oracleError: USDC_MAINNET_ORACLE_ERROR, + erc20: '', // to be set + maxTradeVolume: USDC_MAINNET_MAX_TRADE_VOLUME, + oracleTimeout: USDC_MAINNET_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(USDC_MAINNET_ORACLE_TIMEOUT), + delayUntilDefault: bn('86400'), + }, + { + testName: 'USDC - Mainnet', + aaveIncentivesController: networkConfig[1].AAVE_V3_INCENTIVES_CONTROLLER!, + aavePool: networkConfig[1].AAVE_V3_POOL!, + aToken: networkConfig[1].tokens['aEthUSDC']!, + whaleTokenHolder: '0x0A59649758aa4d66E25f08Dd01271e891fe52199', + forkBlock: 18000000, + targetNetwork: 'mainnet', } - - const collateral = await CollateralFactory.deploy( - combinedOpts, - opts.revenueHiding ?? fp('0'), // change this to test with revenueHiding - { - gasLimit: 30000000, - } - ) - await collateral.deployed() - - // Push forward chainlink feed - await pushOracleForward(combinedOpts.chainlinkFeed!) - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - // our tools really suck don't they - return collateral as unknown as TestICollateral -} - -type Fixture = () => Promise - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts = {} -): Fixture => { - const collateralOpts = { ...defaultCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const collateral = await deployCollateral({ - ...collateralOpts, - chainlinkFeed: chainlinkFeed.address, - }) - - const staticWrapper = await ethers.getContractAt( - 'MockStaticATokenV3LM', - await collateral.erc20() - ) - - return { - collateral, - staticWrapper, - chainlinkFeed, - tok: await ethers.getContractAt('IERC20Metadata', await collateral.erc20()), - baseToken: await ethers.getContractAt('IERC20Metadata', await staticWrapper.asset()), - } +) + +// Base - USDC +makeTests( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[8453].chainlinkFeeds['USDC']!, + oracleError: USDC_BASE_ORACLE_ERROR, + erc20: '', // to be set + maxTradeVolume: USDC_BASE_MAX_TRADE_VOLUME, + oracleTimeout: USDC_BASE_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(USDC_BASE_ORACLE_TIMEOUT), + delayUntilDefault: bn('86400'), + }, + { + testName: 'USDC - Base', + aaveIncentivesController: networkConfig[8453].AAVE_V3_INCENTIVES_CONTROLLER!, + aavePool: networkConfig[8453].AAVE_V3_POOL!, + aToken: networkConfig[8453].tokens['aBasUSDC']!, + whaleTokenHolder: '0x20fe51a9229eef2cf8ad9e89d91cab9312cf3b7a', + forkBlock: 8200000, + targetNetwork: 'base', } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCollateralFunc = async ( - ctx: AaveV3FiatCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - const requiredCollat = await ctx.staticWrapper.previewMint(amount) - - // Impersonate holder - await whileImpersonating(USDC_HOLDER, async (signer) => { - await ctx.baseToken - .connect(signer) - .approve(ctx.staticWrapper.address, ethers.constants.MaxUint256) - await ctx.staticWrapper - .connect(signer) - ['deposit(uint256,address,uint16,bool)'](requiredCollat, recipient, 0, true) - }) -} - -const modifyRefPerTok = async (ctx: AaveV3FiatCollateralFixtureContext, changeFactor = 100) => { - const staticWrapper = ctx.staticWrapper - const currentRate = await staticWrapper.rate() - - await staticWrapper.mockSetCustomRate(currentRate.mul(changeFactor).div(100)) -} - -const reduceRefPerTok = async ( - ctx: AaveV3FiatCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await modifyRefPerTok(ctx, 100 - Number(pctDecrease.toString())) -} - -const increaseRefPerTok = async ( - ctx: AaveV3FiatCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await modifyRefPerTok(ctx, 100 + Number(pctIncrease.toString())) -} - -const getExpectedPrice = async (ctx: AaveV3FiatCollateralFixtureContext): Promise => { - const initRefPerTok = await ctx.collateral.refPerTok() - const decimals = await ctx.chainlinkFeed.decimals() - - const initData = await ctx.chainlinkFeed.latestRoundData() - return initData.answer - .mul(bn(10).pow(18 - decimals)) - .mul(initRefPerTok) - .div(fp('1')) -} - -const reduceTargetPerRef = async ( - ctx: AaveV3FiatCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - const lastRound = await ctx.chainlinkFeed.latestRoundData() - const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) - - await ctx.chainlinkFeed.updateAnswer(nextAnswer) -} - -const increaseTargetPerRef = async ( - ctx: AaveV3FiatCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - const lastRound = await ctx.chainlinkFeed.latestRoundData() - const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) - - await ctx.chainlinkFeed.updateAnswer(nextAnswer) -} - -/* - Run the test suite -*/ - -export const stableOpts = { - deployCollateral, - collateralSpecificConstructorTests: noop, - collateralSpecificStatusTests: noop, - beforeEachRewardsTest: noop, - makeCollateralFixtureContext, - mintCollateralTo, - reduceRefPerTok, - increaseRefPerTok, - resetFork, - collateralName: 'Aave V3 Fiat Collateral (USDC)', - reduceTargetPerRef, - increaseTargetPerRef, - itClaimsRewards: it.skip, // untested: very complicated to get Aave to handout rewards, and none are live currently. - // The StaticATokenV3LM contract is formally verified and the function we added for claimRewards() is pretty obviously correct. - itChecksTargetPerRefDefault: it, - itChecksTargetPerRefDefaultUp: it, - itChecksRefPerTokDefault: it, - itHasRevenueHiding: it, - itChecksNonZeroDefaultThreshold: it, - itIsPricedByPeg: true, - chainlinkDefaultAnswer: 1e8, - itChecksPriceChanges: it, - getExpectedPrice, - toleranceDivisor: bn('1e9'), // 1e15 adjusted for ((x + 1)/x) timestamp precision - targetNetwork: forkNetwork, -} - -collateralTests(stableOpts) +) + +// Mainnet - pyUSD +makeTests( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds['pyUSD']!, + oracleError: PYUSD_ORACLE_ERROR, + erc20: '', // to be set + maxTradeVolume: PYUSD_MAX_TRADE_VOLUME, + oracleTimeout: PYUSD_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(PYUSD_ORACLE_ERROR), + delayUntilDefault: bn('86400'), + }, + { + testName: 'pyUSD - Mainnet', + aaveIncentivesController: networkConfig[1].AAVE_V3_INCENTIVES_CONTROLLER!, + aavePool: networkConfig[1].AAVE_V3_POOL!, + aToken: networkConfig[1].tokens['aEthPyUSD']!, + whaleTokenHolder: '0xCFFAd3200574698b78f32232aa9D63eABD290703', + forkBlock: 19270000, + targetNetwork: 'mainnet', + } +) diff --git a/test/plugins/individual-collateral/aave-v3/common.ts b/test/plugins/individual-collateral/aave-v3/common.ts new file mode 100644 index 0000000000..c728a5479b --- /dev/null +++ b/test/plugins/individual-collateral/aave-v3/common.ts @@ -0,0 +1,221 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { BigNumberish, BigNumber } from 'ethers' +import { + TestICollateral, + AaveV3FiatCollateral__factory, + IERC20Metadata, + MockStaticATokenV3LM, +} from '@typechain/index' +import { bn, fp } from '#/common/numbers' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { noop } from 'lodash' +import { whileImpersonating } from '#/test/utils/impersonation' +import { pushOracleForward } from '../../../utils/oracles' +import { getResetFork } from '../helpers' + +interface AaveV3FiatCollateralFixtureContext extends CollateralFixtureContext { + staticWrapper: MockStaticATokenV3LM + baseToken: IERC20Metadata +} + +/* + Define deployment functions +*/ + +type CollateralParams = Parameters[0] & { + revenueHiding?: BigNumberish +} + +type AltParams = { + testName: string + aavePool: string + aaveIncentivesController: string + aToken: string + whaleTokenHolder: string + forkBlock: number + targetNetwork: 'mainnet' | 'base' +} + +export const makeTests = (defaultCollateralOpts: CollateralParams, altParams: AltParams) => { + const deployCollateral = async (opts: Partial = {}) => { + const combinedOpts = { ...defaultCollateralOpts, ...opts } + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') + + if (!combinedOpts.erc20 || combinedOpts.erc20 === '') { + const V3LMFactory = await ethers.getContractFactory('MockStaticATokenV3LM') + const staticWrapper = await V3LMFactory.deploy( + altParams.aavePool, + altParams.aaveIncentivesController + ) + await staticWrapper.deployed() + await staticWrapper.initialize(altParams.aToken, 'Static Token', 'saToken') // doesn't really matter. + + combinedOpts.erc20 = staticWrapper.address + } + + const collateral = await CollateralFactory.deploy( + combinedOpts, + opts.revenueHiding ?? fp('0'), // change this to test with revenueHiding + { + gasLimit: 30000000, + } + ) + await collateral.deployed() + + // Push forward chainlink feed + await pushOracleForward(combinedOpts.chainlinkFeed!) + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + // our tools really suck don't they + return collateral as unknown as TestICollateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts = {} + ): Fixture => { + const collateralOpts = { ...defaultCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const collateral = await deployCollateral({ + ...collateralOpts, + chainlinkFeed: chainlinkFeed.address, + }) + + const staticWrapper = await ethers.getContractAt( + 'MockStaticATokenV3LM', + await collateral.erc20() + ) + + return { + collateral, + staticWrapper, + chainlinkFeed, + tok: await ethers.getContractAt('IERC20Metadata', await collateral.erc20()), + baseToken: await ethers.getContractAt('IERC20Metadata', await staticWrapper.asset()), + } + } + + return makeCollateralFixtureContext + } + + /* + Define helper functions + */ + + const mintCollateralTo: MintCollateralFunc = async ( + ctx: AaveV3FiatCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string + ) => { + const requiredCollat = await ctx.staticWrapper.previewMint(amount) + + // Impersonate holder + await whileImpersonating(altParams.whaleTokenHolder, async (signer) => { + await ctx.baseToken + .connect(signer) + .approve(ctx.staticWrapper.address, ethers.constants.MaxUint256) + await ctx.staticWrapper + .connect(signer) + ['deposit(uint256,address,uint16,bool)'](requiredCollat, recipient, 0, true) + }) + } + + const modifyRefPerTok = async (ctx: AaveV3FiatCollateralFixtureContext, changeFactor = 100) => { + const staticWrapper = ctx.staticWrapper + const currentRate = await staticWrapper.rate() + + await staticWrapper.mockSetCustomRate(currentRate.mul(changeFactor).div(100)) + } + + const reduceRefPerTok = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + await modifyRefPerTok(ctx, 100 - Number(pctDecrease.toString())) + } + + const increaseRefPerTok = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + await modifyRefPerTok(ctx, 100 + Number(pctIncrease.toString())) + } + + const getExpectedPrice = async (ctx: AaveV3FiatCollateralFixtureContext): Promise => { + const initRefPerTok = await ctx.collateral.refPerTok() + const decimals = await ctx.chainlinkFeed.decimals() + + const initData = await ctx.chainlinkFeed.latestRoundData() + return initData.answer + .mul(bn(10).pow(18 - decimals)) + .mul(initRefPerTok) + .div(fp('1')) + } + + const reduceTargetPerRef = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseTargetPerRef = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + /* + Run the test suite + */ + + const stableOpts = { + deployCollateral, + collateralSpecificConstructorTests: noop, + collateralSpecificStatusTests: noop, + beforeEachRewardsTest: noop, + makeCollateralFixtureContext, + mintCollateralTo, + reduceRefPerTok, + increaseRefPerTok, + resetFork: getResetFork(altParams.forkBlock), + collateralName: `Aave V3 Fiat Collateral (${altParams.testName})`, + reduceTargetPerRef, + increaseTargetPerRef, + itClaimsRewards: it.skip, // untested: very complicated to get Aave to handout rewards, and none are live currently. + // The StaticATokenV3LM contract is formally verified and the function we added for claimRewards() is pretty obviously correct. + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, + itIsPricedByPeg: true, + chainlinkDefaultAnswer: 1e8, + itChecksPriceChanges: it, + getExpectedPrice, + toleranceDivisor: bn('1e9'), // 1e15 adjusted for ((x + 1)/x) timestamp precision + targetNetwork: altParams.targetNetwork, + } + + return collateralTests(stableOpts) +} diff --git a/test/plugins/individual-collateral/aave-v3/constants.ts b/test/plugins/individual-collateral/aave-v3/constants.ts index 0de2aef4f6..00fcaea36f 100644 --- a/test/plugins/individual-collateral/aave-v3/constants.ts +++ b/test/plugins/individual-collateral/aave-v3/constants.ts @@ -1,32 +1,13 @@ -import { networkConfig } from '../../../../common/configuration' -import { useEnv } from '#/utils/env' +import { bn, fp } from '#/common/numbers' -export const forkNetwork = useEnv('FORK_NETWORK') ?? 'mainnet' -let chainId +export const PYUSD_MAX_TRADE_VOLUME = fp('0.5e6') +export const PYUSD_ORACLE_TIMEOUT = bn('86400') +export const PYUSD_ORACLE_ERROR = fp('0.003') -switch (forkNetwork) { - case 'mainnet': - chainId = '1' - break - case 'base': - chainId = '8453' - break - default: - chainId = '1' - break -} +export const USDC_MAINNET_MAX_TRADE_VOLUME = fp('1e6') +export const USDC_MAINNET_ORACLE_TIMEOUT = bn('86400') +export const USDC_MAINNET_ORACLE_ERROR = fp('0.0025') -const aUSDC_NAME = chainId == '8453' ? 'aBasUSDbC' : 'aEthUSDC' - -export const AUSDC_V3 = networkConfig[chainId].tokens[aUSDC_NAME]! -export const USDC_USD_PRICE_FEED = networkConfig[chainId].chainlinkFeeds['USDC']! // currently same key for USDC and USDbC - -export const USDC_HOLDER = - chainId == '8453' - ? '0x4c80E24119CFB836cdF0a6b53dc23F04F7e652CA' - : '0x0A59649758aa4d66E25f08Dd01271e891fe52199' - -export const AAVE_V3_USDC_POOL = networkConfig[chainId].AAVE_V3_POOL! -export const AAVE_V3_INCENTIVES_CONTROLLER = networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! - -export const FORK_BLOCK = chainId == '8453' ? 4446300 : 18000000 +export const USDC_BASE_MAX_TRADE_VOLUME = fp('0.5e6') +export const USDC_BASE_ORACLE_TIMEOUT = bn('86400') +export const USDC_BASE_ORACLE_ERROR = fp('0.003') diff --git a/test/plugins/individual-collateral/aave-v3/helpers.ts b/test/plugins/individual-collateral/aave-v3/helpers.ts deleted file mode 100644 index c6c430bc8a..0000000000 --- a/test/plugins/individual-collateral/aave-v3/helpers.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { getResetFork } from '../helpers' -import { FORK_BLOCK } from './constants' - -export const resetFork = getResetFork(FORK_BLOCK) diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 2575586520..7b0bed849e 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -53,7 +53,6 @@ import { Asset, ATokenFiatCollateral, ERC20Mock, - FacadeRead, FacadeTest, FacadeWrite, IAssetRegistry, @@ -63,6 +62,7 @@ import { RTokenAsset, TestIBackingManager, TestIDeployer, + TestIFacade, TestIMain, TestIRToken, IAToken, @@ -124,7 +124,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let chainlinkFeed: AggregatorInterface let deployer: TestIDeployer - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let facadeWrite: FacadeWrite let govParams: IGovParams @@ -158,6 +158,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi pctRate: fp('0.05'), // 5% }, warmupPeriod: bn('60'), + reweightable: false, } const defaultThreshold = fp('0.01') // 1% @@ -373,7 +374,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi .withArgs(stkAave.address, anyValue) await expect(aDaiCollateral.claimRewards()) - .to.emit(staticAToken, 'RewardsClaimed') + .to.emit(aDaiCollateral, 'RewardsClaimed') .withArgs(stkAave.address, anyValue) expect(await aDaiCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) @@ -630,7 +631,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await expectEvents(backingManager.claimRewards(), [ { - contract: staticAToken, + contract: backingManager, name: 'RewardsClaimed', args: [stkAave.address, anyValue], emitted: true, @@ -660,7 +661,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceBlocks(1000) // Claim rewards - await expect(backingManager.claimRewards()).to.emit(staticAToken, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') // Check rewards both in stkAAVE and stkAAVE const rewardsstkAAVE1: BigNumber = await stkAave.balanceOf(backingManager.address) @@ -671,7 +672,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime(3600) // Get additional rewards - await expect(backingManager.claimRewards()).to.emit(staticAToken, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') const rewardsstkAAVE2: BigNumber = await stkAave.balanceOf(backingManager.address) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 9768c5abf5..e870e2038d 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -186,8 +186,6 @@ export default function fn( }) describe('rewards', () => { - // TODO: these tests will be deprecated along with the claimRewards() - // function on collateral plugins (4/18/23) beforeEach(async () => { await beforeEachRewardsTest(ctx) }) @@ -211,21 +209,6 @@ export default function fn( ) expect(balAfter).gt(balBefore) }) - - itClaimsRewards('claims rewards (via erc20.claimRewards())', async () => { - const rewardable = await ethers.getContractAt('IRewardable', ctx.tok.address) - - const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const balBefore = await (ctx.rewardToken as IERC20Metadata).balanceOf(alice.address) - await expect(rewardable.connect(alice).claimRewards()).to.emit(ctx.tok, 'RewardsClaimed') - const balAfter = await (ctx.rewardToken as IERC20Metadata).balanceOf(alice.address) - expect(balAfter).gt(balBefore) - }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 26e2b7c212..d996238643 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -55,10 +55,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenMock, - CTokenWrapper, - CTokenWrapperMock, ERC20Mock, - FacadeRead, FacadeTest, FacadeWrite, IAssetRegistry, @@ -68,6 +65,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIDeployer, + TestIFacade, TestIMain, TestIRToken, } from '../../../../typechain' @@ -108,7 +106,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Tokens/Assets let dai: ERC20Mock let cDai: CTokenMock - let cDaiVault: CTokenWrapper let cDaiCollateral: CTokenFiatCollateral let compToken: ERC20Mock let compAsset: Asset @@ -125,7 +122,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let basketHandler: TestIBasketHandler let deployer: TestIDeployer - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let facadeWrite: FacadeWrite let govParams: IGovParams @@ -159,6 +156,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi amtRate: fp('1e6'), // 1M RToken pctRate: fp('0.05'), // 5% }, + reweightable: false, } const defaultThreshold = fp('0.01') // 1% @@ -222,16 +220,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) ) - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') - cDaiVault = ( - await cDaiVaultFactory.deploy( - cDai.address, - 'cDAI RToken Vault', - 'rv_cDAI', - comptroller.address - ) - ) - // Deploy cDai collateral plugin CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') cDaiCollateral = await CTokenCollateralFactory.deploy( @@ -239,7 +227,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -256,11 +244,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8)) }) - const initialBalcDai = await cDai.balanceOf(addr1.address) - - await cDai.connect(addr1).approve(cDaiVault.address, initialBalcDai) - await cDaiVault.connect(addr1).deposit(initialBalcDai, addr1.address) - // Set parameters const rTokenConfig: IRTokenConfig = { name: 'RTKN RToken', @@ -341,9 +324,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // cDAI (CTokenFiatCollateral) expect(await cDaiCollateral.isCollateral()).to.equal(true) expect(await cDaiCollateral.referenceERC20Decimals()).to.equal(await dai.decimals()) - expect(await cDaiCollateral.erc20()).to.equal(cDaiVault.address) + expect(await cDaiCollateral.erc20()).to.equal(cDai.address) expect(await cDai.decimals()).to.equal(8) - expect(await cDaiVault.decimals()).to.equal(8) expect(await cDaiCollateral.targetName()).to.equal(ethers.utils.formatBytes32String('USD')) expect(await cDaiCollateral.refPerTok()).to.be.closeTo(fp('0.022'), fp('0.001')) expect(await cDaiCollateral.targetPerRef()).to.equal(fp('1')) @@ -359,18 +341,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) // close to $0.022 cents // Check claim data - await expect(cDaiVault.claimRewards()) - .to.emit(cDaiVault, 'RewardsClaimed') - .withArgs(compToken.address, anyValue) - await expect(cDaiCollateral.claimRewards()) - .to.emit(cDaiVault, 'RewardsClaimed') + .to.emit(cDaiCollateral, 'RewardsClaimed') .withArgs(compToken.address, anyValue) expect(await cDaiCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) // Exchange rate - await cDaiVault.exchangeRateCurrent() - expect(await cDaiVault.exchangeRateStored()).to.equal(await cDaiVault.exchangeRateStored()) + await cDai.exchangeRateCurrent() + expect(await cDai.exchangeRateStored()).to.equal(await cDai.exchangeRateStored()) // Should setup contracts expect(main.address).to.not.equal(ZERO_ADDRESS) @@ -383,7 +361,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(ERC20s[0]).to.equal(rToken.address) expect(ERC20s[1]).to.equal(rsr.address) expect(ERC20s[2]).to.equal(compToken.address) - expect(ERC20s[3]).to.equal(cDaiVault.address) + expect(ERC20s[3]).to.equal(cDai.address) expect(ERC20s.length).to.eql(4) // Assets @@ -401,7 +379,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Basket expect(await basketHandler.fullyCollateralized()).to.equal(true) const backing = await facade.basketTokens(rToken.address) - expect(backing[0]).to.equal(cDaiVault.address) + expect(backing[0]).to.equal(cDai.address) expect(backing.length).to.equal(1) // Check other values @@ -412,7 +390,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Check RToken price const issueAmount: BigNumber = bn('10000e18') - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -427,7 +405,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Validate constructor arguments // Note: Adapt it to your plugin constructor validations it('Should validate constructor arguments correctly', async () => { - // Comptroller await expect( CTokenCollateralFactory.deploy( { @@ -451,15 +428,12 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ).deploy('Bad ERC20', 'BERC20') await token0decimals.setDecimals(0) - const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenWrapperMock' - ) - const vault: CTokenWrapperMock = ( - await CTokenWrapperMockFactory.deploy( + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const vault: CTokenMock = ( + await CTokenMockFactory.deploy( '0 Decimal Token', '0 Decimal Token', token0decimals.address, - compToken.address, comptroller.address ) ) @@ -488,7 +462,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -509,7 +483,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK // instant issuance // Provide approvals for issuances - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) @@ -520,7 +494,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) // Store Balances after issuance - const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) + const balanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) // Check rates and prices const [cDaiPriceLow1, cDaiPriceHigh1] = await cDaiCollateral.price() // ~ 0.022015 cents @@ -614,16 +588,13 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await rToken.totalSupply()).to.equal(0) // Check balances - Fewer cTokens should have been sent to the user - const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) + const newBalanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) // Check received tokens represent ~10K in value at current prices expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('303570e8'), bn('8e7')) // ~0.03294 * 303571 ~= 10K (100% of basket) // Check remainders in Backing Manager - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( - bn('150663e8'), - bn('5e7') - ) // ~= 4962.8 usd in value + expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(bn('150663e8'), bn('5e7')) // ~= 4962.8 usd in value // Check total asset value (remainder) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( @@ -645,7 +616,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await expectEvents(backingManager.claimRewards(), [ { - contract: cDaiVault, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, anyValue], emitted: true, @@ -656,7 +627,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await compToken.balanceOf(backingManager.address)).to.equal(0) // Provide approvals for issuances - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) @@ -673,7 +644,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime(8000) // Claim rewards - await expect(backingManager.claimRewards()).to.emit(cDaiVault, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') // Check rewards both in COMP and stkAAVE const rewardsCOMP1: BigNumber = await compToken.balanceOf(backingManager.address) @@ -684,7 +655,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime(3600) // Get additional rewards - await expect(backingManager.claimRewards()).to.emit(cDaiVault, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') const rewardsCOMP2: BigNumber = await compToken.balanceOf(backingManager.address) @@ -716,7 +687,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -741,7 +712,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), @@ -835,21 +806,11 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') const symbol = await cDai.symbol() const cDaiMock: CTokenMock = ( - await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address, comptroller.address) ) // Set initial exchange rate to the new cDai Mock await cDaiMock.setExchangeRate(fp('0.02')) - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') - const cDaiMockVault = ( - await cDaiVaultFactory.deploy( - cDaiMock.address, - 'cDAI Mock RToken Vault', - 'rv_mock_cDAI', - comptroller.address - ) - ) - // Redeploy plugin using the new cDai mock const newCDaiCollateral: CTokenFiatCollateral = await ( await ethers.getContractFactory('CTokenFiatCollateral') @@ -858,7 +819,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: await cDaiCollateral.chainlinkFeed(), oracleError: ORACLE_ERROR, - erc20: cDaiMockVault.address, + erc20: cDaiMock.address, maxTradeVolume: await cDaiCollateral.maxTradeVolume(), oracleTimeout: await cDaiCollateral.oracleTimeout(), targetName: await cDaiCollateral.targetName(), @@ -886,70 +847,101 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) }) - it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + context('with reverting CToken mock', () => { // Note: In this case requires to use a CToken mock to be able to change the rate - const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') - const symbol = await cDai.symbol() - const cDaiMock: CTokenMock = ( - await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) - ) + let cDaiMock: CTokenMock + let newCDaiCollateral: CTokenFiatCollateral - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') - const cDaiMockVault = ( - await cDaiVaultFactory.deploy( - cDaiMock.address, - 'cDAI Mock RToken Vault', - 'rv_mock_cDAI', - comptroller.address + beforeEach(async () => { + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const symbol = await cDai.symbol() + cDaiMock = ( + await CTokenMockFactory.deploy( + symbol + ' Token', + symbol, + dai.address, + comptroller.address + ) ) - ) - // Redeploy plugin using the new cDai mock - const newCDaiCollateral: CTokenFiatCollateral = await ( - await ethers.getContractFactory('CTokenFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await cDaiCollateral.chainlinkFeed(), - oracleError: ORACLE_ERROR, - erc20: cDaiMockVault.address, - maxTradeVolume: await cDaiCollateral.maxTradeVolume(), - oracleTimeout: await cDaiCollateral.oracleTimeout(), - targetName: await cDaiCollateral.targetName(), - defaultThreshold, - delayUntilDefault: await cDaiCollateral.delayUntilDefault(), - }, - REVENUE_HIDING - ) - await newCDaiCollateral.refresh() + // Redeploy plugin using the new cDai mock + newCDaiCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await cDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cDaiMock.address, + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newCDaiCollateral.refresh() + }) - // Check initial state - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) - await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) - const [currLow, currHigh] = await newCDaiCollateral.price() - const currRate = await cDaiMockVault.exchangeRateStored() + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [currLow, currHigh] = await newCDaiCollateral.price() + const currRate = await cDaiMock.exchangeRateStored() - // Make exchangeRateCurrent() revert - await cDaiMock.setRevertExchangeRate(true) + // Make exchangeRateCurrent() revert + await cDaiMock.setRevertExchangeRateCurrent(true) - // Force updates - Should set to DISABLED - await expect(newCDaiCollateral.refresh()) - .to.emit(newCDaiCollateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + // Force updates - Should set to DISABLED + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) - const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cDaiMock.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await newCDaiCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + + it('Enters DISABLED state when underlyingRefPerTok() reverts', async () => { + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [currLow, currHigh] = await newCDaiCollateral.price() + const currRate = await cDaiMock.exchangeRateStored() + + // Make exchangeRateStored() revert + await cDaiMock.setRevertExchangeRateStored(true) + await expect(cDaiMock.exchangeRateStored()).to.be.reverted + await expect(newCDaiCollateral.underlyingRefPerTok()).to.be.reverted + + // Force updates - Should set to DISABLED + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - // Exchange rate stored is still accessible - expect(await cDaiMockVault.exchangeRateStored()).to.equal(currRate) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) - // Price remains the same - await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) - const [newLow, newHigh] = await newCDaiCollateral.price() - expect(newLow).to.equal(currLow) - expect(newHigh).to.equal(currHigh) + // Price remains the same + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await newCDaiCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) }) it('Reverts if oracle reverts or runs out of gas, maintains status', async () => { @@ -1108,20 +1100,15 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') const symbol = await cDai.symbol() const cDaiMock: CTokenMock = ( - await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) - ) - // Set initial exchange rate to the new cDai Mock - await cDaiMock.setExchangeRate(fp('0.02')) - - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') - cDaiVault = ( - await cDaiVaultFactory.deploy( - cDaiMock.address, - 'cDAI RToken Vault', - 'rv_cDAI', + await CTokenMockFactory.deploy( + symbol + ' Token', + symbol, + dai.address, comptroller.address ) ) + // Set initial exchange rate to the new cDai Mock + await cDaiMock.setExchangeRate(fp('0.02')) // Redeploy plugin using the new cDai mock const newCDaiCollateral: CTokenFiatCollateral = await ( @@ -1131,7 +1118,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: await cDaiCollateral.chainlinkFeed(), oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, + erc20: cDaiMock.address, maxTradeVolume: await cDaiCollateral.maxTradeVolume(), oracleTimeout: await cDaiCollateral.oracleTimeout(), targetName: await cDaiCollateral.targetName(), @@ -1148,12 +1135,5 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st }) }) - - context('ERC20', () => { - it('transfer', async () => { - await snapshotGasCost(cDaiVault.connect(addr1).transfer(cDaiCollateral.address, bn('1'))) - await snapshotGasCost(cDaiVault.connect(addr1).transfer(cDaiCollateral.address, bn('1'))) - }) - }) }) }) diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 2da2bc8b2a..614c8d6e8d 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -76,7 +76,6 @@ interface CometCollateralOpts extends CollateralOpts { */ const chainlinkDefaultAnswer = bn('1e8') -const reservesThresholdIffyDefault = bn('10000e6') // 10k export const defaultCometCollateralOpts: CometCollateralOpts = { erc20: CUSDC_V3, @@ -90,7 +89,6 @@ export const defaultCometCollateralOpts: CometCollateralOpts = { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), - reservesThresholdIffy: reservesThresholdIffyDefault, } export const deployCollateral = async ( @@ -115,7 +113,6 @@ export const deployCollateral = async ( delayUntilDefault: opts.delayUntilDefault, }, opts.revenueHiding, - opts.reservesThresholdIffy, { gasLimit: 2000000000 } ) await collateral.deployed() @@ -183,9 +180,7 @@ const deployCollateralCometMockContext = async ( collateralOpts.chainlinkFeed = chainlinkFeed.address const CometFactory = await ethers.getContractFactory('CometMock') - const cusdcV3 = ( - await CometFactory.deploy(collateralOpts.reservesThresholdIffy as BigNumberish, CUSDC_V3) - ) + const cusdcV3 = await CometFactory.deploy(CUSDC_V3) const CusdcV3WrapperFactory = ( await ethers.getContractFactory('CusdcV3Wrapper') @@ -297,55 +292,6 @@ const collateralSpecificConstructorTests = () => { } const collateralSpecificStatusTests = () => { - it('enters IFFY state when compound reserves are below target reserves iffy threshold', async () => { - const { collateral, cusdcV3 } = await deployCollateralCometMockContext({}) - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // cUSDC/Comet's reserves gone down below targetReserves - await cusdcV3.setReserves(reservesThresholdIffyDefault.sub(1)) - - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - - // Nothing changes if attempt to refresh after default for CTokenV3 - const prevWhenDefault: bigint = (await collateral.whenDefault()).toBigInt() - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(prevWhenDefault) - }) - - it('enters DISABLED state if reserves go negative', async () => { - const { collateral, cusdcV3 } = await deployCollateralCometMockContext({}) - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // cUSDC/Comet's reserves gone down to -1 - await cusdcV3.setReserves(-1) - - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - it('does revenue hiding correctly', async () => { const { collateral, wcusdcV3Mock } = await deployCollateralCometMockContext({ revenueHiding: fp('0.01'), diff --git a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index 7e4d782570..c81881382d 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -248,9 +248,9 @@ describeFork('Wrapped CUSDCv3', () => { await wcusdcV3.connect(charles).withdrawFrom(bob.address, don.address, withdrawAmount) expect(await cusdcV3.balanceOf(don.address)).to.closeTo(withdrawAmount, 100) - expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(bn('0'), 11) + expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(bn('0'), 30) - expect(await wcusdcV3.balanceOf(bob.address)).to.closeTo(bn(0), 50) + expect(await wcusdcV3.balanceOf(bob.address)).to.closeTo(bn(0), 100) }) it('withdraws all underlying balance via multiple withdrawals', async () => { @@ -293,7 +293,7 @@ describeFork('Wrapped CUSDCv3', () => { charlesWithdrawn = charlesWithdrawn.add(firstWithdrawAmt) await wcusdcV3.connect(charles).withdraw(firstWithdrawAmt) const newBalanceCharles = await cusdcV3.balanceOf(charles.address) - expect(newBalanceCharles).to.closeTo(firstWithdrawAmt, 10) + expect(newBalanceCharles).to.closeTo(firstWithdrawAmt, 25) // don deposits await mintWcUSDC(usdc, cusdcV3, wcusdcV3, don, initwusdcAmt, don.address) @@ -321,9 +321,9 @@ describeFork('Wrapped CUSDCv3', () => { const bal = await wcusdcV3.balanceOf(bob.address) expect(bal).to.closeTo(bn('0'), 10) - expect(await cusdcV3.balanceOf(bob.address)).to.closeTo(bobWithdrawn, 100) - expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(charlesWithdrawn, 100) - expect(await cusdcV3.balanceOf(don.address)).to.closeTo(donWithdrawn, 100) + expect(await cusdcV3.balanceOf(bob.address)).to.closeTo(bobWithdrawn, 200) + expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(charlesWithdrawn, 200) + expect(await cusdcV3.balanceOf(don.address)).to.closeTo(donWithdrawn, 200) }) it('updates the totalSupply', async () => { @@ -332,7 +332,7 @@ describeFork('Wrapped CUSDCv3', () => { const expectedDiff = await wcusdcV3.convertDynamicToStatic(withdrawAmt) await wcusdcV3.connect(bob).withdraw(withdrawAmt) // conservative rounding - expect(await wcusdcV3.totalSupply()).to.be.closeTo(totalSupplyBefore.sub(expectedDiff), 10) + expect(await wcusdcV3.totalSupply()).to.be.closeTo(totalSupplyBefore.sub(expectedDiff), 25) }) }) diff --git a/test/plugins/individual-collateral/compoundv3/constants.ts b/test/plugins/individual-collateral/compoundv3/constants.ts index f6cbb6ff43..96784ea4b4 100644 --- a/test/plugins/individual-collateral/compoundv3/constants.ts +++ b/test/plugins/individual-collateral/compoundv3/constants.ts @@ -17,8 +17,8 @@ switch (forkNetwork) { break } -const USDC_NAME = chainId == '8453' ? 'USDbC' : 'USDC' -const CUSDC_NAME = chainId == '8453' ? 'cUSDbCv3' : 'cUSDCv3' +const USDC_NAME = 'USDC' +const CUSDC_NAME = 'cUSDCv3' // Mainnet Addresses export const RSR = networkConfig[chainId].tokens.RSR as string @@ -29,7 +29,7 @@ export const REWARDS = networkConfig[chainId].COMET_REWARDS! export const USDC = networkConfig[chainId].tokens[USDC_NAME]! export const USDC_HOLDER = chainId == '8453' - ? '0x4c80E24119CFB836cdF0a6b53dc23F04F7e652CA' + ? '0xcdac0d6c6c59727a65f871236188350531885c43' : '0x0a59649758aa4d66e25f08dd01271e891fe52199' export const COMET_CONFIGURATOR = networkConfig[chainId].COMET_CONFIGURATOR! export const COMET_PROXY_ADMIN = networkConfig[chainId].COMET_PROXY_ADMIN! @@ -43,4 +43,4 @@ export const DELAY_UNTIL_DEFAULT = bn(86400) export const MAX_TRADE_VOL = bn(1000000) export const USDC_DECIMALS = bn(6) -export const FORK_BLOCK = chainId == '8453' ? 4446300 : 15850930 +export const FORK_BLOCK = chainId == '8453' ? 12292893 : 15850930 diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 77b1602c3e..86ea3c0eab 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -706,7 +706,7 @@ export default function fn( expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) // Few more quanta of decrease results in default - await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(4)) // sub 4 to compenstate for rounding + await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(4)) // sub 4 to compensate for rounding await expect(ctx.collateral.refresh()).to.emit(ctx.collateral, 'CollateralStatusChanged') expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) expect(await ctx.collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) @@ -849,6 +849,7 @@ export default function fn( amtRate: fp('1e6'), // 1M RToken pctRate: fp('0.05'), // 5% }, + reweightable: false, } interface IntegrationFixture { diff --git a/test/plugins/individual-collateral/curve/constants.ts b/test/plugins/individual-collateral/curve/constants.ts index 9e68a63d9e..03b8cf1a3c 100644 --- a/test/plugins/individual-collateral/curve/constants.ts +++ b/test/plugins/individual-collateral/curve/constants.ts @@ -4,63 +4,75 @@ import { networkConfig } from '../../../../common/configuration' // Mainnet Addresses // DAI -export const DAI_USD_FEED = networkConfig['1'].chainlinkFeeds.DAI as string +export const DAI_USD_FEED = networkConfig['1'].chainlinkFeeds.DAI! export const DAI_ORACLE_TIMEOUT = bn('86400') export const DAI_ORACLE_ERROR = fp('0.0025') // USDC -export const USDC_USD_FEED = networkConfig['1'].chainlinkFeeds.USDC as string +export const USDC_USD_FEED = networkConfig['1'].chainlinkFeeds.USDC! export const USDC_ORACLE_TIMEOUT = bn('86400') export const USDC_ORACLE_ERROR = fp('0.0025') // USDT -export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT as string +export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT! export const USDT_ORACLE_TIMEOUT = bn('86400') export const USDT_ORACLE_ERROR = fp('0.0025') // SUSD -export const SUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.sUSD as string +export const SUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.sUSD! export const SUSD_ORACLE_TIMEOUT = bn('86400') export const SUSD_ORACLE_ERROR = fp('0.0025') // FRAX -export const FRAX_USD_FEED = networkConfig['1'].chainlinkFeeds.FRAX as string +export const FRAX_USD_FEED = networkConfig['1'].chainlinkFeeds.FRAX! export const FRAX_ORACLE_TIMEOUT = bn('3600') export const FRAX_ORACLE_ERROR = fp('0.01') // WBTC -export const WBTC_BTC_FEED = networkConfig['1'].chainlinkFeeds.WBTC as string -export const BTC_USD_FEED = networkConfig['1'].chainlinkFeeds.BTC as string +export const WBTC_BTC_FEED = networkConfig['1'].chainlinkFeeds.WBTC! +export const BTC_USD_FEED = networkConfig['1'].chainlinkFeeds.BTC! export const WBTC_ORACLE_TIMEOUT = bn('86400') export const BTC_ORACLE_TIMEOUT = bn('3600') export const WBTC_BTC_ORACLE_ERROR = fp('0.02') export const BTC_USD_ORACLE_ERROR = fp('0.005') // WETH -export const WETH_USD_FEED = networkConfig['1'].chainlinkFeeds.ETH as string +export const WETH_USD_FEED = networkConfig['1'].chainlinkFeeds.ETH! export const WETH_ORACLE_TIMEOUT = bn('86400') export const WETH_ORACLE_ERROR = fp('0.005') // MIM -export const MIM_USD_FEED = networkConfig['1'].chainlinkFeeds.MIM as string +export const MIM_USD_FEED = networkConfig['1'].chainlinkFeeds.MIM! export const MIM_ORACLE_TIMEOUT = bn('86400') export const MIM_ORACLE_ERROR = fp('0.005') // 0.5% export const MIM_DEFAULT_THRESHOLD = fp('0.055') // 5.5% +// crvUSD +export const crvUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.crvUSD! +export const crvUSD_ORACLE_TIMEOUT = bn('86400') +export const crvUSD_ORACLE_ERROR = fp('0.005') + +// pyUSD +export const pyUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.pyUSD! +export const pyUSD_ORACLE_TIMEOUT = bn('86400') +export const pyUSD_ORACLE_ERROR = fp('0.003') + // Tokens -export const DAI = networkConfig['1'].tokens.DAI as string -export const USDC = networkConfig['1'].tokens.USDC as string -export const USDT = networkConfig['1'].tokens.USDT as string -export const SUSD = networkConfig['1'].tokens.sUSD as string -export const FRAX = networkConfig['1'].tokens.FRAX as string -export const MIM = networkConfig['1'].tokens.MIM as string -export const eUSD = networkConfig['1'].tokens.eUSD as string -export const WETH = networkConfig['1'].tokens.WETH as string -export const WBTC = networkConfig['1'].tokens.WBTC as string - -export const RSR = networkConfig['1'].tokens.RSR as string -export const CRV = networkConfig['1'].tokens.CRV as string -export const CVX = networkConfig['1'].tokens.CVX as string +export const DAI = networkConfig['1'].tokens.DAI! +export const USDC = networkConfig['1'].tokens.USDC! +export const USDT = networkConfig['1'].tokens.USDT! +export const SUSD = networkConfig['1'].tokens.sUSD! +export const FRAX = networkConfig['1'].tokens.FRAX! +export const MIM = networkConfig['1'].tokens.MIM! +export const eUSD = networkConfig['1'].tokens.eUSD! +export const WETH = networkConfig['1'].tokens.WETH! +export const WBTC = networkConfig['1'].tokens.WBTC! +export const crvUSD = networkConfig['1'].tokens.crvUSD! +export const pyUSD = networkConfig['1'].tokens.pyUSD! + +export const RSR = networkConfig['1'].tokens.RSR! +export const CRV = networkConfig['1'].tokens.CRV! +export const CVX = networkConfig['1'].tokens.CVX! // 3pool - USDC, USDT, DAI export const THREE_POOL = '0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7' @@ -100,6 +112,18 @@ export const MIM_THREE_POOL_POOL_ID = 40 export const MIM_THREE_POOL_HOLDER = '0x66C90baCE2B68955C875FdA89Ba2c5A94e672440' export const MIM_THREE_POOL_GAUGE = '0xd8b712d29381748db89c36bca0138d7c75866ddf' +// crvUSD/USDC +export const crvUSD_USDC = '0x4dece678ceceb27446b35c672dc7d61f30bad69e' +export const crvUSD_USDC_POOL_ID = 182 +export const crvUSD_USDC_HOLDER = '0x95f00391cB5EebCd190EB58728B4CE23DbFa6ac1' +export const crvUSD_USDC_GAUGE = '0x95f00391cB5EebCd190EB58728B4CE23DbFa6ac1' + +// PayPool +export const PayPool = '0x383e6b4437b59fff47b619cba855ca29342a8559' +export const PayPool_POOL_ID = 270 +export const PayPool_HOLDER = '0x9da75997624C697444958aDeD6790bfCa96Af19A' +export const PayPool_GAUGE = '0x9da75997624c697444958aded6790bfca96af19a' + // Curve-specific export const CURVE_MINTER = '0xd061d61a4d941c39e5453435b6345dc261c2fce0' diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts new file mode 100644 index 0000000000..2ac9d95df7 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts @@ -0,0 +1,222 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + CurvePoolMock, + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + CRV, + CVX, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + USDC, + pyUSD_USD_FEED, + PayPool, + pyUSD_ORACLE_TIMEOUT, + pyUSD_ORACLE_ERROR, + PayPool_POOL_ID, + pyUSD, + PayPool_HOLDER, +} from '../constants' +import { getResetFork } from '../../helpers' + +type Fixture = () => Promise + +export const defaultCvxStableCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: pyUSD_USD_FEED, // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: 2, + curvePool: PayPool, + lpToken: PayPool, + poolType: CurvePoolType.Plain, + feeds: [[pyUSD_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [[pyUSD_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleErrors: [[pyUSD_ORACLE_ERROR], [USDC_ORACLE_ERROR]], +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute feeds + const pyUSDFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(PayPool_POOL_ID) + + opts.feeds = [[pyUSDFeed.address], [usdcFeed.address]] + opts.erc20 = wrapper.address + } + + opts = { ...defaultCvxStableCollateralOpts, ...opts } + + const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableCollateral' + ) + + const collateral = await CvxStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute feeds + const pyUSDFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[pyUSDFeed.address], [usdcFeed.address]] + + // Use mock curvePool seeded with initial balances + const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock') + const realCurvePool = await ethers.getContractAt('CurvePoolMock', PayPool) + const curvePool = ( + await CurvePoolMockFactory.deploy( + [await realCurvePool.balances(0), await realCurvePool.balances(1)], + [await realCurvePool.coins(0), await realCurvePool.coins(1)] + ) + ) + await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(PayPool_POOL_ID) + + collateralOpts.erc20 = wrapper.address + collateralOpts.curvePool = curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + const pyusd = await ethers.getContractAt('ERC20Mock', pyUSD) + + return { + alice, + collateral, + curvePool: curvePool, + wrapper: wrapper, + rewardTokens: [cvx, crv, pyusd], + poolTokens: [ + await ethers.getContractAt('ERC20Mock', pyUSD), + await ethers.getContractAt('ERC20Mock', USDC), + ], + feeds: [pyUSDFeed, usdcFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, PayPool_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + itClaimsRewards: it, + isMetapool: false, + resetFork: getResetFork(19287000), + collateralName: 'CurveStableCollateral - ConvexStakingWrapper (PayPool)', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts new file mode 100644 index 0000000000..d142c1ac7e --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts @@ -0,0 +1,221 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + CurvePoolMock, + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + CRV, + CVX, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + crvUSD_USD_FEED, + crvUSD_USDC, + crvUSD_ORACLE_TIMEOUT, + crvUSD_ORACLE_ERROR, + crvUSD_USDC_HOLDER, + crvUSD_USDC_POOL_ID, + USDC, + crvUSD, +} from '../constants' +import { getResetFork } from '../../helpers' + +type Fixture = () => Promise + +export const defaultCvxStableCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: crvUSD_USD_FEED, // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: 2, + curvePool: crvUSD_USDC, + lpToken: crvUSD_USDC, + poolType: CurvePoolType.Plain, + feeds: [[USDC_USD_FEED], [crvUSD_USD_FEED]], + oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [crvUSD_ORACLE_TIMEOUT]], + oracleErrors: [[USDC_ORACLE_ERROR], [crvUSD_ORACLE_ERROR]], +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute feeds + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const crvUSDFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(crvUSD_USDC_POOL_ID) + + opts.feeds = [[usdcFeed.address], [crvUSDFeed.address]] + opts.erc20 = wrapper.address + } + + opts = { ...defaultCvxStableCollateralOpts, ...opts } + + const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableCollateral' + ) + + const collateral = await CvxStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute feeds + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const crvUSDFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[usdcFeed.address], [crvUSDFeed.address]] + + // Use mock curvePool seeded with initial balances + const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock') + const realCurvePool = await ethers.getContractAt('CurvePoolMock', crvUSD_USDC) + const curvePool = ( + await CurvePoolMockFactory.deploy( + [await realCurvePool.balances(0), await realCurvePool.balances(1)], + [await realCurvePool.coins(0), await realCurvePool.coins(1)] + ) + ) + await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(crvUSD_USDC_POOL_ID) + + collateralOpts.erc20 = wrapper.address + collateralOpts.curvePool = curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: curvePool, + wrapper: wrapper, + rewardTokens: [cvx, crv], + poolTokens: [ + await ethers.getContractAt('ERC20Mock', USDC), + await ethers.getContractAt('ERC20Mock', crvUSD), + ], + feeds: [usdcFeed, crvUSDFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, crvUSD_USDC_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + itClaimsRewards: it, + isMetapool: false, + resetFork: getResetFork(19287000), + collateralName: 'CurveStableCollateral - ConvexStakingWrapper (crvUSD/USDC)', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/helpers.ts b/test/plugins/individual-collateral/curve/cvx/helpers.ts index a3bfbb93dc..ab06cdc726 100644 --- a/test/plugins/individual-collateral/curve/cvx/helpers.ts +++ b/test/plugins/individual-collateral/curve/cvx/helpers.ts @@ -183,8 +183,8 @@ export const mintWPool = async ( await lpToken.connect(signer).transfer(user.address, amount) }) - await lpToken.connect(user).approve(ctx.wrapper.address, amount) - await ctx.wrapper.connect(user).deposit(amount, recipient) + await lpToken.connect(user).approve(cvxWrapper.address, amount) + await cvxWrapper.connect(user).deposit(amount, recipient) } export const resetFork = getResetFork(FORK_BLOCK) diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 463bf9de1b..a250450b38 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -6,6 +6,7 @@ import { bn, fp } from '../../../common/numbers' import { useEnv } from '#/utils/env' import { Implementation, IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT } from '../../fixtures' import { + ActFacet, Asset, AssetRegistryP1, BackingManagerP1, @@ -17,18 +18,18 @@ import { DistributorP1, DutchTrade, ERC20Mock, - FacadeRead, - FacadeAct, FacadeTest, FacadeWrite, FurnaceP1, GnosisTrade, IGnosis, MainP1, + ReadFacet, RevenueTraderP1, RTokenP1, StRSRP1Votes, TestIDeployer, + TestIFacade, RecollateralizationLibP1, } from '../../../typechain' @@ -67,8 +68,7 @@ export interface DefaultFixture extends RSRAndModuleFixture { salt: string deployer: TestIDeployer rsrAsset: Asset - facade: FacadeRead - facadeAct: FacadeAct + facade: TestIFacade facadeTest: FacadeTest facadeWrite: FacadeWrite govParams: IGovParams @@ -84,13 +84,25 @@ export const getDefaultFixture = async function (salt: string) { throw new Error(`Missing network configuration for ${hre.network.name}`) } - // Deploy FacadeRead - const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') - const facade = await FacadeReadFactory.deploy() + // Deploy Facade + const FacadeFactory: ContractFactory = await ethers.getContractFactory('Facade') + const facade = await ethers.getContractAt('TestIFacade', (await FacadeFactory.deploy()).address) + + // Save ReadFacet to Facade + const ReadFacetFactory: ContractFactory = await ethers.getContractFactory('ReadFacet') + const readFacet = await ReadFacetFactory.deploy() + await facade.save( + readFacet.address, + Object.entries(readFacet.functions).map(([fn]) => readFacet.interface.getSighash(fn)) + ) - // Deploy FacadeAct - const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') - const facadeAct = await FacadeActFactory.deploy() + // Save ActFacet to Facade + const ActFacetFactory: ContractFactory = await ethers.getContractFactory('ActFacet') + const actFacet = await ActFacetFactory.deploy() + await facade.save( + actFacet.address, + Object.entries(actFacet.functions).map(([fn]) => actFacet.interface.getSighash(fn)) + ) // Deploy FacadeTest const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') @@ -231,7 +243,6 @@ export const getDefaultFixture = async function (salt: string) { deployer, gnosis, facade, - facadeAct, facadeTest, facadeWrite, govParams, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index 47a242ffb0..ca3c443e88 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -16,7 +16,7 @@ import { } from '../../../../typechain' import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' -import { CollateralStatus } from '../../../../common/constants' +import { CollateralStatus, ZERO_ADDRESS } from '../../../../common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { PRICE_TIMEOUT, @@ -25,7 +25,6 @@ import { MAX_TRADE_VOL, DEFAULT_THRESHOLD, DELAY_UNTIL_DEFAULT, - WETH, FRX_ETH, SFRX_ETH, ETH_USD_PRICE_FEED, @@ -58,10 +57,10 @@ interface SfrxEthCollateralOpts extends CollateralOpts { _maximumCurvePoolEma?: BigNumberish } -export const defaultRethCollateralOpts: SfrxEthCollateralOpts = { +export const defaultSFrxEthCollateralOpts: SfrxEthCollateralOpts = { erc20: SFRX_ETH, targetName: ethers.utils.formatBytes32String('ETH'), - rewardERC20: WETH, + rewardERC20: ZERO_ADDRESS, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ETH_USD_PRICE_FEED, oracleTimeout: ORACLE_TIMEOUT, @@ -78,7 +77,7 @@ export const defaultRethCollateralOpts: SfrxEthCollateralOpts = { export const deployCollateral = async ( opts: SfrxEthCollateralOpts = {} ): Promise => { - opts = { ...defaultRethCollateralOpts, ...opts } + opts = { ...defaultSFrxEthCollateralOpts, ...opts } const SFraxEthCollateralFactory: ContractFactory = await ethers.getContractFactory( 'SFraxEthCollateral' @@ -119,7 +118,7 @@ const makeCollateralFixtureContext = ( alice: SignerWithAddress, opts: CollateralOpts = {} ): Fixture => { - const collateralOpts = { ...defaultRethCollateralOpts, ...opts } + const collateralOpts = { ...defaultSFrxEthCollateralOpts, ...opts } const makeCollateralFixtureContext = async () => { const MockV3AggregatorFactory = ( diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts index ffd7c00be2..a23666dfd6 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -432,14 +432,6 @@ const execTestForToken = ({ await advanceTime(hre, 24 * 60 * 60 - 1) await methods.claimRewards(bob) - // console.log( - // 'MORPHO:', - // formatUnits( - // await instances.morpho.balanceOf(await bob.getAddress()), - // await instances.morpho.decimals() - // ) - // ) - expect(await instances.morpho.balanceOf(await bob.getAddress())).to.be.closeTo( BigNumber.from(i + 1) .mul(100) diff --git a/test/plugins/individual-collateral/morpho-aave/constants.ts b/test/plugins/individual-collateral/morpho-aave/constants.ts index 4c485c3e5d..f8435cdbee 100644 --- a/test/plugins/individual-collateral/morpho-aave/constants.ts +++ b/test/plugins/individual-collateral/morpho-aave/constants.ts @@ -7,4 +7,4 @@ export const ORACLE_ERROR = fp('0.0025') export const DEFAULT_THRESHOLD = ORACLE_ERROR.add(fp('0.01')) // 1% + ORACLE_ERROR export const DELAY_UNTIL_DEFAULT = bn(86400) -export const FORK_BLOCK = 17528677 +export const FORK_BLOCK = 19400000 diff --git a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts index f3636270c0..22017205a9 100644 --- a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts @@ -240,8 +240,6 @@ tests.forEach((test: CurveFiatTest) => { BigNumber.from(slotValue).mul(101).div(100).toHexString() // increase debt by 1% ) - await ethers.provider.getStorageAt(await collateral.erc20(), 0x28).then(console.log) - await collateral.refresh() const refPerTokAfter = await collateral.refPerTok() diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 9dd384a82c..2a27078d8f 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -9,9 +9,8 @@ import { bn, fp, pow10, toBNDecimals } from '../../common/numbers' import { Asset, ComptrollerMock, - CTokenWrapperMock, + CTokenMock, ERC20Mock, - FacadeRead, FacadeTest, GnosisMock, IAssetRegistry, @@ -20,6 +19,7 @@ import { StaticATokenMock, TestIBackingManager, TestIBasketHandler, + TestIFacade, TestIRevenueTrader, TestIRToken, WETH9, @@ -83,11 +83,11 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { let usdToken: ERC20Mock let eurToken: ERC20Mock - let cUSDTokenVault: CTokenWrapperMock + let cUSDToken: CTokenMock let aUSDToken: StaticATokenMock let wbtc: ERC20Mock - let cWBTCVault: CTokenWrapperMock - let cETHVault: CTokenWrapperMock + let cWBTC: CTokenMock + let cETH: CTokenMock let weth: WETH9 @@ -101,7 +101,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { let stRSR: TestIStRSR let assetRegistry: IAssetRegistry let basketHandler: TestIBasketHandler - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let backingManager: TestIBackingManager @@ -156,7 +156,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Setup Factories const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const WETH: ContractFactory = await ethers.getContractFactory('WETH9') - const CToken: ContractFactory = await ethers.getContractFactory('CTokenWrapperMock') + const CToken: ContractFactory = await ethers.getContractFactory('CTokenMock') const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( 'MockV3Aggregator' ) @@ -184,9 +184,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // 2. CTokenFiatCollateral against USD // 3. ATokenFiatCollateral against USD // 4. NonFiatCollateral WBTC against BTC - // 5. CTokenNonFiatCollateral cWBTCVault against BTC + // 5. CTokenNonFiatCollateral cWBTC against BTC // 6. SelfReferentialCollateral WETH against ETH - // 7. CTokenSelfReferentialCollateral cETHVault against ETH + // 7. CTokenSelfReferentialCollateral cETH against ETH primeBasketERC20s = [] targetPricesInUoA = [] @@ -241,12 +241,12 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { collateral.push(await ethers.getContractAt('EURFiatCollateral', fiatEUR)) // 3. CTokenFiatCollateral against USD - cUSDTokenVault = erc20s[4] // cDAI Token + cUSDToken = erc20s[4] // cDAI Token const { collateral: cUSDCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), priceFeed: usdFeed.address, oracleError: ORACLE_ERROR.toString(), - cToken: cUSDTokenVault.address, + cToken: cUSDToken.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -257,7 +257,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { }) await assetRegistry.swapRegistered(cUSDCollateral) - primeBasketERC20s.push(cUSDTokenVault.address) + primeBasketERC20s.push(cUSDToken.address) targetPricesInUoA.push(fp('1')) // USD Target collateral.push(await ethers.getContractAt('CTokenFiatCollateral', cUSDCollateral)) @@ -306,22 +306,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { targetPricesInUoA.push(fp('20000')) // BTC Target collateral.push(await ethers.getContractAt('NonFiatCollateral', wBTCCollateral)) - // 6. CTokenNonFiatCollateral cWBTCVault against BTC - cWBTCVault = ( - await CToken.deploy( - 'cWBTCVault Token', - 'cWBTCVault', - wbtc.address, - compToken.address, - compoundMock.address - ) + // 6. CTokenNonFiatCollateral cWBTC against BTC + cWBTC = ( + await CToken.deploy('cWBTC Token', 'cWBTC', wbtc.address, compoundMock.address) ) - const { collateral: cWBTCVaultCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { + const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), referenceUnitFeed: referenceUnitFeed.address, targetUnitFeed: targetUnitFeed.address, combinedOracleError: ORACLE_ERROR.toString(), - cToken: cWBTCVault.address, + cToken: cWBTC.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetUnitOracleTimeout: ORACLE_TIMEOUT.toString(), @@ -332,11 +326,11 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { noOutput: true, }) - await assetRegistry.register(cWBTCVaultCollateral) - primeBasketERC20s.push(cWBTCVault.address) + await assetRegistry.register(cWBTCCollateral) + primeBasketERC20s.push(cWBTC.address) targetPricesInUoA.push(fp('20000')) // BTC Target collateral.push( - await ethers.getContractAt('CTokenNonFiatCollateral', cWBTCVaultCollateral) + await ethers.getContractAt('CTokenNonFiatCollateral', cWBTCCollateral) ) // 7. SelfReferentialCollateral WETH against ETH @@ -361,24 +355,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await ethers.getContractAt('SelfReferentialCollateral', wETHCollateral) ) - // 8. CTokenSelfReferentialCollateral cETHVault against ETH + // 8. CTokenSelfReferentialCollateral cETH against ETH // Give higher maxTradeVolume: MAX_TRADE_VOLUME.toString(), - cETHVault = ( - await CToken.deploy( - 'cETHVault Token', - 'cETHVault', - weth.address, - compToken.address, - compoundMock.address - ) - ) - const { collateral: cETHVaultCollateral } = await hre.run( + cETH = await CToken.deploy('cETH Token', 'cETH', weth.address, compoundMock.address) + const { collateral: cETHCollateral } = await hre.run( 'deploy-ctoken-selfreferential-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), priceFeed: ethFeed.address, oracleError: ORACLE_ERROR.toString(), - cToken: cETHVault.address, + cToken: cETH.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), @@ -387,11 +373,11 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { noOutput: true, } ) - await assetRegistry.register(cETHVaultCollateral) - primeBasketERC20s.push(cETHVault.address) + await assetRegistry.register(cETHCollateral) + primeBasketERC20s.push(cETH.address) targetPricesInUoA.push(fp('1200')) // ETH Target collateral.push( - await ethers.getContractAt('CTokenSelfReferentialCollateral', cETHVaultCollateral) + await ethers.getContractAt('CTokenSelfReferentialCollateral', cETHCollateral) ) targetAmts = [] @@ -475,8 +461,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await eurToken.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn1)) expect(expectedTkn1).to.equal(quotes[1]) - expect(await cUSDTokenVault.balanceOf(backingManager.address)).to.equal(expectedTkn2) - expect(await cUSDTokenVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn2)) + expect(await cUSDToken.balanceOf(backingManager.address)).to.equal(expectedTkn2) + expect(await cUSDToken.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn2)) expect(expectedTkn2).to.equal(quotes[2]) expect(await aUSDToken.balanceOf(backingManager.address)).to.equal(expectedTkn3) @@ -487,16 +473,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await wbtc.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn4)) expect(expectedTkn4).to.equal(quotes[4]) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(expectedTkn5) - expect(await cWBTCVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn5)) + expect(await cWBTC.balanceOf(backingManager.address)).to.equal(expectedTkn5) + expect(await cWBTC.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn5)) expect(expectedTkn5).to.equal(quotes[5]) expect(await weth.balanceOf(backingManager.address)).to.equal(expectedTkn6) expect(await weth.balanceOf(addr1.address)).to.equal(wethDepositAmt.sub(expectedTkn6)) expect(expectedTkn6).to.equal(quotes[6]) - expect(await cETHVault.balanceOf(backingManager.address)).to.equal(expectedTkn7) - expect(await cETHVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn7)) + expect(await cETH.balanceOf(backingManager.address)).to.equal(expectedTkn7) + expect(await cETH.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn7)) expect(expectedTkn7).to.equal(quotes[7]) // Redeem @@ -511,8 +497,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await eurToken.balanceOf(backingManager.address)).to.equal(0) expect(await eurToken.balanceOf(addr1.address)).to.equal(initialBal) - expect(await cUSDTokenVault.balanceOf(backingManager.address)).to.equal(0) - expect(await cUSDTokenVault.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cUSDToken.balanceOf(backingManager.address)).to.equal(0) + expect(await cUSDToken.balanceOf(addr1.address)).to.equal(initialBal) expect(await aUSDToken.balanceOf(backingManager.address)).to.equal(0) expect(await aUSDToken.balanceOf(addr1.address)).to.equal(initialBal) @@ -520,14 +506,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) expect(await wbtc.balanceOf(addr1.address)).to.equal(initialBal) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTCVault.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cWBTC.balanceOf(backingManager.address)).to.equal(0) + expect(await cWBTC.balanceOf(addr1.address)).to.equal(initialBal) expect(await weth.balanceOf(backingManager.address)).to.equal(0) expect(await weth.balanceOf(addr1.address)).to.equal(wethDepositAmt) - expect(await cETHVault.balanceOf(backingManager.address)).to.equal(0) - expect(await cETHVault.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cETH.balanceOf(backingManager.address)).to.equal(0) + expect(await cETH.balanceOf(addr1.address)).to.equal(initialBal) }) it('Should claim COMP rewards correctly - All RSR', async () => { @@ -577,13 +563,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: cUSDTokenVault, + contract: backingManager, name: 'RewardsClaimed', args: [compToken.address, rewardAmount], emitted: true, }, { - contract: aUSDToken, + contract: backingManager, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -677,9 +663,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Requires 200 cDAI (at $0.02 = $4 USD) expect(quotes[2]).to.equal(bn(200e8)) - // Requires 1600 cWBTCVault (at $400 = $640K USD) - matches 32 BTC @ 20K + // Requires 1600 cWBTC (at $400 = $640K USD) - matches 32 BTC @ 20K expect(quotes[5]).to.equal(bn(1600e8)) - // Requires 6400 cETHVault (at $24 = $153,600 K USD) - matches 128 ETH @ 1200 + // Requires 6400 cETH (at $24 = $153,600 K USD) - matches 128 ETH @ 1200 expect(quotes[7]).to.equal(bn(6400e8)) // Issue 1 RToken @@ -701,19 +687,19 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rToken.totalSupply()).to.equal(issueAmount) // Increase redemption rate for cUSD to double - await cUSDTokenVault.setExchangeRate(fp('2')) + await cUSDToken.setExchangeRate(fp('2')) - // Increase redemption rate for cWBTCVault 25% - await cWBTCVault.setExchangeRate(fp('1.25')) + // Increase redemption rate for cWBTC 25% + await cWBTC.setExchangeRate(fp('1.25')) - // Increase redemption rate for cETHVault 5% - await cETHVault.setExchangeRate(fp('1.05')) + // Increase redemption rate for cETH 5% + await cETH.setExchangeRate(fp('1.05')) // Get updated quotes // Should now require: // Token2: 100 cDAI @ 0.004 = 4 USD - // Token5: 1280 cWBTCVault @ 500 = 640K USD - // Token7: 6095.23 cETHVault @ 25.2 = $153,600 USD + // Token5: 1280 cWBTC @ 500 = 640K USD + // Token7: 6095.23 cETH @ 25.2 = $153,600 USD const [, newQuotes] = await facade.connect(addr1).callStatic.issue(rToken.address, issueAmount) await assetRegistry.refresh() // refresh to update refPerTok() @@ -727,14 +713,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { const expectedTkn5: BigNumber = toBNDecimals( issueAmount.mul(targetAmts[5]).div(await collateral[5].refPerTok()), 8 - ) // cWBTCVault + ) // cWBTC expect(expectedTkn5).to.be.closeTo(newQuotes[5], point5Pct(newQuotes[5])) expect(newQuotes[5]).to.equal(bn(1280e8)) const expectedTkn7: BigNumber = toBNDecimals( issueAmount.mul(targetAmts[7]).div(await collateral[7].refPerTok()), 8 - ) // cETHVault + ) // cETH expect(expectedTkn7).to.be.closeTo(newQuotes[7], point5Pct(newQuotes[7])) expect(newQuotes[7]).to.be.closeTo(bn(6095e8), point5Pct(bn(6095e8))) @@ -751,7 +737,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(excessValueLow2).to.be.lt(fp('4')) expect(excessValueHigh2).to.be.gt(fp('4')) - // Excess cWBTCVault = 320 - valued at 320 * 500 = 160K usd (25%) + // Excess cWBTC = 320 - valued at 320 * 500 = 160K usd (25%) const excessQuantity5: BigNumber = quotes[5].sub(newQuotes[5]).mul(pow10(10)) // Convert to 18 decimals for simplification const [lowPrice5, highPrice5] = await collateral[5].price() const excessValueLow5: BigNumber = excessQuantity5.mul(lowPrice5).div(BN_SCALE_FACTOR) @@ -762,7 +748,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(excessValueLow5).to.be.lt(fp('160000')) expect(excessValueHigh5).to.be.gt(fp('160000')) - // Excess cETHVault = 304.7619- valued at 25.2 = 7679.999 usd (5%) + // Excess cETH = 304.7619- valued at 25.2 = 7679.999 usd (5%) const excessQuantity7: BigNumber = quotes[7].sub(newQuotes[7]).mul(pow10(10)) // Convert to 18 decimals for simplification const [lowPrice7, highPrice7] = await collateral[7].price() const excessValueLow7: BigNumber = excessQuantity7.mul(lowPrice7).div(BN_SCALE_FACTOR) @@ -804,14 +790,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(expectedToFurnace2).to.equal(bn(40e8)) // Token5- CWBTC - const expectedToTrader5 = toBNDecimals(excessQuantity5.mul(60).div(100), 8) // 60% of 320 tokens = 192 cWBTCVault - const expectedToFurnace5 = toBNDecimals(excessQuantity5, 8).sub(expectedToTrader5) // Remainder = 128 cWBTCVault + const expectedToTrader5 = toBNDecimals(excessQuantity5.mul(60).div(100), 8) // 60% of 320 tokens = 192 cWBTC + const expectedToFurnace5 = toBNDecimals(excessQuantity5, 8).sub(expectedToTrader5) // Remainder = 128 cWBTC expect(expectedToTrader5).to.equal(bn(192e8)) expect(expectedToFurnace5).to.equal(bn(128e8)) // Token7- CETH - const expectedToTrader7 = toBNDecimals(excessQuantity7.mul(60).div(100), 8) // 60% of 304.7619 = 182.85 cETHVault - const expectedToFurnace7 = toBNDecimals(excessQuantity7, 8).sub(expectedToTrader7) // Remainder = 121.9 cETHVault + const expectedToTrader7 = toBNDecimals(excessQuantity7.mul(60).div(100), 8) // 60% of 304.7619 = 182.85 cETH + const expectedToFurnace7 = toBNDecimals(excessQuantity7, 8).sub(expectedToTrader7) // Remainder = 121.9 cETH expect(expectedToTrader7).to.be.closeTo(bn('182.85e8'), point5Pct(bn('182.85e8'))) expect(expectedToFurnace7).to.be.closeTo(bn('121.9e8'), point5Pct(bn('121.9e8'))) @@ -866,14 +852,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Check auctions registered // cUSD -> RSR Auction await expectTrade(rsrTrader, { - sell: cUSDTokenVault.address, + sell: cUSDToken.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) // Check trades - let trade = await getTrade(rsrTrader, cUSDTokenVault.address) + let trade = await getTrade(rsrTrader, cUSDToken.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt2, auctionbuyAmt2] = await gnosis.auctions(auctionId) expect(sellAmt2).to.be.closeTo(auctionSellAmt2, point5Pct(auctionSellAmt2)) @@ -881,13 +867,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // cUSD -> RToken Auction await expectTrade(rTokenTrader, { - sell: cUSDTokenVault.address, + sell: cUSDToken.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) - trade = await getTrade(rTokenTrader, cUSDTokenVault.address) + trade = await getTrade(rTokenTrader, cUSDToken.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken2, auctionbuyAmtRToken2] = await gnosis.auctions(auctionId) expect(sellAmtRToken2).to.be.closeTo(auctionSellAmtRToken2, point5Pct(auctionSellAmtRToken2)) @@ -913,15 +899,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) // Check funds in Market and Traders - expect(await cUSDTokenVault.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cUSDToken.balanceOf(gnosis.address)).to.be.closeTo( sellAmt2.add(sellAmtRToken2), point5Pct(sellAmt2.add(sellAmtRToken2)) ) - expect(await cUSDTokenVault.balanceOf(rsrTrader.address)).to.equal( - expectedToTrader2.sub(sellAmt2) - ) - expect(await cUSDTokenVault.balanceOf(rTokenTrader.address)).to.equal( + expect(await cUSDToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader2.sub(sellAmt2)) + expect(await cUSDToken.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace2.sub(sellAmtRToken2) ) @@ -942,7 +926,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { buyAmount: auctionbuyAmtRToken2, }) - // Closing auction will create new auction for cWBTCVault + // Closing auction will create new auction for cWBTC // Set expected values const sellAmt5: BigNumber = expectedToTrader5 // everything is auctioned, below max auction const minBuyAmt5 = toMinBuyAmt( @@ -971,7 +955,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cUSDTokenVault.address, rsr.address, auctionSellAmt2, auctionbuyAmt2], + args: [anyValue, cUSDToken.address, rsr.address, auctionSellAmt2, auctionbuyAmt2], emitted: true, }, { @@ -979,7 +963,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cUSDTokenVault.address, + cUSDToken.address, rToken.address, auctionSellAmtRToken2, auctionbuyAmtRToken2, @@ -1020,51 +1004,51 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cUSDTokenVault.balanceOf(gnosis.address)).to.equal(0) - expect(await cUSDTokenVault.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cUSDTokenVault.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cUSDToken.balanceOf(gnosis.address)).to.equal(0) + expect(await cUSDToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cUSDToken.balanceOf(rTokenTrader.address)).to.equal(0) - // Check new auctions created for cWBTCVault + // Check new auctions created for cWBTC auctionTimestamp = await getLatestBlockTimestamp() // Check auctions registered - // cWBTCVault -> RSR Auction + // cWBTC -> RSR Auction await expectTrade(rsrTrader, { - sell: cWBTCVault.address, + sell: cWBTC.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('6'), }) // Check trades - trade = await getTrade(rsrTrader, cWBTCVault.address) + trade = await getTrade(rsrTrader, cWBTC.address) auctionId = await trade.auctionId() const [, , , auctionSellAmt5, auctionbuyAmt5] = await gnosis.auctions(auctionId) expect(sellAmt5).to.be.closeTo(auctionSellAmt5, point5Pct(auctionSellAmt5)) expect(minBuyAmt5).to.be.closeTo(auctionbuyAmt5, point5Pct(auctionbuyAmt5)) - // cWBTCVault -> RToken Auction + // cWBTC -> RToken Auction await expectTrade(rTokenTrader, { - sell: cWBTCVault.address, + sell: cWBTC.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('8'), }) - trade = await getTrade(rTokenTrader, cWBTCVault.address) + trade = await getTrade(rTokenTrader, cWBTC.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken5, auctionbuyAmtRToken5] = await gnosis.auctions(auctionId) expect(sellAmtRToken5).to.be.closeTo(auctionSellAmtRToken5, point5Pct(auctionSellAmtRToken5)) expect(minBuyAmtRToken5).to.be.closeTo(auctionbuyAmtRToken5, point5Pct(auctionbuyAmtRToken5)) // Check funds in Market and Traders - expect(await cWBTCVault.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cWBTC.balanceOf(gnosis.address)).to.be.closeTo( sellAmt5.add(sellAmtRToken5), point5Pct(sellAmt5.add(sellAmtRToken5)) ) - expect(await cWBTCVault.balanceOf(rsrTrader.address)).to.equal(expectedToTrader5.sub(sellAmt5)) - expect(await cWBTCVault.balanceOf(rTokenTrader.address)).to.equal( + expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(expectedToTrader5.sub(sellAmt5)) + expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace5.sub(sellAmtRToken5) ) @@ -1085,7 +1069,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { buyAmount: auctionbuyAmtRToken5, }) - // Closing auction will create new auction for cETHVault + // Closing auction will create new auction for cETH // Set expected values const sellAmt7: BigNumber = expectedToTrader7 // everything is auctioned, below max auction const minBuyAmt7 = toMinBuyAmt( @@ -1114,7 +1098,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cWBTCVault.address, rsr.address, auctionSellAmt5, auctionbuyAmt5], + args: [anyValue, cWBTC.address, rsr.address, auctionSellAmt5, auctionbuyAmt5], emitted: true, }, { @@ -1122,7 +1106,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cWBTCVault.address, + cWBTC.address, rToken.address, auctionSellAmtRToken5, auctionbuyAmtRToken5, @@ -1170,51 +1154,51 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cWBTCVault.balanceOf(gnosis.address)).to.equal(0) - expect(await cWBTCVault.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cWBTCVault.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cWBTC.balanceOf(gnosis.address)).to.equal(0) + expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal(0) - // Check new auctions created for cWBTCVault + // Check new auctions created for cWBTC auctionTimestamp = await getLatestBlockTimestamp() // Check auctions registered - // cETHVault -> RSR Auction + // cETH -> RSR Auction await expectTrade(rsrTrader, { - sell: cETHVault.address, + sell: cETH.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('10'), }) // Check trades - trade = await getTrade(rsrTrader, cETHVault.address) + trade = await getTrade(rsrTrader, cETH.address) auctionId = await trade.auctionId() const [, , , auctionSellAmt7, auctionbuyAmt7] = await gnosis.auctions(auctionId) expect(sellAmt7).to.be.closeTo(auctionSellAmt7, point5Pct(auctionSellAmt7)) expect(minBuyAmt7).to.be.closeTo(auctionbuyAmt7, point5Pct(auctionbuyAmt7)) - // cETHVault -> RToken Auction + // cETH -> RToken Auction await expectTrade(rTokenTrader, { - sell: cETHVault.address, + sell: cETH.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('11'), }) - trade = await getTrade(rTokenTrader, cETHVault.address) + trade = await getTrade(rTokenTrader, cETH.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken7, auctionbuyAmtRToken7] = await gnosis.auctions(auctionId) expect(sellAmtRToken7).to.be.closeTo(auctionSellAmtRToken7, point5Pct(auctionSellAmtRToken7)) expect(minBuyAmtRToken7).to.be.closeTo(auctionbuyAmtRToken7, point5Pct(auctionbuyAmtRToken7)) // Check funds in Market and Traders - expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo( sellAmt7.add(sellAmtRToken7), point5Pct(sellAmt7.add(sellAmtRToken7)) ) - expect(await cETHVault.balanceOf(rsrTrader.address)).to.equal(expectedToTrader7.sub(sellAmt7)) - expect(await cETHVault.balanceOf(rTokenTrader.address)).to.equal( + expect(await cETH.balanceOf(rsrTrader.address)).to.equal(expectedToTrader7.sub(sellAmt7)) + expect(await cETH.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace7.sub(sellAmtRToken7) ) @@ -1240,19 +1224,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cETHVault.address, rsr.address, auctionSellAmt7, auctionbuyAmt7], + args: [anyValue, cETH.address, rsr.address, auctionSellAmt7, auctionbuyAmt7], emitted: true, }, { contract: rTokenTrader, name: 'TradeSettled', - args: [ - anyValue, - cETHVault.address, - rToken.address, - auctionSellAmtRToken7, - auctionbuyAmtRToken7, - ], + args: [anyValue, cETH.address, rToken.address, auctionSellAmtRToken7, auctionbuyAmtRToken7], emitted: true, }, { @@ -1296,12 +1274,12 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cETHVault.balanceOf(gnosis.address)).to.equal(0) - expect(await cETHVault.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cETHVault.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cETH.balanceOf(gnosis.address)).to.equal(0) + expect(await cETH.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cETH.balanceOf(rTokenTrader.address)).to.equal(0) }) - it('Should recollateralize basket correctly - cWBTCVault', async () => { + it('Should recollateralize basket correctly - cWBTC', async () => { // Set RSR price to 25 cts for less auctions const rsrPrice = fp('0.25') // 0.25 usd await setOraclePrice(rsrAsset.address, toBNDecimals(rsrPrice, 8)) @@ -1329,24 +1307,24 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // cToken expect(quotes[4]).to.equal(fp('16')) // wBTC Target: 16 BTC expect(expectedTkn4).to.equal(quotes[4]) - expect(quotes[5]).to.equal(bn(1600e8)) // cWBTCVault Target: 32 BTC (1600 cWBTCVault @ 400 usd) + expect(quotes[5]).to.equal(bn(1600e8)) // cWBTC Target: 32 BTC (1600 cWBTC @ 400 usd) expect(expectedTkn5).to.equal(quotes[5]) - const cWBTCVaultCollateral = collateral[5] // cWBTCVault + const cWBTCCollateral = collateral[5] // cWBTC - // Set Backup for cWBTCVault to BTC + // Set Backup for cWBTC to BTC await basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('BTC'), bn(1), [wbtc.address]) - // Basket Swapping - Default cWBTCVault - should be replaced by BTC + // Basket Swapping - Default cWBTC - should be replaced by BTC // Decrease rate to cause default in Ctoken - await cWBTCVault.setExchangeRate(fp('0.8')) + await cWBTC.setExchangeRate(fp('0.8')) // Mark Collateral as Defaulted - await cWBTCVaultCollateral.refresh() + await cWBTCCollateral.refresh() - expect(await cWBTCVaultCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await cWBTCCollateral.status()).to.equal(CollateralStatus.DISABLED) // Ensure valid basket await basketHandler.refreshBasket() @@ -1368,8 +1346,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) // Running auctions will trigger recollateralization - All balance of invalid tokens will be redeemed - const sellAmt: BigNumber = await cWBTCVault.balanceOf(backingManager.address) - // For cWBTCVault = price fo $400 (20k / 50), rate 0.8 = $320 + const sellAmt: BigNumber = await cWBTC.balanceOf(backingManager.address) + // For cWBTC = price fo $400 (20k / 50), rate 0.8 = $320 const minBuyAmt = toMinBuyAmt( sellAmt.mul(pow10(10)), fp('320'), @@ -1382,36 +1360,36 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeStarted', - args: [anyValue, cWBTCVault.address, wbtc.address, sellAmt, minBuyAmt], + args: [anyValue, cWBTC.address, wbtc.address, sellAmt, minBuyAmt], emitted: true, }, ]) let auctionTimestamp = await getLatestBlockTimestamp() - // cWBTCVault (Defaulted) -> wBTC (only valid backup token for that target) + // cWBTC (Defaulted) -> wBTC (only valid backup token for that target) await expectTrade(backingManager, { - sell: cWBTCVault.address, + sell: cWBTC.address, buy: wbtc.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) // Check trade - let trade = await getTrade(backingManager, cWBTCVault.address) + let trade = await getTrade(backingManager, cWBTC.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt] = await gnosis.auctions(auctionId) expect(sellAmt).to.be.closeTo(auctionSellAmt, point5Pct(auctionSellAmt)) // Check funds in Market and Traders - expect(await cWBTCVault.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) - expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await cWBTC.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) + expect(await cWBTC.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 80% of value - // 1600 cWTBC -> 80% = 1280 cWBTCVault @ 400 = 512K = 25 BTC + // 1600 cWTBC -> 80% = 1280 cWBTC @ 400 = 512K = 25 BTC const auctionbuyAmt = fp('25') await wbtc.connect(addr1).approve(gnosis.address, auctionbuyAmt) await gnosis.placeBid(0, { @@ -1435,7 +1413,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeSettled', - args: [anyValue, cWBTCVault.address, wbtc.address, auctionSellAmt, auctionbuyAmt], + args: [anyValue, cWBTC.address, wbtc.address, auctionSellAmt, auctionbuyAmt], emitted: true, }, { @@ -1529,7 +1507,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) }) - it('Should recollateralize basket correctly - cETHVault, multiple auctions', async () => { + it('Should recollateralize basket correctly - cETH, multiple auctions', async () => { // Set RSR price to 100 usd const rsrPrice = fp('100') // 100 usd for less auctions await setOraclePrice(rsrAsset.address, toBNDecimals(rsrPrice, 8)) @@ -1557,24 +1535,24 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) expect(quotes[6]).to.equal(fp('12800')) // wETH Target: 64 ETH * 200 expect(expectedTkn6).to.equal(quotes[6]) - expect(quotes[7]).to.equal(bn(1280000e8)) // cETHVault Target: 128 ETH * 200 (6400 * 200 cETHVault @ 24 usd) + expect(quotes[7]).to.equal(bn(1280000e8)) // cETH Target: 128 ETH * 200 (6400 * 200 cETH @ 24 usd) expect(expectedTkn7).to.equal(quotes[7]) - const cETHVaultCollateral = collateral[7] // cETHVault + const cETHCollateral = collateral[7] // cETH - // Set Backup for cETHVault to wETH + // Set Backup for cETH to wETH await basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('ETH'), bn(1), [weth.address]) - // Basket Swapping - Default cETHVault - should be replaced by ETH + // Basket Swapping - Default cETH - should be replaced by ETH // Decrease rate to cause default in Ctoken - await cETHVault.setExchangeRate(fp('0.5')) + await cETH.setExchangeRate(fp('0.5')) // Mark Collateral as Defaulted - await cETHVaultCollateral.refresh() + await cETHCollateral.refresh() - expect(await cETHVaultCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await cETHCollateral.status()).to.equal(CollateralStatus.DISABLED) // Ensure valid basket await basketHandler.refreshBasket() @@ -1595,14 +1573,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(newBacking.length).to.equal(7) // One less token expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - // Running auctions will trigger recollateralization - cETHVault partial sale for weth - // Will sell about 841K of cETHVault, expect to receive 8167 wETH (minimum) - // We would still have about 438K to sell of cETHVault - let [low] = await cETHVaultCollateral.price() + // Running auctions will trigger recollateralization - cETH partial sale for weth + // Will sell about 841K of cETH, expect to receive 8167 wETH (minimum) + // We would still have about 438K to sell of cETH + let [low] = await cETHCollateral.price() const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) const sellAmt = toBNDecimals(sellAmtUnscaled, 8) - const sellAmtRemainder = (await cETHVault.balanceOf(backingManager.address)).sub(sellAmt) - // Price for cETHVault = 1200 / 50 = $24 at rate 50% = $12 + const sellAmtRemainder = (await cETH.balanceOf(backingManager.address)).sub(sellAmt) + // Price for cETH = 1200 / 50 = $24 at rate 50% = $12 const minBuyAmt = toMinBuyAmt( sellAmtUnscaled, fp('12'), @@ -1615,36 +1593,30 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeStarted', - args: [ - anyValue, - cETHVault.address, - weth.address, - withinQuad(sellAmt), - withinQuad(minBuyAmt), - ], + args: [anyValue, cETH.address, weth.address, withinQuad(sellAmt), withinQuad(minBuyAmt)], emitted: true, }, ]) let auctionTimestamp = await getLatestBlockTimestamp() - // cETHVault (Defaulted) -> wETH (only valid backup token for that target) + // cETH (Defaulted) -> wETH (only valid backup token for that target) await expectTrade(backingManager, { - sell: cETHVault.address, + sell: cETH.address, buy: weth.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) // Check trade - let trade = await getTrade(backingManager, cETHVault.address) + let trade = await getTrade(backingManager, cETH.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt] = await gnosis.auctions(auctionId) expect(sellAmt).to.be.closeTo(auctionSellAmt, point5Pct(auctionSellAmt)) // Check funds in Market and Traders - expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) - expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) + expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo( sellAmtRemainder, point5Pct(sellAmtRemainder) ) @@ -1669,12 +1641,12 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { config.maxTradeSlippage ) // Run auctions again for remainder - // We will sell the remaining 438K of cETHVault, expecting about 4253 WETH + // We will sell the remaining 438K of cETH, expecting about 4253 WETH await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, name: 'TradeSettled', - args: [anyValue, cETHVault.address, weth.address, auctionSellAmt, auctionbuyAmt], + args: [anyValue, cETH.address, weth.address, auctionSellAmt, auctionbuyAmt], emitted: true, }, { @@ -1682,7 +1654,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - cETHVault.address, + cETH.address, weth.address, withinQuad(sellAmtRemainder), withinQuad(minBuyAmtRemainder), @@ -1693,16 +1665,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { auctionTimestamp = await getLatestBlockTimestamp() - // cETHVault (Defaulted) -> wETH (only valid backup token for that target) + // cETH (Defaulted) -> wETH (only valid backup token for that target) await expectTrade(backingManager, { - sell: cETHVault.address, + sell: cETH.address, buy: weth.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) // Check trade - trade = await getTrade(backingManager, cETHVault.address) + trade = await getTrade(backingManager, cETH.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRemainder] = await gnosis.auctions(auctionId) expect(sellAmtRemainder).to.be.closeTo( @@ -1711,17 +1683,17 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check funds in Market and Traders - expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo( sellAmtRemainder, point5Pct(sellAmtRemainder) ) - expect(await cETHVault.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await cETH.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - // 438,000 cETHVault @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH + // 438,000 cETH @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH const auctionbuyAmtRemainder = toMinBuyAmt( auctionSellAmtRemainder, fp('12'), @@ -1761,7 +1733,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cETHVault.address, + cETH.address, weth.address, auctionSellAmtRemainder, auctionbuyAmtRemainder, diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index a3ab632140..f78b3434cf 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -10,12 +10,12 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenWrapperMock, + CTokenMock, ERC20Mock, - FacadeRead, FacadeTest, FiatCollateral, IAssetRegistry, + TestIFacade, TestIStRSR, MockV3Aggregator, StaticATokenMock, @@ -65,7 +65,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { let stRSR: TestIStRSR let assetRegistry: IAssetRegistry let basketHandler: TestIBasketHandler - let facade: FacadeRead + let facade: TestIFacade let facadeTest: FacadeTest let backingManager: TestIBackingManager @@ -211,11 +211,9 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { return atoken } - const makeCToken = async (tokenName: string): Promise => { + const makeCToken = async (tokenName: string): Promise => { const ERC20MockFactory: ContractFactory = await ethers.getContractFactory('ERC20Mock') - const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenWrapperMock' - ) + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') const CTokenCollateralFactory: ContractFactory = await ethers.getContractFactory( 'CTokenFiatCollateral' ) @@ -224,12 +222,11 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await ERC20MockFactory.deploy(tokenName, `${tokenName} symbol`) ) - const ctoken: CTokenWrapperMock = ( - await CTokenWrapperMockFactory.deploy( + const ctoken: CTokenMock = ( + await CTokenMockFactory.deploy( 'c' + tokenName, `${'c' + tokenName} symbol`, erc20.address, - compToken.address, compoundMock.address ) ) @@ -491,7 +488,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await assetRegistry.toColl(backing[0]) ) for (let i = maxBasketSize - tokensToDefault; i < backing.length; i++) { - const erc20 = await ethers.getContractAt('CTokenWrapperMock', backing[i]) + const erc20 = await ethers.getContractAt('CTokenMock', backing[i]) // Decrease rate to cause default in Ctoken await erc20.setExchangeRate(fp('0.8')) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 8b1cfa00fb..32b26a024e 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -7,7 +7,7 @@ import { bn, fp, divCeil } from '../../common/numbers' import { IConfig } from '../../common/configuration' import { CollateralStatus, TradeKind } from '../../common/constants' import { - CTokenWrapperMock, + CTokenMock, CTokenFiatCollateral, ERC20Mock, IAssetRegistry, @@ -44,7 +44,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Tokens and Assets let dai: ERC20Mock let daiCollateral: SelfReferentialCollateral - let cDAI: CTokenWrapperMock + let cDAI: CTokenMock let cDAICollateral: CTokenFiatCollateral // Config values @@ -106,7 +106,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Main ERC20 dai = erc20s[0] daiCollateral = collateral[0] - cDAI = erc20s[4] + cDAI = erc20s[4] cDAICollateral = await ( await ethers.getContractFactory('CTokenFiatCollateral') ).deploy( diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 5c8b5cd2b5..590623d245 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -21,7 +21,6 @@ import { TestIRevenueTrader, TestIRToken, WETH9, - CTokenWrapperMock, } from '../../typechain' import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' @@ -47,13 +46,12 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, let collateral: Collateral[] // Non-backing assets - let compToken: ERC20Mock let compoundMock: ComptrollerMock // Tokens and Assets let weth: WETH9 let wethCollateral: SelfReferentialCollateral - let cETH: CTokenWrapperMock + let cETH: CTokenMock let cETHCollateral: CTokenSelfReferentialCollateral let token0: CTokenMock let collateral0: Collateral @@ -84,7 +82,6 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, ;({ rsr, stRSR, - compToken, compoundMock, erc20s, collateral, @@ -123,8 +120,8 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // cETH cETH = await ( - await ethers.getContractFactory('CTokenWrapperMock') - ).deploy('cETH Token', 'cETH', weth.address, compToken.address, compoundMock.address) + await ethers.getContractFactory('CTokenMock') + ).deploy('cETH Token', 'cETH', weth.address, compoundMock.address) cETHCollateral = await ( await ethers.getContractFactory('CTokenSelfReferentialCollateral') diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index f145299065..df5478210c 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -8,7 +8,7 @@ import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' import { CollateralStatus, TradeKind } from '../../common/constants' import { - CTokenWrapperMock, + CTokenMock, CTokenNonFiatCollateral, ComptrollerMock, ERC20Mock, @@ -46,15 +46,14 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => let collateral: Collateral[] // Non-backing assets - let compToken: ERC20Mock let compoundMock: ComptrollerMock // Tokens and Assets let wbtc: ERC20Mock let wBTCCollateral: SelfReferentialCollateral - let cWBTC: CTokenWrapperMock + let cWBTC: CTokenMock let cWBTCCollateral: CTokenNonFiatCollateral - let token0: CTokenWrapperMock + let token0: CTokenMock let collateral0: Collateral let backupToken: ERC20Mock let backupCollateral: Collateral @@ -86,7 +85,6 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => ;({ rsr, stRSR, - compToken, compoundMock, erc20s, collateral, @@ -101,7 +99,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 - token0 = erc20s[4] // cDai + token0 = erc20s[4] // cDai collateral0 = collateral[4] wbtc = await (await ethers.getContractFactory('ERC20Mock')).deploy('WBTC Token', 'WBTC') @@ -131,8 +129,8 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // cWBTC cWBTC = await ( - await ethers.getContractFactory('CTokenWrapperMock') - ).deploy('cWBTC Token', 'cWBTC', wbtc.address, compToken.address, compoundMock.address) + await ethers.getContractFactory('CTokenMock') + ).deploy('cWBTC Token', 'cWBTC', wbtc.address, compoundMock.address) cWBTCCollateral = await ( await ethers.getContractFactory('CTokenNonFiatCollateral') ).deploy( diff --git a/test/utils/issue.ts b/test/utils/issue.ts index 5ef8d289e1..4d852022fd 100644 --- a/test/utils/issue.ts +++ b/test/utils/issue.ts @@ -4,7 +4,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' -import { FacadeRead, TestIRToken } from '../../typechain' +import { TestIFacade, TestIRToken } from '../../typechain' import { IMPLEMENTATION, Implementation } from '../fixtures' import { advanceBlocks } from './time' @@ -14,7 +14,7 @@ import { advanceBlocks } from './time' // blocks, we do this more cleverly than just one big issuance... // This presumes that user has already granted allowances of basket tokens! export async function issueMany( - facade: FacadeRead, + facade: TestIFacade, rToken: TestIRToken, toIssue: BigNumber, user: SignerWithAddress diff --git a/test/utils/tokens.ts b/test/utils/tokens.ts index 3ceebb8626..da6c94d2ca 100644 --- a/test/utils/tokens.ts +++ b/test/utils/tokens.ts @@ -1,5 +1,5 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { CTokenWrapperMock } from '@typechain/CTokenWrapperMock' +import { CTokenMock } from '@typechain/CTokenMock' import { ERC20Mock } from '@typechain/ERC20Mock' import { StaticATokenMock } from '@typechain/StaticATokenMock' import { USDCMock } from '@typechain/USDCMock' @@ -18,9 +18,7 @@ export const mintCollaterals = async ( const token2 = ( await ethers.getContractAt('StaticATokenMock', await basket[2].erc20()) ) - const token3 = ( - await ethers.getContractAt('CTokenWrapperMock', await basket[3].erc20()) - ) + const token3 = await ethers.getContractAt('CTokenMock', await basket[3].erc20()) for (const recipient of recipients) { await token0.connect(owner).mint(recipient.address, amount) diff --git a/utils/subgraph.ts b/utils/subgraph.ts index 76cd7b6a4b..d057660ff1 100644 --- a/utils/subgraph.ts +++ b/utils/subgraph.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { BigNumber, BigNumberish } from 'ethers' import { gql, GraphQLClient } from 'graphql-request' import { useEnv } from './env' @@ -25,6 +24,7 @@ export const getDelegates = async (governance: string): Promise> } ` const whales = await client.request(query, { governance }) + // @ts-expect-error Subgraphs are bad return whales.delegates } @@ -53,5 +53,6 @@ export const getProposalDetails = async (proposalId: string): Promise } ` const prop = await client.request(query, { id: proposalId }) + // @ts-expect-error Subgraphs are bad return prop.proposal } diff --git a/utils/time.ts b/utils/time.ts index cb1ffa1578..73070c350c 100644 --- a/utils/time.ts +++ b/utils/time.ts @@ -42,8 +42,9 @@ export const advanceBlocks = async (hre: HardhatRuntimeEnvironment, blocks: numb const newBlockString = blockString.slice(0, 2) + blockString.slice(3) blockString = newBlockString } - await hre.ethers.provider.send('hardhat_mine', [blockString]) - await hre.network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x0']) // Temporary fix - Hardhat issue + + await hre.ethers.provider.send('hardhat_mine', [blockString, '0xc']) + // await hre.network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x0']) // Temporary fix - Hardhat issue } export const advanceBlocksTenderly = async (