diff --git a/hardhat.config.ts b/hardhat.config.ts index 3b7861abd..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/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..0f5474711 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,9 @@ 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 || ''; // Merge rolling state changes with simulation's own state_objects const stateObjects = sim.state_objects @@ -1043,7 +1061,9 @@ 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 || ''; return axios.post( `https://api.tenderly.co/api/v1/account/${username}/project/${project}/simulations/${simulationId}/share`, {}, @@ -1500,25 +1520,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..63957eccf 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -1,142 +1,155 @@ 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())); - } - 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 '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); - // } - 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); + 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 '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)); + + let deploymentToken: string; + + let dm: DeploymentManager; + let existingBridgedDm: DeploymentManager | undefined; + for (const cachedDm of governanceDeploymentManager.bridgedDeploymentManagers.values()) { + if (cachedDm.network === bridgeNetwork) { + existingBridgedDm = cachedDm; + break; + } } - default: { - const tag = `[${bridgeNetwork} -> ${governanceDeploymentManager.network}]`; - throw new Error(`${tag} Unable to determine whether to relay Proposal ${openProposal.id}`); + + 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); } + + existingBridgeManagers[bridgeNetwork] = dm; + bridgeManagers.push(dm); } + 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/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) {