diff --git a/common/configuration.ts b/common/configuration.ts index bfae5bab6..756a73df2 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -106,7 +106,7 @@ export interface ITokens { Re7WETH?: string } -export type ITokensKeys = Array; +export type ITokensKeys = Array export interface IFeeds { stETHETH?: string @@ -218,7 +218,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', - wcUSDCv3: '0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A', + wcUSDCv3: '0x27F2f159Fe990Ba83D57f39Fd69661764BEbf37a', ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', sFRAX: '0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32', sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', diff --git a/common/constants.ts b/common/constants.ts index f0cffa16b..9ff641460 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1,5 +1,7 @@ import { utils, BigNumber } from 'ethers' +export const ZERO_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000' + export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const ONE_ADDRESS = '0x0000000000000000000000000000000000000001' diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index a47e72f9d..9c86c3ca1 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -171,6 +171,15 @@ interface IBasketHandler is IComponent { } interface TestIBasketHandler is IBasketHandler { + function getPrimeBasket() + external + view + returns ( + IERC20[] memory erc20s, + bytes32[] memory targetNames, + uint192[] memory targetAmts + ); + function lastCollateralized() external view returns (uint48); function warmupPeriod() external view returns (uint48); diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 535a5d4f9..f8732a30b 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -123,4 +123,13 @@ interface TestIDeployer is IDeployer { function gnosis() external view returns (IGnosis); function rsrAsset() external view returns (IAsset); + + function implementations() + external + view + returns ( + IMain, + Components memory, + TradePlugins memory + ); } diff --git a/contracts/interfaces/ISpell.sol b/contracts/interfaces/ISpell.sol new file mode 100644 index 000000000..e83393364 --- /dev/null +++ b/contracts/interfaces/ISpell.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/governance/IGovernor.sol"; +import "./IRToken.sol"; + +interface ISpell { + // Cast once-per-sender + /// @param rToken The RToken to upgrade + /// @param governor The corresponding Governor Alexios for the RToken + function cast(IRToken rToken, IGovernor governor) external; +} diff --git a/contracts/spells/3_4_0.sol b/contracts/spells/3_4_0.sol new file mode 100644 index 000000000..086d4f873 --- /dev/null +++ b/contracts/spells/3_4_0.sol @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts/governance/IGovernor.sol"; +import "@openzeppelin/contracts/governance/TimelockController.sol"; +import "../interfaces/IDeployer.sol"; +import "../interfaces/IMain.sol"; +import "../interfaces/ISpell.sol"; + +// TODO remove console logs. super helpful while working through each RToken though +import "hardhat/console.sol"; + +// === RTokens === +// Mainnet +IRToken constant eUSD = IRToken(0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F); +IRToken constant ETHPlus = IRToken(0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8); +IRToken constant hyUSD_mainnet = IRToken(0xaCdf0DBA4B9839b96221a8487e9ca660a48212be); +IRToken constant USDCPlus = IRToken(0xFc0B1EEf20e4c68B3DCF36c4537Cfa7Ce46CA70b); +IRToken constant USD3 = IRToken(0x0d86883FAf4FfD7aEb116390af37746F45b6f378); +IRToken constant rgUSD = IRToken(0x78da5799CF427Fee11e9996982F4150eCe7a99A7); + +// Base +IRToken constant hyUSD_base = IRToken(0xCc7FF230365bD730eE4B352cC2492CEdAC49383e); +IRToken constant bsdETH = IRToken(0xCb327b99fF831bF8223cCEd12B1338FF3aA322Ff); +IRToken constant iUSDC = IRToken(0xfE0D6D83033e313691E96909d2188C150b834285); +IRToken constant Vaya = IRToken(0xC9a3e2B3064c1c0546D3D0edc0A748E9f93Cf18d); + +// === Anastasius Governors === +// Mainnet +IGovernor constant ANASTASIUS_eUSD = IGovernor(0xfa4Cc3c65c5CCe085Fc78dD262d00500cf7546CD); +IGovernor constant ANASTASIUS_ETHPlus = IGovernor(0x991c13ff5e8bd3FFc59244A8cF13E0253C78d2bD); +IGovernor constant ANASTASIUS_hyUSD_mainnet = IGovernor(0xb79434b4778E5C1930672053f4bE88D11BbD1f97); +IGovernor constant ANASTASIUS_USDCPlus = IGovernor(0x6814F3489cbE3EB32b27508a75821073C85C12b7); +IGovernor constant ANASTASIUS_USD3 = IGovernor(0x16a0F420426FD102a85A7CcA4BA25f6be1E98cFc); +IGovernor constant ANASTASIUS_rgUSD = IGovernor(0xE5D337258a1e8046fa87Ca687e3455Eb8b626e1F); + +// Base +IGovernor constant ANASTASIUS_hyUSD_base = IGovernor(0x5Ef74A083Ac932b5f050bf41cDe1F67c659b4b88); +IGovernor constant ANASTASIUS_bsdETH = IGovernor(0x8A11D590B32186E1236B5E75F2d8D72c280dc880); +IGovernor constant ANASTASIUS_iUSDC = IGovernor(0xaeCa35F0cB9d12D68adC4d734D4383593F109654); +IGovernor constant ANASTASIUS_Vaya = IGovernor(0xC8f487B34251Eb76761168B70Dc10fA38B0Bd90b); + +// === 3.4.0 Implementations === +TestIDeployer constant mainDeployer = TestIDeployer(0x2204EC97D31E2C9eE62eaD9e6E2d5F7712D3f1bF); +TestIDeployer constant baseDeployer = TestIDeployer(0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A); + +// === 3.4.0 Assets === +/** + * ================================= + * | More Constants at END of file | + * ================================= + */ + +// ----------------------------------------------------------------------------------------------- + +// interface avoids needing to know about P1 contracts +interface ICachedComponent { + function cacheComponents() external; +} + +/** + * The upgrade spell for the 3.4.0 release. Can only be cast once per msg.sender. + * + * Expectation: cast by timelock after timelock grants administration and ownership of main + * + * REQUIREMENT before cast(): + * - This spell must be an administrator of the timelock + * - This spell must be an owner of the RToken + * + * Only works on Mainnet and Base. Only supports RTokens listed on the Register as of May 1, 2024 + */ +contract Upgrade3_4_0 is ISpell { + bytes32 constant ALEXIOS_HASH = keccak256(abi.encodePacked("Governor Alexios")); + + // Main + bytes32 constant MAIN_OWNER_ROLE = bytes32("OWNER"); + + // Timelock + bytes32 constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE"); + bytes32 constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); + bytes32 constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + bytes32 constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); + + mapping(IERC20 => IAsset) public assets; + + // msg.sender => bool + mapping(address => bool) public castFrom; + + bool public mainnet; // !mainnet => base + + constructor() { + console.log("block.chainid: ", block.chainid); + mainnet = block.chainid == 1 || block.chainid == 31337; + require(mainnet || block.chainid == 8453, "unsupported chain"); + + // Set up `assets` array + if (mainnet) { + console.log("mainnet"); + for (uint256 i = 0; i < MAINNET_ASSETS.length; i++) { + IERC20 erc20 = MAINNET_ASSETS[i].erc20(); + require(assets[erc20] == IAsset(address(0)), "duplicate asset"); + assets[erc20] = IAsset(MAINNET_ASSETS[i]); + } + console.log("mainnet set up successfully on", MAINNET_ASSETS.length, "assets"); + } else if (block.chainid == 8453) { + console.log("base"); + for (uint256 i = 0; i < BASE_ASSETS.length; i++) { + IERC20 erc20 = BASE_ASSETS[i].erc20(); + require(assets[erc20] == IAsset(address(0)), "duplicate asset"); + assets[erc20] = IAsset(BASE_ASSETS[i]); + } + console.log("base set up successfully on", BASE_ASSETS.length, "assets"); + } else { + revert("unsupported chain"); + } + } + + // Cast once-per-sender, which is assumed to be the timelock + /// @param rToken The RToken to upgrade + /// @param alexios The corresponding Governor Alexios for the RToken + /// @dev Requirement: has administration of Timelock and RToken. revoked at end of execution + function cast(IRToken rToken, IGovernor alexios) external { + console.log("cast", address(rToken), address(alexios)); + + // Can only cast once + require(!castFrom[msg.sender], "repeat cast"); + castFrom[msg.sender] = true; + + IMain main = rToken.main(); + TimelockController timelock = TimelockController(payable(msg.sender)); + + console.log("checking timelock + alexios"); + // Validations + require(keccak256(abi.encodePacked(alexios.name())) == ALEXIOS_HASH, "not alexios"); + require(timelock.hasRole(PROPOSER_ROLE, address(alexios)), "alexios not timelock admin"); + require(timelock.hasRole(TIMELOCK_ADMIN_ROLE, address(this)), "must be timelock admin"); + require(main.hasRole(MAIN_OWNER_ROLE, msg.sender), "timelock does not own Main"); + require(main.hasRole(MAIN_OWNER_ROLE, address(this)), "must be owner of Main"); + + console.log("grabbing anastasius to use"); + // Determine which anastasius to use for the RToken + TestIDeployer deployer = mainnet ? mainDeployer : baseDeployer; + IGovernor anastasius; + if (mainnet) { + if (rToken == eUSD) anastasius = ANASTASIUS_eUSD; + if (rToken == ETHPlus) anastasius = ANASTASIUS_ETHPlus; + if (rToken == hyUSD_mainnet) anastasius = ANASTASIUS_hyUSD_mainnet; + if (rToken == USDCPlus) anastasius = ANASTASIUS_USDCPlus; + if (rToken == USD3) anastasius = ANASTASIUS_USD3; + if (rToken == rgUSD) anastasius = ANASTASIUS_rgUSD; + } else { + if (rToken == hyUSD_base) anastasius = ANASTASIUS_hyUSD_base; + if (rToken == bsdETH) anastasius = ANASTASIUS_bsdETH; + if (rToken == iUSDC) anastasius = ANASTASIUS_iUSDC; + if (rToken == Vaya) anastasius = ANASTASIUS_Vaya; + } + require(address(anastasius) != address(0), "unsupported RToken"); + + Components memory proxies; + proxies.assetRegistry = main.assetRegistry(); + proxies.basketHandler = main.basketHandler(); + proxies.backingManager = main.backingManager(); + proxies.broker = main.broker(); + proxies.distributor = main.distributor(); + proxies.furnace = main.furnace(); + proxies.rToken = rToken; + proxies.rTokenTrader = main.rTokenTrader(); + proxies.rsrTrader = main.rsrTrader(); + proxies.stRSR = main.stRSR(); + + console.log("component upgrades"); + // Component Proxy Upgrades + { + ( + IMain mainImpl, + Components memory compImpls, + TradePlugins memory tradingImpls + ) = deployer.implementations(); + IBackingManager backingManager = main.backingManager(); + IBroker broker = main.broker(); + IDistributor distributor = main.distributor(); + IRevenueTrader rTokenTrader = main.rTokenTrader(); + IRevenueTrader rsrTrader = main.rsrTrader(); + + UUPSUpgradeable(address(main)).upgradeTo(address(mainImpl)); + UUPSUpgradeable(address(proxies.assetRegistry)).upgradeTo( + address(compImpls.assetRegistry) + ); + UUPSUpgradeable(address(proxies.backingManager)).upgradeTo( + address(compImpls.backingManager) + ); + UUPSUpgradeable(address(proxies.basketHandler)).upgradeTo( + address(compImpls.basketHandler) + ); + UUPSUpgradeable(address(proxies.broker)).upgradeTo(address(compImpls.broker)); + UUPSUpgradeable(address(proxies.distributor)).upgradeTo(address(compImpls.distributor)); + UUPSUpgradeable(address(proxies.furnace)).upgradeTo(address(compImpls.furnace)); + UUPSUpgradeable(address(proxies.rTokenTrader)).upgradeTo( + address(compImpls.rTokenTrader) + ); + UUPSUpgradeable(address(proxies.rsrTrader)).upgradeTo(address(compImpls.rsrTrader)); + UUPSUpgradeable(address(proxies.stRSR)).upgradeTo(address(compImpls.stRSR)); + UUPSUpgradeable(address(proxies.rToken)).upgradeTo(address(compImpls.rToken)); + + // Trading plugins + TestIBroker(address(broker)).setDutchTradeImplementation(tradingImpls.dutchTrade); + TestIBroker(address(broker)).setBatchTradeImplementation(tradingImpls.gnosisTrade); + + // cacheComponents() + ICachedComponent(address(broker)).cacheComponents(); + ICachedComponent(address(backingManager)).cacheComponents(); + ICachedComponent(address(distributor)).cacheComponents(); + ICachedComponent(address(rTokenTrader)).cacheComponents(); + ICachedComponent(address(rsrTrader)).cacheComponents(); + } + + console.log("scaling reward ratios"); + // Scale the reward downwards by the blocktime + { + uint48 blocktime = block.chainid == 8453 ? 2 : 12; // checked prior for else cases + proxies.furnace.setRatio(proxies.furnace.ratio() / blocktime); + TestIStRSR(address(proxies.stRSR)).setRewardRatio( + TestIStRSR(address(proxies.stRSR)).rewardRatio() / blocktime + ); + } + + console.log("============================================="); + console.log("swapping assets"); + // Assets + { + IERC20[] memory erc20s = proxies.assetRegistry.erc20s(); + for (uint256 i = 0; i < erc20s.length; i++) { + IERC20Metadata erc20 = IERC20Metadata(address(erc20s[i])); + if (address(erc20) == address(rToken)) continue; + if (assets[erc20] != IAsset(address(0))) { + // if we have a new asset with that erc20, swapRegistered() + proxies.assetRegistry.swapRegistered(assets[erc20]); + } else { + IAsset rotatedWrapperAsset = getRotatedWrapperAsset(erc20); + + // if we have a rotated asset, register() a new asset + if (rotatedWrapperAsset != IAsset(address(0))) { + console.log(i, "rotate:", erc20.symbol(), address(erc20)); + proxies.assetRegistry.register(rotatedWrapperAsset); + } else { + // assets being deprecated will be skipped + console.log(i, "skip:", erc20.symbol(), address(erc20)); + } + } + } + + // RTokenAsset -- always do last since could depend on everything else + proxies.assetRegistry.swapRegistered( + deployer.deployRTokenAsset( + rToken, + proxies.assetRegistry.toAsset(IERC20(address(rToken))).maxTradeVolume() + ) + ); + } + console.log("============================================="); + + console.log("switching basket"); + // Set new prime basket with rotated collateral + { + (IERC20[] memory erc20s, , uint192[] memory targetAmts) = TestIBasketHandler( + address(proxies.basketHandler) + ).getPrimeBasket(); + + for (uint256 i = 0; i < erc20s.length; i++) { + IERC20Metadata erc20 = IERC20Metadata(address(erc20s[i])); + if (assets[erc20s[i]] != IAsset(address(0))) continue; + + IAsset rotatedWrapperAsset = getRotatedWrapperAsset(erc20s[i]); + if (rotatedWrapperAsset != IAsset(address(0))) { + IERC20Metadata newERC20 = rotatedWrapperAsset.erc20(); + console.log(i, erc20.symbol(), "=>", newERC20.symbol()); + erc20s[i] = newERC20; + } + } + + // Set prime basket + proxies.basketHandler.setPrimeBasket(erc20s, targetAmts); + } + proxies.basketHandler.refreshBasket(); + require(proxies.basketHandler.status() == CollateralStatus.SOUND, "basket not sound"); + // basket must be SOUND post-upgrade + + console.log("upgrading governance"); + // Replace Alexios with Anastasius + timelock.revokeRole(EXECUTOR_ROLE, address(alexios)); + timelock.revokeRole(PROPOSER_ROLE, address(alexios)); + timelock.revokeRole(CANCELLER_ROLE, address(alexios)); + timelock.grantRole(EXECUTOR_ROLE, address(anastasius)); + timelock.grantRole(PROPOSER_ROLE, address(anastasius)); + timelock.grantRole(CANCELLER_ROLE, address(anastasius)); + + console.log("renouncing adminships"); + // Renounce adminships + main.renounceRole(MAIN_OWNER_ROLE, address(this)); + assert(!main.hasRole(MAIN_OWNER_ROLE, address(this))); + timelock.renounceRole(TIMELOCK_ADMIN_ROLE, address(this)); + assert(!timelock.hasRole(TIMELOCK_ADMIN_ROLE, address(this))); + console.log("spell cast"); + } + + // === Wrapper Rotation Helper === + + // Some assets may have been rotated to a new ERC20 wrapper and require + // special-hard coding. No erc20s will be unregistered as part of this spell. + function getRotatedWrapperAsset(IERC20 erc20) internal view returns (IAsset) { + if (mainnet) { + if (erc20 == IERC20(0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9)) { + return IAsset(0x8d753659D4E4e4b4601c7F01Dc1c920cA538E333); // saUSDT + } + if (erc20 == IERC20(0x60C384e226b120d93f3e0F4C502957b2B9C32B15)) { + return IAsset(0xc4240D22FFa144E2712aACF3E2cC302af0339ED0); // saUSDC + } + if (erc20 == IERC20(0x8d6E0402A3E3aD1b43575b05905F9468447013cF)) { + return IAsset(0x58a41c87f8C65cf21f961b570540b176e408Cf2E); // saEthPYUSD + } + if (erc20 == IERC20(0x093cB4f405924a0C468b43209d5E466F1dd0aC7d)) { + return IAsset(0x00F820794Bda3fb01E5f159ee1fF7c8409fca5AB); // saEthUSDC + } + if (erc20 == IERC20(0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A)) { + return IAsset(0x33Ba1BC07b0fafb4BBC1520B330081b91ca6bdf0); // wcUSDCv3 + } + } else { + // Base + } + return IAsset(address(0)); + } + + // === Asset Address Constants === + + IAsset[58] MAINNET_ASSETS = [ + IAsset(0x591529f039Ba48C3bEAc5090e30ceDDcb41D0EaA), // RSR + IAsset(0xF4493581D52671a9E04d693a68ccc61853bceEaE), + IAsset(0x63eDdF26Bc65eDa1D1c0147ce8E23c09BE963596), + IAsset(0xc18bF46F178F7e90b9CD8b7A8b00Af026D5ce3D3), + IAsset(0x7ef93b20C10E6662931b32Dd9D4b85861eB2E4b8), + IAsset(0xEc375F2984D21D5ddb0D82767FD8a9C4CE8Eec2F), + IAsset(0x442f8fc98e3cc6B3d49a66f9858Ac9B6e70Dad3e), + IAsset(0xe7Dcd101A027Ec34860ECb634a2797d0D2dc4d8b), + IAsset(0x4C0B21Acb267f1fAE4aeFA977A26c4a63C9B35e6), + IAsset(0x97bb4a995b98b1BfF99046b3c518276f78fA5250), + IAsset(0x9ca9A9cdcE9E943608c945E7001dC89EB163991E), + IAsset(0xc4240D22FFa144E2712aACF3E2cC302af0339ED0), + IAsset(0x8d753659D4E4e4b4601c7F01Dc1c920cA538E333), + IAsset(0x01F9A6bf339cff820cA503A56FD3705AE35c27F7), + IAsset(0xda5cc207CCefD116fF167a8ABEBBd52bD67C958E), + IAsset(0x337E418b880bDA5860e05D632CF039B7751B907B), + IAsset(0x043be931D9C4422e1cFeA528e19818dcDfdE9Ebc), + IAsset(0x5ceadb6606C5D82FcCd3f9b312C018fE1f8aa6dA), + IAsset(0xa0c02De8FfBb9759b9beBA5e29C82112688A0Ff4), + IAsset(0xC0f89AFcb6F1c4E943aA61FFcdFc41fDcB7D84DD), + IAsset(0x4d3A8507a8eb9036895efdD1a462210CE58DE4ad), + IAsset(0x832D65735E541c0404a58B741bEF5652c2B7D0Db), + IAsset(0xADDca344c92Be84A053C5CBE8e067460767FB816), + IAsset(0xb7049ee9F533D32C9434101f0645E6Ea5DFe2cdb), + IAsset(0x987f5e0f845D46262893e680b652D8aAF1B5bCc0), + IAsset(0xB58D95003Af73CF76Ce349103726a51D4Ec8af17), + IAsset(0xD5254b740FbEF6AAcD674936ea7Fb9f4053781aF), + IAsset(0xA0a620B94446a7DC8952ECf252FcC495eeC65873), + IAsset(0xFd9c32198D3cf3ad3b165918FD78De3654cb22eA), + IAsset(0x33Ba1BC07b0fafb4BBC1520B330081b91ca6bdf0), + IAsset(0x8E5ADdC553962DAcdF48106B6218AC93DA9617b2), + IAsset(0x5315Fbe0CEB299F53aE375f65fd9376767C8224c), + IAsset(0xE529B59C1764d6E5a274099Eb660DD9e130A5481), + IAsset(0x3d21f841C0Fb125176C1DBDF0DE196b071323A75), + IAsset(0xc4a5Fb266E8081D605D87f0b1290F54B0a5Dc221), + IAsset(0x945b0ad788dD6dB3864AB23876C68C1bf000d237), + IAsset(0x692cf8CE08d03eF1f8C3dCa82F67935fa9417B62), + IAsset(0xf59a7987EDd5380cbAb30c37D1c808686f9b67B9), + IAsset(0x62a9DDC6FF6077E823690118eCc935d16A8de47e), + IAsset(0xC8b80813cad9139D0eeFe38C711a11b20147aA54), + IAsset(0x2F8F8Ac64ECbAC38f212b05115836120784a29F7), + IAsset(0xC5d03FB7A38E6025D9A32C7444cfbBfa18B7D656), + IAsset(0x7be70371e7ECd9af5A5b49015EC8F8C336B52D81), + IAsset(0x75B6921925e8BD632380706e722035752ffF175d), + IAsset(0xA402078f0A2e077Ea2b1Fb3b6ab74F0cBA10E508), + IAsset(0x4a139215D9E696c0e7618a441eD3CFd12bbD8CD6), + IAsset(0x1573416df7095F698e37A954D9e951868E526650), + IAsset(0xb3A3552Cc52411dFF6D520C6F725E6F9e11001EF), + IAsset(0x0b7DcCBceA6f985301506D575E2661bf858CdEcC), + IAsset(0x00F820794Bda3fb01E5f159ee1fF7c8409fca5AB), + IAsset(0x58a41c87f8C65cf21f961b570540b176e408Cf2E), + IAsset(0x3017d881724D93783e7f065Cc5F62c81C62c36A0), + IAsset(0x4895b9aee383b5dec499F54172Ccc7Ee05FC8Bbc), + IAsset(0xBd01C789Be742688fb73F6aE46f1320196B6c973), + IAsset(0x3421d2cB19c8E69c6FA642C43e60cD943e75Ca8b), + IAsset(0x9Fc0F31e2D26C437461a9eEBfe858d17e2611Ea5), + IAsset(0x69c6597690B8Df61D15F201519C03725bdec40c1), + IAsset(0x4c891fCa6319d492866672E3D2AfdAAA5bDcfF67) + ]; + + IAsset[11] BASE_ASSETS = [ + IAsset(0x02062c16c28A169D1f2F5EfA7eEDc42c3311ec23), // RSR + IAsset(0xB8794Fb1CCd62bFe631293163F4A3fC2d22e37e0), + IAsset(0xEE527CC63122732532d0f1ad33Ec035D30f3050f), + IAsset(0x3E40840d0282C9F9cC7d17094b5239f87fcf18e5), + IAsset(0xaa85216187F92a781D8F9Bcb40825E356ee2635a), + IAsset(0xD126741474B0348D9B0F4911573d8f543c01C2c4), + IAsset(0x073BD162BBD05Cd2CF631B90D44239B8a367276e), + IAsset(0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D), + IAsset(0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50), + IAsset(0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461), + IAsset(0x8b4374005291B8FCD14C4E947604b2FB3C660A73) + ]; +} diff --git a/tasks/index.ts b/tasks/index.ts index b595bfbeb..3b7274cd6 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -26,3 +26,4 @@ import './upgrades/force-import' import './upgrades/validate-upgrade' import './validation/mint-tokens' import './validation/proposal-validator' +import './validation/test-spell' diff --git a/tasks/validation/proposal-validator.ts b/tasks/validation/proposal-validator.ts index 72bd613e4..28b78b16f 100644 --- a/tasks/validation/proposal-validator.ts +++ b/tasks/validation/proposal-validator.ts @@ -19,7 +19,6 @@ import { voteProposal, } from './utils/governance' import { advanceTime, getLatestBlockNumber } from '#/utils/time' -import { test_proposal } from './test-proposal' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { resetFork } from '#/utils/chain' import fs from 'fs' @@ -28,9 +27,9 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { BasketHandlerP1 } from '@typechain/BasketHandlerP1' import { RTokenP1 } from '@typechain/RTokenP1' import { StRSRP1Votes } from '@typechain/StRSRP1Votes' -import { MainP1 } from '@typechain/MainP1' import { IMain } from '@typechain/IMain' import { Whales, getWhalesFile } from '#/scripts/whalesConfig' +import { proposal_3_4_0_step_1, proposal_3_4_0_step_2 } from './proposals/3_4_0' interface Params { proposalid?: string @@ -39,7 +38,7 @@ interface Params { task('proposal-validator', 'Runs a proposal and confirms can fully rebalance + redeem + mint') .addParam('proposalid', 'the ID of the governance proposal', undefined) .setAction(async (params: Params, hre) => { - await resetFork(hre, Number(process.env.FORK_BLOCK)) + // await resetFork(hre, Number(process.env.FORK_BLOCK)) const chainId = await getChainId(hre) @@ -64,7 +63,9 @@ task('proposal-validator', 'Runs a proposal and confirms can fully rebalance + r pid: params.proposalid, }) - const proposalData = JSON.parse(fs.readFileSync(`./tasks/validation/proposals/proposal-${params.proposalid}.json`, 'utf-8')) + const proposalData = JSON.parse( + fs.readFileSync(`./tasks/validation/proposals/proposal-${params.proposalid}.json`, 'utf-8') + ) await hre.run('recollateralize', { rtoken: proposalData.rtoken, governor: proposalData.governor, @@ -85,12 +86,43 @@ task('proposal-validator', 'Runs a proposal and confirms can fully rebalance + r 'IBasketHandler', await main.basketHandler() ) + const backingManager = await hre.ethers.getContractAt( + 'IBackingManager', + await main.backingManager() + ) + const broker = await hre.ethers.getContractAt('IBroker', await main.broker()) + const distributor = await hre.ethers.getContractAt('IDistributor', await main.distributor()) + const furnace = await hre.ethers.getContractAt('IFurnace', await main.furnace()) + const stRSR = await hre.ethers.getContractAt('IStRSR', await main.stRSR()) + const rsrTrader = await hre.ethers.getContractAt('IRevenueTrader', await main.rsrTrader()) + const rTokenTrader = await hre.ethers.getContractAt('IRevenueTrader', await main.rTokenTrader()) 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!') + console.log('💪 Basket is SOUND and fully collateralized!') + + console.log('Core Contract versions') + console.log(' - main:', await main.version()) + console.log(' - assetRegistry:', await assetRegistry.version()) + console.log(' - basketHandler:', await basketHandler.version()) + console.log(' - backingManager:', await backingManager.version()) + console.log(' - broker:', await broker.version()) + console.log(' - distributor:', await distributor.version()) + console.log(' - furnace:', await furnace.version()) + console.log(' - stRSR:', await stRSR.version()) + console.log(' - rsrTrader:', await rsrTrader.version()) + console.log(' - rTokenTrader:', await rTokenTrader.version()) + console.log(' - rToken:', await rToken.version()) + + const [erc20s, assets] = await assetRegistry.getRegistry() + console.log('\n', `Asset versions (${assets.length})`) + for (let i = 0; i < assets.length; i++) { + const erc20 = await hre.ethers.getContractAt('IERC20Metadata', erc20s[i]) + const asset = await hre.ethers.getContractAt('IVersioned', assets[i]) + console.log(` - ${await erc20.symbol()}: ${await asset.version()}`) + } }) interface ProposeParams { @@ -100,9 +132,16 @@ interface ProposeParams { task('propose', 'propose a gov action') .addParam('pid', 'the ID of the governance proposal') .setAction(async (params: ProposeParams, hre) => { - const proposalData = JSON.parse(fs.readFileSync(`./tasks/validation/proposals/proposal-${params.pid}.json`, 'utf-8')) + const proposalData = JSON.parse( + fs.readFileSync(`./tasks/validation/proposals/proposal-${params.pid}.json`, 'utf-8') + ) - const proposal = await proposeUpgrade(hre, proposalData.rtoken, proposalData.governor, proposalData) + const proposal = await proposeUpgrade( + hre, + proposalData.rtoken, + proposalData.governor, + proposalData + ) if (proposal.proposalId != params.pid) { throw new Error(`Proposed Proposal ID does not match expected ID: ${params.pid}`) @@ -111,14 +150,19 @@ task('propose', 'propose a gov action') await moveProposalToActive(hre, proposalData.rtoken, proposalData.governor, proposal.proposalId) await voteProposal(hre, proposalData.rtoken, proposalData.governor, proposal.proposalId) await passProposal(hre, proposalData.governor, proposal.proposalId) - await executeProposal(hre, proposalData.rtoken, proposalData.governor, proposal.proposalId, proposal) + await executeProposal( + hre, + proposalData.rtoken, + proposalData.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, hre) => { - const [tester] = await hre.ethers.getSigners() const rToken = await hre.ethers.getContractAt('RTokenP1', params.rtoken) // 2. Bring back to fully collateralized @@ -131,8 +175,6 @@ task('recollateralize') 'BackingManagerP1', await main.backingManager() ) - // const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) - const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) /* recollateralize @@ -162,10 +204,6 @@ task('run-validations', 'Runs all validations') 'BasketHandlerP1', await main.basketHandler() ) - const backingManager = await hre.ethers.getContractAt( - 'BackingManagerP1', - await main.backingManager() - ) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) const chainId = await getChainId(hre) @@ -210,7 +248,6 @@ const runCheck_stakeUnstake = async ( ) => { const chainId = await getChainId(hre) const whales = getWhalesFile(chainId).tokens - // get RSR const stakeAmount = fp('4e6') const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) @@ -273,32 +310,30 @@ const runCheck_mint = async ( console.log('Successfully minted RTokens') } -import { proposal_3_4_0_step_1 } from './proposals/3_4_0' - task('print-proposal') .addParam('rtoken', 'the address of the RToken being upgraded') .addParam('gov', 'the address of the OWNER of the RToken being upgraded') .addParam('time', 'the address of the timelock') .setAction(async (params, hre) => { - const proposal = await proposal_3_4_0_step_1(hre, params.rtoken, params.gov, params.time) + const proposal = await proposal_3_4_0_step_2(hre, params.rtoken, params.gov, params.time) console.log(`\nGenerating and proposing proposal...`) const [tester] = await hre.ethers.getSigners() - + await hre.run('give-rsr', { address: tester.address }) await stakeAndDelegateRsr(hre, params.rtoken, tester.address) - + const governor = await hre.ethers.getContractAt('Governance', params.gov) - + const call = await governor.populateTransaction.propose( proposal.targets, proposal.values, proposal.calldatas, proposal.description ) - + console.log(`Proposal Transaction:\n`, call.data) - + const r = await governor.propose( proposal.targets, proposal.values, @@ -306,11 +341,14 @@ task('print-proposal') proposal.description ) const resp = await r.wait() - + console.log('\nSuccessfully proposed!') console.log(`Proposal ID: ${resp.events![0].args!.proposalId}`) - + proposal.proposalId = resp.events![0].args!.proposalId.toString() - fs.writeFileSync(`./tasks/validation/proposals/proposal-${proposal.proposalId}.json`, JSON.stringify(proposal, null, 2)) - }) \ No newline at end of file + fs.writeFileSync( + `./tasks/validation/proposals/proposal-${proposal.proposalId}.json`, + JSON.stringify(proposal, null, 2) + ) + }) diff --git a/tasks/validation/proposals/3_4_0.ts b/tasks/validation/proposals/3_4_0.ts index b28a3838d..bd9674475 100644 --- a/tasks/validation/proposals/3_4_0.ts +++ b/tasks/validation/proposals/3_4_0.ts @@ -1,51 +1,29 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types' import { ProposalBuilder, buildProposal } from '../utils/governance' import { Proposal } from '#/utils/subgraph' -import { getDeploymentFile, getDeploymentFilename, IDeployments } from '#/scripts/deployment/common' import { bn } from '#/common/numbers' +const MAIN_OWNER_ROLE = '0x4f574e4552000000000000000000000000000000000000000000000000000000' +const TIMELOCK_ADMIN_ROLE = '0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5' const EXECUTOR_ROLE = '0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63' const PROPOSER_ROLE = '0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1' - -// RToken address => Governor Anastasius address -export const GOVERNOR_ANASTASIUSES: { [key: string]: string } = { - '0xCc7FF230365bD730eE4B352cC2492CEdAC49383e': '0x5ef74a083ac932b5f050bf41cde1f67c659b4b88', - '0xCb327b99fF831bF8223cCEd12B1338FF3aA322Ff': '0x8A11D590B32186E1236B5E75F2d8D72c280dc880', - '0xfE0D6D83033e313691E96909d2188C150b834285': '0xaeCa35F0cB9d12D68adC4d734D4383593F109654', - '0xC9a3e2B3064c1c0546D3D0edc0A748E9f93Cf18d': '0xC8f487B34251Eb76761168B70Dc10fA38B0Bd90b', - '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F': '0xfa4Cc3c65c5CCe085Fc78dD262d00500cf7546CD', - '0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8': '0x991c13ff5e8bd3FFc59244A8cF13E0253C78d2bD', - '0xaCdf0DBA4B9839b96221a8487e9ca660a48212be': '0xb79434b4778E5C1930672053f4bE88D11BbD1f97', - '0xFc0B1EEf20e4c68B3DCF36c4537Cfa7Ce46CA70b': '0x6814F3489cbE3EB32b27508a75821073C85C12b7', - '0x0d86883FAf4FfD7aEb116390af37746F45b6f378': '0x16a0F420426FD102a85A7CcA4BA25f6be1E98cFc', - '0x78da5799CF427Fee11e9996982F4150eCe7a99A7': '0xE5D337258a1e8046fa87Ca687e3455Eb8b626e1F', -} +const CANCELLER_ROLE = '0xfd643c72710c63c0180259aba6b2d05451e3591a24e58b62239378085726f783' // some RTokens are on 1 week and some 2 week const ONE_WEEK_REWARD_RATIO = '1146076687500' -const TWO_WEEK_REWARD_RATIO = '573038343750' +// const TWO_WEEK_REWARD_RATIO = '573038343750' -export const proposal_3_4_0_step_1: ProposalBuilder = async ( +export const proposal_3_4_0_step_1 = async ( hre: HardhatRuntimeEnvironment, rTokenAddress: string, governorAddress: string, - timelockAddress?: string + timelockAddress: string, + spellAddress: string ): Promise => { - const deploymentFilename = getDeploymentFilename(1) // mainnet only - const deployments = getDeploymentFile(deploymentFilename) - console.log(deployments.implementations.components) - // Confirm old governor is Alexios const alexios = await hre.ethers.getContractAt('Governance', governorAddress) if ((await alexios.name()) != 'Governor Alexios') throw new Error('Governor Alexios only') - // Confirm a Governor Anastasius exists - const anastasius = await hre.ethers.getContractAt( - 'Governance', - GOVERNOR_ANASTASIUSES[rTokenAddress] - ) - if ((await anastasius.name()) != 'Governor Anastasius') throw new Error('configuration error') - // Validate timelock is controlled by governance if (!timelockAddress) throw new Error('missing timelockAddress') const timelock = await hre.ethers.getContractAt('TimelockController', timelockAddress) @@ -53,74 +31,26 @@ export const proposal_3_4_0_step_1: ProposalBuilder = async ( throw new Error('missing EXECUTOR_ROLE') if (!(await timelock.hasRole(PROPOSER_ROLE, governorAddress))) throw new Error('missing PROPOSER_ROLE') + // it might be missing CANCELLER_ROLE, that's ok 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 backingManager = await hre.ethers.getContractAt( - 'BackingManagerP1', - await main.backingManager() - ) - const basketHandler = await hre.ethers.getContractAt( - 'BasketHandlerP1', - await main.basketHandler() - ) - const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) - const distributor = await hre.ethers.getContractAt('DistributorP1', await main.distributor()) - const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) - const furnace = await hre.ethers.getContractAt('FurnaceP1', await main.furnace()) - const rsrTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rsrTrader()) - const rTokenTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rTokenTrader()) + + // TODO remove + // Deploy 3.4.0 Upgrade spell + console.log('Deploying 3.4.0 Upgrade spell...') + const SpellFactory = await hre.ethers.getContractFactory('Upgrade3_4_0') + const spell = await SpellFactory.deploy() + console.log('Deployed!') // Build proposal const txs = [ - await main.populateTransaction.upgradeTo(deployments.implementations.main), - await assetRegistry.populateTransaction.upgradeTo( - deployments.implementations.components.assetRegistry - ), - await backingManager.populateTransaction.upgradeTo( - deployments.implementations.components.backingManager - ), - await basketHandler.populateTransaction.upgradeTo( - deployments.implementations.components.basketHandler - ), - await broker.populateTransaction.upgradeTo(deployments.implementations.components.broker), - await distributor.populateTransaction.upgradeTo( - deployments.implementations.components.distributor - ), - await furnace.populateTransaction.upgradeTo(deployments.implementations.components.furnace), - await rsrTrader.populateTransaction.upgradeTo(deployments.implementations.components.rsrTrader), - await rTokenTrader.populateTransaction.upgradeTo( - deployments.implementations.components.rTokenTrader - ), - await stRSR.populateTransaction.upgradeTo(deployments.implementations.components.stRSR), - await rToken.populateTransaction.upgradeTo(deployments.implementations.components.rToken), - await broker.populateTransaction.cacheComponents(), - await backingManager.populateTransaction.cacheComponents(), - await distributor.populateTransaction.cacheComponents(), - await rTokenTrader.populateTransaction.cacheComponents(), - await rsrTrader.populateTransaction.cacheComponents(), - await broker.populateTransaction.setDutchTradeImplementation( - deployments.implementations.trading.dutchTrade - ), - await broker.populateTransaction.setBatchTradeImplementation( - deployments.implementations.trading.gnosisTrade - ), - await furnace.populateTransaction.setRatio(TWO_WEEK_REWARD_RATIO), - await stRSR.populateTransaction.setRewardRatio(TWO_WEEK_REWARD_RATIO), - // TODO - // plugin rotation - - await timelock.populateTransaction.grantRole(EXECUTOR_ROLE, anastasius.address), - await timelock.populateTransaction.grantRole(PROPOSER_ROLE, anastasius.address), - await timelock.populateTransaction.revokeRole(EXECUTOR_ROLE, alexios.address), - await timelock.populateTransaction.grantRole(PROPOSER_ROLE, alexios.address), + await main.populateTransaction.grantRole(MAIN_OWNER_ROLE, spell.address), + await timelock.populateTransaction.grantRole(TIMELOCK_ADMIN_ROLE, spell.address), + await spell.populateTransaction.cast(rTokenAddress, governorAddress), ] - const description = '3.4.0 Upgrade (1/2) - Core Contracts + Plugins' + const description = '3.4.0 Upgrade (1/3) - Core Contracts + Plugins' return buildProposal(txs, description) } @@ -131,6 +61,8 @@ export const proposal_3_4_0_step_2: ProposalBuilder = async ( governorAddress: string, timelockAddress?: string ): Promise => { + // Assumption: The upgrade spell has been cast + // Confirm governor is now Anastasius const anastasius = await hre.ethers.getContractAt('Governance', governorAddress) if ((await anastasius.name()) != 'Governor Anastasius') throw new Error('step one incomplete') @@ -142,6 +74,8 @@ export const proposal_3_4_0_step_2: ProposalBuilder = async ( throw new Error('missing EXECUTOR_ROLE') if (!(await timelock.hasRole(PROPOSER_ROLE, governorAddress))) throw new Error('missing PROPOSER_ROLE') + if (!(await timelock.hasRole(CANCELLER_ROLE, governorAddress))) + throw new Error('missing CANCELLER_ROLE') const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) @@ -163,7 +97,21 @@ export const proposal_3_4_0_step_2: ProposalBuilder = async ( }), ] - const description = '3.4.0 Upgrade (2/2) - Parameters' + const description = '3.4.0 Upgrade (2/3) - Parameters' return buildProposal(txs, description) -} \ No newline at end of file +} + +// export const proposal_3_4_0_step_3: ProposalBuilder = async ( +// hre: HardhatRuntimeEnvironment, +// rTokenAddress: string, +// governorAddress: string, +// timelockAddress?: string +// ): Promise => { +// // Assumption: The upgrade spell has been cast and rebalancing has completed + +// // TODO +// // Unregister assets + +// return buildProposal(txs, description) +// } diff --git a/tasks/validation/proposals/proposal-17900627719419352630033366909136145438567016127212944428505160895530188055111.json b/tasks/validation/proposals/proposal-17900627719419352630033366909136145438567016127212944428505160895530188055111.json new file mode 100644 index 000000000..b3311d322 --- /dev/null +++ b/tasks/validation/proposals/proposal-17900627719419352630033366909136145438567016127212944428505160895530188055111.json @@ -0,0 +1,25 @@ +{ + "targets": [ + "0x7697aE4dEf3C3Cd52493Ba3a6F57fc6d8c59108a", + "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c" + ], + "values": [ + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + } + ], + "calldatas": [ + "0x2f2ff15d4f574e455200000000000000000000000000000000000000000000000000000000000000000000000000000059c7d03d2e9893fb7baa89da50a9452e1e9b8b90", + "0x2f2ff15d5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca500000000000000000000000059c7d03d2e9893fb7baa89da50a9452e1e9b8b90" + ], + "description": "3.4.0 Upgrade (1/2) - Core Contracts + Plugins", + "rtoken": "0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F", + "governor": "0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6", + "timelock": "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c", + "proposalId": "17900627719419352630033366909136145438567016127212944428505160895530188055111" +} \ No newline at end of file diff --git a/tasks/validation/proposals/proposal-34722737871749208643486526059393197165905960221141580558985419711947245387076.json b/tasks/validation/proposals/proposal-34722737871749208643486526059393197165905960221141580558985419711947245387076.json new file mode 100644 index 000000000..9b39148b2 --- /dev/null +++ b/tasks/validation/proposals/proposal-34722737871749208643486526059393197165905960221141580558985419711947245387076.json @@ -0,0 +1,31 @@ +{ + "targets": [ + "0x7697aE4dEf3C3Cd52493Ba3a6F57fc6d8c59108a", + "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c", + "0x59c7D03d2E9893FB7bAa89dA50a9452e1e9B8b90" + ], + "values": [ + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + } + ], + "calldatas": [ + "0x2f2ff15d4f574e455200000000000000000000000000000000000000000000000000000000000000000000000000000059c7d03d2e9893fb7baa89da50a9452e1e9b8b90", + "0x2f2ff15d5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca500000000000000000000000059c7d03d2e9893fb7baa89da50a9452e1e9b8b90", + "0x73e6c4d7000000000000000000000000a0d69e286b938e21cbf7e51d71f6a4c8918f482f0000000000000000000000007e880d8bd9c9612d6a9759f96acd23df4a4650e6" + ], + "description": "3.4.0 Upgrade (1/2) - Core Contracts + Plugins", + "rtoken": "0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F", + "governor": "0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6", + "timelock": "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c", + "proposalId": "34722737871749208643486526059393197165905960221141580558985419711947245387076" +} \ No newline at end of file diff --git a/tasks/validation/proposals/proposal-36144461235757271964227165355811523816506632226260076096227737688903374014153.json b/tasks/validation/proposals/proposal-36144461235757271964227165355811523816506632226260076096227737688903374014153.json new file mode 100644 index 000000000..d38b06c08 --- /dev/null +++ b/tasks/validation/proposals/proposal-36144461235757271964227165355811523816506632226260076096227737688903374014153.json @@ -0,0 +1,31 @@ +{ + "targets": [ + "0xb6A7d481719E97e142114e905E86a39a2Fa0dfD2", + "0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B", + "0x834Ea01e45F9b5365314358159d92d134d89feEb" + ], + "values": [ + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + } + ], + "calldatas": [ + "0x2f2ff15d4f574e4552000000000000000000000000000000000000000000000000000000000000000000000000000000834ea01e45f9b5365314358159d92d134d89feeb", + "0x2f2ff15d5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5000000000000000000000000834ea01e45f9b5365314358159d92d134d89feeb", + "0x73e6c4d7000000000000000000000000e72b141df173b999ae7c1adcbf60cc9833ce56a8000000000000000000000000239cdcbe174b4728c870a24f77540dab3dc5f981" + ], + "description": "3.4.0 Upgrade (1/3) - Core Contracts + Plugins", + "rtoken": "0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8", + "governor": "0x239cDcBE174B4728c870A24F77540dAB3dC5F981", + "timelock": "0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B", + "proposalId": "36144461235757271964227165355811523816506632226260076096227737688903374014153" +} \ No newline at end of file diff --git a/tasks/validation/proposals/proposal-63555209040150894906565748602963996843240335059556549379607478624611035688854.json b/tasks/validation/proposals/proposal-63555209040150894906565748602963996843240335059556549379607478624611035688854.json new file mode 100644 index 000000000..f39e1cd19 --- /dev/null +++ b/tasks/validation/proposals/proposal-63555209040150894906565748602963996843240335059556549379607478624611035688854.json @@ -0,0 +1,31 @@ +{ + "targets": [ + "0x7697aE4dEf3C3Cd52493Ba3a6F57fc6d8c59108a", + "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c", + "0x29023DE63D7075B4cC2CE30B55f050f9c67548d4" + ], + "values": [ + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + }, + { + "type": "BigNumber", + "hex": "0x00" + } + ], + "calldatas": [ + "0x2f2ff15d4f574e455200000000000000000000000000000000000000000000000000000000000000000000000000000029023de63d7075b4cc2ce30b55f050f9c67548d4", + "0x2f2ff15d5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca500000000000000000000000029023de63d7075b4cc2ce30b55f050f9c67548d4", + "0x73e6c4d7000000000000000000000000a0d69e286b938e21cbf7e51d71f6a4c8918f482f0000000000000000000000007e880d8bd9c9612d6a9759f96acd23df4a4650e6" + ], + "description": "3.4.0 Upgrade (1/2) - Core Contracts + Plugins", + "rtoken": "0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F", + "governor": "0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6", + "timelock": "0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c", + "proposalId": "63555209040150894906565748602963996843240335059556549379607478624611035688854" +} \ No newline at end of file diff --git a/tasks/validation/test-spell.ts b/tasks/validation/test-spell.ts new file mode 100644 index 000000000..1705af2e5 --- /dev/null +++ b/tasks/validation/test-spell.ts @@ -0,0 +1,55 @@ +import fs from 'fs' +import { task } from 'hardhat/config' +import { BigNumber } from 'ethers' +import { MAINNET_DEPLOYMENTS } from './utils/constants' +import { proposal_3_4_0_step_1, proposal_3_4_0_step_2 } from './proposals/3_4_0' + +// Use this once to serialize a proposal +task( + 'test-spell', + "Check the implementation to figure out what this does; it's always in flux" +).setAction(async (params, hre) => { + console.log('Part 1') + + // Deploy 3.4.0 Upgrade spell + console.log('Deploying 3.4.0 Upgrade spell...') + const SpellFactory = await hre.ethers.getContractFactory('Upgrade3_4_0') + const spell = await SpellFactory.deploy() + console.log('Deployed!') + + for (const deployment of MAINNET_DEPLOYMENTS) { + const step1 = await proposal_3_4_0_step_1( + hre, + deployment.rToken, + deployment.governor, + deployment.timelock, + spell.address + ) + step1.rtoken = deployment.rToken + step1.governor = deployment.governor + step1.timelock = deployment.timelock + + const governor = await hre.ethers.getContractAt('Governance', deployment.governor) + const descHash = hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(step1.description)) + step1.proposalId = BigNumber.from( + await governor.hashProposal(step1.targets, step1.values, step1.calldatas, descHash) + ).toString() + + fs.writeFileSync( + `./tasks/validation/proposals/proposal-${step1.proposalId}.json`, + JSON.stringify(step1, null, 4) + ) + + await hre.run('proposal-validator', { + proposalid: step1.proposalId, + }) + + const rToken = await hre.ethers.getContractAt('RTokenP1', deployment.rToken) + if ((await rToken.version()) != '3.4.0') throw new Error('Failed to upgrade to 3.4.0') + + console.log('Part 2') + + // const step2 = await proposal_3_4_0_step_2(hre, deployment.rtoken, deployment.governor, deployment.timelock) + // console.log(step2) + } +}) diff --git a/tasks/validation/utils/constants.ts b/tasks/validation/utils/constants.ts index 8b05f2345..045a1d897 100644 --- a/tasks/validation/utils/constants.ts +++ b/tasks/validation/utils/constants.ts @@ -44,3 +44,70 @@ export const collateralToUnderlying: { [key: string]: string } = { [networkConfig['1'].tokens.saEthUSDC!.toLowerCase()]: networkConfig['1'].tokens.aEthUSDC!.toLowerCase(), } + +export interface RTokenDeployment { + rToken: string + governor: string + timelock: string +} + +export const MAINNET_DEPLOYMENTS: RTokenDeployment[] = [ + // { + // rToken: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', // eUSD + // governor: '0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6', + // timelock: '0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', + // }, + { + rToken: '0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8', // ETH+ + governor: '0x239cDcBE174B4728c870A24F77540dAB3dC5F981', + timelock: '0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B', + }, + // { + // rToken: '0xaCdf0DBA4B9839b96221a8487e9ca660a48212be', // hyUSD (mainnet) + // governor: '0x22d7937438b4bBf02f6cA55E3831ABB94Bd0b6f1', + // timelock: '0x624f9f076ED42ba3B37C3011dC5a1761C2209E1C', + // }, + // { + // rToken: '0xFc0B1EEf20e4c68B3DCF36c4537Cfa7Ce46CA70b', // USDC+ + // governor: '0xc837C557071D604bCb1058c8c4891ddBe8FDD630', + // timelock: '0x6C957417cB6DF6e821eec8555DEE8b116C291999', + // }, + // { + // rToken: '0x0d86883FAf4FfD7aEb116390af37746F45b6f378', // USD3 + // governor: '0x020CB71181008369C388CaAEE98b0E69f8F4C471', + // timelock: '0xE0289984F709fc7150E646B672bfaDC879a15f14', + // }, + // { + // rToken: '0x78da5799CF427Fee11e9996982F4150eCe7a99A7', // rgUSD + // governor: '0x409bAc94c4207C6627EA5f4E4FFB7128e8F654Fc', + // timelock: '0x9aD9E73e38c8506a664A3A37e8A9CE910B6FBeb4', + // }, +] + +export const BASE_DEPLOYMENTS: RTokenDeployment[] = [ + { + rToken: '0xCc7FF230365bD730eE4B352cC2492CEdAC49383e', // hyUSD (base) + governor: '0xc8e63d3501A246fa1ddBAbe4ad0B50e9d32aA8bb', + timelock: '0xf093d7f00f3dCe6d415Be564f41Cb4bc032fb367', + }, + { + rToken: '0xCb327b99fF831bF8223cCEd12B1338FF3aA322Ff', // bsdETH + governor: '0xB05C6a7242595f2E23CC6a0aB20699d63D0939Fd', + timelock: '0x321f7493B8B675dFfE2570Bd0F164237D445b9E8', + }, + { + rToken: '0xfE0D6D83033e313691E96909d2188C150b834285', // iUSDC + governor: '0xfe637F7D5B848392c19052631d68F8AC859F71cF', + timelock: '0xd18ED37CA912bbf1EDE93d27459d03DC4343dea1', + }, + { + rToken: '0xC9a3e2B3064c1c0546D3D0edc0A748E9f93Cf18d', // Vaya + governor: '0xEb583EA06501f92E994C353aD2741A35582987aA', + timelock: '0xeE3eC997A37e661a42673D7A489Fbf0E5ed0C223', + }, + { + rToken: '0x641B0453487C9D14c5df96d45a481ef1dc84e31f', // MAAT + governor: '0x0f7f1442dA7F687BB877Fbee0539FA8D6e4d1a02', + timelock: '0xE67cEb03EfdF9B3fb5C3FeBF3103e2efd3a76A1b', + }, +] diff --git a/tasks/validation/utils/governance.ts b/tasks/validation/utils/governance.ts index 10b205700..fb9816516 100644 --- a/tasks/validation/utils/governance.ts +++ b/tasks/validation/utils/governance.ts @@ -173,7 +173,14 @@ export const executeProposal = async ( console.log('Executing now...') // Execute - await governor.execute(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) + const tx = await governor.execute( + proposal.targets, + proposal.values, + proposal.calldatas, + descriptionHash + ) + const receipt = await tx.wait() + console.log('Gas Used:', receipt.gasUsed.toString()) propState = await governor.state(proposalId) await validatePropState(propState, ProposalState.Executed) diff --git a/tasks/validation/utils/logs.ts b/tasks/validation/utils/logs.ts index b79dbedb3..9fede90e3 100644 --- a/tasks/validation/utils/logs.ts +++ b/tasks/validation/utils/logs.ts @@ -53,6 +53,13 @@ const tokens: { [key: string]: string } = { ['0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A'.toLowerCase()]: 'wcUSDCv3', ['0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5'.toLowerCase()]: 'stkcvxeUSDFRAXBP', ['0x83f20f44975d03b1b09e64809b757c47f942beea'.toLowerCase()]: 'sDAI', + ['0xa8157BF67Fd7BcDCC139CB9Bf1bd7Eb921A779D3'.toLowerCase()]: 'saUSDC', + ['0x684AA4faf9b07d5091B88c6e0a8160aCa5e6d17b'.toLowerCase()]: 'saUSDT', + ['0xe176A5ebFB873D5b3cf1909d0EdaE4FE095F5bc7'.toLowerCase()]: 'saEthPyUSD', + ['0x81697e25DFf8564d9E0bC6D27edb40006b34ea2A'.toLowerCase()]: 'stkcvxeUSDFRAXBP', + ['0x8e33D5aC344f9F2fc1f2670D45194C280d4fBcF1'.toLowerCase()]: 'stkcvxeUSDFRAXBP', + ['0x093cb4f405924a0c468b43209d5e466f1dd0ac7d'.toLowerCase()]: 'saEthUSDC', + ['0x27F2f159Fe990Ba83D57f39Fd69661764BEbf37a'.toLowerCase()]: 'wcUSDCv3', } export const logToken = (tokenAddress: string) => { diff --git a/tasks/validation/utils/trades.ts b/tasks/validation/utils/trades.ts index af256b854..7fd661ab7 100644 --- a/tasks/validation/utils/trades.ts +++ b/tasks/validation/utils/trades.ts @@ -2,12 +2,7 @@ import { MAX_UINT256, QUEUE_START, TradeKind, TradeStatus } from '#/common/const import { bn, fp } from '#/common/numbers' import { whileImpersonating } from '#/utils/impersonation' import { networkConfig } from '../../../common/configuration' -import { - advanceToTimestamp, - advanceTime, - getLatestBlockNumber, - getLatestBlockTimestamp, -} from '#/utils/time' +import { advanceTime, getLatestBlockTimestamp } from '#/utils/time' import { DutchTrade } from '@typechain/DutchTrade' import { GnosisTrade } from '@typechain/GnosisTrade' import { TestITrading } from '@typechain/TestITrading' @@ -92,6 +87,7 @@ export const runDutchTrade = async ( trader: TestITrading, tradeToken: string ): Promise<[boolean, string]> => { + const [signer] = await hre.ethers.getSigners() const router = await (await hre.ethers.getContractFactory('DutchTradeRouter')).deploy() // NOTE: // buy & sell are from the perspective of the auction-starter @@ -117,19 +113,31 @@ export const runDutchTrade = async ( ) const endTime = await trade.endTime() - const whaleAddr = whales[buyTokenAddress.toLowerCase()] - - // Bid near 1:1 point, which occurs at the 70% mark - const latestTimestamp = await getLatestBlockTimestamp(hre) - const toAdvance = (endTime - latestTimestamp) * 7 / 10 + let whaleAddr = whales[buyTokenAddress.toLowerCase()] + if (!whaleAddr) console.log('missing whale for ' + buyTokenAddress) + whaleAddr = signer.address + + // Bid near 1:1 point, which occurs at a difficult-to-calculate time due to maxTradeSlippage + const bestPrice = await trade.bestPrice() + const worstPrice = await trade.worstPrice() + const delta = bestPrice.sub(worstPrice).mul(fp('1')).div(bestPrice) + const maxTradeSlippage = (await trader.maxTradeSlippage()).mul(fp('1')).div(delta) + const unofficialEnd = 95 - maxTradeSlippage.div(2).div(bn('1e16')).toNumber() + const fairMidpoint = (unofficialEnd - 45) / 2 + 45 + console.log(bestPrice, worstPrice, delta, maxTradeSlippage, fairMidpoint) + console.log('bidding at pct:', fairMidpoint) + + const toAdvance = ((endTime - (await getLatestBlockTimestamp(hre))) * fairMidpoint) / 100 await advanceTime(hre, toAdvance) - const buyAmount = await trade.bidAmount(await getLatestBlockNumber(hre)) + const buyAmount = await trade.bidAmount(await getLatestBlockTimestamp(hre)) // Ensure funds available await getTokens(hre, buyTokenAddress, buyAmount, whaleAddr) const buyToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) - await buyToken.connect(whaleAddr).approve(router.address, MAX_UINT256) + await whileImpersonating(hre, whaleAddr, async (whale) => { + await buyToken.connect(whale).approve(router.address, MAX_UINT256) + }) // Bid ;[tradesRemain, newSellToken] = await callAndGetNextTrade( @@ -208,9 +216,10 @@ export const getTokens = async ( ) => { console.log('Acquiring tokens...', tokenAddress) switch (tokenAddress.toLowerCase()) { - case '0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase(): // saUSDC mainnet - case '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase(): // saUSDT mainnet - case '0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50'.toLowerCase(): // saBasUSDC base + case '0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase(): // <3.4.0 saUSDC mainnet + case '0xa8157BF67Fd7BcDCC139CB9Bf1bd7Eb921A779D3'.toLowerCase(): // >=3.4.0 saUSDC mainnet + case '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase(): // <3.4.0 saUSDT mainnet + case '0x684AA4faf9b07d5091B88c6e0a8160aCa5e6d17b'.toLowerCase(): // >=3.4.0 saUSDT mainnet await getStaticAToken(hre, tokenAddress, amount, recipient) break case '0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase(): // cUSDCVault mainnet @@ -225,6 +234,7 @@ export const getTokens = async ( case '0x6ad24C0B8fD4B594C6009A7F7F48450d9F56c6b8'.toLowerCase(): // cvxCrvUSDUSDC mainnet case '0x5d1B749bA7f689ef9f260EDC54326C48919cA88b'.toLowerCase(): // cvxCrvUSDUSDT mainnet await getCvxVault(hre, tokenAddress, amount, recipient) + break default: await getERC20Tokens(hre, tokenAddress, amount, recipient) return @@ -246,7 +256,6 @@ const getCvxVault = async ( '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', curveTokenAddy ) - await whileImpersonating(hre, whales[curveTokenAddy.toLowerCase()], async (whaleSigner) => { await curvePool.connect(whaleSigner).transfer(recipient, amount) }) @@ -322,15 +331,18 @@ const getERC20Tokens = async ( // special-cases for wrappers with 0 supply const wcUSDCv3Address = networkConfig[chainId].tokens.wcUSDCv3!.toLowerCase() + const wcUSDCv3AddressOld = '0xfBD1a538f5707C0D67a16ca4e3Fc711B80BD931A'.toLowerCase() const aUSDCv3Address = networkConfig[chainId].tokens.saEthUSDC!.toLowerCase() + const aUSDCv3AddressOld = '0x093cB4f405924a0C468b43209d5E466F1dd0aC7d'.toLowerCase() const aPyUSDv3Address = networkConfig[chainId].tokens.saEthPyUSD!.toLowerCase() - const stkcvxeUSDFRAXBPAddress = '0x8e33D5aC344f9F2fc1f2670D45194C280d4fBcF1'.toLowerCase() + const aPyUSDv3AddressOld = '0xe176A5ebFB873D5b3cf1909d0EdaE4FE095F5bc7'.toLowerCase() + const stkcvxeUSDFRAXBPAddress = '0x81697e25DFf8564d9E0bC6D27edb40006b34ea2A'.toLowerCase() + const stkcvxeUSDFRAXBPAddressOld = '0x8e33D5aC344f9F2fc1f2670D45194C280d4fBcF1'.toLowerCase() - if (tokenAddress.toLowerCase() == wcUSDCv3Address) { - const wcUSDCv3 = await hre.ethers.getContractAt( - 'CusdcV3Wrapper', - wcUSDCv3Address - ) + const tokAddress = tokenAddress.toLowerCase() + + if (tokAddress == wcUSDCv3Address || tokAddress == wcUSDCv3AddressOld) { + const wcUSDCv3 = await hre.ethers.getContractAt('CusdcV3Wrapper', tokAddress) await whileImpersonating( hre, whales[networkConfig['1'].tokens.cUSDCv3!.toLowerCase()], @@ -346,11 +358,10 @@ const getERC20Tokens = async ( await wcUSDCv3.connect(whaleSigner).transfer(recipient, bal) } ) - } else if (tokenAddress.toLowerCase() == aUSDCv3Address) { - const saEthUSDC = await hre.ethers.getContractAt( - 'IStaticATokenV3LM', - aUSDCv3Address - ) + } else if (tokAddress == aUSDCv3Address || tokAddress == aUSDCv3AddressOld) { + console.log('saEthUSDC') + const saEthUSDC = await hre.ethers.getContractAt('IStaticATokenV3LM', tokAddress) + console.log('impersonating') await whileImpersonating( hre, whales[networkConfig['1'].tokens.USDC!.toLowerCase()], @@ -359,13 +370,11 @@ const getERC20Tokens = async ( 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 + console.log('after') } ) - } else if (tokenAddress.toLowerCase() == aPyUSDv3Address) { - const saEthPyUSD = await hre.ethers.getContractAt( - 'IStaticATokenV3LM', - aPyUSDv3Address - ) + } else if (tokAddress == aPyUSDv3Address || tokAddress == aPyUSDv3AddressOld) { + const saEthPyUSD = await hre.ethers.getContractAt('IStaticATokenV3LM', tokAddress) await whileImpersonating( hre, whales[networkConfig['1'].tokens.pyUSD!.toLowerCase()], @@ -376,12 +385,9 @@ const getERC20Tokens = async ( await token.connect(whaleSigner).transfer(recipient, amount) // saEthPyUSD transfer } ) - } else if (tokenAddress.toLowerCase() == stkcvxeUSDFRAXBPAddress) { - const stkcvxeUSDFRAXBP = await hre.ethers.getContractAt( - 'ConvexStakingWrapper', - stkcvxeUSDFRAXBPAddress - ) - + } else if (tokAddress == stkcvxeUSDFRAXBPAddress || tokAddress == stkcvxeUSDFRAXBPAddressOld) { + const stkcvxeUSDFRAXBP = await hre.ethers.getContractAt('ConvexStakingWrapper', tokAddress) + const lpTokenAddr = '0xaeda92e6a3b1028edc139a4ae56ec881f3064d4f'.toLowerCase() await whileImpersonating(hre, whales[lpTokenAddr], async (whaleSigner) => { diff --git a/utils/subgraph.ts b/utils/subgraph.ts index d057660ff..ecf68efda 100644 --- a/utils/subgraph.ts +++ b/utils/subgraph.ts @@ -29,6 +29,9 @@ export const getDelegates = async (governance: string): Promise> } export interface Proposal { + rtoken?: string + governor?: string + timelock?: string targets: Array values: Array calldatas: Array