forked from volt-protocol/volt-protocol-core
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add scripts to construct calldata * proposal calldata constructing * Construct call and execution data * Bump OZ contracts version, fix failing tests, set up timelock integration testing framework * remove dummy vip vip_2 * cleanup * update comment on cancelling proposals in OA contract * update comment in OA, upgrade to async 2.6.4 * add integration test for OA timelock * forge oa timelock integration tests
- Loading branch information
1 parent
1a46091
commit 271b580
Showing
24 changed files
with
1,006 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.4; | ||
|
||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; | ||
import {CoreRef} from "../refs/CoreRef.sol"; | ||
|
||
// Timelock with proposer, executor, canceller and admin roles | ||
contract OptimisticTimelock is TimelockController, CoreRef { | ||
constructor( | ||
address core_, | ||
uint256 minDelay, | ||
address[] memory proposers, | ||
address[] memory executors | ||
) TimelockController(minDelay, proposers, executors) CoreRef(core_) { | ||
// Only guardians and governors are timelock admins | ||
revokeRole(TIMELOCK_ADMIN_ROLE, msg.sender); | ||
} | ||
|
||
/** | ||
@notice allow guardian or governor to assume timelock admin roles | ||
This more elegantly achieves optimistic timelock as follows: | ||
- veto: all proposers are granted the CANCELLER_ROLE in the constructor, | ||
CANCELLER_ROLE can cancel any in-flight proposal | ||
- pause proposals: revoke PROPOSER_ROLE from target | ||
- pause execution: revoke EXECUTOR_ROLE from target | ||
- set new proposer: revoke old proposer and add new one | ||
In addition it allows for much more granular and flexible access for multisig operators | ||
*/ | ||
function becomeAdmin() public onlyGuardianOrGovernor { | ||
this.grantRole(TIMELOCK_ADMIN_ROLE, msg.sender); | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
contracts/test/integration/IntegrationTestOATimelock.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.4; | ||
|
||
import {Vm} from "../unit/utils/Vm.sol"; | ||
import {ICore} from "../../core/ICore.sol"; | ||
import {DSTest} from "../unit/utils/DSTest.sol"; | ||
import {PSMRouter} from "./../../peg/PSMRouter.sol"; | ||
import {OptimisticTimelock} from "./../../dao/OptimisticTimelock.sol"; | ||
import {INonCustodialPSM} from "./../../peg/NonCustodialPSM.sol"; | ||
import {IVolt, Volt} from "../../volt/Volt.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {getCore, getAddresses, getVoltAddresses, FeiTestAddresses, VoltAddresses} from "../unit/utils/Fixtures.sol"; | ||
|
||
contract IntegrationTestOATimelock is DSTest { | ||
Vm public constant vm = Vm(HEVM_ADDRESS); | ||
ICore public core = ICore(0xEC7AD284f7Ad256b64c6E69b84Eb0F48f42e8196); | ||
OptimisticTimelock public oaTimelock; | ||
FeiTestAddresses public addresses = getAddresses(); | ||
VoltAddresses public voltAddresses = getVoltAddresses(); | ||
address public proposer1 = voltAddresses.pcvGuardAddress1; | ||
address public proposer2 = voltAddresses.pcvGuardAddress2; | ||
address public executorAddress = voltAddresses.executorAddress; | ||
|
||
function setUp() public { | ||
address[] memory proposerCancellerAddresses = new address[](3); | ||
proposerCancellerAddresses[0] = proposer1; | ||
proposerCancellerAddresses[1] = proposer2; | ||
proposerCancellerAddresses[2] = executorAddress; | ||
|
||
address[] memory executorAddresses = new address[](1); | ||
executorAddresses[0] = voltAddresses.executorAddress; | ||
|
||
oaTimelock = new OptimisticTimelock( | ||
address(core), | ||
600, | ||
proposerCancellerAddresses, | ||
executorAddresses | ||
); | ||
} | ||
|
||
function testSetup() public { | ||
assertTrue(oaTimelock.hasRole(oaTimelock.CANCELLER_ROLE(), proposer1)); | ||
assertTrue(oaTimelock.hasRole(oaTimelock.PROPOSER_ROLE(), proposer1)); | ||
|
||
assertTrue(oaTimelock.hasRole(oaTimelock.CANCELLER_ROLE(), proposer2)); | ||
assertTrue(oaTimelock.hasRole(oaTimelock.PROPOSER_ROLE(), proposer2)); | ||
|
||
/// ensure multisig has PROPOSER, CANCELLER and EXECUTOR roles | ||
assertTrue( | ||
oaTimelock.hasRole(oaTimelock.CANCELLER_ROLE(), executorAddress) | ||
); | ||
assertTrue( | ||
oaTimelock.hasRole(oaTimelock.PROPOSER_ROLE(), executorAddress) | ||
); | ||
assertTrue( | ||
oaTimelock.hasRole(oaTimelock.EXECUTOR_ROLE(), executorAddress) | ||
); | ||
} | ||
|
||
function testTimelockEthReceive() public { | ||
assertEq(address(oaTimelock).balance, 0); /// starts with 0 balance | ||
|
||
uint256 ethSendAmount = 100 ether; | ||
vm.deal(proposer1, ethSendAmount); | ||
|
||
vm.prank(proposer1); | ||
(bool success, ) = address(oaTimelock).call{value: ethSendAmount}(""); | ||
|
||
assertTrue(success); | ||
assertEq(address(oaTimelock).balance, ethSendAmount); | ||
} | ||
|
||
function testTimelockSendEth() public { | ||
uint256 ethSendAmount = 100 ether; | ||
vm.deal(address(oaTimelock), ethSendAmount); | ||
|
||
assertEq(address(oaTimelock).balance, ethSendAmount); /// starts with 0 balance | ||
|
||
bytes memory data = ""; | ||
bytes32 predecessor = bytes32(0); | ||
bytes32 salt = bytes32(0); | ||
vm.prank(proposer1); | ||
oaTimelock.schedule( | ||
proposer1, | ||
ethSendAmount, | ||
data, | ||
predecessor, | ||
salt, | ||
600 | ||
); | ||
bytes32 id = oaTimelock.hashOperation( | ||
proposer1, | ||
ethSendAmount, | ||
data, | ||
predecessor, | ||
salt | ||
); | ||
|
||
uint256 startingProposerEthBalance = proposer1.balance; | ||
|
||
assertTrue(!oaTimelock.isOperationDone(id)); /// operation is not done | ||
assertTrue(!oaTimelock.isOperationReady(id)); /// operation is not ready | ||
|
||
vm.warp(block.timestamp + 600); | ||
assertTrue(oaTimelock.isOperationReady(id)); /// operation is ready | ||
|
||
vm.prank(executorAddress); | ||
oaTimelock.execute(proposer1, ethSendAmount, data, predecessor, salt); | ||
|
||
assertTrue(oaTimelock.isOperationDone(id)); /// operation is done | ||
|
||
assertEq(address(oaTimelock).balance, 0); | ||
assertEq(proposer1.balance, ethSendAmount + startingProposerEthBalance); /// assert proposer received their eth | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { expect } from 'chai'; | ||
import { | ||
DeployUpgradeFunc, | ||
NamedAddresses, | ||
SetupUpgradeFunc, | ||
TeardownUpgradeFunc, | ||
ValidateUpgradeFunc | ||
} from '@custom-types/types'; | ||
import { getImpersonatedSigner } from '@test/helpers'; | ||
|
||
/* | ||
Timelock Proposal #1 | ||
Description: | ||
Steps: | ||
1 - First grant the timelock the governor role by the multisig | ||
2 - Grant Timelock PCV Controller | ||
3 - Validate state changes | ||
*/ | ||
|
||
const vipNumber = '1'; | ||
|
||
// Do any deployments | ||
// This should exclusively include new contract deployments | ||
const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { | ||
console.log(`No deploy actions for vip${vipNumber}`); | ||
return { | ||
// put returned contract objects here | ||
}; | ||
}; | ||
|
||
// Do any setup necessary for running the test. | ||
// This could include setting up Hardhat to impersonate accounts, | ||
// ensuring contracts have a specific state, etc. | ||
const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
const signer = await getImpersonatedSigner(addresses.protocolMultisig); | ||
await contracts.core.connect(signer).grantGovernor(addresses.optimisticTimelock); | ||
console.log('setup function run'); | ||
}; | ||
|
||
// Tears down any changes made in setup() that need to be | ||
// cleaned up before doing any validation checks. | ||
const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
console.log(`No actions to complete in teardown for vip${vipNumber}`); | ||
}; | ||
|
||
// Run any validations required on the vip using mocha or console logging | ||
// IE check balances, check state of contracts, etc. | ||
const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
const { core, optimisticTimelock } = contracts; | ||
|
||
expect(await core.isGovernor(optimisticTimelock.address)).to.be.true; | ||
expect(await core.isPCVController(optimisticTimelock.address)).to.be.true; | ||
}; | ||
|
||
export { deploy, setup, teardown, validate }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import hre, { ethers, artifacts } from 'hardhat'; | ||
import { expect } from 'chai'; | ||
import { | ||
DeployUpgradeFunc, | ||
NamedAddresses, | ||
SetupUpgradeFunc, | ||
TeardownUpgradeFunc, | ||
ValidateUpgradeFunc | ||
} from '@custom-types/types'; | ||
|
||
/* | ||
Timelock Proposal #9001 | ||
Description: | ||
Steps: | ||
1 - | ||
2 - | ||
3 - | ||
*/ | ||
|
||
const vipNumber = '1'; // Change me! | ||
|
||
// Do any deployments | ||
// This should exclusively include new contract deployments | ||
const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { | ||
console.log(`No deploy actions for vip${vipNumber}`); | ||
return { | ||
// put returned contract objects here | ||
}; | ||
}; | ||
|
||
// Do any setup necessary for running the test. | ||
// This could include setting up Hardhat to impersonate accounts, | ||
// ensuring contracts have a specific state, etc. | ||
const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
console.log(`No actions to complete in setup for vip${vipNumber}`); | ||
}; | ||
|
||
// Tears down any changes made in setup() that need to be | ||
// cleaned up before doing any validation checks. | ||
const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
console.log(`No actions to complete in teardown for vip${vipNumber}`); | ||
}; | ||
|
||
// Run any validations required on the vip using mocha or console logging | ||
// IE check balances, check state of contracts, etc. | ||
const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { | ||
console.log(`No actions to complete in validate for vip${vipNumber}`); | ||
}; | ||
|
||
export { deploy, setup, teardown, validate }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ProposalDescription } from '@custom-types/types'; | ||
|
||
const vip_1: ProposalDescription = { | ||
title: 'VIP-1: Grant Timelock PCV Controller', | ||
commands: [ | ||
{ | ||
target: 'core', | ||
values: '0', | ||
method: 'grantPCVController(address)', | ||
arguments: ['{optimisticTimelock}'], | ||
description: 'Grant timelock PCV Controller role' | ||
} | ||
], | ||
description: `Grant the Timelock PCV Controller Role` | ||
}; | ||
|
||
export default vip_1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ProposalDescription } from '@custom-types/types'; | ||
|
||
const vip_x: ProposalDescription = { | ||
title: 'VIP-X: Title', | ||
commands: [ | ||
{ | ||
target: '', | ||
values: '', | ||
method: '', | ||
arguments: [], | ||
description: '' | ||
} | ||
], | ||
description: 'vip_x will change the game!' | ||
}; | ||
|
||
export default vip_x; |
Oops, something went wrong.