From 9a5d94c8e29f394734ce0e300e7c13f01557d12a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 24 Apr 2026 16:40:17 +0300 Subject: [PATCH 1/2] feat: simulation for proposal, that target multiple l2 chains --- deployments/arbitrum/usdc/relations.ts | 7 + deployments/arbitrum/usdc/roots.json | 3 +- deployments/mainnet/usdc/relations.ts | 7 + deployments/mainnet/usdc/roots.json | 6 +- deployments/relations.ts | 12 + hardhat.config.ts | 14 +- .../deployment_manager/DeploymentManager.ts | 14 + plugins/scenario/utils/hreForBase.ts | 22 +- scenario/constraints/ProposalConstraint.ts | 16 +- scenario/utils/index.ts | 160 ++++--- scenario/utils/isBridgeProposal.ts | 395 ++++++++++++------ scenario/utils/relayArbitrumMessage.ts | 203 ++++++++- scenario/utils/relayBaseMessage.ts | 116 ++++- scenario/utils/relayLineaMessage.ts | 2 +- scenario/utils/relayMessage.ts | 26 +- scenario/utils/relayOptimismMessage.ts | 112 ++++- scenario/utils/scenarioHelper.ts | 12 +- src/deploy/index.ts | 3 +- tasks/deployment_manager/task.ts | 4 +- 19 files changed, 896 insertions(+), 238 deletions(-) diff --git a/deployments/arbitrum/usdc/relations.ts b/deployments/arbitrum/usdc/relations.ts index 9a8535766..0e4bb52be 100644 --- a/deployments/arbitrum/usdc/relations.ts +++ b/deployments/arbitrum/usdc/relations.ts @@ -11,6 +11,13 @@ export default { TransparentUpgradeableProxy: { artifact: 'contracts/ERC20.sol:ERC20' }, + AdminUpgradableProxy: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, // Native USDC '0xaf88d065e77c8cC2239327C5EDb3A432268e5831': { artifact: 'contracts/ERC20.sol:ERC20', diff --git a/deployments/arbitrum/usdc/roots.json b/deployments/arbitrum/usdc/roots.json index 54603b0c6..1ae6e7f63 100644 --- a/deployments/arbitrum/usdc/roots.json +++ b/deployments/arbitrum/usdc/roots.json @@ -4,5 +4,6 @@ "rewards": "0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae", "bridgeReceiver": "0x42480C37B249e33aABaf4c22B20235656bd38068", "bulker": "0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d", - "CCTPMessageTransmitter": "0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca" + "CCTPTokenMessenger": "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d", + "CCTPMessageTransmitter": "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64" } \ No newline at end of file diff --git a/deployments/mainnet/usdc/relations.ts b/deployments/mainnet/usdc/relations.ts index 776d101e6..ef9403471 100644 --- a/deployments/mainnet/usdc/relations.ts +++ b/deployments/mainnet/usdc/relations.ts @@ -21,6 +21,13 @@ export default { } } }, + AdminUpgradableProxy: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, UUPSProxy: { artifact: 'contracts/ERC20.sol:ERC20', delegates: { diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index f01a53815..8cdf987c1 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -1,5 +1,4 @@ { - "l1TokenAdminRegistry": "0xb22764f98dD05c789929716D677382Df22C05Cb6", "comptrollerV2": "0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b", "comet": "0xc3d688B66703497DAA19211EEdff47f25384cdc3", "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", @@ -8,8 +7,8 @@ "fxRoot": "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2", "arbitrumInbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", "arbitrumL1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", - "CCTPTokenMessenger": "0xbd3fa81b58ba92a82136038b25adec7066af3155", - "CCTPMessageTransmitter": "0x0a992d191deec32afe36203ad87d7d289a738f81", + "CCTPTokenMessenger": "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d", + "CCTPMessageTransmitter": "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64", "baseL1CrossDomainMessenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa", "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", "baseL1USDSBridge": "0xA5874756416Fa632257eEA380CAbd2E87cED352A", @@ -25,6 +24,7 @@ "lineaMessageService": "0xd19d4B5d358258f05D7B411E21A1460D11B0876F", "lineaL1TokenBridge": "0x051F1D88f0aF5763fB888eC4378b4D8B29ea3319", "lineaL1USDCBridge": "0x504A330327A089d8364C4ab3811Ee26976d388ce", + "l1TokenAdminRegistry": "0xb22764f98dD05c789929716D677382Df22C05Cb6", "roninl1CCIPOnRamp": "0xdC5b578ff3AFcC4A4a6E149892b9472390b50844", "roninl1NativeBridge": "0x64192819Ac13Ef72bF6b5AE239AC672B43a9AF08" } \ No newline at end of file diff --git a/deployments/relations.ts b/deployments/relations.ts index c7b823b32..b48345c0b 100644 --- a/deployments/relations.ts +++ b/deployments/relations.ts @@ -59,6 +59,12 @@ const relationConfigMap: RelationConfigMap = { if (address === '0xd09acb80c1e8f2291862c4978a008791c9167003') { return 'tETH'; } + if (address === '0x5a7facb970d094b6c7ff1df0ea68d99e6e73cbff') { + return 'weETH'; + } + if (address.toLowerCase() === '0x87eee96d50fb761ad85b1c982d28a042169d61b1') { + return 'wrsETH'; + } throw new Error(`Failed to get symbol for token ${token.address}: ${e.message}`); } @@ -85,6 +91,12 @@ const relationConfigMap: RelationConfigMap = { if (address === '0xd09acb80c1e8f2291862c4978a008791c9167003') { return 'tETH:priceFeed'; } + if (address === '0x5a7facb970d094b6c7ff1df0ea68d99e6e73cbff') { + return 'weETH:priceFeed'; + } + if (address === '0x87eee96d50fb761ad85b1c982d28a042169d61b1') { + return 'wrsETH:priceFeed'; + } throw new Error(`Failed to get symbol for token ${assets[i].address}: ${e.message}`); } diff --git a/hardhat.config.ts b/hardhat.config.ts index 3b7861abd..fe9234d04 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,7 +3,7 @@ import 'dotenv/config'; import { HardhatUserConfig, subtask, task } from 'hardhat/config'; import '@compound-finance/hardhat-import'; import '@nomiclabs/hardhat-etherscan'; -import '@tenderly/hardhat-tenderly'; +// import '@tenderly/hardhat-tenderly'; import '@nomiclabs/hardhat-ethers'; import '@typechain/hardhat'; import 'hardhat-chai-matchers'; @@ -704,12 +704,12 @@ const config: HardhatUserConfig = { ], }, - tenderly: { - project: 'comet', - username: process.env.TENDERLY_USERNAME || '', - accessKey: process.env.TENDERLY_ACCESS_KEY || '', - privateVerification: false, - }, + // tenderly: { + // project: 'comet', + // username: process.env.TENDERLY_USERNAME || '', + // accessKey: process.env.TENDERLY_ACCESS_KEY || '', + // privateVerification: false, + // }, mocha: { reporter: 'mocha-multi-reporters', diff --git a/plugins/deployment_manager/DeploymentManager.ts b/plugins/deployment_manager/DeploymentManager.ts index 5ddf68d71..a72836dc7 100644 --- a/plugins/deployment_manager/DeploymentManager.ts +++ b/plugins/deployment_manager/DeploymentManager.ts @@ -51,6 +51,7 @@ export class DeploymentManager { cache: Cache; // TODO: kind of a misnomer since its handling *all* path stuff contractsCache: ContractMap | null; _signers: SignerWithAddress[]; + bridgedDeploymentManagers: Map = new Map(); constructor( network: string, @@ -75,6 +76,19 @@ export class DeploymentManager { this._signers = []; } + async addBridgedDeploymentManager(network: string, deployment: string, hre?: HardhatRuntimeEnvironment): Promise { + const key = `${network}:${deployment}`; + if (!this.bridgedDeploymentManagers.has(key)) { + if(!hre && this.network !== network) { + throw new Error(`Must provide hre to bridge to a different network deployment manager`); + } + const dm = new DeploymentManager(network, deployment, hre ?? this.hre, { writeCacheToDisk: true }); + await dm.spider(); + this.bridgedDeploymentManagers.set(key, dm); + } + return this.bridgedDeploymentManagers.get(key)!; + } + async getSigners(): Promise { if (this._signers.length > 0) { return this._signers; diff --git a/plugins/scenario/utils/hreForBase.ts b/plugins/scenario/utils/hreForBase.ts index 2328074c7..ee47241bf 100644 --- a/plugins/scenario/utils/hreForBase.ts +++ b/plugins/scenario/utils/hreForBase.ts @@ -9,6 +9,7 @@ import { Environment } from 'hardhat/internal/core/runtime-environment'; import { ForkSpec } from '../World'; import { HttpNetworkUserConfig } from 'hardhat/types'; import { EthereumProvider } from 'hardhat/types/provider'; +import { networkConfigs } from '../../../hardhat.config'; /* mimics https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-core/src/internal/lib/hardhat-lib.ts @@ -91,6 +92,12 @@ function getBlockRollback(base: ForkSpec) { return 25; } +let activeMigration = false; + +export function migrationStarted() { + activeMigration = true; +} + export async function forkedHreForBase(base: ForkSpec): Promise { const ctx: HardhatContext = HardhatContext.getHardhatContext(); @@ -103,16 +110,21 @@ export async function forkedHreForBase(base: ForkSpec): Promise { + if (activeMigration){ + return networkConfigs.find(c => c.network === base.network)?.url; + } + return baseNetwork.url; + })(); + const provider = new ethers.providers.JsonRpcProvider(providerUrl); + if(providerUrl) console.log(`Forking from network: ${base.network} at block number: ${await provider.getBlockNumber() - (getBlockRollback(base) || 0)}`); // noNetwork otherwise - if (!base.blockNumber && baseNetwork.url && getBlockRollback(base) !== undefined) + if (!base.blockNumber && providerUrl && getBlockRollback(base) !== undefined) base.blockNumber = await provider.getBlockNumber() - getBlockRollback(base); // arbitrary number of blocks to go back if (getBlockRollback(base) === 0) { - const provider = new ethers.providers.JsonRpcProvider(baseNetwork.url); const block = await provider.getBlockNumber(); base.blockNumber = block - 1; } @@ -126,7 +138,7 @@ export async function forkedHreForBase(base: ForkSpec): Promise { @@ -87,15 +87,11 @@ export class ProposalConstraint implements StaticConstra try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); - if (isBridged) { - await executeOpenProposalAndRelay( - governanceDeploymentManager, - ctx.world.deploymentManager, - proposal - ); - } else { - await executeOpenProposal(governanceDeploymentManager, proposal); - } + await executeOpenProposalAndRelay( + governanceDeploymentManager, + ctx.world.deploymentManager, + proposal + ); debug(`${label} Open proposal ${proposal.id} was executed`); } catch (err) { debug(`${label} Failed to execute proposal ${proposal.id}`, err.message); diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index ed9cfd7c9..824adbb65 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -12,7 +12,7 @@ import { utils, } from 'ethers'; import { execSync } from 'child_process'; -import { existsSync } from 'fs'; +import { existsSync, unlinkSync } from 'fs'; import { CometContext } from '../context/CometContext'; import CometAsset from '../context/CometAsset'; import { exp } from '../../test/helpers'; @@ -912,8 +912,6 @@ export async function tenderlyExecute( }, ]; - const chainId2 = bdm.hre.ethers.provider.network.chainId; - console.log(`\n========================== TENDERLY ==========================\n`); console.log(`\nExecuting Tenderly simulation for proposal ${id}...`); @@ -926,50 +924,68 @@ export async function tenderlyExecute( console.log(` >>> PROPOSAL EXECUTED ${id} \n`); console.log(`Simulation ${exec1.id} done, status: ${exec1.status}`); console.log(`Link: https://www.tdly.co/shared/simulation/${exec1.id}`); - let proposals; - if (chainId1 !== chainId2) { - proposals = await relayMessage(gdm, bdm, parseFloat(B0.toString()), bundle[bundle.length - 1].transaction.transaction_info.logs); - - debug(`Proposals relayed: ${proposals.length}`); - const timelockL2 = await bdm.getContractOrThrow('timelock'); - const delay = await timelockL2.delay(); - const relayMessages = loadCachedRelayMessages(); - const latestL2 = await bdm.hre.ethers.provider.getBlock('latest'); - const maxEta = Math.max(...proposals.map(p => Number(p.eta || 0))) + delay.toNumber(); - const T0L2 = BigInt(Math.max(latestL2.timestamp, maxEta + 1)); - const B0L2 = Number(latestL2.number) + 1; - const simsL2 = relayMessages.map((msg, i, arr) => { - const isLast = i === arr.length - 1; - - const timestamp = isLast - ? Number(T0L2) - : latestL2.timestamp; - - const block = isLast - ? B0L2 : latestL2.number; - - return { - network_id: chainId2.toString(), - from: msg.signer, - to: msg.messenger, - block_number: Number(block), - block_header: { - timestamp: bdm.hre.ethers.utils.hexlify(Number(timestamp)) - }, - input: msg.callData, - save: true, - save_if_fails: true, - gas_price: 0, - }; - }); + + const bdms = [bdm]; + for (const dm of gdm.bridgedDeploymentManagers.values()) { + if (!bdms.includes(dm)) { + bdms.push(dm); + } + } - if (simsL2.length > 0) { - const bundle2 = await simulateBundle(bdm, simsL2, Number(B0L2)); - console.log(` >>> PROPOSAL RELAYED ${id} \n`); - const sim = bundle2[bundle2.length - 1]; - await shareSimulation(bdm, sim.simulation.id); - console.log(`Simulation ${sim.simulation.id} done, status: ${sim.simulation.status}`); - console.log(`Link: https://www.tdly.co/shared/simulation/${sim.simulation.id}`); + for (const currentBdm of bdms) { + const chainId2 = currentBdm.hre.ethers.provider.network.chainId; + let proposals; + if (chainId1 !== chainId2) { + const relayPath = path.resolve(__dirname, '../../cache/relay.json'); + if (existsSync(relayPath)) unlinkSync(relayPath); + + proposals = await relayMessage(gdm, currentBdm, parseFloat(B0.toString()), bundle[bundle.length - 1].transaction.transaction_info.logs); + + debug(`Proposals relayed to ${currentBdm.network}: ${proposals?.length ?? 0}`); + + if (proposals && proposals.length > 0) { + const timelockL2 = await currentBdm.getContractOrThrow('timelock'); + const delay = await timelockL2.delay(); + const relayMessages = loadCachedRelayMessages(); + const latestL2 = await currentBdm.hre.ethers.provider.getBlock('latest'); + const maxEta = Math.max(...proposals.map(p => Number(p.eta || 0))) + delay.toNumber(); + const T0L2 = BigInt(Math.max(latestL2.timestamp, maxEta + 1)); + const B0L2 = Number(latestL2.number) + 1; + + const simsL2 = relayMessages.map((msg, i, arr) => { + const isLast = i === arr.length - 1; + + const timestamp = isLast + ? Number(T0L2) + : latestL2.timestamp; + + const block = isLast + ? B0L2 : latestL2.number; + + return { + network_id: chainId2.toString(), + from: msg.signer, + to: msg.messenger, + block_number: Number(block), + block_header: { + timestamp: currentBdm.hre.ethers.utils.hexlify(Number(timestamp)) + }, + input: msg.callData, + save: true, + save_if_fails: true, + gas_price: 0, + }; + }); + + if (simsL2.length > 0) { + const bundle2 = await simulateBundle(currentBdm, simsL2, Number(B0L2)); + const sim = bundle2[bundle2.length - 1]; + await shareSimulation(currentBdm, sim.simulation.id); + console.log(`\nRelayed to ${currentBdm.network}`); + console.log(`Simulation ${sim.simulation.id} done, status: ${sim.simulation.status}`); + console.log(`Link: https://www.tdly.co/shared/simulation/${sim.simulation.id}`); + } + } } } @@ -985,7 +1001,15 @@ async function simulateBundle( const results = []; for (const sim of simulations) { - const { username, project, accessKey } = (dm.hre.config as any).tenderly; + // const { + // username, + // project, + // accessKey + // } = (dm.hre.config as any).tenderly; + + const project = 'comet'; + const username = process.env.TENDERLY_USERNAME || ''; + const accessKey = process.env.TENDERLY_ACCESS_KEY || ''; // Merge rolling state changes with simulation's own state_objects const stateObjects = sim.state_objects @@ -1043,7 +1067,10 @@ async function simulateBundle( } async function shareSimulation(dm: DeploymentManager, simulationId: string) { - const { username, project, accessKey } = (dm.hre.config as any).tenderly; + // const { username, project, accessKey } = (dm.hre.config as any).tenderly; + const project = 'comet'; + const username = process.env.TENDERLY_USERNAME || ''; + const accessKey = process.env.TENDERLY_ACCESS_KEY || ''; return axios.post( `https://api.tenderly.co/api/v1/account/${username}/project/${project}/simulations/${simulationId}/share`, {}, @@ -1500,25 +1527,26 @@ export async function executeOpenProposalAndRelay( await governanceDeploymentManager.hre.ethers.provider.getBlockNumber(); await executeOpenProposal(governanceDeploymentManager, openProposal); console.log(`Executed proposal ${openProposal.id} on ${governanceDeploymentManager.network}, checking if relay to ${bridgeDeploymentManager.network} is needed...`); - await mockAllRedstoneOracles(bridgeDeploymentManager); console.log(`All Redstone oracles on ${bridgeDeploymentManager.network} are mocked`); - if ( - await isBridgeProposal( - governanceDeploymentManager, - bridgeDeploymentManager, - openProposal - ) - ) { - await relayMessage( - governanceDeploymentManager, - bridgeDeploymentManager, - startingBlockNumber - ); - } else { - console.log( - `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Proposal ${openProposal.id} doesn't target bridge; not relaying` - ); - return; + const bridgeManagers = await isBridgeProposal( + governanceDeploymentManager, + bridgeDeploymentManager, + openProposal + ); + for (const bridgeManager of bridgeManagers) { + await mockAllRedstoneOracles(bridgeManager); + if (bridgeManager) { + await relayMessage( + governanceDeploymentManager, + bridgeManager, + startingBlockNumber + ); + } else { + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeManager.network}] Proposal ${openProposal.id} doesn't target bridge; not relaying` + ); + return; + } } } diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index f2d4ac362..7f3f71d2a 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -1,142 +1,281 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; +import { getRoots } from '../../plugins/deployment_manager/Roots'; import { OpenProposal } from '../context/Gov'; +import { utils } from 'ethers'; +import { forkedHreForBase } from '../../plugins/scenario/utils/hreForBase'; + +const EXCLUDED_ROOTS = ['comptrollerV2', 'comet', 'configurator', 'rewards', 'bulker', 'cometFactory']; + +const CCTP_DOMAIN_TO_NETWORK: Record = { + 0: 'mainnet', + 1: 'avalanche', + 2: 'optimism', + 3: 'arbitrum', + 6: 'base', + 7: 'polygon', +}; + +const ROOT_TO_NETWORK: Record = { + fxRoot: 'polygon', + arbitrumInbox: 'arbitrum', + arbitrumL1GatewayRouter: 'arbitrum', + baseL1CrossDomainMessenger: 'base', + baseL1StandardBridge: 'base', + baseL1USDSBridge: 'base', + opL1CrossDomainMessenger: 'optimism', + opL1StandardBridge: 'optimism', + mantleL1CrossDomainMessenger: 'mantle', + mantleL1StandardBridge: 'mantle', + unichainL1CrossDomainMessenger: 'unichain', + unichainL1StandardBridge: 'unichain', + scrollMessenger: 'scroll', + scrollL1USDCGateway: 'scroll', + lineaMessageService: 'linea', + lineaL1TokenBridge: 'linea', + lineaL1USDCBridge: 'linea', + l1CCIPRouter: 'ronin', + l1TokenAdminRegistry: 'ronin', + roninl1CCIPOnRamp: 'ronin', + roninl1NativeBridge: 'ronin', +}; + +function parseCCTPNetworks(openProposal: OpenProposal, cctpAddress: string): string[] { + const networks: string[] = []; + const cctpLower = cctpAddress.toLowerCase(); + + for (let i = 0; i < openProposal.targets.length; i++) { + if (openProposal.targets[i].toLowerCase() !== cctpLower) continue; + const sig = openProposal.signatures[i]; + if (!sig.startsWith('depositForBurn(')) continue; + + const calldata = openProposal.calldatas[i]; + // destinationDomain is the second parameter (uint32) in all depositForBurn variants + const decoded = utils.defaultAbiCoder.decode(['uint256', 'uint32'], utils.hexDataSlice(calldata, 0, 64)); + const domain = decoded[1]; + const network = CCTP_DOMAIN_TO_NETWORK[domain]; + if (network) networks.push(network); + } + return networks; +} + +export async function getProposalBridgeNetworks( + governanceDeploymentManager: DeploymentManager, + openProposal: OpenProposal +): Promise { + const roots = await getRoots(governanceDeploymentManager.cache); + const targets = openProposal.targets.map(t => t.toLowerCase()); + + const networks = new Set(); + for (const [alias, address] of roots) { + if (EXCLUDED_ROOTS.includes(alias)) continue; + + if (alias === 'CCTPTokenMessenger' && targets.includes(address.toLowerCase())) { + for (const net of parseCCTPNetworks(openProposal, address)) { + networks.add(net); + } + continue; + } + + const network = ROOT_TO_NETWORK[alias]; + if (network && targets.includes(address.toLowerCase())) { + networks.add(network); + } + } + return [...new Set(networks)]; +} + +const existingBridgeManagers: Record = {}; export async function isBridgeProposal( governanceDeploymentManager: DeploymentManager, bridgeDeploymentManager: DeploymentManager, openProposal: OpenProposal ) { - const bridgeNetwork = bridgeDeploymentManager.network; - console.log(`Checking if proposal ${openProposal.id} is a bridge proposal on ${bridgeNetwork}`); - switch (bridgeNetwork) { - case 'arbitrum': { - const inbox = await governanceDeploymentManager.getContractOrThrow('arbitrumInbox'); - const l1GatewayRouter = await governanceDeploymentManager.getContractOrThrow( - 'arbitrumL1GatewayRouter' - ); - const targets = openProposal.targets; - return targets.includes(inbox.address) || targets.includes(l1GatewayRouter.address); - } - case 'polygon': { - const { - fxRoot, - RootChainManager - } = await governanceDeploymentManager.getContracts(); - const bridgeAddresses = [fxRoot, RootChainManager] - .filter(x => x) - .map(x => x.address.toLowerCase()); - const targets = openProposal.targets; - return targets.some(t => bridgeAddresses.includes(t.toLowerCase())); + const bridgeNetworks = await getProposalBridgeNetworks(governanceDeploymentManager, openProposal); + const otherBridgeNetworks = bridgeNetworks.filter(n => n !== bridgeDeploymentManager.network); + const bridgeManagers = [bridgeDeploymentManager]; + if (!existingBridgeManagers[bridgeDeploymentManager.network]) { + existingBridgeManagers[bridgeDeploymentManager.network] = bridgeDeploymentManager; + } + if (!existingBridgeManagers[governanceDeploymentManager.network]) { + existingBridgeManagers[governanceDeploymentManager.network] = governanceDeploymentManager; + } + for(const bridgeNetwork of otherBridgeNetworks) { + if (existingBridgeManagers[bridgeNetwork]) { + bridgeManagers.push(existingBridgeManagers[bridgeNetwork]); + continue; } - case 'base': { - const baseL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - 'baseL1CrossDomainMessenger' - ); - const baseL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - 'baseL1StandardBridge' - ); - const baseL1USDSBridge = await governanceDeploymentManager.getContractOrThrow( - 'baseL1USDSBridge' - ); - const targets = openProposal.targets; - const bridgeContracts = [baseL1CrossDomainMessenger.address, baseL1StandardBridge.address, baseL1USDSBridge.address]; - - return targets.some(t => bridgeContracts.includes(t)); + + let deploymentToken: string; + + let dm: DeploymentManager; + let existingBridgedDm: DeploymentManager | undefined; + for (const cachedDm of governanceDeploymentManager.bridgedDeploymentManagers.values()) { + if (cachedDm.network === bridgeNetwork) { + existingBridgedDm = cachedDm; + break; + } } - case 'linea': { - const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( - 'lineaMessageService' - ); - const lineaL1USDCBridge = await governanceDeploymentManager.getContractOrThrow( - 'lineaL1USDCBridge' - ); - const lineaL1TokenBridge = await governanceDeploymentManager.getContractOrThrow( - 'lineaL1TokenBridge' - ); - const bridgeContracts = [ - lineaMessageService.address, - lineaL1USDCBridge.address, - lineaL1TokenBridge.address - ]; - const targets = openProposal.targets; - return targets.some(t => bridgeContracts.includes(t)); + + if (existingBridgedDm) { + dm = existingBridgedDm; + } else { + // default deployment token is USDC for all networks except Ronin (WETH) and Mantle (USDE) + switch (bridgeNetwork) { + case 'arbitrum': + case 'polygon': + case 'base': + case 'linea': + case 'optimism': + case 'unichain': + case 'scroll': + deploymentToken = 'usdc'; + break; + case 'mantle': + deploymentToken = 'usde'; + break; + case 'ronin': + deploymentToken = 'weth'; + break; + default: { + const tag = `[${governanceDeploymentManager.network} -> ${bridgeNetwork}]`; + throw new Error(`${tag} Unable to determine whether to relay Proposal ${openProposal.id}`); + } + } + + const hre = await forkedHreForBase({ name: '', network: bridgeNetwork, deployment: '' }); + dm = await governanceDeploymentManager.addBridgedDeploymentManager(bridgeNetwork, deploymentToken, hre); } - // case 'linea': { - // const governor = await governanceDeploymentManager.getContractOrThrow('governor'); - // const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( - // 'lineaMessageService' - // ); - // const { targets } = await governor.getActions(openProposal.id); - // return targets.includes(lineaMessageService.address); + + existingBridgeManagers[bridgeNetwork] = dm; + bridgeManagers.push(dm); + // switch (bridgeNetwork) { + // case 'mainnet': { + // continue; // Mainnet proposals are not bridge proposals + // } + // case 'arbitrum': { + // const inbox = await governanceDeploymentManager.getContractOrThrow('arbitrumInbox'); + // const l1GatewayRouter = await governanceDeploymentManager.getContractOrThrow( + // 'arbitrumL1GatewayRouter' + // ); + // const targets = openProposal.targets; + // return targets.includes(inbox.address) || targets.includes(l1GatewayRouter.address); + // } + // case 'polygon': { + // const { + // fxRoot, + // RootChainManager + // } = await governanceDeploymentManager.getContracts(); + // const bridgeAddresses = [fxRoot, RootChainManager] + // .filter(x => x) + // .map(x => x.address.toLowerCase()); + // const targets = openProposal.targets; + // return targets.some(t => bridgeAddresses.includes(t.toLowerCase())); + // } + // case 'base': { + // const baseL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + // 'baseL1CrossDomainMessenger' + // ); + // const baseL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + // 'baseL1StandardBridge' + // ); + // const baseL1USDSBridge = await governanceDeploymentManager.getContractOrThrow( + // 'baseL1USDSBridge' + // ); + // const targets = openProposal.targets; + // const bridgeContracts = [baseL1CrossDomainMessenger.address, baseL1StandardBridge.address, baseL1USDSBridge.address]; + + // return targets.some(t => bridgeContracts.includes(t)); + // } + // case 'linea': { + // const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( + // 'lineaMessageService' + // ); + // const lineaL1USDCBridge = await governanceDeploymentManager.getContractOrThrow( + // 'lineaL1USDCBridge' + // ); + // const lineaL1TokenBridge = await governanceDeploymentManager.getContractOrThrow( + // 'lineaL1TokenBridge' + // ); + // const bridgeContracts = [ + // lineaMessageService.address, + // lineaL1USDCBridge.address, + // lineaL1TokenBridge.address + // ]; + // const targets = openProposal.targets; + // return targets.some(t => bridgeContracts.includes(t)); + // } + // case 'optimism': { + // const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + // 'opL1CrossDomainMessenger' + // ); + // const opL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + // 'opL1StandardBridge' + // ); + // const targets = openProposal.targets; + // const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; + // return targets.some(t => bridgeContracts.includes(t)); + // } + // case 'mantle': { + // const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + // 'mantleL1CrossDomainMessenger' + // ); + // const mantleL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + // 'mantleL1StandardBridge' + // ); + // const targets = openProposal.targets; + // const bridgeContracts = [ + // mantleL1CrossDomainMessenger.address, + // mantleL1StandardBridge.address + // ]; + // return targets.some(t => bridgeContracts.includes(t)); + // } + // case 'unichain': { + // const unichainL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + // 'unichainL1CrossDomainMessenger' + // ); + // const unichainL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + // 'unichainL1StandardBridge' + // ); + // const targets = openProposal.targets; + // const bridgeContracts = [ + // unichainL1CrossDomainMessenger.address, + // unichainL1StandardBridge.address + // ]; + // return targets.some(t => bridgeContracts.includes(t)); + // } + // case 'scroll': { + // const scrollMessenger = await governanceDeploymentManager.getContractOrThrow( + // 'scrollMessenger' + // ); + // const targets = openProposal.targets; + // return targets.includes(scrollMessenger.address); + // } + // case 'ronin': { + // const governor = await governanceDeploymentManager.getContractOrThrow('governor'); + // const l1CCIPRouter = await governanceDeploymentManager.getContractOrThrow( + // 'l1CCIPRouter' + // ); + // const roninl1NativeBridge = await governanceDeploymentManager.getContractOrThrow( + // 'roninl1NativeBridge' + // ); + // const roninL1OnRamp = await governanceDeploymentManager.getContractOrThrow( + // 'roninl1CCIPOnRamp' + // ); + // const { targets } = await governor.proposalDetails(openProposal.id); + // const bridgeContracts = [ + // roninl1NativeBridge.address, + // l1CCIPRouter.address, + // roninL1OnRamp.address + // ]; + // return targets.some(t => bridgeContracts.includes(t)); + // } + // default: { + // const tag = `[${bridgeNetwork} -> ${governanceDeploymentManager.network}]`; + // throw new Error(`${tag} Unable to determine whether to relay Proposal ${openProposal.id}`); + // } // } - case 'optimism': { - const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - 'opL1CrossDomainMessenger' - ); - const opL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - 'opL1StandardBridge' - ); - const targets = openProposal.targets; - const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; - return targets.some(t => bridgeContracts.includes(t)); - } - case 'mantle': { - const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - 'mantleL1CrossDomainMessenger' - ); - const mantleL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - 'mantleL1StandardBridge' - ); - const targets = openProposal.targets; - const bridgeContracts = [ - mantleL1CrossDomainMessenger.address, - mantleL1StandardBridge.address - ]; - return targets.some(t => bridgeContracts.includes(t)); - } - case 'unichain': { - const unichainL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - 'unichainL1CrossDomainMessenger' - ); - const unichainL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - 'unichainL1StandardBridge' - ); - const targets = openProposal.targets; - const bridgeContracts = [ - unichainL1CrossDomainMessenger.address, - unichainL1StandardBridge.address - ]; - return targets.some(t => bridgeContracts.includes(t)); - } - case 'scroll': { - const scrollMessenger = await governanceDeploymentManager.getContractOrThrow( - 'scrollMessenger' - ); - const targets = openProposal.targets; - return targets.includes(scrollMessenger.address); - } - case 'ronin': { - const governor = await governanceDeploymentManager.getContractOrThrow('governor'); - const l1CCIPRouter = await governanceDeploymentManager.getContractOrThrow( - 'l1CCIPRouter' - ); - const roninl1NativeBridge = await governanceDeploymentManager.getContractOrThrow( - 'roninl1NativeBridge' - ); - const roninL1OnRamp = await governanceDeploymentManager.getContractOrThrow( - 'roninl1CCIPOnRamp' - ); - const { targets } = await governor.proposalDetails(openProposal.id); - const bridgeContracts = [ - roninl1NativeBridge.address, - l1CCIPRouter.address, - roninL1OnRamp.address - ]; - return targets.some(t => bridgeContracts.includes(t)); - } - default: { - const tag = `[${bridgeNetwork} -> ${governanceDeploymentManager.network}]`; - throw new Error(`${tag} Unable to determine whether to relay Proposal ${openProposal.id}`); - } } + return bridgeManagers; } + diff --git a/scenario/utils/relayArbitrumMessage.ts b/scenario/utils/relayArbitrumMessage.ts index 550979448..3c19598a7 100644 --- a/scenario/utils/relayArbitrumMessage.ts +++ b/scenario/utils/relayArbitrumMessage.ts @@ -1,7 +1,7 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; -import { utils, BigNumber } from 'ethers'; +import { utils, BigNumber, Contract, constants } from 'ethers'; import { Log } from '@ethersproject/abstract-provider'; import { sourceTokens } from '../../plugins/scenario/utils/TokenSourcer'; import { OpenBridgedProposal } from '../context/Gov'; @@ -84,18 +84,18 @@ export async function relayArbitrumMessage( const header = '0x'; const headerLength = header.length; const wordLength = 2 * 32; - const innnerData = header + data.slice(headerLength + (11 * wordLength)); + const innerData = header + data.slice(headerLength + (11 * wordLength)); const toValue = data.slice(headerLength + (2 * wordLength), headerLength + (3 * wordLength)); let toAddress = BigNumber.from(`0x${toValue}`).toHexString(); - // if lenght of toAddress is less than 42, then it is padded with 0s and we need to add them after 0x + // if length of toAddress is less than 42, then it is padded with 0s and we need to add them after 0x if(toAddress.length < 42) { toAddress = `0x${toAddress.slice(2).padStart(40, '0')}`; } const messageNum = topics[1]; return { - data: innnerData, + data: innerData, toAddress, messageNum }; @@ -232,6 +232,18 @@ export async function relayArbitrumMessage( await signer.getAddress() ); } else { + // Mock ArbSys precompile (0x64) — Arbitrum precompiles don't exist in Hardhat's EVM, + // but the L2 gateways call ArbSys.sendTxToL1 internally during outboundTransfer. + // Bytecode 0x60206000f3 disassembles to: PUSH1 0x20 | PUSH1 0x00 | RETURN + // which returns 32 zero bytes from uninitialized memory for any call. + await bridgeDeploymentManager.hre.network.provider.request({ + method: 'hardhat_setCode', + params: [ + '0x0000000000000000000000000000000000000064', + '0x60206000f3', + ], + }); + await bridgeReceiver.executeProposal(id, { gasPrice: 0 }); } openBridgedProposals.push({ @@ -244,6 +256,189 @@ export async function relayArbitrumMessage( return openBridgedProposals; } +export async function simulateL2ToL1TokenBridging( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + tenderlyLogs?: any[], + proposalId?: BigNumber +) { + if(tenderlyLogs) { + return; + } + console.log('Simulating L2→L1 token bridging for any executed Arbitrum proposals...'); + + // L2 contracts + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + + // Parse recent ProposalCreated events to find actions that bridge tokens from L2 to L1 + // ProposalCreated(address indexed rootMessageSender, uint256 id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 eta) + console.log('Fetching recent ProposalCreated events from BridgeReceiver...'); + const latestBlockNumber = await bridgeDeploymentManager.hre.ethers.provider.getBlockNumber(); + const proposalCreatedEvents = await bridgeDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: latestBlockNumber - 1000, // look back 1000 blocks for ProposalCreated events, which should be sufficient to cover any recent proposals given typical block times on Arbitrum + toBlock: 'latest', + address: bridgeReceiver.address, + topics: [utils.id('ProposalCreated(address,uint256,address[],uint256[],string[],bytes[],uint256)')] + }); + const outboundTransferSignature = 'outboundTransfer(address,address,uint256,bytes)'; + const outboundTransfer2Signature = 'outboundTransfer(address,address,uint256,uint256,uint256,bytes)'; + const depositForBurnSignature = 'depositForBurn(uint256,uint32,bytes32,address,bytes32,uint256,uint32)'; + const ARBITRUM_GATEWAY_ROUTER = '0x5288c571Fd7aD117beA99bF60FE0846C4E84F933'; + const ARBITRUM_BRIDGE = '0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a'; + const ARBITRUM_OUTBOX = '0x667e23ABd27E623c11d4CC00ca3EC4d0bD63337a'; + const MAINNET_WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + + for (const event of proposalCreatedEvents) { + const decodedEvent = bridgeReceiver.interface.parseLog(event); + const { id, targets, signatures, calldatas } = decodedEvent.args; + + if (proposalId && id.toString() !== proposalId.toString()) { + continue; + } + + for (let i = 0; i < signatures.length; i++) { + // Look for L2→L1 outboundTransfer calls (standard Arbitrum gateway bridge) + if (signatures[i] === outboundTransferSignature || signatures[i] === outboundTransfer2Signature) { + const [l1Token, to, amount] = (() => { + if (signatures[i] === outboundTransferSignature) { + return utils.defaultAbiCoder.decode( + ['address', 'address', 'uint256', 'bytes'], + calldatas[i] + ); + } else if (signatures[i] === outboundTransfer2Signature) { + return utils.defaultAbiCoder.decode( + ['address', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], + calldatas[i] + ); + } + })(); + console.log(`Simulating L2→L1 token bridging: ${amount.toString()} of ${l1Token} to ${to}`); + + const gatewayAddress = await (async () => { + if(targets[i].toLowerCase() === ARBITRUM_GATEWAY_ROUTER.toLowerCase()) { // Arbitrum WETH gateway + const router = new Contract( + ARBITRUM_GATEWAY_ROUTER, + ['function l1TokenToGateway(address l1Token) view returns (address)'], + await governanceDeploymentManager.getSigner() + ); + return await router.l1TokenToGateway(l1Token); + } + return targets[i]; + })(); + const l2Gateway = new Contract( + gatewayAddress, + ['function counterpartGateway() view returns (address)'], + await bridgeDeploymentManager.getSigner() + ); + const l1GatewayAddress = await l2Gateway.counterpartGateway(); + + const l1Gateway = new Contract( + l1GatewayAddress, + [ + 'function finalizeInboundTransfer(address _token, address _from, address _to, uint256 _amount, bytes calldata _data)', + 'function inbox() view returns (address)' + ], + await governanceDeploymentManager.getSigner() + ); + // override 0x4 slot in outbox to L2 gateway + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + ARBITRUM_OUTBOX, + '0x4', + utils.hexZeroPad(gatewayAddress, 32) + ]); + + // impersonate outbox to call finalizeInboundTransfer, as if the message came from L2 gateway + const outboxSigner = await impersonateAddress( + governanceDeploymentManager, + ARBITRUM_OUTBOX + ); + + await governanceDeploymentManager.hre.network.provider.send('hardhat_setBalance', [ + outboxSigner.address, + '0x1000000000000000000', + ]); + + const arbitrumBridge = new Contract( + ARBITRUM_BRIDGE, + ['function executeCall(address to, uint256 value, bytes calldata data)'], + outboxSigner + ); + + const data = l1Gateway.interface.encodeFunctionData( + 'finalizeInboundTransfer', + [ + l1Token, + ARBITRUM_GATEWAY_ROUTER, + to, amount, + utils.defaultAbiCoder.encode(['uint256', 'bytes'], [0, '0x']) + ]); + console.log(`Relaying message to L1 gateway at ${l1GatewayAddress} with data: ${data}`); + const bridgeTx = await arbitrumBridge.connect(outboxSigner).executeCall( + l1Gateway.address, + l1Token.toLowerCase() === MAINNET_WETH.toLowerCase() ? amount : 0, + data, + ); + await (bridgeTx).wait(); + // stop impersonation after the call + await governanceDeploymentManager.hre.network.provider.send('hardhat_stopImpersonatingAccount', [ + outboxSigner.address + ]); + // override 0x4 slot in outbox to L2 gateway + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + ARBITRUM_OUTBOX, + '0x4', + utils.hexZeroPad(constants.AddressZero, 32) + ]); + } + + // Look for L2→L1 CCTP depositForBurn calls (Circle CCTP bridge, e.g. native USDC) + if (signatures[i] === depositForBurnSignature) { + const [amount, , mintRecipientBytes32, burnToken] = utils.defaultAbiCoder.decode( + ['uint256', 'uint32', 'bytes32', 'address', 'bytes32', 'uint256', 'uint32'], + calldatas[i] + ); + + const mintRecipient = utils.getAddress('0x' + utils.hexlify(mintRecipientBytes32).slice(-40)); + + try { + // L2 + const l2CCTPTokenMessenger = await bridgeDeploymentManager.getContractOrThrow('CCTPMessageTransmitter'); + // Resolve L1 token via CCTP TokenMinter: burnToken (L2) → localToken (L1) + const l1CCTPTokenMessenger = await governanceDeploymentManager.getContractOrThrow('CCTPTokenMessenger'); + const tokenMinterAddress = await l1CCTPTokenMessenger.localMinter(); + const L1TokenMinter = new Contract( + tokenMinterAddress, + ['function mint(uint32 sourceDomain, bytes32 burnToken, address recipientOne, address recipientTwo, uint256 amountOne, uint256 amountTwo) returns (address)'], + await governanceDeploymentManager.getSigner() + ); + const l1CCTPTokenMessengerSigner = await impersonateAddress( + governanceDeploymentManager, + l1CCTPTokenMessenger.address + ); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setBalance', [ + l1CCTPTokenMessengerSigner.address, + '0x1000000000000000000', + ]); + const sourceDomain = await l2CCTPTokenMessenger.localDomain(); + const mintTx = await L1TokenMinter.connect(l1CCTPTokenMessengerSigner).mint( + sourceDomain, + utils.hexZeroPad(burnToken, 32), + mintRecipient, + L1TokenMinter.address, // mint to the token minter first, since some tokens (e.g. USDC) have a cap on max amount per mint, and the token minter can then transfer to the recipient + amount, + 1 + ); + console.log('Simulated CCTP mint transaction:', mintTx.hash); + await mintTx.wait(); + } catch (e) { + console.log(`Warning: Could not simulate CCTP L2→L1 bridging for depositForBurn: ${e.message}`); + } + } + await governanceDeploymentManager.hre.network.provider.send('evm_mine'); + } + } +} + export async function relayArbitrumCCTPMint( governanceDeploymentManager: DeploymentManager, bridgeDeploymentManager: DeploymentManager, diff --git a/scenario/utils/relayBaseMessage.ts b/scenario/utils/relayBaseMessage.ts index 1e5418c11..eb5df2f78 100644 --- a/scenario/utils/relayBaseMessage.ts +++ b/scenario/utils/relayBaseMessage.ts @@ -1,7 +1,7 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; -import { BigNumber, ethers } from 'ethers'; +import { BigNumber, ethers, utils } from 'ethers'; import { Log } from '@ethersproject/abstract-provider'; import { OpenBridgedProposal } from '../context/Gov'; import { applyL1ToL2Alias, isTenderlyLog } from './index'; @@ -207,4 +207,116 @@ export default async function relayBaseMessage( } return openBridgedProposals; -} \ No newline at end of file +} + +export async function simulateL2ToL1TokenBridging( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + tenderlyLogs?: any[], + proposalId?: BigNumber +) { + if(tenderlyLogs) { + return; + } + console.log('Simulating L2→L1 token bridging for any executed Base proposals...'); + + // L2 contracts + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + const baseL2Bridge = await bridgeDeploymentManager.getContractOrThrow('l2StandardBridge'); + const l2CrossDomainMessenger = await bridgeDeploymentManager.getContractOrThrow('l2CrossDomainMessenger'); + + // L1 contracts + const baseL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow('baseL1CrossDomainMessenger'); + const baseL1Bridge = await governanceDeploymentManager.getContractOrThrow('baseL1StandardBridge'); + const BASE_L1_PORTAL = '0x49048044D57e1C92A77f79988d21Fa8fAF74E97e'; + + // Parse recent ProposalCreated events to find actions that bridge tokens from L2 to L1 + // ProposalCreated(address indexed rootMessageSender, uint256 id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 eta) + console.log('Fetching recent ProposalCreated events from BridgeReceiver...'); + const latestBlockNumber = await bridgeDeploymentManager.hre.ethers.provider.getBlockNumber(); + const proposalCreatedEvents = await bridgeDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: latestBlockNumber - 1000, // look back 1000 blocks for ProposalCreated events, which should be sufficient to cover any recent proposals given typical block times on Base + toBlock: 'latest', + address: bridgeReceiver.address, + topics: [utils.id('ProposalCreated(address,uint256,address[],uint256[],string[],bytes[],uint256)')] + }); + + const bridgeERC20ToSignature = 'bridgeERC20To(address,address,address,uint256,uint32,bytes)'; + + for (const event of proposalCreatedEvents) { + const decodedEvent = bridgeReceiver.interface.parseLog(event); + const { id, signatures, calldatas } = decodedEvent.args; + + if (proposalId && id.toString() !== proposalId.toString()) { + continue; + } + + for (let i = 0; i < signatures.length; i++) { + if (signatures[i] === bridgeERC20ToSignature) { + const [localToken, remoteToken, to, amount, , extraData] = utils.defaultAbiCoder.decode( + ['address', 'address', 'address', 'uint256', 'uint32', 'bytes'], + calldatas[i] + ); + + console.log(`Simulating L2→L1 bridgeERC20To: ${amount.toString()} of ${remoteToken} to ${to}`); + + console.log('Setting up L1 state to simulate finalizeBridgeERC20...'); + console.log('Base L1 Portal address:', BASE_L1_PORTAL); + console.log('Overriding slot', utils.hexZeroPad('0x32', 32)); + console.log('l2CrossDomainMessenger:', utils.hexZeroPad(l2CrossDomainMessenger.address, 32)); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + BASE_L1_PORTAL, + utils.hexZeroPad('0x32', 32), + utils.hexZeroPad(l2CrossDomainMessenger.address, 32) + ]); + + // Set deposits[_localToken][_remoteToken] on L1StandardBridge so finalizeBridgeERC20 won't underflow + // deposits mapping is at base slot 2 in L1StandardBridge storage layout + // In finalizeBridgeERC20 context: _localToken = remoteToken (L1), _remoteToken = localToken (L2) + const depositsBaseSlot = 2; + const innerSlot = utils.keccak256( + utils.defaultAbiCoder.encode(['address', 'uint256'], [remoteToken, depositsBaseSlot]) + ); + const depositsSlot = utils.keccak256( + utils.defaultAbiCoder.encode(['address', 'bytes32'], [localToken, innerSlot]) + ); + + console.log(`Setting deposits[${remoteToken}][${localToken}] to ${amount.toString()} at slot ${depositsSlot} on ${baseL1Bridge.address}`); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + baseL1Bridge.address, + depositsSlot, + utils.hexZeroPad(amount.toHexString(), 32) + ]); + + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + baseL1CrossDomainMessenger.address, + '0xcc', + utils.hexZeroPad(baseL2Bridge.address, 32) + ]); + + const domainMessengerSigner = await impersonateAddress( + governanceDeploymentManager, + baseL1CrossDomainMessenger.address + ); + + await governanceDeploymentManager.hre.network.provider.send('hardhat_setBalance', [ + domainMessengerSigner.address, + ethers.utils.hexStripZeros(ethers.utils.parseEther('1').toHexString()), + ]); + + await ( + await baseL1Bridge.connect(domainMessengerSigner).finalizeBridgeERC20( + remoteToken, localToken, bridgeReceiver.address, to, amount, extraData, + { gasPrice: 0, gasLimit: 2_500_000 } + ) + ).wait(); + + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + BASE_L1_PORTAL, + utils.hexZeroPad('0x32', 32), + utils.hexZeroPad('0xdead', 32) + ]); + } + } + } +} diff --git a/scenario/utils/relayLineaMessage.ts b/scenario/utils/relayLineaMessage.ts index 78f2b0f14..74da86b7a 100644 --- a/scenario/utils/relayLineaMessage.ts +++ b/scenario/utils/relayLineaMessage.ts @@ -6,7 +6,7 @@ import { OpenBridgedProposal } from '../context/Gov'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { isTenderlyLog } from './index'; -const LINEA_SETTER_ROLE_ACCOUNT = '0xc1C6B09D1eB6fCA0fF3cA11027E5Bc4AeDb47F67'; +const LINEA_SETTER_ROLE_ACCOUNT = '0x2b0F9C76970975aec03784EFd763623757EF7652'; export default async function relayLineaMessage( governanceDeploymentManager: DeploymentManager, diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index fa0523665..52c7d7fca 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -1,9 +1,9 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; import relayPolygonMessage from './relayPolygonMessage'; -import { relayArbitrumMessage, relayArbitrumCCTPMint } from './relayArbitrumMessage'; +import { relayArbitrumMessage, relayArbitrumCCTPMint, simulateL2ToL1TokenBridging } from './relayArbitrumMessage'; import relayBaseMessage from './relayBaseMessage'; import relayLineaMessage from './relayLineaMessage'; -import relayOptimismMessage from './relayOptimismMessage'; +import relayOptimismMessage, { simulateL2ToL1TokenBridging as simulateOptimismL2ToL1TokenBridging } from './relayOptimismMessage'; import relayMantleMessage from './relayMantleMessage'; import { relayUnichainMessage, relayUnichainCCTPMint } from './relayUnichainMessage'; import relayScrollMessage from './relayScrollMessage'; @@ -16,23 +16,36 @@ export default async function relayMessage( tenderlyLogs?: any[] ) { const bridgeNetwork = bridgeDeploymentManager.network; + if(bridgeNetwork === governanceDeploymentManager.network) return; // no need to relay if the proposal is on the same network console.log(`Relaying messages from ${governanceDeploymentManager.network} -> ${bridgeNetwork}`); let proposal; switch (bridgeNetwork) { case 'base': - return await relayBaseMessage( + proposal = await relayBaseMessage( governanceDeploymentManager, bridgeDeploymentManager, startingBlockNumber, tenderlyLogs ); + await simulateOptimismL2ToL1TokenBridging( + governanceDeploymentManager, + bridgeDeploymentManager, + tenderlyLogs + ); + return proposal; case 'optimism': - return await relayOptimismMessage( + proposal = await relayOptimismMessage( governanceDeploymentManager, bridgeDeploymentManager, startingBlockNumber, tenderlyLogs ); + await simulateOptimismL2ToL1TokenBridging( + governanceDeploymentManager, + bridgeDeploymentManager, + tenderlyLogs + ); + return proposal; case 'mantle': return await relayMantleMessage( governanceDeploymentManager, @@ -74,6 +87,11 @@ export default async function relayMessage( startingBlockNumber, tenderlyLogs ); + await simulateL2ToL1TokenBridging( + governanceDeploymentManager, + bridgeDeploymentManager, + tenderlyLogs + ); return proposal; case 'linea': return await relayLineaMessage( diff --git a/scenario/utils/relayOptimismMessage.ts b/scenario/utils/relayOptimismMessage.ts index 5ce1d076d..02b2d9cd8 100644 --- a/scenario/utils/relayOptimismMessage.ts +++ b/scenario/utils/relayOptimismMessage.ts @@ -1,7 +1,7 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; -import { BigNumber, ethers } from 'ethers'; +import { BigNumber, ethers, utils } from 'ethers'; import { Log } from '@ethersproject/abstract-provider'; import { OpenBridgedProposal } from '../context/Gov'; import { applyL1ToL2Alias, isTenderlyLog } from './index'; @@ -176,3 +176,113 @@ export default async function relayOptimismMessage( return openBridgedProposals; } } + +export async function simulateL2ToL1TokenBridging( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + tenderlyLogs?: any[], + proposalId?: BigNumber +) { + if(tenderlyLogs) { + return; + } + console.log('Simulating L2→L1 token bridging for any executed Optimism proposals...'); + + // L2 contracts + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + const optimismL2Bridge = await bridgeDeploymentManager.getContractOrThrow('l2StandardBridge'); + const l2CrossDomainMessenger = await bridgeDeploymentManager.getContractOrThrow('l2CrossDomainMessenger'); + + // L1 contracts + const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow('opL1CrossDomainMessenger'); + const optimismL1Bridge = await governanceDeploymentManager.getContractOrThrow('opL1StandardBridge'); + const OPTIMISM_L1_PORTAL = '0xbEb5Fc579115071764c7423A4f12eDde41f106Ed'; + + // Parse recent ProposalCreated events to find actions that bridge tokens from L2 to L1 + // ProposalCreated(address indexed rootMessageSender, uint256 id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 eta) + console.log('Fetching recent ProposalCreated events from BridgeReceiver...'); + const latestBlockNumber = await bridgeDeploymentManager.hre.ethers.provider.getBlockNumber(); + const proposalCreatedEvents = await bridgeDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: latestBlockNumber - 1000, // look back 1000 blocks for ProposalCreated events, which should be sufficient to cover any recent proposals given typical block times on Optimism + toBlock: 'latest', + address: bridgeReceiver.address, + topics: [utils.id('ProposalCreated(address,uint256,address[],uint256[],string[],bytes[],uint256)')] + }); + + const bridgeERC20ToSignature = 'bridgeERC20To(address,address,address,uint256,uint32,bytes)'; + + for (const event of proposalCreatedEvents) { + const decodedEvent = bridgeReceiver.interface.parseLog(event); + const { id, signatures, calldatas } = decodedEvent.args; + + if (proposalId && id.toString() !== proposalId.toString()) { + continue; + } + + for (let i = 0; i < signatures.length; i++) { + if (signatures[i] === bridgeERC20ToSignature) { + const [localToken, remoteToken, to, amount, , extraData] = utils.defaultAbiCoder.decode( + ['address', 'address', 'address', 'uint256', 'uint32', 'bytes'], + calldatas[i] + ); + + console.log(`Simulating L2→L1 bridgeERC20To: ${amount.toString()} of ${remoteToken} to ${to}`); + + console.log('Setting up L1 state to simulate finalizeBridgeERC20...'); + console.log('Optimism L1 Portal address:', OPTIMISM_L1_PORTAL); + console.log('Overriding slot', utils.hexZeroPad('0x32', 32)); + console.log('l2CrossDomainMessenger:', utils.hexZeroPad(l2CrossDomainMessenger.address, 32)); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + OPTIMISM_L1_PORTAL, + utils.hexZeroPad('0x32', 32), + utils.hexZeroPad(l2CrossDomainMessenger.address, 32) + ]); + + // Set deposits[_localToken][_remoteToken] on L1StandardBridge so finalizeBridgeERC20 won't underflow + // deposits mapping is at base slot 2 in L1StandardBridge storage layout + // In finalizeBridgeERC20 context: _localToken = remoteToken (L1), _remoteToken = localToken (L2) + const depositsBaseSlot = 2; + const innerSlot = utils.keccak256( + utils.defaultAbiCoder.encode(['address', 'uint256'], [remoteToken, depositsBaseSlot]) + ); + const depositsSlot = utils.keccak256( + utils.defaultAbiCoder.encode(['address', 'bytes32'], [localToken, innerSlot]) + ); + + console.log(`Setting deposits[${remoteToken}][${localToken}] to ${amount.toString()} at slot ${depositsSlot} on ${optimismL1Bridge.address}`); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + optimismL1Bridge.address, + depositsSlot, + utils.hexZeroPad(amount.toHexString(), 32) + ]); + + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + opL1CrossDomainMessenger.address, + '0xcc', + utils.hexZeroPad(optimismL2Bridge.address, 32) + ]); + + const domainMessengerSigner = await impersonateAddress( + governanceDeploymentManager, + opL1CrossDomainMessenger.address + ); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setBalance', [ + domainMessengerSigner.address, + ethers.utils.hexStripZeros(ethers.utils.parseEther('1').toHexString()), + ]); + + await ( + await optimismL1Bridge.connect(domainMessengerSigner).finalizeBridgeERC20( + remoteToken, localToken, bridgeReceiver.address, to, amount, extraData, + { gasPrice: 0, gasLimit: 2_500_000 } + ) + ).wait(); + await governanceDeploymentManager.hre.network.provider.send('hardhat_setStorageAt', [ + OPTIMISM_L1_PORTAL, + utils.hexZeroPad('0x32', 32), + utils.hexZeroPad('0xdead', 32) + ]); + } + } + } +} diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 181a6e168..c90a5c287 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -103,10 +103,12 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { } if (ctx.world.base.network === 'optimism' && ctx.world.base.deployment === 'usdc') { - config.bulkerAsset = 10000; - config.bulkerAsset1 = 10000; - config.transferAsset1 = 10000; - config.withdrawAsset = 7000; + config.bulkerAsset = 100000; + config.bulkerAsset1 = 100000; + config.transferAsset = 100000; + config.transferAsset1 = 100000; + config.rewardsAsset = 100000; + config.withdrawAsset = 100000; } if (ctx.world.base.network === 'optimism' && ctx.world.base.deployment === 'usdt') { @@ -127,6 +129,8 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { config.transferAsset = 500000; config.transferAsset1 = 500000; config.transferBase = 100; + config.rewardsBase = 100; + config.withdrawBase = 100; if(i == 8) { // tBTC config.supplyCollateral = 2; config.transferCollateral = 2; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 4e96469ca..c6ed9019b 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -160,6 +160,7 @@ export const WHALES = { '0xcf3D55c10DB69f28fD1A75Bd73f3D8A2d9c595ad', // cbETH whale '0xb125E6687d4313864e53df431d5425969c15Eb2F', // cbETH whale '0x1539A4611f16a139891c14365Cab86599F3A8AFC', // tBTC whale + '0x0a1d576f3eFeF75b330424287a95A366e8281D54', // USDbC whale ], scroll: [ '0xaaaaAAAACB71BF2C8CaE522EA5fa455571A74106', // USDC whale @@ -281,7 +282,7 @@ export async function proposal( const { target, value, signature, calldata: cd } = action as TargetAction; targets.push(target); values.push(value ?? 0); - calldatas.push(utils.id(signature).slice(0, 10) + cd.slice(2)); + calldatas.push(signature ? utils.id(signature).slice(0, 10) + cd.slice(2) : cd); signatures.push(''); } } diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index d4378cb64..47bfc06c7 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -4,7 +4,7 @@ import { writeEnacted } from '../../plugins/deployment_manager/Enacted'; import { HardhatRuntimeEnvironment, HardhatConfig } from 'hardhat/types'; import { DeploymentManager, VerifyArgs } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; -import hreForBase from '../../plugins/scenario/utils/hreForBase'; +import hreForBase, { migrationStarted} from '../../plugins/scenario/utils/hreForBase'; // TODO: Don't depend on scenario's hreForBase async function getForkEnv(env: HardhatRuntimeEnvironment, deployment: string): Promise { @@ -248,6 +248,7 @@ task('migrate', 'Runs migration') governanceDm = dm; } + migrationStarted(); if (impersonate && !simulate) { throw new Error('Cannot impersonate an address if not simulating a migration. Please specify --simulate to simulate.'); } else if (impersonate && simulate) { @@ -376,6 +377,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') governanceDm = dm; } + migrationStarted(); if (impersonate && !simulate) { throw new Error('Cannot impersonate an address if not simulating a migration. Please specify --simulate to simulate.'); } else if (impersonate && simulate) { From 56096fc51cfb02b6b737d0e772ddc4c638325c30 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 24 Apr 2026 16:57:41 +0300 Subject: [PATCH 2/2] fix: clean up --- hardhat.config.ts | 8 -- scenario/utils/index.ts | 7 -- scenario/utils/isBridgeProposal.ts | 126 ----------------------------- 3 files changed, 141 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index fe9234d04..9c6330826 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,7 +3,6 @@ import 'dotenv/config'; import { HardhatUserConfig, subtask, task } from 'hardhat/config'; import '@compound-finance/hardhat-import'; import '@nomiclabs/hardhat-etherscan'; -// import '@tenderly/hardhat-tenderly'; import '@nomiclabs/hardhat-ethers'; import '@typechain/hardhat'; import 'hardhat-chai-matchers'; @@ -704,13 +703,6 @@ const config: HardhatUserConfig = { ], }, - // tenderly: { - // project: 'comet', - // username: process.env.TENDERLY_USERNAME || '', - // accessKey: process.env.TENDERLY_ACCESS_KEY || '', - // privateVerification: false, - // }, - mocha: { reporter: 'mocha-multi-reporters', reporterOptions: { diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 824adbb65..0f5474711 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -1001,12 +1001,6 @@ async function simulateBundle( const results = []; for (const sim of simulations) { - // const { - // username, - // project, - // accessKey - // } = (dm.hre.config as any).tenderly; - const project = 'comet'; const username = process.env.TENDERLY_USERNAME || ''; const accessKey = process.env.TENDERLY_ACCESS_KEY || ''; @@ -1067,7 +1061,6 @@ async function simulateBundle( } async function shareSimulation(dm: DeploymentManager, simulationId: string) { - // const { username, project, accessKey } = (dm.hre.config as any).tenderly; const project = 'comet'; const username = process.env.TENDERLY_USERNAME || ''; const accessKey = process.env.TENDERLY_ACCESS_KEY || ''; diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 7f3f71d2a..63957eccf 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -149,132 +149,6 @@ export async function isBridgeProposal( existingBridgeManagers[bridgeNetwork] = dm; bridgeManagers.push(dm); - // switch (bridgeNetwork) { - // case 'mainnet': { - // continue; // Mainnet proposals are not bridge proposals - // } - // case 'arbitrum': { - // const inbox = await governanceDeploymentManager.getContractOrThrow('arbitrumInbox'); - // const l1GatewayRouter = await governanceDeploymentManager.getContractOrThrow( - // 'arbitrumL1GatewayRouter' - // ); - // const targets = openProposal.targets; - // return targets.includes(inbox.address) || targets.includes(l1GatewayRouter.address); - // } - // case 'polygon': { - // const { - // fxRoot, - // RootChainManager - // } = await governanceDeploymentManager.getContracts(); - // const bridgeAddresses = [fxRoot, RootChainManager] - // .filter(x => x) - // .map(x => x.address.toLowerCase()); - // const targets = openProposal.targets; - // return targets.some(t => bridgeAddresses.includes(t.toLowerCase())); - // } - // case 'base': { - // const baseL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - // 'baseL1CrossDomainMessenger' - // ); - // const baseL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - // 'baseL1StandardBridge' - // ); - // const baseL1USDSBridge = await governanceDeploymentManager.getContractOrThrow( - // 'baseL1USDSBridge' - // ); - // const targets = openProposal.targets; - // const bridgeContracts = [baseL1CrossDomainMessenger.address, baseL1StandardBridge.address, baseL1USDSBridge.address]; - - // return targets.some(t => bridgeContracts.includes(t)); - // } - // case 'linea': { - // const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( - // 'lineaMessageService' - // ); - // const lineaL1USDCBridge = await governanceDeploymentManager.getContractOrThrow( - // 'lineaL1USDCBridge' - // ); - // const lineaL1TokenBridge = await governanceDeploymentManager.getContractOrThrow( - // 'lineaL1TokenBridge' - // ); - // const bridgeContracts = [ - // lineaMessageService.address, - // lineaL1USDCBridge.address, - // lineaL1TokenBridge.address - // ]; - // const targets = openProposal.targets; - // return targets.some(t => bridgeContracts.includes(t)); - // } - // case 'optimism': { - // const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - // 'opL1CrossDomainMessenger' - // ); - // const opL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - // 'opL1StandardBridge' - // ); - // const targets = openProposal.targets; - // const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; - // return targets.some(t => bridgeContracts.includes(t)); - // } - // case 'mantle': { - // const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - // 'mantleL1CrossDomainMessenger' - // ); - // const mantleL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - // 'mantleL1StandardBridge' - // ); - // const targets = openProposal.targets; - // const bridgeContracts = [ - // mantleL1CrossDomainMessenger.address, - // mantleL1StandardBridge.address - // ]; - // return targets.some(t => bridgeContracts.includes(t)); - // } - // case 'unichain': { - // const unichainL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( - // 'unichainL1CrossDomainMessenger' - // ); - // const unichainL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( - // 'unichainL1StandardBridge' - // ); - // const targets = openProposal.targets; - // const bridgeContracts = [ - // unichainL1CrossDomainMessenger.address, - // unichainL1StandardBridge.address - // ]; - // return targets.some(t => bridgeContracts.includes(t)); - // } - // case 'scroll': { - // const scrollMessenger = await governanceDeploymentManager.getContractOrThrow( - // 'scrollMessenger' - // ); - // const targets = openProposal.targets; - // return targets.includes(scrollMessenger.address); - // } - // case 'ronin': { - // const governor = await governanceDeploymentManager.getContractOrThrow('governor'); - // const l1CCIPRouter = await governanceDeploymentManager.getContractOrThrow( - // 'l1CCIPRouter' - // ); - // const roninl1NativeBridge = await governanceDeploymentManager.getContractOrThrow( - // 'roninl1NativeBridge' - // ); - // const roninL1OnRamp = await governanceDeploymentManager.getContractOrThrow( - // 'roninl1CCIPOnRamp' - // ); - // const { targets } = await governor.proposalDetails(openProposal.id); - // const bridgeContracts = [ - // roninl1NativeBridge.address, - // l1CCIPRouter.address, - // roninL1OnRamp.address - // ]; - // return targets.some(t => bridgeContracts.includes(t)); - // } - // default: { - // const tag = `[${bridgeNetwork} -> ${governanceDeploymentManager.network}]`; - // throw new Error(`${tag} Unable to determine whether to relay Proposal ${openProposal.id}`); - // } - // } } return bridgeManagers; }