From b1e68dd554493a22f11e009a42d6a1aa1b0403e2 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 19:48:05 +0530 Subject: [PATCH 01/15] Deployment with Governance proposal --- contracts/utils/GovProposalHelper.sol | 110 ++++++++++++++++ contracts/utils/VmHelper.sol | 21 ++++ foundry.toml | 1 + script/ExtraOGNForMigration.s.sol | 31 +++++ script/deploy/DeployManager.sol | 21 ++-- .../mainnet/011_OgnOgvMigrationScript.sol | 104 +-------------- .../mainnet/012_xOGNGovernanceScript.sol | 118 +++++++++++++++--- script/deploy/mainnet/BaseMainnetScript.sol | 17 ++- tests/governance/XOGNGovernanceForkTest.t.sol | 2 +- tests/staking/OGNRewardsSourceForkTest.t.sol | 2 +- tests/staking/XOGNStakingForkTest.t..sol | 2 +- 11 files changed, 294 insertions(+), 135 deletions(-) create mode 100644 contracts/utils/GovProposalHelper.sol create mode 100644 contracts/utils/VmHelper.sol create mode 100644 script/ExtraOGNForMigration.s.sol diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol new file mode 100644 index 00000000..d838640b --- /dev/null +++ b/contracts/utils/GovProposalHelper.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import {Addresses} from "contracts/utils/Addresses.sol"; +import "forge-std/console.sol"; + +import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/utils/Strings.sol"; +// import {IGovernor} from "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/IGovernor.sol"; + +import "contracts/utils/VmHelper.sol"; + +struct GovAction { + address target; + uint256 value; + string fullsig; + bytes data; +} + +struct GovProposal { + string description; + GovAction[] actions; +} + +library GovProposalHelper { + using VmHelper for Vm; + + function id(GovProposal memory prop) internal view returns (uint256 proposalId) { + bytes32 descriptionHash = keccak256(bytes(prop.description)); + (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getParams(prop); + + proposalId = uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); + } + + function getParams(GovProposal memory prop) + internal + view + returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) + { + uint256 actionLen = prop.actions.length; + targets = new address[](actionLen); + values = new uint256[](actionLen); + + string[] memory sigs = new string[](actionLen); + bytes[] memory data = new bytes[](actionLen); + + for (uint256 i = 0; i < actionLen; ++i) { + targets[i] = prop.actions[i].target; + sigs[i] = prop.actions[i].fullsig; + data[i] = prop.actions[i].data; + values[i] = prop.actions[i].value; + } + + calldatas = _encodeCalldata(sigs, data); + } + + function _encodeCalldata(string[] memory signatures, bytes[] memory calldatas) + private + pure + returns (bytes[] memory) + { + bytes[] memory fullcalldatas = new bytes[](calldatas.length); + + for (uint256 i = 0; i < signatures.length; ++i) { + fullcalldatas[i] = bytes(signatures[i]).length == 0 + ? calldatas[i] + : abi.encodePacked(bytes4(keccak256(bytes(signatures[i]))), calldatas[i]); + } + + return fullcalldatas; + } + + function setDescription(GovProposal storage prop, string memory description) internal { + prop.description = description; + } + + function action(GovProposal storage prop, address target, string memory fullsig, bytes memory data) internal { + prop.actions.push(GovAction({target: target, fullsig: fullsig, data: data, value: 0})); + } + + function getProposeCalldata(GovProposal memory prop) internal view returns (bytes memory proposeCalldata) { + (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getParams(prop); + + proposeCalldata = abi.encodeWithSignature( + "propose(address[] memory,uint256[] memory,bytes[] memory,string memory)", + targets, + values, + calldatas, + prop.description + ); + } + + function impersonateAndSimulate(GovProposal memory prop) internal { + address VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm vm = Vm(VM_ADDRESS); + console.log("Impersonating timelock to simulate governance proposal..."); + vm.startPrank(Addresses.TIMELOCK); + for (uint256 i = 0; i < prop.actions.length; i++) { + GovAction memory propAction = prop.actions[i]; + bytes memory sig = abi.encodePacked(bytes4(keccak256(bytes(propAction.fullsig)))); + (bool success, bytes memory data) = propAction.target.call(abi.encodePacked(sig, propAction.data)); + if (!success) { + console.log(propAction.fullsig); + revert("Governance action failed"); + } + } + vm.stopPrank(); + console.log("Governance proposal simulation complete"); + } +} diff --git a/contracts/utils/VmHelper.sol b/contracts/utils/VmHelper.sol new file mode 100644 index 00000000..0c964780 --- /dev/null +++ b/contracts/utils/VmHelper.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Vm.sol"; + +library VmHelper { + function getVM() internal view returns (Vm vm) { + address VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + vm = Vm(VM_ADDRESS); + } + + function isForkEnv(Vm vm) public view returns (bool) { + return vm.isContext(VmSafe.ForgeContext.ScriptDryRun) || vm.isContext(VmSafe.ForgeContext.Test) + || vm.isContext(VmSafe.ForgeContext.TestGroup); + } + + function isTestEnv(Vm vm) public view returns (bool) { + return vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.TestGroup); + } +} diff --git a/foundry.toml b/foundry.toml index b214348a..8aaafdf8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,7 @@ remappings = [ "contracts/=./contracts", "script/=./script", "tests/=./tests", + "utils/=./contracts/utils", "OpenZeppelin/openzeppelin-contracts@02fcc75bb7f35376c22def91b0fb9bc7a50b9458/=./lib/openzeppelin-contracts", "OpenZeppelin/openzeppelin-contracts-upgradeable@a16f26a063cd018c4c986832c3df332a131f53b9/=./lib/openzeppelin-contracts-upgradeable", "OpenZeppelin/openzeppelin-contracts@4.6.0/=./lib/openzeppelin-contracts", diff --git a/script/ExtraOGNForMigration.s.sol b/script/ExtraOGNForMigration.s.sol new file mode 100644 index 00000000..efa59dd0 --- /dev/null +++ b/script/ExtraOGNForMigration.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Script.sol"; +import {Addresses} from "contracts/utils/Addresses.sol"; +import {IMintableERC20} from "contracts/interfaces/IMintableERC20.sol"; +import {RewardsSource} from "contracts/RewardsSource.sol"; + +contract ExtraOGNForMigration is Script { + uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT + + // Ref: https://snapshot.org/#/origingov.eth/proposal/0x741893a4d9838c0b69fac03650756e21fe00ec35b5309626bb0d6b816f861f9b + uint256 public constant OGN_MINTED = 409_664_846 ether; + + function run() external { + vm.warp(OGN_EPOCH); + + IMintableERC20 ogv = IMintableERC20(Addresses.OGV); + + uint256 rewards = RewardsSource(Addresses.OGV_REWARDS_PROXY).previewRewards(); + + uint256 ogvSupply = ogv.totalSupply(); + uint256 maxOgnNeeded = ((ogvSupply + rewards) * 0.09137 ether) / 1 ether; + + console.log("OGV Supply", ogvSupply / 1 ether); + console.log("Pending OGV Rewards", rewards / 1 ether); + console.log("Max OGN Needed", maxOgnNeeded / 1 ether); + console.log("OGN from Treasury", (maxOgnNeeded - OGN_MINTED) / 1 ether); + } +} diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 6cf75bac..e7f70afc 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -10,19 +10,18 @@ import {XOGNSetupScript} from "./mainnet/010_xOGNSetupScript.sol"; import {OgnOgvMigrationScript} from "./mainnet/011_OgnOgvMigrationScript.sol"; import {XOGNGovernanceScript} from "./mainnet/012_xOGNGovernanceScript.sol"; +import "contracts/utils/VmHelper.sol"; + contract DeployManager is Script { + using VmHelper for Vm; + mapping(string => address) public deployedContracts; mapping(string => bool) public scriptsExecuted; string internal forkFileId = ""; - function isForked() public view returns (bool) { - return vm.isContext(VmSafe.ForgeContext.ScriptDryRun) || vm.isContext(VmSafe.ForgeContext.Test) - || vm.isContext(VmSafe.ForgeContext.TestGroup); - } - function getDeploymentFilePath() public view returns (string memory) { - return isForked() ? getForkDeploymentFilePath() : getMainnetDeploymentFilePath(); + return vm.isForkEnv() ? getForkDeploymentFilePath() : getMainnetDeploymentFilePath(); } function getMainnetDeploymentFilePath() public view returns (string memory) { @@ -30,11 +29,11 @@ contract DeployManager is Script { } function getForkDeploymentFilePath() public view returns (string memory) { - return string(abi.encodePacked(vm.projectRoot(), "/build/deployments-fork-", forkFileId, ".json")); + return string(abi.encodePacked(vm.projectRoot(), "/build/deployments-fork", forkFileId, ".json")); } function setUp() external { - forkFileId = Strings.toString(block.timestamp); + forkFileId = vm.isTestEnv() ? Strings.toString(block.timestamp) : ""; string memory chainIdStr = Strings.toString(block.chainid); string memory chainIdKey = string(abi.encodePacked(".", chainIdStr)); @@ -53,7 +52,7 @@ contract DeployManager is Script { ); } - if (isForked()) { + if (vm.isForkEnv()) { // Duplicate Mainnet File vm.writeFile(getForkDeploymentFilePath(), vm.readFile(mainnetFilePath)); } @@ -67,6 +66,10 @@ contract DeployManager is Script { } function _runDeployFile(BaseMainnetScript deployScript) internal { + if (deployScript.skip()) { + return; + } + string memory chainIdStr = Strings.toString(block.chainid); string memory chainIdKey = string(abi.encodePacked(".", chainIdStr)); diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index f1f5c9f8..2bb84103 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -20,10 +20,10 @@ import {GovFive} from "contracts/utils/GovFive.sol"; import {IMintableERC20} from "contracts/interfaces/IMintableERC20.sol"; import {IOGNGovernance} from "contracts/interfaces/IOGNGovernance.sol"; -contract OgnOgvMigrationScript is BaseMainnetScript { - using GovFive for GovFive.GovFiveProposal; +import {GovProposal, GovProposalHelper} from "contracts/utils/GovProposalHelper.sol"; - GovFive.GovFiveProposal govFive; +contract OgnOgvMigrationScript is BaseMainnetScript { + using GovProposalHelper for GovProposal; string public constant override DEPLOY_NAME = "011_OgnOgvMigration"; @@ -53,103 +53,7 @@ contract OgnOgvMigrationScript is BaseMainnetScript { console.log("- Migrator init"); migratorProxy.initialize(address(migratorImpl), Addresses.TIMELOCK, ""); - - _buildGnosisTx(); } - function _buildGnosisTx() internal { - Timelock timelock = Timelock(payable(Addresses.TIMELOCK)); - - address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; - address veOgvImpl = deployedContracts["VEOGV_IMPL"]; - - govFive.setName("OGV Migration to OGN"); - // Todo: Fuller description - govFive.setDescription("Deploy OGV-OGN migration contracts and revoke OGV Governance roles"); - - console.log(address(veOgvImpl)); - govFive.action(Addresses.VEOGV, "upgradeTo(address)", abi.encode(veOgvImpl)); - - govFive.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.PROPOSER_ROLE(), Addresses.GOVERNOR_FIVE) - ); - govFive.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.CANCELLER_ROLE(), Addresses.GOVERNOR_FIVE) - ); - govFive.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.EXECUTOR_ROLE(), Addresses.GOVERNOR_FIVE) - ); - - govFive.action(Addresses.OUSD_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OUSD_BUYBACK_IMPL)); // Todo, use latest deployed address - govFive.action(Addresses.OUSD_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); - govFive.action(Addresses.OETH_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OETH_BUYBACK_IMPL)); // Todo, use latest deployed address - govFive.action(Addresses.OETH_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); - - // Mint token proposal from OGN governance - IMintableERC20 ogv = IMintableERC20(Addresses.OGV); - // Mint additional OGN, will get returned after starting migration - uint256 ognToMint = ((ogv.totalSupply() * 0.09137 ether) / 1 ether) + 1_000_000 ether; - - address[] memory targets = new address[](1); - string[] memory sigs = new string[](1); - bytes[] memory calldatas = new bytes[](1); - - // OGN Gov 1: Mint OGN - targets[0] = Addresses.OGN; - sigs[0] = "mint(address,uint256)"; - calldatas[0] = abi.encode(deployedContracts["MIGRATOR"], ognToMint); - - govFive.action( - Addresses.OGN_GOVERNOR, - "propose(address[],string[],bytes[],string)", - abi.encode(targets, sigs, calldatas, "") - ); - - if (!isForked) { - govFive.printTxData(); - } - } - - function _fork() internal override { - // Simulate execute on fork - govFive.execute(); - - vm.startPrank(Addresses.GOV_MULTISIG); - - IOGNGovernance ognGovernance = IOGNGovernance(Addresses.OGN_GOVERNOR); - uint256 proposalId = ognGovernance.proposalCount(); - - uint256 state = ognGovernance.state(proposalId); - - if (state == 0) { - console.log("Queueing OGN multisig proposal..."); - ognGovernance.queue(proposalId); - state = ognGovernance.state(proposalId); - } - - if (state == 1) { - console.log("Executing OGN multisig proposal..."); - vm.warp(block.timestamp + 2 days); - ognGovernance.execute(proposalId); - } - vm.stopPrank(); - - IMintableERC20 ogn = IMintableERC20(Addresses.OGN); - - // Start migration - vm.startPrank(Addresses.TIMELOCK); - // TODO: To be called by multisig after mint proposal is executed - Migrator migrator = Migrator(deployedContracts["MIGRATOR"]); - migrator.start(); - migrator.transferExcessTokens(Addresses.GOV_MULTISIG); - vm.stopPrank(); - - console.log("Migration started"); - } + function _fork() internal override {} } diff --git a/script/deploy/mainnet/012_xOGNGovernanceScript.sol b/script/deploy/mainnet/012_xOGNGovernanceScript.sol index 14b6c37d..dc7d54cd 100644 --- a/script/deploy/mainnet/012_xOGNGovernanceScript.sol +++ b/script/deploy/mainnet/012_xOGNGovernanceScript.sol @@ -26,13 +26,24 @@ import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/token/ERC20/extensio import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/TimelockController.sol"; contract XOGNGovernanceScript is BaseMainnetScript { - using GovFive for GovFive.GovFiveProposal; + using GovProposalHelper for GovProposal; - GovFive.GovFiveProposal govFive; + GovProposal govProposal; string public constant override DEPLOY_NAME = "012_xOGNGovernance"; uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT + + // Ref: https://snapshot.org/#/origingov.eth/proposal/0x741893a4d9838c0b69fac03650756e21fe00ec35b5309626bb0d6b816f861f9b + uint256 public constant OGN_TO_MINT = 409_664_846 ether; + + // From `script/ExtraOGNForMigration.s.sol`, rounded off + uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_000_000 ether; + + // TODO: Find right number + uint256 public constant EXTRA_OGN_FOR_REWARDS = 300_000_000 ether; + + // TODO: Confirm reward rate uint256 constant REWARDS_PER_SECOND = 300000 ether / uint256(24 * 60 * 60); // 300k per day constructor() {} @@ -47,45 +58,114 @@ contract XOGNGovernanceScript is BaseMainnetScript { _recordDeploy("XOGN_GOV", address(governance)); - _buildGnosisTx(); + _buildGovernanceProposal(); } - function _buildGnosisTx() internal { + function _buildGovernanceProposal() internal { Timelock timelock = Timelock(payable(Addresses.TIMELOCK)); address xognGov = deployedContracts["XOGN_GOV"]; - govFive.setName("Enable OGN Governance & Begin Rewards"); + address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; + address veOgvImpl = deployedContracts["VEOGV_IMPL"]; + + govProposal.setDescription("Deploy OGV-OGN migration contracts and revoke OGV Governance roles"); + + // Realize any pending rewards + govProposal.action(Addresses.VEOGV, "collectRewards()", ""); + // Upgrade veOGV implementation + govProposal.action(Addresses.VEOGV, "upgradeTo(address)", abi.encode(veOgvImpl)); + + // Revoke access from OGV governance + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.PROPOSER_ROLE(), Addresses.GOVERNOR_FIVE) + ); + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.CANCELLER_ROLE(), Addresses.GOVERNOR_FIVE) + ); + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.EXECUTOR_ROLE(), Addresses.GOVERNOR_FIVE) + ); + + // Upgrade buyback contracts & configure them + govProposal.action(Addresses.OUSD_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OUSD_BUYBACK_IMPL)); + govProposal.action(Addresses.OUSD_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); + govProposal.action(Addresses.OETH_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OETH_BUYBACK_IMPL)); + govProposal.action(Addresses.OETH_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); + + // Mint OGN required + govProposal.action( + Addresses.OGN, "mint(address,uint256)", abi.encode(deployedContracts["MIGRATOR"], OGN_TO_MINT) + ); - govFive.setDescription("Grant roles on Timelock to OGN Governance"); + // Transfer in excess OGN from Multisig (for migration) + govProposal.action( + Addresses.OGN, + "transferFrom(address,address,uint256)", + abi.encode(Addresses.GOV_MULTISIG, deployedContracts["MIGRATOR"], EXTRA_OGN_FOR_MIGRATION) + ); - govFive.action(Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.PROPOSER_ROLE(), xognGov)); - govFive.action(Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.CANCELLER_ROLE(), xognGov)); - govFive.action(Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.EXECUTOR_ROLE(), xognGov)); + // Transfer in OGN from Multisig (for rewards) + govProposal.action( + Addresses.OGN, + "transferFrom(address,address,uint256)", + abi.encode(Addresses.GOV_MULTISIG, deployedContracts["OGN_REWARDS_SOURCE"], EXTRA_OGN_FOR_REWARDS) + ); // Enable rewards for staking - govFive.action( + govProposal.action( deployedContracts["OGN_REWARDS_SOURCE"], "setRewardsPerSecond(uint192)", abi.encode(uint192(REWARDS_PER_SECOND)) ); - if (!isForked) { - govFive.printTxData(); - } + // Start migration + govProposal.action(deployedContracts["MIGRATOR"], "start()", ""); + + // Ensure solvency and transfer out excess OGN + govProposal.action( + deployedContracts["MIGRATOR"], "transferExcessTokens(address)", abi.encode(Addresses.GOV_MULTISIG) + ); + + // Grant access to OGN Governance + govProposal.action( + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.PROPOSER_ROLE(), xognGov) + ); + govProposal.action( + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.CANCELLER_ROLE(), xognGov) + ); + govProposal.action( + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.EXECUTOR_ROLE(), xognGov) + ); } function _fork() internal override { IMintableERC20 ogn = IMintableERC20(Addresses.OGN); - // Mint enough OGN to fund 100 days of rewards - vm.prank(Addresses.OGN_GOVERNOR); - ogn.mint(deployedContracts["OGN_REWARDS_SOURCE"], 30_000_000 ether); + // Make sure multisig has enough of OGN + // to fund migration and rewards + uint256 additionalOGN = EXTRA_OGN_FOR_MIGRATION + EXTRA_OGN_FOR_REWARDS; + vm.prank(Addresses.TIMELOCK); + ogn.mint(Addresses.GOV_MULTISIG, additionalOGN); + + // And timelock can move it + vm.prank(Addresses.GOV_MULTISIG); + ogn.approve(Addresses.TIMELOCK, additionalOGN); // Go to the start of everything - vm.warp(OGN_EPOCH); + vm.warp(OGN_EPOCH - 2 days); // 28th of May + + // Simulate execute on fork by impersonating Timelock + govProposal.impersonateAndSimulate(); + } - // Simulate execute on fork - govFive.execute(); + function skip() external view override returns (bool) { + return false; } } diff --git a/script/deploy/mainnet/BaseMainnetScript.sol b/script/deploy/mainnet/BaseMainnetScript.sol index 5effa75c..6498f016 100644 --- a/script/deploy/mainnet/BaseMainnetScript.sol +++ b/script/deploy/mainnet/BaseMainnetScript.sol @@ -6,8 +6,14 @@ import "forge-std/Script.sol"; import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/utils/Strings.sol"; import {Addresses} from "contracts/utils/Addresses.sol"; +import {GovProposal, GovProposalHelper} from "contracts/utils/GovProposalHelper.sol"; + +import "utils/VmHelper.sol"; abstract contract BaseMainnetScript is Script { + using VmHelper for Vm; + using GovProposalHelper for GovProposal; + uint256 public deployBlockNum = type(uint256).max; bool isForked = false; @@ -48,8 +54,7 @@ abstract contract BaseMainnetScript is Script { return; } - isForked = vm.isContext(VmSafe.ForgeContext.ScriptDryRun) || vm.isContext(VmSafe.ForgeContext.Test) - || vm.isContext(VmSafe.ForgeContext.TestGroup); + isForked = vm.isForkEnv(); if (isForked) { address impersonator = Addresses.INITIAL_DEPLOYER; @@ -74,7 +79,11 @@ abstract contract BaseMainnetScript is Script { function DEPLOY_NAME() external view virtual returns (string memory); - function _execute() internal virtual {} + function skip() external view virtual returns (bool) { + return false; + } + + function _execute() internal virtual; - function _fork() internal virtual {} + function _fork() internal virtual; } diff --git a/tests/governance/XOGNGovernanceForkTest.t.sol b/tests/governance/XOGNGovernanceForkTest.t.sol index dca93607..37a1f7fc 100644 --- a/tests/governance/XOGNGovernanceForkTest.t.sol +++ b/tests/governance/XOGNGovernanceForkTest.t.sol @@ -48,7 +48,7 @@ contract XOGNGovernanceForkTest is Test { ognRewardsSource = deployManager.getDeployment("OGN_REWARDS_SOURCE"); - vm.startPrank(Addresses.OGN_GOVERNOR); + vm.startPrank(Addresses.TIMELOCK); ogn.mint(alice, 200000 ether); ogn.mint(bob, 200000 ether); ogn.mint(xognWhale, 1000_000_000 ether); diff --git a/tests/staking/OGNRewardsSourceForkTest.t.sol b/tests/staking/OGNRewardsSourceForkTest.t.sol index 0578fab0..c27828fb 100644 --- a/tests/staking/OGNRewardsSourceForkTest.t.sol +++ b/tests/staking/OGNRewardsSourceForkTest.t.sol @@ -47,7 +47,7 @@ contract OGNRewardsSourceForkTest is Test { ognRewardsSource = FixedRateRewardsSource(deployManager.getDeployment("OGN_REWARDS_SOURCE")); - vm.startPrank(Addresses.OGN_GOVERNOR); + vm.startPrank(Addresses.TIMELOCK); ogn.mint(alice, 200000 ether); ogn.mint(bob, 200000 ether); vm.stopPrank(); diff --git a/tests/staking/XOGNStakingForkTest.t..sol b/tests/staking/XOGNStakingForkTest.t..sol index db1a188c..89fcadf6 100644 --- a/tests/staking/XOGNStakingForkTest.t..sol +++ b/tests/staking/XOGNStakingForkTest.t..sol @@ -48,7 +48,7 @@ contract XOGNStakingForkTest is Test { ognRewardsSource = deployManager.getDeployment("OGN_REWARDS_SOURCE"); - vm.startPrank(Addresses.OGN_GOVERNOR); + vm.startPrank(Addresses.TIMELOCK); ogn.mint(alice, 200000 ether); ogn.mint(bob, 200000 ether); ogn.mint(xognWhale, 1000_000_000 ether); From 3b16c0389dab454cf915a3aa7ea27f7b0a426e1e Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 19:53:57 +0530 Subject: [PATCH 02/15] Fix launch date --- tests/staking/OGNRewardsSourceForkTest.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/staking/OGNRewardsSourceForkTest.t.sol b/tests/staking/OGNRewardsSourceForkTest.t.sol index c27828fb..644fb6ba 100644 --- a/tests/staking/OGNRewardsSourceForkTest.t.sol +++ b/tests/staking/OGNRewardsSourceForkTest.t.sol @@ -67,7 +67,7 @@ contract OGNRewardsSourceForkTest is Test { } function testRewardDistribution() external { - if (block.timestamp < OGN_EPOCH) { + if (block.timestamp < (OGN_EPOCH - 2 days)) { // If it's post launch date, skip this test (uint64 lastColect,) = ognRewardsSource.rewardConfig(); assertEq(lastColect, OGN_EPOCH, "last collect not updated (before deploy)"); From eb4ad03d9a06fb083950781582c12a99c9c49a62 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 21:09:30 +0530 Subject: [PATCH 03/15] Split proposal --- script/deploy/DeployManager.sol | 2 +- .../mainnet/011_OgnOgvMigrationScript.sol | 115 ++++++++++++++++- .../mainnet/012_xOGNGovernanceScript.sol | 122 ++++-------------- 3 files changed, 132 insertions(+), 107 deletions(-) diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index e7f70afc..36a4ecbe 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -33,7 +33,7 @@ contract DeployManager is Script { } function setUp() external { - forkFileId = vm.isTestEnv() ? Strings.toString(block.timestamp) : ""; + forkFileId = Strings.toString(block.timestamp); string memory chainIdStr = Strings.toString(block.chainid); string memory chainIdKey = string(abi.encodePacked(".", chainIdStr)); diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index 2bb84103..6905224e 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -7,18 +7,12 @@ import {Vm} from "forge-std/Vm.sol"; import {Addresses} from "contracts/utils/Addresses.sol"; -import {FixedRateRewardsSourceProxy} from "contracts/upgrades/FixedRateRewardsSourceProxy.sol"; -import {ExponentialStakingProxy} from "contracts/upgrades/ExponentialStakingProxy.sol"; import {MigratorProxy} from "contracts/upgrades/MigratorProxy.sol"; -import {ExponentialStaking} from "contracts/ExponentialStaking.sol"; -import {FixedRateRewardsSource} from "contracts/FixedRateRewardsSource.sol"; import {OgvStaking} from "contracts/OgvStaking.sol"; import {Migrator} from "contracts/Migrator.sol"; import {Timelock} from "contracts/Timelock.sol"; -import {GovFive} from "contracts/utils/GovFive.sol"; import {IMintableERC20} from "contracts/interfaces/IMintableERC20.sol"; -import {IOGNGovernance} from "contracts/interfaces/IOGNGovernance.sol"; import {GovProposal, GovProposalHelper} from "contracts/utils/GovProposalHelper.sol"; @@ -27,6 +21,22 @@ contract OgnOgvMigrationScript is BaseMainnetScript { string public constant override DEPLOY_NAME = "011_OgnOgvMigration"; + GovProposal govProposal; + + uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT + + // Ref: https://snapshot.org/#/origingov.eth/proposal/0x741893a4d9838c0b69fac03650756e21fe00ec35b5309626bb0d6b816f861f9b + uint256 public constant OGN_TO_MINT = 409_664_846 ether; + + // From `script/ExtraOGNForMigration.s.sol`, rounded off + uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_000_000 ether; + + // TODO: Find right number + uint256 public constant EXTRA_OGN_FOR_REWARDS = 300_000_000 ether; + + // TODO: Confirm reward rate + uint256 constant REWARDS_PER_SECOND = 300000 ether / uint256(24 * 60 * 60); // 300k per day + constructor() {} function _execute() internal override { @@ -53,7 +63,98 @@ contract OgnOgvMigrationScript is BaseMainnetScript { console.log("- Migrator init"); migratorProxy.initialize(address(migratorImpl), Addresses.TIMELOCK, ""); + + _buildGovernanceProposal(); } - function _fork() internal override {} + function _buildGovernanceProposal() internal { + Timelock timelock = Timelock(payable(Addresses.TIMELOCK)); + + address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; + address veOgvImpl = deployedContracts["VEOGV_IMPL"]; + + govProposal.setDescription("Deploy OGV-OGN migration contracts and revoke OGV Governance roles"); + + // Realize any pending rewards + govProposal.action(Addresses.VEOGV, "collectRewards()", ""); + // Upgrade veOGV implementation + govProposal.action(Addresses.VEOGV, "upgradeTo(address)", abi.encode(veOgvImpl)); + + // Revoke access from OGV governance + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.PROPOSER_ROLE(), Addresses.GOVERNOR_FIVE) + ); + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.CANCELLER_ROLE(), Addresses.GOVERNOR_FIVE) + ); + govProposal.action( + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.EXECUTOR_ROLE(), Addresses.GOVERNOR_FIVE) + ); + + // Upgrade buyback contracts & configure them + govProposal.action(Addresses.OUSD_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OUSD_BUYBACK_IMPL)); + govProposal.action(Addresses.OUSD_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); + govProposal.action(Addresses.OETH_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OETH_BUYBACK_IMPL)); + govProposal.action(Addresses.OETH_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); + + // Mint OGN required + govProposal.action( + Addresses.OGN, "mint(address,uint256)", abi.encode(deployedContracts["MIGRATOR"], OGN_TO_MINT) + ); + + // Transfer in excess OGN from Multisig (for migration) + govProposal.action( + Addresses.OGN, + "transferFrom(address,address,uint256)", + abi.encode(Addresses.GOV_MULTISIG, deployedContracts["MIGRATOR"], EXTRA_OGN_FOR_MIGRATION) + ); + + // Transfer in OGN from Multisig (for rewards) + govProposal.action( + Addresses.OGN, + "transferFrom(address,address,uint256)", + abi.encode(Addresses.GOV_MULTISIG, deployedContracts["OGN_REWARDS_SOURCE"], EXTRA_OGN_FOR_REWARDS) + ); + + // Enable rewards for staking + govProposal.action( + deployedContracts["OGN_REWARDS_SOURCE"], + "setRewardsPerSecond(uint192)", + abi.encode(uint192(REWARDS_PER_SECOND)) + ); + + // Start migration + govProposal.action(deployedContracts["MIGRATOR"], "start()", ""); + + // Ensure solvency and transfer out excess OGN + govProposal.action( + deployedContracts["MIGRATOR"], "transferExcessTokens(address)", abi.encode(Addresses.GOV_MULTISIG) + ); + } + + function _fork() internal override { + IMintableERC20 ogn = IMintableERC20(Addresses.OGN); + + // Make sure multisig has enough of OGN + // to fund migration and rewards + uint256 additionalOGN = EXTRA_OGN_FOR_MIGRATION + EXTRA_OGN_FOR_REWARDS; + vm.prank(Addresses.TIMELOCK); + ogn.mint(Addresses.GOV_MULTISIG, additionalOGN); + + // And timelock can move it + vm.prank(Addresses.GOV_MULTISIG); + ogn.approve(Addresses.TIMELOCK, additionalOGN); + + // Go to the start of everything + vm.warp(OGN_EPOCH - 2 days); // 28th of May + + // Simulate execute on fork by impersonating Timelock + govProposal.impersonateAndSimulate(); + } } diff --git a/script/deploy/mainnet/012_xOGNGovernanceScript.sol b/script/deploy/mainnet/012_xOGNGovernanceScript.sol index dc7d54cd..508eda01 100644 --- a/script/deploy/mainnet/012_xOGNGovernanceScript.sol +++ b/script/deploy/mainnet/012_xOGNGovernanceScript.sol @@ -7,45 +7,26 @@ import {Vm} from "forge-std/Vm.sol"; import {Addresses} from "contracts/utils/Addresses.sol"; -import {FixedRateRewardsSourceProxy} from "contracts/upgrades/FixedRateRewardsSourceProxy.sol"; -import {ExponentialStakingProxy} from "contracts/upgrades/ExponentialStakingProxy.sol"; -import {MigratorProxy} from "contracts/upgrades/MigratorProxy.sol"; - -import {ExponentialStaking} from "contracts/ExponentialStaking.sol"; -import {FixedRateRewardsSource} from "contracts/FixedRateRewardsSource.sol"; -import {OgvStaking} from "contracts/OgvStaking.sol"; -import {Migrator} from "contracts/Migrator.sol"; import {Timelock} from "contracts/Timelock.sol"; import {Governance} from "contracts/Governance.sol"; import {GovFive} from "contracts/utils/GovFive.sol"; -import {IMintableERC20} from "contracts/interfaces/IMintableERC20.sol"; +import {VmHelper} from "utils/VmHelper.sol"; import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/token/ERC20/extensions/ERC20Votes.sol"; import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/TimelockController.sol"; contract XOGNGovernanceScript is BaseMainnetScript { - using GovProposalHelper for GovProposal; + using GovFive for GovFive.GovFiveProposal; + using VmHelper for Vm; - GovProposal govProposal; + GovFive.GovFiveProposal govProposal; string public constant override DEPLOY_NAME = "012_xOGNGovernance"; uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT - // Ref: https://snapshot.org/#/origingov.eth/proposal/0x741893a4d9838c0b69fac03650756e21fe00ec35b5309626bb0d6b816f861f9b - uint256 public constant OGN_TO_MINT = 409_664_846 ether; - - // From `script/ExtraOGNForMigration.s.sol`, rounded off - uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_000_000 ether; - - // TODO: Find right number - uint256 public constant EXTRA_OGN_FOR_REWARDS = 300_000_000 ether; - - // TODO: Confirm reward rate - uint256 constant REWARDS_PER_SECOND = 300000 ether / uint256(24 * 60 * 60); // 300k per day - constructor() {} function _execute() internal override { @@ -69,103 +50,46 @@ contract XOGNGovernanceScript is BaseMainnetScript { address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; address veOgvImpl = deployedContracts["VEOGV_IMPL"]; - govProposal.setDescription("Deploy OGV-OGN migration contracts and revoke OGV Governance roles"); + govProposal.setName("Grant access to OGN Governance"); - // Realize any pending rewards - govProposal.action(Addresses.VEOGV, "collectRewards()", ""); - // Upgrade veOGV implementation - govProposal.action(Addresses.VEOGV, "upgradeTo(address)", abi.encode(veOgvImpl)); + govProposal.setDescription("Grant access to OGN Governance"); - // Revoke access from OGV governance - govProposal.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.PROPOSER_ROLE(), Addresses.GOVERNOR_FIVE) - ); - govProposal.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.CANCELLER_ROLE(), Addresses.GOVERNOR_FIVE) - ); - govProposal.action( - Addresses.TIMELOCK, - "revokeRole(bytes32,address)", - abi.encode(timelock.EXECUTOR_ROLE(), Addresses.GOVERNOR_FIVE) - ); - - // Upgrade buyback contracts & configure them - govProposal.action(Addresses.OUSD_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OUSD_BUYBACK_IMPL)); - govProposal.action(Addresses.OUSD_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); - govProposal.action(Addresses.OETH_BUYBACK, "upgradeTo(address)", abi.encode(Addresses.OETH_BUYBACK_IMPL)); - govProposal.action(Addresses.OETH_BUYBACK, "setRewardsSource(address)", abi.encode(ognRewardsSourceProxy)); - - // Mint OGN required - govProposal.action( - Addresses.OGN, "mint(address,uint256)", abi.encode(deployedContracts["MIGRATOR"], OGN_TO_MINT) - ); - - // Transfer in excess OGN from Multisig (for migration) - govProposal.action( - Addresses.OGN, - "transferFrom(address,address,uint256)", - abi.encode(Addresses.GOV_MULTISIG, deployedContracts["MIGRATOR"], EXTRA_OGN_FOR_MIGRATION) - ); - - // Transfer in OGN from Multisig (for rewards) + // Grant access to OGN Governance govProposal.action( - Addresses.OGN, - "transferFrom(address,address,uint256)", - abi.encode(Addresses.GOV_MULTISIG, deployedContracts["OGN_REWARDS_SOURCE"], EXTRA_OGN_FOR_REWARDS) + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.PROPOSER_ROLE(), xognGov) ); - - // Enable rewards for staking govProposal.action( - deployedContracts["OGN_REWARDS_SOURCE"], - "setRewardsPerSecond(uint192)", - abi.encode(uint192(REWARDS_PER_SECOND)) + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.CANCELLER_ROLE(), xognGov) ); - - // Start migration - govProposal.action(deployedContracts["MIGRATOR"], "start()", ""); - - // Ensure solvency and transfer out excess OGN govProposal.action( - deployedContracts["MIGRATOR"], "transferExcessTokens(address)", abi.encode(Addresses.GOV_MULTISIG) + Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.EXECUTOR_ROLE(), xognGov) ); - // Grant access to OGN Governance + // Revoke access from Multisig govProposal.action( - Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.PROPOSER_ROLE(), xognGov) + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.PROPOSER_ROLE(), Addresses.GOV_MULTISIG) ); govProposal.action( - Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.CANCELLER_ROLE(), xognGov) + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.CANCELLER_ROLE(), Addresses.GOV_MULTISIG) ); govProposal.action( - Addresses.TIMELOCK, "grantRole(bytes32,address)", abi.encode(timelock.EXECUTOR_ROLE(), xognGov) + Addresses.TIMELOCK, + "revokeRole(bytes32,address)", + abi.encode(timelock.EXECUTOR_ROLE(), Addresses.GOV_MULTISIG) ); } function _fork() internal override { - IMintableERC20 ogn = IMintableERC20(Addresses.OGN); - - // Make sure multisig has enough of OGN - // to fund migration and rewards - uint256 additionalOGN = EXTRA_OGN_FOR_MIGRATION + EXTRA_OGN_FOR_REWARDS; - vm.prank(Addresses.TIMELOCK); - ogn.mint(Addresses.GOV_MULTISIG, additionalOGN); - - // And timelock can move it - vm.prank(Addresses.GOV_MULTISIG); - ogn.approve(Addresses.TIMELOCK, additionalOGN); - - // Go to the start of everything - vm.warp(OGN_EPOCH - 2 days); // 28th of May - // Simulate execute on fork by impersonating Timelock - govProposal.impersonateAndSimulate(); + govProposal.execute(); } function skip() external view override returns (bool) { - return false; + // Don't deploy on Mainnet for now + return !vm.isForkEnv(); } } From f3dc69a95b1ef5b30b32117d611eb31d256e5e13 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 22:12:37 +0530 Subject: [PATCH 04/15] Full proposal execution flow --- contracts/utils/GovProposalHelper.sol | 80 +++++++++++++++++-- .../mainnet/011_OgnOgvMigrationScript.sol | 8 +- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index d838640b..378b00a0 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -6,7 +6,8 @@ import {Addresses} from "contracts/utils/Addresses.sol"; import "forge-std/console.sol"; import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/utils/Strings.sol"; -// import {IGovernor} from "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/IGovernor.sol"; +import {IGovernor} from "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/IGovernor.sol"; +import {Governance} from "../Governance.sol"; import "contracts/utils/VmHelper.sol"; @@ -82,11 +83,7 @@ library GovProposalHelper { (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getParams(prop); proposeCalldata = abi.encodeWithSignature( - "propose(address[] memory,uint256[] memory,bytes[] memory,string memory)", - targets, - values, - calldatas, - prop.description + "propose(address[],uint256[],bytes[],string)", targets, values, calldatas, prop.description ); } @@ -107,4 +104,75 @@ library GovProposalHelper { vm.stopPrank(); console.log("Governance proposal simulation complete"); } + + function simulate(GovProposal memory prop, address governanceAddr) internal { + address VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm vm = Vm(VM_ADDRESS); + + uint256 proposalId = id(prop); + + Governance governance = Governance(payable(governanceAddr)); + + vm.startPrank(Addresses.GOV_MULTISIG); + + uint256 snapshot = governance.proposalSnapshot(proposalId); + + if (snapshot == 0) { + // Proposal doesn't exists, create it + console.log("Creating proposal..."); + bytes memory proposeData = getProposeCalldata(prop); + (bool success, bytes memory data) = governanceAddr.call(proposeData); + } + + IGovernor.ProposalState state = governance.state(proposalId); + + if (state == IGovernor.ProposalState.Executed) { + // Skipping executed proposal + return; + } + + if (state == IGovernor.ProposalState.Pending) { + console.log("Waiting for voting period..."); + // Wait for voting to start + vm.roll(block.number + 10); + vm.warp(block.timestamp + 10 minutes); + + state = governance.state(proposalId); + } + + if (state == IGovernor.ProposalState.Active) { + console.log("Voting on proposal..."); + // Vote on proposal + governance.castVote(proposalId, 1); + // Wait for voting to end + vm.roll(governance.proposalDeadline(proposalId) + 20); + vm.warp(block.timestamp + 2 days); + + state = governance.state(proposalId); + } + + if (state == IGovernor.ProposalState.Succeeded) { + console.log("Queuing proposal..."); + governance.queue(proposalId); + + state = governance.state(proposalId); + } + + if (state == IGovernor.ProposalState.Queued) { + console.log("Executing proposal"); + // Wait for timelock + vm.roll(governance.proposalEta(proposalId) + 20); + vm.warp(block.timestamp + 2 days); + + governance.execute(proposalId); + + state = governance.state(proposalId); + } + + if (state != IGovernor.ProposalState.Executed) { + revert("Unexpected proposal state"); + } + + vm.stopPrank(); + } } diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index 6905224e..2a09ba88 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -151,10 +151,10 @@ contract OgnOgvMigrationScript is BaseMainnetScript { vm.prank(Addresses.GOV_MULTISIG); ogn.approve(Addresses.TIMELOCK, additionalOGN); - // Go to the start of everything - vm.warp(OGN_EPOCH - 2 days); // 28th of May + // Simulate proposal on OGV Governance + govProposal.simulate(Addresses.GOVERNOR_FIVE); - // Simulate execute on fork by impersonating Timelock - govProposal.impersonateAndSimulate(); + // Go to the start of everything + vm.warp(OGN_EPOCH); // 30th of May } } From d9d2e2e5ad55b090a751774b5450c5b0c0995f72 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 22:26:15 +0530 Subject: [PATCH 05/15] Get rid of hardcoded timestamps --- script/deploy/mainnet/011_OgnOgvMigrationScript.sol | 3 --- tests/staking/OGNRewardsSourceForkTest.t.sol | 6 ------ 2 files changed, 9 deletions(-) diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index 2a09ba88..87be6d6e 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -153,8 +153,5 @@ contract OgnOgvMigrationScript is BaseMainnetScript { // Simulate proposal on OGV Governance govProposal.simulate(Addresses.GOVERNOR_FIVE); - - // Go to the start of everything - vm.warp(OGN_EPOCH); // 30th of May } } diff --git a/tests/staking/OGNRewardsSourceForkTest.t.sol b/tests/staking/OGNRewardsSourceForkTest.t.sol index 644fb6ba..dc7f16c5 100644 --- a/tests/staking/OGNRewardsSourceForkTest.t.sol +++ b/tests/staking/OGNRewardsSourceForkTest.t.sol @@ -67,12 +67,6 @@ contract OGNRewardsSourceForkTest is Test { } function testRewardDistribution() external { - if (block.timestamp < (OGN_EPOCH - 2 days)) { - // If it's post launch date, skip this test - (uint64 lastColect,) = ognRewardsSource.rewardConfig(); - assertEq(lastColect, OGN_EPOCH, "last collect not updated (before deploy)"); - } - uint256 rewardsBefore = ognRewardsSource.previewRewards(); vm.warp(block.timestamp + 1 days); assertEq( From 1d1f42531f48bc6a277f99afd685f7d1e15b58f8 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 May 2024 22:47:34 +0530 Subject: [PATCH 06/15] Update readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6efe32b3..282470a5 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ In another terminal: brownie console --network hardhat-fork ``` +## Deploying contracts (with Foundry) +``` +$ DEPLOYER_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY forge script script/deploy/DeployManager.sol:DeployManager --fork-url $ALCHEMY_PROVIDER_URL --slow --legacy --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY +``` + ## Deploying contracts Setup environment variables: From 1043bffa39a679c7db500892fce5a96190ffe9e3 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Fri, 24 May 2024 17:38:41 +0530 Subject: [PATCH 07/15] Tweaks --- contracts/utils/GovProposalHelper.sol | 12 ++- script/deploy/DeployManager.sol | 74 +++++++++---------- .../mainnet/011_OgnOgvMigrationScript.sol | 14 ++-- .../mainnet/012_xOGNGovernanceScript.sol | 6 +- script/deploy/mainnet/BaseMainnetScript.sol | 7 ++ 5 files changed, 63 insertions(+), 50 deletions(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index 378b00a0..13ba78aa 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -118,9 +118,17 @@ library GovProposalHelper { uint256 snapshot = governance.proposalSnapshot(proposalId); if (snapshot == 0) { - // Proposal doesn't exists, create it - console.log("Creating proposal..."); bytes memory proposeData = getProposeCalldata(prop); + + console.log("----------------------------------"); + console.log("Create following tx on Governance:"); + console.log("To:", governanceAddr); + console.log("Data:"); + console.logBytes(proposeData); + console.log("----------------------------------"); + + // Proposal doesn't exists, create it + console.log("Creating proposal on fork..."); (bool success, bytes memory data) = governanceAddr.call(proposeData); } diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 36a4ecbe..9e77a949 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -93,12 +93,6 @@ contract DeployManager is Script { scriptsExecuted[executionKeys[i]] = true; } - if (scriptsExecuted[deployScript.DEPLOY_NAME()]) { - // TODO: Handle any active governance proposal - console.log("Skipping already deployed script"); - return; - } - /** * Pre-deployment */ @@ -115,38 +109,44 @@ contract DeployManager is Script { deployScript.preloadDeployedContract(existingContracts[i], deployedAddr); } - // Deployment - deployScript.setUp(); - deployScript.run(); - - /** - * Post-deployment - */ - BaseMainnetScript.DeployRecord[] memory records = deployScript.getAllDeployRecords(); - - for (uint256 i = 0; i < records.length; ++i) { - string memory name = records[i].name; - address addr = records[i].addr; - - console.log(string(abi.encodePacked("> Recorded Deploy of ", name, " at")), addr); - networkDeployments = vm.serializeAddress(contractsKey, name, addr); - deployedContracts[name] = addr; + if (scriptsExecuted[deployScript.DEPLOY_NAME()]) { + // Governance handling + deployScript.handleGovernanceProposal(); + console.log("Skipping already deployed script"); + } else { + // Deployment + deployScript.setUp(); + deployScript.run(); + + /** + * Post-deployment + */ + BaseMainnetScript.DeployRecord[] memory records = deployScript.getAllDeployRecords(); + + for (uint256 i = 0; i < records.length; ++i) { + string memory name = records[i].name; + address addr = records[i].addr; + + console.log(string(abi.encodePacked("> Recorded Deploy of ", name, " at")), addr); + networkDeployments = vm.serializeAddress(contractsKey, name, addr); + deployedContracts[name] = addr; + } + + // Sleep 0.5s so that the previous write is complete + vm.sleep(500); + vm.writeJson(networkDeployments, deploymentsFilePath, contractsKey); + console.log("> Deployment addresses stored."); + + /** + * Write Execution History + */ + currentExecutions = vm.serializeUint(executionsKey, deployScript.DEPLOY_NAME(), block.timestamp); + + // Sleep 0.5s so that the previous write is complete + vm.sleep(500); + vm.writeJson(currentExecutions, deploymentsFilePath, executionsKey); + console.log("> Deploy script execution complete."); } - - // Sleep 0.5s so that the previous write is complete - vm.sleep(500); - vm.writeJson(networkDeployments, deploymentsFilePath, contractsKey); - console.log("> Deployment addresses stored."); - - /** - * Write Execution History - */ - currentExecutions = vm.serializeUint(executionsKey, deployScript.DEPLOY_NAME(), block.timestamp); - - // Sleep 0.5s so that the previous write is complete - vm.sleep(500); - vm.writeJson(currentExecutions, deploymentsFilePath, executionsKey); - console.log("> Deploy script execution complete."); } function getDeployment(string calldata contractName) external view returns (address) { diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index 87be6d6e..52166006 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -21,9 +21,9 @@ contract OgnOgvMigrationScript is BaseMainnetScript { string public constant override DEPLOY_NAME = "011_OgnOgvMigration"; - GovProposal govProposal; + GovProposal public govProposal; - uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT + uint256 public constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT // Ref: https://snapshot.org/#/origingov.eth/proposal/0x741893a4d9838c0b69fac03650756e21fe00ec35b5309626bb0d6b816f861f9b uint256 public constant OGN_TO_MINT = 409_664_846 ether; @@ -31,11 +31,9 @@ contract OgnOgvMigrationScript is BaseMainnetScript { // From `script/ExtraOGNForMigration.s.sol`, rounded off uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_000_000 ether; - // TODO: Find right number - uint256 public constant EXTRA_OGN_FOR_REWARDS = 300_000_000 ether; + uint256 public constant EXTRA_OGN_FOR_REWARDS = 344_736 ether; - // TODO: Confirm reward rate - uint256 constant REWARDS_PER_SECOND = 300000 ether / uint256(24 * 60 * 60); // 300k per day + uint256 public constant REWARDS_PER_SECOND = 0.57 ether; constructor() {} @@ -64,10 +62,10 @@ contract OgnOgvMigrationScript is BaseMainnetScript { console.log("- Migrator init"); migratorProxy.initialize(address(migratorImpl), Addresses.TIMELOCK, ""); - _buildGovernanceProposal(); + // _buildGovernanceProposal(); } - function _buildGovernanceProposal() internal { + function _buildGovernanceProposal() internal override { Timelock timelock = Timelock(payable(Addresses.TIMELOCK)); address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; diff --git a/script/deploy/mainnet/012_xOGNGovernanceScript.sol b/script/deploy/mainnet/012_xOGNGovernanceScript.sol index 508eda01..e37993c9 100644 --- a/script/deploy/mainnet/012_xOGNGovernanceScript.sol +++ b/script/deploy/mainnet/012_xOGNGovernanceScript.sol @@ -21,11 +21,11 @@ contract XOGNGovernanceScript is BaseMainnetScript { using GovFive for GovFive.GovFiveProposal; using VmHelper for Vm; - GovFive.GovFiveProposal govProposal; + GovFive.GovFiveProposal public govProposal; string public constant override DEPLOY_NAME = "012_xOGNGovernance"; - uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT + uint256 public constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT constructor() {} @@ -42,7 +42,7 @@ contract XOGNGovernanceScript is BaseMainnetScript { _buildGovernanceProposal(); } - function _buildGovernanceProposal() internal { + function _buildGovernanceProposal() internal override { Timelock timelock = Timelock(payable(Addresses.TIMELOCK)); address xognGov = deployedContracts["XOGN_GOV"]; diff --git a/script/deploy/mainnet/BaseMainnetScript.sol b/script/deploy/mainnet/BaseMainnetScript.sol index 6498f016..795fb7b7 100644 --- a/script/deploy/mainnet/BaseMainnetScript.sol +++ b/script/deploy/mainnet/BaseMainnetScript.sol @@ -86,4 +86,11 @@ abstract contract BaseMainnetScript is Script { function _execute() internal virtual; function _fork() internal virtual; + + function _buildGovernanceProposal() internal virtual {} + + function handleGovernanceProposal() external virtual { + _buildGovernanceProposal(); + _fork(); + } } From 4bc93ff7876dc9c3a1ab62d6fb30be4e9561c2f7 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Fri, 24 May 2024 17:54:43 +0530 Subject: [PATCH 08/15] Fix rewards fork test --- tests/staking/OGNRewardsSourceForkTest.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/staking/OGNRewardsSourceForkTest.t.sol b/tests/staking/OGNRewardsSourceForkTest.t.sol index dc7f16c5..296864e9 100644 --- a/tests/staking/OGNRewardsSourceForkTest.t.sol +++ b/tests/staking/OGNRewardsSourceForkTest.t.sol @@ -27,7 +27,7 @@ contract OGNRewardsSourceForkTest is Test { uint256 constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT - uint256 constant REWARDS_PER_SECOND = 300000 ether / uint256(24 * 60 * 60); // 300k per day + uint256 constant REWARDS_PER_SECOND = 0.57 ether; int256 constant NEW_STAKE = -1; From 085389cca53bfaecdd3cf2fe80cd38c84e9c5e68 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Fri, 24 May 2024 19:09:32 +0530 Subject: [PATCH 09/15] Round up --- script/deploy/mainnet/011_OgnOgvMigrationScript.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index 52166006..bc5a5639 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -29,7 +29,7 @@ contract OgnOgvMigrationScript is BaseMainnetScript { uint256 public constant OGN_TO_MINT = 409_664_846 ether; // From `script/ExtraOGNForMigration.s.sol`, rounded off - uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_000_000 ether; + uint256 public constant EXTRA_OGN_FOR_MIGRATION = 3_010_000 ether; uint256 public constant EXTRA_OGN_FOR_REWARDS = 344_736 ether; From bb98b6125ee349b13ffbdcd23a348ce129d43ac2 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Fri, 24 May 2024 19:32:01 +0530 Subject: [PATCH 10/15] Fix tests --- script/deploy/mainnet/BaseMainnetScript.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/script/deploy/mainnet/BaseMainnetScript.sol b/script/deploy/mainnet/BaseMainnetScript.sol index 795fb7b7..15fe1aad 100644 --- a/script/deploy/mainnet/BaseMainnetScript.sol +++ b/script/deploy/mainnet/BaseMainnetScript.sol @@ -71,6 +71,7 @@ abstract contract BaseMainnetScript is Script { if (isForked) { vm.stopPrank(); + _buildGovernanceProposal(); _fork(); } else { vm.stopBroadcast(); From 109ee475a44ae4186fdc0411603a73addfb0682e Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Fri, 24 May 2024 23:35:53 +0530 Subject: [PATCH 11/15] Make proposal desc more detailed --- script/deploy/mainnet/011_OgnOgvMigrationScript.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol index bc5a5639..b2cfbdce 100644 --- a/script/deploy/mainnet/011_OgnOgvMigrationScript.sol +++ b/script/deploy/mainnet/011_OgnOgvMigrationScript.sol @@ -71,7 +71,12 @@ contract OgnOgvMigrationScript is BaseMainnetScript { address ognRewardsSourceProxy = deployedContracts["OGN_REWARDS_SOURCE"]; address veOgvImpl = deployedContracts["VEOGV_IMPL"]; - govProposal.setDescription("Deploy OGV-OGN migration contracts and revoke OGV Governance roles"); + govProposal.setDescription( + "OGV>OGN Migration Contracts" + "\n\nThis proposal deploys, funds and enables the Migrator contract which can be used to migrate OGV to OGN and also veOGV to xOGN." + "\n\nThe proposal mints 409,664,846 OGN (as specificed in the previous off-chain snapshot governance proposal). It also uses some OGN from the treasury multisig to account for the increase in OGV supply due to inflation since the snapshot proposal was posted." + "\n\nThis proposal also revokes all roles that the OGV Governance has on the Timelock. Buyback contracts are upgraded to buy OGN instead of OGV" + ); // Realize any pending rewards govProposal.action(Addresses.VEOGV, "collectRewards()", ""); From 8a3d9e810e271a7f835abc389090e04f66ff7a0d Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Sat, 25 May 2024 15:42:45 -0400 Subject: [PATCH 12/15] Do governance proposals using plain text signatures --- contracts/utils/GovProposalHelper.sol | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index 13ba78aa..15b54f0e 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -28,7 +28,7 @@ library GovProposalHelper { function id(GovProposal memory prop) internal view returns (uint256 proposalId) { bytes32 descriptionHash = keccak256(bytes(prop.description)); - (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getParams(prop); + (address[] memory targets, uint256[] memory values,,, bytes[] memory calldatas) = getParams(prop); proposalId = uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); } @@ -36,20 +36,26 @@ library GovProposalHelper { function getParams(GovProposal memory prop) internal view - returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) + returns ( + address[] memory targets, + uint256[] memory values, + string[] memory sigs, + bytes[] memory data, + bytes[] memory calldatas + ) { uint256 actionLen = prop.actions.length; targets = new address[](actionLen); values = new uint256[](actionLen); - string[] memory sigs = new string[](actionLen); - bytes[] memory data = new bytes[](actionLen); + sigs = new string[](actionLen); + data = new bytes[](actionLen); for (uint256 i = 0; i < actionLen; ++i) { targets[i] = prop.actions[i].target; + values[i] = prop.actions[i].value; sigs[i] = prop.actions[i].fullsig; data[i] = prop.actions[i].data; - values[i] = prop.actions[i].value; } calldatas = _encodeCalldata(sigs, data); @@ -80,10 +86,11 @@ library GovProposalHelper { } function getProposeCalldata(GovProposal memory prop) internal view returns (bytes memory proposeCalldata) { - (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getParams(prop); + (address[] memory targets, uint256[] memory values, string[] memory sigs, bytes[] memory data,) = + getParams(prop); proposeCalldata = abi.encodeWithSignature( - "propose(address[],uint256[],bytes[],string)", targets, values, calldatas, prop.description + "propose(address[],uint256[],string[], bytes[],string)", targets, values, sigs, data, prop.description ); } From 8859c9fb76c4f57201811ca50ec055b85c2687f8 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Sun, 26 May 2024 22:07:31 +0530 Subject: [PATCH 13/15] Handle vote casting --- contracts/utils/GovProposalHelper.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index 15b54f0e..b22c4b83 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -158,7 +158,8 @@ library GovProposalHelper { if (state == IGovernor.ProposalState.Active) { console.log("Voting on proposal..."); // Vote on proposal - governance.castVote(proposalId, 1); + try governance.castVote(proposalId, 1) {} + catch () {} // Wait for voting to end vm.roll(governance.proposalDeadline(proposalId) + 20); vm.warp(block.timestamp + 2 days); From d2e1045ad3fcea5dbf763a3b4eb020d1b333afd0 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Sun, 26 May 2024 22:08:46 +0530 Subject: [PATCH 14/15] Fix syntax --- contracts/utils/GovProposalHelper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index b22c4b83..b6ca5367 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -159,7 +159,7 @@ library GovProposalHelper { console.log("Voting on proposal..."); // Vote on proposal try governance.castVote(proposalId, 1) {} - catch () {} + catch {} // Wait for voting to end vm.roll(governance.proposalDeadline(proposalId) + 20); vm.warp(block.timestamp + 2 days); From f7f33747f60b15b3f4ef529f020e56db82672d67 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Sun, 26 May 2024 22:09:03 +0530 Subject: [PATCH 15/15] Prettify --- contracts/utils/GovProposalHelper.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/utils/GovProposalHelper.sol b/contracts/utils/GovProposalHelper.sol index b6ca5367..73d5bf71 100644 --- a/contracts/utils/GovProposalHelper.sol +++ b/contracts/utils/GovProposalHelper.sol @@ -158,8 +158,7 @@ library GovProposalHelper { if (state == IGovernor.ProposalState.Active) { console.log("Voting on proposal..."); // Vote on proposal - try governance.castVote(proposalId, 1) {} - catch {} + try governance.castVote(proposalId, 1) {} catch {} // Wait for voting to end vm.roll(governance.proposalDeadline(proposalId) + 20); vm.warp(block.timestamp + 2 days);