Skip to content

feat: oracle bridge upgrade on admin networks #4817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/networks/100.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
"contractName": "OracleSpoke",
"address": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64"
"address": "0x5aef1Ed97f5e3c002d7552a8e09603039494Fb94"
},
{
"contractName": "GovernorSpoke",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/networks/1116.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
"contractName": "OracleSpoke",
"address": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64"
"address": "0x2C4A8C2eeeE1EA06f444dBe1b8b6DbfBD1f4b3D3"
},
{
"contractName": "GovernorSpoke",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/networks/1514.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
"contractName": "OracleSpoke",
"address": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64"
"address": "0xD43463Fadd73373bE260b67F5825274F4403dAF0"
},
{
"contractName": "GovernorSpoke",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/networks/43114.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
"contractName": "OracleSpoke",
"address": "0xc5E1687895A563a43d956EE1D71B4c9Fe9425a34"
"address": "0x48e687205D3962c43891b8Cde5A4Fe75FA6C8D7a"
},
{
"contractName": "GovernorSpoke",
Expand Down
1 change: 1 addition & 0 deletions packages/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
out
39 changes: 39 additions & 0 deletions packages/scripts/generate-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from "fs";
import path from "path";
import { runTypeChain } from "typechain";
import { getSafeL2SingletonDeployment, getMultiSendCallOnlyDeployment } from "@safe-global/safe-deployments";

const version = "1.3.0";

async function main() {
const safeL2SingletonDeployment130 = getSafeL2SingletonDeployment({ version });
if (!safeL2SingletonDeployment130) throw new Error(`SafeL2SingletonDeployment v${version} not found!`);
const multiSendCallOnlyDeployment130 = getMultiSendCallOnlyDeployment({ version });
if (!multiSendCallOnlyDeployment130) throw new Error(`MultiSendCallOnlyDeployment v${version} not found!`);
const deployments = [safeL2SingletonDeployment130, multiSendCallOnlyDeployment130];

const abiDir = path.resolve(__dirname, "./build/abi");
const typechainDir = path.resolve(__dirname, "./build/typechain");
fs.mkdirSync(abiDir, { recursive: true });
fs.mkdirSync(typechainDir, { recursive: true });

const abiPaths = deployments.map((deployment) => {
const abiPath = path.join(abiDir, `${deployment.contractName}${version.replace(/\./g, "")}.json`);
fs.writeFileSync(abiPath, JSON.stringify(deployment.abi, null, 2));
return abiPath;
});

const { filesGenerated } = await runTypeChain({
cwd: __dirname,
filesToProcess: abiPaths,
allFiles: abiPaths,
outDir: typechainDir,
target: "ethers-v5",
});
console.log(`Generated ethers-v5 typechain for ${filesGenerated} files`);
}

main().catch((err) => {
console.error("Failed to generate types:", err);
process.exit(1);
});
8 changes: 8 additions & 0 deletions packages/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
"@gnosis.pm/zodiac": "^3.2.0",
"@google-cloud/bigquery": "^5.3.0",
"@safe-global/safe-deployments": "^1.21.1",
"@safe-global/types-kit": "^2.0.1",
"@uma/common": "^2.37.3",
"@uma/contracts-node": "^0.4.25",
"@uma/financial-templates-lib": "^2.36.3",
"dotenv": "^6.2.0",
"ethers": "^5.4.2",
"hardhat": "^2.12.6",
"lodash": "^4.17.21",
"minimist": "^1.2.0",
"web3": "^1.6.0",
"winston": "^3.2.1",
Expand All @@ -30,10 +32,16 @@
},
"scripts": {
"test": "mocha 'test/**/*.js'",
"prebuild": "ts-node generate-types.ts",
"clean": "rm -rf build",
"build": "tsc --build"
},
"publishConfig": {
"registry": "https://registry.npmjs.com/",
"access": "public"
},
"devDependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@safe-global/types-kit was not compatible with older typescipt from the root of the workspace.

}
}
25 changes: 13 additions & 12 deletions packages/scripts/src/admin-proposals/registerContract.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// - Verify: Add --verify flag to Propose command.

require("dotenv").config();
const isEqual = require("lodash.isequal");
const assert = require("assert");
const Web3 = require("web3");
const { utf8ToHex, toChecksumAddress } = Web3.utils;
Expand Down Expand Up @@ -424,10 +425,10 @@ async function run() {
fromBlock: 0,
}
);
const relayedRegisterContractEvent = relayedTransactions.find(
(e) =>
e.returnValues.calls ===
[{ to: contractsByNetId[network.chainId].registry.options.address, data: registerContractData }]
const relayedRegisterContractEvent = relayedTransactions.find((e) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newer typescript version errored to build this as it does not allow comparison of object references

isEqual(e.returnValues.calls, [
{ to: contractsByNetId[network.chainId].registry.options.address, data: registerContractData },
])
);
// It's hard to test whether the addMember and removeMember transactions were relayed as well, since those
// governance transactions could have been executed many blocks before and after the registerContract
Expand All @@ -442,10 +443,10 @@ async function run() {
}
);
assert(
beforeRelayedRegistryTransactions.find(
(e) =>
e.returnValues.calls ===
[{ to: contractsByNetId[network.chainId].registry.options.address, data: addMemberData }]
beforeRelayedRegistryTransactions.find((e) =>
isEqual(e.returnValues.calls, [
{ to: contractsByNetId[network.chainId].registry.options.address, data: addMemberData },
])
),
"Could not find RelayedGovernanceRequest matching expected relayed addMemberData transaction"
);
Expand All @@ -458,10 +459,10 @@ async function run() {
}
);
assert(
afterRelayedRegistryTransactions.find(
(e) =>
e.returnValues.calls ===
[{ to: contractsByNetId[network.chainId].registry.options.address, data: removeMemberData }]
afterRelayedRegistryTransactions.find((e) =>
isEqual(e.returnValues.calls, [
{ to: contractsByNetId[network.chainId].registry.options.address, data: removeMemberData },
])
),
"Could not find RelayedGovernanceRequest matching expected relayed removeMemberData transaction"
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// This script creates a safe payload to be executed on a multisig controlled child messenger for the Oracle bridging
// contracts upgrade. Make sure that the newly deployed bridging contracts have been added to the networks config in the
// `core` package and the `contracts-node` package has been rebuilt. This will also impersonate the multisig owners and
// execute the safe payload on a forked network.
// Export following environment variables:
// - NODE_URL_X: Child chain ID specific node URL (not required when using localhost for a forked network).
// Then run the script with:
// yarn hardhat run packages/scripts/src/admin-proposals/upgrade-oo-request-bridging-admin/0_PayloadAdminChain.ts --network <network>
// Note: use localhost for the forked network, for public network also need to export the chain ID specific NODE_URL_X
// environment variable.

import { FinderEthers, getAbi, getAddress, OptimisticOracleV3Ethers } from "@uma/contracts-node";
import { interfaceName } from "@uma/common";
import { AdminChildMessenger } from "@uma/contracts-node/dist/packages/contracts-node/typechain/core/ethers";
import { utils as ethersUtils, constants as ethersConstants, BytesLike } from "ethers";
import fs from "fs";
import path from "path";
import hre from "hardhat";
import { getContractInstance } from "../../utils/contracts";
import {
appendTxToSafePayload,
baseSafePayload,
getContractMethod,
simulateSafePayload,
} from "../../utils/gnosisPayload";

async function main() {
const chainId = (await hre.ethers.provider.getNetwork()).chainId;
const oracleSpokeAddress = await getAddress("OracleSpoke", chainId);
const governorSpokeAddress = await getAddress("GovernorSpoke", chainId);
const adminChildMessenger = await getContractInstance<AdminChildMessenger>(
"Admin_ChildMessenger",
undefined,
chainId
);
const adminChildMessengerAbi = getAbi("Admin_ChildMessenger");
const finder = await getContractInstance<FinderEthers>("Finder", undefined, chainId);
const optimisticOracleV3 = await getContractInstance<OptimisticOracleV3Ethers>(
"OptimisticOracleV3",
undefined,
chainId
);

// Will construct the safe payload for the multisig owners to execute.
const multisig = await adminChildMessenger.owner();

// Update the OracleSpoke address in the AdminChildMessenger
let safePayload = baseSafePayload(chainId, "", "", multisig);
safePayload = appendTxToSafePayload(
safePayload,
adminChildMessenger.address,
getContractMethod(adminChildMessengerAbi, "setOracleSpoke"),
{
newOracleSpoke: oracleSpokeAddress,
}
);

// Set OracleSpoke as Oracle in L2 Finder and sync the cached value in OptimisticOracleV3 atomically
const relayedTransactions: {
to: string;
data: BytesLike;
}[] = [];
const changeImplementationAddressTx = await finder.populateTransaction.changeImplementationAddress(
ethersUtils.formatBytes32String(interfaceName.Oracle),
oracleSpokeAddress
);
if (!changeImplementationAddressTx.data) throw new Error("changeImplementationAddressTx.data is empty");
relayedTransactions.push({ to: finder.address, data: changeImplementationAddressTx.data });
const syncUmaParamsTx = await optimisticOracleV3.populateTransaction.syncUmaParams(
ethersUtils.formatBytes32String(""),
ethersConstants.AddressZero
);
if (!syncUmaParamsTx.data) throw new Error("syncUmaParamsTx.data is empty");
relayedTransactions.push({ to: optimisticOracleV3.address, data: syncUmaParamsTx.data });
const encodedCalls = ethersUtils.defaultAbiCoder.encode(["tuple(address to, bytes data)[]"], [relayedTransactions]);
safePayload = appendTxToSafePayload(
safePayload,
adminChildMessenger.address,
getContractMethod(adminChildMessengerAbi, "processMessageFromCrossChainParent"),
{
data: encodedCalls,
target: governorSpokeAddress,
}
);

const outDir = path.resolve(__dirname, "../../../out"); // root of the scripts package, must check when moving the script
fs.mkdirSync(outDir, { recursive: true });
const savePath = path.join(outDir, `${path.basename(__dirname)}_${chainId}.json`);
fs.writeFileSync(savePath, JSON.stringify(safePayload, null, 4));

console.log(`Safe payload for ${multisig} saved to ${savePath}`);

// Only spoof the execution on a forked network.
if (hre.network.name === "localhost") {
// The version only impacts which MultiSendCallOnly contract is called as the used safe interfaces for the
// simulation are the same across the versions.
const safeVersion = "1.3.0";
await simulateSafePayload(hre.ethers.provider, safePayload, safeVersion);
}
}

main().then(
() => {
process.exit(0);
},
(err) => {
console.error(err);
process.exit(1);
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// This script verifies the governance payload on Oracle bridging contracts upgrade has been properly executed.
// Export following environment variables:
// - NODE_URL_X: Child chain ID specific node URL (not required when using localhost for a forked network).
// Then run the script with:
// yarn hardhat run packages/scripts/src/admin-proposals/upgrade-oo-request-bridging-admin/1_Verify.ts --network <network>
// Note: use localhost for the forked network, for public network also need to export the chain ID specific NODE_URL_X
// environment variable.

import { strict as assert } from "assert";
import { FinderEthers, getAddress, OptimisticOracleV3Ethers } from "@uma/contracts-node";
import { interfaceName } from "@uma/common";
import { utils as ethersUtils } from "ethers";
import { getContractInstance } from "../../utils/contracts";
import { AdminChildMessenger } from "@uma/contracts-node/typechain/core/ethers";

async function verifyChildMessenger(chainId: number) {
const oracleSpokeAddress = await getAddress("OracleSpoke", chainId);
const adminChildMessenger = await getContractInstance<AdminChildMessenger>(
"Admin_ChildMessenger",
undefined,
chainId
);
assert((await adminChildMessenger.oracleSpoke()) === oracleSpokeAddress);
console.log(
` ✅ OracleSpoke ${oracleSpokeAddress} is set in the admin child messenger ${adminChildMessenger.address} on chainId ${chainId}`
);
}

async function verifyOracleImplementation(chainId: number) {
const oracleSpokeAddress = await getAddress("OracleSpoke", chainId);
const finder = await getContractInstance<FinderEthers>("Finder", undefined, chainId);
assert(
(await finder.getImplementationAddress(ethersUtils.formatBytes32String(interfaceName.Oracle))) ===
oracleSpokeAddress
);
console.log(` ✅ OracleSpoke ${oracleSpokeAddress} is set as Oracle in the Finder on chainId ${chainId}`);
}

async function verifyCachedOracle(chainId: number) {
const oracleSpokeAddress = await getAddress("OracleSpoke", chainId);
const optimisticOracleV3 = await getContractInstance<OptimisticOracleV3Ethers>(
"OptimisticOracleV3",
undefined,
chainId
);
assert((await optimisticOracleV3.cachedOracle()) === oracleSpokeAddress);
console.log(
` ✅ OracleSpoke ${oracleSpokeAddress} is cached as Oracle in the OptimisticOracleV3 on chainId ${chainId}`
);
}

async function main() {
const chainId = (await hre.ethers.provider.getNetwork()).chainId;

console.log(" 1. Validating OracleSpoke address is set in the admin child messenger");
await verifyChildMessenger(chainId);

console.log(" 2. Validating OracleSpoke address is set as Oracle in the child chain Finder contract");
await verifyOracleImplementation(chainId);

console.log(" 3. Validating OracleSpoke address is cached as Oracle in the child chain OptimisticOracleV3 contract");
await verifyCachedOracle(chainId);
}

main().then(
() => {
process.exit(0);
},
(err) => {
console.error(err);
process.exit(1);
}
);
Loading