Skip to content

Commit

Permalink
Merge pull request #8 from renproject/feat/timelock-calls
Browse files Browse the repository at this point in the history
feat: Support timelock calls in deployments
  • Loading branch information
jazg authored Sep 8, 2022
2 parents eb5c8c4 + f48e82b commit 224e4e0
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 49 deletions.
90 changes: 56 additions & 34 deletions deploy/001_deploy_gateways.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
BasicBridge__factory,
BeaconProxy__factory,
ERC20,
// ForceSend__factory,
GatewayRegistryV2,
GatewayRegistryV2__factory,
LockGatewayProxyBeacon,
Expand Down Expand Up @@ -36,6 +35,7 @@ import {
setupCreate2,
setupDeployProxy,
setupGetExistingDeployment,
setupWaitForTimelockedTx,
setupWaitForTx,
} from "./deploymentUtils";
import { NetworkConfig, networks } from "./networks";
Expand Down Expand Up @@ -64,7 +64,7 @@ export const deployGatewaySol = async function (
logger.log(`Deploying to ${network.name} from ${deployer}...`);

if (Ox(mintAuthority) === Ox0) {
throw new Error(`Invalid empty mintAuthority.`);
throw new Error(`Invalid empty mintAuthority`);
}

const create2 = setupCreate2(hre, create2SaltOverride, logger);
Expand All @@ -91,6 +91,8 @@ export const deployGatewaySol = async function (
logger.log(chalk.yellow("RenTimelock"));
const renTimelock = await create2<RenTimelock__factory>("RenTimelock", [0, [deployer], [deployer]]);

const waitForTimelockedTx = setupWaitForTimelockedTx(hre, renTimelock, logger);

let governanceAddress: string;
if (network.name === "hardhat") {
governanceAddress = deployer;
Expand Down Expand Up @@ -123,8 +125,10 @@ export const deployGatewaySol = async function (
deployer,
]));
if (Ox(await renAssetProxyBeacon.implementation()) !== Ox(renAssetImplementation.address)) {
logger.log(`Updating RenAsset implementation to ${Ox(renAssetImplementation.address)}`);
await waitForTx(renAssetProxyBeacon.upgradeTo(renAssetImplementation.address));
await waitForTimelockedTx(
renAssetProxyBeacon.populateTransaction.upgradeTo(renAssetImplementation.address),
`Updating RenAssetProxy beacon implementation to ${renAssetImplementation.address}`
);
}

// Deploy MintGatewayProxyBeacon /////////////////////////////////////////////
Expand All @@ -143,8 +147,10 @@ export const deployGatewaySol = async function (
deployer,
]));
if (Ox(await mintGatewayProxyBeacon.implementation()) !== Ox(mintGatewayImplementation.address)) {
logger.log(`Updating MintGateway implementation to ${Ox(mintGatewayImplementation.address)}`);
await waitForTx(mintGatewayProxyBeacon.upgradeTo(mintGatewayImplementation.address));
await waitForTimelockedTx(
mintGatewayProxyBeacon.populateTransaction.upgradeTo(mintGatewayImplementation.address),
`Updating MintGateway implementation to ${Ox(mintGatewayImplementation.address)}`
);
}

// Deploy LockGatewayProxyBeacon /////////////////////////////////////////////
Expand All @@ -163,8 +169,10 @@ export const deployGatewaySol = async function (
deployer,
]));
if (Ox(await lockGatewayProxyBeacon.implementation()) !== Ox(lockGatewayImplementation.address)) {
logger.log(`Updating LockGateway implementation to ${Ox(lockGatewayImplementation.address)}`);
await waitForTx(lockGatewayProxyBeacon.upgradeTo(lockGatewayImplementation.address));
await waitForTimelockedTx(
lockGatewayProxyBeacon.populateTransaction.upgradeTo(lockGatewayImplementation.address),
`Updating LockGateway implementation to ${Ox(lockGatewayImplementation.address)}`
);
}

logger.log(chalk.yellow("Signature Verifier"));
Expand All @@ -182,12 +190,12 @@ export const deployGatewaySol = async function (
);
const signatureVerifierOwner = Ox(await signatureVerifier.owner());
if (signatureVerifierOwner !== Ox(governanceAddress)) {
logger.log(
await waitForTimelockedTx(
signatureVerifier.populateTransaction.transferOwnership(governanceAddress),
`Transferring RenVMSignatureVerifier ownership to governance address. (Was ${signatureVerifierOwner}, changing to ${Ox(
governanceAddress
)}) Deployer: ${deployer}.`
)}) Deployer: ${deployer}`
);
await waitForTx(signatureVerifier.transferOwnership(governanceAddress));
}

logger.log(chalk.yellow("TransferWithLog"));
Expand Down Expand Up @@ -222,22 +230,22 @@ export const deployGatewaySol = async function (
);
const existingSignatureVerifier = Ox(await gatewayRegistry.getSignatureVerifier());
if (existingSignatureVerifier !== Ox(signatureVerifier.address)) {
logger.log(
await waitForTimelockedTx(
gatewayRegistry.populateTransaction.updateSignatureVerifier(signatureVerifier.address),
`Updating signature verifier in gateway registry. Was ${existingSignatureVerifier}, updating to ${Ox(
signatureVerifier.address
)}.`
)}`
);
await waitForTx(gatewayRegistry.updateSignatureVerifier(signatureVerifier.address));
}

const existingTransferWithLog = Ox(await gatewayRegistry.getTransferContract());
if (existingTransferWithLog !== Ox(transferWithLog.address)) {
logger.log(
`Updating TransferWithLog in gateway registry. Was ${existingTransferWithLog}, updating to ${Ox(
transferWithLog.address
)}.`
)}`
);
await waitForTx(gatewayRegistry.updateTransferContract(transferWithLog.address));
await waitForTimelockedTx(gatewayRegistry.populateTransaction.updateTransferContract(transferWithLog.address));
}

if (Ox(await gatewayRegistry.getRenAssetProxyBeacon()) !== Ox(renAssetProxyBeacon.address)) {
Expand All @@ -252,29 +260,33 @@ export const deployGatewaySol = async function (

if (Ox(await renAssetProxyBeacon.getProxyDeployer()) !== Ox(gatewayRegistry.address)) {
logger.log(`Granting deployer role to gateway registry in renAssetProxyBeacon.`);
await waitForTx(renAssetProxyBeacon.updateProxyDeployer(gatewayRegistry.address));
await waitForTimelockedTx(renAssetProxyBeacon.populateTransaction.updateProxyDeployer(gatewayRegistry.address));
}
if ((await renAssetProxyBeacon.owner()) !== governanceAddress) {
logger.log(`Transferring renAssetProxyBeacon ownership to timelock.`);
await waitForTx(renAssetProxyBeacon.transferOwnership(governanceAddress));
await waitForTimelockedTx(renAssetProxyBeacon.populateTransaction.transferOwnership(governanceAddress));
}

if (Ox(await mintGatewayProxyBeacon.getProxyDeployer()) !== Ox(gatewayRegistry.address)) {
logger.log(`Granting deployer role to gateway registry in mintGatewayProxyBeacon.`);
await waitForTx(mintGatewayProxyBeacon.updateProxyDeployer(gatewayRegistry.address));
await waitForTimelockedTx(
mintGatewayProxyBeacon.populateTransaction.updateProxyDeployer(gatewayRegistry.address)
);
}
if ((await mintGatewayProxyBeacon.owner()) !== governanceAddress) {
logger.log(`Transferring mintGatewayProxyBeacon ownership to timelock.`);
await waitForTx(mintGatewayProxyBeacon.transferOwnership(governanceAddress));
await waitForTimelockedTx(mintGatewayProxyBeacon.populateTransaction.transferOwnership(governanceAddress));
}

if (Ox(await lockGatewayProxyBeacon.getProxyDeployer()) !== Ox(gatewayRegistry.address)) {
logger.log(`Granting deployer role to gateway registry in lockGatewayProxyBeacon.`);
await waitForTx(lockGatewayProxyBeacon.updateProxyDeployer(gatewayRegistry.address));
await waitForTimelockedTx(
lockGatewayProxyBeacon.populateTransaction.updateProxyDeployer(gatewayRegistry.address)
);
}
if ((await lockGatewayProxyBeacon.owner()) !== governanceAddress) {
logger.log(`Transferring lockGatewayProxyBeacon ownership to timelock.`);
await waitForTx(lockGatewayProxyBeacon.transferOwnership(governanceAddress));
await waitForTimelockedTx(lockGatewayProxyBeacon.populateTransaction.transferOwnership(governanceAddress));
}

logger.log(`Deploying contract verification helper contract.`);
Expand All @@ -288,7 +300,7 @@ export const deployGatewaySol = async function (

logger.log(`Handling ${(config.mintGateways || []).length} mint assets.`);
const prefix = config.tokenPrefix;
for (const { symbol, gateway, token, decimals, version } of config.mintGateways) {
for (const { symbol, gateway, token, decimals, version, legacy } of config.mintGateways) {
logger.log(chalk.yellow(`Mint asset: ${symbol}`));
const existingGateway = Ox(await gatewayRegistry.getMintGatewayBySymbol(symbol));
const existingToken = Ox(await gatewayRegistry.getRenAssetBySymbol(symbol));
Expand Down Expand Up @@ -320,8 +332,10 @@ export const deployGatewaySol = async function (
logger.log(`Skipping ${symbol} - ${existingGateway}, ${existingToken}!`);
}

const updatedGateway = existingGateway || Ox(await gatewayRegistry.getMintGatewayBySymbol(symbol));
const updatedToken = existingToken || Ox(await gatewayRegistry.getRenAssetBySymbol(symbol));
const updatedGateway =
existingGateway !== Ox0 ? existingGateway : Ox(await gatewayRegistry.getMintGatewayBySymbol(symbol));
const updatedToken =
existingToken !== Ox0 ? existingToken : Ox(await gatewayRegistry.getRenAssetBySymbol(symbol));

const gatewayLabel = `ren${symbol}_MintGateway_Proxy`;
await deployments.save(gatewayLabel, {
Expand All @@ -347,23 +361,28 @@ export const deployGatewaySol = async function (
const gatewayInstance = await getContractAt(hre)<MintGatewayV3>("MintGatewayV3", updatedGateway);
const existingSignatureVerifier = Ox(await gatewayInstance.getSignatureVerifier());
if (existingSignatureVerifier !== Ox(signatureVerifier.address)) {
logger.log(
await waitForTimelockedTx(
gatewayInstance.populateTransaction.updateSignatureVerifier(signatureVerifier.address),
`Updating signature verifier in the ${symbol} mint gateway. Was ${existingSignatureVerifier}, updating to ${Ox(
signatureVerifier.address
)}.`
)}`
);
await waitForTx(gatewayInstance.updateSignatureVerifier(signatureVerifier.address));
}

// Update selector hash, by updating symbol.
const expectedSelectorHash = keccak256(
Buffer.concat([Buffer.from(symbol), Buffer.from("/to"), Buffer.from(chainName)])
);
if (expectedSelectorHash !== (await gatewayInstance.getSelectorHash())) {
await gatewayInstance.updateAsset(symbol);
await waitForTimelockedTx(
gatewayInstance.populateTransaction.updateAsset(symbol),
`Updating ${symbol} mint gateway symbol.`
);
}
} catch (error) {
console.error(error);
if (!legacy) {
throw error;
}
}
}

Expand Down Expand Up @@ -433,20 +452,23 @@ export const deployGatewaySol = async function (
const gatewayInstance = await getContractAt(hre)<LockGatewayV3>("LockGatewayV3", updatedGateway);
const existingSignatureVerifier = Ox(await gatewayInstance.getSignatureVerifier());
if (existingSignatureVerifier !== Ox(signatureVerifier.address)) {
logger.log(
await waitForTimelockedTx(
gatewayInstance.populateTransaction.updateSignatureVerifier(signatureVerifier.address),
`Updating signature verifier in the ${symbol} lock gateway. Was ${existingSignatureVerifier}, updating to ${Ox(
signatureVerifier.address
)}.`
)}`
);
await waitForTx(gatewayInstance.updateSignatureVerifier(signatureVerifier.address));
}

// Update selector hash, by updating symbol.
const expectedSelectorHash = keccak256(
Buffer.concat([Buffer.from(symbol), Buffer.from("/to"), Buffer.from(chainName)])
);
if (expectedSelectorHash !== (await gatewayInstance.getSelectorHash())) {
await gatewayInstance.updateAsset(symbol);
await waitForTimelockedTx(
gatewayInstance.populateTransaction.updateAsset(symbol),
`Updating ${symbol} lock gateway symbol.`
);
}
} catch (error) {
console.error(error);
Expand Down
90 changes: 77 additions & 13 deletions deploy/deploymentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import {
Manifest,
setProxyKind,
} from "@openzeppelin/upgrades-core";
import { SyncOrPromise } from "@renproject/utils";
import { SyncOrPromise, utils } from "@renproject/utils";
import BigNumber from "bignumber.js";
import BN from "bn.js";
import chalk from "chalk";
import { BaseContract, ContractFactory, ContractTransaction } from "ethers";
import { BaseContract, ContractFactory, ContractTransaction, PopulatedTransaction } from "ethers";
import { getAddress, keccak256 } from "ethers/lib/utils";
import { CallOptions, DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";
Expand All @@ -25,6 +25,7 @@ import {
AccessControlEnumerableUpgradeable,
ERC20,
Ownable,
Ownable__factory,
RenProxyAdmin,
RenTimelock,
TransparentUpgradeableProxy,
Expand Down Expand Up @@ -93,7 +94,7 @@ export const setupDeploy =
skipIfAlreadyDeployed: false,
...overrides,
});
logger.log(`Deployed ${name} at ${result.address}.`);
logger.log(`Deployed ${name} at ${result.address}`);
const contract = await ethers.getContractAt<C>(name, result.address, deployer);

let owner;
Expand Down Expand Up @@ -147,7 +148,7 @@ export const setupCreate2 =
skipIfAlreadyDeployed: true,
...overrides,
});
logger.log(`Deployed ${name} at ${result.address}.`);
logger.log(`Deployed ${name} at ${result.address}`);
const contract = await ethers.getContractAt<C>(name, result.address, deployer);

let owner;
Expand Down Expand Up @@ -205,6 +206,7 @@ export const setupDeployProxy =
const { initializer, constructorArgs } = options;

const waitForTx = setupWaitForTx(logger);
const waitForTimelockedTx = setupWaitForTimelockedTx(hre, renTimelock, logger);

const existingProxyDeployment = await setupGetExistingDeployment(hre)<TransparentUpgradeableProxy>(proxyName);
let proxy: TransparentUpgradeableProxy;
Expand Down Expand Up @@ -266,15 +268,9 @@ export const setupDeployProxy =
);

// Check if the proxyAdmin is owned by the timelock.
if (Ox(await proxyAdmin.owner()) === Ox(renTimelock.address)) {
const txData = await proxyAdmin.populateTransaction.upgrade(proxy.address, implementation.address);
const tx = await renTimelock.schedule(proxyAdmin.address, 0, txData.data!, Ox0_32, Ox0_32, 0);
await waitForTx(tx);
const tx2 = await renTimelock.execute(proxyAdmin.address, 0, txData.data!, Ox0_32, Ox0_32);
await waitForTx(tx2);
} else {
await waitForTx(proxyAdmin.upgrade(proxy.address, implementation.address));
}
await waitForTimelockedTx(
proxyAdmin.populateTransaction.upgrade(proxy.address, implementation.address)
);
}
} else {
proxy = await create2<TransparentUpgradeableProxy__factory>(
Expand Down Expand Up @@ -326,6 +322,74 @@ export const setupWaitForTx =
logger.log(`Transaction confirmed: ${tx.hash}`);
};

export const setupWaitForTimelockedTx =
(hre: HardhatRuntimeEnvironment, renTimelock: RenTimelock, logger: ConsoleInterface = console) =>
async (txDetailsPromise: Promise<PopulatedTransaction> | PopulatedTransaction, msg?: string) => {
const waitForTx = setupWaitForTx(logger);

const { ethers, getNamedAccounts } = hre;
const Ox = ethers.utils.getAddress;
const { deployer } = await getNamedAccounts();
const provider = await ethers.getSigner(deployer);

const txDetails = await txDetailsPromise;

const ownableContract = await Ownable__factory.connect(txDetails.to || Ox0, provider);

// Check if the proxyAdmin is owned by the timelock.
if (Ox(await ownableContract.owner()) === Ox(renTimelock.address)) {
const minimumDelay = (await renTimelock.getMinDelay()).toNumber();

// If the call hasn't already been scheduled, schedule it with the
// smallest delay.
const hash = await renTimelock.hashOperation(
txDetails.to || Ox0,
0,
txDetails.data || Buffer.from([]),
Ox0_32,
Ox0_32
);
if (!(await renTimelock.isOperation(hash))) {
const tx = renTimelock.schedule(
txDetails.to || Ox0,
0,
txDetails.data || Buffer.from([]),
Ox0_32,
Ox0_32,
minimumDelay
);
await waitForTx(tx, `Sheduling ${msg || "timelock call"}`);
}

// Check if the timelocked call is ready to be called.
const readyTimestamp = (await renTimelock.getTimestamp(hash)).toNumber() - Date.now() / 1000;
if (readyTimestamp <= 60) {
if (readyTimestamp > 0) {
console.log(`Sleeping ${readyTimestamp.toFixed(2)} seconds for timelock`);
await utils.sleep(readyTimestamp * utils.sleep.SECONDS);
}
const tx2 = renTimelock.execute(
txDetails.to || Ox0,
0,
txDetails.data || Buffer.from([]),
Ox0_32,
Ox0_32
);
await waitForTx(tx2, msg);
} else {
const hoursRemaining = readyTimestamp / 60 / 60;
logger.log(
`WARNING: Timelock not ready for another ${hoursRemaining.toFixed(2)} hours: ${msg || hash}`
);
// Continue without calling `execute`. If there's any calls in
// the rest of the migration that depend on the execution, they
// will fail.
}
} else {
await waitForTx((await ethers.getSigner(deployer)).sendTransaction(txDetails), msg);
}
};

export const fixNonce = async (hre: HardhatRuntimeEnvironment, nonce: number) => {
const { getNamedAccounts, ethers } = hre;
const { deployer } = await getNamedAccounts();
Expand Down
Loading

0 comments on commit 224e4e0

Please sign in to comment.