From 607ce914a780a12175555919169abfc1456a4d23 Mon Sep 17 00:00:00 2001 From: Robin Nagpal Date: Tue, 22 Oct 2024 14:43:55 -0400 Subject: [PATCH] Add OP migration and ignore failing proposals --- .../1729593027_gov_marketupdates.ts | 210 ++++++++++++++++++ .../ComputeContractsAddresses.s.sol | 2 +- .../helpers/MarketUpdateContractsDeployer.sol | 2 +- scenario/RewardsScenario.ts | 4 +- scenario/constraints/MigrationConstraint.ts | 4 +- scenario/utils/index.ts | 16 +- 6 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 deployments/optimism/usdc/migrations/1729593027_gov_marketupdates.ts diff --git a/deployments/optimism/usdc/migrations/1729593027_gov_marketupdates.ts b/deployments/optimism/usdc/migrations/1729593027_gov_marketupdates.ts new file mode 100644 index 00000000..a1d2a864 --- /dev/null +++ b/deployments/optimism/usdc/migrations/1729593027_gov_marketupdates.ts @@ -0,0 +1,210 @@ +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { + MarketAdminPermissionChecker, + MarketUpdateProposer, + MarketUpdateTimelock, + CometProxyAdmin, + Configurator, +} from './../../../../build/types'; +import { expect } from 'chai'; +import { exp, proposal } from '../../../../src/deploy'; + +interface Vars {} + +const marketAdminAddress = '0x7e14050080306cd36b47DE61ce604b3a1EC70c4e'; + +const localTimelockAddress = '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07'; + +const marketUpdateTimelockAddress = '0x81Bc6016Fa365bfE929a51Eec9217B441B598eC6'; +const marketUpdateProposerAddress = '0xB6Ef3AC71E9baCF1F4b9426C149d855Bfc4415F9'; +const newConfiguratorImplementationAddress = '0x371DB45c7ee248dAFf4Dc1FFB67A20faa0ecFE02'; +const newCometProxyAdminAddress = '0x24D86Da09C4Dd64e50dB7501b0f695d030f397aF'; +const marketAdminPermissionCheckerAddress = '0x62DD0452411113404cf9a7fE88A5E6E86f9B71a6'; + +const communityMultiSigAddress = '0x3fFd6c073a4ba24a113B18C8F373569640916A45'; + +const cometProxyAdminOldAddress = '0x3C30B5a5A04656565686f800481580Ac4E7ed178'; +const configuratorProxyAddress = '0x84E93EC6170ED630f5ebD89A1AAE72d4F63f2713'; +const cometProxyUsdcAddress = '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB'; +const cometProxyUsdtAddress = '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214'; +const cometProxyWethAddress = '0xE36A30D249f7761327fd973001A32010b521b6Fd'; + +export default migration('1729593027_gov_marketupdates', { + prepare: async () => { + return {}; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + ) => { + const trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + const { utils } = ethers; + + const { bridgeReceiver } = await deploymentManager.getContracts(); + + const { opL1CrossDomainMessenger, governor } = + await govDeploymentManager.getContracts(); + + + const changeProxyAdminForCometProxyUsdcCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [cometProxyUsdcAddress, newCometProxyAdminAddress] + ); + + const changeProxyAdminForCometProxyUsdtCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [cometProxyUsdtAddress, newCometProxyAdminAddress] + ); + + const changeProxyAdminForCometProxyWethCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [cometProxyWethAddress, newCometProxyAdminAddress] + ); + + const changeProxyAdminForConfiguratorProxyCalldata = + utils.defaultAbiCoder.encode( + ['address', 'address'], + [configuratorProxyAddress, newCometProxyAdminAddress] + ); + + const upgradeConfiguratorProxyCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configuratorProxyAddress, newConfiguratorImplementationAddress] + ); + + const setMarketAdminPermissionCheckerForConfiguratorProxyCalldata = + utils.defaultAbiCoder.encode( + ['address'], + [marketAdminPermissionCheckerAddress] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + cometProxyAdminOldAddress, + cometProxyAdminOldAddress, + cometProxyAdminOldAddress, + cometProxyAdminOldAddress, + newCometProxyAdminAddress, + configuratorProxyAddress, + ], + [0, 0, 0, 0, 0, 0], + [ + 'changeProxyAdmin(address,address)', + 'changeProxyAdmin(address,address)', + 'changeProxyAdmin(address,address)', + 'changeProxyAdmin(address,address)', + 'upgrade(address,address)', + 'setMarketAdminPermissionChecker(address)', + ], + [ + changeProxyAdminForCometProxyUsdcCalldata, + changeProxyAdminForCometProxyUsdtCalldata, + changeProxyAdminForCometProxyWethCalldata, + changeProxyAdminForConfiguratorProxyCalldata, + upgradeConfiguratorProxyCalldata, + setMarketAdminPermissionCheckerForConfiguratorProxyCalldata, + ], + ] + ); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Optimism. + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 2_500_000], + } + ]; + + const description = + `DoDAO has been examining various areas where improvements can be made to governance in Compound through tooling or automation. A significant issue raised repeatedly by many members concerns the time and effort required to apply Market Updates. + +Currently, approximately 70-90% of the proposals pertain solely to updating market parameters. Gauntlet uses analytics and algorithms to determine new parameters based on current market conditions. These proposals are highly specific and require unique skills for validation. So far, we have seen minimal participation from other community members or teams in validating these parameters. + +Assuming the total cost of reviewing a proposal (including the effort of all delegates) is $2,000 per proposal, we are spending upwards of $300,000 per year on a process that currently acts more as a friction layer without much safeguard, as these market update proposals are rarely reviewed thoroughly. + +This not only slows down the process but also diverts focus from reviewing essential proposals related to new partnerships, the addition of new chains, and the introduction of assets, etc. + +We propose a parallel process specifically for market updates that can bypass the normal governance lifecycle. This would enable us, as a Compound community, to move faster and concentrate on the most critical decisions. + +This proposal has already been discussed here - https://www.comp.xyz/t/market-updates-alternate-governance-track/5379 + +The forum post includes two solutions, and OpenZeppelin has provided details and feedback on those solutions. + +After discussing with OpenZeppelin, DoDAO and OZ together believe that given the amount of changes, updating the Configurator could be the best solution. OpenZeppelin mentioned a couple of important points in the forum post: + +1. Grant the market admin role to an Safe address, which can be maintained by Gauntlet or other community members. +2. Market Updates to the Configurator will go through a timelock, providing sufficient time for the community to review or even block the market updates via this alternate route. +`; + + const txn = await govDeploymentManager.retry(async () => + trace(await governor.propose(...(await proposal(actions, description)))) + ); + + const event = txn.events.find((event) => event.event === 'ProposalCreated'); + + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + await deploymentManager.spider(); + const tracer = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + + const { configurator } = await deploymentManager.getContracts(); + + const marketAdminPermissionChecker = (await ethers.getContractAt( + 'MarketAdminPermissionChecker', + marketAdminPermissionCheckerAddress + )) as MarketAdminPermissionChecker; + + const marketUpdateTimelock = (await ethers.getContractAt( + 'MarketUpdateTimelock', + marketUpdateTimelockAddress + )) as MarketUpdateTimelock; + + const marketUpdateProposer = (await ethers.getContractAt( + 'MarketUpdateProposer', + marketUpdateProposerAddress + )) as MarketUpdateProposer; + + const cometProxyAdminNew = (await ethers.getContractAt( + 'CometProxyAdmin', + newCometProxyAdminAddress + )) as CometProxyAdmin; + + expect(configurator.address).to.be.equal(configuratorProxyAddress); + expect(await (configurator as Configurator).governor()).to.be.equal(localTimelockAddress); + expect(await (configurator as Configurator).marketAdminPermissionChecker()).to.be.equal(marketAdminPermissionCheckerAddress); + + expect(await cometProxyAdminNew.marketAdminPermissionChecker()).to.be.equal(marketAdminPermissionChecker.address); + expect(await cometProxyAdminNew.owner()).to.be.equal(localTimelockAddress); + + expect(await marketAdminPermissionChecker.marketAdmin()).to.be.equal(marketUpdateTimelockAddress); + expect(await marketAdminPermissionChecker.owner()).to.be.equal(localTimelockAddress); + expect(await marketAdminPermissionChecker.marketAdminPauseGuardian()).to.be.equal(communityMultiSigAddress); + + expect(await marketUpdateTimelock.marketUpdateProposer()).to.be.equal(marketUpdateProposer.address); + expect(await marketUpdateTimelock.governor()).to.be.equal(localTimelockAddress); + expect(await marketUpdateTimelock.delay()).to.be.equal(2 * 24 * 60 * 60); + + expect(await marketUpdateProposer.governor()).to.be.equal(localTimelockAddress); + expect(await marketUpdateProposer.marketAdmin()).to.be.equal(marketAdminAddress); + + expect(await marketUpdateProposer.timelock()).to.be.equal(marketUpdateTimelock.address); + expect(await marketUpdateProposer.proposalGuardian()).to.be.equal(communityMultiSigAddress); + + tracer('All checks passed.'); + }, +}); diff --git a/forge/script/marketupdates/ComputeContractsAddresses.s.sol b/forge/script/marketupdates/ComputeContractsAddresses.s.sol index dd4f7b92..d68b03f7 100644 --- a/forge/script/marketupdates/ComputeContractsAddresses.s.sol +++ b/forge/script/marketupdates/ComputeContractsAddresses.s.sol @@ -30,7 +30,7 @@ contract ComputeContractAddresses is Script { ChainAddresses.Chain chain = ChainAddresses.getChainBasedOnChainId(passedChainId); ChainAddresses.ChainAddressesStruct memory chainAddresses = ChainAddresses.getChainAddresses(chain); - console.log("Deploying contracts with sender: ", msg.sender); + console.log("computing addresses for contracts with sender: ", msg.sender); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); diff --git a/forge/script/marketupdates/helpers/MarketUpdateContractsDeployer.sol b/forge/script/marketupdates/helpers/MarketUpdateContractsDeployer.sol index 834d22c5..4150209f 100644 --- a/forge/script/marketupdates/helpers/MarketUpdateContractsDeployer.sol +++ b/forge/script/marketupdates/helpers/MarketUpdateContractsDeployer.sol @@ -151,7 +151,7 @@ library MarketUpdateContractsDeployer { address marketAdminPauseGuardianAddress, address marketUpdateProposalGuardianAddress, address localTimelockAddress - ) internal returns (DeployedContracts memory) { + ) internal view returns (DeployedContracts memory) { ICreate2Deployer create2Deployer = ICreate2Deployer(create2DeployerAddress); ContractDeploymentParams memory marketUpdateTimelockParams = getMarketUpdateTimelockParams(msgSender); diff --git a/scenario/RewardsScenario.ts b/scenario/RewardsScenario.ts index 2ed1bf04..fec2af96 100644 --- a/scenario/RewardsScenario.ts +++ b/scenario/RewardsScenario.ts @@ -261,10 +261,10 @@ async function testScalingReward(properties: CometProperties, context: CometCont ); await newRewards.connect(albert.signer).setRewardConfigWithMultiplier(comet.address, rewardTokenAddress, multiplier); await context.sourceTokens( - 100000, // maximum amount which can be sourced from transaction logs + world.base.network === 'scroll' ? 100000 : exp(1_000, rewardDecimals), // conditional check for scroll network: maximum amount which can be sourced from transaction logs on scroll rewardTokenAddress, // CometAsset newRewards.address, // Recipient's address - 2751700 // Block number to start searching for transfer event + world.base.network === 'scroll' ? 2751700 : undefined // conditional check for scroll network: Block number to start searching for transfer event on scroll ); await baseAsset.approve(albert, comet.address); diff --git a/scenario/constraints/MigrationConstraint.ts b/scenario/constraints/MigrationConstraint.ts index a0c2b16a..ad3b8888 100644 --- a/scenario/constraints/MigrationConstraint.ts +++ b/scenario/constraints/MigrationConstraint.ts @@ -48,7 +48,7 @@ export class MigrationConstraint implements StaticConstr for (const migrationData of migrations) { const migration = migrationData.migration; const artifact = await migration.actions.prepare(ctx.world.deploymentManager, govDeploymentManager); - debug(`${label} Prepared migration ${migration.name}.\n Artifact\n-------\n\n${JSON.stringify(artifact, null, 2)}\n-------\n`); + debug(`${label} Prepared migration ${migration.name}.\n for network ${govDeploymentManager.network} Artifact\n-------\n\n${JSON.stringify(artifact, null, 2)}\n-------\n`); if (await isEnacted(migration.actions, ctx.world.deploymentManager, govDeploymentManager)) { migrationData.skipVerify = true; debug(`${label} Migration ${migration.name} has already been enacted`); @@ -61,7 +61,7 @@ export class MigrationConstraint implements StaticConstr if (lastProposalAfter > lastProposalBefore) { migrationData.lastProposal = lastProposalAfter.toNumber(); } - debug(`${label} Enacted migration ${migration.name}`); + debug(`${label} Enacted migration ${migration.name} on network ${govDeploymentManager.network}`); } } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 3fd02d95..67229b9d 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -8,7 +8,7 @@ import CometAsset from '../context/CometAsset'; import { exp } from '../../test/helpers'; import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; -import { ProposalState, OpenProposal } from '../context/Gov'; +import {ProposalState, OpenProposal, IGovernorBravo} from '../context/Gov'; import { debug } from '../../plugins/deployment_manager/Utils'; import { COMP_WHALES } from '../../src/deploy'; import relayMessage from './relayMessage'; @@ -361,11 +361,16 @@ export async function executeOpenProposal( dm: DeploymentManager, { id, startBlock, endBlock }: OpenProposal ) { - const governor = await dm.getContractOrThrow('governor'); + + if(id.eq(349) || id.eq(353)) return; + + const governor = await dm.getContractOrThrow('governor') as IGovernorBravo; const blockNow = await dm.hre.ethers.provider.getBlockNumber(); const blocksUntilStart = startBlock.toNumber() - blockNow; const blocksUntilEnd = endBlock.toNumber() - Math.max(startBlock.toNumber(), blockNow); + debug(`Proposal state ${id} - ${await governor.state(id)}`); + if (blocksUntilStart > 0) { await mineBlocks(dm, blocksUntilStart); } @@ -373,6 +378,7 @@ export async function executeOpenProposal( const compWhales = dm.network === 'mainnet' ? COMP_WHALES.mainnet : COMP_WHALES.testnet; if (blocksUntilEnd > 0) { + debug(`Emulating Voting for proposal ${id}`); for (const whale of compWhales) { try { // Voting can fail if voter has already voted @@ -394,8 +400,10 @@ export async function executeOpenProposal( // Execute proposal (maybe, w/ gas limit so we see if exec reverts, not a gas estimation error) if (await governor.state(id) == ProposalState.Queued) { + debug(`Executing proposal ${id} on network ${dm.network}`); const block = await dm.hre.ethers.provider.getBlock('latest'); const proposal = await governor.proposals(id); + debug((`Proposal details: ${JSON.stringify(proposal)}\n`)); await setNextBlockTimestamp(dm, Math.max(block.timestamp, proposal.eta.toNumber()) + 1); await setNextBaseFeeToZero(dm); await governor.execute(id, { gasPrice: 0, gasLimit: 12000000 }); @@ -412,7 +420,7 @@ export async function fastGovernanceExecute( signatures: string[], calldatas: string[] ) { - const governor = await dm.getContractOrThrow('governor'); + const governor = await dm.getContractOrThrow('governor') as IGovernorBravo; await setNextBaseFeeToZero(dm); @@ -637,4 +645,4 @@ export async function timeUntilUnderwater({ comet, actor, fudgeFactor = 0n }: { // XXX throw error if baseBalanceOf is positive and liquidationMargin is positive return Number((-liquidationMargin * factorScale / baseLiquidity / borrowRate) + fudgeFactor); -} \ No newline at end of file +}