diff --git a/ethereum/deploy/003_bed_manager_migrations.ts b/ethereum/deploy/003_bed_manager_migrations.ts new file mode 100644 index 0000000..443b83f --- /dev/null +++ b/ethereum/deploy/003_bed_manager_migrations.ts @@ -0,0 +1,80 @@ +import "module-alias/register"; + +import { HardhatRuntimeEnvironment as HRE } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +import { + findDependency, + getCurrentStage, + stageAlreadyFinished, + trackFinishedStage, +} from "@utils/index"; + +import { DEPENDENCY } from "../deployments/utils/dependencies"; +import {ManagerMigrator} from "@utils/managerMigrationUtils"; + +const { + BED, + BED_OPERATOR_V1, + BED_METHODOLOGIST, + BED_FEE_EXTENSION_V1 +} = DEPENDENCY; + +const CURRENT_STAGE = getCurrentStage(__filename); + +const func: DeployFunction = trackFinishedStage(CURRENT_STAGE, async function (hre: HRE) { + const migrator = new ManagerMigrator(hre.ethers.provider); + await migrator.initialize(hre); + + const bed = await findDependency(BED); + const bedOperator = await findDependency(BED_OPERATOR_V1); + const bedMethodologist = await findDependency(BED_METHODOLOGIST); + const bedFeeExtension = await findDependency(BED_FEE_EXTENSION_V1); + + await migrator.impersonateMultisigs([bedOperator, bedMethodologist]); + + // Transfer SetToken managership to a multisig. Before this step, methodologist can be added + // to the operator multisig or a dedicated multisig could be created to manage the transition. + // Methodologist would sign both sides of this setManager() mutual upgrade tx and be a + // co-signer for all other txs. + + // Assume for the moment operator will be the transitionalManager and also have the + // DelegatedManager operator role. + const transitionalManager = bedOperator; + const delegatedOperators = [bedOperator]; + + await migrator.transferSetToTransitionalManager( + transitionalManager, + bed + ); + + await migrator.createDelegatedManager( + transitionalManager, + bed, + bedOperator, + bedMethodologist, + delegatedOperators + ); + + // Cache DelegatedManager address (we need this for the final transfer) + const delegatedManager = await migrator.getDelegatedManagerAddress(bed); + + // Initializes DelegatedManager and inherits existing FeeSplitExtension settings for + // `operatorFeeSplit` and `operatorFeeRecipient`. In practice, executors will need to fetch + // the new delegated manager address by querying the factory before running factory.initialize() + await migrator.initializeManager( + transitionalManager, + bedFeeExtension, + bed + ); + + await migrator.transferSetToDelegatedManager( + transitionalManager, + delegatedManager, + bed + ); +}); + +func.skip = stageAlreadyFinished(CURRENT_STAGE); + +export default func; \ No newline at end of file diff --git a/ethereum/deployments/outputs/1-production.json b/ethereum/deployments/outputs/1-production.json index fd96265..0c23449 100644 --- a/ethereum/deployments/outputs/1-production.json +++ b/ethereum/deployments/outputs/1-production.json @@ -3,7 +3,7 @@ "network_key": "1-production", "human_friendly_name": "main-net-production", "network_id": 1, - "last_deployment_stage": 3 + "last_deployment_stage": 4 }, "addresses": { "ManagerCore": "0x7a397B3ed39E84C6181e47309CE940574290f4e7", @@ -85,6 +85,84 @@ "id": "0xd293f491d8dc0fcacfce7cc9ae8a64099f092ad623fd18e28f79633f66f67157", "timestamp": 1648697282613, "description": "Transfer ManagerCore ownership to Multisig" + }, + "7": { + "id": null, + "timestamp": null, + "data": "0xd0ebdbe7000000000000000000000000186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "description": "BED.BaseManager.setManager(): set 0x186b0a2b5028916fa237bb5b11a9841dbd8567b5 as temporary SetToken manager", + "contractName": "BaseManager", + "to": "0x5E6898eE65EDd36F76571DB4171B4ebdEab5262C", + "from": "0x186b0A2B5028916Fa237bb5B11A9841Dbd8567B5", + "params": { + "newManager": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5" + } + }, + "8": { + "id": null, + "timestamp": null, + "data": "0x983ac4490000000000000000000000002af1df3ab0ab157e1e2ad8f88a7d04fbea0c7dc6000000000000000000000000186b0a2b5028916fa237bb5b11a9841dbd8567b5000000000000000000000000f26d1bb347a59f6c283c53156519cc1b1abaca5100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000186b0a2b5028916fa237bb5b11a9841dbd8567b500000000000000000000000000000000000000000000000000000000000000030000000000000000000000001494ca1f11d487c2bbe4543e90080aeba4ba3c2b0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000300000000000000000000000005c5c57e5e75fc8ead83fe06ebe4acc471fb294800000000000000000000000000dd1d0a8acf768700c009cb5e54bbb5b69200710000000000000000000000001f52ba34eb80cbd48b9f3dad43ffb4cb6d0fccf5", + "description": "BED.DelegatedManagerFactory.createManager(): create new DelegatedManager", + "contractName": "DELEGATED_MANAGER_FACTORY", + "to": "0x5132044c71b98315bDD5D8E6900bcf93EB2EbeC0", + "from": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "params": { + "setToken": "0x2aF1dF3AB0ab157e1E2Ad8F88A7D04fbea0c7dc6", + "owner": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "methodologist": "0xf26d1bb347a59f6c283c53156519cc1b1abaca51", + "operators": [ + "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5" + ], + "assets": [ + "0x1494CA1F11D487c2bBe4543E90080AeBa4BA3C2b", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ], + "extensions": [ + "0x05C5c57E5E75FC8EaD83FE06ebe4aCc471Fb2948", + "0x00DD1D0A8acF768700c009CB5E54BBb5b6920071", + "0x1F52bA34eb80cBD48b9f3Dad43FFB4Cb6D0FCCF5" + ] + } + }, + "9": { + "id": null, + "timestamp": null, + "data": "0xad8238220000000000000000000000002af1df3ab0ab157e1e2ad8f88a7d04fbea0c7dc600000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000069bdb276a17dd90f9d3a545944ccb20e593ae8e300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000300000000000000000000000005c5c57e5e75fc8ead83fe06ebe4acc471fb294800000000000000000000000000dd1d0a8acf768700c009cb5e54bbb5b69200710000000000000000000000001f52ba34eb80cbd48b9f3dad43ffb4cb6d0fccf50000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "description": "BED.DelegatedManagerFactory.initialize(): initialize new DelegatedManager", + "contractName": "DELEGATED_MANAGER_FACTORY", + "to": "0x5132044c71b98315bDD5D8E6900bcf93EB2EbeC0", + "from": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "params": { + "setToken": "0x2aF1dF3AB0ab157e1E2Ad8F88A7D04fbea0c7dc6", + "ownerFeeSplit": { + "type": "BigNumber", + "hex": "0x06f05b59d3b20000" + }, + "ownerFeeRecipient": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "extensions": [ + "0x05C5c57E5E75FC8EaD83FE06ebe4aCc471Fb2948", + "0x00DD1D0A8acF768700c009CB5E54BBb5b6920071", + "0x1F52bA34eb80cBD48b9f3Dad43FFB4Cb6D0FCCF5" + ], + "initializeBytecode": [ + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ] + } + }, + "10": { + "id": null, + "timestamp": null, + "data": "0xd0ebdbe7000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "description": "BED.SetToken.setManager(): set 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa as final SetToken manager", + "contractName": "BaseManager", + "to": "0x2aF1dF3AB0ab157e1E2Ad8F88A7D04fbea0c7dc6", + "from": "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5", + "params": { + "newManager": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } } } } \ No newline at end of file diff --git a/ethereum/deployments/outputs/1-staging.json b/ethereum/deployments/outputs/1-staging.json index 13ef572..b60b678 100644 --- a/ethereum/deployments/outputs/1-staging.json +++ b/ethereum/deployments/outputs/1-staging.json @@ -3,7 +3,7 @@ "network_key": "1-staging", "human_friendly_name": "main-net-staging", "network_id": 1, - "last_deployment_stage": 3 + "last_deployment_stage": 4 }, "addresses": { "ExchangeIssuanceZeroEx": "0x939eD0665f6CC78882C0ef451d2636110FEA87fe", @@ -95,6 +95,84 @@ "id": "0xf1dc847c5ca60e8dbc170ca8672ffb5d30cbd4d42c37a573c7cae9a1e3fe1fcb", "timestamp": 1648694500282, "description": "Initialized ManagerCore with DelegatedManagerFactory, IssuanceExtension, StreamingFeeSplitExtension, and TradeExtension" + }, + "7": { + "id": null, + "timestamp": null, + "data": "0xd0ebdbe700000000000000000000000068170278804fc18481aecfb497d63a9b11dc692f", + "description": "BED.BaseManager.setManager(): set 0x68170278804fc18481aecfb497d63a9b11dc692f as temporary SetToken manager", + "contractName": "BaseManager", + "to": "0xe337794481879179838413b581FFaBA8356A9C8B", + "from": "0x68170278804Fc18481aECfB497D63A9b11DC692F", + "params": { + "newManager": "0x68170278804fc18481aecfb497d63a9b11dc692f" + } + }, + "8": { + "id": null, + "timestamp": null, + "data": "0x983ac44900000000000000000000000068ad048fa2e1bcafa690ce257b69a13f5a79a51400000000000000000000000068170278804fc18481aecfb497d63a9b11dc692f00000000000000000000000069bdb276a17dd90f9d3a545944ccb20e593ae8e300000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068170278804fc18481aecfb497d63a9b11dc692f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001494ca1f11d487c2bbe4543e90080aeba4ba3c2b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000fc4e27bdb739a6e2aada6c8764860bec6f4288ee000000000000000000000000ea2f360529dfb5a3423ec84d44482673c28960e5000000000000000000000000946735c0b0c47dd74a38eae96e75e0eff5bfb93e", + "description": "BED.DelegatedManagerFactory.createManager(): create new DelegatedManager", + "contractName": "DELEGATED_MANAGER_FACTORY", + "to": "0x55cC794247Ec45db9A1D5B0F26055B7B729F19D2", + "from": "0x68170278804fc18481aecfb497d63a9b11dc692f", + "params": { + "setToken": "0x68aD048fA2e1bcaFa690Ce257b69A13f5A79a514", + "owner": "0x68170278804fc18481aecfb497d63a9b11dc692f", + "methodologist": "0x69bdb276a17dd90f9d3a545944ccb20e593ae8e3", + "operators": [ + "0x68170278804fc18481aecfb497d63a9b11dc692f" + ], + "assets": [ + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0x1494CA1F11D487c2bBe4543E90080AeBa4BA3C2b" + ], + "extensions": [ + "0xFc4E27bDb739A6e2AAdA6c8764860bec6F4288Ee", + "0xEa2F360529dFB5A3423ec84D44482673C28960e5", + "0x946735c0B0C47dd74a38eAe96E75E0eFF5BFB93E" + ] + } + }, + "9": { + "id": null, + "timestamp": null, + "data": "0xad82382200000000000000000000000068ad048fa2e1bcafa690ce257b69a13f5a79a51400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000069bdb276a17dd90f9d3a545944ccb20e593ae8e300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000fc4e27bdb739a6e2aada6c8764860bec6f4288ee000000000000000000000000ea2f360529dfb5a3423ec84d44482673c28960e5000000000000000000000000946735c0b0c47dd74a38eae96e75e0eff5bfb93e0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024de2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "description": "BED.DelegatedManagerFactory.initialize(): initialize new DelegatedManager", + "contractName": "DELEGATED_MANAGER_FACTORY", + "to": "0x55cC794247Ec45db9A1D5B0F26055B7B729F19D2", + "from": "0x68170278804fc18481aecfb497d63a9b11dc692f", + "params": { + "setToken": "0x68aD048fA2e1bcaFa690Ce257b69A13f5A79a514", + "ownerFeeSplit": { + "type": "BigNumber", + "hex": "0x06f05b59d3b20000" + }, + "ownerFeeRecipient": "0x68170278804fc18481aecfb497d63a9b11dc692f", + "extensions": [ + "0xFc4E27bDb739A6e2AAdA6c8764860bec6F4288Ee", + "0xEa2F360529dFB5A3423ec84D44482673C28960e5", + "0x946735c0B0C47dd74a38eAe96E75E0eFF5BFB93E" + ], + "initializeBytecode": [ + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0xde2236bd000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ] + } + }, + "10": { + "id": null, + "timestamp": null, + "data": "0xd0ebdbe7000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "description": "BED.SetToken.setManager(): set 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa as final SetToken manager", + "contractName": "BaseManager", + "to": "0x68aD048fA2e1bcaFa690Ce257b69A13f5A79a514", + "from": "0x68170278804fc18481aecfb497d63a9b11dc692f", + "params": { + "newManager": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } } } } \ No newline at end of file diff --git a/ethereum/deployments/utils/dependencies.ts b/ethereum/deployments/utils/dependencies.ts index ee18362..2e15e66 100644 --- a/ethereum/deployments/utils/dependencies.ts +++ b/ethereum/deployments/utils/dependencies.ts @@ -137,6 +137,132 @@ export default { 1: "0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998", }, + // SetTokens + BED: { + 1: { + staging: "0x68aD048fA2e1bcaFa690Ce257b69A13f5A79a514", + production: "0x2aF1dF3AB0ab157e1E2Ad8F88A7D04fbea0c7dc6" + }, + }, + GMI: { + 1: { + staging: "0x9a6D7B340F7035d97feaAf27e6cC9C9D084eBe2F", + production: "0x47110d43175f7f2C2425E7d15792acC5817EB44f" + }, + }, + DATA: { + 1: { + staging: "0x78468eAB2753AE5A9af9bD08ef5ABaCE767286F3", + production: "0x33d63Ba1E57E54779F7dDAeaA7109349344cf5F1" + }, + }, + + // Source: etherscan via SetToken.manager() + BED_MANAGER_V1: { + 1: { + staging: "0xe337794481879179838413b581ffaba8356a9c8b", + production: "0x5e6898ee65edd36f76571db4171b4ebdeab5262c" + }, + }, + GMI_MANAGER_V1: { + 1: { + staging: "0x59fedba373c52dc06501bcbea4948d58955b6364", + production: "0x4039703deb28748dbf155f8567fb5f0ca2ddd742" + }, + }, + DATA_MANAGER_V1: { + 1: { + staging: "0xb0a058e8d82d56b6a169a30993d171214051ef6e", + production: "0xa420cf4e06cf28dafbdb436ac444920634f1c766" + }, + }, + + // Source: etherscan via ManagerContractV1.methodologist() + BED_METHODOLOGIST: { + 1: { + staging: "0x69bdb276a17dd90f9d3a545944ccb20e593ae8e3", + production: "0xf26d1bb347a59f6c283c53156519cc1b1abaca51" + }, + }, + GMI_METHODOLOGIST: { + 1: { + staging: "0x37e6365d4f6ae378467b0e24c9065ce5f06d70bf", + production: "0xf26d1bb347a59f6c283c53156519cc1b1abaca51" + } + }, + DATA_METHODOLOGIST: { + 1: { + staging: "0x37e6365d4f6ae378467b0e24c9065ce5f06d70bf", + production: "0xe4665d12bab964955011fb8270c99bf2f8b49979" + } + }, + + // Source: etherscan via ManagerContractV1.operator() + BED_OPERATOR_V1: { + 1: { + staging: "0x68170278804fc18481aecfb497d63a9b11dc692f", + production: "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5" + } + }, + GMI_OPERATOR_V1: { + 1: { + staging: "0x68170278804fc18481aecfb497d63a9b11dc692f", + production: "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5" + } + }, + DATA_OPERATOR_V1: { + 1: { + staging: "0x68170278804fc18481aecfb497d63a9b11dc692f", + production: "0x186b0a2b5028916fa237bb5b11a9841dbd8567b5" + } + }, + + // Source: index-coop-deployments + BED_FEE_EXTENSION_V1: { + 1: { + staging: "0x16C2143ebbE391403CFe755CC968184244B810C2", + production: "0xF5090c139703cF49eEfeF6E2A8E62ef150062B17" + } + }, + GMI_FEE_EXTENSION_V1: { + 1: { + staging: "0xF8aF89c1285cdeBE63413449Ce982b9ce185dbAe", + production: "0x48CF49701028CfBbCe18a963C9134F7A86E71A22" + } + }, + DATA_FEE_EXTENSION_V1: { + 1: { + staging: "0xB9d08e14C4AfC1F9113E440EAE42028e1377A869", + production: "0x0104a6FA30540DC1d9F45D2797F05eEa79304525" + } + }, + + // These are used in the SetToken manager migration forked scripts + DELEGATED_MANAGER_FACTORY: { + 1: { + staging: "0x55cC794247Ec45db9A1D5B0F26055B7B729F19D2", + production: "0x5132044c71b98315bDD5D8E6900bcf93EB2EbeC0" + } + }, + TRADE_EXTENSION: { + 1: { + staging: "0xEa2F360529dFB5A3423ec84D44482673C28960e5", + production: "0x00DD1D0A8acF768700c009CB5E54BBb5b6920071" + } + }, + ISSUANCE_EXTENSION: { + 1: { + staging: "0xFc4E27bDb739A6e2AAdA6c8764860bec6F4288Ee", + production: "0x05C5c57E5E75FC8EaD83FE06ebe4aCc471Fb2948" + } + }, + FEE_EXTENSION: { + 1: { + staging: "0x946735c0B0C47dd74a38eAe96E75E0eFF5BFB93E", + production: "0x1F52bA34eb80cBD48b9f3Dad43FFB4Cb6D0FCCF5" + } + }, + // UNISWAP UNISWAP_FACTORY: { @@ -484,6 +610,30 @@ export const DEPENDENCY = { C_DAI: "C_DAI", C_USDT: "C_USDT", ETH: "ETH", + BED: "BED", + GMI: "GMI", + DATA: "DATA", + + BED_MANAGER_V1: "BED_MANAGER_V1", + GMI_MANAGER_V1: "GMI_MANAGER_V1", + DATA_MANAGER_V1: "DATA_MANAGER_V1", + + BED_METHODOLOGIST: "BED_METHODOLOGIST", + GMI_METHODOLOGIST: "GMI_METHODOLOGIST", + DATA_METHODOLOGIST: "DATA_METHODOLOGIST", + + BED_OPERATOR_V1: "BED_OPERATOR_V1", + GMI_OPERATOR_V1: "GMI_OPERATOR_V1", + DATA_OPERATOR_V1: "DATA_OPERATOR_V1", + + BED_FEE_EXTENSION_V1: "BED_FEE_EXTENSION_V1", + GMI_FEE_EXTENSION_V1: "GMI_FEE_EXTENSION_V1", + DATA_FEE_EXTENSION_V1: "DATA_FEE_EXTENSION_V1", + + DELEGATED_MANAGER_FACTORY: "DELEGATED_MANAGER_FACTORY", + TRADE_EXTENSION: "TRADE_EXTENSION", + ISSUANCE_EXTENSION: "ISSUANCE_EXTENSION", + FEE_EXTENSION: "FEE_EXTENSION", // External Protocols UNISWAP_FACTORY: "UNISWAP_FACTORY", diff --git a/ethereum/test/deploys/003_bed_manager_migrations.spec.ts b/ethereum/test/deploys/003_bed_manager_migrations.spec.ts new file mode 100644 index 0000000..1ccaeb9 --- /dev/null +++ b/ethereum/test/deploys/003_bed_manager_migrations.spec.ts @@ -0,0 +1,111 @@ +import "module-alias/register"; +import { deployments } from "hardhat"; + +import { Account } from "@utils/types"; + +import { + DelegatedManager, + DelegatedManager__factory +} from "@set/typechain/index"; + +import { + SetToken +} from "@setprotocol/set-protocol-v2/typechain"; + +import { + SetToken__factory +} from "@setprotocol/set-protocol-v2/dist/typechain"; + +import { + addSnapshotBeforeRestoreAfterEach, + getAccounts, + getWaffleExpect, + findDependency, +} from "@utils/index"; + +import { DEPENDENCY } from "../../deployments/utils/dependencies"; + +const { + BED, + BED_METHODOLOGIST, + BED_OPERATOR_V1, + DELEGATED_MANAGER_FACTORY, + TRADE_EXTENSION, + ISSUANCE_EXTENSION, + FEE_EXTENSION +} = DEPENDENCY; + +const expect = getWaffleExpect(); + +describe("BED DelegatedManager Migration", () => { + let deployer: Account; + + let bedInstance: SetToken; + let delegatedManagerInstance: DelegatedManager; + + before(async () => { + [deployer] = await getAccounts(); + + await deployments.fixture(); + + const bedAddress = await findDependency(BED); + bedInstance = new SetToken__factory(deployer.wallet).attach(bedAddress); + + const delegatedManagerAddress = await bedInstance.manager(); + delegatedManagerInstance = new DelegatedManager__factory(deployer.wallet).attach( + delegatedManagerAddress + ); + }); + + addSnapshotBeforeRestoreAfterEach(); + + it("has a manager deployed by the factory", async () => { + const expectedFactory = await findDependency(DELEGATED_MANAGER_FACTORY); + const factory = await delegatedManagerInstance.factory(); + + expect(factory).to.equal(expectedFactory); + }); + + it("has the expected methodologist", async () => { + const expectedMethodologist = (await findDependency(BED_METHODOLOGIST)).toLowerCase(); + const methodologist = (await delegatedManagerInstance.methodologist()).toLowerCase(); + expect(methodologist).to.eq(expectedMethodologist); + }); + + it("has the expected owner", async () => { + const expectedOwner = (await findDependency(BED_OPERATOR_V1)).toLowerCase(); + const owner = (await delegatedManagerInstance.owner()).toLowerCase(); + expect(owner).to.eq(expectedOwner); + }); + + it("has owner as an operator", async () => { + const owner = await delegatedManagerInstance.owner(); + const operators = await delegatedManagerInstance.getOperators(); + expect(operators.includes(owner)).to.be.true; + }); + + it("has correct initialized extensions", async () => { + const extensions = await delegatedManagerInstance.getExtensions(); + + const tradeExtension = await findDependency(TRADE_EXTENSION); + const issuanceExtension = await findDependency(ISSUANCE_EXTENSION); + const feeExtension = await findDependency(FEE_EXTENSION); + + expect(await extensions.includes(tradeExtension)).to.be.true; + expect(await extensions.includes(issuanceExtension)).to.be.true; + expect(await extensions.includes(feeExtension)).to.be.true; + + expect(await delegatedManagerInstance.isInitializedExtension(tradeExtension)).to.be.true; + expect(await delegatedManagerInstance.isInitializedExtension(issuanceExtension)).to.be.true; + expect(await delegatedManagerInstance.isInitializedExtension(feeExtension)).to.be.true; + }); + + it("has all components as allowed assets", async () => { + const components = await bedInstance.getComponents(); + + for (const component of components) { + const isAllowedAsset = await delegatedManagerInstance.isAllowedAsset(component); + expect(isAllowedAsset).to.be.true; + } + }); +}); \ No newline at end of file diff --git a/utils/instanceGetter.ts b/utils/instanceGetter.ts index d9b9308..79689bf 100644 --- a/utils/instanceGetter.ts +++ b/utils/instanceGetter.ts @@ -7,6 +7,12 @@ import { BaseManager__factory, ManagerCore, ManagerCore__factory, + DelegatedManager, + DelegatedManager__factory, + DelegatedManagerFactory, + DelegatedManagerFactory__factory, + TradeExtension, + TradeExtension__factory } from "@set/typechain/index"; import { @@ -35,6 +41,9 @@ import { SlippageIssuanceModule__factory } from "@setprotocol/set-protocol-v2/dist/typechain"; +import { StreamingFeeSplitExtension } from "@indexcoop/index-coop-smart-contracts/typechain"; +import { StreamingFeeSplitExtension__factory } from "@indexcoop/index-coop-smart-contracts/dist/typechain"; + import { Signer } from "ethers"; import { Address } from "@utils/types"; @@ -102,4 +111,20 @@ export class InstanceGetter { public async getManagerCore(managerCoreAddress: Address): Promise { return await new ManagerCore__factory(this._deployerSigner).attach(managerCoreAddress); } + + public async getDelegatedManagerFactory(factoryAddress: Address): Promise { + return await new DelegatedManagerFactory__factory(this._deployerSigner).attach(factoryAddress); + } + + public async getDelegatedManager(managerAddress: Address): Promise { + return await new DelegatedManager__factory(this._deployerSigner).attach(managerAddress); + } + + public async getStreamingFeeSplitExtension(extensionAddress: Address): Promise { + return await new StreamingFeeSplitExtension__factory(this._deployerSigner).attach(extensionAddress); + } + + public async getTradeExtension(extensionAddress: Address): Promise { + return await new TradeExtension__factory(this._deployerSigner).attach(extensionAddress); + } } \ No newline at end of file diff --git a/utils/managerMigrationUtils.ts b/utils/managerMigrationUtils.ts new file mode 100644 index 0000000..b785e3c --- /dev/null +++ b/utils/managerMigrationUtils.ts @@ -0,0 +1,353 @@ +import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"; +import { HardhatRuntimeEnvironment as HRE } from "hardhat/types"; +import { ethers } from "ethers"; + +import { + findDependency, + saveDeferredTransactionData, + writeTransactionToOutputs +} from "./outputHelper"; + +import { Address } from "./types"; +import { getAccounts } from "./accountUtils"; +import { InstanceGetter } from "./instanceGetter"; +import { prepareDeployment } from "./deploys/deployUtils"; + +const DELEGATED_MANAGER_FACTORY = "DELEGATED_MANAGER_FACTORY"; +const ISSUANCE_EXTENSION = "ISSUANCE_EXTENSION"; +const TRADE_EXTENSION = "TRADE_EXTENSION"; +const FEE_EXTENSION = "FEE_EXTENSION"; + +export class ManagerMigrator { + public provider: Web3Provider | JsonRpcProvider; + + private rawTx: any; + private instanceGetter: InstanceGetter; + private factoryContractName: string; + private delegatedManagerAddressPlaceholder: Address; + private factoryAddress: Address; + private tradeExtension: Address; + private issuanceExtension: Address; + private feeExtension: Address; + private isDevelopment: boolean; + + constructor(provider: Web3Provider | JsonRpcProvider) { + this.provider = provider; + + // Dummy address injected into calldata when DelegatedManager deployment is a deferred transaction + this.delegatedManagerAddressPlaceholder = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + } + + // Initialization for all class variables which require an async request + async initialize(hre: HRE): Promise { + const [owner] = await getAccounts(); + const { rawTx, networkConstant } = await prepareDeployment(hre); + + this.rawTx = rawTx; + this.instanceGetter = new InstanceGetter(owner.wallet); + this.isDevelopment = networkConstant === "development"; + this.factoryAddress = await findDependency(DELEGATED_MANAGER_FACTORY); + + this.issuanceExtension = await findDependency(ISSUANCE_EXTENSION); + this.tradeExtension = await findDependency(TRADE_EXTENSION); + this.feeExtension = await findDependency(FEE_EXTENSION); + } + + // Impersonates and funds multisig accounts (for unit tests) + public async impersonateMultisigs(multisigs: Address[]) { + if (this.isDevelopment) { + const signer = this.provider.getSigner(); + const accounts = await this.provider.send("eth_accounts", []); + + for (const multisig of multisigs) { + await this.provider.send("hardhat_impersonateAccount", [multisig]); + await signer.sendTransaction({ + to: multisig, + from: accounts[0], + value: ethers.utils.parseUnits("100", "ether").toHexString() + }); + } + } + } + + // Transfers SetToken managership from BaseManager to `transitionalManager` - a + // multisig account which has temporarily taken control of the SetToken + public async transferSetToTransitionalManager( + transitionalManager: Address, + setToken: Address + ): Promise { + const legacyManagerContractName = "BaseManager"; + + const setTokenInstance = await this.instanceGetter.getSetToken(setToken); + const manager = await setTokenInstance.manager(); + const symbol = await setTokenInstance.symbol(); + + const baseManagerInstance = await this.instanceGetter.getBaseManager(manager); + const operator = await baseManagerInstance.operator(); + const methodologist = await baseManagerInstance.methodologist(); + + const data = baseManagerInstance.interface.encodeFunctionData("setManager", [ + transitionalManager + ]); + + const description = `${symbol}.BaseManager.setManager(): set ${transitionalManager} as temporary SetToken manager`; + + const from = operator; + const to = manager; + const params = { newManager: transitionalManager }; + + await this.executeOrLogTransaction( + from, + to, + data, + params, + description, + legacyManagerContractName + ); + + // If development, execute twice because this tx may be mutual upgrade + if (this.isDevelopment && await this.isMutualUpgrade(setToken)) { + await this.executeOrLogTransaction( + methodologist, + to, + data, + params, + description, + legacyManagerContractName + ); + } + } + + // Transfers SetToken managership from `transitionalManager` to newly deployed and initialized + // DelegatedManager + public async transferSetToDelegatedManager( + transitionalManager: Address, + delegatedManager: Address, + setToken: Address + ): Promise { + const setTokenInstance = await this.instanceGetter.getSetToken(setToken); + const symbol = await setTokenInstance.symbol(); + + const data = setTokenInstance.interface.encodeFunctionData("setManager", [ + delegatedManager, + ]); + + const description = `${symbol}.SetToken.setManager(): set ${delegatedManager} as final SetToken manager`; + + const from = transitionalManager; + const to = setToken; + const params = { newManager: delegatedManager }; + + await this.executeOrLogTransaction( + from, + to, + data, + params, + description, + "BaseManager" + ); + } + + // Creates a DelegatedManager. Transaction will be sent from the `transitionalManager` - a + // multisig account which has temporarily taken control of the SetToken + public async createDelegatedManager( + transitionalManager: Address, + setToken: Address, + owner: Address, + methodologist: Address, + operators: Address[] + ): Promise { + const setTokenInstance = await this.instanceGetter.getSetToken(setToken); + const factoryInstance = await this.instanceGetter.getDelegatedManagerFactory(this.factoryAddress); + + const symbol = await setTokenInstance.symbol(); + const assets = await setTokenInstance.getComponents(); + + const extensions = [ + this.issuanceExtension, + this.tradeExtension, + this.feeExtension + ]; + + // Calldata + const data = factoryInstance.interface.encodeFunctionData("createManager", [ + setToken, + owner, + methodologist, + operators, + assets, + extensions + ]); + + // Logging info + const params = { + setToken, + owner, + methodologist, + operators, + assets, + extensions + }; + + const description = `${symbol}.DelegatedManagerFactory.createManager(): create new DelegatedManager`; + + const from = transitionalManager; + const to = this.factoryAddress; + + await this.executeOrLogTransaction( + from, + to, + data, + params, + description, + DELEGATED_MANAGER_FACTORY + ); + } + + // Initializes DelegatedManager. Transaction will be sent from the `transitionalManager` - a + // multisig account which has temporarily taken control of the SetToken + public async initializeManager( + transitionalManager: Address, + legacyFeeExtension: Address, + setToken: Address, + ) { + const setTokenInstance = await this + .instanceGetter + .getSetToken(setToken); + + const legacyFeeExtensionInstance = await this + .instanceGetter + .getStreamingFeeSplitExtension(legacyFeeExtension); + + const ownerFeeRecipient = await this.getOwnerFeeRecipient(legacyFeeExtensionInstance, transitionalManager); + const ownerFeeSplit = await legacyFeeExtensionInstance.operatorFeeSplit(); + const symbol = await setTokenInstance.symbol(); + + const delegatedManager = await this.getDelegatedManagerAddress(setToken); + const bytecode = await this.getExtensionInitializationBytecode(this.tradeExtension, delegatedManager); + + const extensions = [ + this.issuanceExtension, + this.tradeExtension, + this.feeExtension + ]; + + const initializeBytecode = [ + bytecode, + bytecode, + bytecode + ]; + + // Calldata: fallback on manual encoding method due to tsc error in factory interface + const iface = new ethers.utils.Interface(["function initialize(address,uint256,address,address[],bytes[])"]); + const data = iface.encodeFunctionData("initialize", [ + setToken, + ownerFeeSplit, + ownerFeeRecipient, + extensions, + initializeBytecode + ]); + + // Logging info + const params = { + setToken, + ownerFeeSplit, + ownerFeeRecipient, + extensions, + initializeBytecode, + }; + const description = `${symbol}.DelegatedManagerFactory.initialize(): initialize new DelegatedManager`; + + const from = transitionalManager; + const to = this.factoryAddress; + + await this.executeOrLogTransaction( + from, + to, + data, + params, + description, + DELEGATED_MANAGER_FACTORY + ); + } + + // In development, returns the deployed DelegatedManager address. In production + // returns a placeholder address that can be easily replaced in the calldata bytecode. + public async getDelegatedManagerAddress(setToken: Address): Promise
{ + if (this.isDevelopment) { + const factoryInstance = await this + .instanceGetter + .getDelegatedManagerFactory(this.factoryAddress); + + const initializeState = await factoryInstance.initializeState(setToken); + return initializeState.manager; + } + + return this.delegatedManagerAddressPlaceholder; + } + + // Not all managers expose an `operatorFeeRecipient`. We assume transitionalManager is the `operator` + // of the BaseManager. + private async getOwnerFeeRecipient(feeExtensionInstance: any, transitionalManager: Address) { + try { + return await feeExtensionInstance.operatorFeeRecipient(); + } catch (e) { + return transitionalManager; + } + } + + // Not all managers have setManager gated by mutualUpgrade + private async isMutualUpgrade(setToken: Address): Promise { + const bed = await findDependency("BED"); + const gmi = await findDependency("BMI"); + const data = await findDependency("DATA"); + + switch (setToken) { + case bed: return false; + case gmi: return true; + case data: return true; + default: return true; + } + } + + // All default extension init bytecodes have the same signature + private async getExtensionInitializationBytecode( + extension: Address, + delegatedManager: Address + ): Promise { + const extensionInstance = await this.instanceGetter.getTradeExtension(extension); + return extensionInstance.interface.encodeFunctionData("initializeExtension", [ delegatedManager ]); + } + + // Executes rawTx if context is development (so unit tests can validate end state) + // Logs deferred transactions with params otherwise so we can create execution docs easily + private async executeOrLogTransaction( + from: Address, + to: Address, + data: string, + params: any, + description: string, + contractName: string, + ): Promise { + + if (this.isDevelopment) { + const signer = this.provider.getSigner(from); + const tx = await signer.sendTransaction({to, data}); + const receipt = await tx.wait(); + + return await writeTransactionToOutputs( + receipt.transactionHash, + description + ); + } + + await saveDeferredTransactionData({ + data, + to, + from, + params, + description, + contractName + }); + } +} \ No newline at end of file diff --git a/utils/outputHelper.ts b/utils/outputHelper.ts index f485eee..437e656 100644 --- a/utils/outputHelper.ts +++ b/utils/outputHelper.ts @@ -30,6 +30,9 @@ export type DeferredTransactionData = { data: string; description: string; contractName: string; + to?: string; + from?: string; + params?: any; }; export function getCurrentStage(fileName: string): number { @@ -121,6 +124,9 @@ export async function saveDeferredTransactionData(tx: DeferredTransactionData) { data: tx.data, description: tx.description, contractName: tx.contractName, + to: tx.to, + from: tx.from, + params: tx.params }; await fs.outputFile(OUTPUTS_PATH, JSON.stringify(outputs, undefined, 2)); }