Skip to content

Commit

Permalink
Add deployment script for Timelock governance (#2212)
Browse files Browse the repository at this point in the history
* Add deployment script for Timelock governance

* Add test

* Fix base tests

* Prettify

* Lint

* Fix mainnet tests
  • Loading branch information
shahthepro authored Sep 9, 2024
1 parent 99ca92c commit 9bbe3f9
Show file tree
Hide file tree
Showing 15 changed files with 332 additions and 73 deletions.
2 changes: 2 additions & 0 deletions contracts/contracts/interfaces/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ interface IVault {

function setDripper(address _dripper) external;

function dripper() external view returns (address);

function weth() external view returns (address);

function cacheWETHAssetIndex() external;
Expand Down
9 changes: 6 additions & 3 deletions contracts/contracts/vault/OETHBaseVaultCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,31 +79,34 @@ contract OETHBaseVaultCore is OETHVaultCore {
}

// @inheritdoc OETHVaultCore
// solhint-disable-next-line no-unused-vars
function requestWithdrawal(uint256 _amount)
external
virtual
override
returns (uint256 requestId, uint256 queued)
returns (uint256, uint256)
{
revert("Async withdrawals disabled");
}

// @inheritdoc OETHVaultCore
// solhint-disable-next-line no-unused-vars
function claimWithdrawal(uint256 _requestId)
external
virtual
override
returns (uint256 amount)
returns (uint256)
{
revert("Async withdrawals disabled");
}

// @inheritdoc OETHVaultCore
// solhint-disable-next-line no-unused-vars
function claimWithdrawals(uint256[] memory _requestIds)
external
virtual
override
returns (uint256[] memory amounts, uint256 totalAmount)
returns (uint256[] memory, uint256)
{
revert("Async withdrawals disabled");
}
Expand Down
87 changes: 87 additions & 0 deletions contracts/deploy/base/011_transfer_governance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2");
const addresses = require("../../utils/addresses");

const ADMIN_ROLE =
"0x0000000000000000000000000000000000000000000000000000000000000000";

module.exports = deployOnBaseWithGuardian(
{
deployName: "011_transfer_governance",
},
async ({ ethers }) => {
const cBridgedWOETHProxy = await ethers.getContract(
"BridgedBaseWOETHProxy"
);
const cBridgedWOETH = await ethers.getContractAt(
"BridgedWOETH",
cBridgedWOETHProxy.address
);

const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy");
const cOETHbProxy = await ethers.getContract("OETHBaseProxy");
const cWOETHbProxy = await ethers.getContract("WOETHBaseProxy");

const cDripperProxy = await ethers.getContract("OETHBaseDripperProxy");

const cAMOStrategyProxy = await ethers.getContract(
"AerodromeAMOStrategyProxy"
);
const cWOETHStrategyProxy = await ethers.getContract(
"BridgedWOETHStrategyProxy"
);

return {
actions: [
{
// 1. Grant admin role to Timelock on Bridged wOETH
// TODO: Revoke role later when everything works fine
contract: cBridgedWOETH,
signature: "grantRole(bytes32,address)",
args: [ADMIN_ROLE, addresses.base.timelock],
},
{
// 2. Bridged wOETH proxy
contract: cBridgedWOETHProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 3. Vault proxy
contract: cOETHbVaultProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 4. OETHb proxy
contract: cOETHbProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 5. WOETHb proxy
contract: cWOETHbProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 6. Dripper proxy
contract: cDripperProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 7. AMO Strategy proxy
contract: cAMOStrategyProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
{
// 8. Bridged WOETH Strategy Proxy
contract: cWOETHStrategyProxy,
signature: "transferGovernance(address)",
args: [addresses.base.timelock],
},
],
};
}
);
74 changes: 74 additions & 0 deletions contracts/deploy/base/012_claim_governance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2");

module.exports = deployOnBaseWithGuardian(
{
deployName: "012_claim_governance",
useTimelock: true,
},
async ({ ethers }) => {
const cBridgedWOETHProxy = await ethers.getContract(
"BridgedBaseWOETHProxy"
);

const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy");
const cOETHbProxy = await ethers.getContract("OETHBaseProxy");
const cWOETHbProxy = await ethers.getContract("WOETHBaseProxy");

const cDripperProxy = await ethers.getContract("OETHBaseDripperProxy");

const cAMOStrategyProxy = await ethers.getContract(
"AerodromeAMOStrategyProxy"
);
const cWOETHStrategyProxy = await ethers.getContract(
"BridgedWOETHStrategyProxy"
);

return {
name: "Claim Governance on superOETHb contracts",
actions: [
{
// 1. Bridged wOETH proxy
contract: cBridgedWOETHProxy,
signature: "claimGovernance()",
args: [],
},
{
// 2. Vault proxy
contract: cOETHbVaultProxy,
signature: "claimGovernance()",
args: [],
},
{
// 3. OETHb proxy
contract: cOETHbProxy,
signature: "claimGovernance()",
args: [],
},
{
// 4. WOETHb proxy
contract: cWOETHbProxy,
signature: "claimGovernance()",
args: [],
},
{
// 5. Dripper proxy
contract: cDripperProxy,
signature: "claimGovernance()",
args: [],
},
{
// 6. AMO Strategy proxy
contract: cAMOStrategyProxy,
signature: "claimGovernance()",
args: [],
},
{
// 7. Bridged WOETH Strategy Proxy
contract: cWOETHStrategyProxy,
signature: "claimGovernance()",
args: [],
},
],
};
}
);
12 changes: 9 additions & 3 deletions contracts/hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,21 @@ module.exports = {
default: ethers.constants.AddressZero,
// On Mainnet and fork, the governor is the Governor contract.
localhost:
process.env.FORK === "true"
process.env.FORK_NETWORK_NAME == "base"
? addresses.base.timelock
: process.env.FORK_NETWORK_NAME == "mainnet" ||
(!process.env.FORK_NETWORK_NAME && process.env.FORK == "true")
? MAINNET_TIMELOCK
: ethers.constants.AddressZero,
hardhat:
process.env.FORK === "true"
process.env.FORK_NETWORK_NAME == "base"
? addresses.base.timelock
: process.env.FORK_NETWORK_NAME == "mainnet" ||
(!process.env.FORK_NETWORK_NAME && process.env.FORK == "true")
? MAINNET_TIMELOCK
: ethers.constants.AddressZero,
mainnet: MAINNET_TIMELOCK,
// Base has no timelock
base: addresses.base.timelock,
},
guardianAddr: {
default: 1,
Expand Down
35 changes: 17 additions & 18 deletions contracts/test/_fixture-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,29 @@ const defaultBaseFixture = deployments.createFixture(async () => {
const signers = await hre.ethers.getSigners();

const [minter, burner, rafael, nick, clement] = signers.slice(4); // Skip first 4 addresses to avoid conflict
const { governorAddr, strategistAddr } = await getNamedAccounts();
const governor = await ethers.getSigner(governorAddr);
const woethGovernor = await ethers.getSigner(await woethProxy.governor());
const { governorAddr, strategistAddr, timelockAddr } =
await getNamedAccounts();
const governor = await ethers.getSigner(isFork ? timelockAddr : governorAddr);

const guardian = await ethers.getSigner(governorAddr);
const timelock = await ethers.getContractAt(
"ITimelockController",
timelockAddr
);

let strategist;
if (isFork) {
// Impersonate strategist on Fork
strategist = await impersonateAndFund(strategistAddr);
strategist.address = strategistAddr;
}

// Make sure we can print bridged WOETH for tests
if (isBaseFork) {
await impersonateAndFund(woethGovernor.address);

const woethImplAddr = await woethProxy.implementation();
const latestImplAddr = (await ethers.getContract("BridgedWOETH")).address;

if (woethImplAddr != latestImplAddr) {
await woethProxy.connect(woethGovernor).upgradeTo(latestImplAddr);
}
await impersonateAndFund(governor.address);
await impersonateAndFund(timelock.address);
}

await woeth.connect(woethGovernor).grantRole(MINTER_ROLE, minter.address);
await woeth.connect(woethGovernor).grantRole(BURNER_ROLE, burner.address);
// Make sure we can print bridged WOETH for tests
await woeth.connect(governor).grantRole(MINTER_ROLE, minter.address);
await woeth.connect(governor).grantRole(BURNER_ROLE, burner.address);

for (const user of [rafael, nick]) {
// Mint some bridged WOETH
Expand All @@ -143,7 +141,7 @@ const defaultBaseFixture = deployments.createFixture(async () => {
await weth.connect(user).approve(oethbVault.address, oethUnits("50"));
}

await woeth.connect(minter).mint(woethGovernor.address, oethUnits("1"));
await woeth.connect(minter).mint(governor.address, oethUnits("1"));

if (isFork) {
// Governor opts in for rebasing
Expand Down Expand Up @@ -189,8 +187,9 @@ const defaultBaseFixture = deployments.createFixture(async () => {

// Signers
governor,
guardian,
timelock,
strategist,
woethGovernor,
minter,
burner,

Expand Down
6 changes: 2 additions & 4 deletions contracts/test/_fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -667,10 +667,8 @@ const defaultFixture = deployments.createFixture(async () => {
if (isFork) {
governor = await ethers.provider.getSigner(governorAddr);
strategist = await ethers.provider.getSigner(strategistAddr);
timelock = await ethers.provider.getSigner(timelockAddr);
oldTimelock = await ethers.provider.getSigner(
addresses.mainnet.OldTimelock
);
timelock = await impersonateAndFund(timelockAddr);
oldTimelock = await impersonateAndFund(addresses.mainnet.OldTimelock);
} else {
timelock = governor;
}
Expand Down
6 changes: 5 additions & 1 deletion contracts/test/buyback/buyback.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { expect } = require("chai");

const { createFixtureLoader, buybackFixture } = require("../_fixture");
const { ousdUnits, usdcUnits, oethUnits } = require("../helpers");
const { ousdUnits, usdcUnits, oethUnits, isCI } = require("../helpers");
const addresses = require("../../utils/addresses");
const { impersonateAndFund } = require("../../utils/signers");
const { setERC20TokenBalance } = require("../_fund");
Expand All @@ -11,6 +11,10 @@ const loadFixture = createFixtureLoader(buybackFixture);

describe("Buyback", function () {
let fixture;

// Retry up to 3 times on CI
this.retries(isCI ? 3 : 0);

beforeEach(async () => {
fixture = await loadFixture();
});
Expand Down
46 changes: 46 additions & 0 deletions contracts/test/governance/oethb-timelock.base.fork-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { createFixtureLoader } = require("../_fixture");
const { defaultBaseFixture } = require("../_fixture-base");
const { expect } = require("chai");
const addresses = require("../../utils/addresses");
const { advanceTime, advanceBlocks } = require("../helpers");

const baseFixture = createFixtureLoader(defaultBaseFixture);

describe("ForkTest: OETHb Timelock", function () {
let fixture;
beforeEach(async () => {
fixture = await baseFixture();
});

it("Multisig can propose and execute on Timelock", async () => {
const { guardian, timelock, oethbVault } = fixture;

const calldata = oethbVault.interface.encodeFunctionData(
"setDripper(address)",
[addresses.dead]
);

const args = [
[oethbVault.address], // Targets
[0], // Values
[calldata], // Calldata,
"0x0000000000000000000000000000000000000000000000000000000000000000", // Predecessor
"0x0000000000000000000000000000000000000000000000000000000000000001", // Salt
];

const minDelay = await timelock.getMinDelay();

await timelock.connect(guardian).scheduleBatch(
...args,
minDelay // minDelay
);

// Wait for timelock
await advanceTime(minDelay.toNumber() + 10);
await advanceBlocks(2);

await timelock.connect(guardian).executeBatch(...args);

expect(await oethbVault.dripper()).to.eq(addresses.dead);
});
});
2 changes: 1 addition & 1 deletion contracts/test/token/oeth.base.fork-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("ForkTest: OETHb", function () {

it("Should have the right governor", async () => {
const { oethb } = fixture;
expect(await oethb.governor()).to.eq(addresses.base.governor);
expect(await oethb.governor()).to.eq(addresses.base.timelock);
});

it("Should have the right Vault address", async () => {
Expand Down
Loading

0 comments on commit 9bbe3f9

Please sign in to comment.