github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/src/adapters/standard-bridge.ts (about) 1 /* eslint-disable @typescript-eslint/no-unused-vars */ 2 import { 3 ethers, 4 Contract, 5 Overrides, 6 Signer, 7 BigNumber, 8 CallOverrides, 9 } from 'ethers' 10 import { 11 TransactionRequest, 12 TransactionResponse, 13 BlockTag, 14 } from '@ethersproject/abstract-provider' 15 import { predeploys } from '@eth-optimism/contracts' 16 import { hexStringEquals } from '@eth-optimism/core-utils' 17 import l1StandardBridgeArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json' 18 import l2StandardBridgeArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json' 19 import optimismMintableERC20 from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json' 20 21 import { CrossChainMessenger } from '../cross-chain-messenger' 22 import { 23 IBridgeAdapter, 24 NumberLike, 25 AddressLike, 26 TokenBridgeMessage, 27 MessageDirection, 28 } from '../interfaces' 29 import { toAddress } from '../utils' 30 31 /** 32 * Bridge adapter for any token bridge that uses the standard token bridge interface. 33 */ 34 export class StandardBridgeAdapter implements IBridgeAdapter { 35 public messenger: CrossChainMessenger 36 public l1Bridge: Contract 37 public l2Bridge: Contract 38 39 /** 40 * Creates a StandardBridgeAdapter instance. 41 * 42 * @param opts Options for the adapter. 43 * @param opts.messenger Provider used to make queries related to cross-chain interactions. 44 * @param opts.l1Bridge L1 bridge contract. 45 * @param opts.l2Bridge L2 bridge contract. 46 */ 47 constructor(opts: { 48 messenger: CrossChainMessenger 49 l1Bridge: AddressLike 50 l2Bridge: AddressLike 51 }) { 52 this.messenger = opts.messenger 53 this.l1Bridge = new Contract( 54 toAddress(opts.l1Bridge), 55 l1StandardBridgeArtifact.abi, 56 this.messenger.l1Provider 57 ) 58 this.l2Bridge = new Contract( 59 toAddress(opts.l2Bridge), 60 l2StandardBridgeArtifact.abi, 61 this.messenger.l2Provider 62 ) 63 } 64 65 public async getDepositsByAddress( 66 address: AddressLike, 67 opts?: { 68 fromBlock?: BlockTag 69 toBlock?: BlockTag 70 } 71 ): Promise<TokenBridgeMessage[]> { 72 const events = await this.l1Bridge.queryFilter( 73 this.l1Bridge.filters.ERC20DepositInitiated( 74 undefined, 75 undefined, 76 address 77 ), 78 opts?.fromBlock, 79 opts?.toBlock 80 ) 81 82 return events 83 .filter((event) => { 84 // Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge 85 // adapter. Bridges that are not the ETH bridge should not be able to handle or even 86 // present ETH deposits or withdrawals. 87 return ( 88 !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) && 89 !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH) 90 ) 91 }) 92 .map((event) => { 93 return { 94 direction: MessageDirection.L1_TO_L2, 95 from: event.args.from, 96 to: event.args.to, 97 l1Token: event.args.l1Token, 98 l2Token: event.args.l2Token, 99 amount: event.args.amount, 100 data: event.args.extraData, 101 logIndex: event.logIndex, 102 blockNumber: event.blockNumber, 103 transactionHash: event.transactionHash, 104 } 105 }) 106 .sort((a, b) => { 107 // Sort descending by block number 108 return b.blockNumber - a.blockNumber 109 }) 110 } 111 112 public async getWithdrawalsByAddress( 113 address: AddressLike, 114 opts?: { 115 fromBlock?: BlockTag 116 toBlock?: BlockTag 117 } 118 ): Promise<TokenBridgeMessage[]> { 119 const events = await this.l2Bridge.queryFilter( 120 this.l2Bridge.filters.WithdrawalInitiated(undefined, undefined, address), 121 opts?.fromBlock, 122 opts?.toBlock 123 ) 124 125 return events 126 .filter((event) => { 127 // Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge 128 // adapter. Bridges that are not the ETH bridge should not be able to handle or even 129 // present ETH deposits or withdrawals. 130 return ( 131 !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) && 132 !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH) 133 ) 134 }) 135 .map((event) => { 136 return { 137 direction: MessageDirection.L2_TO_L1, 138 from: event.args.from, 139 to: event.args.to, 140 l1Token: event.args.l1Token, 141 l2Token: event.args.l2Token, 142 amount: event.args.amount, 143 data: event.args.extraData, 144 logIndex: event.logIndex, 145 blockNumber: event.blockNumber, 146 transactionHash: event.transactionHash, 147 } 148 }) 149 .sort((a, b) => { 150 // Sort descending by block number 151 return b.blockNumber - a.blockNumber 152 }) 153 } 154 155 public async supportsTokenPair( 156 l1Token: AddressLike, 157 l2Token: AddressLike 158 ): Promise<boolean> { 159 const contract = new Contract( 160 toAddress(l2Token), 161 optimismMintableERC20.abi, 162 this.messenger.l2Provider 163 ) 164 // Don't support ETH deposits or withdrawals via this bridge. 165 if ( 166 hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) || 167 hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH) 168 ) { 169 return false 170 } 171 172 // Make sure the L1 token matches. 173 const remoteL1Token = await contract.l1Token() 174 175 if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) { 176 return false 177 } 178 179 // Make sure the L2 bridge matches. 180 const remoteL2Bridge = await contract.l2Bridge() 181 if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) { 182 return false 183 } 184 185 return true 186 } 187 188 public async approval( 189 l1Token: AddressLike, 190 l2Token: AddressLike, 191 signer: ethers.Signer 192 ): Promise<BigNumber> { 193 if (!(await this.supportsTokenPair(l1Token, l2Token))) { 194 throw new Error(`token pair not supported by bridge`) 195 } 196 197 const token = new Contract( 198 toAddress(l1Token), 199 optimismMintableERC20.abi, 200 this.messenger.l1Provider 201 ) 202 203 return token.allowance(await signer.getAddress(), this.l1Bridge.address) 204 } 205 206 public async approve( 207 l1Token: AddressLike, 208 l2Token: AddressLike, 209 amount: NumberLike, 210 signer: Signer, 211 opts?: { 212 overrides?: Overrides 213 } 214 ): Promise<TransactionResponse> { 215 return signer.sendTransaction( 216 await this.populateTransaction.approve(l1Token, l2Token, amount, opts) 217 ) 218 } 219 220 public async deposit( 221 l1Token: AddressLike, 222 l2Token: AddressLike, 223 amount: NumberLike, 224 signer: Signer, 225 opts?: { 226 recipient?: AddressLike 227 l2GasLimit?: NumberLike 228 overrides?: Overrides 229 } 230 ): Promise<TransactionResponse> { 231 return signer.sendTransaction( 232 await this.populateTransaction.deposit(l1Token, l2Token, amount, opts) 233 ) 234 } 235 236 public async withdraw( 237 l1Token: AddressLike, 238 l2Token: AddressLike, 239 amount: NumberLike, 240 signer: Signer, 241 opts?: { 242 recipient?: AddressLike 243 overrides?: Overrides 244 } 245 ): Promise<TransactionResponse> { 246 return signer.sendTransaction( 247 await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts) 248 ) 249 } 250 251 populateTransaction = { 252 approve: async ( 253 l1Token: AddressLike, 254 l2Token: AddressLike, 255 amount: NumberLike, 256 opts?: { 257 overrides?: Overrides 258 } 259 ): Promise<TransactionRequest> => { 260 if (!(await this.supportsTokenPair(l1Token, l2Token))) { 261 throw new Error(`token pair not supported by bridge`) 262 } 263 264 const token = new Contract( 265 toAddress(l1Token), 266 optimismMintableERC20.abi, 267 this.messenger.l1Provider 268 ) 269 270 return token.populateTransaction.approve( 271 this.l1Bridge.address, 272 amount, 273 opts?.overrides || {} 274 ) 275 }, 276 277 deposit: async ( 278 l1Token: AddressLike, 279 l2Token: AddressLike, 280 amount: NumberLike, 281 opts?: { 282 recipient?: AddressLike 283 l2GasLimit?: NumberLike 284 overrides?: Overrides 285 } 286 ): Promise<TransactionRequest> => { 287 if (!(await this.supportsTokenPair(l1Token, l2Token))) { 288 throw new Error(`token pair not supported by bridge`) 289 } 290 291 if (opts?.recipient === undefined) { 292 return this.l1Bridge.populateTransaction.depositERC20( 293 toAddress(l1Token), 294 toAddress(l2Token), 295 amount, 296 opts?.l2GasLimit || 200_000, // Default to 200k gas limit. 297 '0x', // No data. 298 opts?.overrides || {} 299 ) 300 } else { 301 return this.l1Bridge.populateTransaction.depositERC20To( 302 toAddress(l1Token), 303 toAddress(l2Token), 304 toAddress(opts.recipient), 305 amount, 306 opts?.l2GasLimit || 200_000, // Default to 200k gas limit. 307 '0x', // No data. 308 opts?.overrides || {} 309 ) 310 } 311 }, 312 313 withdraw: async ( 314 l1Token: AddressLike, 315 l2Token: AddressLike, 316 amount: NumberLike, 317 opts?: { 318 recipient?: AddressLike 319 overrides?: Overrides 320 } 321 ): Promise<TransactionRequest> => { 322 if (!(await this.supportsTokenPair(l1Token, l2Token))) { 323 throw new Error(`token pair not supported by bridge`) 324 } 325 326 if (opts?.recipient === undefined) { 327 return this.l2Bridge.populateTransaction.withdraw( 328 toAddress(l2Token), 329 amount, 330 0, // L1 gas not required. 331 '0x', // No data. 332 opts?.overrides || {} 333 ) 334 } else { 335 return this.l2Bridge.populateTransaction.withdrawTo( 336 toAddress(l2Token), 337 toAddress(opts.recipient), 338 amount, 339 0, // L1 gas not required. 340 '0x', // No data. 341 opts?.overrides || {} 342 ) 343 } 344 }, 345 } 346 347 estimateGas = { 348 approve: async ( 349 l1Token: AddressLike, 350 l2Token: AddressLike, 351 amount: NumberLike, 352 opts?: { 353 overrides?: CallOverrides 354 } 355 ): Promise<BigNumber> => { 356 return this.messenger.l1Provider.estimateGas( 357 await this.populateTransaction.approve(l1Token, l2Token, amount, opts) 358 ) 359 }, 360 361 deposit: async ( 362 l1Token: AddressLike, 363 l2Token: AddressLike, 364 amount: NumberLike, 365 opts?: { 366 recipient?: AddressLike 367 l2GasLimit?: NumberLike 368 overrides?: CallOverrides 369 } 370 ): Promise<BigNumber> => { 371 return this.messenger.l1Provider.estimateGas( 372 await this.populateTransaction.deposit(l1Token, l2Token, amount, opts) 373 ) 374 }, 375 376 withdraw: async ( 377 l1Token: AddressLike, 378 l2Token: AddressLike, 379 amount: NumberLike, 380 opts?: { 381 recipient?: AddressLike 382 overrides?: CallOverrides 383 } 384 ): Promise<BigNumber> => { 385 return this.messenger.l2Provider.estimateGas( 386 await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts) 387 ) 388 }, 389 } 390 }