From 1cbdba4aa3c690dfc93bef14bc2985012a6a2af6 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 17 Oct 2024 02:42:54 +0530 Subject: [PATCH 01/13] [WIP] Add Registry Setup --- common/registries.ts | 31 ++++++ contracts/facade/DeployerRegistry.sol | 2 - contracts/interfaces/IDeployer.sol | 14 ++- contracts/p0/Deployer.sol | 5 +- contracts/p1/Deployer.sol | 22 ++++- .../plugins/mocks/upgrades/DeployerV2.sol | 5 +- contracts/plugins/trading/GnosisTrade.sol | 2 +- scripts/deploy.ts | 13 +++ scripts/deployment/common.ts | 9 +- .../phase0-dao/0_setup_registries.ts | 97 +++++++++++++++++++ .../phase1-core/5_deploy_deployer.ts | 14 ++- test/Deployer.test.ts | 1 - test/DeployerRegistry.test.ts | 1 - 13 files changed, 194 insertions(+), 22 deletions(-) create mode 100644 common/registries.ts create mode 100644 scripts/deployment/phase0-dao/0_setup_registries.ts diff --git a/common/registries.ts b/common/registries.ts new file mode 100644 index 0000000000..2ea5e63545 --- /dev/null +++ b/common/registries.ts @@ -0,0 +1,31 @@ +interface IRegistries { + roleRegistry: string + versionRegistry: string + assetPluginRegistry: string + daoFeeRegistry: string +} + +interface IRegistryControl { + owner: string + feeRecipient: string +} + +export interface RegistryChainRecord { + registries: IRegistries + registryControl: IRegistryControl +} + +export const registryConfig: Record = { + '1': { + registryControl: { + owner: '0x0000000000000000000000000000000000000123', + feeRecipient: '0x0000000000000000000000000000000000000123', + }, + registries: { + roleRegistry: '', + versionRegistry: '', + assetPluginRegistry: '', + daoFeeRegistry: '', + }, + }, +} diff --git a/contracts/facade/DeployerRegistry.sol b/contracts/facade/DeployerRegistry.sol index b8f868e75a..b344682299 100644 --- a/contracts/facade/DeployerRegistry.sol +++ b/contracts/facade/DeployerRegistry.sol @@ -10,8 +10,6 @@ import "../interfaces/IDeployerRegistry.sol"; * @dev Does not allow overwriting without deregistration */ contract DeployerRegistry is IDeployerRegistry, Ownable { - string public constant ENS = "reserveprotocol.eth"; - mapping(string => IDeployer) public deployments; IDeployer public override latestDeployment; diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 04246ccaa2..e573ba75bb 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -12,6 +12,10 @@ import "./IStRSR.sol"; import "./ITrade.sol"; import "./IVersioned.sol"; +import "../registry/VersionRegistry.sol"; +import "../registry/AssetPluginRegistry.sol"; +import "../registry/DAOFeeRegistry.sol"; + /** * @title DeploymentParams * @notice The set of protocol params needed to configure a new system deployment. @@ -92,7 +96,11 @@ interface IDeployer is IVersioned { /// @param rTokenAsset The address of the RTokenAsset event RTokenAssetCreated(IRToken indexed rToken, IAsset rTokenAsset); - // + struct Registries { + VersionRegistry versionRegistry; + AssetPluginRegistry assetPluginRegistry; + DAOFeeRegistry daoFeeRegistry; + } /// Deploys an instance of the entire system /// @param name The name of the RToken to deploy @@ -117,10 +125,6 @@ interface IDeployer is IVersioned { } interface TestIDeployer is IDeployer { - /// A top-level ENS domain that should always point to the latest Deployer instance - // solhint-disable-next-line func-name-mixedcase - function ENS() external view returns (string memory); - function rsr() external view returns (IERC20Metadata); function rsrAsset() external view returns (IAsset); diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index f2445d763c..4137b95148 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -27,8 +27,6 @@ import "../mixins/Versioned.sol"; * @notice The factory contract that deploys the entire P0 system. */ contract DeployerP0 is IDeployer, Versioned { - string public constant ENS = "reserveprotocol.eth"; - IERC20Metadata public immutable rsr; IGnosis public immutable gnosis; IAsset public immutable rsrAsset; @@ -36,7 +34,8 @@ contract DeployerP0 is IDeployer, Versioned { constructor( IERC20Metadata rsr_, IGnosis gnosis_, - IAsset rsrAsset_ + IAsset rsrAsset_, + Registries memory // ignored ) { require( address(rsr_) != address(0) && diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 927d8a8a66..1c7f549ad8 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -29,20 +29,21 @@ import "../plugins/trading/GnosisTrade.sol"; contract DeployerP1 is IDeployer, Versioned { using Clones for address; - string public constant ENS = "reserveprotocol.eth"; - IERC20Metadata public immutable rsr; IAsset public immutable rsrAsset; // Implementation contracts for Upgradeability Implementations private _implementations; + Registries public registries; + // checks: every address in the input is nonzero // effects: post, all contract-state values are set constructor( IERC20Metadata rsr_, IAsset rsrAsset_, - Implementations memory implementations_ + Implementations memory implementations_, + Registries memory registries_ ) { require( address(rsr_) != address(0) && @@ -63,6 +64,15 @@ contract DeployerP1 is IDeployer, Versioned { "invalid address" ); + require( + address(registries_.versionRegistry) != address(0) && + address(registries_.assetPluginRegistry) != address(0) && + address(registries_.daoFeeRegistry) != address(0), + "invalid registry address" + ); + + registries = registries_; + rsr = rsr_; rsrAsset = rsrAsset_; _implementations = implementations_; @@ -251,11 +261,17 @@ contract DeployerP1 is IDeployer, Versioned { // Init Asset Registry components.assetRegistry.init(main, assets); + // Assign DAO Registries + main.setVersionRegistry(registries.versionRegistry); + main.setAssetPluginRegistry(registries.assetPluginRegistry); + main.setDAOFeeRegistry(registries.daoFeeRegistry); + // Transfer Ownership main.grantRole(OWNER, owner); main.renounceRole(OWNER, address(this)); emit RTokenCreated(main, components.rToken, components.stRSR, owner, version()); + return (address(components.rToken)); } diff --git a/contracts/plugins/mocks/upgrades/DeployerV2.sol b/contracts/plugins/mocks/upgrades/DeployerV2.sol index 4d6881bab2..eb76ed4d03 100644 --- a/contracts/plugins/mocks/upgrades/DeployerV2.sol +++ b/contracts/plugins/mocks/upgrades/DeployerV2.sol @@ -10,8 +10,9 @@ contract DeployerP1V2 is DeployerP1 { constructor( IERC20Metadata rsr_, IAsset rsrAsset_, - Implementations memory implementations_ - ) DeployerP1(rsr_, rsrAsset_, implementations_) {} + Implementations memory implementations_, + Registries memory registries_ + ) DeployerP1(rsr_, rsrAsset_, implementations_, registries_) {} function setNewValue(uint256 newValue_) external { newValue = newValue_; diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index 2cc4f7501c..3805d040b2 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -41,7 +41,7 @@ contract GnosisTrade is ITrade, Versioned { // ==== The rest of contract state is all parameters that are immutable after init() // == Metadata - IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for testing compatibility + IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for testing/storage compatibility uint256 public auctionId; // The Gnosis Auction ID returned by gnosis.initiateAuction() IBroker public broker; // The Broker that cloned this contract into existence diff --git a/scripts/deploy.ts b/scripts/deploy.ts index a1347953a3..98ac3f52a6 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -3,6 +3,7 @@ import hre from 'hardhat' import { getChainId } from '../common/blockchain-utils' import { arbitrumL2Chains, baseL2Chains, networkConfig } from '../common/configuration' import { sh } from './deployment/utils' +import { registryConfig } from '#/common/registries' async function main() { const [deployer] = await hre.ethers.getSigners() @@ -25,6 +26,18 @@ async function main() { // Part 1/3 of the *overall* deployment process: Deploy all contracts // See `confirm.ts` for part 2 + // Phase 0 -- Registries + // Phase 0 must be run manually, and is a one time setup for the DAO. + const rConfig = registryConfig[chainId] + + if (!rConfig) { + throw new Error(`Missing registry configuration for ${hre.network.name}`) + } + + if (Object.values(rConfig.registries).includes('')) { + throw new Error(`Missing registry configuration for ${hre.network.name}, please run phase0`) + } + // Phase 1 -- Implementations const scripts = [ 'phase1-core/0_setup_deployments.ts', diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index a1f3393fd5..0dfe37e4bf 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -49,15 +49,18 @@ const pathToFolder = './scripts/addresses/' const tempFileSuffix = '-tmp-deployments.json' const tempAssetCollFileSuffix = '-tmp-assets-collateral.json' -export const getDeploymentFilename = (chainId: number, version?: string): string => { +export const getDeploymentFilename = (chainId: number | string, version?: string): string => { return `${pathToFolder}${version ? `/${version}/` : ''}${chainId}${tempFileSuffix}` } -export const getAssetCollDeploymentFilename = (chainId: number, version?: string): string => { +export const getAssetCollDeploymentFilename = ( + chainId: number | string, + version?: string +): string => { return `${pathToFolder}${version ? `/${version}/` : ''}${chainId}${tempAssetCollFileSuffix}` } -export const getRTokenDeploymentFilename = (chainId: number, name: string): string => { +export const getRTokenDeploymentFilename = (chainId: number | string, name: string): string => { return `${pathToFolder}${chainId}-${name}${tempFileSuffix}` } diff --git a/scripts/deployment/phase0-dao/0_setup_registries.ts b/scripts/deployment/phase0-dao/0_setup_registries.ts new file mode 100644 index 0000000000..4f0a127dc9 --- /dev/null +++ b/scripts/deployment/phase0-dao/0_setup_registries.ts @@ -0,0 +1,97 @@ +import hre, { ethers } from 'hardhat' + +import { getChainId } from '../../../common/blockchain-utils' +import { registryConfig } from '#/common/registries' + +async function main() { + // ==== Read Configuration ==== + const [burner] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log( + `Deploying Registries to network ${hre.network.name} (${chainId}) with burner account: ${burner.address}` + ) + + const rConfig = registryConfig[chainId] + + if (!rConfig) { + throw new Error(`Missing registry configuration for ${hre.network.name}`) + } + + if (rConfig.registryControl.owner == '') { + throw new Error(`Missing registry owner configuration for ${hre.network.name}`) + } + + /** + * Deploy Registries + */ + if (rConfig.registries.roleRegistry == '') { + console.log('Deploying Role Registry...') + + const RoleRegistryFactory = await ethers.getContractFactory('RoleRegistry') + const roleRegistry = await RoleRegistryFactory.connect(burner).deploy() + + await roleRegistry.deployed() + + await roleRegistry + .grantRole(await roleRegistry.DEFAULT_ADMIN_ROLE(), rConfig.registryControl.owner) + .then((e) => e.wait()) + + rConfig.registries.roleRegistry = roleRegistry.address + } + + if (rConfig.registries.versionRegistry == '') { + console.log('Deploying Version Registry...') + + const VersionRegistryFactory = await ethers.getContractFactory('VersionRegistry') + const versionRegistry = await VersionRegistryFactory.connect(burner).deploy( + rConfig.registries.roleRegistry + ) + + await versionRegistry.deployed() + + rConfig.registries.versionRegistry = versionRegistry.address + } + + if (rConfig.registries.assetPluginRegistry == '') { + console.log('Deploying Asset Plugin Registry...') + + const AssetPluginRegistryFactory = await ethers.getContractFactory('AssetPluginRegistry') + const assetPluginRegistry = await AssetPluginRegistryFactory.connect(burner).deploy( + rConfig.registries.versionRegistry + ) + + await assetPluginRegistry.deployed() + + rConfig.registries.assetPluginRegistry = assetPluginRegistry.address + } + + if (rConfig.registries.daoFeeRegistry == '') { + console.log('Deploying DAO Fee Registry...') + + const DaoFeeRegistryFactory = await ethers.getContractFactory('DAOFeeRegistry') + const daoFeeRegistry = await DaoFeeRegistryFactory.connect(burner).deploy( + rConfig.registries.roleRegistry, + rConfig.registryControl.feeRecipient + ) + + await daoFeeRegistry.deployed() + + rConfig.registries.daoFeeRegistry = daoFeeRegistry.address + } + + console.log('!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!') + console.log('Update the registry configuration in *common/registries.ts*') + console.log('You must do this before continuing to the next phase') + console.log('This script does not setup any allowlists, the owner must do this') + console.log('You must also either renounce ownership or revoke role from the deployer') // TODO: Can automate this? + console.log('Chain ID:', chainId) + console.dir(rConfig, { depth: Infinity }) + console.log('!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!') +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase1-core/5_deploy_deployer.ts b/scripts/deployment/phase1-core/5_deploy_deployer.ts index 640c9d7b24..bcea8af961 100644 --- a/scripts/deployment/phase1-core/5_deploy_deployer.ts +++ b/scripts/deployment/phase1-core/5_deploy_deployer.ts @@ -6,6 +6,7 @@ import { networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' import { validateImplementations } from '../utils' import { DeployerP1 } from '../../../typechain' +import { registryConfig } from '#/common/registries' let deployer: DeployerP1 @@ -35,13 +36,24 @@ async function main() { throw new Error(`Facade contract not found in network ${hre.network.name}`) } + const rConfig = registryConfig[chainId] + + if (!rConfig) { + throw new Error(`Missing registry configuration for ${hre.network.name}`) + } + + if (Object.values(rConfig.registries).includes('')) { + throw new Error(`Missing registry configuration for ${hre.network.name}, please run phase0`) + } + // ******************** Deploy Deployer ****************************************/ const DeployerFactory = await ethers.getContractFactory('DeployerP1') deployer = ( await DeployerFactory.connect(burner).deploy( deployments.prerequisites.RSR, deployments.rsrAsset, - deployments.implementations + deployments.implementations, + rConfig.registries ) ) await deployer.deployed() diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index e75ca59e7b..ebcf4cf162 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -210,7 +210,6 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { describe('Deployment', () => { it('Should setup values correctly', async () => { - expect(await deployer.ENS()).to.equal('reserveprotocol.eth') expect(await deployer.rsr()).to.equal(rsr.address) expect(await deployer.rsrAsset()).to.equal(rsrAsset.address) }) diff --git a/test/DeployerRegistry.test.ts b/test/DeployerRegistry.test.ts index a5e1a9b354..8abfeb485d 100644 --- a/test/DeployerRegistry.test.ts +++ b/test/DeployerRegistry.test.ts @@ -32,7 +32,6 @@ describe(`DeployerRegistry contract #fast`, () => { describe('Deployment', () => { it('Should deploy registry correctly', async () => { expect(await deployerRegistry.owner()).to.equal(owner.address) - expect(await deployerRegistry.ENS()).to.equal('reserveprotocol.eth') expect(await deployerRegistry.latestDeployment()).to.equal(ZERO_ADDRESS) expect(await deployerRegistry.deployments('1.0.0')).to.equal(ZERO_ADDRESS) }) From 1db94567f91dc3c4e30901ab18b961d014a86216 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 17 Oct 2024 02:46:51 +0530 Subject: [PATCH 02/13] Lint comment --- contracts/plugins/trading/GnosisTrade.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index 3805d040b2..95d7006c71 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -41,7 +41,7 @@ contract GnosisTrade is ITrade, Versioned { // ==== The rest of contract state is all parameters that are immutable after init() // == Metadata - IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for testing/storage compatibility + IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for storage compat uint256 public auctionId; // The Gnosis Auction ID returned by gnosis.initiateAuction() IBroker public broker; // The Broker that cloned this contract into existence From 0bb1be9ffb85a5cebe39e3ba23f1ec5802dad4f2 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 17 Oct 2024 03:14:01 +0530 Subject: [PATCH 03/13] Type sync without fixes --- common/registries.ts | 2 ++ contracts/interfaces/IDeployer.sol | 8 ----- contracts/spells/3_4_0.sol | 7 +++-- test/Deployer.test.ts | 10 +++---- test/DeployerRegistry.test.ts | 4 +-- test/FacadeWrite.test.ts | 4 +-- test/Main.test.ts | 4 +-- test/fixtures.ts | 20 ++++++------- test/integration/Deployment.test.ts | 4 +-- test/integration/fixtures.ts | 14 ++++----- .../aave/ATokenFiatCollateral.test.ts | 4 +-- .../individual-collateral/collateralTests.ts | 4 +-- .../compoundv2/CTokenFiatCollateral.test.ts | 4 +-- .../curve/collateralTests.ts | 4 +-- .../plugins/individual-collateral/fixtures.ts | 29 +++++++++---------- test/registries/AssetPluginRegistry.test.ts | 4 +-- test/registries/VersionRegistry.test.ts | 4 +-- 17 files changed, 58 insertions(+), 72 deletions(-) diff --git a/common/registries.ts b/common/registries.ts index 2ea5e63545..f8596e53fa 100644 --- a/common/registries.ts +++ b/common/registries.ts @@ -29,3 +29,5 @@ export const registryConfig: Record = { }, }, } + +registryConfig['31337'] = registryConfig['1'] diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index e573ba75bb..6bee171ee5 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -123,11 +123,3 @@ interface IDeployer is IVersioned { function implementations() external view returns (Implementations memory); } - -interface TestIDeployer is IDeployer { - function rsr() external view returns (IERC20Metadata); - - function rsrAsset() external view returns (IAsset); - - function implementations() external view returns (Implementations memory); -} diff --git a/contracts/spells/3_4_0.sol b/contracts/spells/3_4_0.sol index 6565fee965..6578d65f7c 100644 --- a/contracts/spells/3_4_0.sol +++ b/contracts/spells/3_4_0.sol @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/governance/IGovernor.sol"; import "@openzeppelin/contracts/governance/TimelockController.sol"; import "../interfaces/IDeployer.sol"; import "../interfaces/IMain.sol"; +import "../contracts/p1/Deployer.sol"; // interface avoids needing to know about P1 contracts interface ICachedComponent { @@ -120,7 +121,7 @@ contract Upgrade3_4_0 { using EnumerableSet for EnumerableSet.Bytes32Set; - TestIDeployer public deployer; + DeployerP1 public deployer; struct NewGovernance { IGovernor anastasius; @@ -163,7 +164,7 @@ contract Upgrade3_4_0 { // Setup `assets` array if (_mainnet) { // Setup `deployer` - deployer = TestIDeployer(0x2204EC97D31E2C9eE62eaD9e6E2d5F7712D3f1bF); + deployer = DeployerP1(0x2204EC97D31E2C9eE62eaD9e6E2d5F7712D3f1bF); // Setup `newGovs` // eUSD @@ -244,7 +245,7 @@ contract Upgrade3_4_0 { } } else { // Setup `deployer` - deployer = TestIDeployer(0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A); + deployer = DeployerP1(0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A); // Setup `newGovs` // hyUSD (base) diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index ebcf4cf162..04b85e473f 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -15,7 +15,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - TestIDeployer, + DeployerP1, TestIDistributor, TestIFacade, TestIFurnace, @@ -32,7 +32,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { let mock: SignerWithAddress // Deployer contract - let deployer: TestIDeployer + let deployer: DeployerP1 // Config let config: IConfig @@ -69,7 +69,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { gnosis: string, rsrAsset: string, implementations?: IImplementations - ): Promise => { + ): Promise => { if (IMPLEMENTATION == Implementation.P0) { const TradingLibFactory: ContractFactory = await ethers.getContractFactory('TradingLibP0') const tradingLib: TradingLibP0 = await TradingLibFactory.deploy() @@ -77,10 +77,10 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { libraries: { TradingLibP0: tradingLib.address }, }) - return await DeployerFactory.deploy(rsr, gnosis, rsrAsset) + return await DeployerFactory.deploy(rsr, gnosis, rsrAsset) } else if (IMPLEMENTATION == Implementation.P1) { const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') - return await DeployerFactory.deploy(rsr, rsrAsset, implementations) + return await DeployerFactory.deploy(rsr, rsrAsset, implementations) } else { throw new Error('PROTO_IMPL must be set to either `0` or `1`') } diff --git a/test/DeployerRegistry.test.ts b/test/DeployerRegistry.test.ts index 8abfeb485d..6ad5c3b42f 100644 --- a/test/DeployerRegistry.test.ts +++ b/test/DeployerRegistry.test.ts @@ -3,7 +3,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' import { ZERO_ADDRESS } from '../common/constants' -import { DeployerRegistry, TestIDeployer } from '../typechain' +import { DeployerRegistry, DeployerP1 } from '../typechain' import { defaultFixture } from './fixtures' describe(`DeployerRegistry contract #fast`, () => { @@ -16,7 +16,7 @@ describe(`DeployerRegistry contract #fast`, () => { let deployerRegistry: DeployerRegistry // Deployer contract - let deployer: TestIDeployer + let deployer: DeployerP1 beforeEach(async () => { ;[owner, mockDeployer1, mockDeployer2, addr1] = await ethers.getSigners() diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index d44bffcbed..dcc6878ddf 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -41,7 +41,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - TestIDeployer, + DeployerP1, TestIDistributor, TestIFacade, TestIFurnace, @@ -97,7 +97,7 @@ describe('FacadeWrite contract', () => { let config: IConfig // Deployer - let deployer: TestIDeployer + let deployer: DeployerP1 // Governor let governor: Governance diff --git a/test/Main.test.ts b/test/Main.test.ts index 3ce2b12c2b..eef4cd17ef 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -54,7 +54,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - TestIDeployer, + DeployerP1, TestIDistributor, TestIFacade, TestIFurnace, @@ -102,7 +102,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let other: SignerWithAddress // Deployer contract - let deployer: TestIDeployer + let deployer: DeployerP1 // Assets let collateral: Collateral[] diff --git a/test/fixtures.ts b/test/fixtures.ts index cf2dc04d66..776f88fff3 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -32,7 +32,6 @@ import { CTokenSelfReferentialCollateral, CTokenMock, ERC20Mock, - DeployerP0, DeployerP1, DutchTrade, ReadFacet, @@ -59,7 +58,6 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - TestIDeployer, TestIDistributor, TestIFacade, TestIFurnace, @@ -398,7 +396,7 @@ type RSRAndCompAaveAndCollateralAndModuleFixture = RSRFixture & export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { config: IConfig dist: IRevenueShare - deployer: TestIDeployer + deployer: DeployerP1 main: TestIMain assetRegistry: IAssetRegistry backingManager: TestIBackingManager @@ -526,12 +524,14 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = await rsrAsset.refresh() // Create Deployer - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { + const DeployerFactory = await ethers.getContractFactory('DeployerP0', { libraries: { TradingLibP0: tradingLib.address }, }) - let deployer: TestIDeployer = ( - await DeployerFactory.deploy(rsr.address, gnosisAddr, rsrAsset.address) - ) + let deployer = (await DeployerFactory.deploy( + rsr.address, + gnosisAddr, + rsrAsset.address + )) as unknown as DeployerP1 if (IMPLEMENTATION == Implementation.P1) { // Deploy implementations @@ -614,10 +614,8 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = }, } - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') - deployer = ( - await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) - ) + const DeployerFactory = await ethers.getContractFactory('DeployerP1') + deployer = await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) } // Deploy actual contracts diff --git a/test/integration/Deployment.test.ts b/test/integration/Deployment.test.ts index 46c5346024..1acd22eb79 100644 --- a/test/integration/Deployment.test.ts +++ b/test/integration/Deployment.test.ts @@ -10,7 +10,7 @@ import { IConfig, IRTokenConfig, IRTokenSetup, networkConfig } from '../../commo import { ZERO_ADDRESS } from '../../common/constants' import { expectInIndirectReceipt } from '../../common/events' import { bn, fp } from '../../common/numbers' -import { FacadeWrite, FiatCollateral, TestIDeployer, TestIMain } from '../../typechain' +import { FacadeWrite, FiatCollateral, DeployerP1, TestIMain } from '../../typechain' import { useEnv } from '#/utils/env' const describeFork = useEnv('FORK') ? describe : describe.skip @@ -26,7 +26,7 @@ describeFork(`Deployment - Integration - Mainnet Forking P${IMPLEMENTATION}`, fu // Contracts to retrieve after deploy let main: TestIMain - let deployer: TestIDeployer + let deployer: DeployerP1 let facadeWrite: FacadeWrite let config: IConfig diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index d41f5f2632..02ab5cdb98 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -55,7 +55,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - TestIDeployer, + DeployerP1, TestIDistributor, TestIFacade, TestIFurnace, @@ -585,7 +585,7 @@ type RSRAndCompAaveAndCollateralAndModuleFixture = RSRFixture & export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { config: IConfig dist: IRevenueShare - deployer: TestIDeployer + deployer: DeployerP1 main: TestIMain assetRegistry: IAssetRegistry backingManager: TestIBackingManager @@ -758,9 +758,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { libraries: { TradingLibP0: tradingLib.address }, }) - let deployer: TestIDeployer = ( - await DeployerFactory.deploy(rsr.address, easyAuction.address, rsrAsset.address) - ) + let deployer = await DeployerFactory.deploy(rsr.address, easyAuction.address, rsrAsset.address) if (IMPLEMENTATION == Implementation.P1) { // Deploy implementations @@ -833,10 +831,8 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = }, } - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') - deployer = ( - await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) - ) + const DeployerFactory = await ethers.getContractFactory('DeployerP1') + deployer = await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) } // Deploy actual contracts diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index ad632a8d07..7b142047b1 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -61,7 +61,7 @@ import { MockV3Aggregator, RTokenAsset, TestIBackingManager, - TestIDeployer, + DeployerP1, TestIFacade, TestIMain, TestIRToken, @@ -123,7 +123,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let basketHandler: IBasketHandler let chainlinkFeed: AggregatorInterface - let deployer: TestIDeployer + let deployer: DeployerP1 let facade: TestIFacade let facadeTest: FacadeTest let facadeWrite: FacadeWrite diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 66119a1fe9..4c9607df02 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -51,7 +51,7 @@ import { TestIBackingManager, TestIBasketHandler, TestICollateral, - TestIDeployer, + DeployerP1, TestIMain, TestIRevenueTrader, TestIRToken, @@ -657,7 +657,7 @@ export default function fn( let rsrTrader: TestIRevenueTrader let rsr: ERC20Mock - let deployer: TestIDeployer + let deployer: DeployerP1 let facadeWrite: FacadeWrite let govParams: IGovParams let govRoles: IGovRoles diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 3e282cdbff..ee11b27cc9 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -64,7 +64,7 @@ import { RTokenAsset, TestIBackingManager, TestIBasketHandler, - TestIDeployer, + DeployerP1, TestIFacade, TestIMain, TestIRToken, @@ -121,7 +121,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let backingManager: TestIBackingManager let basketHandler: TestIBasketHandler - let deployer: TestIDeployer + let deployer: DeployerP1 let facade: TestIFacade let facadeTest: FacadeTest let facadeWrite: FacadeWrite diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 0ae2e202be..a54ff76730 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -53,7 +53,7 @@ import { TestIBackingManager, TestIBasketHandler, TestICollateral, - TestIDeployer, + DeployerP1, TestIMain, TestIRevenueTrader, TestIRToken, @@ -820,7 +820,7 @@ export default function fn( let rsrTrader: TestIRevenueTrader let rsr: ERC20Mock - let deployer: TestIDeployer + let deployer: DeployerP1 let facadeWrite: FacadeWrite let govParams: IGovParams let govRoles: IGovRoles diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 454fdc36b2..1b9c9018c9 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -28,7 +28,6 @@ import { RevenueTraderP1, RTokenP1, StRSRP1Votes, - TestIDeployer, TestIFacade, RecollateralizationLibP1, } from '../../../typechain' @@ -45,10 +44,8 @@ interface RSRFixture { rsr: ERC20Mock } -async function rsrFixture(chainId: number): Promise { - const rsr: ERC20Mock = ( - await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.RSR || '') - ) +async function rsrFixture(chainId: string): Promise { + const rsr = await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.RSR || '') return { rsr } } @@ -66,7 +63,7 @@ type RSRAndModuleFixture = RSRFixture & ModuleFixture export interface DefaultFixture extends RSRAndModuleFixture { salt: string - deployer: TestIDeployer + deployer: DeployerP1 rsrAsset: Asset facade: TestIFacade facadeTest: FacadeTest @@ -77,8 +74,8 @@ export interface DefaultFixture extends RSRAndModuleFixture { export const getDefaultFixture = async function (salt: string) { const defaultFixture: Fixture = async function (): Promise { let chainId = await getChainId(hre) - if (useEnv('FORK_NETWORK').toLowerCase() == 'base') chainId = 8453 - if (useEnv('FORK_NETWORK').toLowerCase() == 'arbitrum') chainId = 42161 + if (useEnv('FORK_NETWORK').toLowerCase() == 'base') chainId = '8453' + if (useEnv('FORK_NETWORK').toLowerCase() == 'arbitrum') chainId = '42161' const { rsr } = await rsrFixture(chainId) const { gnosis } = await gnosisFixture() if (!networkConfig[chainId]) { @@ -136,12 +133,14 @@ export const getDefaultFixture = async function (salt: string) { ) // Create Deployer - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { + const DeployerFactory = await ethers.getContractFactory('DeployerP0', { libraries: { TradingLibP0: tradingLib.address }, }) - let deployer: TestIDeployer = ( - await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address) - ) + let deployer = (await DeployerFactory.deploy( + rsr.address, + gnosis.address, + rsrAsset.address + )) as unknown as DeployerP1 if (IMPLEMENTATION == Implementation.P1) { // Deploy implementations @@ -216,10 +215,8 @@ export const getDefaultFixture = async function (salt: string) { stRSR: stRSRImpl.address, }, } - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') - deployer = ( - await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) - ) + const DeployerFactory = await ethers.getContractFactory('DeployerP1') + deployer = await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) } // Deploy Facade diff --git a/test/registries/AssetPluginRegistry.test.ts b/test/registries/AssetPluginRegistry.test.ts index 2b3287c550..121fd2904f 100644 --- a/test/registries/AssetPluginRegistry.test.ts +++ b/test/registries/AssetPluginRegistry.test.ts @@ -7,7 +7,7 @@ import { Collateral, Implementation, IMPLEMENTATION, defaultFixture } from '../f import { AssetPluginRegistry, RoleRegistry, - TestIDeployer, + DeployerP1, VersionRegistry, DeployerMock, } from '../../typechain' @@ -24,7 +24,7 @@ describeP1('Asset Plugin Registry', () => { let basket: Collateral[] // Deployers - let deployer: TestIDeployer + let deployer: DeployerP1 let deployerMockV1: DeployerMock let deployerMockV2: DeployerMock diff --git a/test/registries/VersionRegistry.test.ts b/test/registries/VersionRegistry.test.ts index 789a3a3c1b..7618504ff8 100644 --- a/test/registries/VersionRegistry.test.ts +++ b/test/registries/VersionRegistry.test.ts @@ -4,7 +4,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ONE_ADDRESS, ZERO_ADDRESS, ZERO_BYTES } from '#/common/constants' import { IImplementations } from '#/common/configuration' -import { DeployerMock, RoleRegistry, TestIDeployer, VersionRegistry } from '../../typechain' +import { DeployerMock, RoleRegistry, DeployerP1, VersionRegistry } from '../../typechain' import { Implementation, IMPLEMENTATION, defaultFixture } from '../fixtures' const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip @@ -12,7 +12,7 @@ const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.ski describeP1('Version Registry', () => { let roleRegistry: RoleRegistry let versionRegistry: VersionRegistry - let deployer: TestIDeployer + let deployer: DeployerP1 let deployerMockV1: DeployerMock let deployerMockV2: DeployerMock let owner: SignerWithAddress From 1fe2e4ba1bc4d76612ab00e39f68bd6be0c74b61 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 17 Oct 2024 03:28:39 +0530 Subject: [PATCH 04/13] mix --- contracts/spells/3_4_0.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/spells/3_4_0.sol b/contracts/spells/3_4_0.sol index 6578d65f7c..8e28cb3da5 100644 --- a/contracts/spells/3_4_0.sol +++ b/contracts/spells/3_4_0.sol @@ -2,12 +2,11 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -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 "../contracts/p1/Deployer.sol"; +import "../p1/Deployer.sol"; // interface avoids needing to know about P1 contracts interface ICachedComponent { From 7a47f19ffa7bc7866377f956e4a178c74651241c Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 18 Oct 2024 04:19:54 +0530 Subject: [PATCH 05/13] A different way --- contracts/facade/FacadeWrite.sol | 13 ++++--- contracts/interfaces/IDeployer.sol | 4 +- contracts/interfaces/IFacadeWrite.sol | 8 ++-- contracts/p0/Deployer.sol | 6 +-- contracts/p1/Deployer.sol | 29 ++++++-------- .../plugins/mocks/upgrades/DeployerV2.sol | 5 +-- scripts/deploy.ts | 11 +----- .../phase0-dao/0_setup_registries.ts | 4 ++ .../phase1-core/5_deploy_deployer.ts | 14 +------ test/Deployer.test.ts | 38 ++++++++++++++++--- test/Main.test.ts | 19 ++++++++-- test/fixtures.ts | 6 ++- test/integration/Deployment.test.ts | 8 +++- test/integration/fixtures.ts | 15 ++++++-- .../aave/ATokenFiatCollateral.test.ts | 9 ++++- .../individual-collateral/collateralTests.ts | 25 +++++++++--- .../compoundv2/CTokenFiatCollateral.test.ts | 10 +++-- .../plugins/individual-collateral/fixtures.ts | 2 +- 18 files changed, 143 insertions(+), 83 deletions(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 0f9b3fbcc5..965309f268 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "../interfaces/IFacadeWrite.sol"; +import "../interfaces/IDeployer.sol"; import "./lib/FacadeWriteLib.sol"; /** @@ -21,10 +22,11 @@ contract FacadeWrite is IFacadeWrite { } /// Step 1 - function deployRToken(ConfigurationParams calldata config, SetupParams calldata setup) - external - returns (address) - { + function deployRToken( + ConfigurationParams calldata config, + SetupParams calldata setup, + IDeployer.Registries calldata registries + ) external returns (address) { // Perform validations require(setup.primaryBasket.length != 0, "no collateral"); require(setup.primaryBasket.length == setup.weights.length, "invalid length"); @@ -51,7 +53,8 @@ contract FacadeWrite is IFacadeWrite { config.symbol, config.mandate, address(this), // set as owner - config.params + config.params, + registries ) ); diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 6bee171ee5..28a8ef26dc 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -108,13 +108,15 @@ interface IDeployer is IVersioned { /// @param mandate An IPFS link or direct string; describes what the RToken _should be_ /// @param owner The address that should own the entire system, hopefully a governance contract /// @param params Deployment params + /// @param registries Registries list; can be 0 to unset /// @return The address of the newly deployed Main instance. function deploy( string calldata name, string calldata symbol, string calldata mandate, address owner, - DeploymentParams calldata params + DeploymentParams calldata params, + Registries calldata registries ) external returns (address); /// Deploys a new RTokenAsset instance. Not needed during normal deployment flow diff --git a/contracts/interfaces/IFacadeWrite.sol b/contracts/interfaces/IFacadeWrite.sol index 837c68a106..a8ac83daac 100644 --- a/contracts/interfaces/IFacadeWrite.sol +++ b/contracts/interfaces/IFacadeWrite.sol @@ -91,9 +91,11 @@ interface IFacadeWrite { ); /// Deploys an instance of an RToken - function deployRToken(ConfigurationParams calldata config, SetupParams calldata setup) - external - returns (address); + function deployRToken( + ConfigurationParams calldata config, + SetupParams calldata setup, + IDeployer.Registries calldata registries + ) external returns (address); /// Sets up governance for an RToken function setupGovernance( diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 4137b95148..bdf8d4167e 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -34,8 +34,7 @@ contract DeployerP0 is IDeployer, Versioned { constructor( IERC20Metadata rsr_, IGnosis gnosis_, - IAsset rsrAsset_, - Registries memory // ignored + IAsset rsrAsset_ ) { require( address(rsr_) != address(0) && @@ -60,7 +59,8 @@ contract DeployerP0 is IDeployer, Versioned { string memory symbol, string calldata mandate, address owner, - DeploymentParams memory params + DeploymentParams memory params, + Registries calldata // ignored ) external returns (address) { require(owner != address(0) && owner != address(this), "invalid owner"); diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 1c7f549ad8..71cd118d12 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -35,15 +35,12 @@ contract DeployerP1 is IDeployer, Versioned { // Implementation contracts for Upgradeability Implementations private _implementations; - Registries public registries; - // checks: every address in the input is nonzero // effects: post, all contract-state values are set constructor( IERC20Metadata rsr_, IAsset rsrAsset_, - Implementations memory implementations_, - Registries memory registries_ + Implementations memory implementations_ ) { require( address(rsr_) != address(0) && @@ -64,15 +61,6 @@ contract DeployerP1 is IDeployer, Versioned { "invalid address" ); - require( - address(registries_.versionRegistry) != address(0) && - address(registries_.assetPluginRegistry) != address(0) && - address(registries_.daoFeeRegistry) != address(0), - "invalid registry address" - ); - - registries = registries_; - rsr = rsr_; rsrAsset = rsrAsset_; _implementations = implementations_; @@ -116,7 +104,8 @@ contract DeployerP1 is IDeployer, Versioned { string memory symbol, string calldata mandate, address owner, - DeploymentParams memory params + DeploymentParams memory params, + Registries calldata registries ) external returns (address) { require(owner != address(0) && owner != address(this), "invalid owner"); @@ -262,9 +251,15 @@ contract DeployerP1 is IDeployer, Versioned { components.assetRegistry.init(main, assets); // Assign DAO Registries - main.setVersionRegistry(registries.versionRegistry); - main.setAssetPluginRegistry(registries.assetPluginRegistry); - main.setDAOFeeRegistry(registries.daoFeeRegistry); + if (address(registries.versionRegistry) != address(0)) { + main.setVersionRegistry(registries.versionRegistry); + } + if (address(registries.assetPluginRegistry) != address(0)) { + main.setAssetPluginRegistry(registries.assetPluginRegistry); + } + if (address(registries.daoFeeRegistry) != address(0)) { + main.setDAOFeeRegistry(registries.daoFeeRegistry); + } // Transfer Ownership main.grantRole(OWNER, owner); diff --git a/contracts/plugins/mocks/upgrades/DeployerV2.sol b/contracts/plugins/mocks/upgrades/DeployerV2.sol index eb76ed4d03..4d6881bab2 100644 --- a/contracts/plugins/mocks/upgrades/DeployerV2.sol +++ b/contracts/plugins/mocks/upgrades/DeployerV2.sol @@ -10,9 +10,8 @@ contract DeployerP1V2 is DeployerP1 { constructor( IERC20Metadata rsr_, IAsset rsrAsset_, - Implementations memory implementations_, - Registries memory registries_ - ) DeployerP1(rsr_, rsrAsset_, implementations_, registries_) {} + Implementations memory implementations_ + ) DeployerP1(rsr_, rsrAsset_, implementations_) {} function setNewValue(uint256 newValue_) external { newValue = newValue_; diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 98ac3f52a6..d8b372af9d 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -3,7 +3,6 @@ import hre from 'hardhat' import { getChainId } from '../common/blockchain-utils' import { arbitrumL2Chains, baseL2Chains, networkConfig } from '../common/configuration' import { sh } from './deployment/utils' -import { registryConfig } from '#/common/registries' async function main() { const [deployer] = await hre.ethers.getSigners() @@ -28,15 +27,7 @@ async function main() { // Phase 0 -- Registries // Phase 0 must be run manually, and is a one time setup for the DAO. - const rConfig = registryConfig[chainId] - - if (!rConfig) { - throw new Error(`Missing registry configuration for ${hre.network.name}`) - } - - if (Object.values(rConfig.registries).includes('')) { - throw new Error(`Missing registry configuration for ${hre.network.name}, please run phase0`) - } + // The deploy scripts do not enforce them. // Phase 1 -- Implementations const scripts = [ diff --git a/scripts/deployment/phase0-dao/0_setup_registries.ts b/scripts/deployment/phase0-dao/0_setup_registries.ts index 4f0a127dc9..372e65f97d 100644 --- a/scripts/deployment/phase0-dao/0_setup_registries.ts +++ b/scripts/deployment/phase0-dao/0_setup_registries.ts @@ -23,6 +23,10 @@ async function main() { throw new Error(`Missing registry owner configuration for ${hre.network.name}`) } + console.log('!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!') + console.log('This will only deploy registries that are not already deployed.') + console.log('!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!') + /** * Deploy Registries */ diff --git a/scripts/deployment/phase1-core/5_deploy_deployer.ts b/scripts/deployment/phase1-core/5_deploy_deployer.ts index bcea8af961..640c9d7b24 100644 --- a/scripts/deployment/phase1-core/5_deploy_deployer.ts +++ b/scripts/deployment/phase1-core/5_deploy_deployer.ts @@ -6,7 +6,6 @@ import { networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' import { validateImplementations } from '../utils' import { DeployerP1 } from '../../../typechain' -import { registryConfig } from '#/common/registries' let deployer: DeployerP1 @@ -36,24 +35,13 @@ async function main() { throw new Error(`Facade contract not found in network ${hre.network.name}`) } - const rConfig = registryConfig[chainId] - - if (!rConfig) { - throw new Error(`Missing registry configuration for ${hre.network.name}`) - } - - if (Object.values(rConfig.registries).includes('')) { - throw new Error(`Missing registry configuration for ${hre.network.name}, please run phase0`) - } - // ******************** Deploy Deployer ****************************************/ const DeployerFactory = await ethers.getContractFactory('DeployerP1') deployer = ( await DeployerFactory.connect(burner).deploy( deployments.prerequisites.RSR, deployments.rsrAsset, - deployments.implementations, - rConfig.registries + deployments.implementations ) ) await deployer.deployed() diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index 04b85e473f..4071edd804 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -254,33 +254,59 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { it('Should emit event', async () => { await expect( - deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config) + deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.emit(deployer, 'RTokenCreated') }) it('Should not allow empty name', async () => { - await expect(deployer.deploy('', 'RTKN', 'mandate', owner.address, config)).to.be.reverted + await expect( + deployer.deploy('', 'RTKN', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) + ).to.be.reverted }) it('Should not allow empty symbol', async () => { await expect( - deployer.deploy('RTKN RToken', '', 'mandate', owner.address, config) + deployer.deploy('RTKN RToken', '', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('symbol empty') }) it('Should not allow empty mandate', async () => { await expect( - deployer.deploy('RTKN RToken', 'RTKN', '', owner.address, config) + deployer.deploy('RTKN RToken', 'RTKN', '', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('mandate empty') }) it('Should not allow invalid owner address', async () => { await expect( - deployer.deploy('RTKN RToken', 'RTKN', 'mandate', ZERO_ADDRESS, config) + deployer.deploy('RTKN RToken', 'RTKN', 'mandate', ZERO_ADDRESS, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('invalid owner') await expect( - deployer.deploy('RTKN RToken', 'RTKN', 'mandate', deployer.address, config) + deployer.deploy('RTKN RToken', 'RTKN', 'mandate', deployer.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('invalid owner') }) diff --git a/test/Main.test.ts b/test/Main.test.ts index eef4cd17ef..d342e0e79e 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -478,7 +478,11 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { invalidDistConfig.dist = { rTokenDist: bn(0), rsrDist: bn(0) } await expect( - deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, invalidDistConfig) + deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, invalidDistConfig, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('totals too low') // Create a new instance of Main @@ -512,7 +516,11 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should emit events on init', async () => { // Deploy new system instance const receipt = await ( - await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config) + await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInReceipt(receipt, 'RTokenCreated').args.main @@ -2869,7 +2877,12 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { 'RTKN (empty basket)', 'mandate (empty basket)', owner.address, - config + config, + { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + } ) ).wait() const mainAddr = expectInReceipt(receipt, 'RTokenCreated').args.main diff --git a/test/fixtures.ts b/test/fixtures.ts index 776f88fff3..e2653c06a9 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -620,7 +620,11 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = // Deploy actual contracts const receipt = await ( - await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config) + await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInReceipt(receipt, 'RTokenCreated').args.main diff --git a/test/integration/Deployment.test.ts b/test/integration/Deployment.test.ts index 1acd22eb79..8069625bbe 100644 --- a/test/integration/Deployment.test.ts +++ b/test/integration/Deployment.test.ts @@ -33,7 +33,7 @@ describeFork(`Deployment - Integration - Mainnet Forking P${IMPLEMENTATION}`, fu let rTokenConfig: IRTokenConfig let rTokenSetup: IRTokenSetup - let chainId: number + let chainId: string describe('Deployment', () => { before(async () => { @@ -87,7 +87,11 @@ describeFork(`Deployment - Integration - Mainnet Forking P${IMPLEMENTATION}`, fu } // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 02ab5cdb98..a25e4da05e 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -55,7 +55,6 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, - DeployerP1, TestIDistributor, TestIFacade, TestIFurnace, @@ -755,7 +754,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = await rsrAsset.refresh() // Create Deployer - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { + const DeployerFactory = await ethers.getContractFactory('DeployerP0', { libraries: { TradingLibP0: tradingLib.address }, }) let deployer = await DeployerFactory.deploy(rsr.address, easyAuction.address, rsrAsset.address) @@ -832,12 +831,20 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = } const DeployerFactory = await ethers.getContractFactory('DeployerP1') - deployer = await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) + deployer = (await DeployerFactory.deploy( + rsr.address, + rsrAsset.address, + implementations + )) as unknown as DeployerP0 } // Deploy actual contracts const receipt = await ( - await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config) + await deployer.deploy('RTKN RToken', 'RTKN', 'mandate', owner.address, config, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInReceipt(receipt, 'RTokenCreated').args.main diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 7b142047b1..8ff2126940 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -159,6 +159,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi }, warmupPeriod: bn('60'), reweightable: false, + enableIssuancePremium: false, } const defaultThreshold = fp('0.01') // 1% @@ -166,7 +167,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let initialBal: BigNumber - let chainId: number + let chainId: string let ATokenFiatCollateralFactory: ContractFactory let MockV3AggregatorFactory: ContractFactory @@ -284,7 +285,11 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() // Get Main diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 4c9607df02..47f89b767f 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -16,7 +16,13 @@ import { } from './fixtures' import { expectInIndirectReceipt } from '../../../common/events' import { whileImpersonating } from '../../utils/impersonation' -import { IGovParams, IGovRoles, IRTokenSetup, networkConfig } from '../../../common/configuration' +import { + IConfig, + IGovParams, + IGovRoles, + IRTokenSetup, + networkConfig, +} from '../../../common/configuration' import { advanceBlocks, advanceTime, @@ -437,6 +443,7 @@ export default function fn( }) it('lotPrice (deprecated) is equal to price()', async () => { + // @ts-expect-error -- this is deprecated but whatever const lotPrice = await collateral.lotPrice() const price = await collateral.price() expect(price.length).to.equal(2) @@ -636,7 +643,7 @@ export default function fn( let owner: SignerWithAddress let addr1: SignerWithAddress - let chainId: number + let chainId: string let defaultFixture: Fixture @@ -662,7 +669,7 @@ export default function fn( let govParams: IGovParams let govRoles: IGovRoles - const config = { + const config: IConfig = { dist: { rTokenDist: bn(0), // 0% RToken rsrDist: bn(10000), // 100% RSR @@ -689,6 +696,7 @@ export default function fn( pctRate: fp('0.05'), // 5% }, reweightable: false, + enableIssuancePremium: false, } interface IntegrationFixture { @@ -709,8 +717,8 @@ export default function fn( before(async () => { defaultFixture = await getDefaultFixture(collateralName) chainId = await getChainId(hre) - if (useEnv('FORK_NETWORK').toLowerCase() === 'base') chainId = 8453 - if (useEnv('FORK_NETWORK').toLowerCase() === 'arbitrum') chainId = 42161 + if (useEnv('FORK_NETWORK').toLowerCase() === 'base') chainId = '8453' + if (useEnv('FORK_NETWORK').toLowerCase() === 'arbitrum') chainId = '42161' if (!networkConfig[chainId]) { throw new Error(`Missing network configuration for ${hre.network.name}`) } @@ -758,7 +766,12 @@ export default function fn( mandate: 'mandate', params: config, }, - rTokenSetup + rTokenSetup, + { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + } ) ).wait() diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index ee11b27cc9..8a671c2c03 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -157,6 +157,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi pctRate: fp('0.05'), // 5% }, reweightable: false, + enableIssuancePremium: false, } const defaultThreshold = fp('0.01') // 1% @@ -164,7 +165,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let initialBal: BigNumber - let chainId: number + let chainId: string let CTokenCollateralFactory: ContractFactory let MockV3AggregatorFactory: ContractFactory @@ -263,7 +264,11 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() // Get Main @@ -917,7 +922,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi 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) diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 1b9c9018c9..6010b77c7a 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -13,7 +13,6 @@ import { BasketHandlerP1, BasketLibP1, BrokerP1, - DeployerP0, DeployerP1, DistributorP1, DutchTrade, @@ -215,6 +214,7 @@ export const getDefaultFixture = async function (salt: string) { stRSR: stRSRImpl.address, }, } + const DeployerFactory = await ethers.getContractFactory('DeployerP1') deployer = await DeployerFactory.deploy(rsr.address, rsrAsset.address, implementations) } From f033805ef23c795de4815c6b2588c3d811b0d31c Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 18 Oct 2024 04:40:20 +0530 Subject: [PATCH 06/13] Another place --- .../curve/collateralTests.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index a54ff76730..c3ed665860 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -32,6 +32,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { useEnv } from '#/utils/env' import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../utils/oracles' import { + IConfig, IGovParams, IGovRoles, IRTokenSetup, @@ -509,6 +510,7 @@ export default function fn( }) it('lotPrice (deprecated) is equal to price()', async () => { + // @ts-expect-error -- deprecated but oh well const lotPrice = await ctx.collateral.lotPrice() const price = await ctx.collateral.price() expect(price.length).to.equal(2) @@ -799,7 +801,7 @@ export default function fn( let owner: SignerWithAddress let addr1: SignerWithAddress - let chainId: number + let chainId: string let defaultFixture: Fixture @@ -825,7 +827,7 @@ export default function fn( let govParams: IGovParams let govRoles: IGovRoles - const config = { + const config: IConfig = { dist: { rTokenDist: bn(0), // 0% RToken rsrDist: bn(10000), // 100% RSR @@ -852,6 +854,7 @@ export default function fn( pctRate: fp('0.05'), // 5% }, reweightable: false, + enableIssuancePremium: false, } interface IntegrationFixture { @@ -919,7 +922,12 @@ export default function fn( mandate: 'mandate', params: config, }, - rTokenSetup + rTokenSetup, + { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + } ) ).wait() @@ -1082,8 +1090,8 @@ export default function fn( ) let chainId = await getChainId(hre) - if (onBase) chainId = 8453 - if (onArbitrum) chainId = 42161 + if (onBase) chainId = '8453' + if (onArbitrum) chainId = '42161' if (target == ethers.utils.formatBytes32String('USD')) { // USD From 5d513e8ac0d254f95c2779b3c741ffc3ec080eb9 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 18 Oct 2024 04:41:56 +0530 Subject: [PATCH 07/13] more places --- .../phase3-rtoken/1_deploy_rtoken.ts | 9 ++- test/FacadeWrite.test.ts | 66 +++++++++++++++---- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/scripts/deployment/phase3-rtoken/1_deploy_rtoken.ts b/scripts/deployment/phase3-rtoken/1_deploy_rtoken.ts index a4cc9084bb..7070d25e1e 100644 --- a/scripts/deployment/phase3-rtoken/1_deploy_rtoken.ts +++ b/scripts/deployment/phase3-rtoken/1_deploy_rtoken.ts @@ -14,6 +14,7 @@ import { IRTokenDeployments, } from '../common' import { AssetRegistryP1, DeployerP1, FacadeWrite, MainP1 } from '../../../typechain' +import { ZERO_ADDRESS } from '#/common/constants' async function main() { // ==== Read Configuration ==== @@ -104,7 +105,13 @@ async function main() { ) // Deploy RToken - const receipt = await (await facadeWrite.deployRToken(rTokenConfig, rTokenSetup)).wait() + const receipt = await ( + await facadeWrite.deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) + ).wait() // Get Main const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args.main diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index dcc6878ddf..6913b21779 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -229,19 +229,31 @@ describe('FacadeWrite contract', () => { rTokenSetup.primaryBasket = [tokenAsset.address, tokenAsset.address] rTokenSetup.weights = [fp('0.5'), fp('0.5')] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('duplicate collateral') // Cannot deploy with duplicate asset rTokenSetup.assets = [tokenAsset.address, tokenAsset.address] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('duplicate asset') // Should not accept zero addr beneficiary rTokenSetup.beneficiaries = [{ beneficiary: ZERO_ADDRESS, revShare: revShare1 }] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('beneficiary revShare mismatch') // Should not accept empty revShare @@ -249,25 +261,41 @@ describe('FacadeWrite contract', () => { { beneficiary: beneficiary1.address, revShare: { rsrDist: bn(0), rTokenDist: bn(0) } }, ] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('beneficiary revShare mismatch') // Cannot deploy backup info with no collateral tokens rTokenSetup.backups[0].backupCollateral = [] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('no backup collateral') // Cannot deploy with invalid length in weights rTokenSetup.weights = [fp('1')] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('invalid length') // Cannot deploy with no basket rTokenSetup.primaryBasket = [] await expect( - facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).to.be.revertedWith('no collateral') }) @@ -277,7 +305,11 @@ describe('FacadeWrite contract', () => { ] // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args.main @@ -290,7 +322,11 @@ describe('FacadeWrite contract', () => { ] // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args.main @@ -301,7 +337,11 @@ describe('FacadeWrite contract', () => { beforeEach(async () => { // Deploy RToken via FacadeWrite const receipt = await ( - await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ).wait() const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args @@ -821,7 +861,11 @@ describe('FacadeWrite contract', () => { describeGas('Gas Reporting', () => { it('Phase 1 - RToken Deployment', async () => { await snapshotGasCost( - await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup) + await facadeWrite.connect(deployerUser).deployRToken(rTokenConfig, rTokenSetup, { + assetPluginRegistry: ZERO_ADDRESS, + daoFeeRegistry: ZERO_ADDRESS, + versionRegistry: ZERO_ADDRESS, + }) ) }) From 43bd2aa1af74f3a745362b47657e69307d671269 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 22 Oct 2024 21:12:40 +0530 Subject: [PATCH 08/13] Add spell --- contracts/spells/SpellBasketNormalizer.sol | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 contracts/spells/SpellBasketNormalizer.sol diff --git a/contracts/spells/SpellBasketNormalizer.sol b/contracts/spells/SpellBasketNormalizer.sol new file mode 100644 index 0000000000..7a1b10f36f --- /dev/null +++ b/contracts/spells/SpellBasketNormalizer.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../p1/mixins/BasketLib.sol"; +import "../interfaces/IDeployer.sol"; +import "../interfaces/IMain.sol"; +import "../p1/Deployer.sol"; + +/** + * This spell is used by reweightable RTokens with rev 4.0.0 or later. + * + * This allows governance to normalize the basket by price at the time of setting it in place, + * effectively allowing for a basket that is continous in USD terms. + * + * Before casting `setNormalizedBasket` function this contract must have `MAIN_OWNER_ROLE` of Main, + * and should also revoke the said role at the end of the transaction. + * + * The spell function should be called by the timelock owning Main. Governance should NOT + * grant this spell ownership without immediately executing the spell function after. + */ +contract SpellBasketNormalizer { + function setNormalizedBasket( + IRToken rToken, + IERC20[] calldata erc20s, + uint192[] calldata targetAmts + ) external { + require(erc20s.length == targetAmts.length); + + IMain main = rToken.main(); + IAssetRegistry assetRegistry = main.assetRegistry(); + IBasketHandler basketHandler = main.basketHandler(); + + assetRegistry.refresh(); + (uint192 low, uint192 high) = basketHandler.price(false); + + uint192[] memory newTargetAmts = BasketLibP1.normalizeByPrice( + assetRegistry, + erc20s, + targetAmts, + (low + high + 1) / 2 + ); + + basketHandler.forceSetPrimeBasket(erc20s, newTargetAmts); + + // TODO: Should role stuff happen here or outside? + // Pros of doing it outside would mean this can be a generic use contract + } +} From c577dff759bfd0f85b88af2e4cfd42a7205a4508 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Wed, 23 Oct 2024 18:15:18 +0530 Subject: [PATCH 09/13] Spell test --- contracts/spells/SpellBasketNormalizer.sol | 5 +- test/scenario/BasketNormalization.test.ts | 205 +++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 test/scenario/BasketNormalization.test.ts diff --git a/contracts/spells/SpellBasketNormalizer.sol b/contracts/spells/SpellBasketNormalizer.sol index 7a1b10f36f..f84afa5169 100644 --- a/contracts/spells/SpellBasketNormalizer.sol +++ b/contracts/spells/SpellBasketNormalizer.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "../p1/mixins/BasketLib.sol"; +import "../p1/BasketHandler.sol"; import "../interfaces/IDeployer.sol"; import "../interfaces/IMain.sol"; import "../p1/Deployer.sol"; @@ -24,12 +25,14 @@ contract SpellBasketNormalizer { IERC20[] calldata erc20s, uint192[] calldata targetAmts ) external { - require(erc20s.length == targetAmts.length); + require(erc20s.length == targetAmts.length, "SBN: mismatch"); IMain main = rToken.main(); IAssetRegistry assetRegistry = main.assetRegistry(); IBasketHandler basketHandler = main.basketHandler(); + require(BasketHandlerP1(address(basketHandler)).reweightable(), "SBN: reweightable"); + assetRegistry.refresh(); (uint192 low, uint192 high) = basketHandler.price(false); diff --git a/test/scenario/BasketNormalization.test.ts b/test/scenario/BasketNormalization.test.ts new file mode 100644 index 0000000000..de3bce00c9 --- /dev/null +++ b/test/scenario/BasketNormalization.test.ts @@ -0,0 +1,205 @@ +import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { ethers, upgrades } from 'hardhat' +import { IConfig } from '../../common/configuration' +import { bn, fp } from '../../common/numbers' +import { + IAssetRegistry, + TestIBackingManager, + TestIRToken, + SelfReferentialCollateral, + BasketHandlerP1, + ERC20Mock, + MainP1, + BasketLibP1, +} from '../../typechain' +import { advanceTime } from '../utils/time' +import { + defaultFixtureNoBasket, + IMPLEMENTATION, + Implementation, + PRICE_TIMEOUT, + ORACLE_ERROR, + ORACLE_TIMEOUT, +} from '../fixtures' +import { CollateralStatus } from '../../common/constants' + +const makeBasicCollateral = async (symbol: string, target: 'ETH' | 'BTC' | 'USD') => { + const ERC20Factory = await ethers.getContractFactory('ERC20Mock') + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const SelfReferentialFactory = await ethers.getContractFactory('SelfReferentialCollateral') + + const erc20 = await ERC20Factory.deploy(symbol + ' Token', symbol) + const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + if (target === 'BTC') { + await chainlinkFeed.updateAnswer(bn('60000e8')) + } else if (target === 'ETH') { + await chainlinkFeed.updateAnswer(bn('2500e8')) + } + + const coll = await SelfReferentialFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: erc20.address, + maxTradeVolume: fp('1e6'), // $1m + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String(target), + defaultThreshold: fp('0'), // Unsupported + delayUntilDefault: bn('86400'), // 24h + }) + + await coll.refresh() + + return [erc20, coll] as const +} + +const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip + +describeP1(`Basket Normalization Test (Spell)`, () => { + const amt = fp('1') + + let owner: SignerWithAddress + let addr1: SignerWithAddress + + let config: IConfig + + let main: MainP1 + let backingManager: TestIBackingManager + let rToken: TestIRToken + let assetRegistry: IAssetRegistry + let bh: BasketHandlerP1 + let basketLib: BasketLibP1 + + let ethCollateral: SelfReferentialCollateral + let btcCollateral: SelfReferentialCollateral + let usdCollateral: SelfReferentialCollateral + let allCollaterals: [ + SelfReferentialCollateral, + SelfReferentialCollateral, + SelfReferentialCollateral + ] + + let ethERC20: ERC20Mock + let btcERC20: ERC20Mock + let usdERC20: ERC20Mock + let allERC20s: [ERC20Mock, ERC20Mock, ERC20Mock] + + describe('ETH + BTC -> ETH + USD', () => { + beforeEach(async () => { + ;[owner, addr1] = await ethers.getSigners() + + // Deploy fixture + ;({ assetRegistry, backingManager, config, rToken } = await loadFixture( + defaultFixtureNoBasket + )) + + // God types in this repo are horrendous + main = await ethers.getContractAt('MainP1', await rToken.main()) + + // Setup Factories + const BasketLibFactory = await ethers.getContractFactory('BasketLibP1') + basketLib = await BasketLibFactory.deploy() + + const BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1', { + libraries: { + BasketLibP1: basketLib.address, + }, + }) + + // Enables reweightable and disables issuance premium + bh = await ethers.getContractAt( + 'BasketHandlerP1', + ( + await upgrades.deployProxy( + BasketHandlerFactory, + [main.address, config.warmupPeriod, true, false], + { + initializer: 'init', + kind: 'uups', + } + ) + ).address + ) + + await setStorageAt(main.address, 204, bh.address) + await setStorageAt(rToken.address, 355, bh.address) + await setStorageAt(backingManager.address, 302, bh.address) + await setStorageAt(assetRegistry.address, 201, bh.address) + + // Create collaterals + ;[ethERC20, ethCollateral] = await makeBasicCollateral('ETH', 'ETH') + ;[btcERC20, btcCollateral] = await makeBasicCollateral('BTC', 'BTC') + ;[usdERC20, usdCollateral] = await makeBasicCollateral('USD', 'USD') + + await assetRegistry.connect(owner).register(ethCollateral.address) + await assetRegistry.connect(owner).register(btcCollateral.address) + await assetRegistry.connect(owner).register(usdCollateral.address) + + allCollaterals = [ethCollateral, btcCollateral, usdCollateral] + allERC20s = [ethERC20, btcERC20, usdERC20] + + for (let i = 0; i < allCollaterals.length; i++) { + await assetRegistry.connect(owner).register(allCollaterals[i].address) + } + + await bh.connect(owner).setPrimeBasket( + [ethERC20.address, btcERC20.address], // Assets + [fp('1'), fp('1')] // Initial Ratio by Quantity: 50% ETH + 50% BTC + ) + + await bh.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + + expect(await rToken.totalSupply()).to.equal(0) + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('Issue & Redeem', async () => { + // Issue + for (let i = 0; i < allERC20s.length; i++) { + await allERC20s[i].mint(addr1.address, amt) + await allERC20s[i].connect(addr1).approve(rToken.address, amt) + } + + await rToken.connect(addr1).issue(amt) + expect(await rToken.totalSupply()).to.equal(amt) + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + + // Redeem + await rToken.connect(addr1).redeem(amt) + expect(await rToken.totalSupply()).to.equal(0) + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('Spell Act', async () => { + const SpellFactory = await ethers.getContractFactory('SpellBasketNormalizer', { + libraries: { + BasketLibP1: basketLib.address, + }, + }) + const basketNormalizerSpell = await SpellFactory.deploy() + + const currentBasket = await bh.getPrimeBasket() + expect(currentBasket.targetAmts[0]).to.equal(fp('1')) + expect(currentBasket.targetAmts[1]).to.equal(fp('1')) + + await main.connect(owner).grantRole(await main.OWNER_ROLE(), basketNormalizerSpell.address) + await basketNormalizerSpell.setNormalizedBasket( + rToken.address, + [ethERC20.address, usdERC20.address], // Assets + [fp('1'), fp('2')] // Next Ratio by Quantity: 33% ETH + 66% USD + ) + await main.connect(owner).revokeRole(await main.OWNER_ROLE(), basketNormalizerSpell.address) + + const nextBasket = await bh.getPrimeBasket() + expect(nextBasket.targetAmts[0]).to.greaterThanOrEqual(fp('24.9')) + expect(nextBasket.targetAmts[1]).to.greaterThanOrEqual(fp('49.9')) + }) + }) +}) From 9bdce51bd46deb37948fd07062119b2dac18fdb6 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Wed, 23 Oct 2024 18:24:32 +0530 Subject: [PATCH 10/13] Add role revoke --- contracts/spells/SpellBasketNormalizer.sol | 6 +++--- test/scenario/BasketNormalization.test.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/spells/SpellBasketNormalizer.sol b/contracts/spells/SpellBasketNormalizer.sol index f84afa5169..4502b0b50a 100644 --- a/contracts/spells/SpellBasketNormalizer.sol +++ b/contracts/spells/SpellBasketNormalizer.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import "../p1/mixins/BasketLib.sol"; import "../p1/BasketHandler.sol"; +import "../p1/Main.sol"; import "../interfaces/IDeployer.sol"; import "../interfaces/IMain.sol"; import "../p1/Deployer.sol"; @@ -27,7 +28,7 @@ contract SpellBasketNormalizer { ) external { require(erc20s.length == targetAmts.length, "SBN: mismatch"); - IMain main = rToken.main(); + MainP1 main = MainP1(address(rToken.main())); IAssetRegistry assetRegistry = main.assetRegistry(); IBasketHandler basketHandler = main.basketHandler(); @@ -45,7 +46,6 @@ contract SpellBasketNormalizer { basketHandler.forceSetPrimeBasket(erc20s, newTargetAmts); - // TODO: Should role stuff happen here or outside? - // Pros of doing it outside would mean this can be a generic use contract + main.revokeRole(main.OWNER_ROLE(), address(this)); } } diff --git a/test/scenario/BasketNormalization.test.ts b/test/scenario/BasketNormalization.test.ts index de3bce00c9..a78e3f1cb2 100644 --- a/test/scenario/BasketNormalization.test.ts +++ b/test/scenario/BasketNormalization.test.ts @@ -195,7 +195,8 @@ describeP1(`Basket Normalization Test (Spell)`, () => { [ethERC20.address, usdERC20.address], // Assets [fp('1'), fp('2')] // Next Ratio by Quantity: 33% ETH + 66% USD ) - await main.connect(owner).revokeRole(await main.OWNER_ROLE(), basketNormalizerSpell.address) + + expect(await main.hasRole(await main.OWNER_ROLE(), basketNormalizerSpell.address)).to.be.false const nextBasket = await bh.getPrimeBasket() expect(nextBasket.targetAmts[0]).to.greaterThanOrEqual(fp('24.9')) From 322d753006887b65e3c17181b1674f0ca547902d Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Wed, 23 Oct 2024 18:54:22 +0530 Subject: [PATCH 11/13] Fix RPCs --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9234f27175..4b4a728a6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: - run: yarn install --immutable - run: yarn devchain & env: - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet - run: yarn deploy:run --network localhost env: @@ -82,7 +82,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet PROTO_IMPL: 1 FORK: 1 @@ -109,7 +109,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet PROTO_IMPL: 1 FORK: 1 @@ -136,7 +136,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet PROTO_IMPL: 1 FORK: 1 @@ -163,7 +163,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - BASE_RPC_URL: https://base-mainnet.infura.io/v3/${{ secrets.INFURA_BASE_KEY }} + BASE_RPC_URL: https://base-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_BASE_KEY }} FORK_NETWORK: base FORK_BLOCK: 4446300 FORK: 1 @@ -262,7 +262,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet integration-tests: @@ -289,7 +289,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet monitor-tests: @@ -314,7 +314,7 @@ jobs: env: NODE_OPTIONS: '--max-old-space-size=32768' TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet FORK: 1 PROTO_IMPL: 1 From 458ced6ffc81b8219602a66e5d276d3690446dbd Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 23 Oct 2024 18:02:54 -0400 Subject: [PATCH 12/13] expand test slightly --- test/scenario/BasketNormalization.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/scenario/BasketNormalization.test.ts b/test/scenario/BasketNormalization.test.ts index a78e3f1cb2..514bd2fa0f 100644 --- a/test/scenario/BasketNormalization.test.ts +++ b/test/scenario/BasketNormalization.test.ts @@ -189,6 +189,8 @@ describeP1(`Basket Normalization Test (Spell)`, () => { expect(currentBasket.targetAmts[0]).to.equal(fp('1')) expect(currentBasket.targetAmts[1]).to.equal(fp('1')) + const initialPrice = await bh['price(bool)'](false) + await main.connect(owner).grantRole(await main.OWNER_ROLE(), basketNormalizerSpell.address) await basketNormalizerSpell.setNormalizedBasket( rToken.address, @@ -199,8 +201,14 @@ describeP1(`Basket Normalization Test (Spell)`, () => { expect(await main.hasRole(await main.OWNER_ROLE(), basketNormalizerSpell.address)).to.be.false const nextBasket = await bh.getPrimeBasket() - expect(nextBasket.targetAmts[0]).to.greaterThanOrEqual(fp('24.9')) - expect(nextBasket.targetAmts[1]).to.greaterThanOrEqual(fp('49.9')) + expect(nextBasket.targetAmts[0]).to.be.greaterThanOrEqual(fp('24.9')) + expect(nextBasket.targetAmts[1]).to.be.greaterThanOrEqual(fp('49.9')) + expect(nextBasket.targetAmts[0]).to.be.lessThanOrEqual(fp('25')) + expect(nextBasket.targetAmts[1]).to.be.lessThanOrEqual(fp('50')) + + const price = await bh['price(bool)'](false) + expect(price.low).to.be.closeTo(initialPrice.low, 1) + expect(price.high).to.be.closeTo(initialPrice.high, 1) }) }) }) From 73975f3ddefdaad6cfbb688f222c6e9a1811f078 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 24 Oct 2024 19:05:58 +0530 Subject: [PATCH 13/13] Add documentation --- docs/reweightable-rtokens.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/reweightable-rtokens.md diff --git a/docs/reweightable-rtokens.md b/docs/reweightable-rtokens.md new file mode 100644 index 0000000000..e32292a27c --- /dev/null +++ b/docs/reweightable-rtokens.md @@ -0,0 +1,15 @@ +# Reweightable RTokens + +The protocol includes a flag to enable reweightable baskets for RTokens. This flag can only be set at the time of deployment and enables certain additional capabilities for the RToken while disabling others. + +In simple terms, a reweightable RToken can change the target units. For example, if an RToken is configured as 1 ETH + 1 BTC in the basket, only the reweightable RTokens can change it to something like 1 ETH + 1 BTC + 100 USD. + +In most cases, a non-reweightable RToken will suffice, we expect that to be 99% of all RTokens that exist. However, there are specific cases where you'd want to have reweightable RTokens such as ETFs. + +## Basket Normalization + +In reweightable RTokens, it's not a guarantee that during a basket change the USD value of the basket remains continuous at the time of the switch. You can easily see this property when, say, a basket switches from being 1 ETH to 1 ETH + 100 USD. The USD value of the basket will increase in this case, but the protocol doesn't have the extra funds (unless it seizes from the stRSR staking pool). + +To enable this functionality and to allow governance to make sure that the set baskets can keep the same price at the time of the switch, a spell `SpellBasketNormalizer` is provided in the spells directory. This spell can be used to set the basket in such a way that the USD value of the basket remains the same at the time of the switch. + +In order to use the spell, you must create a governance proposal granting the spell contract the `OWNER` role on the RToken then calling the `setNormalizedBasket` basket on the spell contract with appropriate parameters. See the `BasketNormalization` scenario test in the `test` directory for an example of how to use the spell.