github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/tasks/deposit-eth.ts (about) 1 import { promises as fs } from 'fs' 2 3 import { task, types } from 'hardhat/config' 4 import '@nomiclabs/hardhat-ethers' 5 import 'hardhat-deploy' 6 import { Deployment } from 'hardhat-deploy/types' 7 import { predeploys } from '@eth-optimism/core-utils' 8 import { providers, utils, ethers } from 'ethers' 9 import Artifact__L2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json' 10 import Artifact__L2CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json' 11 import Artifact__L2StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json' 12 import Artifact__OptimismPortal from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal.sol/OptimismPortal.json' 13 import Artifact__L1CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json' 14 import Artifact__L1StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json' 15 import Artifact__L2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json' 16 17 import { 18 CrossChainMessenger, 19 MessageStatus, 20 CONTRACT_ADDRESSES, 21 OEContractsLike, 22 DEFAULT_L2_CONTRACT_ADDRESSES, 23 } from '../src' 24 25 const { formatEther } = utils 26 27 task('deposit-eth', 'Deposits ether to L2.') 28 .addParam( 29 'l2ProviderUrl', 30 'L2 provider URL.', 31 'http://localhost:9545', 32 types.string 33 ) 34 .addOptionalParam('to', 'Recipient of the ether', '', types.string) 35 .addOptionalParam( 36 'amount', 37 'Amount of ether to send (in ETH)', 38 '', 39 types.string 40 ) 41 .addOptionalParam( 42 'withdraw', 43 'Follow up with a withdrawal', 44 true, 45 types.boolean 46 ) 47 .addOptionalParam( 48 'l1ContractsJsonPath', 49 'Path to a JSON with L1 contract addresses in it', 50 '', 51 types.string 52 ) 53 .addOptionalParam('signerIndex', 'Index of signer to use', 0, types.int) 54 .addOptionalParam('withdrawAmount', 'Amount to withdraw', '', types.string) 55 .setAction(async (args, hre) => { 56 const signers = await hre.ethers.getSigners() 57 if (signers.length === 0) { 58 throw new Error('No configured signers') 59 } 60 if (args.signerIndex < 0 || signers.length <= args.signerIndex) { 61 throw new Error('Invalid signer index') 62 } 63 const signer = signers[args.signerIndex] 64 const address = await signer.getAddress() 65 console.log(`Using signer ${address}`) 66 67 // Ensure that the signer has a balance before trying to 68 // do anything 69 const balance = await signer.getBalance() 70 if (balance.eq(0)) { 71 throw new Error('Signer has no balance') 72 } 73 console.log(`Signer balance: ${formatEther(balance.toString())}`) 74 75 const l2Provider = new providers.StaticJsonRpcProvider(args.l2ProviderUrl) 76 77 // send to self if not specified 78 const to = args.to ? args.to : address 79 const amount = args.amount 80 ? utils.parseEther(args.amount) 81 : utils.parseEther('1') 82 const withdrawAmount = args.withdrawAmount 83 ? utils.parseEther(args.withdrawAmount) 84 : amount.div(2) 85 86 const l2Signer = new hre.ethers.Wallet( 87 hre.network.config.accounts[args.signerIndex], 88 l2Provider 89 ) 90 91 const l2ChainId = await l2Signer.getChainId() 92 let contractAddrs = CONTRACT_ADDRESSES[l2ChainId] 93 if (args.l1ContractsJsonPath) { 94 const data = await fs.readFile(args.l1ContractsJsonPath) 95 const json = JSON.parse(data.toString()) 96 contractAddrs = { 97 l1: { 98 AddressManager: json.AddressManager, 99 L1CrossDomainMessenger: json.L1CrossDomainMessengerProxy, 100 L1StandardBridge: json.L1StandardBridgeProxy, 101 StateCommitmentChain: ethers.constants.AddressZero, 102 CanonicalTransactionChain: ethers.constants.AddressZero, 103 BondManager: ethers.constants.AddressZero, 104 OptimismPortal: json.OptimismPortalProxy, 105 L2OutputOracle: json.L2OutputOracleProxy, 106 OptimismPortal2: json.OptimismPortalProxy, 107 DisputeGameFactory: json.DisputeGameFactoryProxy, 108 }, 109 l2: DEFAULT_L2_CONTRACT_ADDRESSES, 110 } as OEContractsLike 111 } else if (!contractAddrs) { 112 // If the contract addresses have not been hardcoded, 113 // attempt to read them from deployment artifacts 114 let Deployment__AddressManager: Deployment 115 try { 116 Deployment__AddressManager = await hre.deployments.get('AddressManager') 117 } catch (e) { 118 Deployment__AddressManager = await hre.deployments.get( 119 'Lib_AddressManager' 120 ) 121 } 122 let Deployment__L1CrossDomainMessenger: Deployment 123 try { 124 Deployment__L1CrossDomainMessenger = await hre.deployments.get( 125 'L1CrossDomainMessengerProxy' 126 ) 127 } catch (e) { 128 Deployment__L1CrossDomainMessenger = await hre.deployments.get( 129 'Proxy__OVM_L1CrossDomainMessenger' 130 ) 131 } 132 let Deployment__L1StandardBridge: Deployment 133 try { 134 Deployment__L1StandardBridge = await hre.deployments.get( 135 'L1StandardBridgeProxy' 136 ) 137 } catch (e) { 138 Deployment__L1StandardBridge = await hre.deployments.get( 139 'Proxy__OVM_L1StandardBridge' 140 ) 141 } 142 143 const Deployment__OptimismPortal = await hre.deployments.get( 144 'OptimismPortalProxy' 145 ) 146 const Deployment__L2OutputOracle = await hre.deployments.get( 147 'L2OutputOracleProxy' 148 ) 149 contractAddrs = { 150 l1: { 151 AddressManager: Deployment__AddressManager.address, 152 L1CrossDomainMessenger: Deployment__L1CrossDomainMessenger.address, 153 L1StandardBridge: Deployment__L1StandardBridge.address, 154 StateCommitmentChain: ethers.constants.AddressZero, 155 CanonicalTransactionChain: ethers.constants.AddressZero, 156 BondManager: ethers.constants.AddressZero, 157 OptimismPortal: Deployment__OptimismPortal.address, 158 L2OutputOracle: Deployment__L2OutputOracle.address, 159 }, 160 l2: DEFAULT_L2_CONTRACT_ADDRESSES, 161 } 162 } 163 164 console.log(`OptimismPortal: ${contractAddrs.l1.OptimismPortal}`) 165 const OptimismPortal = new hre.ethers.Contract( 166 contractAddrs.l1.OptimismPortal, 167 Artifact__OptimismPortal.abi, 168 signer 169 ) 170 171 console.log( 172 `L1CrossDomainMessenger: ${contractAddrs.l1.L1CrossDomainMessenger}` 173 ) 174 const L1CrossDomainMessenger = new hre.ethers.Contract( 175 contractAddrs.l1.L1CrossDomainMessenger, 176 Artifact__L1CrossDomainMessenger.abi, 177 signer 178 ) 179 180 console.log(`L1StandardBridge: ${contractAddrs.l1.L1StandardBridge}`) 181 const L1StandardBridge = new hre.ethers.Contract( 182 contractAddrs.l1.L1StandardBridge, 183 Artifact__L1StandardBridge.abi, 184 signer 185 ) 186 187 console.log(`L2OutputOracle: ${contractAddrs.l1.L2OutputOracle}`) 188 const L2OutputOracle = new hre.ethers.Contract( 189 contractAddrs.l1.L2OutputOracle, 190 Artifact__L2OutputOracle.abi, 191 signer 192 ) 193 194 const L2ToL1MessagePasser = new hre.ethers.Contract( 195 predeploys.L2ToL1MessagePasser, 196 Artifact__L2ToL1MessagePasser.abi 197 ) 198 199 const L2CrossDomainMessenger = new hre.ethers.Contract( 200 predeploys.L2CrossDomainMessenger, 201 Artifact__L2CrossDomainMessenger.abi 202 ) 203 204 const L2StandardBridge = new hre.ethers.Contract( 205 predeploys.L2StandardBridge, 206 Artifact__L2StandardBridge.abi 207 ) 208 209 const messenger = new CrossChainMessenger({ 210 l1SignerOrProvider: signer, 211 l2SignerOrProvider: l2Signer, 212 l1ChainId: await signer.getChainId(), 213 l2ChainId, 214 bedrock: true, 215 contracts: contractAddrs, 216 }) 217 218 const opBalanceBefore = await signer!.provider!.getBalance( 219 OptimismPortal.address 220 ) 221 222 const l1BridgeBalanceBefore = await signer!.provider!.getBalance( 223 L1StandardBridge.address 224 ) 225 226 // Deposit ETH 227 console.log('Depositing ETH through StandardBridge') 228 console.log(`Sending ${formatEther(amount)} ether`) 229 const ethDeposit = await messenger.depositETH(amount, { recipient: to }) 230 console.log(`Transaction hash: ${ethDeposit.hash}`) 231 const depositMessageReceipt = await messenger.waitForMessageReceipt( 232 ethDeposit 233 ) 234 if (depositMessageReceipt.receiptStatus !== 1) { 235 throw new Error('deposit failed') 236 } 237 console.log( 238 `Deposit complete - included in block ${depositMessageReceipt.transactionReceipt.blockNumber}` 239 ) 240 241 const opBalanceAfter = await signer!.provider!.getBalance( 242 OptimismPortal.address 243 ) 244 245 const l1BridgeBalanceAfter = await signer!.provider!.getBalance( 246 L1StandardBridge.address 247 ) 248 249 console.log( 250 `L1StandardBridge balance before: ${formatEther(l1BridgeBalanceBefore)}` 251 ) 252 253 console.log( 254 `L1StandardBridge balance after: ${formatEther(l1BridgeBalanceAfter)}` 255 ) 256 257 console.log( 258 `OptimismPortal balance before: ${formatEther(opBalanceBefore)}` 259 ) 260 console.log(`OptimismPortal balance after: ${formatEther(opBalanceAfter)}`) 261 262 if (!opBalanceBefore.add(amount).eq(opBalanceAfter)) { 263 throw new Error(`OptimismPortal balance mismatch`) 264 } 265 266 const l2Balance = await l2Provider.getBalance(to) 267 console.log( 268 `L2 balance of deposit recipient: ${utils.formatEther( 269 l2Balance.toString() 270 )}` 271 ) 272 273 if (!args.withdraw) { 274 return 275 } 276 277 console.log('Withdrawing ETH') 278 const ethWithdraw = await messenger.withdrawETH(withdrawAmount) 279 console.log(`Transaction hash: ${ethWithdraw.hash}`) 280 const ethWithdrawReceipt = await ethWithdraw.wait() 281 console.log( 282 `ETH withdrawn on L2 - included in block ${ethWithdrawReceipt.blockNumber}` 283 ) 284 285 { 286 // check the logs 287 for (const log of ethWithdrawReceipt.logs) { 288 switch (log.address) { 289 case L2ToL1MessagePasser.address: { 290 const parsed = L2ToL1MessagePasser.interface.parseLog(log) 291 console.log(parsed.name) 292 console.log(parsed.args) 293 console.log() 294 break 295 } 296 case L2StandardBridge.address: { 297 const parsed = L2StandardBridge.interface.parseLog(log) 298 console.log(parsed.name) 299 console.log(parsed.args) 300 console.log() 301 break 302 } 303 case L2CrossDomainMessenger.address: { 304 const parsed = L2CrossDomainMessenger.interface.parseLog(log) 305 console.log(parsed.name) 306 console.log(parsed.args) 307 console.log() 308 break 309 } 310 default: { 311 console.log(`Unknown log from ${log.address} - ${log.topics[0]}`) 312 } 313 } 314 } 315 } 316 317 console.log('Waiting to be able to prove withdrawal') 318 319 const proveInterval = setInterval(async () => { 320 const currentStatus = await messenger.getMessageStatus(ethWithdrawReceipt) 321 console.log(`Message status: ${MessageStatus[currentStatus]}`) 322 const latest = await L2OutputOracle.latestBlockNumber() 323 console.log( 324 `Latest L2OutputOracle commitment number: ${latest.toString()}` 325 ) 326 const tip = await signer.provider!.getBlockNumber() 327 console.log(`L1 chain tip: ${tip.toString()}`) 328 }, 3000) 329 330 try { 331 await messenger.waitForMessageStatus( 332 ethWithdrawReceipt, 333 MessageStatus.READY_TO_PROVE 334 ) 335 } finally { 336 clearInterval(proveInterval) 337 } 338 339 console.log('Proving eth withdrawal...') 340 const ethProve = await messenger.proveMessage(ethWithdrawReceipt) 341 console.log(`Transaction hash: ${ethProve.hash}`) 342 const ethProveReceipt = await ethProve.wait() 343 if (ethProveReceipt.status !== 1) { 344 throw new Error('Prove withdrawal transaction reverted') 345 } 346 console.log('Successfully proved withdrawal') 347 348 console.log('Waiting to be able to finalize withdrawal') 349 350 const finalizeInterval = setInterval(async () => { 351 const currentStatus = await messenger.getMessageStatus(ethWithdrawReceipt) 352 console.log(`Message status: ${MessageStatus[currentStatus]}`) 353 }, 3000) 354 355 try { 356 await messenger.waitForMessageStatus( 357 ethWithdrawReceipt, 358 MessageStatus.READY_FOR_RELAY 359 ) 360 } finally { 361 clearInterval(finalizeInterval) 362 } 363 364 console.log('Finalizing eth withdrawal...') 365 const ethFinalize = await messenger.finalizeMessage(ethWithdrawReceipt) 366 console.log(`Transaction hash: ${ethFinalize.hash}`) 367 const ethFinalizeReceipt = await ethFinalize.wait() 368 if (ethFinalizeReceipt.status !== 1) { 369 throw new Error('Finalize withdrawal reverted') 370 } 371 372 console.log( 373 `ETH withdrawal complete - included in block ${ethFinalizeReceipt.blockNumber}` 374 ) 375 { 376 // Check that the logs are correct 377 for (const log of ethFinalizeReceipt.logs) { 378 switch (log.address) { 379 case L1StandardBridge.address: { 380 const parsed = L1StandardBridge.interface.parseLog(log) 381 console.log(parsed.name) 382 console.log(parsed.args) 383 console.log() 384 if ( 385 parsed.name !== 'ETHBridgeFinalized' && 386 parsed.name !== 'ETHWithdrawalFinalized' 387 ) { 388 throw new Error('Wrong event name from L1StandardBridge') 389 } 390 if (!parsed.args.amount.eq(withdrawAmount)) { 391 throw new Error('Wrong amount in event') 392 } 393 if (parsed.args.from !== address) { 394 throw new Error('Wrong to in event') 395 } 396 if (parsed.args.to !== address) { 397 throw new Error('Wrong from in event') 398 } 399 break 400 } 401 case L1CrossDomainMessenger.address: { 402 const parsed = L1CrossDomainMessenger.interface.parseLog(log) 403 console.log(parsed.name) 404 console.log(parsed.args) 405 console.log() 406 if (parsed.name !== 'RelayedMessage') { 407 throw new Error('Wrong event from L1CrossDomainMessenger') 408 } 409 break 410 } 411 case OptimismPortal.address: { 412 const parsed = OptimismPortal.interface.parseLog(log) 413 console.log(parsed.name) 414 console.log(parsed.args) 415 console.log() 416 // TODO: remove this if check 417 if (parsed.name === 'WithdrawalFinalized') { 418 if (parsed.args.success !== true) { 419 throw new Error('Unsuccessful withdrawal call') 420 } 421 } 422 break 423 } 424 default: { 425 console.log(`Unknown log from ${log.address} - ${log.topics[0]}`) 426 } 427 } 428 } 429 } 430 431 const opBalanceFinally = await signer!.provider!.getBalance( 432 OptimismPortal.address 433 ) 434 435 if (!opBalanceFinally.add(withdrawAmount).eq(opBalanceAfter)) { 436 throw new Error('OptimismPortal balance mismatch') 437 } 438 console.log('Withdraw success') 439 })