From ed2fac0bc41cbe5e56f6c3e06a024018ef90b00e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:55:15 -0400 Subject: [PATCH 01/80] teleporter --- scripts/genAbi.js | 7 + src/index.ts | 1 + src/lib/assetBridger/teleporter.ts | 453 +++++++++++++++++++++++++++ src/lib/dataEntities/networks.ts | 6 + tests/integration/teleporter.test.ts | 151 +++++++++ tests/unit/teleporter.test.ts | 187 +++++++++++ 6 files changed, 805 insertions(+) create mode 100644 src/lib/assetBridger/teleporter.ts create mode 100644 tests/integration/teleporter.test.ts create mode 100644 tests/unit/teleporter.test.ts diff --git a/scripts/genAbi.js b/scripts/genAbi.js index d704ee14aa..7c4419ee1f 100644 --- a/scripts/genAbi.js +++ b/scripts/genAbi.js @@ -11,6 +11,7 @@ async function main() { const cwd = process.cwd() const nitroPath = getPackagePath('@arbitrum/nitro-contracts') + const teleporterPath = getPackagePath('arb-teleporter') // TODO: just symlinked to the repo on my machine const peripheralsPath = getPackagePath('@arbitrum/token-bridge-contracts') console.log('Compiling paths.') @@ -37,11 +38,17 @@ async function main() { cwd: peripheralsPath, }) + console.log('building teleporter') + execSync(`${npmExec} run hardhat compile`, { + cwd: teleporterPath, + }) + console.log('Done compiling') const nitroFiles = glob(cwd, [ `${peripheralsPath}/build/contracts/!(build-info)/**/+([a-zA-Z0-9_]).json`, `${nitroPath}/build/contracts/!(build-info)/**/+([a-zA-Z0-9_]).json`, + `${teleporterPath}/artifacts/!(build-info)/**/+([a-zA-Z0-9_]).json`, ]) // TODO: generate files into different subfolders (ie `/nitro/*`) to avoid overwrite of contracts with the same name diff --git a/src/index.ts b/src/index.ts index 787d30979c..31c0119d8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ /* eslint-env node */ 'use strict' +export { TeleporterUtils } from './lib/assetBridger/teleporter' export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { diff --git a/src/lib/assetBridger/teleporter.ts b/src/lib/assetBridger/teleporter.ts new file mode 100644 index 0000000000..9e128ad9e9 --- /dev/null +++ b/src/lib/assetBridger/teleporter.ts @@ -0,0 +1,453 @@ +import { + Provider, + TransactionRequest, +} from '@ethersproject/abstract-provider' +import { ArbSdkError } from "../dataEntities/errors" +import { L1Network, L2Network, TeleporterAddresses, l1Networks, l2Networks } from "../dataEntities/networks" +import { SignerOrProvider, SignerProviderUtils } from '../dataEntities/signerOrProvider' +import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' +import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' +import { L2GatewayToken } from '../abi/L2GatewayToken' +import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factory' +import { ERC20 } from '../abi/ERC20' +import { ERC20__factory } from '../abi/factories/ERC20__factory' +import { DISABLED_GATEWAY } from '../dataEntities/constants' +import { TokenApproveParams } from './erc20Bridger' +import { BigNumber, BigNumberish, ContractFactory, Signer, Wallet, ethers } from 'ethers' +import { Teleporter__factory } from '../abi/factories/Teleporter__factory' +import { AbiCoder } from 'ethers/lib/utils' +import { L1TransactionReceipt } from '../message/L1Transaction' + +/* +API: + + +fromProvider +DONE: checkL1Network (and L2 and L3) + +deposit +getDepositRequest + +DONE: getL1L2GatewayAddress (and L2L3) + +approveToken +DONE (untested): getApproveTokenRequest + +DONE: getL1TokenContract (L2 and L3 too) + +DONE: getL2ERC20Address (L3) + +DONE: l1TokenIsDisabled (L2) + + +for gas estimation: + if the l1l2 gateway is default, use hardcoded gas limit and calldata size + if it is custom, then throw if user does not provide a specific gas limit and calldata size + do the same for the l2l3 gateway +*/ + +// https://github.com/Arachnid/deterministic-deployment-proxy +const CREATE2_FACTORY = "0x4e59b44847b379578588920cA78FbF26c0B4956C"; + +export interface RetryableGasParams { + l2GasPrice?: BigNumber + l3GasPrice?: BigNumber + l2ForwarderFactoryGasLimit: BigNumber + l1l2TokenBridgeGasLimit: BigNumber + l2l3TokenBridgeGasLimit: BigNumber + l1l2TokenBridgeRetryableSize: BigNumber // todo: could call the gateway to get the calldata for a given token? + l2l3TokenBridgeRetryableSize: BigNumber // todo: could call the gateway to get the calldata for a given token? +} + +export interface DepositRequestParams { + l1Token: string + to: string + amount: BigNumberish + gasParams?: RetryableGasParams +} + +export class TeleporterUtils { + public readonly l1Network: L1Network + public readonly l2Network: L2Network + public readonly teleporterAddresses: TeleporterAddresses + + // todo: tune these + public readonly defaultRetryableGasParams: RetryableGasParams = { + l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), + l1l2TokenBridgeGasLimit: BigNumber.from(1_000_000), + l2l3TokenBridgeGasLimit: BigNumber.from(1_000_000), + l1l2TokenBridgeRetryableSize: BigNumber.from(1000), + l2l3TokenBridgeRetryableSize: BigNumber.from(1000), + } as const + + public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase + + public constructor(public readonly l3Network: L2Network) { + this.l2Network = l2Networks[l3Network.partnerChainID] + if (!this.l2Network) { + throw new ArbSdkError( + `Unknown l2 network chain id: ${l3Network.partnerChainID}` + ) + } + + if (!this.l2Network.teleporterAddresses) { + throw new ArbSdkError( + `L2 network ${this.l2Network.name} does not have a teleporter` + ) + } + + this.teleporterAddresses = this.l2Network.teleporterAddresses; + + this.l1Network = l1Networks[this.l2Network.partnerChainID] + if (!this.l1Network) { + throw new ArbSdkError( + `Unknown l1 network chain id: ${this.l2Network.partnerChainID}` + ) + } + } + + /** + * Check the signer/provider matches the l1Network, throws if not + * @param sop + */ + protected async checkL1Network(sop: SignerOrProvider): Promise { + await SignerProviderUtils.checkNetworkMatches(sop, this.l1Network.chainID) + } + + /** + * Check the signer/provider matches the l2Network, throws if not + * @param sop + */ + protected async checkL2Network(sop: SignerOrProvider): Promise { + await SignerProviderUtils.checkNetworkMatches(sop, this.l2Network.chainID) + } + + /** + * Check the signer/provider matches the l2Network, throws if not + * @param sop + */ + protected async checkL3Network(sop: SignerOrProvider): Promise { + await SignerProviderUtils.checkNetworkMatches(sop, this.l3Network.chainID) + } + + /** + * Get the corresponding L2 token address for the provided L1 token + * @param erc20L1Address + * @param l1Provider + * @returns + */ + public async getL2ERC20Address( + erc20L1Address: string, + l1Provider: Provider + ): Promise { + await this.checkL1Network(l1Provider) + return this._getChildErc20Address(erc20L1Address, l1Provider, this.l2Network) + } + + /** + * Get the corresponding L3 token address for the provided L1 token + * @param erc20L1Address + * @param l1Provider + * @param l2Provider + * @returns + */ + public async getL3ERC20Address( + erc20L1Address: string, + l1Provider: Provider, + l2Provider: Provider + ): Promise { + await this.checkL2Network(l2Provider) + const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) + return this._getChildErc20Address(l2Token, l2Provider, this.l3Network) + } + + private async _getChildErc20Address( + erc20ParentAddress: string, + parentProvider: Provider, + childNetwork: L2Network + ) { + // assume that provider has been checked + const parentGatewayRouter = L1GatewayRouter__factory.connect( + childNetwork.tokenBridge.l1GatewayRouter, + parentProvider + ) + + return await parentGatewayRouter.functions + .calculateL2TokenAddress(erc20ParentAddress) + .then(([res]) => res) + } + + /** + * Get the address of the l1 <-> l2 gateway on l1 for this token + * @param erc20L1Address + * @param l1Provider + * @returns + */ + public async getL1L2GatewayAddress( + erc20L1Address: string, + l1Provider: Provider + ): Promise { + await this.checkL1Network(l1Provider) + + return await L1GatewayRouter__factory.connect( + this.l2Network.tokenBridge.l1GatewayRouter, + l1Provider + ).getGateway(erc20L1Address) + } + + /** + * Get the address of the l2 <-> l3 gateway on l2 for this token + * @param erc20L1Address + * @param l1Provider + * @param l2Provider + * @returns + */ + public async getL2L3GatewayAddress( + erc20L1Address: string, + l1Provider: Provider, + l2Provider: Provider + ): Promise { + await this.checkL1Network(l1Provider) + await this.checkL2Network(l2Provider) + + const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider); + + return await L1GatewayRouter__factory.connect( + this.l3Network.tokenBridge.l1GatewayRouter, + l2Provider + ).getGateway(l2Token) + } + + public async isL1L2GatewayDefault( + erc20L1Address: string, + l1Provider: Provider, + ): Promise { + const gateway = await this.getL1L2GatewayAddress(erc20L1Address, l1Provider) + return gateway === this.l2Network.tokenBridge.l1ERC20Gateway + } + + public async isL2L3GatewayDefault( + erc20L1Address: string, + l1Provider: Provider, + l2Provider: Provider + ): Promise { + const gateway = await this.getL2L3GatewayAddress(erc20L1Address, l1Provider, l2Provider) + return gateway === this.l3Network.tokenBridge.l1ERC20Gateway + } + + /** + * Get the L1 token contract at the provided address + * Note: This function just returns a typed ethers object for the provided address, it doesnt + * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity + * of any of the underlying functions on that contract. + * @param l1Provider + * @param l1TokenAddr + * @returns + */ + public getL1TokenContract(l1Provider: Provider, l1TokenAddr: string): ERC20 { + return ERC20__factory.connect(l1TokenAddr, l1Provider) + } + + /** + * Get the L2 token contract at the provided address + * Note: This function just returns a typed ethers object for the provided address, it doesnt + * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity + * of any of the underlying functions on that contract. + * @param l2Provider + * @param l2TokenAddr + * @returns + */ + public getL2TokenContract( + l2Provider: Provider, + l2TokenAddr: string + ): L2GatewayToken { + return L2GatewayToken__factory.connect(l2TokenAddr, l2Provider) + } + + /** + * Get the L3 token contract at the provided address + * Note: This function just returns a typed ethers object for the provided address, it doesnt + * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity + * of any of the underlying functions on that contract. + * @param l2Provider + * @param l2TokenAddr + * @returns + */ + public getL3TokenContract( + l3Provider: Provider, + l3TokenAddr: string + ): L2GatewayToken { + return this.getL2TokenContract(l3Provider, l3TokenAddr) + } + + /** + * Whether the L1 token has been disabled on the router + * @param l1TokenAddress + * @param l1Provider + * @returns + */ + public async l1TokenIsDisabled( + l1TokenAddress: string, + l1Provider: Provider + ): Promise { + await this.checkL1Network(l1Provider) + + return this._tokenIsDisabled( + l1TokenAddress, + this.l2Network.tokenBridge.l1GatewayRouter, + l1Provider + ); + } + + /** + * Whether the L2 token has been disabled on the router + * @param l2TokenAddress + * @param l2Provider + * @returns + */ + public async l2TokenIsDisabled( + l2TokenAddress: string, + l2Provider: Provider + ): Promise { + await this.checkL2Network(l2Provider) + + return this._tokenIsDisabled( + l2TokenAddress, + this.l3Network.tokenBridge.l1GatewayRouter, + l2Provider + ); + } + + private async _tokenIsDisabled( + tokenAddress: string, + gatewayRouterAddress: string, + provider: Provider + ): Promise { + // assumes provider has been checked + const gatewayRouter = L2GatewayRouter__factory.connect( + gatewayRouterAddress, + provider + ) + + return (await gatewayRouter.l1TokenToGateway(tokenAddress)) === DISABLED_GATEWAY; + } + + /** + * Get a tx request to approve tokens for teleportation. + * The tokens will be approved for the Teleporter on L1. + * @param params + * @returns + */ + public async getApproveTokenRequest( + params: TokenApproveParams + ): Promise>> { + const iErc20Interface = ERC20__factory.createInterface() + const data = iErc20Interface.encodeFunctionData('approve', [ + this.teleporterAddresses.l1Teleporter, + params.amount || ethers.constants.MaxUint256, + ]) + + return { + to: params.erc20L1Address, + data, + value: BigNumber.from(0), + } + } + + public async approveToken( + params: TokenApproveParams, + l1Signer: Signer + ) { + await this.checkL1Network(l1Signer) + + const approveRequest = await this.getApproveTokenRequest(params) + + return l1Signer.sendTransaction(approveRequest) + } + + public async getDepositRequest( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider, + gasPricePercentIncrease?: BigNumber + ): Promise>> { + await this.checkL1Network(l1Signer) + await this.checkL2Network(l2Provider) + await this.checkL3Network(l3Provider) + + gasPricePercentIncrease = gasPricePercentIncrease || this.defaultGasPricePercentIncrease + + if (!params.gasParams) { + // make sure both gateways are default + if (!(await this.isL1L2GatewayDefault(params.l1Token, l1Signer.provider!))) { + throw new ArbSdkError( + `Cannot estimate gas for custom l1l2 gateway, please provide gas params` + ) + } + + if (!(await this.isL2L3GatewayDefault(params.l1Token, l1Signer.provider!, l2Provider))) { + throw new ArbSdkError( + `Cannot estimate gas for custom l2l3 gateway, please provide gas params` + ) + } + + params.gasParams = this.defaultRetryableGasParams; + } + + // populate gasParams gas prices + const gasParams = { + ...params.gasParams, + l2GasPrice: params.gasParams.l2GasPrice || this.percentIncrease(await l2Provider.getGasPrice(), gasPricePercentIncrease), + l3GasPrice: params.gasParams.l3GasPrice || this.percentIncrease(await l3Provider.getGasPrice(), gasPricePercentIncrease) + } + + const teleporter = Teleporter__factory.connect(this.teleporterAddresses.l1Teleporter, l1Signer) + + const calldata = teleporter.interface.encodeFunctionData("teleport", [ + params.l1Token, + this.l2Network.tokenBridge.l1GatewayRouter, + this.l3Network.tokenBridge.l1GatewayRouter, + params.to, + params.amount, + gasParams + ]) + + const l1GasPrice = await l1Signer.provider!.getGasPrice() + + const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( + this.l2Network.ethBridge.inbox, + this.percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), + gasParams + ) + + return { + to: this.teleporterAddresses.l1Teleporter, + data: calldata, + value: calculatedGasCosts.total, + from: await l1Signer.getAddress() + } + } + + public async deposit( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider, + gasPricePercentIncrease?: BigNumber + ) { + const txRequest = await this.getDepositRequest( + params, + l1Signer, + l2Provider, + l3Provider, + gasPricePercentIncrease + ) + + return L1TransactionReceipt.monkeyPatchContractCallWait(await l1Signer.sendTransaction(txRequest)) + } + + private percentIncrease(base: BigNumber, percent: BigNumber) { + return base.mul(percent).div(100) + } +} \ No newline at end of file diff --git a/src/lib/dataEntities/networks.ts b/src/lib/dataEntities/networks.ts index 65a27ac2e2..6d8f90ff88 100644 --- a/src/lib/dataEntities/networks.ts +++ b/src/lib/dataEntities/networks.ts @@ -27,10 +27,16 @@ export interface L1Network extends Network { isArbitrum: false } +export interface TeleporterAddresses { + l1Teleporter: string + l2ForwarderFactory: string +} + export interface L2Network extends Network { partnerChainIDs?: number[] tokenBridge: TokenBridge ethBridge: EthBridge + teleporterAddresses?: TeleporterAddresses partnerChainID: number isArbitrum: true confirmPeriodBlocks: number diff --git a/tests/integration/teleporter.test.ts b/tests/integration/teleporter.test.ts new file mode 100644 index 0000000000..4d3cf03ea5 --- /dev/null +++ b/tests/integration/teleporter.test.ts @@ -0,0 +1,151 @@ +import { testSetup } from '../../scripts/testSetup' +import { + Erc20Bridger, + L1ToL2Message, + L1ToL2MessageStatus, + L1TransactionReceipt, + TeleporterUtils, +} from '../../src' +import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' +import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' +import { MockToken } from '../../src/lib/abi/MockToken' +import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' +import { fundL1, fundL2, skipIfMainnet } from './testHelpers' +import { ethers } from 'ethers' +import { L1ContractCallTransactionReceipt } from '../../src/lib/message/L1Transaction' + +type Unwrap = T extends Promise ? U : T + +describe('Teleporter', () => { + let setup: Unwrap> + let teleporterUtils: TeleporterUtils + let l1Token: MockToken + + before(async function () { + await skipIfMainnet(this) + + setup = await testSetup() + + // fund signers on L1 and L2 + await fundL1(setup.l1Signer, ethers.utils.parseEther('1')) + await fundL2(setup.l2Signer, ethers.utils.parseEther('1')) + + // deploy teleporter contracts (todo: this should maybe be done in gen:network in the future) + + const l2ContractsDeployer = await new L2ForwarderContractsDeployer__factory( + setup.l2Signer + ).deploy() + await l2ContractsDeployer.deployed() + + const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() + const l2ForwarderFactory = await l2ContractsDeployer.factory() + + const l1Teleporter = await new Teleporter__factory(setup.l1Signer).deploy( + l2ForwarderFactory, + l2ForwarderImplAddr + ) + await l1Teleporter.deployed() + + // set the teleporter on the l2Network + setup.l2Network.teleporterAddresses = { + l1Teleporter: l1Teleporter.address, + l2ForwarderFactory, + } + + teleporterUtils = new TeleporterUtils(setup.l3Network) + + // deploy the mock token + l1Token = await new MockToken__factory(setup.l1Signer).deploy( + 'MOCK', + 'MOCK', + ethers.utils.parseEther('100'), + await setup.l1Signer.getAddress() + ) + await l1Token.deployed() + + // approve the teleporter + await ( + await l1Token + .connect(setup.l1Signer) + .approve( + setup.l2Network.teleporterAddresses!.l1Teleporter, + ethers.utils.parseEther('100') + ) + ).wait() + }) + + it('L1 Teleporter should move tokens to L3 with correct parameters', async () => { + const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + + // todo: rename to teleport? + const depositTx = await teleporterUtils.deposit( + { + l1Token: l1Token.address, + to: l3Recipient, + amount: ethers.utils.parseEther('1'), + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + const depositReceipt = await depositTx.wait() + + // wait for l1 l2 messages to redeem + console.log('waiting for l1 l2 messages to redeem') + const l1l2Messages = await depositReceipt.getL1ToL2Messages( + setup.l2Signer.provider! + ) + + if ( + (await l1l2Messages[0].waitForStatus()).status !== + L1ToL2MessageStatus.REDEEMED + ) { + throw new Error('first L1 to L2 message not redeemed') + } + if ( + (await l1l2Messages[1].waitForStatus()).status !== + L1ToL2MessageStatus.REDEEMED + ) { + throw new Error('second L1 to L2 message not redeemed') + } + + // get l2 l3 message + const secondRedeemReceipt = new L1ContractCallTransactionReceipt( + (await l1l2Messages[1].getAutoRedeemAttempt())! + ) + const l2l3Message = ( + await secondRedeemReceipt.getL1ToL2Messages(setup.l3Signer.provider!) + )[0] + + // wait for l2 l3 message to be redeemed + console.log('waiting for l2 l3 message to be redeemed') + if ( + (await l2l3Message.waitForStatus()).status !== + L1ToL2MessageStatus.REDEEMED + ) { + throw new Error('L2 to L3 message not redeemed') + } + + // make sure the tokens have landed in the right place + const l3TokenAddr = await teleporterUtils.getL3ERC20Address( + l1Token.address, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) + const l3Token = teleporterUtils.getL3TokenContract( + setup.l3Signer.provider!, + l3TokenAddr + ) + + const l3Balance = await l3Token.balanceOf(l3Recipient) + + if (!l3Balance.eq(ethers.utils.parseEther('1').sub(1))) { + throw new Error('L3 balance is incorrect') + } + }) + + // should throw if gas overrides not passed when using non default gateway + // test relayer stuff + // don't need to test rescue here i think +}) diff --git a/tests/unit/teleporter.test.ts b/tests/unit/teleporter.test.ts new file mode 100644 index 0000000000..f91e16df47 --- /dev/null +++ b/tests/unit/teleporter.test.ts @@ -0,0 +1,187 @@ +// import * as dotenv from "dotenv" +// dotenv.config() +// import { expect } from "chai" +// import {TeleporterUtils} from "../../src/lib/assetBridger/teleporter" +// import { getL2Network, l1Networks, l2Networks } from "../../src/lib/dataEntities/networks" +// import { ethers, providers } from "ethers" +// import { anything, deepEqual, instance, mock, when } from "ts-mockito" +// import { L1GatewayRouter__factory } from "../../src/lib/abi/factories/L1GatewayRouter__factory" +// import { AbiCoder } from "ethers/lib/utils" +// import { DISABLED_GATEWAY } from "../../src/lib/dataEntities/constants" + +// type Provider = providers.JsonRpcProvider + +// const l3ChainId = 23011913 +// const l2ChainId = 421614 +// const l1ChainId = 11155111 +// const l1WETH = l2Networks[l2ChainId].tokenBridge.l1Weth +// const l2WETH = l2Networks[l2ChainId].tokenBridge.l2Weth +// const l3WETH = l2Networks[l3ChainId].tokenBridge.l2Weth + +// function getEnv(k: string) { +// const v = process.env[k] +// if (!v) { +// throw new Error(`Missing env var ${k}`) +// } +// return v +// } + +// function createProviderMock(chainId: number) { +// const l2ProviderMock = mock(providers.JsonRpcProvider) +// const latestBlock = 1 +// when(l2ProviderMock.getBlockNumber()).thenResolve(latestBlock) +// when(l2ProviderMock.getNetwork()).thenResolve({ +// chainId: chainId +// } as any) +// when(l2ProviderMock._isProvider).thenReturn(true) +// when(l2ProviderMock.getLogs(anything())).thenResolve([]) + +// return l2ProviderMock +// } + +// function mockFunctionCall( +// mockProvider: Provider, +// args: { data: string; to: string; response: string } +// ) { +// when( +// mockProvider.call( +// deepEqual({ +// data: args.data, +// to: args.to, +// }), +// anything() +// ) +// ).thenResolve(args.response) + +// return mockProvider +// } + +// function mockGetGateway( +// mockProvider: Provider, +// args: { router: string; token: string; gateway: string } +// ) { +// return mockFunctionCall(mockProvider, { +// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("getGateway", [args.token]), +// to: args.router, +// response: new AbiCoder().encode(["address"], [args.gateway]) +// }) +// } + +// function mockCalculateL2TokenAddress( +// mockProvider: Provider, +// args: { router: string; l1Token: string; l2Token: string } +// ) { +// return mockFunctionCall(mockProvider, { +// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("calculateL2TokenAddress", [args.l1Token]), +// to: args.router, +// response: new AbiCoder().encode(["address"], [args.l2Token]) +// }) +// } + +// function mockL1TokenToGateway( +// mockProvider: Provider, +// args: { router: string; l1Token: string; gateway: string } +// ) { +// return mockFunctionCall(mockProvider, { +// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("l1TokenToGateway", [args.l1Token]), +// to: args.router, +// response: new AbiCoder().encode(["address"], [args.gateway]) +// }) +// } + +// describe('Teleporter', () => { +// let teleporter: TeleporterUtils + +// const l1Network = l1Networks[l1ChainId] +// const l2Network = l2Networks[l2ChainId] +// const l3Network = l2Networks[l3ChainId] + +// let l1ProviderMock: Provider +// let l2ProviderMock: Provider +// let l3ProviderMock: Provider + +// let l1Provider: Provider +// let l2Provider: Provider +// let l3Provider: Provider + +// beforeEach(() => { +// teleporter = new TeleporterUtils(l3Network) + +// l1ProviderMock = createProviderMock(l1Network.chainID) +// l2ProviderMock = createProviderMock(l2Network.chainID) +// l3ProviderMock = createProviderMock(l3Network.chainID) + +// // mock get gateway calls +// l1ProviderMock = mockGetGateway(l1ProviderMock, { +// router: l2Network.tokenBridge.l1GatewayRouter, +// token: l1WETH, +// gateway: l2Network.tokenBridge.l1WethGateway +// }) +// l2ProviderMock = mockGetGateway(l2ProviderMock, { +// router: l3Network.tokenBridge.l1GatewayRouter, +// token: l2WETH, +// gateway: l3Network.tokenBridge.l1WethGateway +// }) + +// // mock calculateL2TokenAddress calls +// l1ProviderMock = mockCalculateL2TokenAddress(l1ProviderMock, { +// router: l2Network.tokenBridge.l1GatewayRouter, +// l1Token: l1WETH, +// l2Token: l2WETH +// }) +// l2ProviderMock = mockCalculateL2TokenAddress(l2ProviderMock, { +// router: l3Network.tokenBridge.l1GatewayRouter, +// l1Token: l2WETH, +// l2Token: l3WETH +// }) + +// l1Provider = instance(l1ProviderMock) +// l2Provider = instance(l2ProviderMock) +// l3Provider = instance(l3ProviderMock) +// }) + +// it('should set L2 and L1 properly', () => { +// expect(teleporter.l2Network.chainID).to.equal(l2ChainId) +// expect(teleporter.l1Network.chainID).to.equal(l1ChainId) +// }) + +// it('should return correct L2 token address', async () => { +// const l2TokenAddress = await teleporter.getL2ERC20Address(l1WETH, l1Provider) +// expect(l2TokenAddress).to.equal(l2WETH) +// }) + +// it('should return correct L3 token address', async () => { +// const l3TokenAddress = await teleporter.getL3ERC20Address(l1WETH, l1Provider, l2Provider) +// expect(l3TokenAddress).to.equal(l3WETH) +// }) + +// it('should return correct L1 -> L2 gateway', async () => { +// const l1l2Gateway = await teleporter.getL1L2GatewayAddress(l1WETH, l1Provider) +// expect(l1l2Gateway).to.equal(l2Network.tokenBridge.l1WethGateway) +// }) + +// it('should return correct L2 -> L3 gateway', async () => { +// const l2l3Gateway = await teleporter.getL2L3GatewayAddress(l1WETH, l1Provider, l2Provider) +// expect(l2l3Gateway).to.equal(l3Network.tokenBridge.l1WethGateway) +// }) + +// it('should return wether a token is disabled', async () => { +// // mock l1TokenToGateway calls +// l1ProviderMock = mockL1TokenToGateway(l1ProviderMock, { +// router: l2Network.tokenBridge.l1GatewayRouter, +// l1Token: l1WETH, +// gateway: DISABLED_GATEWAY +// }) +// l1Provider = instance(l1ProviderMock) + +// l2ProviderMock = mockL1TokenToGateway(l2ProviderMock, { +// router: l3Network.tokenBridge.l1GatewayRouter, +// l1Token: l2WETH, +// gateway: l3Network.tokenBridge.l1WethGateway +// }) +// l2Provider = instance(l2ProviderMock) + +// expect(await teleporter.l1TokenIsDisabled(l1WETH, l1Provider)).to.equal(true) +// expect(await teleporter.l2TokenIsDisabled(l2WETH, l2Provider)).to.equal(false) +// }) +// }) \ No newline at end of file From b5896f87492fe21b4122d7c3c4cb9f28066dffbc Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:33:42 -0400 Subject: [PATCH 02/80] rename --- src/index.ts | 2 +- .../assetBridger/{teleporter.ts => l1l3Bridger.ts} | 5 +---- .../{teleporter.test.ts => l1l3Bridger.test.ts} | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) rename src/lib/assetBridger/{teleporter.ts => l1l3Bridger.ts} (98%) rename tests/integration/{teleporter.test.ts => l1l3Bridger.test.ts} (93%) diff --git a/src/index.ts b/src/index.ts index 31c0119d8f..59ae314a04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ /* eslint-env node */ 'use strict' -export { TeleporterUtils } from './lib/assetBridger/teleporter' +export { L1L3Bridger } from './lib/assetBridger/l1l3Bridger' export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { diff --git a/src/lib/assetBridger/teleporter.ts b/src/lib/assetBridger/l1l3Bridger.ts similarity index 98% rename from src/lib/assetBridger/teleporter.ts rename to src/lib/assetBridger/l1l3Bridger.ts index 9e128ad9e9..b7fa5a2385 100644 --- a/src/lib/assetBridger/teleporter.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -46,9 +46,6 @@ for gas estimation: do the same for the l2l3 gateway */ -// https://github.com/Arachnid/deterministic-deployment-proxy -const CREATE2_FACTORY = "0x4e59b44847b379578588920cA78FbF26c0B4956C"; - export interface RetryableGasParams { l2GasPrice?: BigNumber l3GasPrice?: BigNumber @@ -66,7 +63,7 @@ export interface DepositRequestParams { gasParams?: RetryableGasParams } -export class TeleporterUtils { +export class L1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network public readonly teleporterAddresses: TeleporterAddresses diff --git a/tests/integration/teleporter.test.ts b/tests/integration/l1l3Bridger.test.ts similarity index 93% rename from tests/integration/teleporter.test.ts rename to tests/integration/l1l3Bridger.test.ts index 4d3cf03ea5..8b4a315d91 100644 --- a/tests/integration/teleporter.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -4,7 +4,7 @@ import { L1ToL2Message, L1ToL2MessageStatus, L1TransactionReceipt, - TeleporterUtils, + L1L3Bridger, } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' @@ -18,7 +18,7 @@ type Unwrap = T extends Promise ? U : T describe('Teleporter', () => { let setup: Unwrap> - let teleporterUtils: TeleporterUtils + let l1l3Bridger: L1L3Bridger let l1Token: MockToken before(async function () { @@ -52,7 +52,7 @@ describe('Teleporter', () => { l2ForwarderFactory, } - teleporterUtils = new TeleporterUtils(setup.l3Network) + l1l3Bridger = new L1L3Bridger(setup.l3Network) // deploy the mock token l1Token = await new MockToken__factory(setup.l1Signer).deploy( @@ -78,7 +78,7 @@ describe('Teleporter', () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) // todo: rename to teleport? - const depositTx = await teleporterUtils.deposit( + const depositTx = await l1l3Bridger.deposit( { l1Token: l1Token.address, to: l3Recipient, @@ -128,12 +128,12 @@ describe('Teleporter', () => { } // make sure the tokens have landed in the right place - const l3TokenAddr = await teleporterUtils.getL3ERC20Address( + const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( l1Token.address, setup.l1Signer.provider!, setup.l2Signer.provider! ) - const l3Token = teleporterUtils.getL3TokenContract( + const l3Token = l1l3Bridger.getL3TokenContract( setup.l3Signer.provider!, l3TokenAddr ) From 4d74fb596e70a85bca7f4bf12e4dacc8e88172bf Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:35:22 -0400 Subject: [PATCH 03/80] fmt --- scripts/testSetup.ts | 4 +- src/lib/assetBridger/l1l3Bridger.ts | 117 +++++++++++++++++++--------- tests/unit/teleporter.test.ts | 8 +- 3 files changed, 88 insertions(+), 41 deletions(-) diff --git a/scripts/testSetup.ts b/scripts/testSetup.ts index 1dcd065554..55be486d71 100644 --- a/scripts/testSetup.ts +++ b/scripts/testSetup.ts @@ -271,7 +271,7 @@ export const setupNetworks = async ( addCustomChain({ customParentChain: { ...l2Network, - partnerChainIDs: [l3Network.chainID] + partnerChainIDs: [l3Network.chainID], }, customChain: l3Network, }) @@ -376,7 +376,7 @@ export const testSetup = async (): Promise<{ addCustomChain({ customParentChain: { ...l2Network, - partnerChainIDs: [l3Network.chainID] + partnerChainIDs: [l3Network.chainID], }, customChain: l3Network, }) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index b7fa5a2385..cedf9f16bf 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,10 +1,16 @@ +import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' +import { ArbSdkError } from '../dataEntities/errors' import { - Provider, - TransactionRequest, -} from '@ethersproject/abstract-provider' -import { ArbSdkError } from "../dataEntities/errors" -import { L1Network, L2Network, TeleporterAddresses, l1Networks, l2Networks } from "../dataEntities/networks" -import { SignerOrProvider, SignerProviderUtils } from '../dataEntities/signerOrProvider' + L1Network, + L2Network, + TeleporterAddresses, + l1Networks, + l2Networks, +} from '../dataEntities/networks' +import { + SignerOrProvider, + SignerProviderUtils, +} from '../dataEntities/signerOrProvider' import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' import { L2GatewayToken } from '../abi/L2GatewayToken' @@ -13,7 +19,14 @@ import { ERC20 } from '../abi/ERC20' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { DISABLED_GATEWAY } from '../dataEntities/constants' import { TokenApproveParams } from './erc20Bridger' -import { BigNumber, BigNumberish, ContractFactory, Signer, Wallet, ethers } from 'ethers' +import { + BigNumber, + BigNumberish, + ContractFactory, + Signer, + Wallet, + ethers, +} from 'ethers' import { Teleporter__factory } from '../abi/factories/Teleporter__factory' import { AbiCoder } from 'ethers/lib/utils' import { L1TransactionReceipt } from '../message/L1Transaction' @@ -77,7 +90,8 @@ export class L1L3Bridger { l2l3TokenBridgeRetryableSize: BigNumber.from(1000), } as const - public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase + public readonly defaultGasPricePercentIncrease: BigNumber = + BigNumber.from(130) // 30% increase public constructor(public readonly l3Network: L2Network) { this.l2Network = l2Networks[l3Network.partnerChainID] @@ -93,7 +107,7 @@ export class L1L3Bridger { ) } - this.teleporterAddresses = this.l2Network.teleporterAddresses; + this.teleporterAddresses = this.l2Network.teleporterAddresses this.l1Network = l1Networks[this.l2Network.partnerChainID] if (!this.l1Network) { @@ -138,7 +152,11 @@ export class L1L3Bridger { l1Provider: Provider ): Promise { await this.checkL1Network(l1Provider) - return this._getChildErc20Address(erc20L1Address, l1Provider, this.l2Network) + return this._getChildErc20Address( + erc20L1Address, + l1Provider, + this.l2Network + ) } /** @@ -207,7 +225,7 @@ export class L1L3Bridger { await this.checkL1Network(l1Provider) await this.checkL2Network(l2Provider) - const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider); + const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) return await L1GatewayRouter__factory.connect( this.l3Network.tokenBridge.l1GatewayRouter, @@ -217,7 +235,7 @@ export class L1L3Bridger { public async isL1L2GatewayDefault( erc20L1Address: string, - l1Provider: Provider, + l1Provider: Provider ): Promise { const gateway = await this.getL1L2GatewayAddress(erc20L1Address, l1Provider) return gateway === this.l2Network.tokenBridge.l1ERC20Gateway @@ -228,7 +246,11 @@ export class L1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - const gateway = await this.getL2L3GatewayAddress(erc20L1Address, l1Provider, l2Provider) + const gateway = await this.getL2L3GatewayAddress( + erc20L1Address, + l1Provider, + l2Provider + ) return gateway === this.l3Network.tokenBridge.l1ERC20Gateway } @@ -293,7 +315,7 @@ export class L1L3Bridger { l1TokenAddress, this.l2Network.tokenBridge.l1GatewayRouter, l1Provider - ); + ) } /** @@ -312,7 +334,7 @@ export class L1L3Bridger { l2TokenAddress, this.l3Network.tokenBridge.l1GatewayRouter, l2Provider - ); + ) } private async _tokenIsDisabled( @@ -326,7 +348,9 @@ export class L1L3Bridger { provider ) - return (await gatewayRouter.l1TokenToGateway(tokenAddress)) === DISABLED_GATEWAY; + return ( + (await gatewayRouter.l1TokenToGateway(tokenAddress)) === DISABLED_GATEWAY + ) } /** @@ -351,10 +375,7 @@ export class L1L3Bridger { } } - public async approveToken( - params: TokenApproveParams, - l1Signer: Signer - ) { + public async approveToken(params: TokenApproveParams, l1Signer: Signer) { await this.checkL1Network(l1Signer) const approveRequest = await this.getApproveTokenRequest(params) @@ -368,53 +389,77 @@ export class L1L3Bridger { l2Provider: Provider, l3Provider: Provider, gasPricePercentIncrease?: BigNumber - ): Promise>> { + ): Promise< + Required> + > { await this.checkL1Network(l1Signer) await this.checkL2Network(l2Provider) await this.checkL3Network(l3Provider) - gasPricePercentIncrease = gasPricePercentIncrease || this.defaultGasPricePercentIncrease + gasPricePercentIncrease = + gasPricePercentIncrease || this.defaultGasPricePercentIncrease if (!params.gasParams) { // make sure both gateways are default - if (!(await this.isL1L2GatewayDefault(params.l1Token, l1Signer.provider!))) { + if ( + !(await this.isL1L2GatewayDefault(params.l1Token, l1Signer.provider!)) + ) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` ) } - if (!(await this.isL2L3GatewayDefault(params.l1Token, l1Signer.provider!, l2Provider))) { + if ( + !(await this.isL2L3GatewayDefault( + params.l1Token, + l1Signer.provider!, + l2Provider + )) + ) { throw new ArbSdkError( `Cannot estimate gas for custom l2l3 gateway, please provide gas params` ) } - params.gasParams = this.defaultRetryableGasParams; + params.gasParams = this.defaultRetryableGasParams } // populate gasParams gas prices const gasParams = { ...params.gasParams, - l2GasPrice: params.gasParams.l2GasPrice || this.percentIncrease(await l2Provider.getGasPrice(), gasPricePercentIncrease), - l3GasPrice: params.gasParams.l3GasPrice || this.percentIncrease(await l3Provider.getGasPrice(), gasPricePercentIncrease) + l2GasPrice: + params.gasParams.l2GasPrice || + this.percentIncrease( + await l2Provider.getGasPrice(), + gasPricePercentIncrease + ), + l3GasPrice: + params.gasParams.l3GasPrice || + this.percentIncrease( + await l3Provider.getGasPrice(), + gasPricePercentIncrease + ), } - const teleporter = Teleporter__factory.connect(this.teleporterAddresses.l1Teleporter, l1Signer) + const teleporter = Teleporter__factory.connect( + this.teleporterAddresses.l1Teleporter, + l1Signer + ) - const calldata = teleporter.interface.encodeFunctionData("teleport", [ + const calldata = teleporter.interface.encodeFunctionData('teleport', [ params.l1Token, this.l2Network.tokenBridge.l1GatewayRouter, this.l3Network.tokenBridge.l1GatewayRouter, params.to, params.amount, - gasParams + gasParams, ]) const l1GasPrice = await l1Signer.provider!.getGasPrice() const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( - this.l2Network.ethBridge.inbox, - this.percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), + this.l2Network.ethBridge.inbox, + this.percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), gasParams ) @@ -422,7 +467,7 @@ export class L1L3Bridger { to: this.teleporterAddresses.l1Teleporter, data: calldata, value: calculatedGasCosts.total, - from: await l1Signer.getAddress() + from: await l1Signer.getAddress(), } } @@ -441,10 +486,12 @@ export class L1L3Bridger { gasPricePercentIncrease ) - return L1TransactionReceipt.monkeyPatchContractCallWait(await l1Signer.sendTransaction(txRequest)) + return L1TransactionReceipt.monkeyPatchContractCallWait( + await l1Signer.sendTransaction(txRequest) + ) } private percentIncrease(base: BigNumber, percent: BigNumber) { return base.mul(percent).div(100) } -} \ No newline at end of file +} diff --git a/tests/unit/teleporter.test.ts b/tests/unit/teleporter.test.ts index f91e16df47..a79e29b9de 100644 --- a/tests/unit/teleporter.test.ts +++ b/tests/unit/teleporter.test.ts @@ -144,17 +144,17 @@ // expect(teleporter.l2Network.chainID).to.equal(l2ChainId) // expect(teleporter.l1Network.chainID).to.equal(l1ChainId) // }) - + // it('should return correct L2 token address', async () => { // const l2TokenAddress = await teleporter.getL2ERC20Address(l1WETH, l1Provider) // expect(l2TokenAddress).to.equal(l2WETH) // }) - + // it('should return correct L3 token address', async () => { // const l3TokenAddress = await teleporter.getL3ERC20Address(l1WETH, l1Provider, l2Provider) // expect(l3TokenAddress).to.equal(l3WETH) // }) - + // it('should return correct L1 -> L2 gateway', async () => { // const l1l2Gateway = await teleporter.getL1L2GatewayAddress(l1WETH, l1Provider) // expect(l1l2Gateway).to.equal(l2Network.tokenBridge.l1WethGateway) @@ -184,4 +184,4 @@ // expect(await teleporter.l1TokenIsDisabled(l1WETH, l1Provider)).to.equal(true) // expect(await teleporter.l2TokenIsDisabled(l2WETH, l2Provider)).to.equal(false) // }) -// }) \ No newline at end of file +// }) From 0ba6bcbbd960d29032882b3e9a11f0177c801054 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:10:54 -0400 Subject: [PATCH 04/80] waitForDeposit --- src/lib/assetBridger/l1l3Bridger.ts | 65 ++++++++++++++++++++++++++- tests/integration/l1l3Bridger.test.ts | 42 ++--------------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index cedf9f16bf..dd27f13074 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -29,7 +29,11 @@ import { } from 'ethers' import { Teleporter__factory } from '../abi/factories/Teleporter__factory' import { AbiCoder } from 'ethers/lib/utils' -import { L1TransactionReceipt } from '../message/L1Transaction' +import { + L1ContractCallTransactionReceipt, + L1TransactionReceipt, +} from '../message/L1Transaction' +import { L1ToL2MessageStatus } from '../message/L1ToL2Message' /* API: @@ -76,6 +80,12 @@ export interface DepositRequestParams { gasParams?: RetryableGasParams } +export interface WaitForDepositResult { + success: boolean + failedLeg?: 0 | 1 | 2 // 0 is token bridge to l2, 1 is call to l2 forwarder factory, 2 is token bridge to l3 + failedLegStatus?: Exclude +} + export class L1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network @@ -491,6 +501,59 @@ export class L1L3Bridger { ) } + public async waitForDeposit( + depositTxReceipt: L1TransactionReceipt, + l2Provider: Provider, + l3Provider: Provider + ): Promise { + if (depositTxReceipt.to !== this.teleporterAddresses.l1Teleporter) { + throw new ArbSdkError( + `Transaction receipt is not for the teleporter: ${depositTxReceipt.to}` + ) + } + + await this.checkL2Network(l2Provider) + await this.checkL3Network(l3Provider) + + const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) + const firstLegStatus = (await l1l2Messages[0].waitForStatus()).status + const secondLegStatus = (await l1l2Messages[1].waitForStatus()).status + + if (firstLegStatus != L1ToL2MessageStatus.REDEEMED) { + return { + success: false, + failedLeg: 0, + failedLegStatus: firstLegStatus, + } + } else if (secondLegStatus != L1ToL2MessageStatus.REDEEMED) { + return { + success: false, + failedLeg: 1, + failedLegStatus: secondLegStatus, + } + } + + const thirdLegMessage = ( + await new L1ContractCallTransactionReceipt( + (await l1l2Messages[1].getAutoRedeemAttempt())! + ).getL1ToL2Messages(l3Provider) + )[0] + + const thirdLegStatus = (await thirdLegMessage.waitForStatus()).status + + if (thirdLegStatus != L1ToL2MessageStatus.REDEEMED) { + return { + success: false, + failedLeg: 2, + failedLegStatus: thirdLegStatus, + } + } + + return { + success: true, + } + } + private percentIncrease(base: BigNumber, percent: BigNumber) { return base.mul(percent).div(100) } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 8b4a315d91..5e39e429a4 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,10 +1,6 @@ import { testSetup } from '../../scripts/testSetup' import { - Erc20Bridger, - L1ToL2Message, - L1ToL2MessageStatus, - L1TransactionReceipt, - L1L3Bridger, + L1L3Bridger } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' @@ -12,7 +8,6 @@ import { MockToken } from '../../src/lib/abi/MockToken' import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { ethers } from 'ethers' -import { L1ContractCallTransactionReceipt } from '../../src/lib/message/L1Transaction' type Unwrap = T extends Promise ? U : T @@ -92,39 +87,10 @@ describe('Teleporter', () => { const depositReceipt = await depositTx.wait() // wait for l1 l2 messages to redeem - console.log('waiting for l1 l2 messages to redeem') - const l1l2Messages = await depositReceipt.getL1ToL2Messages( - setup.l2Signer.provider! - ) - - if ( - (await l1l2Messages[0].waitForStatus()).status !== - L1ToL2MessageStatus.REDEEMED - ) { - throw new Error('first L1 to L2 message not redeemed') - } - if ( - (await l1l2Messages[1].waitForStatus()).status !== - L1ToL2MessageStatus.REDEEMED - ) { - throw new Error('second L1 to L2 message not redeemed') - } + const depositWaitResult = await l1l3Bridger.waitForDeposit(depositReceipt, setup.l2Signer.provider!, setup.l3Signer.provider!) - // get l2 l3 message - const secondRedeemReceipt = new L1ContractCallTransactionReceipt( - (await l1l2Messages[1].getAutoRedeemAttempt())! - ) - const l2l3Message = ( - await secondRedeemReceipt.getL1ToL2Messages(setup.l3Signer.provider!) - )[0] - - // wait for l2 l3 message to be redeemed - console.log('waiting for l2 l3 message to be redeemed') - if ( - (await l2l3Message.waitForStatus()).status !== - L1ToL2MessageStatus.REDEEMED - ) { - throw new Error('L2 to L3 message not redeemed') + if (depositWaitResult.success !== true || depositWaitResult.failedLeg !== undefined || depositWaitResult.failedLegStatus !== undefined) { + throw new Error('Deposit failed') } // make sure the tokens have landed in the right place From 6bddbd31054b8b1754c8aeb07173a9a5f2ddcc42 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:20:17 -0400 Subject: [PATCH 05/80] stub relayer related functions --- src/lib/assetBridger/l1l3Bridger.ts | 30 +++++++++++++++++++++------ tests/integration/l1l3Bridger.test.ts | 16 +++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index dd27f13074..52a2054759 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -182,11 +182,14 @@ export class L1L3Bridger { l2Provider: Provider ): Promise { await this.checkL2Network(l2Provider) - const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) - return this._getChildErc20Address(l2Token, l2Provider, this.l3Network) + return this._getChildErc20Address( + await this.getL2ERC20Address(erc20L1Address, l1Provider), + l2Provider, + this.l3Network + ) } - private async _getChildErc20Address( + private _getChildErc20Address( erc20ParentAddress: string, parentProvider: Provider, childNetwork: L2Network @@ -197,9 +200,7 @@ export class L1L3Bridger { parentProvider ) - return await parentGatewayRouter.functions - .calculateL2TokenAddress(erc20ParentAddress) - .then(([res]) => res) + return parentGatewayRouter.calculateL2TokenAddress(erc20ParentAddress) } /** @@ -554,6 +555,23 @@ export class L1L3Bridger { } } + public async getDepositRequestUsingRelayer() { + throw new Error('Not implemented') + } + + public async depositUsingRelayer() { + throw new Error('Not implemented') + } + + public async waitForDepositUsingRelayer() { + throw new Error('Not implemented') + } + + // takes: deposit tx hash, l2 forwarder params, l2 signer + public async relayDeposit() { + throw new Error('Not implemented') + } + private percentIncrease(base: BigNumber, percent: BigNumber) { return base.mul(percent).div(100) } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 5e39e429a4..584fb3e9de 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,7 +1,5 @@ import { testSetup } from '../../scripts/testSetup' -import { - L1L3Bridger -} from '../../src' +import { L1L3Bridger } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' import { MockToken } from '../../src/lib/abi/MockToken' @@ -87,9 +85,17 @@ describe('Teleporter', () => { const depositReceipt = await depositTx.wait() // wait for l1 l2 messages to redeem - const depositWaitResult = await l1l3Bridger.waitForDeposit(depositReceipt, setup.l2Signer.provider!, setup.l3Signer.provider!) + const depositWaitResult = await l1l3Bridger.waitForDeposit( + depositReceipt, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) - if (depositWaitResult.success !== true || depositWaitResult.failedLeg !== undefined || depositWaitResult.failedLegStatus !== undefined) { + if ( + depositWaitResult.success !== true || + depositWaitResult.failedLeg !== undefined || + depositWaitResult.failedLegStatus !== undefined + ) { throw new Error('Deposit failed') } From 9619e3015c050d3b4c39dab11032493016f88ae3 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:55:17 -0400 Subject: [PATCH 06/80] wip: relayer related functions --- src/lib/assetBridger/l1l3Bridger.ts | 130 +++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 20 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 52a2054759..a4f7e41b1d 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -18,7 +18,7 @@ import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factor import { ERC20 } from '../abi/ERC20' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { DISABLED_GATEWAY } from '../dataEntities/constants' -import { TokenApproveParams } from './erc20Bridger' +import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' import { BigNumber, BigNumberish, @@ -34,6 +34,7 @@ import { L1TransactionReceipt, } from '../message/L1Transaction' import { L1ToL2MessageStatus } from '../message/L1ToL2Message' +import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' /* API: @@ -103,6 +104,9 @@ export class L1L3Bridger { public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase + public readonly defaultRelayerPaymentPercentIncrease: BigNumber = + BigNumber.from(130) // 30% increase + public constructor(public readonly l3Network: L2Network) { this.l2Network = l2Networks[l3Network.partnerChainID] if (!this.l2Network) { @@ -394,27 +398,16 @@ export class L1L3Bridger { return l1Signer.sendTransaction(approveRequest) } - public async getDepositRequest( + private async _populateGasParams( params: DepositRequestParams, - l1Signer: Signer, + l1Provider: Provider, l2Provider: Provider, l3Provider: Provider, - gasPricePercentIncrease?: BigNumber - ): Promise< - Required> - > { - await this.checkL1Network(l1Signer) - await this.checkL2Network(l2Provider) - await this.checkL3Network(l3Provider) - - gasPricePercentIncrease = - gasPricePercentIncrease || this.defaultGasPricePercentIncrease - + gasPricePercentIncrease: BigNumber + ) { if (!params.gasParams) { // make sure both gateways are default - if ( - !(await this.isL1L2GatewayDefault(params.l1Token, l1Signer.provider!)) - ) { + if (!(await this.isL1L2GatewayDefault(params.l1Token, l1Provider))) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` ) @@ -423,7 +416,7 @@ export class L1L3Bridger { if ( !(await this.isL2L3GatewayDefault( params.l1Token, - l1Signer.provider!, + l1Provider, l2Provider )) ) { @@ -436,7 +429,7 @@ export class L1L3Bridger { } // populate gasParams gas prices - const gasParams = { + return { ...params.gasParams, l2GasPrice: params.gasParams.l2GasPrice || @@ -451,6 +444,31 @@ export class L1L3Bridger { gasPricePercentIncrease ), } + } + + public async getDepositRequest( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider, + gasPricePercentIncrease?: BigNumber + ): Promise< + Required> + > { + await this.checkL1Network(l1Signer) + await this.checkL2Network(l2Provider) + await this.checkL3Network(l3Provider) + + gasPricePercentIncrease = + gasPricePercentIncrease || this.defaultGasPricePercentIncrease + + const gasParams = await this._populateGasParams( + params, + l1Signer.provider!, + l2Provider, + l3Provider, + gasPricePercentIncrease + ) const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, @@ -555,10 +573,82 @@ export class L1L3Bridger { } } - public async getDepositRequestUsingRelayer() { + public async getApproveTokenRequestUsingRelayer() { throw new Error('Not implemented') } + public async getDepositRequestUsingRelayer( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider, + overrides: {gasPricePercentIncrease?: BigNumber, relayerPaymentPercentIncrease?: BigNumber} = {} + ) { + await this.checkL1Network(l1Signer) + await this.checkL2Network(l2Provider) + await this.checkL3Network(l3Provider) + + const gasPricePercentIncrease = + overrides.gasPricePercentIncrease || this.defaultGasPricePercentIncrease + const relayerPaymentPercentIncrease = + overrides.relayerPaymentPercentIncrease || this.defaultRelayerPaymentPercentIncrease + + const gasParams = await this._populateGasParams( + params, + l1Signer.provider!, + l2Provider, + l3Provider, + gasPricePercentIncrease + ) + + const teleporter = Teleporter__factory.connect( + this.teleporterAddresses.l1Teleporter, + l1Signer + ) + + // calculate l2 forwarder address + const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { + owner: await l1Signer.getAddress(), + token: params.l1Token, + router: this.l3Network.tokenBridge.l1GatewayRouter, + to: params.to, + amount: params.amount, + gasLimit: gasParams.l2l3TokenBridgeGasLimit, + gasPrice: gasParams.l3GasPrice, + // todo: custom relayer payment parameter. default percent increase. + relayerPayment: gasParams.l2ForwarderFactoryGasLimit.mul( + gasParams.l2GasPrice + ), + } + + const l2ForwarderAddress = await teleporter.l2ForwarderAddress( + l2ForwarderParams + ) + + const erc20Bridger = new Erc20Bridger(this.l2Network) + + const tokenBridgeRequest = await erc20Bridger.getDepositRequest({ + amount: BigNumber.from(params.amount), + l1Provider: l1Signer.provider!, + l2Provider: l2Provider, + erc20L1Address: params.l1Token, + from: await l1Signer.getAddress(), + }) + + // add relayer payment and l3 retryable costs to value + const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( + this.l2Network.ethBridge.inbox, + '0', // we don't care about L1 gas price, this will just set it to current gas price + gasParams + ) + const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost + .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) + .add(relayerPaymentPercentIncrease.mul(l2ForwarderParams.relayerPayment).div(100)) + tokenBridgeRequest.txRequest.value = extraValue.add(tokenBridgeRequest.txRequest.value) + + return tokenBridgeRequest + } + public async depositUsingRelayer() { throw new Error('Not implemented') } From a82067173676e900ddaf39ffb688ea72d5c757f8 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:35:56 -0400 Subject: [PATCH 07/80] start types refactor --- src/lib/assetBridger/l1l3Bridger.ts | 62 ++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index a4f7e41b1d..1c9ed08b62 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -30,11 +30,13 @@ import { import { Teleporter__factory } from '../abi/factories/Teleporter__factory' import { AbiCoder } from 'ethers/lib/utils' import { + L1ContractCallTransaction, L1ContractCallTransactionReceipt, L1TransactionReceipt, } from '../message/L1Transaction' import { L1ToL2MessageStatus } from '../message/L1ToL2Message' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' +import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' /* API: @@ -64,6 +66,7 @@ for gas estimation: do the same for the l2l3 gateway */ + export interface RetryableGasParams { l2GasPrice?: BigNumber l3GasPrice?: BigNumber @@ -87,6 +90,22 @@ export interface WaitForDepositResult { failedLegStatus?: Exclude } +export type ApproveTokenRequestResult = Required> + +export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { + chainId: number +} + +export type DepositRequestUsingRelayerResult = { + txRequest: L1ToL2TransactionRequest + relayerInfo: RelayerInfo +} + +export type DepositUsingRelayerResult = { + tx: L1ContractCallTransaction + relayerInfo: RelayerInfo +} + export class L1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network @@ -390,7 +409,7 @@ export class L1L3Bridger { } } - public async approveToken(params: TokenApproveParams, l1Signer: Signer) { + public async approveToken(params: TokenApproveParams, l1Signer: Signer): Promise { await this.checkL1Network(l1Signer) const approveRequest = await this.getApproveTokenRequest(params) @@ -448,14 +467,14 @@ export class L1L3Bridger { public async getDepositRequest( params: DepositRequestParams, - l1Signer: Signer, + l1Provider: Provider, l2Provider: Provider, l3Provider: Provider, gasPricePercentIncrease?: BigNumber ): Promise< - Required> + Required> > { - await this.checkL1Network(l1Signer) + await this.checkL1Network(l1Provider) await this.checkL2Network(l2Provider) await this.checkL3Network(l3Provider) @@ -464,7 +483,7 @@ export class L1L3Bridger { const gasParams = await this._populateGasParams( params, - l1Signer.provider!, + l1Provider, l2Provider, l3Provider, gasPricePercentIncrease @@ -472,7 +491,7 @@ export class L1L3Bridger { const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, - l1Signer + l1Provider ) const calldata = teleporter.interface.encodeFunctionData('teleport', [ @@ -484,7 +503,7 @@ export class L1L3Bridger { gasParams, ]) - const l1GasPrice = await l1Signer.provider!.getGasPrice() + const l1GasPrice = await l1Provider.getGasPrice() const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, @@ -496,7 +515,6 @@ export class L1L3Bridger { to: this.teleporterAddresses.l1Teleporter, data: calldata, value: calculatedGasCosts.total, - from: await l1Signer.getAddress(), } } @@ -506,10 +524,10 @@ export class L1L3Bridger { l2Provider: Provider, l3Provider: Provider, gasPricePercentIncrease?: BigNumber - ) { + ): Promise { const txRequest = await this.getDepositRequest( params, - l1Signer, + l1Signer.provider!, l2Provider, l3Provider, gasPricePercentIncrease @@ -573,7 +591,7 @@ export class L1L3Bridger { } } - public async getApproveTokenRequestUsingRelayer() { + public async getApproveTokenRequestUsingRelayer(params: TokenApproveParams & { l1Provider: Provider }) { throw new Error('Not implemented') } @@ -583,7 +601,7 @@ export class L1L3Bridger { l2Provider: Provider, l3Provider: Provider, overrides: {gasPricePercentIncrease?: BigNumber, relayerPaymentPercentIncrease?: BigNumber} = {} - ) { + ): Promise { await this.checkL1Network(l1Signer) await this.checkL2Network(l2Provider) await this.checkL3Network(l3Provider) @@ -633,6 +651,9 @@ export class L1L3Bridger { l2Provider: l2Provider, erc20L1Address: params.l1Token, from: await l1Signer.getAddress(), + destinationAddress: l2ForwarderAddress, + callValueRefundAddress: l2ForwarderAddress, + excessFeeRefundAddress: l2ForwarderAddress, }) // add relayer payment and l3 retryable costs to value @@ -646,10 +667,22 @@ export class L1L3Bridger { .add(relayerPaymentPercentIncrease.mul(l2ForwarderParams.relayerPayment).div(100)) tokenBridgeRequest.txRequest.value = extraValue.add(tokenBridgeRequest.txRequest.value) - return tokenBridgeRequest + return { + relayerInfo: { + ...l2ForwarderParams, + chainId: this.l2Network.chainID + }, + txRequest: tokenBridgeRequest + } } - public async depositUsingRelayer() { + public async depositUsingRelayer( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider, + overrides: {gasPricePercentIncrease?: BigNumber, relayerPaymentPercentIncrease?: BigNumber} = {} + ): Promise { throw new Error('Not implemented') } @@ -662,6 +695,7 @@ export class L1L3Bridger { throw new Error('Not implemented') } + // todo: find and replace '100' with this private percentIncrease(base: BigNumber, percent: BigNumber) { return base.mul(percent).div(100) } From 7119f0a57b434f31e81af0ae9f8a461a25c910ef Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:47:18 -0400 Subject: [PATCH 08/80] types look better --- src/lib/assetBridger/l1l3Bridger.ts | 206 +++++++++++++++++++--------- 1 file changed, 139 insertions(+), 67 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 1c9ed08b62..eadb7a17f7 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -66,10 +66,7 @@ for gas estimation: do the same for the l2l3 gateway */ - -export interface RetryableGasParams { - l2GasPrice?: BigNumber - l3GasPrice?: BigNumber +export interface ManualRetryableGasParams { l2ForwarderFactoryGasLimit: BigNumber l1l2TokenBridgeGasLimit: BigNumber l2l3TokenBridgeGasLimit: BigNumber @@ -77,21 +74,29 @@ export interface RetryableGasParams { l2l3TokenBridgeRetryableSize: BigNumber // todo: could call the gateway to get the calldata for a given token? } +export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { + l2GasPrice: BigNumber + l3GasPrice: BigNumber +} + export interface DepositRequestParams { l1Token: string to: string amount: BigNumberish - gasParams?: RetryableGasParams + overrides?: { + gasPricePercentIncrease?: BigNumber + relayerPaymentPercentIncrease?: BigNumber + manualGasParams?: ManualRetryableGasParams + } } +// if using relayer and leg 1 times out, failedLegStatus will be undefined export interface WaitForDepositResult { success: boolean failedLeg?: 0 | 1 | 2 // 0 is token bridge to l2, 1 is call to l2 forwarder factory, 2 is token bridge to l3 failedLegStatus?: Exclude } -export type ApproveTokenRequestResult = Required> - export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number } @@ -112,7 +117,7 @@ export class L1L3Bridger { public readonly teleporterAddresses: TeleporterAddresses // todo: tune these - public readonly defaultRetryableGasParams: RetryableGasParams = { + public readonly defaultRetryableGasParams: ManualRetryableGasParams = { l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), l1l2TokenBridgeGasLimit: BigNumber.from(1_000_000), l2l3TokenBridgeGasLimit: BigNumber.from(1_000_000), @@ -409,7 +414,10 @@ export class L1L3Bridger { } } - public async approveToken(params: TokenApproveParams, l1Signer: Signer): Promise { + public async approveToken( + params: TokenApproveParams, + l1Signer: Signer + ): Promise { await this.checkL1Network(l1Signer) const approveRequest = await this.getApproveTokenRequest(params) @@ -421,10 +429,12 @@ export class L1L3Bridger { params: DepositRequestParams, l1Provider: Provider, l2Provider: Provider, - l3Provider: Provider, - gasPricePercentIncrease: BigNumber - ) { - if (!params.gasParams) { + l3Provider: Provider + ): Promise { + params.overrides = params.overrides || {} + + let manualGasParams = params.overrides.manualGasParams + if (!manualGasParams) { // make sure both gateways are default if (!(await this.isL1L2GatewayDefault(params.l1Token, l1Provider))) { throw new ArbSdkError( @@ -444,24 +454,22 @@ export class L1L3Bridger { ) } - params.gasParams = this.defaultRetryableGasParams + manualGasParams = this.defaultRetryableGasParams } // populate gasParams gas prices return { - ...params.gasParams, - l2GasPrice: - params.gasParams.l2GasPrice || - this.percentIncrease( - await l2Provider.getGasPrice(), - gasPricePercentIncrease - ), - l3GasPrice: - params.gasParams.l3GasPrice || - this.percentIncrease( - await l3Provider.getGasPrice(), - gasPricePercentIncrease - ), + ...manualGasParams, + l2GasPrice: this.percentIncrease( + await l2Provider.getGasPrice(), + params.overrides.gasPricePercentIncrease || + this.defaultGasPricePercentIncrease + ), + l3GasPrice: this.percentIncrease( + await l3Provider.getGasPrice(), + params.overrides.gasPricePercentIncrease || + this.defaultGasPricePercentIncrease + ), } } @@ -469,24 +477,17 @@ export class L1L3Bridger { params: DepositRequestParams, l1Provider: Provider, l2Provider: Provider, - l3Provider: Provider, - gasPricePercentIncrease?: BigNumber - ): Promise< - Required> - > { + l3Provider: Provider + ): Promise>> { await this.checkL1Network(l1Provider) await this.checkL2Network(l2Provider) await this.checkL3Network(l3Provider) - gasPricePercentIncrease = - gasPricePercentIncrease || this.defaultGasPricePercentIncrease - const gasParams = await this._populateGasParams( params, l1Provider, l2Provider, - l3Provider, - gasPricePercentIncrease + l3Provider ) const teleporter = Teleporter__factory.connect( @@ -522,15 +523,13 @@ export class L1L3Bridger { params: DepositRequestParams, l1Signer: Signer, l2Provider: Provider, - l3Provider: Provider, - gasPricePercentIncrease?: BigNumber + l3Provider: Provider ): Promise { const txRequest = await this.getDepositRequest( params, l1Signer.provider!, l2Provider, - l3Provider, - gasPricePercentIncrease + l3Provider ) return L1TransactionReceipt.monkeyPatchContractCallWait( @@ -591,7 +590,10 @@ export class L1L3Bridger { } } - public async getApproveTokenRequestUsingRelayer(params: TokenApproveParams & { l1Provider: Provider }) { + public async getApproveTokenRequestUsingRelayer( + params: TokenApproveParams, + l1Provider: Provider + ): Promise>> { throw new Error('Not implemented') } @@ -599,24 +601,17 @@ export class L1L3Bridger { params: DepositRequestParams, l1Signer: Signer, l2Provider: Provider, - l3Provider: Provider, - overrides: {gasPricePercentIncrease?: BigNumber, relayerPaymentPercentIncrease?: BigNumber} = {} + l3Provider: Provider ): Promise { await this.checkL1Network(l1Signer) await this.checkL2Network(l2Provider) await this.checkL3Network(l3Provider) - const gasPricePercentIncrease = - overrides.gasPricePercentIncrease || this.defaultGasPricePercentIncrease - const relayerPaymentPercentIncrease = - overrides.relayerPaymentPercentIncrease || this.defaultRelayerPaymentPercentIncrease - - const gasParams = await this._populateGasParams( + const populatedGasParams = await this._populateGasParams( params, l1Signer.provider!, l2Provider, - l3Provider, - gasPricePercentIncrease + l3Provider ) const teleporter = Teleporter__factory.connect( @@ -624,6 +619,14 @@ export class L1L3Bridger { l1Signer ) + const relayerPayment = this.percentIncrease( + populatedGasParams.l2ForwarderFactoryGasLimit.mul( + populatedGasParams.l2GasPrice + ), + params.overrides?.relayerPaymentPercentIncrease || + this.defaultRelayerPaymentPercentIncrease + ) + // calculate l2 forwarder address const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { owner: await l1Signer.getAddress(), @@ -631,12 +634,9 @@ export class L1L3Bridger { router: this.l3Network.tokenBridge.l1GatewayRouter, to: params.to, amount: params.amount, - gasLimit: gasParams.l2l3TokenBridgeGasLimit, - gasPrice: gasParams.l3GasPrice, - // todo: custom relayer payment parameter. default percent increase. - relayerPayment: gasParams.l2ForwarderFactoryGasLimit.mul( - gasParams.l2GasPrice - ), + gasLimit: populatedGasParams.l2l3TokenBridgeGasLimit, + gasPrice: populatedGasParams.l3GasPrice, + relayerPayment, } const l2ForwarderAddress = await teleporter.l2ForwarderAddress( @@ -660,19 +660,21 @@ export class L1L3Bridger { const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, '0', // we don't care about L1 gas price, this will just set it to current gas price - gasParams + populatedGasParams ) const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) - .add(relayerPaymentPercentIncrease.mul(l2ForwarderParams.relayerPayment).div(100)) - tokenBridgeRequest.txRequest.value = extraValue.add(tokenBridgeRequest.txRequest.value) + .add(relayerPayment) + tokenBridgeRequest.txRequest.value = extraValue.add( + tokenBridgeRequest.txRequest.value + ) return { relayerInfo: { ...l2ForwarderParams, - chainId: this.l2Network.chainID + chainId: this.l2Network.chainID, }, - txRequest: tokenBridgeRequest + txRequest: tokenBridgeRequest, } } @@ -680,18 +682,24 @@ export class L1L3Bridger { params: DepositRequestParams, l1Signer: Signer, l2Provider: Provider, - l3Provider: Provider, - overrides: {gasPricePercentIncrease?: BigNumber, relayerPaymentPercentIncrease?: BigNumber} = {} + l3Provider: Provider ): Promise { throw new Error('Not implemented') } - public async waitForDepositUsingRelayer() { + public async waitForDepositUsingRelayer( + depositResult: DepositUsingRelayerResult, + l2Provider: Provider, + l3Provider: Provider + ): Promise { throw new Error('Not implemented') } // takes: deposit tx hash, l2 forwarder params, l2 signer - public async relayDeposit() { + public static async relayDeposit( + relayerInfo: RelayerInfo, + l2Signer: Signer + ): Promise { throw new Error('Not implemented') } @@ -700,3 +708,67 @@ export class L1L3Bridger { return base.mul(percent).div(100) } } + +// for reference +interface IL1L3Bridger { + getApproveTokenRequest( + params: TokenApproveParams + ): Promise>> + + approveToken( + params: TokenApproveParams, + l1Signer: Signer + ): Promise + + getDepositRequest( + params: DepositRequestParams, + l1Provider: Provider, + l2Provider: Provider, + l3Provider: Provider + ): Promise>> + + deposit( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + // waits for deposit to arrive on L3 + waitForDeposit( + depositTxReceipt: L1TransactionReceipt, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + getApproveTokenRequestUsingRelayer( + params: TokenApproveParams, + l1Provider: Provider + ): Promise>> + + getDepositRequestUsingRelayer( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + depositUsingRelayer( + params: DepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + waitForDepositUsingRelayer( + depositResult: DepositUsingRelayerResult, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + // static + relayDeposit( + relayerInfo: RelayerInfo, + l2Signer: Signer + ): Promise +} From 752dd2fb697951393053d142d3a1b2e3af13d16e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:06:40 -0400 Subject: [PATCH 09/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 66 ++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index eadb7a17f7..98c9aa3efb 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -66,6 +66,17 @@ for gas estimation: do the same for the l2l3 gateway */ +export enum TeleportationLeg { + BridgeToL2, + CallL2ForwarderFactory, + BridgeToL3, +} + +export enum EthTeleportationLeg { + L2Retryable, + L3Retryable, +} + export interface ManualRetryableGasParams { l2ForwarderFactoryGasLimit: BigNumber l1l2TokenBridgeGasLimit: BigNumber @@ -80,7 +91,7 @@ export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { } export interface DepositRequestParams { - l1Token: string + erc20L1Address: string to: string amount: BigNumberish overrides?: { @@ -90,10 +101,24 @@ export interface DepositRequestParams { } } +export interface DepositEthRequestParams { + to: string + amount: BigNumberish + overrides?: { + gasPricePercentIncrease?: BigNumber + } +} + // if using relayer and leg 1 times out, failedLegStatus will be undefined export interface WaitForDepositResult { success: boolean - failedLeg?: 0 | 1 | 2 // 0 is token bridge to l2, 1 is call to l2 forwarder factory, 2 is token bridge to l3 + failedLeg?: TeleportationLeg + failedLegStatus?: Exclude +} + +export interface WaitForEthDepositResult { + success: boolean + failedLeg?: EthTeleportationLeg failedLegStatus?: Exclude } @@ -436,7 +461,7 @@ export class L1L3Bridger { let manualGasParams = params.overrides.manualGasParams if (!manualGasParams) { // make sure both gateways are default - if (!(await this.isL1L2GatewayDefault(params.l1Token, l1Provider))) { + if (!(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider))) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` ) @@ -444,7 +469,7 @@ export class L1L3Bridger { if ( !(await this.isL2L3GatewayDefault( - params.l1Token, + params.erc20L1Address, l1Provider, l2Provider )) @@ -496,7 +521,7 @@ export class L1L3Bridger { ) const calldata = teleporter.interface.encodeFunctionData('teleport', [ - params.l1Token, + params.erc20L1Address, this.l2Network.tokenBridge.l1GatewayRouter, this.l3Network.tokenBridge.l1GatewayRouter, params.to, @@ -558,13 +583,13 @@ export class L1L3Bridger { if (firstLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: 0, + failedLeg: TeleportationLeg.BridgeToL2, failedLegStatus: firstLegStatus, } } else if (secondLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: 1, + failedLeg: TeleportationLeg.CallL2ForwarderFactory, failedLegStatus: secondLegStatus, } } @@ -580,7 +605,7 @@ export class L1L3Bridger { if (thirdLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: 2, + failedLeg: TeleportationLeg.BridgeToL3, failedLegStatus: thirdLegStatus, } } @@ -630,7 +655,7 @@ export class L1L3Bridger { // calculate l2 forwarder address const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { owner: await l1Signer.getAddress(), - token: params.l1Token, + token: params.erc20L1Address, router: this.l3Network.tokenBridge.l1GatewayRouter, to: params.to, amount: params.amount, @@ -649,7 +674,7 @@ export class L1L3Bridger { amount: BigNumber.from(params.amount), l1Provider: l1Signer.provider!, l2Provider: l2Provider, - erc20L1Address: params.l1Token, + erc20L1Address: params.erc20L1Address, from: await l1Signer.getAddress(), destinationAddress: l2ForwarderAddress, callValueRefundAddress: l2ForwarderAddress, @@ -766,6 +791,27 @@ interface IL1L3Bridger { l3Provider: Provider ): Promise + getDepositEthRequest( + params: DepositEthRequestParams, + l1Provider: Provider, + l2Provider: Provider, + l3Provider: Provider + ): Promise>> + + depositEth( + params: DepositEthRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise + + // waits for deposit to arrive on L3 + waitForDepositEth( + depositTxReceipt: L1TransactionReceipt, + l2Provider: Provider, + l3Provider: Provider + ): Promise + // static relayDeposit( relayerInfo: RelayerInfo, From 90b46558abbff6cb4d10006182cbcd86543135d2 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:29:03 -0400 Subject: [PATCH 10/80] start to split into 3 classes --- src/lib/assetBridger/l1l3Bridger.ts | 273 ++++++++++++++------------ tests/integration/l1l3Bridger.test.ts | 2 +- 2 files changed, 143 insertions(+), 132 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 98c9aa3efb..0a34148f6c 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -136,7 +136,7 @@ export type DepositUsingRelayerResult = { relayerInfo: RelayerInfo } -export class L1L3Bridger { +class Erc20L1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network public readonly teleporterAddresses: TeleporterAddresses @@ -417,40 +417,7 @@ export class L1L3Bridger { ) } - /** - * Get a tx request to approve tokens for teleportation. - * The tokens will be approved for the Teleporter on L1. - * @param params - * @returns - */ - public async getApproveTokenRequest( - params: TokenApproveParams - ): Promise>> { - const iErc20Interface = ERC20__factory.createInterface() - const data = iErc20Interface.encodeFunctionData('approve', [ - this.teleporterAddresses.l1Teleporter, - params.amount || ethers.constants.MaxUint256, - ]) - - return { - to: params.erc20L1Address, - data, - value: BigNumber.from(0), - } - } - - public async approveToken( - params: TokenApproveParams, - l1Signer: Signer - ): Promise { - await this.checkL1Network(l1Signer) - - const approveRequest = await this.getApproveTokenRequest(params) - - return l1Signer.sendTransaction(approveRequest) - } - - private async _populateGasParams( + protected async _populateGasParams( params: DepositRequestParams, l1Provider: Provider, l2Provider: Provider, @@ -485,12 +452,12 @@ export class L1L3Bridger { // populate gasParams gas prices return { ...manualGasParams, - l2GasPrice: this.percentIncrease( + l2GasPrice: this._percentIncrease( await l2Provider.getGasPrice(), params.overrides.gasPricePercentIncrease || this.defaultGasPricePercentIncrease ), - l3GasPrice: this.percentIncrease( + l3GasPrice: this._percentIncrease( await l3Provider.getGasPrice(), params.overrides.gasPricePercentIncrease || this.defaultGasPricePercentIncrease @@ -498,6 +465,46 @@ export class L1L3Bridger { } } + // todo: find and replace '100' with this + protected _percentIncrease(base: BigNumber, percent: BigNumber) { + return base.mul(percent).div(100) + } +} + +export class L1L3Bridger extends Erc20L1L3Bridger { + /** + * Get a tx request to approve tokens for teleportation. + * The tokens will be approved for the Teleporter on L1. + * @param params + * @returns + */ + public async getApproveTokenRequest( + params: TokenApproveParams + ): Promise>> { + const iErc20Interface = ERC20__factory.createInterface() + const data = iErc20Interface.encodeFunctionData('approve', [ + this.teleporterAddresses.l1Teleporter, + params.amount || ethers.constants.MaxUint256, + ]) + + return { + to: params.erc20L1Address, + data, + value: BigNumber.from(0), + } + } + + public async approveToken( + params: TokenApproveParams, + l1Signer: Signer + ): Promise { + await this.checkL1Network(l1Signer) + + const approveRequest = await this.getApproveTokenRequest(params) + + return l1Signer.sendTransaction(approveRequest) + } + public async getDepositRequest( params: DepositRequestParams, l1Provider: Provider, @@ -533,7 +540,7 @@ export class L1L3Bridger { const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, - this.percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), + this._percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), gasParams ) @@ -614,15 +621,24 @@ export class L1L3Bridger { success: true, } } +} - public async getApproveTokenRequestUsingRelayer( +class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { + public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider ): Promise>> { throw new Error('Not implemented') } - public async getDepositRequestUsingRelayer( + public async approveToken( + params: TokenApproveParams, + l1Signer: Signer + ): Promise { + throw new Error('Not implemented') + } + + public async getDepositRequest( params: DepositRequestParams, l1Signer: Signer, l2Provider: Provider, @@ -644,7 +660,7 @@ export class L1L3Bridger { l1Signer ) - const relayerPayment = this.percentIncrease( + const relayerPayment = this._percentIncrease( populatedGasParams.l2ForwarderFactoryGasLimit.mul( populatedGasParams.l2GasPrice ), @@ -703,7 +719,7 @@ export class L1L3Bridger { } } - public async depositUsingRelayer( + public async deposit( params: DepositRequestParams, l1Signer: Signer, l2Provider: Provider, @@ -712,7 +728,7 @@ export class L1L3Bridger { throw new Error('Not implemented') } - public async waitForDepositUsingRelayer( + public async waitForDeposit( depositResult: DepositUsingRelayerResult, l2Provider: Provider, l3Provider: Provider @@ -727,94 +743,89 @@ export class L1L3Bridger { ): Promise { throw new Error('Not implemented') } - - // todo: find and replace '100' with this - private percentIncrease(base: BigNumber, percent: BigNumber) { - return base.mul(percent).div(100) - } } // for reference -interface IL1L3Bridger { - getApproveTokenRequest( - params: TokenApproveParams - ): Promise>> - - approveToken( - params: TokenApproveParams, - l1Signer: Signer - ): Promise - - getDepositRequest( - params: DepositRequestParams, - l1Provider: Provider, - l2Provider: Provider, - l3Provider: Provider - ): Promise>> - - deposit( - params: DepositRequestParams, - l1Signer: Signer, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - // waits for deposit to arrive on L3 - waitForDeposit( - depositTxReceipt: L1TransactionReceipt, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - getApproveTokenRequestUsingRelayer( - params: TokenApproveParams, - l1Provider: Provider - ): Promise>> - - getDepositRequestUsingRelayer( - params: DepositRequestParams, - l1Signer: Signer, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - depositUsingRelayer( - params: DepositRequestParams, - l1Signer: Signer, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - waitForDepositUsingRelayer( - depositResult: DepositUsingRelayerResult, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - getDepositEthRequest( - params: DepositEthRequestParams, - l1Provider: Provider, - l2Provider: Provider, - l3Provider: Provider - ): Promise>> - - depositEth( - params: DepositEthRequestParams, - l1Signer: Signer, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - // waits for deposit to arrive on L3 - waitForDepositEth( - depositTxReceipt: L1TransactionReceipt, - l2Provider: Provider, - l3Provider: Provider - ): Promise - - // static - relayDeposit( - relayerInfo: RelayerInfo, - l2Signer: Signer - ): Promise -} +// interface IL1L3Bridger { +// getApproveTokenRequest( +// params: TokenApproveParams +// ): Promise>> + +// approveToken( +// params: TokenApproveParams, +// l1Signer: Signer +// ): Promise + +// getDepositRequest( +// params: DepositRequestParams, +// l1Provider: Provider, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise>> + +// deposit( +// params: DepositRequestParams, +// l1Signer: Signer, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// // waits for deposit to arrive on L3 +// waitForDeposit( +// depositTxReceipt: L1TransactionReceipt, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// getApproveTokenRequestUsingRelayer( +// params: TokenApproveParams, +// l1Provider: Provider +// ): Promise>> + +// getDepositRequestUsingRelayer( +// params: DepositRequestParams, +// l1Signer: Signer, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// depositUsingRelayer( +// params: DepositRequestParams, +// l1Signer: Signer, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// waitForDepositUsingRelayer( +// depositResult: DepositUsingRelayerResult, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// getDepositEthRequest( +// params: DepositEthRequestParams, +// l1Provider: Provider, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise>> + +// depositEth( +// params: DepositEthRequestParams, +// l1Signer: Signer, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// // waits for deposit to arrive on L3 +// waitForDepositEth( +// depositTxReceipt: L1TransactionReceipt, +// l2Provider: Provider, +// l3Provider: Provider +// ): Promise + +// // static +// relayDeposit( +// relayerInfo: RelayerInfo, +// l2Signer: Signer +// ): Promise +// } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 584fb3e9de..d3af9c0f40 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -73,7 +73,7 @@ describe('Teleporter', () => { // todo: rename to teleport? const depositTx = await l1l3Bridger.deposit( { - l1Token: l1Token.address, + erc20L1Address: l1Token.address, to: l3Recipient, amount: ethers.utils.parseEther('1'), }, From 3b8b6009aa06f53700092f4607dbbb052b2fd5e8 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:05:22 -0400 Subject: [PATCH 11/80] class split done --- src/lib/assetBridger/l1l3Bridger.ts | 203 ++++++++++------------------ 1 file changed, 70 insertions(+), 133 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 0a34148f6c..23dbfecf6c 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -136,55 +136,45 @@ export type DepositUsingRelayerResult = { relayerInfo: RelayerInfo } -class Erc20L1L3Bridger { +class BaseL1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network - public readonly teleporterAddresses: TeleporterAddresses - - // todo: tune these - public readonly defaultRetryableGasParams: ManualRetryableGasParams = { - l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), - l1l2TokenBridgeGasLimit: BigNumber.from(1_000_000), - l2l3TokenBridgeGasLimit: BigNumber.from(1_000_000), - l1l2TokenBridgeRetryableSize: BigNumber.from(1000), - l2l3TokenBridgeRetryableSize: BigNumber.from(1000), - } as const + public readonly l3Network: L2Network public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase - public readonly defaultRelayerPaymentPercentIncrease: BigNumber = - BigNumber.from(130) // 30% increase - - public constructor(public readonly l3Network: L2Network) { - this.l2Network = l2Networks[l3Network.partnerChainID] - if (!this.l2Network) { + constructor(l3Network: L2Network) { + const l2Network = l2Networks[l3Network.partnerChainID] + if (!l2Network) { throw new ArbSdkError( `Unknown l2 network chain id: ${l3Network.partnerChainID}` ) } - if (!this.l2Network.teleporterAddresses) { + if (!l2Network.teleporterAddresses) { throw new ArbSdkError( - `L2 network ${this.l2Network.name} does not have a teleporter` + `L2 network ${l2Network.name} does not have a teleporter` ) } - this.teleporterAddresses = this.l2Network.teleporterAddresses - - this.l1Network = l1Networks[this.l2Network.partnerChainID] - if (!this.l1Network) { + const l1Network = l1Networks[l2Network.partnerChainID] + if (!l1Network) { throw new ArbSdkError( - `Unknown l1 network chain id: ${this.l2Network.partnerChainID}` + `Unknown l1 network chain id: ${l2Network.partnerChainID}` ) } + + this.l1Network = l1Network + this.l2Network = l2Network + this.l3Network = l3Network } /** * Check the signer/provider matches the l1Network, throws if not * @param sop */ - protected async checkL1Network(sop: SignerOrProvider): Promise { + protected async _checkL1Network(sop: SignerOrProvider): Promise { await SignerProviderUtils.checkNetworkMatches(sop, this.l1Network.chainID) } @@ -192,7 +182,7 @@ class Erc20L1L3Bridger { * Check the signer/provider matches the l2Network, throws if not * @param sop */ - protected async checkL2Network(sop: SignerOrProvider): Promise { + protected async _checkL2Network(sop: SignerOrProvider): Promise { await SignerProviderUtils.checkNetworkMatches(sop, this.l2Network.chainID) } @@ -200,10 +190,43 @@ class Erc20L1L3Bridger { * Check the signer/provider matches the l2Network, throws if not * @param sop */ - protected async checkL3Network(sop: SignerOrProvider): Promise { + protected async _checkL3Network(sop: SignerOrProvider): Promise { await SignerProviderUtils.checkNetworkMatches(sop, this.l3Network.chainID) } + // todo: find and replace '100' with this + protected _percentIncrease(base: BigNumber, percent: BigNumber) { + return base.mul(percent).div(100) + } +} + +class Erc20L1L3Bridger extends BaseL1L3Bridger { + public readonly teleporterAddresses: TeleporterAddresses + + // todo: tune these + public readonly defaultRetryableGasParams: ManualRetryableGasParams = { + l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), + l1l2TokenBridgeGasLimit: BigNumber.from(1_000_000), + l2l3TokenBridgeGasLimit: BigNumber.from(1_000_000), + l1l2TokenBridgeRetryableSize: BigNumber.from(1000), + l2l3TokenBridgeRetryableSize: BigNumber.from(1000), + } as const + + public readonly defaultRelayerPaymentPercentIncrease: BigNumber = + BigNumber.from(130) // 30% increase + + public constructor(public readonly l3Network: L2Network) { + super(l3Network) + + if (!this.l2Network.teleporterAddresses) { + throw new ArbSdkError( + `L2 network ${this.l2Network.name} does not have teleporter contracts` + ) + } + + this.teleporterAddresses = this.l2Network.teleporterAddresses + } + /** * Get the corresponding L2 token address for the provided L1 token * @param erc20L1Address @@ -214,7 +237,7 @@ class Erc20L1L3Bridger { erc20L1Address: string, l1Provider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this._checkL1Network(l1Provider) return this._getChildErc20Address( erc20L1Address, l1Provider, @@ -234,7 +257,7 @@ class Erc20L1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - await this.checkL2Network(l2Provider) + await this._checkL2Network(l2Provider) return this._getChildErc20Address( await this.getL2ERC20Address(erc20L1Address, l1Provider), l2Provider, @@ -266,7 +289,7 @@ class Erc20L1L3Bridger { erc20L1Address: string, l1Provider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this._checkL1Network(l1Provider) return await L1GatewayRouter__factory.connect( this.l2Network.tokenBridge.l1GatewayRouter, @@ -286,8 +309,8 @@ class Erc20L1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - await this.checkL1Network(l1Provider) - await this.checkL2Network(l2Provider) + await this._checkL1Network(l1Provider) + await this._checkL2Network(l2Provider) const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) @@ -373,7 +396,7 @@ class Erc20L1L3Bridger { l1TokenAddress: string, l1Provider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this._checkL1Network(l1Provider) return this._tokenIsDisabled( l1TokenAddress, @@ -392,7 +415,7 @@ class Erc20L1L3Bridger { l2TokenAddress: string, l2Provider: Provider ): Promise { - await this.checkL2Network(l2Provider) + await this._checkL2Network(l2Provider) return this._tokenIsDisabled( l2TokenAddress, @@ -464,11 +487,6 @@ class Erc20L1L3Bridger { ), } } - - // todo: find and replace '100' with this - protected _percentIncrease(base: BigNumber, percent: BigNumber) { - return base.mul(percent).div(100) - } } export class L1L3Bridger extends Erc20L1L3Bridger { @@ -498,7 +516,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { params: TokenApproveParams, l1Signer: Signer ): Promise { - await this.checkL1Network(l1Signer) + await this._checkL1Network(l1Signer) const approveRequest = await this.getApproveTokenRequest(params) @@ -511,9 +529,9 @@ export class L1L3Bridger extends Erc20L1L3Bridger { l2Provider: Provider, l3Provider: Provider ): Promise>> { - await this.checkL1Network(l1Provider) - await this.checkL2Network(l2Provider) - await this.checkL3Network(l3Provider) + await this._checkL1Network(l1Provider) + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) const gasParams = await this._populateGasParams( params, @@ -580,8 +598,8 @@ export class L1L3Bridger extends Erc20L1L3Bridger { ) } - await this.checkL2Network(l2Provider) - await this.checkL3Network(l3Provider) + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) const firstLegStatus = (await l1l2Messages[0].waitForStatus()).status @@ -623,7 +641,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { } } -class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { +export class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -644,9 +662,9 @@ class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { l2Provider: Provider, l3Provider: Provider ): Promise { - await this.checkL1Network(l1Signer) - await this.checkL2Network(l2Provider) - await this.checkL3Network(l3Provider) + await this._checkL1Network(l1Signer) + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) const populatedGasParams = await this._populateGasParams( params, @@ -745,87 +763,6 @@ class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { } } -// for reference -// interface IL1L3Bridger { -// getApproveTokenRequest( -// params: TokenApproveParams -// ): Promise>> - -// approveToken( -// params: TokenApproveParams, -// l1Signer: Signer -// ): Promise - -// getDepositRequest( -// params: DepositRequestParams, -// l1Provider: Provider, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise>> - -// deposit( -// params: DepositRequestParams, -// l1Signer: Signer, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// // waits for deposit to arrive on L3 -// waitForDeposit( -// depositTxReceipt: L1TransactionReceipt, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// getApproveTokenRequestUsingRelayer( -// params: TokenApproveParams, -// l1Provider: Provider -// ): Promise>> - -// getDepositRequestUsingRelayer( -// params: DepositRequestParams, -// l1Signer: Signer, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// depositUsingRelayer( -// params: DepositRequestParams, -// l1Signer: Signer, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// waitForDepositUsingRelayer( -// depositResult: DepositUsingRelayerResult, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// getDepositEthRequest( -// params: DepositEthRequestParams, -// l1Provider: Provider, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise>> - -// depositEth( -// params: DepositEthRequestParams, -// l1Signer: Signer, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// // waits for deposit to arrive on L3 -// waitForDepositEth( -// depositTxReceipt: L1TransactionReceipt, -// l2Provider: Provider, -// l3Provider: Provider -// ): Promise - -// // static -// relayDeposit( -// relayerInfo: RelayerInfo, -// l2Signer: Signer -// ): Promise -// } +export class EthL1L3Bridger extends BaseL1L3Bridger { + +} From 2c6fbf750799b445d72210977fa7851f034f2cbc Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:41:17 -0400 Subject: [PATCH 12/80] rename some types --- src/index.ts | 2 +- src/lib/assetBridger/l1l3Bridger.ts | 74 +++++++++------------------ tests/integration/l1l3Bridger.test.ts | 6 +-- 3 files changed, 27 insertions(+), 55 deletions(-) diff --git a/src/index.ts b/src/index.ts index 59ae314a04..bc67a62ebf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ /* eslint-env node */ 'use strict' -export { L1L3Bridger } from './lib/assetBridger/l1l3Bridger' +export { Erc20L1L3Bridger } from './lib/assetBridger/l1l3Bridger' export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 23dbfecf6c..5f688fabfd 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -38,35 +38,7 @@ import { L1ToL2MessageStatus } from '../message/L1ToL2Message' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' -/* -API: - - -fromProvider -DONE: checkL1Network (and L2 and L3) - -deposit -getDepositRequest - -DONE: getL1L2GatewayAddress (and L2L3) - -approveToken -DONE (untested): getApproveTokenRequest - -DONE: getL1TokenContract (L2 and L3 too) - -DONE: getL2ERC20Address (L3) - -DONE: l1TokenIsDisabled (L2) - - -for gas estimation: - if the l1l2 gateway is default, use hardcoded gas limit and calldata size - if it is custom, then throw if user does not provide a specific gas limit and calldata size - do the same for the l2l3 gateway -*/ - -export enum TeleportationLeg { +export enum Erc20TeleportationLeg { BridgeToL2, CallL2ForwarderFactory, BridgeToL3, @@ -90,7 +62,7 @@ export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { l3GasPrice: BigNumber } -export interface DepositRequestParams { +export interface Erc20DepositRequestParams { erc20L1Address: string to: string amount: BigNumberish @@ -101,7 +73,7 @@ export interface DepositRequestParams { } } -export interface DepositEthRequestParams { +export interface EthDepositRequestParams { to: string amount: BigNumberish overrides?: { @@ -110,9 +82,9 @@ export interface DepositEthRequestParams { } // if using relayer and leg 1 times out, failedLegStatus will be undefined -export interface WaitForDepositResult { +export interface WaitForErc20DepositResult { success: boolean - failedLeg?: TeleportationLeg + failedLeg?: Erc20TeleportationLeg failedLegStatus?: Exclude } @@ -126,12 +98,12 @@ export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number } -export type DepositRequestUsingRelayerResult = { +export type RelayedErc20DepositRequestResult = { txRequest: L1ToL2TransactionRequest relayerInfo: RelayerInfo } -export type DepositUsingRelayerResult = { +export type RelayedErc20DepositResult = { tx: L1ContractCallTransaction relayerInfo: RelayerInfo } @@ -200,7 +172,7 @@ class BaseL1L3Bridger { } } -class Erc20L1L3Bridger extends BaseL1L3Bridger { +class BaseErc20L1L3Bridger extends BaseL1L3Bridger { public readonly teleporterAddresses: TeleporterAddresses // todo: tune these @@ -441,7 +413,7 @@ class Erc20L1L3Bridger extends BaseL1L3Bridger { } protected async _populateGasParams( - params: DepositRequestParams, + params: Erc20DepositRequestParams, l1Provider: Provider, l2Provider: Provider, l3Provider: Provider @@ -489,7 +461,7 @@ class Erc20L1L3Bridger extends BaseL1L3Bridger { } } -export class L1L3Bridger extends Erc20L1L3Bridger { +export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to approve tokens for teleportation. * The tokens will be approved for the Teleporter on L1. @@ -524,7 +496,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { } public async getDepositRequest( - params: DepositRequestParams, + params: Erc20DepositRequestParams, l1Provider: Provider, l2Provider: Provider, l3Provider: Provider @@ -570,7 +542,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { } public async deposit( - params: DepositRequestParams, + params: Erc20DepositRequestParams, l1Signer: Signer, l2Provider: Provider, l3Provider: Provider @@ -591,7 +563,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { depositTxReceipt: L1TransactionReceipt, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { if (depositTxReceipt.to !== this.teleporterAddresses.l1Teleporter) { throw new ArbSdkError( `Transaction receipt is not for the teleporter: ${depositTxReceipt.to}` @@ -608,13 +580,13 @@ export class L1L3Bridger extends Erc20L1L3Bridger { if (firstLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: TeleportationLeg.BridgeToL2, + failedLeg: Erc20TeleportationLeg.BridgeToL2, failedLegStatus: firstLegStatus, } } else if (secondLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: TeleportationLeg.CallL2ForwarderFactory, + failedLeg: Erc20TeleportationLeg.CallL2ForwarderFactory, failedLegStatus: secondLegStatus, } } @@ -630,7 +602,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { if (thirdLegStatus != L1ToL2MessageStatus.REDEEMED) { return { success: false, - failedLeg: TeleportationLeg.BridgeToL3, + failedLeg: Erc20TeleportationLeg.BridgeToL3, failedLegStatus: thirdLegStatus, } } @@ -641,7 +613,7 @@ export class L1L3Bridger extends Erc20L1L3Bridger { } } -export class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { +export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -657,11 +629,11 @@ export class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { } public async getDepositRequest( - params: DepositRequestParams, + params: Erc20DepositRequestParams, l1Signer: Signer, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { await this._checkL1Network(l1Signer) await this._checkL2Network(l2Provider) await this._checkL3Network(l3Provider) @@ -738,19 +710,19 @@ export class L1L3BridgerUsingRelayer extends Erc20L1L3Bridger { } public async deposit( - params: DepositRequestParams, + params: Erc20DepositRequestParams, l1Signer: Signer, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { throw new Error('Not implemented') } public async waitForDeposit( - depositResult: DepositUsingRelayerResult, + depositResult: RelayedErc20DepositResult, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { throw new Error('Not implemented') } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index d3af9c0f40..c73b2ecbc3 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,5 +1,5 @@ import { testSetup } from '../../scripts/testSetup' -import { L1L3Bridger } from '../../src' +import { Erc20L1L3Bridger } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' import { MockToken } from '../../src/lib/abi/MockToken' @@ -11,7 +11,7 @@ type Unwrap = T extends Promise ? U : T describe('Teleporter', () => { let setup: Unwrap> - let l1l3Bridger: L1L3Bridger + let l1l3Bridger: Erc20L1L3Bridger let l1Token: MockToken before(async function () { @@ -45,7 +45,7 @@ describe('Teleporter', () => { l2ForwarderFactory, } - l1l3Bridger = new L1L3Bridger(setup.l3Network) + l1l3Bridger = new Erc20L1L3Bridger(setup.l3Network) // deploy the mock token l1Token = await new MockToken__factory(setup.l1Signer).deploy( From d121bbc1f01806afe410787cd342743e81fb8ff7 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:24:44 -0400 Subject: [PATCH 13/80] reorganize tests --- tests/integration/l1l3Bridger.test.ts | 212 ++++++++++++++------------ 1 file changed, 112 insertions(+), 100 deletions(-) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index c73b2ecbc3..75910f9401 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -9,115 +9,127 @@ import { ethers } from 'ethers' type Unwrap = T extends Promise ? U : T -describe('Teleporter', () => { +describe('L1 to L3 Bridging', () => { let setup: Unwrap> - let l1l3Bridger: Erc20L1L3Bridger - let l1Token: MockToken + // setup for all test cases before(async function () { await skipIfMainnet(this) setup = await testSetup() // fund signers on L1 and L2 - await fundL1(setup.l1Signer, ethers.utils.parseEther('1')) - await fundL2(setup.l2Signer, ethers.utils.parseEther('1')) - - // deploy teleporter contracts (todo: this should maybe be done in gen:network in the future) - - const l2ContractsDeployer = await new L2ForwarderContractsDeployer__factory( - setup.l2Signer - ).deploy() - await l2ContractsDeployer.deployed() - - const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() - const l2ForwarderFactory = await l2ContractsDeployer.factory() - - const l1Teleporter = await new Teleporter__factory(setup.l1Signer).deploy( - l2ForwarderFactory, - l2ForwarderImplAddr - ) - await l1Teleporter.deployed() - - // set the teleporter on the l2Network - setup.l2Network.teleporterAddresses = { - l1Teleporter: l1Teleporter.address, - l2ForwarderFactory, - } - - l1l3Bridger = new Erc20L1L3Bridger(setup.l3Network) - - // deploy the mock token - l1Token = await new MockToken__factory(setup.l1Signer).deploy( - 'MOCK', - 'MOCK', - ethers.utils.parseEther('100'), - await setup.l1Signer.getAddress() - ) - await l1Token.deployed() - - // approve the teleporter - await ( - await l1Token - .connect(setup.l1Signer) - .approve( - setup.l2Network.teleporterAddresses!.l1Teleporter, - ethers.utils.parseEther('100') - ) - ).wait() + await fundL1(setup.l1Signer, ethers.utils.parseEther('100')) + await fundL2(setup.l2Signer, ethers.utils.parseEther('100')) }) - it('L1 Teleporter should move tokens to L3 with correct parameters', async () => { - const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) - - // todo: rename to teleport? - const depositTx = await l1l3Bridger.deposit( - { - erc20L1Address: l1Token.address, - to: l3Recipient, - amount: ethers.utils.parseEther('1'), - }, - setup.l1Signer, - setup.l2Signer.provider!, - setup.l3Signer.provider! - ) - - const depositReceipt = await depositTx.wait() - - // wait for l1 l2 messages to redeem - const depositWaitResult = await l1l3Bridger.waitForDeposit( - depositReceipt, - setup.l2Signer.provider!, - setup.l3Signer.provider! - ) - - if ( - depositWaitResult.success !== true || - depositWaitResult.failedLeg !== undefined || - depositWaitResult.failedLegStatus !== undefined - ) { - throw new Error('Deposit failed') - } - - // make sure the tokens have landed in the right place - const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( - l1Token.address, - setup.l1Signer.provider!, - setup.l2Signer.provider! - ) - const l3Token = l1l3Bridger.getL3TokenContract( - setup.l3Signer.provider!, - l3TokenAddr - ) - - const l3Balance = await l3Token.balanceOf(l3Recipient) - - if (!l3Balance.eq(ethers.utils.parseEther('1').sub(1))) { - throw new Error('L3 balance is incorrect') - } + describe("ERC20 Bridging", () => { + let l1Token: MockToken + + // deploy teleporter contracts and mock token + before(async function () { + // deploy teleporter contracts (todo: this should maybe be done in gen:network in the future) + const l2ContractsDeployer = await new L2ForwarderContractsDeployer__factory( + setup.l2Signer + ).deploy() + await l2ContractsDeployer.deployed() + + const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() + const l2ForwarderFactory = await l2ContractsDeployer.factory() + + const l1Teleporter = await new Teleporter__factory(setup.l1Signer).deploy( + l2ForwarderFactory, + l2ForwarderImplAddr + ) + await l1Teleporter.deployed() + + // set the teleporter on the l2Network + setup.l2Network.teleporterAddresses = { + l1Teleporter: l1Teleporter.address, + l2ForwarderFactory, + } + + // deploy the mock token + l1Token = await new MockToken__factory(setup.l1Signer).deploy( + 'MOCK', + 'MOCK', + ethers.utils.parseEther('100'), + await setup.l1Signer.getAddress() + ) + await l1Token.deployed() + }) + + describe("Erc20L1L3Bridger", () => { + let l1l3Bridger: Erc20L1L3Bridger + + // create the bridger and approve the teleporter + before(async () => { + l1l3Bridger = new Erc20L1L3Bridger(setup.l3Network) + + // approve the teleporter + await ( + await l1Token + .connect(setup.l1Signer) + .approve( + setup.l2Network.teleporterAddresses!.l1Teleporter, + ethers.utils.parseEther('100') + ) + ).wait() + }) + + // should throw if gas overrides not passed when using non default gateway + // test relayer stuff + // don't need to test rescue here i think + + it('happy path', async () => { + const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + + const depositTx = await l1l3Bridger.deposit( + { + erc20L1Address: l1Token.address, + to: l3Recipient, + amount: ethers.utils.parseEther('1'), + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + const depositReceipt = await depositTx.wait() + + // wait for l1 l2 messages to redeem + const depositWaitResult = await l1l3Bridger.waitForDeposit( + depositReceipt, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + // just checking .success should be sufficient normally + if ( + depositWaitResult.success !== true || + depositWaitResult.failedLeg !== undefined || + depositWaitResult.failedLegStatus !== undefined + ) { + throw new Error('Deposit failed') + } + + // make sure the tokens have landed in the right place + const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( + l1Token.address, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) + const l3Token = l1l3Bridger.getL3TokenContract( + setup.l3Signer.provider!, + l3TokenAddr + ) + + const l3Balance = await l3Token.balanceOf(l3Recipient) + + if (!l3Balance.eq(ethers.utils.parseEther('1').sub(1))) { + throw new Error('L3 balance is incorrect') + } + }) + }) }) - - // should throw if gas overrides not passed when using non default gateway - // test relayer stuff - // don't need to test rescue here i think }) From 772ca7e378d9a17b5c80eda7bc24625788056631 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:25:06 -0400 Subject: [PATCH 14/80] untested eth bridger --- src/lib/assetBridger/l1l3Bridger.ts | 125 ++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 5f688fabfd..33a0dca404 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -32,11 +32,15 @@ import { AbiCoder } from 'ethers/lib/utils' import { L1ContractCallTransaction, L1ContractCallTransactionReceipt, + L1EthDepositTransaction, + L1EthDepositTransactionReceipt, L1TransactionReceipt, } from '../message/L1Transaction' import { L1ToL2MessageStatus } from '../message/L1ToL2Message' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' +import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' +import { Address } from '../dataEntities/address' export enum Erc20TeleportationLeg { BridgeToL2, @@ -64,7 +68,7 @@ export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { export interface Erc20DepositRequestParams { erc20L1Address: string - to: string + to: string // todo: make optional, default to signer's address amount: BigNumberish overrides?: { gasPricePercentIncrease?: BigNumber @@ -74,10 +78,15 @@ export interface Erc20DepositRequestParams { } export interface EthDepositRequestParams { - to: string amount: BigNumberish + destinationOverrides?: { + l3DestinationAddress: string, + l2RefundAddress: string, + } overrides?: { gasPricePercentIncrease?: BigNumber + l2RetryableGasLimit?: BigNumber + l3RetryableGasLimit?: BigNumber } } @@ -124,12 +133,6 @@ class BaseL1L3Bridger { ) } - if (!l2Network.teleporterAddresses) { - throw new ArbSdkError( - `L2 network ${l2Network.name} does not have a teleporter` - ) - } - const l1Network = l1Networks[l2Network.partnerChainID] if (!l1Network) { throw new ArbSdkError( @@ -735,6 +738,112 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } +export type EthDepositStatus = { + l2RetryableStatus: L1ToL2MessageStatus + l3RetryableStatus: L1ToL2MessageStatus +} + export class EthL1L3Bridger extends BaseL1L3Bridger { + // getDepositRequest + // deposit + // some status check function + + public async getDepositRequest( + params: EthDepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise { + await this._checkL1Network(l1Signer) + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) + + const l1Address = await l1Signer.getAddress() + + const l3DestinationAddress = params.destinationOverrides?.l3DestinationAddress || l1Address + const l2RefundAddress = params.destinationOverrides?.l2RefundAddress || l1Address + + const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( + { + to: l3DestinationAddress, + data: '0x', + from: new Address(l1Address).applyAlias().value, + l2CallValue: BigNumber.from(params.amount), + excessFeeRefundAddress: l3DestinationAddress, + callValueRefundAddress: l3DestinationAddress + }, + l2Provider, + l3Provider + ) + + const l2TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( + { + from: l1Address, + to: l3TicketRequest.txRequest.to, + l2CallValue: BigNumber.from(l3TicketRequest.txRequest.value), + data: ethers.utils.hexlify(l3TicketRequest.txRequest.data), + excessFeeRefundAddress: l2RefundAddress, + callValueRefundAddress: l2RefundAddress, + }, + l1Signer.provider!, + l2Provider + ) + + return l2TicketRequest + } + + public async deposit( + params: EthDepositRequestParams, + l1Signer: Signer, + l2Provider: Provider, + l3Provider: Provider + ): Promise { + const txRequest = await this.getDepositRequest( + params, + l1Signer, + l2Provider, + l3Provider + ) + + return L1TransactionReceipt.monkeyPatchEthDepositWait( + await l1Signer.sendTransaction(txRequest.txRequest) + ) + } + + // don't pass a tx that isn't an l1 to l3 eth deposit + public async getDepositStatus( + l1TxReceipt: L1EthDepositTransactionReceipt, + l2Provider: Provider, + l3Provider: Provider + ): Promise { + const l1l2Message = (await l1TxReceipt.getL1ToL2Messages(l2Provider))[0] + + const l1l2Redeem = await l1l2Message.getSuccessfulRedeem() + + if (l1l2Redeem.status != L1ToL2MessageStatus.REDEEMED) { + return { + l2RetryableStatus: l1l2Redeem.status, + l3RetryableStatus: L1ToL2MessageStatus.NOT_YET_CREATED, + } + } + + const l2l3Message = ( + await new L1EthDepositTransactionReceipt(l1l2Redeem.l2TxReceipt).getL1ToL2Messages( + l3Provider + ) + )[0] + + if (l2l3Message === undefined) { + throw new ArbSdkError( + `L2 to L3 message not found` + ) + } + + const l2l3Redeem = await l2l3Message.getSuccessfulRedeem() + return { + l2RetryableStatus: l1l2Redeem.status, + l3RetryableStatus: l2l3Redeem.status, + } + } } From 22b49560cd17e0752a51444a17b042d9645855c2 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:45:41 -0400 Subject: [PATCH 15/80] happy path eth bridge test --- src/lib/assetBridger/l1l3Bridger.ts | 36 +++++----- tests/integration/l1l3Bridger.test.ts | 96 +++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 33a0dca404..211d314589 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -80,8 +80,8 @@ export interface Erc20DepositRequestParams { export interface EthDepositRequestParams { amount: BigNumberish destinationOverrides?: { - l3DestinationAddress: string, - l2RefundAddress: string, + l3DestinationAddress: string + l2RefundAddress: string } overrides?: { gasPricePercentIncrease?: BigNumber @@ -426,7 +426,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { let manualGasParams = params.overrides.manualGasParams if (!manualGasParams) { // make sure both gateways are default - if (!(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider))) { + if ( + !(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider)) + ) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` ) @@ -744,10 +746,6 @@ export type EthDepositStatus = { } export class EthL1L3Bridger extends BaseL1L3Bridger { - // getDepositRequest - // deposit - // some status check function - public async getDepositRequest( params: EthDepositRequestParams, l1Signer: Signer, @@ -760,8 +758,10 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l1Address = await l1Signer.getAddress() - const l3DestinationAddress = params.destinationOverrides?.l3DestinationAddress || l1Address - const l2RefundAddress = params.destinationOverrides?.l2RefundAddress || l1Address + const l3DestinationAddress = + params.destinationOverrides?.l3DestinationAddress || l1Address + const l2RefundAddress = + params.destinationOverrides?.l2RefundAddress || l1Address const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( { @@ -770,9 +770,9 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { from: new Address(l1Address).applyAlias().value, l2CallValue: BigNumber.from(params.amount), excessFeeRefundAddress: l3DestinationAddress, - callValueRefundAddress: l3DestinationAddress - }, - l2Provider, + callValueRefundAddress: l3DestinationAddress, + }, + l2Provider, l3Provider ) @@ -788,7 +788,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { l1Signer.provider!, l2Provider ) - + return l2TicketRequest } @@ -828,15 +828,13 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { } const l2l3Message = ( - await new L1EthDepositTransactionReceipt(l1l2Redeem.l2TxReceipt).getL1ToL2Messages( - l3Provider - ) + await new L1EthDepositTransactionReceipt( + l1l2Redeem.l2TxReceipt + ).getL1ToL2Messages(l3Provider) )[0] if (l2l3Message === undefined) { - throw new ArbSdkError( - `L2 to L3 message not found` - ) + throw new ArbSdkError(`L2 to L3 message not found`) } const l2l3Redeem = await l2l3Message.getSuccessfulRedeem() diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 75910f9401..ea24a06cc8 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,14 +1,37 @@ import { testSetup } from '../../scripts/testSetup' -import { Erc20L1L3Bridger } from '../../src' +import { Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' import { MockToken } from '../../src/lib/abi/MockToken' import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { ethers } from 'ethers' +import { EthL1L3Bridger } from '../../src/lib/assetBridger/l1l3Bridger' +import { expect } from 'chai' type Unwrap = T extends Promise ? U : T +function poll( + fn: () => Promise, + pollInterval: number +): Promise { + return new Promise((resolve, reject) => { + const interval = setInterval(async () => { + try { + const result = await fn() + if (result === true) { + clearInterval(interval) + resolve(true) + } + } catch (e) { + clearInterval(interval) + reject(e) + } + }, pollInterval) + }) +} + + describe('L1 to L3 Bridging', () => { let setup: Unwrap> @@ -23,15 +46,62 @@ describe('L1 to L3 Bridging', () => { await fundL2(setup.l2Signer, ethers.utils.parseEther('100')) }) - describe("ERC20 Bridging", () => { + describe('ETH Bridging', () => { + describe('EthL1L3Bridger', () => { + let l1l3Bridger: EthL1L3Bridger + + before(() => { + l1l3Bridger = new EthL1L3Bridger(setup.l3Network) + }) + + // send some eth to L3 with custom l3 recipient and l2 refund address + // makes sure that appropriate amounts land at the right places + it('happy path', async () => { + const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + const l2RefundAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + + const depositTx = await l1l3Bridger.deposit( + { + amount: ethers.utils.parseEther('1'), + destinationOverrides: { + l3DestinationAddress: l3Recipient, + l2RefundAddress: l2RefundAddress + } + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + const depositReceipt = await depositTx.wait() + + // poll status + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus(depositReceipt, setup.l2Signer.provider!, setup.l3Signer.provider!) + if (status.l2RetryableStatus === L1ToL2MessageStatus.REDEEMED && status.l3RetryableStatus === L1ToL2MessageStatus.REDEEMED) { + return true + } + return false + }, 1000) + + // check eth balances + const l3Balance = await setup.l3Signer.provider!.getBalance(l3Recipient) + expect(l3Balance.gt(ethers.utils.parseEther('1'))).to.be.true; + + const l2Balance = await setup.l2Signer.provider!.getBalance(l2RefundAddress) + expect(l2Balance.gt(ethers.utils.parseEther('0'))).to.be.true; + }) + }) + }) + + describe('ERC20 Bridging', () => { let l1Token: MockToken // deploy teleporter contracts and mock token before(async function () { // deploy teleporter contracts (todo: this should maybe be done in gen:network in the future) - const l2ContractsDeployer = await new L2ForwarderContractsDeployer__factory( - setup.l2Signer - ).deploy() + const l2ContractsDeployer = + await new L2ForwarderContractsDeployer__factory(setup.l2Signer).deploy() await l2ContractsDeployer.deployed() const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() @@ -59,7 +129,7 @@ describe('L1 to L3 Bridging', () => { await l1Token.deployed() }) - describe("Erc20L1L3Bridger", () => { + describe('Erc20L1L3Bridger', () => { let l1l3Bridger: Erc20L1L3Bridger // create the bridger and approve the teleporter @@ -83,7 +153,7 @@ describe('L1 to L3 Bridging', () => { it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) - + const depositTx = await l1l3Bridger.deposit( { erc20L1Address: l1Token.address, @@ -94,16 +164,16 @@ describe('L1 to L3 Bridging', () => { setup.l2Signer.provider!, setup.l3Signer.provider! ) - + const depositReceipt = await depositTx.wait() - + // wait for l1 l2 messages to redeem const depositWaitResult = await l1l3Bridger.waitForDeposit( depositReceipt, setup.l2Signer.provider!, setup.l3Signer.provider! ) - + // just checking .success should be sufficient normally if ( depositWaitResult.success !== true || @@ -112,7 +182,7 @@ describe('L1 to L3 Bridging', () => { ) { throw new Error('Deposit failed') } - + // make sure the tokens have landed in the right place const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( l1Token.address, @@ -123,9 +193,9 @@ describe('L1 to L3 Bridging', () => { setup.l3Signer.provider!, l3TokenAddr ) - + const l3Balance = await l3Token.balanceOf(l3Recipient) - + if (!l3Balance.eq(ethers.utils.parseEther('1').sub(1))) { throw new Error('L3 balance is incorrect') } From a36a232f8478a73eb7308b257ccc55994ad0bdb4 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:24:20 -0400 Subject: [PATCH 16/80] deposit state for erc20 bridger --- src/lib/assetBridger/l1l3Bridger.ts | 106 ++++++++++++++------------ tests/integration/l1l3Bridger.test.ts | 52 ++++++------- 2 files changed, 81 insertions(+), 77 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 211d314589..a1ea64636d 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -90,17 +90,18 @@ export interface EthDepositRequestParams { } } -// if using relayer and leg 1 times out, failedLegStatus will be undefined -export interface WaitForErc20DepositResult { - success: boolean - failedLeg?: Erc20TeleportationLeg - failedLegStatus?: Exclude +export interface Erc20DepositStatus { + bridgeToL2Status: L1ToL2MessageStatus + callToL2ForwarderStatus: L1ToL2MessageStatus + bridgeToL3Status: L1ToL2MessageStatus + completed: boolean } -export interface WaitForEthDepositResult { - success: boolean - failedLeg?: EthTeleportationLeg - failedLegStatus?: Exclude +export interface RelayedErc20DepositStatus { + bridgeToL2Status: L1ToL2MessageStatus + l2ForwarderCalled: boolean + bridgeToL3Status: L1ToL2MessageStatus + completed: boolean } export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { @@ -117,6 +118,12 @@ export type RelayedErc20DepositResult = { relayerInfo: RelayerInfo } +export type EthDepositStatus = { + l2RetryableStatus: L1ToL2MessageStatus + l3RetryableStatus: L1ToL2MessageStatus + completed: boolean +} + class BaseL1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network @@ -564,56 +571,57 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { ) } - public async waitForDeposit( - depositTxReceipt: L1TransactionReceipt, + /** + * Get the status of a deposit given an L1 tx receipt. + * + * Note: This function does not verify that the tx is actually a deposit tx. + * + * Note: It is possible that two identical deposits are made where the first leg of one deposit has redeemed + * as well as the second/third legs of the other deposit. In this case, neither deposit will be marked as completed. + * In other words, a deposit is only marked as completed if all three legs have been redeemed. + * @param depositTxReceipt + * @param l2Provider + * @param l3Provider + * @returns + */ + public async getDepositStatus( + depositTxReceipt: L1ContractCallTransactionReceipt, l2Provider: Provider, l3Provider: Provider - ): Promise { - if (depositTxReceipt.to !== this.teleporterAddresses.l1Teleporter) { - throw new ArbSdkError( - `Transaction receipt is not for the teleporter: ${depositTxReceipt.to}` - ) - } - + ): Promise { await this._checkL2Network(l2Provider) await this._checkL3Network(l3Provider) const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) - const firstLegStatus = (await l1l2Messages[0].waitForStatus()).status - const secondLegStatus = (await l1l2Messages[1].waitForStatus()).status + const firstLegRedeem = await l1l2Messages[0].getSuccessfulRedeem() + const secondLegRedeem = await l1l2Messages[1].getSuccessfulRedeem() - if (firstLegStatus != L1ToL2MessageStatus.REDEEMED) { + // if second leg is not redeemed, the third must not be created + if (secondLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { return { - success: false, - failedLeg: Erc20TeleportationLeg.BridgeToL2, - failedLegStatus: firstLegStatus, - } - } else if (secondLegStatus != L1ToL2MessageStatus.REDEEMED) { - return { - success: false, - failedLeg: Erc20TeleportationLeg.CallL2ForwarderFactory, - failedLegStatus: secondLegStatus, + bridgeToL2Status: firstLegRedeem.status, + callToL2ForwarderStatus: secondLegRedeem.status, + bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + completed: false, } } + // otherwise, third leg must be created const thirdLegMessage = ( - await new L1ContractCallTransactionReceipt( - (await l1l2Messages[1].getAutoRedeemAttempt())! + await new L1TransactionReceipt( + secondLegRedeem.l2TxReceipt ).getL1ToL2Messages(l3Provider) )[0] - const thirdLegStatus = (await thirdLegMessage.waitForStatus()).status - - if (thirdLegStatus != L1ToL2MessageStatus.REDEEMED) { - return { - success: false, - failedLeg: Erc20TeleportationLeg.BridgeToL3, - failedLegStatus: thirdLegStatus, - } - } + const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() return { - success: true, + bridgeToL2Status: firstLegRedeem.status, + callToL2ForwarderStatus: secondLegRedeem.status, + bridgeToL3Status: thirdLegRedeem.status, + completed: + firstLegRedeem.status === L1ToL2MessageStatus.REDEEMED && + thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, } } } @@ -723,11 +731,12 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { throw new Error('Not implemented') } - public async waitForDeposit( - depositResult: RelayedErc20DepositResult, + public async getDepositStatus( + depositTxReceipt: L1ContractCallTransactionReceipt, + relayerInfo: RelayerInfo, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { throw new Error('Not implemented') } @@ -740,11 +749,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } -export type EthDepositStatus = { - l2RetryableStatus: L1ToL2MessageStatus - l3RetryableStatus: L1ToL2MessageStatus -} - export class EthL1L3Bridger extends BaseL1L3Bridger { public async getDepositRequest( params: EthDepositRequestParams, @@ -824,6 +828,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { return { l2RetryableStatus: l1l2Redeem.status, l3RetryableStatus: L1ToL2MessageStatus.NOT_YET_CREATED, + completed: false, } } @@ -842,6 +847,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { return { l2RetryableStatus: l1l2Redeem.status, l3RetryableStatus: l2l3Redeem.status, + completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, } } } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index ea24a06cc8..0ec662bf4c 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -31,7 +31,6 @@ function poll( }) } - describe('L1 to L3 Bridging', () => { let setup: Unwrap> @@ -58,15 +57,17 @@ describe('L1 to L3 Bridging', () => { // makes sure that appropriate amounts land at the right places it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) - const l2RefundAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + const l2RefundAddress = ethers.utils.hexlify( + ethers.utils.randomBytes(20) + ) const depositTx = await l1l3Bridger.deposit( { amount: ethers.utils.parseEther('1'), destinationOverrides: { l3DestinationAddress: l3Recipient, - l2RefundAddress: l2RefundAddress - } + l2RefundAddress: l2RefundAddress, + }, }, setup.l1Signer, setup.l2Signer.provider!, @@ -77,19 +78,22 @@ describe('L1 to L3 Bridging', () => { // poll status await poll(async () => { - const status = await l1l3Bridger.getDepositStatus(depositReceipt, setup.l2Signer.provider!, setup.l3Signer.provider!) - if (status.l2RetryableStatus === L1ToL2MessageStatus.REDEEMED && status.l3RetryableStatus === L1ToL2MessageStatus.REDEEMED) { - return true - } - return false + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + return status.completed }, 1000) // check eth balances const l3Balance = await setup.l3Signer.provider!.getBalance(l3Recipient) - expect(l3Balance.gt(ethers.utils.parseEther('1'))).to.be.true; + expect(l3Balance.gt(ethers.utils.parseEther('1'))).to.be.true - const l2Balance = await setup.l2Signer.provider!.getBalance(l2RefundAddress) - expect(l2Balance.gt(ethers.utils.parseEther('0'))).to.be.true; + const l2Balance = await setup.l2Signer.provider!.getBalance( + l2RefundAddress + ) + expect(l2Balance.gt(ethers.utils.parseEther('0'))).to.be.true }) }) }) @@ -167,21 +171,15 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositTx.wait() - // wait for l1 l2 messages to redeem - const depositWaitResult = await l1l3Bridger.waitForDeposit( - depositReceipt, - setup.l2Signer.provider!, - setup.l3Signer.provider! - ) - - // just checking .success should be sufficient normally - if ( - depositWaitResult.success !== true || - depositWaitResult.failedLeg !== undefined || - depositWaitResult.failedLegStatus !== undefined - ) { - throw new Error('Deposit failed') - } + // poll status + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + return status.completed + }, 1000) // make sure the tokens have landed in the right place const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( From d9dafb5a17706955d871baeb3c78f4153ea9da4e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:43:47 -0400 Subject: [PATCH 17/80] add note about race condition --- src/lib/assetBridger/l1l3Bridger.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index a1ea64636d..f2704d0c81 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -731,6 +731,21 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { throw new Error('Not implemented') } + /** + * Get the status of a deposit given an L1 tx receipt and relayer info. + * + * Note: This function does not verify that the tx is actually a deposit tx. + * + * Note: It is possible that two or more identical deposits are made where both token deposits to L2 have succeeded, + * but < 2 of the relayer calls have been made. In this case, neither deposit will be marked as completed. + * If one of the relayer calls has been made, both will be marked as not completed until the second relayer call has been made, + * after which BOTH will be marked as completed. + * + * @param depositTxReceipt + * @param relayerInfo + * @param l2Provider + * @param l3Provider + */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, relayerInfo: RelayerInfo, From 26f875875dab11e88c5bf2e78ab08ad346f1360b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:11:23 -0400 Subject: [PATCH 18/80] add randomNonce --- src/lib/assetBridger/l1l3Bridger.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index f2704d0c81..aa62feabc7 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -530,12 +530,15 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { ) const calldata = teleporter.interface.encodeFunctionData('teleport', [ - params.erc20L1Address, - this.l2Network.tokenBridge.l1GatewayRouter, - this.l3Network.tokenBridge.l1GatewayRouter, - params.to, - params.amount, - gasParams, + { + l1Token: params.erc20L1Address, + l1l2Router: this.l2Network.tokenBridge.l1GatewayRouter, + l2l3Router: this.l3Network.tokenBridge.l1GatewayRouter, + to: params.to, + amount: params.amount, + gasParams, + randomNonce: ethers.utils.randomBytes(32), + }, ]) const l1GasPrice = await l1Provider.getGasPrice() @@ -681,6 +684,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { gasLimit: populatedGasParams.l2l3TokenBridgeGasLimit, gasPrice: populatedGasParams.l3GasPrice, relayerPayment, + randomNonce: ethers.utils.randomBytes(32), } const l2ForwarderAddress = await teleporter.l2ForwarderAddress( @@ -736,15 +740,10 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { * * Note: This function does not verify that the tx is actually a deposit tx. * - * Note: It is possible that two or more identical deposits are made where both token deposits to L2 have succeeded, - * but < 2 of the relayer calls have been made. In this case, neither deposit will be marked as completed. - * If one of the relayer calls has been made, both will be marked as not completed until the second relayer call has been made, - * after which BOTH will be marked as completed. - * - * @param depositTxReceipt - * @param relayerInfo - * @param l2Provider - * @param l3Provider + * @param depositTxReceipt + * @param relayerInfo + * @param l2Provider + * @param l3Provider */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, From 341d4f8017254b937f862157ffd9f9dfaaaae95d Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 5 Oct 2023 20:25:19 -0400 Subject: [PATCH 19/80] untested relayed erc20 bridger --- src/lib/assetBridger/l1l3Bridger.ts | 172 +++++++++++++++++++++----- tests/integration/l1l3Bridger.test.ts | 2 +- 2 files changed, 144 insertions(+), 30 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index aa62feabc7..08e3e09eb4 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,3 +1,4 @@ +import { JsonRpcProvider } from "@ethersproject/providers" import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' import { ArbSdkError } from '../dataEntities/errors' import { @@ -22,7 +23,9 @@ import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' import { BigNumber, BigNumberish, + Contract, ContractFactory, + Overrides, Signer, Wallet, ethers, @@ -41,6 +44,10 @@ import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' import { Address } from '../dataEntities/address' +import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' +import { EventFetcher } from '../utils/eventFetcher' +import { L2Forwarder__factory } from '../abi/factories/L2Forwarder__factory' +import { getFirstBlockForL1Block } from '../utils/lib' export enum Erc20TeleportationLeg { BridgeToL2, @@ -69,7 +76,7 @@ export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { export interface Erc20DepositRequestParams { erc20L1Address: string to: string // todo: make optional, default to signer's address - amount: BigNumberish + amount: BigNumber overrides?: { gasPricePercentIncrease?: BigNumber relayerPaymentPercentIncrease?: BigNumber @@ -97,6 +104,7 @@ export interface Erc20DepositStatus { completed: boolean } +// todo: change to L1ToL2MessageWaitResult and transaction receipt export interface RelayedErc20DepositStatus { bridgeToL2Status: L1ToL2MessageStatus l2ForwarderCalled: boolean @@ -332,7 +340,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * @param l1TokenAddr * @returns */ - public getL1TokenContract(l1Provider: Provider, l1TokenAddr: string): ERC20 { + public getL1TokenContract(l1TokenAddr: string, l1Provider: Provider): ERC20 { return ERC20__factory.connect(l1TokenAddr, l1Provider) } @@ -346,8 +354,8 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * @returns */ public getL2TokenContract( - l2Provider: Provider, - l2TokenAddr: string + l2TokenAddr: string, + l2Provider: Provider ): L2GatewayToken { return L2GatewayToken__factory.connect(l2TokenAddr, l2Provider) } @@ -362,10 +370,10 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * @returns */ public getL3TokenContract( - l3Provider: Provider, - l3TokenAddr: string + l3TokenAddr: string, + l3Provider: Provider ): L2GatewayToken { - return this.getL2TokenContract(l3Provider, l3TokenAddr) + return L2GatewayToken__factory.connect(l3TokenAddr, l3Provider) } /** @@ -579,9 +587,6 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { * * Note: This function does not verify that the tx is actually a deposit tx. * - * Note: It is possible that two identical deposits are made where the first leg of one deposit has redeemed - * as well as the second/third legs of the other deposit. In this case, neither deposit will be marked as completed. - * In other words, a deposit is only marked as completed if all three legs have been redeemed. * @param depositTxReceipt * @param l2Provider * @param l3Provider @@ -622,9 +627,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { bridgeToL2Status: firstLegRedeem.status, callToL2ForwarderStatus: secondLegRedeem.status, bridgeToL3Status: thirdLegRedeem.status, - completed: - firstLegRedeem.status === L1ToL2MessageStatus.REDEEMED && - thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, + completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, } } } @@ -634,14 +637,20 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Provider: Provider ): Promise>> { - throw new Error('Not implemented') + return new Erc20Bridger(this.l2Network).getApproveTokenRequest({ + ...params, + l1Provider, + }) } public async approveToken( params: TokenApproveParams, l1Signer: Signer ): Promise { - throw new Error('Not implemented') + return new Erc20Bridger(this.l2Network).approveToken({ + ...params, + l1Signer, + }) } public async getDepositRequest( @@ -661,11 +670,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) - const teleporter = Teleporter__factory.connect( - this.teleporterAddresses.l1Teleporter, - l1Signer - ) - const relayerPayment = this._percentIncrease( populatedGasParams.l2ForwarderFactoryGasLimit.mul( populatedGasParams.l2GasPrice @@ -687,13 +691,18 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { randomNonce: ethers.utils.randomBytes(32), } + const teleporter = Teleporter__factory.connect( + this.teleporterAddresses.l1Teleporter, + l1Signer + ) + const l2ForwarderAddress = await teleporter.l2ForwarderAddress( l2ForwarderParams ) - const erc20Bridger = new Erc20Bridger(this.l2Network) - - const tokenBridgeRequest = await erc20Bridger.getDepositRequest({ + const tokenBridgeRequest = await new Erc20Bridger( + this.l2Network + ).getDepositRequest({ amount: BigNumber.from(params.amount), l1Provider: l1Signer.provider!, l2Provider: l2Provider, @@ -704,7 +713,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { excessFeeRefundAddress: l2ForwarderAddress, }) - // add relayer payment and l3 retryable costs to value + // figure out how much extra ETH we should pass along through the token bridge const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, '0', // we don't care about L1 gas price, this will just set it to current gas price @@ -732,13 +741,25 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { l2Provider: Provider, l3Provider: Provider ): Promise { - throw new Error('Not implemented') + const depositRequest = await this.getDepositRequest( + params, + l1Signer, + l2Provider, + l3Provider + ) + + return { + tx: L1TransactionReceipt.monkeyPatchContractCallWait( + await l1Signer.sendTransaction(depositRequest.txRequest.txRequest) + ), + relayerInfo: depositRequest.relayerInfo, + } } /** * Get the status of a deposit given an L1 tx receipt and relayer info. * - * Note: This function does not verify that the tx is actually a deposit tx. + * Note: This function does not verify that the tx is actually a deposit tx, nor does it check the provider chain ids. * * @param depositTxReceipt * @param relayerInfo @@ -748,10 +769,55 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, relayerInfo: RelayerInfo, - l2Provider: Provider, + l2Provider: JsonRpcProvider, l3Provider: Provider ): Promise { - throw new Error('Not implemented') + const firstLegRedeem = await ( + await depositTxReceipt.getL1ToL2Messages(l2Provider) + )[0].getSuccessfulRedeem() + + if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { + return { + bridgeToL2Status: firstLegRedeem.status, + l2ForwarderCalled: false, + bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + completed: false, + } + } + + const l2ForwarderAddress = await L2ForwarderFactory__factory.connect( + this.teleporterAddresses.l2ForwarderFactory, + l2Provider + ).l2ForwarderAddress(relayerInfo) + + const bridgeToL3Event = await this._findBridgedToL3Event( + l2ForwarderAddress, + firstLegRedeem.l2TxReceipt.blockNumber, + l2Provider + ) + + if (bridgeToL3Event === undefined) { + return { + bridgeToL2Status: firstLegRedeem.status, + l2ForwarderCalled: false, + bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + completed: false, + } + } + + // get tx or receipt from the event + const tx = await l2Provider.getTransaction(bridgeToL3Event.transactionHash) + const receipt = new L1ContractCallTransactionReceipt(await tx.wait()) + const l2l3Redeem = await (( + await receipt.getL1ToL2Messages(l3Provider) + )[0].getSuccessfulRedeem()) + + return { + bridgeToL2Status: firstLegRedeem.status, + l2ForwarderCalled: true, + bridgeToL3Status: l2l3Redeem.status, + completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, + } } // takes: deposit tx hash, l2 forwarder params, l2 signer @@ -759,7 +825,55 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { relayerInfo: RelayerInfo, l2Signer: Signer ): Promise { - throw new Error('Not implemented') + const teleporterAddresses = l2Networks[relayerInfo.chainId].teleporterAddresses + + if (!teleporterAddresses) { + throw new ArbSdkError( + `L2 network ${l2Networks[relayerInfo.chainId].name} does not have teleporter contracts` + ) + } + + const l2ForwarderFactory = L2ForwarderFactory__factory.connect( + teleporterAddresses.l2ForwarderFactory, + l2Signer + ) + + return l2ForwarderFactory.callForwarder(relayerInfo) + } + + // todo: cache last toBlock and use it as fromBlock + // cache answer + // cache l1 -> l2 block + private async _findBridgedToL3Event( + l2ForwarderAddress: string, + fromL1Block: number, + l2Provider: JsonRpcProvider + ) { + const fromL2Block = await getFirstBlockForL1Block({ + provider: l2Provider, + forL1Block: fromL1Block + }) + + if (fromL2Block === undefined) { + throw new ArbSdkError(`Could not find L2 block for L1 block ${fromL1Block}`) + } + + const latest = await l2Provider.getBlockNumber() + const eventFetcher = new EventFetcher(l2Provider) + const events = ( + await eventFetcher.getEvents( + L2Forwarder__factory, + contract => + contract.filters.BridgedToL3(), + { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } + ) + ) + + if (events.length === 0) { + return undefined + } + + return events[0] } } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 0ec662bf4c..fd558e7afe 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -188,8 +188,8 @@ describe('L1 to L3 Bridging', () => { setup.l2Signer.provider! ) const l3Token = l1l3Bridger.getL3TokenContract( + l3TokenAddr, setup.l3Signer.provider!, - l3TokenAddr ) const l3Balance = await l3Token.balanceOf(l3Recipient) From 6ca5701c8053d6568278a0fead8df95b2196c811 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:06:55 -0400 Subject: [PATCH 20/80] relayed e2e happy path works --- src/lib/assetBridger/l1l3Bridger.ts | 71 ++++++++++-------- tests/integration/l1l3Bridger.test.ts | 104 ++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 39 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 08e3e09eb4..a8a75b5415 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -414,6 +414,13 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ) } + public l2ForwarderAddress( + params: L2ForwarderPredictor.L2ForwarderParamsStruct, + l2Provider: Provider + ): Promise { + return L2ForwarderFactory__factory.connect(this.teleporterAddresses.l2ForwarderFactory, l2Provider).l2ForwarderAddress(params) + } + private async _tokenIsDisabled( tokenAddress: string, gatewayRouterAddress: string, @@ -681,7 +688,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // calculate l2 forwarder address const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { owner: await l1Signer.getAddress(), - token: params.erc20L1Address, + token: await this.getL2ERC20Address(params.erc20L1Address, l1Signer.provider!), router: this.l3Network.tokenBridge.l1GatewayRouter, to: params.to, amount: params.amount, @@ -691,18 +698,24 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { randomNonce: ethers.utils.randomBytes(32), } + // figure out how much extra ETH we should pass along through the token bridge + // populatedGasParams has a 30% default increase already const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, l1Signer ) - - const l2ForwarderAddress = await teleporter.l2ForwarderAddress( - l2ForwarderParams + const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( + this.l2Network.ethBridge.inbox, + '0', // we don't care about L1 gas price, this will just set it to current gas price + populatedGasParams ) - - const tokenBridgeRequest = await new Erc20Bridger( - this.l2Network - ).getDepositRequest({ + const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost + .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) + .add(relayerPayment) + .add(ethers.utils.parseEther("0.1")) + + const l2ForwarderAddress = await this.l2ForwarderAddress(l2ForwarderParams, l2Provider) + const baseDepositRequestParams = { amount: BigNumber.from(params.amount), l1Provider: l1Signer.provider!, l2Provider: l2Provider, @@ -711,21 +724,23 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { destinationAddress: l2ForwarderAddress, callValueRefundAddress: l2ForwarderAddress, excessFeeRefundAddress: l2ForwarderAddress, - }) - - // figure out how much extra ETH we should pass along through the token bridge - const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( - this.l2Network.ethBridge.inbox, - '0', // we don't care about L1 gas price, this will just set it to current gas price - populatedGasParams - ) - const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost - .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) - .add(relayerPayment) - tokenBridgeRequest.txRequest.value = extraValue.add( - tokenBridgeRequest.txRequest.value - ) + } + const erc20Bridger = new Erc20Bridger(this.l2Network) + const submissionCostBefore = (await erc20Bridger.getDepositRequest(baseDepositRequestParams)).retryableData.maxSubmissionCost + const tokenBridgeRequest = await erc20Bridger.getDepositRequest({ + ...baseDepositRequestParams, + retryableGasOverrides: { + // we need to INCREASE submission cost by extraValue + // percent increase has already been applied to submissionCostBefore and extraValue + // so we set percentIncrease to 0 so we don't needlessly increase again + maxSubmissionFee: { + base: submissionCostBefore.add(extraValue), + percentIncrease: ethers.BigNumber.from(0) + } + } + }) + return { relayerInfo: { ...l2ForwarderParams, @@ -843,21 +858,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // todo: cache last toBlock and use it as fromBlock // cache answer - // cache l1 -> l2 block private async _findBridgedToL3Event( l2ForwarderAddress: string, - fromL1Block: number, + fromL2Block: number, l2Provider: JsonRpcProvider ) { - const fromL2Block = await getFirstBlockForL1Block({ - provider: l2Provider, - forL1Block: fromL1Block - }) - - if (fromL2Block === undefined) { - throw new ArbSdkError(`Could not find L2 block for L1 block ${fromL1Block}`) - } - const latest = await l2Provider.getBlockNumber() const eventFetcher = new EventFetcher(l2Provider) const events = ( diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index fd558e7afe..3667c29764 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,4 +1,4 @@ -import { testSetup } from '../../scripts/testSetup' +import { config, testSetup } from '../../scripts/testSetup' import { Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' @@ -6,7 +6,7 @@ import { MockToken } from '../../src/lib/abi/MockToken' import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { ethers } from 'ethers' -import { EthL1L3Bridger } from '../../src/lib/assetBridger/l1l3Bridger' +import { EthL1L3Bridger, RelayedErc20L1L3Bridger } from '../../src/lib/assetBridger/l1l3Bridger' import { expect } from 'chai' type Unwrap = T extends Promise ? U : T @@ -41,8 +41,8 @@ describe('L1 to L3 Bridging', () => { setup = await testSetup() // fund signers on L1 and L2 - await fundL1(setup.l1Signer, ethers.utils.parseEther('100')) - await fundL2(setup.l2Signer, ethers.utils.parseEther('100')) + await fundL1(setup.l1Signer, ethers.utils.parseEther('1')) + await fundL2(setup.l2Signer, ethers.utils.parseEther('1')) }) describe('ETH Bridging', () => { @@ -63,7 +63,7 @@ describe('L1 to L3 Bridging', () => { const depositTx = await l1l3Bridger.deposit( { - amount: ethers.utils.parseEther('1'), + amount: ethers.utils.parseEther('0.1'), destinationOverrides: { l3DestinationAddress: l3Recipient, l2RefundAddress: l2RefundAddress, @@ -88,7 +88,7 @@ describe('L1 to L3 Bridging', () => { // check eth balances const l3Balance = await setup.l3Signer.provider!.getBalance(l3Recipient) - expect(l3Balance.gt(ethers.utils.parseEther('1'))).to.be.true + expect(l3Balance.gt(ethers.utils.parseEther('0.1'))).to.be.true const l2Balance = await setup.l2Signer.provider!.getBalance( l2RefundAddress @@ -199,5 +199,97 @@ describe('L1 to L3 Bridging', () => { } }) }) + + describe('RelayedErc20L1L3Bridger', () => { + let l1l3Bridger: RelayedErc20L1L3Bridger + + // create the bridger and approve the teleporter + before(async () => { + l1l3Bridger = new RelayedErc20L1L3Bridger(setup.l3Network) + }) + + // todo: change other tests to go like this instead of in before() + it('approves', async () => { + await (await l1l3Bridger.approveToken({ + erc20L1Address: l1Token.address + }, setup.l1Signer)).wait() + }) + + // should throw if gas overrides not passed when using non default gateway + // test relayer stuff + // don't need to test rescue here i think + + it('happy path', async () => { + const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + + const depositResult = await l1l3Bridger.deposit( + { + erc20L1Address: l1Token.address, + to: l3Recipient, + amount: ethers.utils.parseEther('1'), + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + const depositReceipt = await depositResult.tx.wait() + + const l2JsonRpcProvider = new ethers.providers.JsonRpcProvider(config.arbUrl) + + // wait until first leg finishes + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + depositResult.relayerInfo, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + return status.bridgeToL2Status === L1ToL2MessageStatus.REDEEMED + }, 1000) + + // make sure status shows that l2 forwarder hasn't been called yet + expect((await l1l3Bridger.getDepositStatus(depositReceipt, depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider!)).l2ForwarderCalled).to.be.false + + // relay + const relayTx = await RelayedErc20L1L3Bridger.relayDeposit( + depositResult.relayerInfo, + setup.l2Signer + ) + + await relayTx.wait() + + // make sure status is updated + expect((await l1l3Bridger.getDepositStatus(depositReceipt, depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider!)).l2ForwarderCalled).to.be.true + + // wait for third leg to finish + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + depositResult.relayerInfo, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + return status.completed + }, 1000) + + // make sure the tokens have landed in the right place + const l3TokenAddr = await l1l3Bridger.getL3ERC20Address( + l1Token.address, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) + const l3Token = l1l3Bridger.getL3TokenContract( + l3TokenAddr, + setup.l3Signer.provider!, + ) + + const l3Balance = await l3Token.balanceOf(l3Recipient) + + if (!l3Balance.eq(ethers.utils.parseEther('1'))) { + throw new Error('L3 balance is incorrect') + } + }) + }) }) }) From ece6adeaca186c34109ccae38ecfa44659a1e922 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:20:01 -0400 Subject: [PATCH 21/80] cache --- src/lib/assetBridger/l1l3Bridger.ts | 76 +++++++++++++++++------------ 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index a8a75b5415..8cafe08164 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,5 +1,22 @@ -import { JsonRpcProvider } from "@ethersproject/providers" import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' +import { JsonRpcProvider } from "@ethersproject/providers" +import { + BigNumber, + BigNumberish, Signer, ethers +} from 'ethers' +import { ERC20 } from '../abi/ERC20' +import { BridgedToL3Event } from '../abi/L2Forwarder' +import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' +import { L2GatewayToken } from '../abi/L2GatewayToken' +import { ERC20__factory } from '../abi/factories/ERC20__factory' +import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' +import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' +import { L2Forwarder__factory } from '../abi/factories/L2Forwarder__factory' +import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' +import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factory' +import { Teleporter__factory } from '../abi/factories/Teleporter__factory' +import { Address } from '../dataEntities/address' +import { DISABLED_GATEWAY } from '../dataEntities/constants' import { ArbSdkError } from '../dataEntities/errors' import { L1Network, @@ -12,26 +29,9 @@ import { SignerOrProvider, SignerProviderUtils, } from '../dataEntities/signerOrProvider' -import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' -import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' -import { L2GatewayToken } from '../abi/L2GatewayToken' -import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factory' -import { ERC20 } from '../abi/ERC20' -import { ERC20__factory } from '../abi/factories/ERC20__factory' -import { DISABLED_GATEWAY } from '../dataEntities/constants' -import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' -import { - BigNumber, - BigNumberish, - Contract, - ContractFactory, - Overrides, - Signer, - Wallet, - ethers, -} from 'ethers' -import { Teleporter__factory } from '../abi/factories/Teleporter__factory' -import { AbiCoder } from 'ethers/lib/utils' +import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' +import { L1ToL2MessageStatus } from '../message/L1ToL2Message' +import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' import { L1ContractCallTransaction, L1ContractCallTransactionReceipt, @@ -39,15 +39,8 @@ import { L1EthDepositTransactionReceipt, L1TransactionReceipt, } from '../message/L1Transaction' -import { L1ToL2MessageStatus } from '../message/L1ToL2Message' -import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' -import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' -import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' -import { Address } from '../dataEntities/address' -import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' -import { EventFetcher } from '../utils/eventFetcher' -import { L2Forwarder__factory } from '../abi/factories/L2Forwarder__factory' -import { getFirstBlockForL1Block } from '../utils/lib' +import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' +import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' export enum Erc20TeleportationLeg { BridgeToL2, @@ -137,6 +130,7 @@ class BaseL1L3Bridger { public readonly l2Network: L2Network public readonly l3Network: L2Network + // TODO: REPLACE THIS WITH L1TOL2MESSAGEGASESTIMATOR public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase @@ -640,6 +634,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { + // value is [lastBlockChecked, event] + private readonly _eventFetchCache: Map | undefined]> = new Map() + public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -865,6 +862,21 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) { const latest = await l2Provider.getBlockNumber() const eventFetcher = new EventFetcher(l2Provider) + + + // begin maybe useless caching stuff + let cacheValue = this._eventFetchCache.get(l2ForwarderAddress) + if (!cacheValue) { + cacheValue = [fromL2Block, undefined] + this._eventFetchCache.set(l2ForwarderAddress, cacheValue) + } + const [lastBlockChecked, event] = cacheValue + + if (event) return event + + fromL2Block = lastBlockChecked + // end maybe useless caching stuff + const events = ( await eventFetcher.getEvents( L2Forwarder__factory, @@ -874,9 +886,13 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) ) + cacheValue[0] = latest + if (events.length === 0) { return undefined } + + cacheValue[1] = events[0] return events[0] } From 7dafe6d02e88bf822828c0772edb7af45a46bce9 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:01:09 -0400 Subject: [PATCH 22/80] to is optional, remove caching --- src/lib/assetBridger/l1l3Bridger.ts | 39 +++++++-------------------- tests/integration/l1l3Bridger.test.ts | 3 ++- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 8cafe08164..f3b52b5ded 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -68,8 +68,8 @@ export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { export interface Erc20DepositRequestParams { erc20L1Address: string - to: string // todo: make optional, default to signer's address amount: BigNumber + to?: string overrides?: { gasPricePercentIncrease?: BigNumber relayerPaymentPercentIncrease?: BigNumber @@ -518,24 +518,24 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getDepositRequest( params: Erc20DepositRequestParams, - l1Provider: Provider, + l1Signer: Signer, l2Provider: Provider, l3Provider: Provider ): Promise>> { - await this._checkL1Network(l1Provider) + await this._checkL1Network(l1Signer) await this._checkL2Network(l2Provider) await this._checkL3Network(l3Provider) const gasParams = await this._populateGasParams( params, - l1Provider, + l1Signer.provider!, l2Provider, l3Provider ) const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, - l1Provider + l1Signer ) const calldata = teleporter.interface.encodeFunctionData('teleport', [ @@ -543,14 +543,14 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l1Token: params.erc20L1Address, l1l2Router: this.l2Network.tokenBridge.l1GatewayRouter, l2l3Router: this.l3Network.tokenBridge.l1GatewayRouter, - to: params.to, + to: params.to || await l1Signer.getAddress(), amount: params.amount, gasParams, randomNonce: ethers.utils.randomBytes(32), }, ]) - const l1GasPrice = await l1Provider.getGasPrice() + const l1GasPrice = await l1Signer.provider!.getGasPrice() const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, @@ -573,7 +573,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { ): Promise { const txRequest = await this.getDepositRequest( params, - l1Signer.provider!, + l1Signer, l2Provider, l3Provider ) @@ -634,9 +634,6 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { - // value is [lastBlockChecked, event] - private readonly _eventFetchCache: Map | undefined]> = new Map() - public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -687,7 +684,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { owner: await l1Signer.getAddress(), token: await this.getL2ERC20Address(params.erc20L1Address, l1Signer.provider!), router: this.l3Network.tokenBridge.l1GatewayRouter, - to: params.to, + to: params.to || await l1Signer.getAddress(), amount: params.amount, gasLimit: populatedGasParams.l2l3TokenBridgeGasLimit, gasPrice: populatedGasParams.l3GasPrice, @@ -862,20 +859,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) { const latest = await l2Provider.getBlockNumber() const eventFetcher = new EventFetcher(l2Provider) - - - // begin maybe useless caching stuff - let cacheValue = this._eventFetchCache.get(l2ForwarderAddress) - if (!cacheValue) { - cacheValue = [fromL2Block, undefined] - this._eventFetchCache.set(l2ForwarderAddress, cacheValue) - } - const [lastBlockChecked, event] = cacheValue - - if (event) return event - - fromL2Block = lastBlockChecked - // end maybe useless caching stuff const events = ( await eventFetcher.getEvents( @@ -885,15 +868,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } ) ) - - cacheValue[0] = latest if (events.length === 0) { return undefined } - cacheValue[1] = events[0] - return events[0] } } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 3667c29764..562fb51371 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -139,7 +139,9 @@ describe('L1 to L3 Bridging', () => { // create the bridger and approve the teleporter before(async () => { l1l3Bridger = new Erc20L1L3Bridger(setup.l3Network) + }) + it('approves', async () => { // approve the teleporter await ( await l1Token @@ -208,7 +210,6 @@ describe('L1 to L3 Bridging', () => { l1l3Bridger = new RelayedErc20L1L3Bridger(setup.l3Network) }) - // todo: change other tests to go like this instead of in before() it('approves', async () => { await (await l1l3Bridger.approveToken({ erc20L1Address: l1Token.address From b4467a0f66b4ac101d713c33347f22fcde6ad7fa Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:17:32 -0400 Subject: [PATCH 23/80] change Erc20DepositStatus type --- src/lib/assetBridger/l1l3Bridger.ts | 127 ++++++++++++++------------ tests/integration/l1l3Bridger.test.ts | 48 +++++++--- 2 files changed, 104 insertions(+), 71 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index f3b52b5ded..2f113c94c6 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,9 +1,6 @@ import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' -import { JsonRpcProvider } from "@ethersproject/providers" -import { - BigNumber, - BigNumberish, Signer, ethers -} from 'ethers' +import { JsonRpcProvider } from '@ethersproject/providers' +import { BigNumber, BigNumberish, Signer, ethers } from 'ethers' import { ERC20 } from '../abi/ERC20' import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' @@ -30,7 +27,10 @@ import { SignerProviderUtils, } from '../dataEntities/signerOrProvider' import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' -import { L1ToL2MessageStatus } from '../message/L1ToL2Message' +import { + L1ToL2MessageStatus, + L1ToL2MessageWaitResult, +} from '../message/L1ToL2Message' import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' import { L1ContractCallTransaction, @@ -57,8 +57,8 @@ export interface ManualRetryableGasParams { l2ForwarderFactoryGasLimit: BigNumber l1l2TokenBridgeGasLimit: BigNumber l2l3TokenBridgeGasLimit: BigNumber - l1l2TokenBridgeRetryableSize: BigNumber // todo: could call the gateway to get the calldata for a given token? - l2l3TokenBridgeRetryableSize: BigNumber // todo: could call the gateway to get the calldata for a given token? + l1l2TokenBridgeRetryableSize: BigNumber + l2l3TokenBridgeRetryableSize: BigNumber } export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { @@ -91,17 +91,16 @@ export interface EthDepositRequestParams { } export interface Erc20DepositStatus { - bridgeToL2Status: L1ToL2MessageStatus - callToL2ForwarderStatus: L1ToL2MessageStatus - bridgeToL3Status: L1ToL2MessageStatus + bridgeToL2Status: L1ToL2MessageWaitResult + callToL2ForwarderStatus: L1ToL2MessageWaitResult + bridgeToL3Status: L1ToL2MessageWaitResult completed: boolean } -// todo: change to L1ToL2MessageWaitResult and transaction receipt export interface RelayedErc20DepositStatus { - bridgeToL2Status: L1ToL2MessageStatus - l2ForwarderCalled: boolean - bridgeToL3Status: L1ToL2MessageStatus + bridgeToL2: L1ToL2MessageWaitResult + l2ForwarderCall: L1ContractCallTransactionReceipt | undefined + bridgeToL3: L1ToL2MessageWaitResult completed: boolean } @@ -130,7 +129,7 @@ class BaseL1L3Bridger { public readonly l2Network: L2Network public readonly l3Network: L2Network - // TODO: REPLACE THIS WITH L1TOL2MESSAGEGASESTIMATOR + // TODO: REPLACE THIS WITH L1TOL2MESSAGEGASESTIMATOR public readonly defaultGasPricePercentIncrease: BigNumber = BigNumber.from(130) // 30% increase @@ -178,7 +177,6 @@ class BaseL1L3Bridger { await SignerProviderUtils.checkNetworkMatches(sop, this.l3Network.chainID) } - // todo: find and replace '100' with this protected _percentIncrease(base: BigNumber, percent: BigNumber) { return base.mul(percent).div(100) } @@ -412,7 +410,10 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { params: L2ForwarderPredictor.L2ForwarderParamsStruct, l2Provider: Provider ): Promise { - return L2ForwarderFactory__factory.connect(this.teleporterAddresses.l2ForwarderFactory, l2Provider).l2ForwarderAddress(params) + return L2ForwarderFactory__factory.connect( + this.teleporterAddresses.l2ForwarderFactory, + l2Provider + ).l2ForwarderAddress(params) } private async _tokenIsDisabled( @@ -543,7 +544,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l1Token: params.erc20L1Address, l1l2Router: this.l2Network.tokenBridge.l1GatewayRouter, l2l3Router: this.l3Network.tokenBridge.l1GatewayRouter, - to: params.to || await l1Signer.getAddress(), + to: params.to || (await l1Signer.getAddress()), amount: params.amount, gasParams, randomNonce: ethers.utils.randomBytes(32), @@ -608,9 +609,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { // if second leg is not redeemed, the third must not be created if (secondLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { return { - bridgeToL2Status: firstLegRedeem.status, - callToL2ForwarderStatus: secondLegRedeem.status, - bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + bridgeToL2Status: firstLegRedeem, + callToL2ForwarderStatus: secondLegRedeem, + bridgeToL3Status: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } @@ -625,9 +626,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() return { - bridgeToL2Status: firstLegRedeem.status, - callToL2ForwarderStatus: secondLegRedeem.status, - bridgeToL3Status: thirdLegRedeem.status, + bridgeToL2Status: firstLegRedeem, + callToL2ForwarderStatus: secondLegRedeem, + bridgeToL3Status: thirdLegRedeem, completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, } } @@ -682,9 +683,12 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // calculate l2 forwarder address const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { owner: await l1Signer.getAddress(), - token: await this.getL2ERC20Address(params.erc20L1Address, l1Signer.provider!), + token: await this.getL2ERC20Address( + params.erc20L1Address, + l1Signer.provider! + ), router: this.l3Network.tokenBridge.l1GatewayRouter, - to: params.to || await l1Signer.getAddress(), + to: params.to || (await l1Signer.getAddress()), amount: params.amount, gasLimit: populatedGasParams.l2l3TokenBridgeGasLimit, gasPrice: populatedGasParams.l3GasPrice, @@ -706,9 +710,12 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) .add(relayerPayment) - .add(ethers.utils.parseEther("0.1")) - - const l2ForwarderAddress = await this.l2ForwarderAddress(l2ForwarderParams, l2Provider) + .add(ethers.utils.parseEther('0.1')) + + const l2ForwarderAddress = await this.l2ForwarderAddress( + l2ForwarderParams, + l2Provider + ) const baseDepositRequestParams = { amount: BigNumber.from(params.amount), l1Provider: l1Signer.provider!, @@ -721,7 +728,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } const erc20Bridger = new Erc20Bridger(this.l2Network) - const submissionCostBefore = (await erc20Bridger.getDepositRequest(baseDepositRequestParams)).retryableData.maxSubmissionCost + const submissionCostBefore = ( + await erc20Bridger.getDepositRequest(baseDepositRequestParams) + ).retryableData.maxSubmissionCost const tokenBridgeRequest = await erc20Bridger.getDepositRequest({ ...baseDepositRequestParams, retryableGasOverrides: { @@ -730,11 +739,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // so we set percentIncrease to 0 so we don't needlessly increase again maxSubmissionFee: { base: submissionCostBefore.add(extraValue), - percentIncrease: ethers.BigNumber.from(0) - } - } + percentIncrease: ethers.BigNumber.from(0), + }, + }, }) - + return { relayerInfo: { ...l2ForwarderParams, @@ -787,9 +796,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { return { - bridgeToL2Status: firstLegRedeem.status, - l2ForwarderCalled: false, - bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + bridgeToL2: firstLegRedeem, + l2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } @@ -807,9 +816,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { if (bridgeToL3Event === undefined) { return { - bridgeToL2Status: firstLegRedeem.status, - l2ForwarderCalled: false, - bridgeToL3Status: L1ToL2MessageStatus.NOT_YET_CREATED, + bridgeToL2: firstLegRedeem, + l2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } @@ -817,14 +826,14 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // get tx or receipt from the event const tx = await l2Provider.getTransaction(bridgeToL3Event.transactionHash) const receipt = new L1ContractCallTransactionReceipt(await tx.wait()) - const l2l3Redeem = await (( + const l2l3Redeem = await ( await receipt.getL1ToL2Messages(l3Provider) - )[0].getSuccessfulRedeem()) + )[0].getSuccessfulRedeem() return { - bridgeToL2Status: firstLegRedeem.status, - l2ForwarderCalled: true, - bridgeToL3Status: l2l3Redeem.status, + bridgeToL2: firstLegRedeem, + l2ForwarderCall: receipt, + bridgeToL3: l2l3Redeem, completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, } } @@ -834,11 +843,14 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { relayerInfo: RelayerInfo, l2Signer: Signer ): Promise { - const teleporterAddresses = l2Networks[relayerInfo.chainId].teleporterAddresses + const teleporterAddresses = + l2Networks[relayerInfo.chainId].teleporterAddresses if (!teleporterAddresses) { throw new ArbSdkError( - `L2 network ${l2Networks[relayerInfo.chainId].name} does not have teleporter contracts` + `L2 network ${ + l2Networks[relayerInfo.chainId].name + } does not have teleporter contracts` ) } @@ -850,8 +862,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { return l2ForwarderFactory.callForwarder(relayerInfo) } - // todo: cache last toBlock and use it as fromBlock - // cache answer private async _findBridgedToL3Event( l2ForwarderAddress: string, fromL2Block: number, @@ -859,20 +869,17 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) { const latest = await l2Provider.getBlockNumber() const eventFetcher = new EventFetcher(l2Provider) - - const events = ( - await eventFetcher.getEvents( - L2Forwarder__factory, - contract => - contract.filters.BridgedToL3(), - { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } - ) + + const events = await eventFetcher.getEvents( + L2Forwarder__factory, + contract => contract.filters.BridgedToL3(), + { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } ) - + if (events.length === 0) { return undefined } - + return events[0] } } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 562fb51371..aab6d87b32 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -6,7 +6,10 @@ import { MockToken } from '../../src/lib/abi/MockToken' import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { ethers } from 'ethers' -import { EthL1L3Bridger, RelayedErc20L1L3Bridger } from '../../src/lib/assetBridger/l1l3Bridger' +import { + EthL1L3Bridger, + RelayedErc20L1L3Bridger, +} from '../../src/lib/assetBridger/l1l3Bridger' import { expect } from 'chai' type Unwrap = T extends Promise ? U : T @@ -191,7 +194,7 @@ describe('L1 to L3 Bridging', () => { ) const l3Token = l1l3Bridger.getL3TokenContract( l3TokenAddr, - setup.l3Signer.provider!, + setup.l3Signer.provider! ) const l3Balance = await l3Token.balanceOf(l3Recipient) @@ -211,9 +214,14 @@ describe('L1 to L3 Bridging', () => { }) it('approves', async () => { - await (await l1l3Bridger.approveToken({ - erc20L1Address: l1Token.address - }, setup.l1Signer)).wait() + await ( + await l1l3Bridger.approveToken( + { + erc20L1Address: l1Token.address, + }, + setup.l1Signer + ) + ).wait() }) // should throw if gas overrides not passed when using non default gateway @@ -236,7 +244,9 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositResult.tx.wait() - const l2JsonRpcProvider = new ethers.providers.JsonRpcProvider(config.arbUrl) + const l2JsonRpcProvider = new ethers.providers.JsonRpcProvider( + config.arbUrl + ) // wait until first leg finishes await poll(async () => { @@ -246,22 +256,38 @@ describe('L1 to L3 Bridging', () => { l2JsonRpcProvider, setup.l3Signer.provider! ) - return status.bridgeToL2Status === L1ToL2MessageStatus.REDEEMED + return status.bridgeToL2.status === L1ToL2MessageStatus.REDEEMED }, 1000) // make sure status shows that l2 forwarder hasn't been called yet - expect((await l1l3Bridger.getDepositStatus(depositReceipt, depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider!)).l2ForwarderCalled).to.be.false + expect( + ( + await l1l3Bridger.getDepositStatus( + depositReceipt, + depositResult.relayerInfo, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + ).l2ForwarderCall + ).to.be.undefined // relay const relayTx = await RelayedErc20L1L3Bridger.relayDeposit( depositResult.relayerInfo, setup.l2Signer ) - + await relayTx.wait() // make sure status is updated - expect((await l1l3Bridger.getDepositStatus(depositReceipt, depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider!)).l2ForwarderCalled).to.be.true + expect( + await l1l3Bridger.getDepositStatus( + depositReceipt, + depositResult.relayerInfo, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + ).to.be.not.undefined // wait for third leg to finish await poll(async () => { @@ -282,7 +308,7 @@ describe('L1 to L3 Bridging', () => { ) const l3Token = l1l3Bridger.getL3TokenContract( l3TokenAddr, - setup.l3Signer.provider!, + setup.l3Signer.provider! ) const l3Balance = await l3Token.balanceOf(l3Recipient) From c3ba30a464adefa3b1b53e313b0efe931bd3a4c2 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:35:23 -0400 Subject: [PATCH 24/80] change pctIncrease --- src/lib/assetBridger/l1l3Bridger.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2f113c94c6..a362854bc0 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -129,9 +129,8 @@ class BaseL1L3Bridger { public readonly l2Network: L2Network public readonly l3Network: L2Network - // TODO: REPLACE THIS WITH L1TOL2MESSAGEGASESTIMATOR public readonly defaultGasPricePercentIncrease: BigNumber = - BigNumber.from(130) // 30% increase + BigNumber.from(200) constructor(l3Network: L2Network) { const l2Network = l2Networks[l3Network.partnerChainID] @@ -177,8 +176,8 @@ class BaseL1L3Bridger { await SignerProviderUtils.checkNetworkMatches(sop, this.l3Network.chainID) } - protected _percentIncrease(base: BigNumber, percent: BigNumber) { - return base.mul(percent).div(100) + protected _percentIncrease(num: BigNumber, increase: BigNumber): BigNumber { + return num.add(num.mul(increase).div(100)) } } @@ -195,7 +194,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } as const public readonly defaultRelayerPaymentPercentIncrease: BigNumber = - BigNumber.from(130) // 30% increase + BigNumber.from(30) public constructor(public readonly l3Network: L2Network) { super(l3Network) From b1501049fdb6dba56ed3c4e54a6e4d8ea921888e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:35:27 -0400 Subject: [PATCH 25/80] test base getters --- tests/integration/l1l3Bridger.test.ts | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index aab6d87b32..69478ea9a5 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -136,6 +136,65 @@ describe('L1 to L3 Bridging', () => { await l1Token.deployed() }) + describe('BaseErc20L1L3Bridger', () => { + // use Erc20L1L3Bridger to test base class + let l1l3Bridger: Erc20L1L3Bridger + + // create the bridger and approve the teleporter + before(async () => { + l1l3Bridger = new Erc20L1L3Bridger(setup.l3Network) + }) + + it('getL2ERC20Address', async () => { + // use weth to test, since we already know its addresses + const l1Weth = setup.l2Network.tokenBridge.l1Weth + const l2Weth = setup.l2Network.tokenBridge.l2Weth + const ans = await l1l3Bridger.getL2ERC20Address(l1Weth, setup.l1Signer.provider!) + expect(ans).to.eq(l2Weth) + }) + + it('getL3ERC20Address', async () => { + // use weth to test, since we already know its addresses + const l1Weth = setup.l2Network.tokenBridge.l1Weth + const l3Weth = setup.l3Network.tokenBridge.l2Weth + const ans = await l1l3Bridger.getL3ERC20Address(l1Weth, setup.l1Signer.provider!, setup.l2Signer.provider!) + expect(ans).to.eq(l3Weth) + }) + + it('getL1L2GatewayAddress', async () => { + // test weth and default gateway + const l1Weth = setup.l2Network.tokenBridge.l1Weth + const l1l2WethGateway = setup.l2Network.tokenBridge.l1WethGateway + + const wethAns = await l1l3Bridger.getL1L2GatewayAddress(l1Weth, setup.l1Signer.provider!) + + expect(wethAns).to.eq(l1l2WethGateway) + + // test default gateway + const l1l2Gateway = setup.l2Network.tokenBridge.l1ERC20Gateway + const defaultAns = await l1l3Bridger.getL1L2GatewayAddress(l1Token.address, setup.l1Signer.provider!) + expect(defaultAns).to.eq(l1l2Gateway) + }) + + it('getL2L3GatewayAddress', async () => { + // test weth and default gateway + const l1Weth = setup.l2Network.tokenBridge.l1Weth + const l2l3WethGateway = setup.l3Network.tokenBridge.l1WethGateway + + const wethAns = await l1l3Bridger.getL2L3GatewayAddress(l1Weth, setup.l1Signer.provider!, setup.l2Signer.provider!) + + expect(wethAns).to.eq(l2l3WethGateway) + + // test default gateway + const l2l3Gateway = setup.l3Network.tokenBridge.l1ERC20Gateway + const defaultAns = await l1l3Bridger.getL2L3GatewayAddress(l1Token.address, setup.l1Signer.provider!, setup.l2Signer.provider!) + expect(defaultAns).to.eq(l2l3Gateway) + }) + + // todo: disabled + + }) + describe('Erc20L1L3Bridger', () => { let l1l3Bridger: Erc20L1L3Bridger From 2ae922abc59aa1521d23eb52aae724db4837fe2e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:36:38 -0400 Subject: [PATCH 26/80] remove randomNonce --- src/lib/assetBridger/l1l3Bridger.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index a362854bc0..2120f133b8 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -546,7 +546,6 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { to: params.to || (await l1Signer.getAddress()), amount: params.amount, gasParams, - randomNonce: ethers.utils.randomBytes(32), }, ]) @@ -688,11 +687,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ), router: this.l3Network.tokenBridge.l1GatewayRouter, to: params.to || (await l1Signer.getAddress()), - amount: params.amount, gasLimit: populatedGasParams.l2l3TokenBridgeGasLimit, gasPrice: populatedGasParams.l3GasPrice, relayerPayment, - randomNonce: ethers.utils.randomBytes(32), } // figure out how much extra ETH we should pass along through the token bridge From df472abb54a2734a3deab1cbf31a6f4252f552cf Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:38:16 -0400 Subject: [PATCH 27/80] modify getDepositStatus to account for frontrunning scenario --- src/lib/assetBridger/l1l3Bridger.ts | 135 ++++++++++++++++++-------- tests/integration/l1l3Bridger.test.ts | 11 +-- 2 files changed, 95 insertions(+), 51 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2120f133b8..6ae20c2262 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -90,10 +90,19 @@ export interface EthDepositRequestParams { } } +/** + * @param bridgeToL2 The status + tx receipt of the first leg of the teleportation + * @param retryableL2ForwarderCall The status + tx receipt of the second leg of the teleportation + * @param otherL2ForwarderCall Since calling the L2 forwarder is permissionless, the retryable can be frontran. + * In this case then retryableL2ForwarderCall will not redeem, but that's okay beacause ETH and tokens will still be forwarded to L3. + * @param bridgeToL3 The status + tx receipt of the third leg of the teleportation + * @param completed Whether the teleportation has completed + */ export interface Erc20DepositStatus { - bridgeToL2Status: L1ToL2MessageWaitResult - callToL2ForwarderStatus: L1ToL2MessageWaitResult - bridgeToL3Status: L1ToL2MessageWaitResult + bridgeToL2: L1ToL2MessageWaitResult + retryableL2ForwarderCall: L1ToL2MessageWaitResult + otherL2ForwarderCall: L1ContractCallTransactionReceipt | undefined + bridgeToL3: L1ToL2MessageWaitResult completed: boolean } @@ -415,6 +424,23 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ).l2ForwarderAddress(params) } + public getRecipientFromParentBridgeTx( + depositTxReceipt: L1ContractCallTransactionReceipt, + ) { + const topic0 = L1GatewayRouter__factory.createInterface().getEventTopic('TransferRouted') + const log = depositTxReceipt.logs.find(log => log.topics[0] === topic0) + + if (!log) { + throw new ArbSdkError(`Could not find TransferRouted event in tx receipt`) + } + + // topic 3 is "_userTo" + const bytes32UserTo = log.topics[3] + + // parse address + return ethers.utils.getAddress(bytes32UserTo.slice(26)) + } + private async _tokenIsDisabled( tokenAddress: string, gatewayRouterAddress: string, @@ -480,6 +506,27 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ), } } + + protected async _findBridgedToL3Event( + l2ForwarderAddress: string, + fromL2Block: number, + l2Provider: JsonRpcProvider + ) { + const latest = await l2Provider.getBlockNumber() + const eventFetcher = new EventFetcher(l2Provider) + + const events = await eventFetcher.getEvents( + L2Forwarder__factory, + contract => contract.filters.BridgedToL3(), + { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } + ) + + if (events.length === 0) { + return undefined + } + + return events[0] + } } export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { @@ -594,7 +641,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, - l2Provider: Provider, + l2Provider: JsonRpcProvider, l3Provider: Provider ): Promise { await this._checkL2Network(l2Provider) @@ -604,29 +651,58 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { const firstLegRedeem = await l1l2Messages[0].getSuccessfulRedeem() const secondLegRedeem = await l1l2Messages[1].getSuccessfulRedeem() - // if second leg is not redeemed, the third must not be created - if (secondLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { + if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { return { - bridgeToL2Status: firstLegRedeem, - callToL2ForwarderStatus: secondLegRedeem, - bridgeToL3Status: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, + bridgeToL2: firstLegRedeem, + retryableL2ForwarderCall: secondLegRedeem, + otherL2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } - // otherwise, third leg must be created + let secondLegTxReceipt: ethers.providers.TransactionReceipt; + if (secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED) { + secondLegTxReceipt = secondLegRedeem.l2TxReceipt + } + else { + // see if there are any other calls to the l2 forwarder after the first leg redeem + const bridgedToL3Event = await this._findBridgedToL3Event( + this.getRecipientFromParentBridgeTx(depositTxReceipt), + firstLegRedeem.l2TxReceipt.blockNumber, + l2Provider + ) + + // second leg has not completed + if (!bridgedToL3Event) { + return { + bridgeToL2: firstLegRedeem, + retryableL2ForwarderCall: secondLegRedeem, + otherL2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, + completed: false, + } + } + + // second leg has completed via another forwarder call + secondLegTxReceipt = await l2Provider.getTransactionReceipt( + bridgedToL3Event.transactionHash + ) + } + + // third leg must be created + const secondLegL1TxReceipt = new L1ContractCallTransactionReceipt(secondLegTxReceipt) const thirdLegMessage = ( - await new L1TransactionReceipt( - secondLegRedeem.l2TxReceipt - ).getL1ToL2Messages(l3Provider) + await secondLegL1TxReceipt.getL1ToL2Messages(l3Provider) )[0] const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() return { - bridgeToL2Status: firstLegRedeem, - callToL2ForwarderStatus: secondLegRedeem, - bridgeToL3Status: thirdLegRedeem, + bridgeToL2: firstLegRedeem, + retryableL2ForwarderCall: secondLegRedeem, + otherL2ForwarderCall: secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED ? undefined : secondLegL1TxReceipt, + bridgeToL3: thirdLegRedeem, completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, } } @@ -782,7 +858,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, - relayerInfo: RelayerInfo, l2Provider: JsonRpcProvider, l3Provider: Provider ): Promise { @@ -799,10 +874,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } - const l2ForwarderAddress = await L2ForwarderFactory__factory.connect( - this.teleporterAddresses.l2ForwarderFactory, - l2Provider - ).l2ForwarderAddress(relayerInfo) + const l2ForwarderAddress = this.getRecipientFromParentBridgeTx(depositTxReceipt) const bridgeToL3Event = await this._findBridgedToL3Event( l2ForwarderAddress, @@ -857,27 +929,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { return l2ForwarderFactory.callForwarder(relayerInfo) } - - private async _findBridgedToL3Event( - l2ForwarderAddress: string, - fromL2Block: number, - l2Provider: JsonRpcProvider - ) { - const latest = await l2Provider.getBlockNumber() - const eventFetcher = new EventFetcher(l2Provider) - - const events = await eventFetcher.getEvents( - L2Forwarder__factory, - contract => contract.filters.BridgedToL3(), - { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } - ) - - if (events.length === 0) { - return undefined - } - - return events[0] - } } export class EthL1L3Bridger extends BaseL1L3Bridger { diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 69478ea9a5..79bc2b5832 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -36,6 +36,7 @@ function poll( describe('L1 to L3 Bridging', () => { let setup: Unwrap> + const l2JsonRpcProvider = new ethers.providers.JsonRpcProvider(config.arbUrl) // setup for all test cases before(async function () { @@ -239,7 +240,7 @@ describe('L1 to L3 Bridging', () => { await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, - setup.l2Signer.provider!, + l2JsonRpcProvider, setup.l3Signer.provider! ) return status.completed @@ -303,15 +304,10 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositResult.tx.wait() - const l2JsonRpcProvider = new ethers.providers.JsonRpcProvider( - config.arbUrl - ) - // wait until first leg finishes await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, - depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider! ) @@ -323,7 +319,6 @@ describe('L1 to L3 Bridging', () => { ( await l1l3Bridger.getDepositStatus( depositReceipt, - depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider! ) @@ -342,7 +337,6 @@ describe('L1 to L3 Bridging', () => { expect( await l1l3Bridger.getDepositStatus( depositReceipt, - depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider! ) @@ -352,7 +346,6 @@ describe('L1 to L3 Bridging', () => { await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, - depositResult.relayerInfo, l2JsonRpcProvider, setup.l3Signer.provider! ) From abfc4f3be2ba2e3532ae55e011ba09201d16910d Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:18:38 -0400 Subject: [PATCH 28/80] test status when retryable is frontran: wip --- src/lib/assetBridger/l1l3Bridger.ts | 50 +++++---- tests/integration/l1l3Bridger.test.ts | 147 +++++++++++++++++++++++--- 2 files changed, 160 insertions(+), 37 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 6ae20c2262..f997cafd74 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -41,6 +41,7 @@ import { } from '../message/L1Transaction' import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' +import { PercentIncrease } from '../message/L1ToL2MessageGasEstimator' export enum Erc20TeleportationLeg { BridgeToL2, @@ -71,8 +72,10 @@ export interface Erc20DepositRequestParams { amount: BigNumber to?: string overrides?: { - gasPricePercentIncrease?: BigNumber - relayerPaymentPercentIncrease?: BigNumber + // gasPricePercentIncrease?: BigNumber + l2GasPrice?: PercentIncrease + l3GasPrice?: PercentIncrease + relayerPayment?: PercentIncrease manualGasParams?: ManualRetryableGasParams } } @@ -425,10 +428,11 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } public getRecipientFromParentBridgeTx( - depositTxReceipt: L1ContractCallTransactionReceipt, + depositTxReceipt: L1ContractCallTransactionReceipt ) { - const topic0 = L1GatewayRouter__factory.createInterface().getEventTopic('TransferRouted') - const log = depositTxReceipt.logs.find(log => log.topics[0] === topic0) + const topic0 = + L1GatewayRouter__factory.createInterface().getEventTopic('TransferRouted') + const log = depositTxReceipt.logs.find(x => x.topics[0] === topic0) if (!log) { throw new ArbSdkError(`Could not find TransferRouted event in tx receipt`) @@ -495,13 +499,13 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { return { ...manualGasParams, l2GasPrice: this._percentIncrease( - await l2Provider.getGasPrice(), - params.overrides.gasPricePercentIncrease || + params.overrides.l2GasPrice?.base || (await l2Provider.getGasPrice()), + params.overrides.l2GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease ), l3GasPrice: this._percentIncrease( - await l3Provider.getGasPrice(), - params.overrides.gasPricePercentIncrease || + params.overrides.l3GasPrice?.base || (await l3Provider.getGasPrice()), + params.overrides.l3GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease ), } @@ -661,18 +665,17 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } } - let secondLegTxReceipt: ethers.providers.TransactionReceipt; + let secondLegTxReceipt: ethers.providers.TransactionReceipt if (secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED) { secondLegTxReceipt = secondLegRedeem.l2TxReceipt - } - else { + } else { // see if there are any other calls to the l2 forwarder after the first leg redeem const bridgedToL3Event = await this._findBridgedToL3Event( this.getRecipientFromParentBridgeTx(depositTxReceipt), firstLegRedeem.l2TxReceipt.blockNumber, l2Provider ) - + // second leg has not completed if (!bridgedToL3Event) { return { @@ -691,7 +694,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } // third leg must be created - const secondLegL1TxReceipt = new L1ContractCallTransactionReceipt(secondLegTxReceipt) + const secondLegL1TxReceipt = new L1ContractCallTransactionReceipt( + secondLegTxReceipt + ) const thirdLegMessage = ( await secondLegL1TxReceipt.getL1ToL2Messages(l3Provider) )[0] @@ -701,7 +706,10 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { return { bridgeToL2: firstLegRedeem, retryableL2ForwarderCall: secondLegRedeem, - otherL2ForwarderCall: secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED ? undefined : secondLegL1TxReceipt, + otherL2ForwarderCall: + secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED + ? undefined + : secondLegL1TxReceipt, bridgeToL3: thirdLegRedeem, completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, } @@ -747,10 +755,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) const relayerPayment = this._percentIncrease( - populatedGasParams.l2ForwarderFactoryGasLimit.mul( - populatedGasParams.l2GasPrice - ), - params.overrides?.relayerPaymentPercentIncrease || + params.overrides?.relayerPayment?.base || + populatedGasParams.l2ForwarderFactoryGasLimit.mul( + populatedGasParams.l2GasPrice + ), + params.overrides?.relayerPayment?.percentIncrease || this.defaultRelayerPaymentPercentIncrease ) @@ -874,7 +883,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } - const l2ForwarderAddress = this.getRecipientFromParentBridgeTx(depositTxReceipt) + const l2ForwarderAddress = + this.getRecipientFromParentBridgeTx(depositTxReceipt) const bridgeToL3Event = await this._findBridgedToL3Event( l2ForwarderAddress, diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 79bc2b5832..a812dac184 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,11 +1,11 @@ import { config, testSetup } from '../../scripts/testSetup' -import { Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' +import { Address, Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' import { MockToken } from '../../src/lib/abi/MockToken' import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' -import { ethers } from 'ethers' +import { BigNumber, ethers } from 'ethers' import { EthL1L3Bridger, RelayedErc20L1L3Bridger, @@ -150,7 +150,10 @@ describe('L1 to L3 Bridging', () => { // use weth to test, since we already know its addresses const l1Weth = setup.l2Network.tokenBridge.l1Weth const l2Weth = setup.l2Network.tokenBridge.l2Weth - const ans = await l1l3Bridger.getL2ERC20Address(l1Weth, setup.l1Signer.provider!) + const ans = await l1l3Bridger.getL2ERC20Address( + l1Weth, + setup.l1Signer.provider! + ) expect(ans).to.eq(l2Weth) }) @@ -158,7 +161,11 @@ describe('L1 to L3 Bridging', () => { // use weth to test, since we already know its addresses const l1Weth = setup.l2Network.tokenBridge.l1Weth const l3Weth = setup.l3Network.tokenBridge.l2Weth - const ans = await l1l3Bridger.getL3ERC20Address(l1Weth, setup.l1Signer.provider!, setup.l2Signer.provider!) + const ans = await l1l3Bridger.getL3ERC20Address( + l1Weth, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) expect(ans).to.eq(l3Weth) }) @@ -167,13 +174,19 @@ describe('L1 to L3 Bridging', () => { const l1Weth = setup.l2Network.tokenBridge.l1Weth const l1l2WethGateway = setup.l2Network.tokenBridge.l1WethGateway - const wethAns = await l1l3Bridger.getL1L2GatewayAddress(l1Weth, setup.l1Signer.provider!) + const wethAns = await l1l3Bridger.getL1L2GatewayAddress( + l1Weth, + setup.l1Signer.provider! + ) expect(wethAns).to.eq(l1l2WethGateway) // test default gateway const l1l2Gateway = setup.l2Network.tokenBridge.l1ERC20Gateway - const defaultAns = await l1l3Bridger.getL1L2GatewayAddress(l1Token.address, setup.l1Signer.provider!) + const defaultAns = await l1l3Bridger.getL1L2GatewayAddress( + l1Token.address, + setup.l1Signer.provider! + ) expect(defaultAns).to.eq(l1l2Gateway) }) @@ -182,18 +195,25 @@ describe('L1 to L3 Bridging', () => { const l1Weth = setup.l2Network.tokenBridge.l1Weth const l2l3WethGateway = setup.l3Network.tokenBridge.l1WethGateway - const wethAns = await l1l3Bridger.getL2L3GatewayAddress(l1Weth, setup.l1Signer.provider!, setup.l2Signer.provider!) + const wethAns = await l1l3Bridger.getL2L3GatewayAddress( + l1Weth, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) expect(wethAns).to.eq(l2l3WethGateway) // test default gateway const l2l3Gateway = setup.l3Network.tokenBridge.l1ERC20Gateway - const defaultAns = await l1l3Bridger.getL2L3GatewayAddress(l1Token.address, setup.l1Signer.provider!, setup.l2Signer.provider!) + const defaultAns = await l1l3Bridger.getL2L3GatewayAddress( + l1Token.address, + setup.l1Signer.provider!, + setup.l2Signer.provider! + ) expect(defaultAns).to.eq(l2l3Gateway) }) // todo: disabled - }) describe('Erc20L1L3Bridger', () => { @@ -206,20 +226,19 @@ describe('L1 to L3 Bridging', () => { it('approves', async () => { // approve the teleporter - await ( - await l1Token - .connect(setup.l1Signer) - .approve( - setup.l2Network.teleporterAddresses!.l1Teleporter, - ethers.utils.parseEther('100') - ) - ).wait() + await (await l1l3Bridger.approveToken( + { + erc20L1Address: l1Token.address, + }, + setup.l1Signer + )).wait() }) // should throw if gas overrides not passed when using non default gateway // test relayer stuff // don't need to test rescue here i think + // todo: check status at each step, do this for other happy path tests too it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) @@ -263,6 +282,100 @@ describe('L1 to L3 Bridging', () => { throw new Error('L3 balance is incorrect') } }) + + it('should report correct status when second leg is frontran', async () => { + const adjustedL3GasPrice = (await setup.l3Signer.provider!.getGasPrice()).mul(3) + const depositTx = await l1l3Bridger.deposit( + { + erc20L1Address: l1Token.address, + amount: ethers.utils.parseEther('1'), + overrides: { + manualGasParams: { + ...l1l3Bridger.defaultRetryableGasParams, + l2ForwarderFactoryGasLimit: BigNumber.from(10) // make sure the second leg retryable fails + }, + l3GasPrice: { + base: adjustedL3GasPrice, + percentIncrease: BigNumber.from(0) + } + } + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) + + const depositReceipt = await depositTx.wait() + + // poll status until first leg completes + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + return status.bridgeToL2.status === L1ToL2MessageStatus.REDEEMED + }, 1000) + + // make sure we have FUNDS_DEPOSITED_ON_L2 and undefined + const statusAfterLeg1 = await l1l3Bridger.getDepositStatus( + depositReceipt, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + expect(statusAfterLeg1.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) + expect(statusAfterLeg1.otherL2ForwarderCall).to.be.undefined + expect(statusAfterLeg1.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) + + // relay the second leg (use the RelayedErc20L1L3Bridger to do this) + const l1SignerAddr = await setup.l1Signer.getAddress() + const relayTx = await RelayedErc20L1L3Bridger.relayDeposit( + { + owner: new Address(l1SignerAddr).applyAlias().value, + token: await l1l3Bridger.getL2ERC20Address(l1Token.address, setup.l1Signer.provider!), + router: setup.l3Network.tokenBridge.l1GatewayRouter, + to: l1SignerAddr, // here we're doubling this test to make sure it defaults to the signer when "to" is not passed to deposit + gasLimit: l1l3Bridger.defaultRetryableGasParams.l2l3TokenBridgeGasLimit, + gasPrice: adjustedL3GasPrice, + relayerPayment: BigNumber.from(0), + chainId: setup.l2Network.chainID + }, + setup.l2Signer + ) + + await relayTx.wait() + + // make sure we get FUNDS_DEPOSITED_ON_L2 and the relay tx receipt + const statusAfterLeg2 = await l1l3Bridger.getDepositStatus( + depositReceipt, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + expect(statusAfterLeg2.bridgeToL2.status).to.eq(L1ToL2MessageStatus.REDEEMED) + expect(statusAfterLeg2.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) + expect(statusAfterLeg2.otherL2ForwarderCall?.transactionHash).to.eq(relayTx.hash) + expect(statusAfterLeg2.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) + + // make sure we get the correct final result on L3 + await poll(async () => { + const status = await l1l3Bridger.getDepositStatus( + depositReceipt, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + return status.completed + }, 1000) + + const statusAfterLeg3 = await l1l3Bridger.getDepositStatus( + depositReceipt, + l2JsonRpcProvider, + setup.l3Signer.provider! + ) + expect(statusAfterLeg3.completed).to.be.true + expect(statusAfterLeg3.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) + expect(statusAfterLeg3.otherL2ForwarderCall?.transactionHash).to.eq(relayTx.hash) + expect(statusAfterLeg3.bridgeToL3.status).to.eq(L1ToL2MessageStatus.REDEEMED) + }) }) describe('RelayedErc20L1L3Bridger', () => { From 2baabfd5c5821c754df9bd9cbc4ba3d27bcb34bc Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:15:19 -0400 Subject: [PATCH 29/80] finish get deposit status --- src/lib/assetBridger/l1l3Bridger.ts | 207 +++++++++++--------------- tests/integration/l1l3Bridger.test.ts | 6 +- 2 files changed, 94 insertions(+), 119 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index f997cafd74..b5bef08b38 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -95,27 +95,26 @@ export interface EthDepositRequestParams { /** * @param bridgeToL2 The status + tx receipt of the first leg of the teleportation - * @param retryableL2ForwarderCall The status + tx receipt of the second leg of the teleportation - * @param otherL2ForwarderCall Since calling the L2 forwarder is permissionless, the retryable can be frontran. - * In this case then retryableL2ForwarderCall will not redeem, but that's okay beacause ETH and tokens will still be forwarded to L3. + * @param l2ForwarderCall tx receipt of the second leg of the teleportation * @param bridgeToL3 The status + tx receipt of the third leg of the teleportation * @param completed Whether the teleportation has completed */ -export interface Erc20DepositStatus { +interface BaseErc20DepositStatus { bridgeToL2: L1ToL2MessageWaitResult - retryableL2ForwarderCall: L1ToL2MessageWaitResult - otherL2ForwarderCall: L1ContractCallTransactionReceipt | undefined + l2ForwarderCall: L1ContractCallTransactionReceipt | undefined bridgeToL3: L1ToL2MessageWaitResult completed: boolean } -export interface RelayedErc20DepositStatus { - bridgeToL2: L1ToL2MessageWaitResult - l2ForwarderCall: L1ContractCallTransactionReceipt | undefined - bridgeToL3: L1ToL2MessageWaitResult - completed: boolean +/** + * @param retryableL2ForwarderCall The status + tx receipt of the second leg retryable + */ +export interface Erc20DepositStatus extends BaseErc20DepositStatus { + retryableL2ForwarderCall: L1ToL2MessageWaitResult } +export interface RelayedErc20DepositStatus extends BaseErc20DepositStatus {} + export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number } @@ -445,6 +444,72 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { return ethers.utils.getAddress(bytes32UserTo.slice(26)) } + /** + * Get the status of a deposit given an L1 tx receipt. + * + * Note: This function does not verify that the tx is actually a deposit tx. + * + * @param depositTxReceipt + * @param l2Provider + * @param l3Provider + * @returns + */ + protected async _getDepositStatus( + depositTxReceipt: L1ContractCallTransactionReceipt, + l2Provider: JsonRpcProvider, + l3Provider: Provider + ): Promise { + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) + + const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) + const firstLegRedeem = await l1l2Messages[0].getSuccessfulRedeem() + + if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { + return { + bridgeToL2: firstLegRedeem, + l2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, + completed: false, + } + } + + // see if there are any other calls to the l2 forwarder after the first leg redeem + const bridgedToL3Event = await this._findBridgedToL3Event( + this.getRecipientFromParentBridgeTx(depositTxReceipt), + firstLegRedeem.l2TxReceipt.blockNumber, + l2Provider + ) + + // second leg has not completed + if (!bridgedToL3Event) { + return { + bridgeToL2: firstLegRedeem, + l2ForwarderCall: undefined, + bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, + completed: false, + } + } + + // second leg has completed via another forwarder call + const secondLegTxReceipt = new L1ContractCallTransactionReceipt(await l2Provider.getTransactionReceipt( + bridgedToL3Event.transactionHash + )) + + const thirdLegMessage = ( + await secondLegTxReceipt.getL1ToL2Messages(l3Provider) + )[0] + + const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() + + return { + bridgeToL2: firstLegRedeem, + l2ForwarderCall: secondLegTxReceipt, + bridgeToL3: thirdLegRedeem, + completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, + } + } + private async _tokenIsDisabled( tokenAddress: string, gatewayRouterAddress: string, @@ -648,70 +713,17 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l2Provider: JsonRpcProvider, l3Provider: Provider ): Promise { - await this._checkL2Network(l2Provider) - await this._checkL3Network(l3Provider) - - const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) - const firstLegRedeem = await l1l2Messages[0].getSuccessfulRedeem() - const secondLegRedeem = await l1l2Messages[1].getSuccessfulRedeem() - - if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { - return { - bridgeToL2: firstLegRedeem, - retryableL2ForwarderCall: secondLegRedeem, - otherL2ForwarderCall: undefined, - bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, - completed: false, - } - } - - let secondLegTxReceipt: ethers.providers.TransactionReceipt - if (secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED) { - secondLegTxReceipt = secondLegRedeem.l2TxReceipt - } else { - // see if there are any other calls to the l2 forwarder after the first leg redeem - const bridgedToL3Event = await this._findBridgedToL3Event( - this.getRecipientFromParentBridgeTx(depositTxReceipt), - firstLegRedeem.l2TxReceipt.blockNumber, - l2Provider - ) - - // second leg has not completed - if (!bridgedToL3Event) { - return { - bridgeToL2: firstLegRedeem, - retryableL2ForwarderCall: secondLegRedeem, - otherL2ForwarderCall: undefined, - bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, - completed: false, - } - } - - // second leg has completed via another forwarder call - secondLegTxReceipt = await l2Provider.getTransactionReceipt( - bridgedToL3Event.transactionHash - ) - } - - // third leg must be created - const secondLegL1TxReceipt = new L1ContractCallTransactionReceipt( - secondLegTxReceipt + const baseStatus = await this._getDepositStatus( + depositTxReceipt, + l2Provider, + l3Provider ) - const thirdLegMessage = ( - await secondLegL1TxReceipt.getL1ToL2Messages(l3Provider) - )[0] - const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() + const secondLegRetryableRedeem = await (await depositTxReceipt.getL1ToL2Messages(l2Provider))[1].getSuccessfulRedeem() return { - bridgeToL2: firstLegRedeem, - retryableL2ForwarderCall: secondLegRedeem, - otherL2ForwarderCall: - secondLegRedeem.status === L1ToL2MessageStatus.REDEEMED - ? undefined - : secondLegL1TxReceipt, - bridgeToL3: thirdLegRedeem, - completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, + ...baseStatus, + retryableL2ForwarderCall: secondLegRetryableRedeem, } } } @@ -865,55 +877,12 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { * @param l2Provider * @param l3Provider */ - public async getDepositStatus( + public getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, l2Provider: JsonRpcProvider, l3Provider: Provider ): Promise { - const firstLegRedeem = await ( - await depositTxReceipt.getL1ToL2Messages(l2Provider) - )[0].getSuccessfulRedeem() - - if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { - return { - bridgeToL2: firstLegRedeem, - l2ForwarderCall: undefined, - bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, - completed: false, - } - } - - const l2ForwarderAddress = - this.getRecipientFromParentBridgeTx(depositTxReceipt) - - const bridgeToL3Event = await this._findBridgedToL3Event( - l2ForwarderAddress, - firstLegRedeem.l2TxReceipt.blockNumber, - l2Provider - ) - - if (bridgeToL3Event === undefined) { - return { - bridgeToL2: firstLegRedeem, - l2ForwarderCall: undefined, - bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, - completed: false, - } - } - - // get tx or receipt from the event - const tx = await l2Provider.getTransaction(bridgeToL3Event.transactionHash) - const receipt = new L1ContractCallTransactionReceipt(await tx.wait()) - const l2l3Redeem = await ( - await receipt.getL1ToL2Messages(l3Provider) - )[0].getSuccessfulRedeem() - - return { - bridgeToL2: firstLegRedeem, - l2ForwarderCall: receipt, - bridgeToL3: l2l3Redeem, - completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, - } + return this._getDepositStatus(depositTxReceipt, l2Provider, l3Provider) } // takes: deposit tx hash, l2 forwarder params, l2 signer @@ -921,6 +890,12 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { relayerInfo: RelayerInfo, l2Signer: Signer ): Promise { + if (await l2Signer.getChainId() !== relayerInfo.chainId) { + throw new ArbSdkError( + `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${relayerInfo.chainId}` + ) + } + const teleporterAddresses = l2Networks[relayerInfo.chainId].teleporterAddresses diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index a812dac184..121e4e87c2 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -324,7 +324,7 @@ describe('L1 to L3 Bridging', () => { setup.l3Signer.provider! ) expect(statusAfterLeg1.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) - expect(statusAfterLeg1.otherL2ForwarderCall).to.be.undefined + expect(statusAfterLeg1.l2ForwarderCall).to.be.undefined expect(statusAfterLeg1.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) // relay the second leg (use the RelayedErc20L1L3Bridger to do this) @@ -353,7 +353,7 @@ describe('L1 to L3 Bridging', () => { ) expect(statusAfterLeg2.bridgeToL2.status).to.eq(L1ToL2MessageStatus.REDEEMED) expect(statusAfterLeg2.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) - expect(statusAfterLeg2.otherL2ForwarderCall?.transactionHash).to.eq(relayTx.hash) + expect(statusAfterLeg2.l2ForwarderCall?.transactionHash).to.eq(relayTx.hash) expect(statusAfterLeg2.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) // make sure we get the correct final result on L3 @@ -373,7 +373,7 @@ describe('L1 to L3 Bridging', () => { ) expect(statusAfterLeg3.completed).to.be.true expect(statusAfterLeg3.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) - expect(statusAfterLeg3.otherL2ForwarderCall?.transactionHash).to.eq(relayTx.hash) + expect(statusAfterLeg3.l2ForwarderCall?.transactionHash).to.eq(relayTx.hash) expect(statusAfterLeg3.bridgeToL3.status).to.eq(L1ToL2MessageStatus.REDEEMED) }) }) From 6fbbfa3c9fdf47a0f2de97dd26d1121a3062d16a Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:19:21 -0400 Subject: [PATCH 30/80] fmt --- src/lib/assetBridger/l1l3Bridger.ts | 34 ++++++------- tests/integration/l1l3Bridger.test.ts | 72 ++++++++++++++++++--------- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index b5bef08b38..fc6649c11f 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -113,7 +113,7 @@ export interface Erc20DepositStatus extends BaseErc20DepositStatus { retryableL2ForwarderCall: L1ToL2MessageWaitResult } -export interface RelayedErc20DepositStatus extends BaseErc20DepositStatus {} +export type RelayedErc20DepositStatus = BaseErc20DepositStatus export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number @@ -204,9 +204,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l2l3TokenBridgeRetryableSize: BigNumber.from(1000), } as const - public readonly defaultRelayerPaymentPercentIncrease: BigNumber = - BigNumber.from(30) - public constructor(public readonly l3Network: L2Network) { super(l3Network) @@ -221,9 +218,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the corresponding L2 token address for the provided L1 token - * @param erc20L1Address - * @param l1Provider - * @returns */ public async getL2ERC20Address( erc20L1Address: string, @@ -239,10 +233,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the corresponding L3 token address for the provided L1 token - * @param erc20L1Address - * @param l1Provider - * @param l2Provider - * @returns */ public async getL3ERC20Address( erc20L1Address: string, @@ -492,9 +482,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } // second leg has completed via another forwarder call - const secondLegTxReceipt = new L1ContractCallTransactionReceipt(await l2Provider.getTransactionReceipt( - bridgedToL3Event.transactionHash - )) + const secondLegTxReceipt = new L1ContractCallTransactionReceipt( + await l2Provider.getTransactionReceipt(bridgedToL3Event.transactionHash) + ) const thirdLegMessage = ( await secondLegTxReceipt.getL1ToL2Messages(l3Provider) @@ -719,7 +709,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) - const secondLegRetryableRedeem = await (await depositTxReceipt.getL1ToL2Messages(l2Provider))[1].getSuccessfulRedeem() + const secondLegRetryableRedeem = await ( + await depositTxReceipt.getL1ToL2Messages(l2Provider) + )[1].getSuccessfulRedeem() return { ...baseStatus, @@ -729,6 +721,12 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { + /** + * The default percent increase for the relayer payment + */ + public readonly defaultRelayerPaymentPercentIncrease: BigNumber = + BigNumber.from(30) + public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -890,9 +888,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { relayerInfo: RelayerInfo, l2Signer: Signer ): Promise { - if (await l2Signer.getChainId() !== relayerInfo.chainId) { + if ((await l2Signer.getChainId()) !== relayerInfo.chainId) { throw new ArbSdkError( - `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${relayerInfo.chainId}` + `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${ + relayerInfo.chainId + }` ) } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 121e4e87c2..00e3b0a102 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -226,12 +226,14 @@ describe('L1 to L3 Bridging', () => { it('approves', async () => { // approve the teleporter - await (await l1l3Bridger.approveToken( - { - erc20L1Address: l1Token.address, - }, - setup.l1Signer - )).wait() + await ( + await l1l3Bridger.approveToken( + { + erc20L1Address: l1Token.address, + }, + setup.l1Signer + ) + ).wait() }) // should throw if gas overrides not passed when using non default gateway @@ -284,7 +286,9 @@ describe('L1 to L3 Bridging', () => { }) it('should report correct status when second leg is frontran', async () => { - const adjustedL3GasPrice = (await setup.l3Signer.provider!.getGasPrice()).mul(3) + const adjustedL3GasPrice = ( + await setup.l3Signer.provider!.getGasPrice() + ).mul(3) const depositTx = await l1l3Bridger.deposit( { erc20L1Address: l1Token.address, @@ -292,13 +296,13 @@ describe('L1 to L3 Bridging', () => { overrides: { manualGasParams: { ...l1l3Bridger.defaultRetryableGasParams, - l2ForwarderFactoryGasLimit: BigNumber.from(10) // make sure the second leg retryable fails + l2ForwarderFactoryGasLimit: BigNumber.from(10), // make sure the second leg retryable fails }, l3GasPrice: { base: adjustedL3GasPrice, - percentIncrease: BigNumber.from(0) - } - } + percentIncrease: BigNumber.from(0), + }, + }, }, setup.l1Signer, setup.l2Signer.provider!, @@ -323,22 +327,30 @@ describe('L1 to L3 Bridging', () => { l2JsonRpcProvider, setup.l3Signer.provider! ) - expect(statusAfterLeg1.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) + expect(statusAfterLeg1.retryableL2ForwarderCall.status).to.eq( + L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + ) expect(statusAfterLeg1.l2ForwarderCall).to.be.undefined - expect(statusAfterLeg1.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) + expect(statusAfterLeg1.bridgeToL3.status).to.eq( + L1ToL2MessageStatus.NOT_YET_CREATED + ) // relay the second leg (use the RelayedErc20L1L3Bridger to do this) const l1SignerAddr = await setup.l1Signer.getAddress() const relayTx = await RelayedErc20L1L3Bridger.relayDeposit( { owner: new Address(l1SignerAddr).applyAlias().value, - token: await l1l3Bridger.getL2ERC20Address(l1Token.address, setup.l1Signer.provider!), + token: await l1l3Bridger.getL2ERC20Address( + l1Token.address, + setup.l1Signer.provider! + ), router: setup.l3Network.tokenBridge.l1GatewayRouter, to: l1SignerAddr, // here we're doubling this test to make sure it defaults to the signer when "to" is not passed to deposit - gasLimit: l1l3Bridger.defaultRetryableGasParams.l2l3TokenBridgeGasLimit, + gasLimit: + l1l3Bridger.defaultRetryableGasParams.l2l3TokenBridgeGasLimit, gasPrice: adjustedL3GasPrice, relayerPayment: BigNumber.from(0), - chainId: setup.l2Network.chainID + chainId: setup.l2Network.chainID, }, setup.l2Signer ) @@ -351,10 +363,18 @@ describe('L1 to L3 Bridging', () => { l2JsonRpcProvider, setup.l3Signer.provider! ) - expect(statusAfterLeg2.bridgeToL2.status).to.eq(L1ToL2MessageStatus.REDEEMED) - expect(statusAfterLeg2.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) - expect(statusAfterLeg2.l2ForwarderCall?.transactionHash).to.eq(relayTx.hash) - expect(statusAfterLeg2.bridgeToL3.status).to.eq(L1ToL2MessageStatus.NOT_YET_CREATED) + expect(statusAfterLeg2.bridgeToL2.status).to.eq( + L1ToL2MessageStatus.REDEEMED + ) + expect(statusAfterLeg2.retryableL2ForwarderCall.status).to.eq( + L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + ) + expect(statusAfterLeg2.l2ForwarderCall?.transactionHash).to.eq( + relayTx.hash + ) + expect(statusAfterLeg2.bridgeToL3.status).to.eq( + L1ToL2MessageStatus.NOT_YET_CREATED + ) // make sure we get the correct final result on L3 await poll(async () => { @@ -372,9 +392,15 @@ describe('L1 to L3 Bridging', () => { setup.l3Signer.provider! ) expect(statusAfterLeg3.completed).to.be.true - expect(statusAfterLeg3.retryableL2ForwarderCall.status).to.eq(L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) - expect(statusAfterLeg3.l2ForwarderCall?.transactionHash).to.eq(relayTx.hash) - expect(statusAfterLeg3.bridgeToL3.status).to.eq(L1ToL2MessageStatus.REDEEMED) + expect(statusAfterLeg3.retryableL2ForwarderCall.status).to.eq( + L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + ) + expect(statusAfterLeg3.l2ForwarderCall?.transactionHash).to.eq( + relayTx.hash + ) + expect(statusAfterLeg3.bridgeToL3.status).to.eq( + L1ToL2MessageStatus.REDEEMED + ) }) }) From 2f8f18f547a0f07dae80548e61fc5c6aa412ada0 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:48:33 -0400 Subject: [PATCH 31/80] tweak types and add comments --- src/lib/assetBridger/l1l3Bridger.ts | 257 ++++++++++++++++---------- tests/integration/l1l3Bridger.test.ts | 6 +- 2 files changed, 161 insertions(+), 102 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index fc6649c11f..1a8b9d68b3 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -2,7 +2,6 @@ import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' import { JsonRpcProvider } from '@ethersproject/providers' import { BigNumber, BigNumberish, Signer, ethers } from 'ethers' import { ERC20 } from '../abi/ERC20' -import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L2GatewayToken } from '../abi/L2GatewayToken' import { ERC20__factory } from '../abi/factories/ERC20__factory' @@ -32,6 +31,7 @@ import { L1ToL2MessageWaitResult, } from '../message/L1ToL2Message' import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' +import { GasOverrides, PercentIncrease } from '../message/L1ToL2MessageGasEstimator' import { L1ContractCallTransaction, L1ContractCallTransactionReceipt, @@ -41,19 +41,14 @@ import { } from '../message/L1Transaction' import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' -import { PercentIncrease } from '../message/L1ToL2MessageGasEstimator' +import { Teleporter } from '../abi/Teleporter' +import { BridgedToL3Event } from '../abi/L2Forwarder' -export enum Erc20TeleportationLeg { - BridgeToL2, - CallL2ForwarderFactory, - BridgeToL3, -} - -export enum EthTeleportationLeg { - L2Retryable, - L3Retryable, -} +// todo: make sure all fields of all types are actually used +/** + * Some manual or hardcoded gas params for retryables. Includes gas limits and retryable sizes. + */ export interface ManualRetryableGasParams { l2ForwarderFactoryGasLimit: BigNumber l1l2TokenBridgeGasLimit: BigNumber @@ -62,38 +57,53 @@ export interface ManualRetryableGasParams { l2l3TokenBridgeRetryableSize: BigNumber } -export interface PopulatedRetryableGasParams extends ManualRetryableGasParams { - l2GasPrice: BigNumber - l3GasPrice: BigNumber +export interface Erc20DepositRequestParamsOverrides { + l2GasPrice?: PercentIncrease + l3GasPrice?: PercentIncrease + manualGasParams?: ManualRetryableGasParams } +/** + * Parameters for `Erc20L1L3Bridger` functions `getDepositRequest` and `deposit` + * @param erc20L1Address Address of L1 token + * @param amount Amount of L1 token to send to L3 + * @param to Optional recipient on L3, defaults to signer's address + * @param overrides Optional overrides for retryable gas parameters and relayer payment + */ export interface Erc20DepositRequestParams { erc20L1Address: string amount: BigNumber to?: string - overrides?: { - // gasPricePercentIncrease?: BigNumber - l2GasPrice?: PercentIncrease - l3GasPrice?: PercentIncrease + overrides?: Erc20DepositRequestParamsOverrides +} + +/** + * Parameters for `RelayedErc20L1L3Bridger` functions `getDepositRequest` and `deposit` + */ +export interface RelayedErc20DepositRequestParams extends Erc20DepositRequestParams { + overrides?: Erc20DepositRequestParamsOverrides & { relayerPayment?: PercentIncrease - manualGasParams?: ManualRetryableGasParams } } +/** + * Parameters for `EthL1L3Bridger` functions `getDepositRequest` and `deposit` + * @param amount Amount of ETH to send to L3 + * @param to Optional recipient on L3, defaults to signer's address + * @param l2RefundAddress Optional fee refund address on L2, defaults to signer's address + * @param l2TicketGasOverrides Optional gas overrides for L1 to L2 message + * @param l3TicketGasOverrides Optional gas overrides for L2 to L3 message + */ export interface EthDepositRequestParams { amount: BigNumberish - destinationOverrides?: { - l3DestinationAddress: string - l2RefundAddress: string - } - overrides?: { - gasPricePercentIncrease?: BigNumber - l2RetryableGasLimit?: BigNumber - l3RetryableGasLimit?: BigNumber - } + to?: string // todo: document defaults in the function docstrings as well + l2RefundAddress?: string + l2TicketGasOverrides?: Omit + l3TicketGasOverrides?: Omit } /** + * Return type for `BaseErc20L1L3Bridger.getDepositStatus` * @param bridgeToL2 The status + tx receipt of the first leg of the teleportation * @param l2ForwarderCall tx receipt of the second leg of the teleportation * @param bridgeToL3 The status + tx receipt of the third leg of the teleportation @@ -107,34 +117,55 @@ interface BaseErc20DepositStatus { } /** + * Return type for `Erc20L1L3Bridger.getDepositStatus`. + * The second leg of the teleportation is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. + * This is because the retryable could possibly be frontran and the teleportation will still succeed. * @param retryableL2ForwarderCall The status + tx receipt of the second leg retryable */ export interface Erc20DepositStatus extends BaseErc20DepositStatus { retryableL2ForwarderCall: L1ToL2MessageWaitResult } +/** + * Return type for `RelayedErc20L1L3Bridger.getDepositStatus` + */ export type RelayedErc20DepositStatus = BaseErc20DepositStatus +/** + * Return type for `EthL1L3Bridger.getDepositStatus` + */ +export type EthDepositStatus = { + l2RetryableStatus: L1ToL2MessageStatus + l3RetryableStatus: L1ToL2MessageStatus + completed: boolean +} + +/** + * Information required by a relayer to relay a deposit. + */ export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number } +/** + * Return type for `RelayedErc20L1L3Bridger.getDepositRequest`. Includes the transaction request as well as relayer info. + */ export type RelayedErc20DepositRequestResult = { txRequest: L1ToL2TransactionRequest relayerInfo: RelayerInfo } +/** + * Return type for `RelayedErc20L1L3Bridger.deposit`. Includes the transaction as well as relayer info. + */ export type RelayedErc20DepositResult = { tx: L1ContractCallTransaction relayerInfo: RelayerInfo } -export type EthDepositStatus = { - l2RetryableStatus: L1ToL2MessageStatus - l3RetryableStatus: L1ToL2MessageStatus - completed: boolean -} - +/** + * Base functionality for L1 to L3 bridging. + */ class BaseL1L3Bridger { public readonly l1Network: L1Network public readonly l2Network: L2Network @@ -192,6 +223,9 @@ class BaseL1L3Bridger { } } +/** + * Base functionality for bridging ERC20 tokens from L1 to L3. + */ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { public readonly teleporterAddresses: TeleporterAddresses @@ -247,6 +281,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ) } + /** + * Given an erc20 address on a parent network, get the corresponding erc20 address on a child network + */ private _getChildErc20Address( erc20ParentAddress: string, parentProvider: Provider, @@ -262,10 +299,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Get the address of the l1 <-> l2 gateway on l1 for this token - * @param erc20L1Address - * @param l1Provider - * @returns + * Get the address of the L1 <-> L2 gateway on L1 given an L1 token address */ public async getL1L2GatewayAddress( erc20L1Address: string, @@ -280,11 +314,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Get the address of the l2 <-> l3 gateway on l2 for this token - * @param erc20L1Address - * @param l1Provider - * @param l2Provider - * @returns + * Get the address of the L2 <-> L3 gateway on L2 given an L1 token address */ public async getL2L3GatewayAddress( erc20L1Address: string, @@ -302,6 +332,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ).getGateway(l2Token) } + /** + * Whether the L1 <-> L2 gateway is the default gateway given an L1 token address + */ public async isL1L2GatewayDefault( erc20L1Address: string, l1Provider: Provider @@ -310,6 +343,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { return gateway === this.l2Network.tokenBridge.l1ERC20Gateway } + /** + * Whether the L2 <-> L3 gateway is the default gateway given an L1 token address + */ public async isL2L3GatewayDefault( erc20L1Address: string, l1Provider: Provider, @@ -328,9 +364,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * Note: This function just returns a typed ethers object for the provided address, it doesnt * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. - * @param l1Provider - * @param l1TokenAddr - * @returns */ public getL1TokenContract(l1TokenAddr: string, l1Provider: Provider): ERC20 { return ERC20__factory.connect(l1TokenAddr, l1Provider) @@ -341,9 +374,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * Note: This function just returns a typed ethers object for the provided address, it doesnt * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. - * @param l2Provider - * @param l2TokenAddr - * @returns */ public getL2TokenContract( l2TokenAddr: string, @@ -357,9 +387,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * Note: This function just returns a typed ethers object for the provided address, it doesnt * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. - * @param l2Provider - * @param l2TokenAddr - * @returns */ public getL3TokenContract( l3TokenAddr: string, @@ -369,10 +396,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Whether the L1 token has been disabled on the router - * @param l1TokenAddress - * @param l1Provider - * @returns + * Whether the L1 token has been disabled on the router given an L1 token address */ public async l1TokenIsDisabled( l1TokenAddress: string, @@ -388,10 +412,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Whether the L2 token has been disabled on the router - * @param l2TokenAddress - * @param l2Provider - * @returns + * Whether the L2 token has been disabled on the router given an L2 token address */ public async l2TokenIsDisabled( l2TokenAddress: string, @@ -406,16 +427,24 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ) } - public l2ForwarderAddress( + /** + * Given L2Forwarder parameters, get the address of the L2Forwarder contract + */ + public async l2ForwarderAddress( params: L2ForwarderPredictor.L2ForwarderParamsStruct, l2Provider: Provider ): Promise { + await this._checkL2Network(l2Provider) + return L2ForwarderFactory__factory.connect( this.teleporterAddresses.l2ForwarderFactory, l2Provider ).l2ForwarderAddress(params) } + /** + * Given a parent network token bridge deposit transaction receipt, get the recipient address on the child network + */ public getRecipientFromParentBridgeTx( depositTxReceipt: L1ContractCallTransactionReceipt ) { @@ -438,11 +467,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { * Get the status of a deposit given an L1 tx receipt. * * Note: This function does not verify that the tx is actually a deposit tx. - * - * @param depositTxReceipt - * @param l2Provider - * @param l3Provider - * @returns */ protected async _getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, @@ -500,6 +524,9 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } } + /** + * Whether the token is disabled on the router given a token address and router address + */ private async _tokenIsDisabled( tokenAddress: string, gatewayRouterAddress: string, @@ -516,12 +543,15 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { ) } + /** + * Given a ERC20 deposit request parameters, return the gas params with default values if not provided + */ protected async _populateGasParams( params: Erc20DepositRequestParams, l1Provider: Provider, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { params.overrides = params.overrides || {} let manualGasParams = params.overrides.manualGasParams @@ -566,11 +596,14 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } } + /** + * Find the first BridgedToL3 event emitted by the L2Forwarder contract after the provided block number + */ protected async _findBridgedToL3Event( l2ForwarderAddress: string, fromL2Block: number, l2Provider: JsonRpcProvider - ) { + ): Promise | undefined> { const latest = await l2Provider.getBlockNumber() const eventFetcher = new EventFetcher(l2Provider) @@ -580,20 +613,17 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { { address: l2ForwarderAddress, fromBlock: fromL2Block, toBlock: latest } ) - if (events.length === 0) { - return undefined - } - return events[0] } } +/** + * Bridge ERC20 tokens from L1 to L3 using the L1 Teleporter contract. + */ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to approve tokens for teleportation. - * The tokens will be approved for the Teleporter on L1. - * @param params - * @returns + * The tokens will be approved to be spent by the Teleporter on L1. */ public async getApproveTokenRequest( params: TokenApproveParams @@ -611,6 +641,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } } + /** + * Approve tokens for teleportation. The tokens will be approved to be spent by the `Teleporter` on L1. + */ public async approveToken( params: TokenApproveParams, l1Signer: Signer @@ -622,6 +655,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { return l1Signer.sendTransaction(approveRequest) } + /** + * Get a tx request to deposit tokens to L3 through the L1 Teleporter contract. + */ public async getDepositRequest( params: Erc20DepositRequestParams, l1Signer: Signer, @@ -670,6 +706,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } } + /** + * Deposit tokens to L3 through the L1 Teleporter contract. + */ public async deposit( params: Erc20DepositRequestParams, l1Signer: Signer, @@ -689,14 +728,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Get the status of a deposit given an L1 tx receipt. + * Get the status of a deposit given an L1 tx receipt. See `Erc20DepositStatus` interface for more info on the return type. * * Note: This function does not verify that the tx is actually a deposit tx. - * - * @param depositTxReceipt - * @param l2Provider - * @param l3Provider - * @returns */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, @@ -720,6 +754,9 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } } +/** + * Bridge ERC20 tokens from L1 to L3 directly through the token bridge, using a relayer on L2 to call the L2Forwarder contract. + */ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * The default percent increase for the relayer payment @@ -727,6 +764,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { public readonly defaultRelayerPaymentPercentIncrease: BigNumber = BigNumber.from(30) + /** + * Get a tx request to approve tokens for teleportation. Will approve the token's L1 gateway to spend the provided amount. + */ public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider @@ -737,6 +777,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { }) } + /** + * Approve tokens for teleportation. Will approve the token's L1 gateway to spend the provided amount. + */ public async approveToken( params: TokenApproveParams, l1Signer: Signer @@ -747,8 +790,11 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { }) } + /** + * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly + */ public async getDepositRequest( - params: Erc20DepositRequestParams, + params: RelayedErc20DepositRequestParams, l1Signer: Signer, l2Provider: Provider, l3Provider: Provider @@ -766,7 +812,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { const relayerPayment = this._percentIncrease( params.overrides?.relayerPayment?.base || - populatedGasParams.l2ForwarderFactoryGasLimit.mul( + BigNumber.from(populatedGasParams.l2ForwarderFactoryGasLimit).mul( populatedGasParams.l2GasPrice ), params.overrides?.relayerPayment?.percentIncrease || @@ -788,7 +834,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } // figure out how much extra ETH we should pass along through the token bridge - // populatedGasParams has a 30% default increase already + // populatedGasParams has already applied a percent increase to gas prices const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, l1Signer @@ -801,7 +847,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { const extraValue = calculatedGasCosts.l2l3TokenBridgeGasCost .add(calculatedGasCosts.l2l3TokenBridgeSubmissionCost) .add(relayerPayment) - .add(ethers.utils.parseEther('0.1')) const l2ForwarderAddress = await this.l2ForwarderAddress( l2ForwarderParams, @@ -844,6 +889,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } + /** + * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. + */ public async deposit( params: Erc20DepositRequestParams, l1Signer: Signer, @@ -868,12 +916,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get the status of a deposit given an L1 tx receipt and relayer info. * - * Note: This function does not verify that the tx is actually a deposit tx, nor does it check the provider chain ids. - * - * @param depositTxReceipt - * @param relayerInfo - * @param l2Provider - * @param l3Provider + * Note: This function does not verify that the tx is actually a deposit tx. */ public getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, @@ -883,7 +926,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { return this._getDepositStatus(depositTxReceipt, l2Provider, l3Provider) } - // takes: deposit tx hash, l2 forwarder params, l2 signer + /** + * Call an `L2Forwarder` to relay a deposit to L3. + */ public static async relayDeposit( relayerInfo: RelayerInfo, l2Signer: Signer @@ -916,7 +961,13 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } } +/** + * Bridge ETH from L1 to L3 using a double retryable ticket + */ export class EthL1L3Bridger extends BaseL1L3Bridger { + /** + * Get a tx request to deposit ETH to L3 via a double retryable ticket + */ public async getDepositRequest( params: EthDepositRequestParams, l1Signer: Signer, @@ -930,9 +981,9 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l1Address = await l1Signer.getAddress() const l3DestinationAddress = - params.destinationOverrides?.l3DestinationAddress || l1Address + params.to || l1Address const l2RefundAddress = - params.destinationOverrides?.l2RefundAddress || l1Address + params.l2RefundAddress || l1Address const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( { @@ -944,7 +995,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { callValueRefundAddress: l3DestinationAddress, }, l2Provider, - l3Provider + l3Provider, + params.l3TicketGasOverrides ) const l2TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( @@ -957,12 +1009,16 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { callValueRefundAddress: l2RefundAddress, }, l1Signer.provider!, - l2Provider + l2Provider, + params.l2TicketGasOverrides ) return l2TicketRequest } + /** + * Deposit ETH to L3 via a double retryable ticket + */ public async deposit( params: EthDepositRequestParams, l1Signer: Signer, @@ -981,12 +1037,17 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { ) } - // don't pass a tx that isn't an l1 to l3 eth deposit + /** + * Get the status of a deposit given an L1 tx receipt. Does not check if the tx is actually a deposit tx. + */ public async getDepositStatus( l1TxReceipt: L1EthDepositTransactionReceipt, l2Provider: Provider, l3Provider: Provider ): Promise { + await this._checkL2Network(l2Provider) + await this._checkL3Network(l3Provider) + const l1l2Message = (await l1TxReceipt.getL1ToL2Messages(l2Provider))[0] const l1l2Redeem = await l1l2Message.getSuccessfulRedeem() diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 00e3b0a102..b65379afa3 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -68,10 +68,8 @@ describe('L1 to L3 Bridging', () => { const depositTx = await l1l3Bridger.deposit( { amount: ethers.utils.parseEther('0.1'), - destinationOverrides: { - l3DestinationAddress: l3Recipient, - l2RefundAddress: l2RefundAddress, - }, + to: l3Recipient, + l2RefundAddress: l2RefundAddress, }, setup.l1Signer, setup.l2Signer.provider!, From 0670a50ae4c422e129a802859ba5e9c3dcf9dab3 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:17:08 -0400 Subject: [PATCH 32/80] types comments --- src/lib/assetBridger/l1l3Bridger.ts | 159 ++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 45 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 1a8b9d68b3..8dbad9f61a 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -46,112 +46,165 @@ import { BridgedToL3Event } from '../abi/L2Forwarder' // todo: make sure all fields of all types are actually used -/** - * Some manual or hardcoded gas params for retryables. Includes gas limits and retryable sizes. - */ export interface ManualRetryableGasParams { + /** + * Gas limit for the L2ForwarderFactory contract call + */ l2ForwarderFactoryGasLimit: BigNumber + /** + * Gas limit for the L1 to L2 token bridge ticket redemption + */ l1l2TokenBridgeGasLimit: BigNumber + /** + * Gas limit for the L2 to L3 token bridge ticket redemption + */ l2l3TokenBridgeGasLimit: BigNumber + /** + * Calldata size of the L1 to L2 token bridge ticket + */ l1l2TokenBridgeRetryableSize: BigNumber + /** + * Calldata size of the L2 to L3 token bridge ticket + */ l2l3TokenBridgeRetryableSize: BigNumber } -export interface Erc20DepositRequestParamsOverrides { +export interface Erc20DepositRequestParamsGasOverrides { + /** + * Optional L2 gas price override + */ l2GasPrice?: PercentIncrease + /** + * Optional L3 gas price override + */ l3GasPrice?: PercentIncrease + /** + * Optional manual gas params override. Not optional if the L1 to L2 or L2 to L3 gateways are not the default gateways. + */ manualGasParams?: ManualRetryableGasParams } -/** - * Parameters for `Erc20L1L3Bridger` functions `getDepositRequest` and `deposit` - * @param erc20L1Address Address of L1 token - * @param amount Amount of L1 token to send to L3 - * @param to Optional recipient on L3, defaults to signer's address - * @param overrides Optional overrides for retryable gas parameters and relayer payment - */ export interface Erc20DepositRequestParams { + /** + * Address of L1 token + */ erc20L1Address: string + /** + * Amount of L1 token to send to L3 + */ amount: BigNumber + /** + * Optional recipient on L3, defaults to signer's address + */ to?: string - overrides?: Erc20DepositRequestParamsOverrides + /** + * Optional overrides for retryable gas parameters + */ + overrides?: Erc20DepositRequestParamsGasOverrides } -/** - * Parameters for `RelayedErc20L1L3Bridger` functions `getDepositRequest` and `deposit` - */ export interface RelayedErc20DepositRequestParams extends Erc20DepositRequestParams { - overrides?: Erc20DepositRequestParamsOverrides & { + /** + * Optional overrides for retryable gas parameters and relayer payment + */ + overrides?: Erc20DepositRequestParamsGasOverrides & { + /** + * Optional relayer payment override + */ relayerPayment?: PercentIncrease } } -/** - * Parameters for `EthL1L3Bridger` functions `getDepositRequest` and `deposit` - * @param amount Amount of ETH to send to L3 - * @param to Optional recipient on L3, defaults to signer's address - * @param l2RefundAddress Optional fee refund address on L2, defaults to signer's address - * @param l2TicketGasOverrides Optional gas overrides for L1 to L2 message - * @param l3TicketGasOverrides Optional gas overrides for L2 to L3 message - */ export interface EthDepositRequestParams { + /** + * Amount of ETH to send to L3 + */ amount: BigNumberish - to?: string // todo: document defaults in the function docstrings as well + /** + * Optional recipient on L3, defaults to signer's address + */ + to?: string + /** + * Optional fee refund address on L2, defaults to signer's address + */ l2RefundAddress?: string + /** + * Optional gas overrides for L1 to L2 message + */ l2TicketGasOverrides?: Omit + /** + * Optional gas overrides for L2 to L3 message + */ l3TicketGasOverrides?: Omit } -/** - * Return type for `BaseErc20L1L3Bridger.getDepositStatus` - * @param bridgeToL2 The status + tx receipt of the first leg of the teleportation - * @param l2ForwarderCall tx receipt of the second leg of the teleportation - * @param bridgeToL3 The status + tx receipt of the third leg of the teleportation - * @param completed Whether the teleportation has completed - */ interface BaseErc20DepositStatus { + /** + * Status + redemption tx receipt of the token bridge to L2 + */ bridgeToL2: L1ToL2MessageWaitResult + /** + * Tx receipt of the call to the L2Forwarder contract + */ l2ForwarderCall: L1ContractCallTransactionReceipt | undefined + /** + * Status + redemption tx receipt of the token bridge to L3 + */ bridgeToL3: L1ToL2MessageWaitResult + /** + * Whether the teleportation has completed + */ completed: boolean } /** - * Return type for `Erc20L1L3Bridger.getDepositStatus`. * The second leg of the teleportation is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. * This is because the retryable could possibly be frontran and the teleportation will still succeed. - * @param retryableL2ForwarderCall The status + tx receipt of the second leg retryable */ export interface Erc20DepositStatus extends BaseErc20DepositStatus { + /** + * The status + redemption tx receipt of the retryable to call the L2Forwarder contract + */ retryableL2ForwarderCall: L1ToL2MessageWaitResult } -/** - * Return type for `RelayedErc20L1L3Bridger.getDepositStatus` - */ export type RelayedErc20DepositStatus = BaseErc20DepositStatus -/** - * Return type for `EthL1L3Bridger.getDepositStatus` - */ export type EthDepositStatus = { + /** + * Status + redemption tx receipt of the retryable ticket to L2 + */ l2RetryableStatus: L1ToL2MessageStatus + /** + * Status + redemption tx receipt of the retryable ticket to L3 + */ l3RetryableStatus: L1ToL2MessageStatus + /** + * Whether the teleportation has completed + */ completed: boolean } /** * Information required by a relayer to relay a deposit. + * + * This information is also required to rescue funds from the L2Forwarder contract. */ export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { chainId: number } -/** - * Return type for `RelayedErc20L1L3Bridger.getDepositRequest`. Includes the transaction request as well as relayer info. - */ export type RelayedErc20DepositRequestResult = { + /** + * The transaction request to deposit ERC20 tokens to L3 using a relayer + */ txRequest: L1ToL2TransactionRequest + /** + * Information required by the relayer to forward tokens from L2 to L3 + * + * IMPORTANT! DO NOT LOSE THIS INFO! + * Once tokens are sent through the bridge, losing this information means losing the funds! + */ relayerInfo: RelayerInfo } @@ -159,7 +212,16 @@ export type RelayedErc20DepositRequestResult = { * Return type for `RelayedErc20L1L3Bridger.deposit`. Includes the transaction as well as relayer info. */ export type RelayedErc20DepositResult = { + /** + * The transaction that was sent to deposit ERC20 tokens to L3 using a relayer + */ tx: L1ContractCallTransaction + /** + * Information required by the relayer to forward tokens from L2 to L3 + * + * IMPORTANT! DO NOT LOSE THIS INFO! + * Once tokens are sent through the bridge, losing this information means losing the funds! + */ relayerInfo: RelayerInfo } @@ -791,7 +853,10 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly + * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * + * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! + * Once tokens are sent through the bridge, losing this information means losing the funds! */ public async getDepositRequest( params: RelayedErc20DepositRequestParams, @@ -891,6 +956,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * + * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! + * Once tokens are sent through the bridge, losing this information means losing the funds! */ public async deposit( params: Erc20DepositRequestParams, @@ -985,6 +1053,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l2RefundAddress = params.l2RefundAddress || l1Address + // todo: use the ethBridger instead to create this request const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( { to: l3DestinationAddress, @@ -1047,7 +1116,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { ): Promise { await this._checkL2Network(l2Provider) await this._checkL3Network(l3Provider) - + const l1l2Message = (await l1TxReceipt.getL1ToL2Messages(l2Provider))[0] const l1l2Redeem = await l1l2Message.getSuccessfulRedeem() From 476b5269db4f33d436105c9c8208569f53b50dcb Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:37:10 -0400 Subject: [PATCH 33/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 8dbad9f61a..7c3817331b 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1053,7 +1053,6 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l2RefundAddress = params.l2RefundAddress || l1Address - // todo: use the ethBridger instead to create this request const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( { to: l3DestinationAddress, From 78cb2db419f9b0cc7301d03544ad993a3548dcac Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:49:22 -0400 Subject: [PATCH 34/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 7c3817331b..8890abd1e6 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -2,8 +2,10 @@ import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' import { JsonRpcProvider } from '@ethersproject/providers' import { BigNumber, BigNumberish, Signer, ethers } from 'ethers' import { ERC20 } from '../abi/ERC20' +import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L2GatewayToken } from '../abi/L2GatewayToken' +import { Teleporter } from '../abi/Teleporter' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' @@ -41,10 +43,6 @@ import { } from '../message/L1Transaction' import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' -import { Teleporter } from '../abi/Teleporter' -import { BridgedToL3Event } from '../abi/L2Forwarder' - -// todo: make sure all fields of all types are actually used export interface ManualRetryableGasParams { /** From ed79b155012bb49b5ae2b6360ec63b74f70778c1 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:57:40 -0400 Subject: [PATCH 35/80] add final review notes --- src/lib/assetBridger/l1l3Bridger.ts | 50 +++++++++++++++-------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 8890abd1e6..6f009b4785 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -44,7 +44,11 @@ import { import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' -export interface ManualRetryableGasParams { +/** + * Manual gas parameters for the L1 to L2 and L2 to L3 tickets. + * Percent increase is not applied to these values. + */ +export type ManualRetryableGasParams = { /** * Gas limit for the L2ForwarderFactory contract call */ @@ -67,7 +71,7 @@ export interface ManualRetryableGasParams { l2l3TokenBridgeRetryableSize: BigNumber } -export interface Erc20DepositRequestParamsGasOverrides { +export type Erc20DepositRequestParamsGasOverrides = { /** * Optional L2 gas price override */ @@ -82,7 +86,7 @@ export interface Erc20DepositRequestParamsGasOverrides { manualGasParams?: ManualRetryableGasParams } -export interface Erc20DepositRequestParams { +export type Erc20DepositRequestParams = { /** * Address of L1 token */ @@ -101,7 +105,7 @@ export interface Erc20DepositRequestParams { overrides?: Erc20DepositRequestParamsGasOverrides } -export interface RelayedErc20DepositRequestParams extends Erc20DepositRequestParams { +export type RelayedErc20DepositRequestParams = Erc20DepositRequestParams & { /** * Optional overrides for retryable gas parameters and relayer payment */ @@ -113,7 +117,7 @@ export interface RelayedErc20DepositRequestParams extends Erc20DepositRequestPar } } -export interface EthDepositRequestParams { +export type EthDepositRequestParams = { /** * Amount of ETH to send to L3 */ @@ -136,7 +140,7 @@ export interface EthDepositRequestParams { l3TicketGasOverrides?: Omit } -interface BaseErc20DepositStatus { +type BaseErc20DepositStatus = { /** * Status + redemption tx receipt of the token bridge to L2 */ @@ -156,10 +160,10 @@ interface BaseErc20DepositStatus { } /** - * The second leg of the teleportation is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. + * When using the L1 Teleporter the second leg is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. * This is because the retryable could possibly be frontran and the teleportation will still succeed. */ -export interface Erc20DepositStatus extends BaseErc20DepositStatus { +export type Erc20DepositStatus = BaseErc20DepositStatus & { /** * The status + redemption tx receipt of the retryable to call the L2Forwarder contract */ @@ -565,7 +569,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } } - // second leg has completed via another forwarder call + // second leg has completed const secondLegTxReceipt = new L1ContractCallTransactionReceipt( await l2Provider.getTransactionReceipt(bridgedToL3Event.transactionHash) ) @@ -618,7 +622,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { if (!manualGasParams) { // make sure both gateways are default if ( - !(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider)) + !(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider)) // todo: test this ) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` @@ -702,7 +706,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Approve tokens for teleportation. The tokens will be approved to be spent by the `Teleporter` on L1. + * Approve tokens for teleportation. The tokens will be approved to be spent by the L1 `Teleporter` */ public async approveToken( params: TokenApproveParams, @@ -755,7 +759,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, - this._percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), + this._percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), // todo: this should have overrides gasParams ) @@ -882,9 +886,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { this.defaultRelayerPaymentPercentIncrease ) - // calculate l2 forwarder address const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { - owner: await l1Signer.getAddress(), + owner: await l1Signer.getAddress(), // todo: add override token: await this.getL2ERC20Address( params.erc20L1Address, l1Signer.provider! @@ -897,7 +900,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } // figure out how much extra ETH we should pass along through the token bridge - // populatedGasParams has already applied a percent increase to gas prices + // _populateGasParams has already applied a percent increase to gas prices const teleporter = Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, l1Signer @@ -915,6 +918,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { l2ForwarderParams, l2Provider ) + + // parameters cfor Erc20Bridger.getDepositRequest const baseDepositRequestParams = { amount: BigNumber.from(params.amount), l1Provider: l1Signer.provider!, @@ -943,6 +948,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { }, }) + // todo: double check here that the computed l2 forwarder is correct + return { relayerInfo: { ...l2ForwarderParams, @@ -1001,20 +1008,15 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ): Promise { if ((await l2Signer.getChainId()) !== relayerInfo.chainId) { throw new ArbSdkError( - `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${ - relayerInfo.chainId - }` + `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${relayerInfo.chainId}` ) } - const teleporterAddresses = - l2Networks[relayerInfo.chainId].teleporterAddresses + const teleporterAddresses = l2Networks[relayerInfo.chainId].teleporterAddresses if (!teleporterAddresses) { throw new ArbSdkError( - `L2 network ${ - l2Networks[relayerInfo.chainId].name - } does not have teleporter contracts` + `L2 network ${l2Networks[relayerInfo.chainId].name} does not have teleporter contracts` ) } @@ -1120,7 +1122,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { if (l1l2Redeem.status != L1ToL2MessageStatus.REDEEMED) { return { - l2RetryableStatus: l1l2Redeem.status, + l2RetryableStatus: l1l2Redeem.status, // todo: change these to be wait results like the other bridgers l3RetryableStatus: L1ToL2MessageStatus.NOT_YET_CREATED, completed: false, } From 482fa65b4dcf6003a9f3dd8b087620eb579ff959 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:58:13 -0400 Subject: [PATCH 36/80] fmt --- src/lib/assetBridger/l1l3Bridger.ts | 40 +++++++++++++++++------------ 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 6f009b4785..91b64351f1 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -33,7 +33,10 @@ import { L1ToL2MessageWaitResult, } from '../message/L1ToL2Message' import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' -import { GasOverrides, PercentIncrease } from '../message/L1ToL2MessageGasEstimator' +import { + GasOverrides, + PercentIncrease, +} from '../message/L1ToL2MessageGasEstimator' import { L1ContractCallTransaction, L1ContractCallTransactionReceipt, @@ -189,7 +192,7 @@ export type EthDepositStatus = { /** * Information required by a relayer to relay a deposit. - * + * * This information is also required to rescue funds from the L2Forwarder contract. */ export type RelayerInfo = L2ForwarderPredictor.L2ForwarderParamsStruct & { @@ -203,8 +206,8 @@ export type RelayedErc20DepositRequestResult = { txRequest: L1ToL2TransactionRequest /** * Information required by the relayer to forward tokens from L2 to L3 - * - * IMPORTANT! DO NOT LOSE THIS INFO! + * + * IMPORTANT! DO NOT LOSE THIS INFO! * Once tokens are sent through the bridge, losing this information means losing the funds! */ relayerInfo: RelayerInfo @@ -220,8 +223,8 @@ export type RelayedErc20DepositResult = { tx: L1ContractCallTransaction /** * Information required by the relayer to forward tokens from L2 to L3 - * - * IMPORTANT! DO NOT LOSE THIS INFO! + * + * IMPORTANT! DO NOT LOSE THIS INFO! * Once tokens are sent through the bridge, losing this information means losing the funds! */ relayerInfo: RelayerInfo @@ -856,8 +859,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly. - * - * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! + * + * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! * Once tokens are sent through the bridge, losing this information means losing the funds! */ public async getDepositRequest( @@ -961,8 +964,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. - * - * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! + * + * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! * Once tokens are sent through the bridge, losing this information means losing the funds! */ public async deposit( @@ -1008,15 +1011,20 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ): Promise { if ((await l2Signer.getChainId()) !== relayerInfo.chainId) { throw new ArbSdkError( - `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${relayerInfo.chainId}` + `L2 signer chain id ${await l2Signer.getChainId()} does not match correct chain id ${ + relayerInfo.chainId + }` ) } - const teleporterAddresses = l2Networks[relayerInfo.chainId].teleporterAddresses + const teleporterAddresses = + l2Networks[relayerInfo.chainId].teleporterAddresses if (!teleporterAddresses) { throw new ArbSdkError( - `L2 network ${l2Networks[relayerInfo.chainId].name} does not have teleporter contracts` + `L2 network ${ + l2Networks[relayerInfo.chainId].name + } does not have teleporter contracts` ) } @@ -1048,10 +1056,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l1Address = await l1Signer.getAddress() - const l3DestinationAddress = - params.to || l1Address - const l2RefundAddress = - params.l2RefundAddress || l1Address + const l3DestinationAddress = params.to || l1Address + const l2RefundAddress = params.l2RefundAddress || l1Address const l3TicketRequest = await L1ToL2MessageCreator.getTicketCreationRequest( { From 2fc7667ba7b92e1ec66bf6857984fe4c4fa561f5 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:21:36 -0400 Subject: [PATCH 37/80] l1 gas price override --- src/lib/assetBridger/l1l3Bridger.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 91b64351f1..b6d52fc275 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -74,7 +74,7 @@ export type ManualRetryableGasParams = { l2l3TokenBridgeRetryableSize: BigNumber } -export type Erc20DepositRequestParamsGasOverrides = { +export type BaseErc20DepositRequestParamsGasOverrides = { /** * Optional L2 gas price override */ @@ -105,14 +105,19 @@ export type Erc20DepositRequestParams = { /** * Optional overrides for retryable gas parameters */ - overrides?: Erc20DepositRequestParamsGasOverrides + overrides?: BaseErc20DepositRequestParamsGasOverrides & { + /** + * Optional L1 gas price override. Used to estimate submission fees. + */ + l1GasPrice?: PercentIncrease + } } export type RelayedErc20DepositRequestParams = Erc20DepositRequestParams & { /** * Optional overrides for retryable gas parameters and relayer payment */ - overrides?: Erc20DepositRequestParamsGasOverrides & { + overrides?: BaseErc20DepositRequestParamsGasOverrides & { /** * Optional relayer payment override */ @@ -758,11 +763,14 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { }, ]) - const l1GasPrice = await l1Signer.provider!.getGasPrice() + const adjustedL1GasPrice = this._percentIncrease( + params.overrides?.l1GasPrice?.base || (await l1Signer.provider!.getGasPrice()), + params.overrides?.l1GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease + ) const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( this.l2Network.ethBridge.inbox, - this._percentIncrease(l1GasPrice, this.defaultGasPricePercentIncrease), // todo: this should have overrides + adjustedL1GasPrice, gasParams ) From fa23c2c2ed92f771c50bdc2fa4fe1277040eb729 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:41:02 -0400 Subject: [PATCH 38/80] l2 forwarder owner override --- src/lib/assetBridger/l1l3Bridger.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index b6d52fc275..2c97fd5044 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -115,13 +115,17 @@ export type Erc20DepositRequestParams = { export type RelayedErc20DepositRequestParams = Erc20DepositRequestParams & { /** - * Optional overrides for retryable gas parameters and relayer payment + * Optional overrides for retryable gas parameters, relayer payment and L2Forwarder owner */ overrides?: BaseErc20DepositRequestParamsGasOverrides & { /** * Optional relayer payment override */ relayerPayment?: PercentIncrease + /** + * Optional L2Forwarder owner override. This L2 account is able to make arbitrary calls from the L2Forwarder in order to rescue funds. + */ + l2ForwarderOwner?: string } } @@ -764,8 +768,10 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { ]) const adjustedL1GasPrice = this._percentIncrease( - params.overrides?.l1GasPrice?.base || (await l1Signer.provider!.getGasPrice()), - params.overrides?.l1GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease + params.overrides?.l1GasPrice?.base || + (await l1Signer.provider!.getGasPrice()), + params.overrides?.l1GasPrice?.percentIncrease || + this.defaultGasPricePercentIncrease ) const calculatedGasCosts = await teleporter.calculateRetryableGasCosts( @@ -898,7 +904,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) const l2ForwarderParams: L2ForwarderPredictor.L2ForwarderParamsStruct = { - owner: await l1Signer.getAddress(), // todo: add override + owner: + params.overrides?.l2ForwarderOwner || (await l1Signer.getAddress()), token: await this.getL2ERC20Address( params.erc20L1Address, l1Signer.provider! From b83cfec68da51d3868bbc3b6d79d0cfcd4ac6086 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:53:06 -0400 Subject: [PATCH 39/80] change eth deposit status type --- src/lib/assetBridger/l1l3Bridger.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2c97fd5044..2d87da705e 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -188,11 +188,11 @@ export type EthDepositStatus = { /** * Status + redemption tx receipt of the retryable ticket to L2 */ - l2RetryableStatus: L1ToL2MessageStatus + l2RetryableStatus: L1ToL2MessageWaitResult /** * Status + redemption tx receipt of the retryable ticket to L3 */ - l3RetryableStatus: L1ToL2MessageStatus + l3RetryableStatus: L1ToL2MessageWaitResult /** * Whether the teleportation has completed */ @@ -966,8 +966,6 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { }, }) - // todo: double check here that the computed l2 forwarder is correct - return { relayerInfo: { ...l2ForwarderParams, @@ -1143,8 +1141,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { if (l1l2Redeem.status != L1ToL2MessageStatus.REDEEMED) { return { - l2RetryableStatus: l1l2Redeem.status, // todo: change these to be wait results like the other bridgers - l3RetryableStatus: L1ToL2MessageStatus.NOT_YET_CREATED, + l2RetryableStatus: l1l2Redeem, + l3RetryableStatus: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } @@ -1162,8 +1160,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l2l3Redeem = await l2l3Message.getSuccessfulRedeem() return { - l2RetryableStatus: l1l2Redeem.status, - l3RetryableStatus: l2l3Redeem.status, + l2RetryableStatus: l1l2Redeem, + l3RetryableStatus: l2l3Redeem, completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, } } From 6dd417eb33ee312d52b5c06693fdcf89a93ccf71 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:53:58 -0400 Subject: [PATCH 40/80] remove old unit test file --- tests/unit/teleporter.test.ts | 187 ---------------------------------- 1 file changed, 187 deletions(-) delete mode 100644 tests/unit/teleporter.test.ts diff --git a/tests/unit/teleporter.test.ts b/tests/unit/teleporter.test.ts deleted file mode 100644 index a79e29b9de..0000000000 --- a/tests/unit/teleporter.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -// import * as dotenv from "dotenv" -// dotenv.config() -// import { expect } from "chai" -// import {TeleporterUtils} from "../../src/lib/assetBridger/teleporter" -// import { getL2Network, l1Networks, l2Networks } from "../../src/lib/dataEntities/networks" -// import { ethers, providers } from "ethers" -// import { anything, deepEqual, instance, mock, when } from "ts-mockito" -// import { L1GatewayRouter__factory } from "../../src/lib/abi/factories/L1GatewayRouter__factory" -// import { AbiCoder } from "ethers/lib/utils" -// import { DISABLED_GATEWAY } from "../../src/lib/dataEntities/constants" - -// type Provider = providers.JsonRpcProvider - -// const l3ChainId = 23011913 -// const l2ChainId = 421614 -// const l1ChainId = 11155111 -// const l1WETH = l2Networks[l2ChainId].tokenBridge.l1Weth -// const l2WETH = l2Networks[l2ChainId].tokenBridge.l2Weth -// const l3WETH = l2Networks[l3ChainId].tokenBridge.l2Weth - -// function getEnv(k: string) { -// const v = process.env[k] -// if (!v) { -// throw new Error(`Missing env var ${k}`) -// } -// return v -// } - -// function createProviderMock(chainId: number) { -// const l2ProviderMock = mock(providers.JsonRpcProvider) -// const latestBlock = 1 -// when(l2ProviderMock.getBlockNumber()).thenResolve(latestBlock) -// when(l2ProviderMock.getNetwork()).thenResolve({ -// chainId: chainId -// } as any) -// when(l2ProviderMock._isProvider).thenReturn(true) -// when(l2ProviderMock.getLogs(anything())).thenResolve([]) - -// return l2ProviderMock -// } - -// function mockFunctionCall( -// mockProvider: Provider, -// args: { data: string; to: string; response: string } -// ) { -// when( -// mockProvider.call( -// deepEqual({ -// data: args.data, -// to: args.to, -// }), -// anything() -// ) -// ).thenResolve(args.response) - -// return mockProvider -// } - -// function mockGetGateway( -// mockProvider: Provider, -// args: { router: string; token: string; gateway: string } -// ) { -// return mockFunctionCall(mockProvider, { -// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("getGateway", [args.token]), -// to: args.router, -// response: new AbiCoder().encode(["address"], [args.gateway]) -// }) -// } - -// function mockCalculateL2TokenAddress( -// mockProvider: Provider, -// args: { router: string; l1Token: string; l2Token: string } -// ) { -// return mockFunctionCall(mockProvider, { -// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("calculateL2TokenAddress", [args.l1Token]), -// to: args.router, -// response: new AbiCoder().encode(["address"], [args.l2Token]) -// }) -// } - -// function mockL1TokenToGateway( -// mockProvider: Provider, -// args: { router: string; l1Token: string; gateway: string } -// ) { -// return mockFunctionCall(mockProvider, { -// data: L1GatewayRouter__factory.createInterface().encodeFunctionData("l1TokenToGateway", [args.l1Token]), -// to: args.router, -// response: new AbiCoder().encode(["address"], [args.gateway]) -// }) -// } - -// describe('Teleporter', () => { -// let teleporter: TeleporterUtils - -// const l1Network = l1Networks[l1ChainId] -// const l2Network = l2Networks[l2ChainId] -// const l3Network = l2Networks[l3ChainId] - -// let l1ProviderMock: Provider -// let l2ProviderMock: Provider -// let l3ProviderMock: Provider - -// let l1Provider: Provider -// let l2Provider: Provider -// let l3Provider: Provider - -// beforeEach(() => { -// teleporter = new TeleporterUtils(l3Network) - -// l1ProviderMock = createProviderMock(l1Network.chainID) -// l2ProviderMock = createProviderMock(l2Network.chainID) -// l3ProviderMock = createProviderMock(l3Network.chainID) - -// // mock get gateway calls -// l1ProviderMock = mockGetGateway(l1ProviderMock, { -// router: l2Network.tokenBridge.l1GatewayRouter, -// token: l1WETH, -// gateway: l2Network.tokenBridge.l1WethGateway -// }) -// l2ProviderMock = mockGetGateway(l2ProviderMock, { -// router: l3Network.tokenBridge.l1GatewayRouter, -// token: l2WETH, -// gateway: l3Network.tokenBridge.l1WethGateway -// }) - -// // mock calculateL2TokenAddress calls -// l1ProviderMock = mockCalculateL2TokenAddress(l1ProviderMock, { -// router: l2Network.tokenBridge.l1GatewayRouter, -// l1Token: l1WETH, -// l2Token: l2WETH -// }) -// l2ProviderMock = mockCalculateL2TokenAddress(l2ProviderMock, { -// router: l3Network.tokenBridge.l1GatewayRouter, -// l1Token: l2WETH, -// l2Token: l3WETH -// }) - -// l1Provider = instance(l1ProviderMock) -// l2Provider = instance(l2ProviderMock) -// l3Provider = instance(l3ProviderMock) -// }) - -// it('should set L2 and L1 properly', () => { -// expect(teleporter.l2Network.chainID).to.equal(l2ChainId) -// expect(teleporter.l1Network.chainID).to.equal(l1ChainId) -// }) - -// it('should return correct L2 token address', async () => { -// const l2TokenAddress = await teleporter.getL2ERC20Address(l1WETH, l1Provider) -// expect(l2TokenAddress).to.equal(l2WETH) -// }) - -// it('should return correct L3 token address', async () => { -// const l3TokenAddress = await teleporter.getL3ERC20Address(l1WETH, l1Provider, l2Provider) -// expect(l3TokenAddress).to.equal(l3WETH) -// }) - -// it('should return correct L1 -> L2 gateway', async () => { -// const l1l2Gateway = await teleporter.getL1L2GatewayAddress(l1WETH, l1Provider) -// expect(l1l2Gateway).to.equal(l2Network.tokenBridge.l1WethGateway) -// }) - -// it('should return correct L2 -> L3 gateway', async () => { -// const l2l3Gateway = await teleporter.getL2L3GatewayAddress(l1WETH, l1Provider, l2Provider) -// expect(l2l3Gateway).to.equal(l3Network.tokenBridge.l1WethGateway) -// }) - -// it('should return wether a token is disabled', async () => { -// // mock l1TokenToGateway calls -// l1ProviderMock = mockL1TokenToGateway(l1ProviderMock, { -// router: l2Network.tokenBridge.l1GatewayRouter, -// l1Token: l1WETH, -// gateway: DISABLED_GATEWAY -// }) -// l1Provider = instance(l1ProviderMock) - -// l2ProviderMock = mockL1TokenToGateway(l2ProviderMock, { -// router: l3Network.tokenBridge.l1GatewayRouter, -// l1Token: l2WETH, -// gateway: l3Network.tokenBridge.l1WethGateway -// }) -// l2Provider = instance(l2ProviderMock) - -// expect(await teleporter.l1TokenIsDisabled(l1WETH, l1Provider)).to.equal(true) -// expect(await teleporter.l2TokenIsDisabled(l2WETH, l2Provider)).to.equal(false) -// }) -// }) From f75e4c29663b62a2bc1bd32fb892eeadf2ef5126 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:04:59 -0400 Subject: [PATCH 41/80] update index.ts --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index bc67a62ebf..93f949b226 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ /* eslint-env node */ 'use strict' -export { Erc20L1L3Bridger } from './lib/assetBridger/l1l3Bridger' +export { Erc20L1L3Bridger, RelayedErc20L1L3Bridger, EthL1L3Bridger } from './lib/assetBridger/l1l3Bridger' export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { From 5b2f5310fbaa2d2cdfa82a112ec3668d8f996bce Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:28:07 -0400 Subject: [PATCH 42/80] remove .sub(1) resulting from old contract optimization strategy --- tests/integration/l1l3Bridger.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index b65379afa3..07dce7775d 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -278,9 +278,7 @@ describe('L1 to L3 Bridging', () => { const l3Balance = await l3Token.balanceOf(l3Recipient) - if (!l3Balance.eq(ethers.utils.parseEther('1').sub(1))) { - throw new Error('L3 balance is incorrect') - } + expect(l3Balance.eq(ethers.utils.parseEther('1'))).to.be.true }) it('should report correct status when second leg is frontran', async () => { From 3751add943b6f67a1c0f2d6ddc64ece1a8cdf267 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:25:33 -0400 Subject: [PATCH 43/80] rename Teleporter to L1Teleporter --- src/lib/assetBridger/l1l3Bridger.ts | 22 +++++++++++----------- tests/integration/l1l3Bridger.test.ts | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2d87da705e..81fb62a2a4 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -5,14 +5,14 @@ import { ERC20 } from '../abi/ERC20' import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' import { L2GatewayToken } from '../abi/L2GatewayToken' -import { Teleporter } from '../abi/Teleporter' +import { L1Teleporter } from '../abi/L1Teleporter' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' import { L2Forwarder__factory } from '../abi/factories/L2Forwarder__factory' import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factory' -import { Teleporter__factory } from '../abi/factories/Teleporter__factory' +import { L1Teleporter__factory } from '../abi/factories/L1Teleporter__factory' import { Address } from '../dataEntities/address' import { DISABLED_GATEWAY } from '../dataEntities/constants' import { ArbSdkError } from '../dataEntities/errors' @@ -172,7 +172,7 @@ type BaseErc20DepositStatus = { } /** - * When using the L1 Teleporter the second leg is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. + * When using the L1Teleporter the second leg is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. * This is because the retryable could possibly be frontran and the teleportation will still succeed. */ export type Erc20DepositStatus = BaseErc20DepositStatus & { @@ -627,7 +627,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l1Provider: Provider, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { params.overrides = params.overrides || {} let manualGasParams = params.overrides.manualGasParams @@ -694,12 +694,12 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Bridge ERC20 tokens from L1 to L3 using the L1 Teleporter contract. + * Bridge ERC20 tokens from L1 to L3 using the L1Teleporter contract. */ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to approve tokens for teleportation. - * The tokens will be approved to be spent by the Teleporter on L1. + * The tokens will be approved to be spent by the L1Teleporter. */ public async getApproveTokenRequest( params: TokenApproveParams @@ -718,7 +718,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Approve tokens for teleportation. The tokens will be approved to be spent by the L1 `Teleporter` + * Approve tokens for teleportation. The tokens will be approved to be spent by the `L1Teleporter` */ public async approveToken( params: TokenApproveParams, @@ -732,7 +732,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Get a tx request to deposit tokens to L3 through the L1 Teleporter contract. + * Get a tx request to deposit tokens to L3 through the L1Teleporter contract. */ public async getDepositRequest( params: Erc20DepositRequestParams, @@ -751,7 +751,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) - const teleporter = Teleporter__factory.connect( + const teleporter = L1Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, l1Signer ) @@ -788,7 +788,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Deposit tokens to L3 through the L1 Teleporter contract. + * Deposit tokens to L3 through the L1Teleporter contract. */ public async deposit( params: Erc20DepositRequestParams, @@ -919,7 +919,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // figure out how much extra ETH we should pass along through the token bridge // _populateGasParams has already applied a percent increase to gas prices - const teleporter = Teleporter__factory.connect( + const teleporter = L1Teleporter__factory.connect( this.teleporterAddresses.l1Teleporter, l1Signer ) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 07dce7775d..49bdea8884 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -3,7 +3,7 @@ import { Address, Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' import { MockToken } from '../../src/lib/abi/MockToken' -import { Teleporter__factory } from '../../src/lib/abi/factories/Teleporter__factory' +import { L1Teleporter__factory } from '../../src/lib/abi/factories/L1Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { BigNumber, ethers } from 'ethers' import { @@ -113,7 +113,7 @@ describe('L1 to L3 Bridging', () => { const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() const l2ForwarderFactory = await l2ContractsDeployer.factory() - const l1Teleporter = await new Teleporter__factory(setup.l1Signer).deploy( + const l1Teleporter = await new L1Teleporter__factory(setup.l1Signer).deploy( l2ForwarderFactory, l2ForwarderImplAddr ) From 0185e294ae5dfed5400315752602c7258494dc13 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:47:09 -0400 Subject: [PATCH 44/80] remove comment --- tests/integration/l1l3Bridger.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 49bdea8884..bbddbf94e8 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -234,10 +234,6 @@ describe('L1 to L3 Bridging', () => { ).wait() }) - // should throw if gas overrides not passed when using non default gateway - // test relayer stuff - // don't need to test rescue here i think - // todo: check status at each step, do this for other happy path tests too it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) From 84f6ed8f827e1da254c559624679f9c46c969494 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:08:37 -0400 Subject: [PATCH 45/80] fmt --- src/index.ts | 6 +++++- tests/integration/l1l3Bridger.test.ts | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 93f949b226..fb0d90cbac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,11 @@ /* eslint-env node */ 'use strict' -export { Erc20L1L3Bridger, RelayedErc20L1L3Bridger, EthL1L3Bridger } from './lib/assetBridger/l1l3Bridger' +export { + Erc20L1L3Bridger, + RelayedErc20L1L3Bridger, + EthL1L3Bridger, +} from './lib/assetBridger/l1l3Bridger' export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index bbddbf94e8..a22d99a2a7 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -113,10 +113,9 @@ describe('L1 to L3 Bridging', () => { const l2ForwarderImplAddr = await l2ContractsDeployer.implementation() const l2ForwarderFactory = await l2ContractsDeployer.factory() - const l1Teleporter = await new L1Teleporter__factory(setup.l1Signer).deploy( - l2ForwarderFactory, - l2ForwarderImplAddr - ) + const l1Teleporter = await new L1Teleporter__factory( + setup.l1Signer + ).deploy(l2ForwarderFactory, l2ForwarderImplAddr) await l1Teleporter.deployed() // set the teleporter on the l2Network @@ -234,7 +233,6 @@ describe('L1 to L3 Bridging', () => { ).wait() }) - // todo: check status at each step, do this for other happy path tests too it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) From 98b9526391aa6b0bced0c48b386f3547231553c8 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:23:24 -0400 Subject: [PATCH 46/80] test non default gateway throw --- src/lib/assetBridger/l1l3Bridger.ts | 14 ++++++-------- tests/integration/l1l3Bridger.test.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 81fb62a2a4..e366e62994 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -564,7 +564,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } } - // see if there are any other calls to the l2 forwarder after the first leg redeem + // see if there are any calls to the l2 forwarder after the first leg redeem const bridgedToL3Event = await this._findBridgedToL3Event( this.getRecipientFromParentBridgeTx(depositTxReceipt), firstLegRedeem.l2TxReceipt.blockNumber, @@ -628,9 +628,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l2Provider: Provider, l3Provider: Provider ): Promise { - params.overrides = params.overrides || {} - - let manualGasParams = params.overrides.manualGasParams + let manualGasParams = params.overrides?.manualGasParams if (!manualGasParams) { // make sure both gateways are default if ( @@ -660,13 +658,13 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { return { ...manualGasParams, l2GasPrice: this._percentIncrease( - params.overrides.l2GasPrice?.base || (await l2Provider.getGasPrice()), - params.overrides.l2GasPrice?.percentIncrease || + params.overrides?.l2GasPrice?.base || (await l2Provider.getGasPrice()), + params.overrides?.l2GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease ), l3GasPrice: this._percentIncrease( - params.overrides.l3GasPrice?.base || (await l3Provider.getGasPrice()), - params.overrides.l3GasPrice?.percentIncrease || + params.overrides?.l3GasPrice?.base || (await l3Provider.getGasPrice()), + params.overrides?.l3GasPrice?.percentIncrease || this.defaultGasPricePercentIncrease ), } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index a22d99a2a7..ae4c3be6c3 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -233,6 +233,20 @@ describe('L1 to L3 Bridging', () => { ).wait() }) + it('should throw if using non-default gateway and gas overrides not passed', async () => { + try { + await l1l3Bridger.getDepositRequest({ + erc20L1Address: setup.l2Network.tokenBridge.l1Weth, + amount: BigNumber.from(1) + }, setup.l1Signer, setup.l2Signer.provider!, setup.l3Signer.provider!) + throw new Error() + } catch (e: any) { + expect(e.message).to.eq('Cannot estimate gas for custom l1l2 gateway, please provide gas params') + } + + // l1 to l2 default but l2 to l3 gateway non default, we have to register a custom one + }) + it('happy path', async () => { const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) From fa841a86bec5dd73f9694f8331b0655f33fed944 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:23:58 -0400 Subject: [PATCH 47/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index e366e62994..606fb8c5bc 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -632,7 +632,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { if (!manualGasParams) { // make sure both gateways are default if ( - !(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider)) // todo: test this + !(await this.isL1L2GatewayDefault(params.erc20L1Address, l1Provider)) ) { throw new ArbSdkError( `Cannot estimate gas for custom l1l2 gateway, please provide gas params` From e741d07e75fcaa2c7e16409b7efe9108febf21d2 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:43:38 -0400 Subject: [PATCH 48/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 606fb8c5bc..f0184b116b 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -173,7 +173,7 @@ type BaseErc20DepositStatus = { /** * When using the L1Teleporter the second leg is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. - * This is because the retryable could possibly be frontran and the teleportation will still succeed. + * This is because the retryable could possibly be frontrun and the teleportation will still succeed. */ export type Erc20DepositStatus = BaseErc20DepositStatus & { /** From 10abd8fa14144697533c083a48e2b53fe523b55d Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:43:57 -0400 Subject: [PATCH 49/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index f0184b116b..72b7ccc89e 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -287,7 +287,7 @@ class BaseL1L3Bridger { } /** - * Check the signer/provider matches the l2Network, throws if not + * Check the signer/provider matches the l3Network, throws if not * @param sop */ protected async _checkL3Network(sop: SignerOrProvider): Promise { From 0121ce12c9b85439c27babf0ee4f904b341c472c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:44:32 -0400 Subject: [PATCH 50/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 72b7ccc89e..aaa3822fc5 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -375,7 +375,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Get the address of the L1 <-> L2 gateway on L1 given an L1 token address + * Given an L1 token's address, get the address of the token's L1 <-> L2 gateway on L1 */ public async getL1L2GatewayAddress( erc20L1Address: string, From a7b1c4c14455f324eb394e00ddeb3688b48496fe Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:45:46 -0400 Subject: [PATCH 51/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index aaa3822fc5..be30b3a15f 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -460,7 +460,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the L3 token contract at the provided address - * Note: This function just returns a typed ethers object for the provided address, it doesnt + * Note: This function just returns a typed ethers object for the provided address, it doesn't * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. */ From cca52aacc3d33ff035a0c65c3bc4a57be221559d Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:45:52 -0400 Subject: [PATCH 52/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index be30b3a15f..5c8e6dc2fa 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -447,7 +447,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the L2 token contract at the provided address - * Note: This function just returns a typed ethers object for the provided address, it doesnt + * Note: This function just returns a typed ethers object for the provided address, it doesn't * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. */ From 1d9665018bd6ef914e222beb7fe279876e16e304 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:46:12 -0400 Subject: [PATCH 53/80] Update src/lib/assetBridger/l1l3Bridger.ts Co-authored-by: Daniel Goldman --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 5c8e6dc2fa..d4e76555ac 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -437,7 +437,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the L1 token contract at the provided address - * Note: This function just returns a typed ethers object for the provided address, it doesnt + * Note: This function just returns a typed ethers object for the provided address, it doesn't * check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity * of any of the underlying functions on that contract. */ From 50662b1331520633b0f9cfc6bdd8f428dd981adc Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:21:16 -0400 Subject: [PATCH 54/80] remove 'value' from getApproveTokenRequest --- src/lib/assetBridger/l1l3Bridger.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index d4e76555ac..b9392f9a06 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -701,7 +701,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { */ public async getApproveTokenRequest( params: TokenApproveParams - ): Promise>> { + ): Promise>> { const iErc20Interface = ERC20__factory.createInterface() const data = iErc20Interface.encodeFunctionData('approve', [ this.teleporterAddresses.l1Teleporter, @@ -711,7 +711,6 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { return { to: params.erc20L1Address, data, - value: BigNumber.from(0), } } @@ -849,7 +848,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getApproveTokenRequest( params: TokenApproveParams, l1Provider: Provider - ): Promise>> { + ): Promise>> { return new Erc20Bridger(this.l2Network).getApproveTokenRequest({ ...params, l1Provider, From 13dc195a1d734b5f3409d62db733d122dc69aa64 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:26:40 -0400 Subject: [PATCH 55/80] use hexDataSlice --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index b9392f9a06..e936b92858 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -536,7 +536,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { const bytes32UserTo = log.topics[3] // parse address - return ethers.utils.getAddress(bytes32UserTo.slice(26)) + return ethers.utils.getAddress(ethers.utils.hexDataSlice(bytes32UserTo, 12)) } /** From 0c9d2ea7367fa77e4bf5a63eb014973f27591c1d Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:33:35 -0400 Subject: [PATCH 56/80] comment --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index e936b92858..f241e6f56a 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -403,7 +403,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) return await L1GatewayRouter__factory.connect( - this.l3Network.tokenBridge.l1GatewayRouter, + this.l3Network.tokenBridge.l1GatewayRouter, // note: this is the L2 <-> L3 gateway router on L2 l2Provider ).getGateway(l2Token) } From 9aeaab28a2525aa0f7b0f3a562eb1ae323c8187f Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:20:15 -0400 Subject: [PATCH 57/80] reuse erc20bridger to get l2 and l3 token addrs --- src/lib/assetBridger/l1l3Bridger.ts | 32 ++++------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index f241e6f56a..05e7acefda 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -329,16 +329,11 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Get the corresponding L2 token address for the provided L1 token */ - public async getL2ERC20Address( + public getL2ERC20Address( erc20L1Address: string, l1Provider: Provider ): Promise { - await this._checkL1Network(l1Provider) - return this._getChildErc20Address( - erc20L1Address, - l1Provider, - this.l2Network - ) + return new Erc20Bridger(this.l2Network).getL2ERC20Address(erc20L1Address, l1Provider) } /** @@ -349,29 +344,10 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - await this._checkL2Network(l2Provider) - return this._getChildErc20Address( + return new Erc20Bridger(this.l3Network).getL2ERC20Address( await this.getL2ERC20Address(erc20L1Address, l1Provider), - l2Provider, - this.l3Network - ) - } - - /** - * Given an erc20 address on a parent network, get the corresponding erc20 address on a child network - */ - private _getChildErc20Address( - erc20ParentAddress: string, - parentProvider: Provider, - childNetwork: L2Network - ) { - // assume that provider has been checked - const parentGatewayRouter = L1GatewayRouter__factory.connect( - childNetwork.tokenBridge.l1GatewayRouter, - parentProvider + l2Provider ) - - return parentGatewayRouter.calculateL2TokenAddress(erc20ParentAddress) } /** From 95dd404825dc5e696fdfa1ebdb4153ca624bbca4 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:15:00 -0400 Subject: [PATCH 58/80] leg -> step --- src/lib/assetBridger/l1l3Bridger.ts | 38 ++++++++++++------------- tests/integration/l1l3Bridger.test.ts | 40 +++++++++++++-------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 05e7acefda..cd1d050388 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -172,7 +172,7 @@ type BaseErc20DepositStatus = { } /** - * When using the L1Teleporter the second leg is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. + * When using the L1Teleporter the second step is a retryable tx, so this type includes the status of that tx as well as the base type's `l2ForwarderCall`. * This is because the retryable could possibly be frontrun and the teleportation will still succeed. */ export type Erc20DepositStatus = BaseErc20DepositStatus & { @@ -529,50 +529,50 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { await this._checkL3Network(l3Provider) const l1l2Messages = await depositTxReceipt.getL1ToL2Messages(l2Provider) - const firstLegRedeem = await l1l2Messages[0].getSuccessfulRedeem() + const firstStepRedeem = await l1l2Messages[0].getSuccessfulRedeem() - if (firstLegRedeem.status !== L1ToL2MessageStatus.REDEEMED) { + if (firstStepRedeem.status !== L1ToL2MessageStatus.REDEEMED) { return { - bridgeToL2: firstLegRedeem, + bridgeToL2: firstStepRedeem, l2ForwarderCall: undefined, bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } - // see if there are any calls to the l2 forwarder after the first leg redeem + // see if there are any calls to the l2 forwarder after the first step redeem const bridgedToL3Event = await this._findBridgedToL3Event( this.getRecipientFromParentBridgeTx(depositTxReceipt), - firstLegRedeem.l2TxReceipt.blockNumber, + firstStepRedeem.l2TxReceipt.blockNumber, l2Provider ) - // second leg has not completed + // second step has not completed if (!bridgedToL3Event) { return { - bridgeToL2: firstLegRedeem, + bridgeToL2: firstStepRedeem, l2ForwarderCall: undefined, bridgeToL3: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } - // second leg has completed - const secondLegTxReceipt = new L1ContractCallTransactionReceipt( + // second step has completed + const secondStepTxReceipt = new L1ContractCallTransactionReceipt( await l2Provider.getTransactionReceipt(bridgedToL3Event.transactionHash) ) - const thirdLegMessage = ( - await secondLegTxReceipt.getL1ToL2Messages(l3Provider) + const thirdStepMessage = ( + await secondStepTxReceipt.getL1ToL2Messages(l3Provider) )[0] - const thirdLegRedeem = await thirdLegMessage.getSuccessfulRedeem() + const thirdStepRedeem = await thirdStepMessage.getSuccessfulRedeem() return { - bridgeToL2: firstLegRedeem, - l2ForwarderCall: secondLegTxReceipt, - bridgeToL3: thirdLegRedeem, - completed: thirdLegRedeem.status === L1ToL2MessageStatus.REDEEMED, + bridgeToL2: firstStepRedeem, + l2ForwarderCall: secondStepTxReceipt, + bridgeToL3: thirdStepRedeem, + completed: thirdStepRedeem.status === L1ToL2MessageStatus.REDEEMED, } } @@ -797,13 +797,13 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) - const secondLegRetryableRedeem = await ( + const secondStepRetryableRedeem = await ( await depositTxReceipt.getL1ToL2Messages(l2Provider) )[1].getSuccessfulRedeem() return { ...baseStatus, - retryableL2ForwarderCall: secondLegRetryableRedeem, + retryableL2ForwarderCall: secondStepRetryableRedeem, } } } diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index ae4c3be6c3..b07af779dc 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -289,7 +289,7 @@ describe('L1 to L3 Bridging', () => { expect(l3Balance.eq(ethers.utils.parseEther('1'))).to.be.true }) - it('should report correct status when second leg is frontran', async () => { + it('should report correct status when second step is frontran', async () => { const adjustedL3GasPrice = ( await setup.l3Signer.provider!.getGasPrice() ).mul(3) @@ -300,7 +300,7 @@ describe('L1 to L3 Bridging', () => { overrides: { manualGasParams: { ...l1l3Bridger.defaultRetryableGasParams, - l2ForwarderFactoryGasLimit: BigNumber.from(10), // make sure the second leg retryable fails + l2ForwarderFactoryGasLimit: BigNumber.from(10), // make sure the second step retryable fails }, l3GasPrice: { base: adjustedL3GasPrice, @@ -315,7 +315,7 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositTx.wait() - // poll status until first leg completes + // poll status until first step completes await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, @@ -326,20 +326,20 @@ describe('L1 to L3 Bridging', () => { }, 1000) // make sure we have FUNDS_DEPOSITED_ON_L2 and undefined - const statusAfterLeg1 = await l1l3Bridger.getDepositStatus( + const statusAfterStep1 = await l1l3Bridger.getDepositStatus( depositReceipt, l2JsonRpcProvider, setup.l3Signer.provider! ) - expect(statusAfterLeg1.retryableL2ForwarderCall.status).to.eq( + expect(statusAfterStep1.retryableL2ForwarderCall.status).to.eq( L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 ) - expect(statusAfterLeg1.l2ForwarderCall).to.be.undefined - expect(statusAfterLeg1.bridgeToL3.status).to.eq( + expect(statusAfterStep1.l2ForwarderCall).to.be.undefined + expect(statusAfterStep1.bridgeToL3.status).to.eq( L1ToL2MessageStatus.NOT_YET_CREATED ) - // relay the second leg (use the RelayedErc20L1L3Bridger to do this) + // relay the second step (use the RelayedErc20L1L3Bridger to do this) const l1SignerAddr = await setup.l1Signer.getAddress() const relayTx = await RelayedErc20L1L3Bridger.relayDeposit( { @@ -362,21 +362,21 @@ describe('L1 to L3 Bridging', () => { await relayTx.wait() // make sure we get FUNDS_DEPOSITED_ON_L2 and the relay tx receipt - const statusAfterLeg2 = await l1l3Bridger.getDepositStatus( + const statusAfterStep2 = await l1l3Bridger.getDepositStatus( depositReceipt, l2JsonRpcProvider, setup.l3Signer.provider! ) - expect(statusAfterLeg2.bridgeToL2.status).to.eq( + expect(statusAfterStep2.bridgeToL2.status).to.eq( L1ToL2MessageStatus.REDEEMED ) - expect(statusAfterLeg2.retryableL2ForwarderCall.status).to.eq( + expect(statusAfterStep2.retryableL2ForwarderCall.status).to.eq( L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 ) - expect(statusAfterLeg2.l2ForwarderCall?.transactionHash).to.eq( + expect(statusAfterStep2.l2ForwarderCall?.transactionHash).to.eq( relayTx.hash ) - expect(statusAfterLeg2.bridgeToL3.status).to.eq( + expect(statusAfterStep2.bridgeToL3.status).to.eq( L1ToL2MessageStatus.NOT_YET_CREATED ) @@ -390,19 +390,19 @@ describe('L1 to L3 Bridging', () => { return status.completed }, 1000) - const statusAfterLeg3 = await l1l3Bridger.getDepositStatus( + const statusAfterStep3 = await l1l3Bridger.getDepositStatus( depositReceipt, l2JsonRpcProvider, setup.l3Signer.provider! ) - expect(statusAfterLeg3.completed).to.be.true - expect(statusAfterLeg3.retryableL2ForwarderCall.status).to.eq( + expect(statusAfterStep3.completed).to.be.true + expect(statusAfterStep3.retryableL2ForwarderCall.status).to.eq( L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 ) - expect(statusAfterLeg3.l2ForwarderCall?.transactionHash).to.eq( + expect(statusAfterStep3.l2ForwarderCall?.transactionHash).to.eq( relayTx.hash ) - expect(statusAfterLeg3.bridgeToL3.status).to.eq( + expect(statusAfterStep3.bridgeToL3.status).to.eq( L1ToL2MessageStatus.REDEEMED ) }) @@ -447,7 +447,7 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositResult.tx.wait() - // wait until first leg finishes + // wait until first step finishes await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, @@ -485,7 +485,7 @@ describe('L1 to L3 Bridging', () => { ) ).to.be.not.undefined - // wait for third leg to finish + // wait for third step to finish await poll(async () => { const status = await l1l3Bridger.getDepositStatus( depositReceipt, From 8885e15e9483d974947d1b11e6c5bbb9771fa63c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:15:29 -0400 Subject: [PATCH 59/80] use teleporter npm package --- package.json | 1 + scripts/genAbi.js | 9 +- yarn.lock | 581 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 583 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f77789d7ac..9e7cea915b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "ethers": "^5.1.0" }, "devDependencies": { + "l1-l3-teleport-contracts": "^0.1.0", "@arbitrum/nitro-contracts": "1.0.1", "@arbitrum/token-bridge-contracts": "1.0.0-beta.0", "@nomiclabs/hardhat-ethers": "^2.0.4", diff --git a/scripts/genAbi.js b/scripts/genAbi.js index 7c4419ee1f..da26d6864c 100644 --- a/scripts/genAbi.js +++ b/scripts/genAbi.js @@ -11,8 +11,8 @@ async function main() { const cwd = process.cwd() const nitroPath = getPackagePath('@arbitrum/nitro-contracts') - const teleporterPath = getPackagePath('arb-teleporter') // TODO: just symlinked to the repo on my machine const peripheralsPath = getPackagePath('@arbitrum/token-bridge-contracts') + const teleporterPath = getPackagePath('l1-l3-teleport-contracts') console.log('Compiling paths.') @@ -33,13 +33,8 @@ async function main() { cwd: nitroPath, }) - console.log('building peripherals') - execSync(`${npmExec} && ${npmExec} run hardhat compile`, { - cwd: peripheralsPath, - }) - console.log('building teleporter') - execSync(`${npmExec} run hardhat compile`, { + execSync(`${npmExec} && ${npmExec} run hardhat compile --config ./hardhat.config.js`, { cwd: teleporterPath, }) diff --git a/yarn.lock b/yarn.lock index 9ededb2520..7420137e01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,6 +49,18 @@ optionalDependencies: "@openzeppelin/upgrades-core" "^1.7.6" +"@arbitrum/token-bridge-contracts@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@arbitrum/token-bridge-contracts/-/token-bridge-contracts-1.1.0.tgz#59edd3d864737cf15f331597cac3498c8a7ceeb8" + integrity sha512-UEXF5FT2Rus37X4uKBJkaloVITjz/BOeoF/FDpP2hgR0Q3gIgaDenhwY+hKX8donU24X1OacSFW5cFJlpKK6rg== + dependencies: + "@arbitrum/nitro-contracts" "^1.0.0-beta.8" + "@offchainlabs/upgrade-executor" "1.1.0-beta.0" + "@openzeppelin/contracts" "4.8.3" + "@openzeppelin/contracts-upgradeable" "4.8.3" + optionalDependencies: + "@openzeppelin/upgrades-core" "^1.24.1" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1107,6 +1119,14 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz#131b0da1b71680d5a01569f916ae878229d326d3" integrity sha512-A2gZAGB6kUvLx+kzM92HKuUF33F1FSe90L0TmkXkT2Hh0OKRpvWZURUSU2nghD2yC4DzfEZ3DftfeHGvZ2JTUw== +"@offchainlabs/upgrade-executor@1.1.0-beta.0": + version "1.1.0-beta.0" + resolved "https://registry.yarnpkg.com/@offchainlabs/upgrade-executor/-/upgrade-executor-1.1.0-beta.0.tgz#c4b1375176546a18aaef01a43956abfb58250e0a" + integrity sha512-mpn6PHjH/KDDjNX0pXHEKdyv8m6DVGQiI2nGzQn0JbM1nOSHJpWx6fvfjtH7YxHJ6zBZTcsKkqGkFKDtCfoSLw== + dependencies: + "@openzeppelin/contracts" "4.7.3" + "@openzeppelin/contracts-upgradeable" "4.7.3" + "@openzeppelin/contracts-upgradeable@3.4.2": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3" @@ -1117,6 +1137,16 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz#90d9e47bacfd8693bfad0ac8a394645575528d05" integrity sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA== +"@openzeppelin/contracts-upgradeable@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" + integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== + +"@openzeppelin/contracts-upgradeable@4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" + integrity sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg== + "@openzeppelin/contracts@3.4.2": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" @@ -1127,6 +1157,30 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.5.0.tgz#3fd75d57de172b3743cdfc1206883f56430409cc" integrity sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA== +"@openzeppelin/contracts@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" + integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== + +"@openzeppelin/contracts@4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a" + integrity sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg== + +"@openzeppelin/upgrades-core@^1.24.1": + version "1.31.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.31.0.tgz#cc262e2618d90540a8ad4dfafbdf06afdb17b8fa" + integrity sha512-E1Cz8lVpo2mnBeFWxiGDLWtuTYMFNTEWwbnhle4dZ+5UHX6xTRO+Q/CaWBHm33HHhuuiUbRwgGNnAR9zOu+fyQ== + dependencies: + cbor "^9.0.0" + chalk "^4.1.0" + compare-versions "^6.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + minimist "^1.2.7" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.51" + "@openzeppelin/upgrades-core@^1.7.6": version "1.14.2" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.14.2.tgz#1acf6560dbe42b5e68fdffe6e6979b90fa33bb56" @@ -1688,11 +1742,43 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlast@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.3.tgz#4e4b375de5adf4897fed155e2d2771564865cc3b" + integrity sha512-kcBubumjciBg4JKp5KTKtI7ec7tRefPk88yjkWJwaVKYd9QfTaxcsOxoMNKd7iBr447zCfDV0z1kOF47umv42g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -1741,6 +1827,11 @@ audit-ci@^6.3.0: semver "^7.0.0" yargs "^17.0.0" +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + axios@^0.21.3: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -1946,6 +2037,15 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1973,6 +2073,13 @@ cbor@^8.0.0: dependencies: nofilter "^3.1.0" +cbor@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" + integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== + dependencies: + nofilter "^3.1.0" + chai@^4.2.0: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" @@ -2170,6 +2277,11 @@ compare-versions@^4.0.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== +compare-versions@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a" + integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2348,6 +2460,24 @@ deferred-leveldown@~5.3.0: abstract-leveldown "~6.2.1" inherits "^2.0.3" +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2496,6 +2626,76 @@ errno@~0.1.1: dependencies: prr "~1.0.1" +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.5" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.2" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.13" + +es-set-tostringtag@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" + integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== + dependencies: + get-intrinsic "^1.2.2" + has-tostringtag "^1.0.0" + hasown "^2.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es6-error@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" @@ -2970,6 +3170,13 @@ follow-redirects@^1.14.9: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -3057,11 +3264,31 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + functional-red-black-tree@^1.0.1, functional-red-black-tree@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3086,6 +3313,16 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -3103,6 +3340,14 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3157,6 +3402,13 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -3169,6 +3421,13 @@ globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.11, graceful-fs@^4.2.0: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -3250,6 +3509,11 @@ hardhat@^2.6.6, hardhat@^2.8.3: uuid "^8.3.2" ws "^7.4.6" +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3260,11 +3524,30 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1: +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3297,6 +3580,13 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -3413,6 +3703,15 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +internal-slot@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" + integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== + dependencies: + get-intrinsic "^1.2.2" + hasown "^2.0.0" + side-channel "^1.0.4" + io-ts@1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" @@ -3420,6 +3719,22 @@ io-ts@1.10.4: dependencies: fp-ts "^1.0.0" +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3427,6 +3742,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -3441,6 +3769,13 @@ is-core-module@^2.8.1: dependencies: has "^1.0.3" +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -3468,6 +3803,18 @@ is-hex-prefixed@1.0.0: resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -3478,11 +3825,47 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3493,6 +3876,13 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -3505,6 +3895,11 @@ is-wsl@^2.1.1: dependencies: is-docker "^2.0.0" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3697,6 +4092,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +l1-l3-teleport-contracts@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/l1-l3-teleport-contracts/-/l1-l3-teleport-contracts-0.1.0.tgz#912e60d715293872534b7138f6ebab56a2a70e46" + integrity sha512-geFG8Nxef7qX5R0r5b9WFcyZ+Ng42KfjNFhnXrkMPRF+rR5R3vs1yrndR8TpMZwNbNmoY/942I8o/127eiwrdw== + dependencies: + "@arbitrum/token-bridge-contracts" "1.1.0" + level-codec@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" @@ -4023,6 +4425,11 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -4186,11 +4593,31 @@ nyc@^15.1.0: test-exclude "^6.0.0" yargs "^15.0.2" +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + obliterator@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.3.tgz#17a7840d562c7c61eb399f4905f0e4d3b22d1d3f" @@ -4599,6 +5026,15 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -4703,6 +5139,16 @@ rustbn.js@~0.2.0: resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -4713,6 +5159,15 @@ safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4766,6 +5221,25 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -4891,6 +5365,13 @@ solidity-ast@^0.4.15: resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.32.tgz#ba613ca24c7c79007798033e8a0f32a71285f09e" integrity sha512-vCx17410X+NMnpLVyg6ix4NMCHFIkvWrJb1rPBBeQYEQChX93Zgb9WB9NaIY4zpsr3Q8IvAfohw+jmuBzGf8OQ== +solidity-ast@^0.4.51: + version "0.4.52" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.52.tgz#9f1a9abc7e5ba28bbf91146ecd07aec7e70f3c85" + integrity sha512-iOya9BSiB9jhM8Vf40n8lGELGzwrUc57rl5BhfNtJ5cvAaMvRcNlHeAMNvqJJyjoUnczqRbHqdivEqK89du3Cw== + dependencies: + array.prototype.findlast "^1.2.2" + solidity-comments-extractor@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" @@ -4972,6 +5453,33 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5273,6 +5781,45 @@ typechain@7.0.0: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -5317,6 +5864,16 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.4.tgz#fa95c257e88f85614915b906204b9623d4fa340d" integrity sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA== +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + unbzip2-stream@1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -5400,11 +5957,33 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-typed-array@^1.1.11, which-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.4" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + which@2.0.2, which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From a227816afd2fec989b8d25c58e9c96a7ac44a984 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:30:19 -0400 Subject: [PATCH 60/80] add sepolia teleporter deployment --- src/lib/dataEntities/networks.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/dataEntities/networks.ts b/src/lib/dataEntities/networks.ts index 6d8f90ff88..e66b233b2a 100644 --- a/src/lib/dataEntities/networks.ts +++ b/src/lib/dataEntities/networks.ts @@ -280,6 +280,10 @@ export const l2Networks: L2Networks = { 421614: { chainID: 421614, confirmPeriodBlocks: 20, + teleporterAddresses: { + l1Teleporter: "0x33bd25e6156BCF698B8F2Ad82005e710037403f4", + l2ForwarderFactory: "0xB14C63bb526Db2A49b835ca954923dE7DE3968e2", + }, ethBridge: { bridge: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9', inbox: '0xaAe29B0366299461418F5324a79Afc425BE5ae21', From 950267315be25b82620b8577245f8c83e6c45692 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:59:52 -0400 Subject: [PATCH 61/80] use parseLog --- src/lib/assetBridger/l1l3Bridger.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index cd1d050388..96fbb1dfd9 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -500,19 +500,16 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { public getRecipientFromParentBridgeTx( depositTxReceipt: L1ContractCallTransactionReceipt ) { + const iface = L1GatewayRouter__factory.createInterface() const topic0 = - L1GatewayRouter__factory.createInterface().getEventTopic('TransferRouted') + iface.getEventTopic('TransferRouted') const log = depositTxReceipt.logs.find(x => x.topics[0] === topic0) if (!log) { throw new ArbSdkError(`Could not find TransferRouted event in tx receipt`) } - // topic 3 is "_userTo" - const bytes32UserTo = log.topics[3] - - // parse address - return ethers.utils.getAddress(ethers.utils.hexDataSlice(bytes32UserTo, 12)) + return iface.parseLog(log).args._userTo } /** From d6cbb4584460799e8ddbc39fc2c3c72d4d84173a Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:00:49 -0400 Subject: [PATCH 62/80] format --- src/lib/assetBridger/l1l3Bridger.ts | 10 ++++++---- tests/integration/l1l3Bridger.test.ts | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 96fbb1dfd9..57acad7f6f 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -333,7 +333,10 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { erc20L1Address: string, l1Provider: Provider ): Promise { - return new Erc20Bridger(this.l2Network).getL2ERC20Address(erc20L1Address, l1Provider) + return new Erc20Bridger(this.l2Network).getL2ERC20Address( + erc20L1Address, + l1Provider + ) } /** @@ -351,7 +354,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } /** - * Given an L1 token's address, get the address of the token's L1 <-> L2 gateway on L1 + * Given an L1 token's address, get the address of the token's L1 <-> L2 gateway on L1 */ public async getL1L2GatewayAddress( erc20L1Address: string, @@ -501,8 +504,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { depositTxReceipt: L1ContractCallTransactionReceipt ) { const iface = L1GatewayRouter__factory.createInterface() - const topic0 = - iface.getEventTopic('TransferRouted') + const topic0 = iface.getEventTopic('TransferRouted') const log = depositTxReceipt.logs.find(x => x.topics[0] === topic0) if (!log) { diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index b07af779dc..93f617eb36 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -235,13 +235,20 @@ describe('L1 to L3 Bridging', () => { it('should throw if using non-default gateway and gas overrides not passed', async () => { try { - await l1l3Bridger.getDepositRequest({ - erc20L1Address: setup.l2Network.tokenBridge.l1Weth, - amount: BigNumber.from(1) - }, setup.l1Signer, setup.l2Signer.provider!, setup.l3Signer.provider!) + await l1l3Bridger.getDepositRequest( + { + erc20L1Address: setup.l2Network.tokenBridge.l1Weth, + amount: BigNumber.from(1), + }, + setup.l1Signer, + setup.l2Signer.provider!, + setup.l3Signer.provider! + ) throw new Error() } catch (e: any) { - expect(e.message).to.eq('Cannot estimate gas for custom l1l2 gateway, please provide gas params') + expect(e.message).to.eq( + 'Cannot estimate gas for custom l1l2 gateway, please provide gas params' + ) } // l1 to l2 default but l2 to l3 gateway non default, we have to register a custom one From 5d1b86c31595b72e2dad48bf30d03c4b1c47dc5c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:37:26 -0400 Subject: [PATCH 63/80] don't create new erc20bridgers --- src/lib/assetBridger/l1l3Bridger.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 57acad7f6f..68bc087446 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -305,6 +305,9 @@ class BaseL1L3Bridger { class BaseErc20L1L3Bridger extends BaseL1L3Bridger { public readonly teleporterAddresses: TeleporterAddresses + protected readonly l2Erc20Bridger = new Erc20Bridger(this.l2Network) + protected readonly l3Erc20Bridger = new Erc20Bridger(this.l3Network) + // todo: tune these public readonly defaultRetryableGasParams: ManualRetryableGasParams = { l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), @@ -333,7 +336,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { erc20L1Address: string, l1Provider: Provider ): Promise { - return new Erc20Bridger(this.l2Network).getL2ERC20Address( + return this.l2Erc20Bridger.getL2ERC20Address( erc20L1Address, l1Provider ) @@ -347,7 +350,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - return new Erc20Bridger(this.l3Network).getL2ERC20Address( + return this.l3Erc20Bridger.getL2ERC20Address( await this.getL2ERC20Address(erc20L1Address, l1Provider), l2Provider ) @@ -824,7 +827,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Provider: Provider ): Promise>> { - return new Erc20Bridger(this.l2Network).getApproveTokenRequest({ + return this.l2Erc20Bridger.getApproveTokenRequest({ ...params, l1Provider, }) @@ -837,7 +840,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Signer: Signer ): Promise { - return new Erc20Bridger(this.l2Network).approveToken({ + return this.l2Erc20Bridger.approveToken({ ...params, l1Signer, }) @@ -921,11 +924,10 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { excessFeeRefundAddress: l2ForwarderAddress, } - const erc20Bridger = new Erc20Bridger(this.l2Network) const submissionCostBefore = ( - await erc20Bridger.getDepositRequest(baseDepositRequestParams) + await this.l2Erc20Bridger.getDepositRequest(baseDepositRequestParams) ).retryableData.maxSubmissionCost - const tokenBridgeRequest = await erc20Bridger.getDepositRequest({ + const tokenBridgeRequest = await this.l2Erc20Bridger.getDepositRequest({ ...baseDepositRequestParams, retryableGasOverrides: { // we need to INCREASE submission cost by extraValue From f60201a3571c7ddaf2fed3225f0abdde85f6c17b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:02:21 -0400 Subject: [PATCH 64/80] _getApproveTokenRequest and use the erc20bridger for a couple more things --- src/lib/assetBridger/l1l3Bridger.ts | 90 ++++++++--------------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 68bc087446..ff0692dac6 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -359,16 +359,11 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { /** * Given an L1 token's address, get the address of the token's L1 <-> L2 gateway on L1 */ - public async getL1L2GatewayAddress( + public getL1L2GatewayAddress( erc20L1Address: string, l1Provider: Provider ): Promise { - await this._checkL1Network(l1Provider) - - return await L1GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l1GatewayRouter, - l1Provider - ).getGateway(erc20L1Address) + return this.l2Erc20Bridger.getL1GatewayAddress(erc20L1Address, l1Provider) } /** @@ -379,15 +374,8 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l1Provider: Provider, l2Provider: Provider ): Promise { - await this._checkL1Network(l1Provider) - await this._checkL2Network(l2Provider) - const l2Token = await this.getL2ERC20Address(erc20L1Address, l1Provider) - - return await L1GatewayRouter__factory.connect( - this.l3Network.tokenBridge.l1GatewayRouter, // note: this is the L2 <-> L3 gateway router on L2 - l2Provider - ).getGateway(l2Token) + return this.l3Erc20Bridger.getL1GatewayAddress(l2Token, l2Provider) } /** @@ -460,13 +448,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l1TokenAddress: string, l1Provider: Provider ): Promise { - await this._checkL1Network(l1Provider) - - return this._tokenIsDisabled( - l1TokenAddress, - this.l2Network.tokenBridge.l1GatewayRouter, - l1Provider - ) + return this.l2Erc20Bridger.l1TokenIsDisabled(l1TokenAddress, l1Provider) } /** @@ -476,13 +458,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l2TokenAddress: string, l2Provider: Provider ): Promise { - await this._checkL2Network(l2Provider) - - return this._tokenIsDisabled( - l2TokenAddress, - this.l3Network.tokenBridge.l1GatewayRouter, - l2Provider - ) + return this.l3Erc20Bridger.l1TokenIsDisabled(l2TokenAddress, l2Provider) } /** @@ -578,25 +554,6 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { } } - /** - * Whether the token is disabled on the router given a token address and router address - */ - private async _tokenIsDisabled( - tokenAddress: string, - gatewayRouterAddress: string, - provider: Provider - ): Promise { - // assumes provider has been checked - const gatewayRouter = L2GatewayRouter__factory.connect( - gatewayRouterAddress, - provider - ) - - return ( - (await gatewayRouter.l1TokenToGateway(tokenAddress)) === DISABLED_GATEWAY - ) - } - /** * Given a ERC20 deposit request parameters, return the gas params with default values if not provided */ @@ -667,6 +624,22 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { return events[0] } + + protected async _getApproveTokenRequest( + params: TokenApproveParams, + spender: string + ) { + const iErc20Interface = ERC20__factory.createInterface() + const data = iErc20Interface.encodeFunctionData('approve', [ + spender, + params.amount || ethers.constants.MaxUint256, + ]) + + return { + to: params.erc20L1Address, + data, + } + } } /** @@ -680,16 +653,7 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getApproveTokenRequest( params: TokenApproveParams ): Promise>> { - const iErc20Interface = ERC20__factory.createInterface() - const data = iErc20Interface.encodeFunctionData('approve', [ - this.teleporterAddresses.l1Teleporter, - params.amount || ethers.constants.MaxUint256, - ]) - - return { - to: params.erc20L1Address, - data, - } + return this._getApproveTokenRequest(params, this.teleporterAddresses.l1Teleporter) } /** @@ -827,10 +791,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Provider: Provider ): Promise>> { - return this.l2Erc20Bridger.getApproveTokenRequest({ - ...params, - l1Provider, - }) + return this._getApproveTokenRequest(params, await this.getL1L2GatewayAddress(params.erc20L1Address, l1Provider)) } /** @@ -840,10 +801,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Signer: Signer ): Promise { - return this.l2Erc20Bridger.approveToken({ - ...params, - l1Signer, - }) + return l1Signer.sendTransaction(await this.getApproveTokenRequest(params, l1Signer.provider!)) } /** From 7b61e13b1563ed2d60724750fb5a8acc90c154ae Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:31:04 -0400 Subject: [PATCH 65/80] format --- src/lib/assetBridger/l1l3Bridger.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index ff0692dac6..72a188cf7e 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -336,10 +336,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { erc20L1Address: string, l1Provider: Provider ): Promise { - return this.l2Erc20Bridger.getL2ERC20Address( - erc20L1Address, - l1Provider - ) + return this.l2Erc20Bridger.getL2ERC20Address(erc20L1Address, l1Provider) } /** @@ -653,7 +650,10 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { public async getApproveTokenRequest( params: TokenApproveParams ): Promise>> { - return this._getApproveTokenRequest(params, this.teleporterAddresses.l1Teleporter) + return this._getApproveTokenRequest( + params, + this.teleporterAddresses.l1Teleporter + ) } /** @@ -791,7 +791,10 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Provider: Provider ): Promise>> { - return this._getApproveTokenRequest(params, await this.getL1L2GatewayAddress(params.erc20L1Address, l1Provider)) + return this._getApproveTokenRequest( + params, + await this.getL1L2GatewayAddress(params.erc20L1Address, l1Provider) + ) } /** @@ -801,7 +804,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { params: TokenApproveParams, l1Signer: Signer ): Promise { - return l1Signer.sendTransaction(await this.getApproveTokenRequest(params, l1Signer.provider!)) + return l1Signer.sendTransaction( + await this.getApproveTokenRequest(params, l1Signer.provider!) + ) } /** From 4ac272626b804a7220a7f15b75b15aba9c009142 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:57:59 -0400 Subject: [PATCH 66/80] fix yarn.lock --- yarn.lock | 73 +++++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/yarn.lock b/yarn.lock index d64a0faa3b..7a2ac7d7e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,18 +50,6 @@ optionalDependencies: "@openzeppelin/upgrades-core" "^1.24.1" -"@arbitrum/token-bridge-contracts@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@arbitrum/token-bridge-contracts/-/token-bridge-contracts-1.1.0.tgz#59edd3d864737cf15f331597cac3498c8a7ceeb8" - integrity sha512-UEXF5FT2Rus37X4uKBJkaloVITjz/BOeoF/FDpP2hgR0Q3gIgaDenhwY+hKX8donU24X1OacSFW5cFJlpKK6rg== - dependencies: - "@arbitrum/nitro-contracts" "^1.0.0-beta.8" - "@offchainlabs/upgrade-executor" "1.1.0-beta.0" - "@openzeppelin/contracts" "4.8.3" - "@openzeppelin/contracts-upgradeable" "4.8.3" - optionalDependencies: - "@openzeppelin/upgrades-core" "^1.24.1" - "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1120,30 +1108,48 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz#131b0da1b71680d5a01569f916ae878229d326d3" integrity sha512-A2gZAGB6kUvLx+kzM92HKuUF33F1FSe90L0TmkXkT2Hh0OKRpvWZURUSU2nghD2yC4DzfEZ3DftfeHGvZ2JTUw== -"@openzeppelin/contracts-upgradeable@3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3" - integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw== +"@offchainlabs/upgrade-executor@1.1.0-beta.0": + version "1.1.0-beta.0" + resolved "https://registry.yarnpkg.com/@offchainlabs/upgrade-executor/-/upgrade-executor-1.1.0-beta.0.tgz#c4b1375176546a18aaef01a43956abfb58250e0a" + integrity sha512-mpn6PHjH/KDDjNX0pXHEKdyv8m6DVGQiI2nGzQn0JbM1nOSHJpWx6fvfjtH7YxHJ6zBZTcsKkqGkFKDtCfoSLw== + dependencies: + "@openzeppelin/contracts" "4.7.3" + "@openzeppelin/contracts-upgradeable" "4.7.3" "@openzeppelin/contracts-upgradeable@4.5.2": version "4.5.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz#90d9e47bacfd8693bfad0ac8a394645575528d05" integrity sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA== -"@openzeppelin/contracts@3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" - integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== +"@openzeppelin/contracts-upgradeable@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" + integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== + +"@openzeppelin/contracts-upgradeable@4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" + integrity sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg== "@openzeppelin/contracts@4.5.0": version "4.5.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.5.0.tgz#3fd75d57de172b3743cdfc1206883f56430409cc" integrity sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA== -"@openzeppelin/upgrades-core@^1.7.6": - version "1.14.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.14.2.tgz#1acf6560dbe42b5e68fdffe6e6979b90fa33bb56" - integrity sha512-JkrMcsB0v6vwX+fObY+y51L3tD3BcLjNpPnKkgtsEOC1Umwt6WzvI8Gq2brmNOzFLNQqeX2xySiJTGvypqUQow== +"@openzeppelin/contracts@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" + integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== + +"@openzeppelin/contracts@4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a" + integrity sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg== + +"@openzeppelin/upgrades-core@^1.24.1": + version "1.31.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.31.0.tgz#cc262e2618d90540a8ad4dfafbdf06afdb17b8fa" + integrity sha512-E1Cz8lVpo2mnBeFWxiGDLWtuTYMFNTEWwbnhle4dZ+5UHX6xTRO+Q/CaWBHm33HHhuuiUbRwgGNnAR9zOu+fyQ== dependencies: cbor "^9.0.0" chalk "^4.1.0" @@ -2032,13 +2038,6 @@ cbor@^9.0.0: dependencies: nofilter "^3.1.0" -cbor@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" - integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== - dependencies: - nofilter "^3.1.0" - chai@^4.2.0: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" @@ -2236,11 +2235,6 @@ compare-versions@^6.0.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a" integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg== -compare-versions@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a" - integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -5326,13 +5320,6 @@ solidity-ast@^0.4.51: dependencies: array.prototype.findlast "^1.2.2" -solidity-ast@^0.4.51: - version "0.4.52" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.52.tgz#9f1a9abc7e5ba28bbf91146ecd07aec7e70f3c85" - integrity sha512-iOya9BSiB9jhM8Vf40n8lGELGzwrUc57rl5BhfNtJ5cvAaMvRcNlHeAMNvqJJyjoUnczqRbHqdivEqK89du3Cw== - dependencies: - array.prototype.findlast "^1.2.2" - solidity-comments-extractor@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" From 5d7388a994fca9a0dc6aeff1ad70d8d0235a005c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:36:52 -0500 Subject: [PATCH 67/80] append relayer info --- src/lib/assetBridger/l1l3Bridger.ts | 71 ++++++++++++++++++++++----- tests/integration/l1l3Bridger.test.ts | 16 +++++- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 72a188cf7e..83ce2fc77e 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,6 +1,6 @@ import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' import { JsonRpcProvider } from '@ethersproject/providers' -import { BigNumber, BigNumberish, Signer, ethers } from 'ethers' +import { BigNumber, BigNumberish, Signer, Transaction, ethers } from 'ethers' import { ERC20 } from '../abi/ERC20' import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' @@ -46,6 +46,8 @@ import { } from '../message/L1Transaction' import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' +import { L2ForwarderPredictor__factory } from '../abi/factories/L2ForwarderPredictor__factory' +import { AbiCoder } from 'ethers/lib/utils' /** * Manual gas parameters for the L1 to L2 and L2 to L3 tickets. @@ -215,9 +217,6 @@ export type RelayedErc20DepositRequestResult = { txRequest: L1ToL2TransactionRequest /** * Information required by the relayer to forward tokens from L2 to L3 - * - * IMPORTANT! DO NOT LOSE THIS INFO! - * Once tokens are sent through the bridge, losing this information means losing the funds! */ relayerInfo: RelayerInfo } @@ -232,9 +231,6 @@ export type RelayedErc20DepositResult = { tx: L1ContractCallTransaction /** * Information required by the relayer to forward tokens from L2 to L3 - * - * IMPORTANT! DO NOT LOSE THIS INFO! - * Once tokens are sent through the bridge, losing this information means losing the funds! */ relayerInfo: RelayerInfo } @@ -812,8 +808,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly. * - * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! - * Once tokens are sent through the bridge, losing this information means losing the funds! + * Relayer info will be returned as well as appended to calldata. */ public async getDepositRequest( params: RelayedErc20DepositRequestParams, @@ -903,6 +898,18 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { }, }) + // append forwarder params to calldata + const encodedForwarderParamsWithSelector = + L2ForwarderPredictor__factory.createInterface().encodeFunctionData( + 'l2ForwarderAddress', + [l2ForwarderParams] + ) + // strip selector and add to tx data + tokenBridgeRequest.txRequest.data = ethers.utils.concat([ + tokenBridgeRequest.txRequest.data, + ethers.utils.hexDataSlice(encodedForwarderParamsWithSelector, 4) + ]) + return { relayerInfo: { ...l2ForwarderParams, @@ -913,10 +920,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. - * - * IMPORTANT! DO NOT LOSE THE RETURNED RELAYER INFO! - * Once tokens are sent through the bridge, losing this information means losing the funds! + * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * + * Relayer info will be returned as well as appended to calldata. */ public async deposit( params: Erc20DepositRequestParams, @@ -952,6 +958,45 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { return this._getDepositStatus(depositTxReceipt, l2Provider, l3Provider) } + /** + * Parse relayer info from a transaction. If the transaction was created using this class, then the relayer info is appended to the calldata. + * To determine L2 chain id, 'to' is assumed to be the L1GatewayRouter. All l2Networks are searched for the L1GatewayRouter. + * + * @param tx A Transaction, TransactionRequest, or TransactionReceipt that initiated step 1 of a relayed teleportation + */ + public static parseRelayerInfoFromTx( + tx: Required> + ): RelayerInfo { + // 'to' is the L1GatewayRouter. Go through the list of chains and find the one that matches the L1GatewayRouter + const chainId = Object.values(l2Networks).find( + network => network.tokenBridge.l1GatewayRouter === tx.to + )?.chainID + + if (!chainId) { + throw new ArbSdkError(`Could not find chain id for L1GatewayRouter`) + } + + const l2ForwarderAddressFragment = L2ForwarderPredictor__factory.createInterface().fragments.find( + f => f.name === 'l2ForwarderAddress' + )! + + const l2ForwarderParamsStructLength = l2ForwarderAddressFragment.inputs[0].components.length + + // get the last x words of the calldata + const encodedL2ForwarderParams = ethers.utils.hexDataSlice( + tx.data, + ethers.utils.hexDataLength(tx.data) - l2ForwarderParamsStructLength * 32 + ) + + // parse the encoded params + const decodedL2ForwarderParams = new AbiCoder().decode(l2ForwarderAddressFragment.inputs, encodedL2ForwarderParams)[0] as L2ForwarderPredictor.L2ForwarderParamsStruct + + return { + chainId, + ...decodedL2ForwarderParams, + } + } + /** * Call an `L2Forwarder` to relay a deposit to L3. */ diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 93f617eb36..09bbb0149c 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -439,7 +439,7 @@ describe('L1 to L3 Bridging', () => { // don't need to test rescue here i think it('happy path', async () => { - const l3Recipient = ethers.utils.hexlify(ethers.utils.randomBytes(20)) + const l3Recipient = ethers.utils.getAddress(ethers.utils.hexlify(ethers.utils.randomBytes(20))) const depositResult = await l1l3Bridger.deposit( { @@ -454,6 +454,20 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositResult.tx.wait() + // make sure relayer info was encoded correctly + const parsedRelayerInfo = RelayedErc20L1L3Bridger.parseRelayerInfoFromTx({to: depositResult.tx.to!, data: depositResult.tx.data}) + // include a hardcoded check so that the test fails if the data format changes + expect(Object.keys(depositResult.relayerInfo).length).to.eq(8) + + expect(depositResult.relayerInfo.chainId).to.eq(parsedRelayerInfo.chainId).to.eq(setup.l2Network.chainID) + expect(depositResult.relayerInfo.owner).to.eq(parsedRelayerInfo.owner) + expect(depositResult.relayerInfo.token).to.eq(parsedRelayerInfo.token) + expect(depositResult.relayerInfo.router).to.eq(parsedRelayerInfo.router) + expect(depositResult.relayerInfo.to).to.eq(parsedRelayerInfo.to) + expect(BigNumber.from(depositResult.relayerInfo.gasLimit).eq(parsedRelayerInfo.gasLimit)).to.be.true + expect(BigNumber.from(depositResult.relayerInfo.gasPrice).eq(parsedRelayerInfo.gasPrice)).to.be.true + expect(BigNumber.from(depositResult.relayerInfo.relayerPayment).eq(parsedRelayerInfo.relayerPayment)).to.be.true + // wait until first step finishes await poll(async () => { const status = await l1l3Bridger.getDepositStatus( From c3fd64c59fde9a4f8ba075be4041e1b220972b63 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:35:30 -0500 Subject: [PATCH 68/80] remove unused imports and fmt --- src/lib/assetBridger/l1l3Bridger.ts | 25 ++++++++++++--------- tests/integration/l1l3Bridger.test.ts | 32 ++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 83ce2fc77e..4b96401850 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1,6 +1,6 @@ import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' import { JsonRpcProvider } from '@ethersproject/providers' -import { BigNumber, BigNumberish, Signer, Transaction, ethers } from 'ethers' +import { BigNumber, BigNumberish, Signer, ethers } from 'ethers' import { ERC20 } from '../abi/ERC20' import { BridgedToL3Event } from '../abi/L2Forwarder' import { L2ForwarderPredictor } from '../abi/L2ForwarderPredictor' @@ -10,11 +10,9 @@ import { ERC20__factory } from '../abi/factories/ERC20__factory' import { L1GatewayRouter__factory } from '../abi/factories/L1GatewayRouter__factory' import { L2ForwarderFactory__factory } from '../abi/factories/L2ForwarderFactory__factory' import { L2Forwarder__factory } from '../abi/factories/L2Forwarder__factory' -import { L2GatewayRouter__factory } from '../abi/factories/L2GatewayRouter__factory' import { L2GatewayToken__factory } from '../abi/factories/L2GatewayToken__factory' import { L1Teleporter__factory } from '../abi/factories/L1Teleporter__factory' import { Address } from '../dataEntities/address' -import { DISABLED_GATEWAY } from '../dataEntities/constants' import { ArbSdkError } from '../dataEntities/errors' import { L1Network, @@ -907,7 +905,7 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { // strip selector and add to tx data tokenBridgeRequest.txRequest.data = ethers.utils.concat([ tokenBridgeRequest.txRequest.data, - ethers.utils.hexDataSlice(encodedForwarderParamsWithSelector, 4) + ethers.utils.hexDataSlice(encodedForwarderParamsWithSelector, 4), ]) return { @@ -920,8 +918,8 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { } /** - * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. - * + * Deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * * Relayer info will be returned as well as appended to calldata. */ public async deposit( @@ -976,11 +974,13 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { throw new ArbSdkError(`Could not find chain id for L1GatewayRouter`) } - const l2ForwarderAddressFragment = L2ForwarderPredictor__factory.createInterface().fragments.find( - f => f.name === 'l2ForwarderAddress' - )! + const l2ForwarderAddressFragment = + L2ForwarderPredictor__factory.createInterface().fragments.find( + f => f.name === 'l2ForwarderAddress' + )! - const l2ForwarderParamsStructLength = l2ForwarderAddressFragment.inputs[0].components.length + const l2ForwarderParamsStructLength = + l2ForwarderAddressFragment.inputs[0].components.length // get the last x words of the calldata const encodedL2ForwarderParams = ethers.utils.hexDataSlice( @@ -989,7 +989,10 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { ) // parse the encoded params - const decodedL2ForwarderParams = new AbiCoder().decode(l2ForwarderAddressFragment.inputs, encodedL2ForwarderParams)[0] as L2ForwarderPredictor.L2ForwarderParamsStruct + const decodedL2ForwarderParams = new AbiCoder().decode( + l2ForwarderAddressFragment.inputs, + encodedL2ForwarderParams + )[0] as L2ForwarderPredictor.L2ForwarderParamsStruct return { chainId, diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 09bbb0149c..1973dfffae 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -439,7 +439,9 @@ describe('L1 to L3 Bridging', () => { // don't need to test rescue here i think it('happy path', async () => { - const l3Recipient = ethers.utils.getAddress(ethers.utils.hexlify(ethers.utils.randomBytes(20))) + const l3Recipient = ethers.utils.getAddress( + ethers.utils.hexlify(ethers.utils.randomBytes(20)) + ) const depositResult = await l1l3Bridger.deposit( { @@ -455,18 +457,36 @@ describe('L1 to L3 Bridging', () => { const depositReceipt = await depositResult.tx.wait() // make sure relayer info was encoded correctly - const parsedRelayerInfo = RelayedErc20L1L3Bridger.parseRelayerInfoFromTx({to: depositResult.tx.to!, data: depositResult.tx.data}) + const parsedRelayerInfo = + RelayedErc20L1L3Bridger.parseRelayerInfoFromTx({ + to: depositResult.tx.to!, + data: depositResult.tx.data, + }) // include a hardcoded check so that the test fails if the data format changes expect(Object.keys(depositResult.relayerInfo).length).to.eq(8) - expect(depositResult.relayerInfo.chainId).to.eq(parsedRelayerInfo.chainId).to.eq(setup.l2Network.chainID) + expect(depositResult.relayerInfo.chainId) + .to.eq(parsedRelayerInfo.chainId) + .to.eq(setup.l2Network.chainID) expect(depositResult.relayerInfo.owner).to.eq(parsedRelayerInfo.owner) expect(depositResult.relayerInfo.token).to.eq(parsedRelayerInfo.token) expect(depositResult.relayerInfo.router).to.eq(parsedRelayerInfo.router) expect(depositResult.relayerInfo.to).to.eq(parsedRelayerInfo.to) - expect(BigNumber.from(depositResult.relayerInfo.gasLimit).eq(parsedRelayerInfo.gasLimit)).to.be.true - expect(BigNumber.from(depositResult.relayerInfo.gasPrice).eq(parsedRelayerInfo.gasPrice)).to.be.true - expect(BigNumber.from(depositResult.relayerInfo.relayerPayment).eq(parsedRelayerInfo.relayerPayment)).to.be.true + expect( + BigNumber.from(depositResult.relayerInfo.gasLimit).eq( + parsedRelayerInfo.gasLimit + ) + ).to.be.true + expect( + BigNumber.from(depositResult.relayerInfo.gasPrice).eq( + parsedRelayerInfo.gasPrice + ) + ).to.be.true + expect( + BigNumber.from(depositResult.relayerInfo.relayerPayment).eq( + parsedRelayerInfo.relayerPayment + ) + ).to.be.true // wait until first step finishes await poll(async () => { From 6b34ed5155e42e402714e334b9a434922209ae9c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:45:07 -0500 Subject: [PATCH 69/80] measure L2 gas use and add L1 gas when using relayer --- src/lib/assetBridger/l1l3Bridger.ts | 52 ++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 4b96401850..2db13222f7 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -46,6 +46,8 @@ import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' import { Erc20Bridger, TokenApproveParams } from './erc20Bridger' import { L2ForwarderPredictor__factory } from '../abi/factories/L2ForwarderPredictor__factory' import { AbiCoder } from 'ethers/lib/utils' +import { NODE_INTERFACE_ADDRESS } from '../dataEntities/constants' +import { NodeInterface__factory } from '../abi/factories/NodeInterface__factory' /** * Manual gas parameters for the L1 to L2 and L2 to L3 tickets. @@ -302,13 +304,12 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { protected readonly l2Erc20Bridger = new Erc20Bridger(this.l2Network) protected readonly l3Erc20Bridger = new Erc20Bridger(this.l3Network) - // todo: tune these public readonly defaultRetryableGasParams: ManualRetryableGasParams = { - l2ForwarderFactoryGasLimit: BigNumber.from(1_000_000), - l1l2TokenBridgeGasLimit: BigNumber.from(1_000_000), - l2l3TokenBridgeGasLimit: BigNumber.from(1_000_000), - l1l2TokenBridgeRetryableSize: BigNumber.from(1000), - l2l3TokenBridgeRetryableSize: BigNumber.from(1000), + l2ForwarderFactoryGasLimit: BigNumber.from(500_000), // measured: 357,751 L2 Gas + l1l2TokenBridgeGasLimit: BigNumber.from(700_000), // measured: 531,744 L2 Gas + l2l3TokenBridgeGasLimit: BigNumber.from(700_000), // measured: 531,756 L2 Gas + l1l2TokenBridgeRetryableSize: BigNumber.from(1000), // measured: 740. includes variable length information like token symbol (todo: verify this statement) + l2l3TokenBridgeRetryableSize: BigNumber.from(1000), // measured: 740. includes variable length information like token symbol } as const public constructor(public readonly l3Network: L2Network) { @@ -827,9 +828,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { const relayerPayment = this._percentIncrease( params.overrides?.relayerPayment?.base || - BigNumber.from(populatedGasParams.l2ForwarderFactoryGasLimit).mul( - populatedGasParams.l2GasPrice - ), + BigNumber.from(populatedGasParams.l2ForwarderFactoryGasLimit) + .add(await this._estimateL1GasForL2ForwarderCall(l2Provider)) + .mul(populatedGasParams.l2GasPrice), params.overrides?.relayerPayment?.percentIncrease || this.defaultRelayerPaymentPercentIncrease ) @@ -1033,6 +1034,39 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { return l2ForwarderFactory.callForwarder(relayerInfo) } + + private async _estimateL1GasForL2ForwarderCall(l2Provider: Provider) { + const nodeInterface = NodeInterface__factory.connect( + NODE_INTERFACE_ADDRESS, + l2Provider + ) + + // generate some fake calldata with random values + const r = (n: number) => ethers.utils.hexlify(ethers.utils.randomBytes(n)) + const params: L2ForwarderPredictor.L2ForwarderParamsStruct = { + owner: r(20), + token: r(20), + router: r(20), + to: r(20), + gasLimit: r(32), + gasPrice: r(32), + relayerPayment: r(32), + } + + const txData = + L2ForwarderFactory__factory.createInterface().encodeFunctionData( + 'callForwarder', + [params] + ) + + return ( + await nodeInterface.callStatic.gasEstimateL1Component( + this.l2Network.teleporterAddresses!.l2ForwarderFactory, + false, + txData + ) + ).gasEstimateForL1 + } } /** From a23237e2cfe96c61a03254a1918c88c57b197497 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:12:36 -0500 Subject: [PATCH 70/80] use new contracts package --- package.json | 2 +- scripts/genAbi.js | 13 ++++++++----- yarn.lock | 26 +++++++++++++++++++------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 210feb1d52..d34e335ccd 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,10 @@ "ethers": "^5.1.0" }, "devDependencies": { - "l1-l3-teleport-contracts": "^0.1.0", "@arbitrum/nitro-contracts": "1.0.1", "@arbitrum/token-bridge-contracts": "1.1.0", "@nomiclabs/hardhat-ethers": "^2.0.4", + "@offchainlabs/l1-l3-teleport-contracts": "^0.1.0-alpha.0", "@typechain/ethers-v5": "9.0.0", "@types/chai": "^4.2.11", "@types/mocha": "^9.0.0", diff --git a/scripts/genAbi.js b/scripts/genAbi.js index 9d04912383..d35b5959ef 100644 --- a/scripts/genAbi.js +++ b/scripts/genAbi.js @@ -11,7 +11,7 @@ async function main() { const cwd = process.cwd() const nitroPath = getPackagePath('@arbitrum/nitro-contracts') - const teleporterPath = getPackagePath('l1-l3-teleport-contracts') + const teleporterPath = getPackagePath('@offchainlabs/l1-l3-teleport-contracts') const tokenBridgePath = getPackagePath('@arbitrum/token-bridge-contracts') console.log('Compiling paths.') @@ -34,10 +34,13 @@ async function main() { }) console.log('building teleporter') - execSync(`${npmExec} && ${npmExec} run hardhat compile --config ./hardhat.config.js`, { - cwd: teleporterPath, - }) - + execSync( + `${npmExec} run build --config ./hardhat.config.js`, + { + cwd: teleporterPath, + } + ) + // copy the hardhat config to the token-bridge-contracts package execSync(`cp ${cwd}/hardhat-abigen.ts ${tokenBridgePath}/hardhat-abigen.ts`) diff --git a/yarn.lock b/yarn.lock index 05b6e35f08..c39aee9e43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -51,6 +51,18 @@ optionalDependencies: "@openzeppelin/upgrades-core" "^1.24.1" +"@arbitrum/token-bridge-contracts@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@arbitrum/token-bridge-contracts/-/token-bridge-contracts-1.1.1.tgz#716af9d3a50f544b9ce4e49dfa55058d1715b728" + integrity sha512-4gvzydeNBIGlMHFLvO/+r8SdtKglZLKOo6bh8a+nK3pjovKUfBs4ZcpqYNkbabKWeJ9cZCOvw74VX+kJGn65xQ== + dependencies: + "@arbitrum/nitro-contracts" "^1.0.0-beta.8" + "@offchainlabs/upgrade-executor" "1.1.0-beta.0" + "@openzeppelin/contracts" "4.8.3" + "@openzeppelin/contracts-upgradeable" "4.8.3" + optionalDependencies: + "@openzeppelin/upgrades-core" "^1.24.1" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1130,6 +1142,13 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz#131b0da1b71680d5a01569f916ae878229d326d3" integrity sha512-A2gZAGB6kUvLx+kzM92HKuUF33F1FSe90L0TmkXkT2Hh0OKRpvWZURUSU2nghD2yC4DzfEZ3DftfeHGvZ2JTUw== +"@offchainlabs/l1-l3-teleport-contracts@^0.1.0-alpha.0": + version "0.1.0-alpha.0" + resolved "https://registry.yarnpkg.com/@offchainlabs/l1-l3-teleport-contracts/-/l1-l3-teleport-contracts-0.1.0-alpha.0.tgz#1f5a841f7a40ee26a46c907b7c1f6d6d8987bd4f" + integrity sha512-ZTbYhDhjZciqa9cDR0Oxy1Ub4a6gE4pz5JSX83etAFNQeNQJ4jNJP9viNXHHMp7HtQhuzZkaHZdldtlYfuQa1Q== + dependencies: + "@arbitrum/token-bridge-contracts" "1.1.1" + "@offchainlabs/upgrade-executor@1.1.0-beta.0": version "1.1.0-beta.0" resolved "https://registry.yarnpkg.com/@offchainlabs/upgrade-executor/-/upgrade-executor-1.1.0-beta.0.tgz#c4b1375176546a18aaef01a43956abfb58250e0a" @@ -4088,13 +4107,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -l1-l3-teleport-contracts@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/l1-l3-teleport-contracts/-/l1-l3-teleport-contracts-0.1.0.tgz#912e60d715293872534b7138f6ebab56a2a70e46" - integrity sha512-geFG8Nxef7qX5R0r5b9WFcyZ+Ng42KfjNFhnXrkMPRF+rR5R3vs1yrndR8TpMZwNbNmoY/942I8o/127eiwrdw== - dependencies: - "@arbitrum/token-bridge-contracts" "1.1.0" - level-codec@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" From 72087942a3f217dd6514e99b1df6e5f1c14d8df1 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:22:39 -0500 Subject: [PATCH 71/80] teleporter artifacts dir --- scripts/genAbi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/genAbi.js b/scripts/genAbi.js index d35b5959ef..3b1ebeab04 100644 --- a/scripts/genAbi.js +++ b/scripts/genAbi.js @@ -54,7 +54,7 @@ async function main() { const nitroFiles = glob(cwd, [ `${tokenBridgePath}/build/contracts/!(build-info)/**/+([a-zA-Z0-9_]).json`, `${nitroPath}/build/contracts/!(build-info)/**/+([a-zA-Z0-9_]).json`, - `${teleporterPath}/artifacts/!(build-info)/**/+([a-zA-Z0-9_]).json`, + `${teleporterPath}/build/contracts/!(build-info)/**/+([a-zA-Z0-9_]).json`, ]) // TODO: generate files into different subfolders (ie `/nitro/*`) to avoid overwrite of contracts with the same name From 385890b573d5033438e803bd1c2d4bb7b3e67142 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:28:29 -0500 Subject: [PATCH 72/80] format --- scripts/genAbi.js | 13 ++++++------- src/lib/dataEntities/networks.ts | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/scripts/genAbi.js b/scripts/genAbi.js index 3b1ebeab04..796c243aa3 100644 --- a/scripts/genAbi.js +++ b/scripts/genAbi.js @@ -11,7 +11,9 @@ async function main() { const cwd = process.cwd() const nitroPath = getPackagePath('@arbitrum/nitro-contracts') - const teleporterPath = getPackagePath('@offchainlabs/l1-l3-teleport-contracts') + const teleporterPath = getPackagePath( + '@offchainlabs/l1-l3-teleport-contracts' + ) const tokenBridgePath = getPackagePath('@arbitrum/token-bridge-contracts') console.log('Compiling paths.') @@ -34,12 +36,9 @@ async function main() { }) console.log('building teleporter') - execSync( - `${npmExec} run build --config ./hardhat.config.js`, - { - cwd: teleporterPath, - } - ) + execSync(`${npmExec} run build --config ./hardhat.config.js`, { + cwd: teleporterPath, + }) // copy the hardhat config to the token-bridge-contracts package execSync(`cp ${cwd}/hardhat-abigen.ts ${tokenBridgePath}/hardhat-abigen.ts`) diff --git a/src/lib/dataEntities/networks.ts b/src/lib/dataEntities/networks.ts index 144bfafb5c..ed6a953916 100644 --- a/src/lib/dataEntities/networks.ts +++ b/src/lib/dataEntities/networks.ts @@ -281,8 +281,8 @@ export const l2Networks: L2Networks = { chainID: 421614, confirmPeriodBlocks: 20, teleporterAddresses: { - l1Teleporter: "0x33bd25e6156BCF698B8F2Ad82005e710037403f4", - l2ForwarderFactory: "0xB14C63bb526Db2A49b835ca954923dE7DE3968e2", + l1Teleporter: '0x33bd25e6156BCF698B8F2Ad82005e710037403f4', + l2ForwarderFactory: '0xB14C63bb526Db2A49b835ca954923dE7DE3968e2', }, ethBridge: { bridge: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9', From a2c8f0c75b9ed8f0bd8c94340bb5be21167c35b1 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:06:08 -0500 Subject: [PATCH 73/80] use other test token --- tests/integration/l1l3Bridger.test.ts | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/integration/l1l3Bridger.test.ts b/tests/integration/l1l3Bridger.test.ts index 1973dfffae..df4c06c300 100644 --- a/tests/integration/l1l3Bridger.test.ts +++ b/tests/integration/l1l3Bridger.test.ts @@ -1,8 +1,8 @@ import { config, testSetup } from '../../scripts/testSetup' import { Address, Erc20L1L3Bridger, L1ToL2MessageStatus } from '../../src' import { L2ForwarderContractsDeployer__factory } from '../../src/lib/abi/factories/L2ForwarderContractsDeployer__factory' -import { MockToken__factory } from '../../src/lib/abi/factories/MockToken__factory' -import { MockToken } from '../../src/lib/abi/MockToken' +import { TestERC20__factory } from '../../src/lib/abi/factories/TestERC20__factory' +import { TestERC20 } from '../../src/lib/abi/TestERC20' import { L1Teleporter__factory } from '../../src/lib/abi/factories/L1Teleporter__factory' import { fundL1, fundL2, skipIfMainnet } from './testHelpers' import { BigNumber, ethers } from 'ethers' @@ -101,7 +101,7 @@ describe('L1 to L3 Bridging', () => { }) describe('ERC20 Bridging', () => { - let l1Token: MockToken + let l1Token: TestERC20 // deploy teleporter contracts and mock token before(async function () { @@ -125,13 +125,9 @@ describe('L1 to L3 Bridging', () => { } // deploy the mock token - l1Token = await new MockToken__factory(setup.l1Signer).deploy( - 'MOCK', - 'MOCK', - ethers.utils.parseEther('100'), - await setup.l1Signer.getAddress() - ) + l1Token = await new TestERC20__factory(setup.l1Signer).deploy() await l1Token.deployed() + await (await l1Token.connect(setup.l1Signer).mint()).wait() }) describe('BaseErc20L1L3Bridger', () => { @@ -215,6 +211,7 @@ describe('L1 to L3 Bridging', () => { describe('Erc20L1L3Bridger', () => { let l1l3Bridger: Erc20L1L3Bridger + const amount = BigNumber.from(100) // create the bridger and approve the teleporter before(async () => { @@ -261,7 +258,7 @@ describe('L1 to L3 Bridging', () => { { erc20L1Address: l1Token.address, to: l3Recipient, - amount: ethers.utils.parseEther('1'), + amount, }, setup.l1Signer, setup.l2Signer.provider!, @@ -293,7 +290,7 @@ describe('L1 to L3 Bridging', () => { const l3Balance = await l3Token.balanceOf(l3Recipient) - expect(l3Balance.eq(ethers.utils.parseEther('1'))).to.be.true + expect(l3Balance.eq(amount)).to.be.true }) it('should report correct status when second step is frontran', async () => { @@ -303,7 +300,7 @@ describe('L1 to L3 Bridging', () => { const depositTx = await l1l3Bridger.deposit( { erc20L1Address: l1Token.address, - amount: ethers.utils.parseEther('1'), + amount, overrides: { manualGasParams: { ...l1l3Bridger.defaultRetryableGasParams, @@ -417,6 +414,7 @@ describe('L1 to L3 Bridging', () => { describe('RelayedErc20L1L3Bridger', () => { let l1l3Bridger: RelayedErc20L1L3Bridger + const amount = BigNumber.from(200) // create the bridger and approve the teleporter before(async () => { @@ -447,7 +445,7 @@ describe('L1 to L3 Bridging', () => { { erc20L1Address: l1Token.address, to: l3Recipient, - amount: ethers.utils.parseEther('1'), + amount, }, setup.l1Signer, setup.l2Signer.provider!, @@ -549,7 +547,7 @@ describe('L1 to L3 Bridging', () => { const l3Balance = await l3Token.balanceOf(l3Recipient) - if (!l3Balance.eq(ethers.utils.parseEther('1'))) { + if (!l3Balance.eq(amount)) { throw new Error('L3 balance is incorrect') } }) From f97dc146fcac965cfc784a1ed1f1fb712dfe7b0e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:37:16 -0500 Subject: [PATCH 74/80] use more-traffic testnode branch --- .github/workflows/build-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index beea3caea4..cf59437ae0 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -115,6 +115,7 @@ jobs: with: no-token-bridge: true l3-node: true + nitro-testnode-ref: more-traffic - name: Copy .env run: cp ./.env-sample ./.env From 5630c84637266d989360121b929694b4df51502b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:36:22 -0600 Subject: [PATCH 75/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2db13222f7..58c2909b00 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -308,7 +308,7 @@ class BaseErc20L1L3Bridger extends BaseL1L3Bridger { l2ForwarderFactoryGasLimit: BigNumber.from(500_000), // measured: 357,751 L2 Gas l1l2TokenBridgeGasLimit: BigNumber.from(700_000), // measured: 531,744 L2 Gas l2l3TokenBridgeGasLimit: BigNumber.from(700_000), // measured: 531,756 L2 Gas - l1l2TokenBridgeRetryableSize: BigNumber.from(1000), // measured: 740. includes variable length information like token symbol (todo: verify this statement) + l1l2TokenBridgeRetryableSize: BigNumber.from(1000), // measured: 740. includes variable length information like token symbol l2l3TokenBridgeRetryableSize: BigNumber.from(1000), // measured: 740. includes variable length information like token symbol } as const From 6268fad9b6bf72f9149be3edee4abdd085f998fc Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:03:28 -0500 Subject: [PATCH 76/80] change run-nitro-test-node branch --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index cf59437ae0..15ab293f1a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -111,7 +111,7 @@ jobs: uses: OffchainLabs/actions/node-modules/restore@main - name: Set up the local node - uses: OffchainLabs/actions/run-nitro-test-node@wait-for-l3 + uses: OffchainLabs/actions/run-nitro-test-node@main with: no-token-bridge: true l3-node: true From aed1c3ddc035fd25184dd17c9f25ef4c2ca100c2 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:18:13 -0500 Subject: [PATCH 77/80] add some comments about failure modes --- src/lib/assetBridger/l1l3Bridger.ts | 69 ++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 58c2909b00..2f1de5529d 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -190,11 +190,11 @@ export type EthDepositStatus = { /** * Status + redemption tx receipt of the retryable ticket to L2 */ - l2RetryableStatus: L1ToL2MessageWaitResult + l2Retryable: L1ToL2MessageWaitResult /** * Status + redemption tx receipt of the retryable ticket to L3 */ - l3RetryableStatus: L1ToL2MessageWaitResult + l3Retryable: L1ToL2MessageWaitResult /** * Whether the teleportation has completed */ @@ -746,6 +746,30 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { * Get the status of a deposit given an L1 tx receipt. See `Erc20DepositStatus` interface for more info on the return type. * * Note: This function does not verify that the tx is actually a deposit tx. + * + * @return Information regarding each step of the deposit + * and `Erc20DepositStatus.completed` which indicates whether the deposit has fully completed. + * + * If `Erc20DepositStatus.bridgeToL2.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the first step has failed (the token bridge to L2). + * The first retryable to L2 must be manually redeemed before proceeding. + * + * If `Erc20DepositStatus.l2ForwarderCall` is `undefined` + * AND `Erc20DepositStatus.retryableL2ForwarderCall.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the second step has failed (the call to the `L2Forwarder` contract). + * The second retryable to L2 must be manually redeemed or the `L2Forwarder` must be called manually before proceeding. + * Note that if the `L2Forwarder` is called manually, then the second retryable to L2 will remain unredeemed. + * + * It is possible that the second retryable to L2 is not redeemed but `Erc20DepositStatus.l2ForwarderCall` is defined. + * In this case the teleportation flow can proceed but the second retryable to L2 will remain unredeemed. + * + * If the call to the `L2Forwarder` cannot succeed (due to bad parameters for example), + * then the forwarder's owner can call `L2Forwarder.rescue` to recover the funds. + * The owner of the `L2Forwarder` is the signer's aliased address by default. + * + * If `Erc20DepositStatus.bridgeToL3.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the third step has failed (the token bridge to L3). + * The retryable to L3 must be manually redeemed. */ public async getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, @@ -806,6 +830,9 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { /** * Get a tx request to deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * + * It is important to specify `params.overrides.l2ForwarderOwner` if signer is not an EOA. + * Failure to set an appropriate owner could result in loss of funds. * * Relayer info will be returned as well as appended to calldata. */ @@ -948,6 +975,25 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { * Get the status of a deposit given an L1 tx receipt and relayer info. * * Note: This function does not verify that the tx is actually a deposit tx. + * + * @return Information regarding each step of the deposit + * and `Erc20DepositStatus.completed` which indicates whether the deposit has fully completed. + * + * If `Erc20DepositStatus.bridgeToL2.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the first step has failed (the token bridge to L2). + * The first retryable to L2 must be manually redeemed before proceeding. + * + * If `Erc20DepositStatus.l2ForwarderCall` is `undefined`, + * then the second step has not yet been executed (the call to the `L2Forwarder` contract). + * The `L2Forwarder` must be called before proceeding. + * + * If the call to the `L2Forwarder` cannot succeed (due to bad parameters for example), + * then the forwarder's owner can call `L2Forwarder.rescue` to recover the funds. + * The owner of the `L2Forwarder` is the signer's address by default. + * + * If `Erc20DepositStatus.bridgeToL3.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the third step has failed (the token bridge to L3). + * the third retryable to L3 must be manually redeemed. */ public getDepositStatus( depositTxReceipt: L1ContractCallTransactionReceipt, @@ -1145,6 +1191,17 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { /** * Get the status of a deposit given an L1 tx receipt. Does not check if the tx is actually a deposit tx. + * + * @return Information regarding each step of the deposit + * and `EthDepositStatus.completed` which indicates whether the deposit has fully completed. + * + * If `EthDepositStatus.l2Retryable.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the first step has failed (creating an ETH deposit retryable to L3). + * The retryable to L2 must be manually redeemed before proceeding. + * + * If `EthDepositStatus.l3Retryable.status` is `L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2`, + * then the second step has failed (depositing ETH to L3 via retryable). + * The retryable to L3 must be manually redeemed. */ public async getDepositStatus( l1TxReceipt: L1EthDepositTransactionReceipt, @@ -1160,8 +1217,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { if (l1l2Redeem.status != L1ToL2MessageStatus.REDEEMED) { return { - l2RetryableStatus: l1l2Redeem, - l3RetryableStatus: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, + l2Retryable: l1l2Redeem, + l3Retryable: { status: L1ToL2MessageStatus.NOT_YET_CREATED }, completed: false, } } @@ -1179,8 +1236,8 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { const l2l3Redeem = await l2l3Message.getSuccessfulRedeem() return { - l2RetryableStatus: l1l2Redeem, - l3RetryableStatus: l2l3Redeem, + l2Retryable: l1l2Redeem, + l3Retryable: l2l3Redeem, completed: l2l3Redeem.status === L1ToL2MessageStatus.REDEEMED, } } From 622de486b5aea3b62a9834fd5fadd41102f54c41 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:50:57 -0600 Subject: [PATCH 78/80] executeDepositRequest --- src/lib/assetBridger/l1l3Bridger.ts | 40 ++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 2f1de5529d..21e9bc3d2d 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -737,8 +737,18 @@ export class Erc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) + return this.executeDepositRequest(txRequest, l1Signer) + } + + /** + * Execute a deposit request to L3 through the L1Teleporter contract. + */ + public async executeDepositRequest( + depositRequest: Required>, + l1Signer: Signer + ): Promise { return L1TransactionReceipt.monkeyPatchContractCallWait( - await l1Signer.sendTransaction(txRequest) + await l1Signer.sendTransaction(depositRequest) ) } @@ -963,6 +973,18 @@ export class RelayedErc20L1L3Bridger extends BaseErc20L1L3Bridger { l3Provider ) + return this.executeDepositRequest(depositRequest, l1Signer) + } + + /** + * Execute the result of `getDepositRequest` to deposit tokens to L3. Will call the `L1GatewayRouter` directly. + * + * Relayer info will be returned as well as appended to calldata. + */ + public async executeDepositRequest( + depositRequest: RelayedErc20DepositRequestResult, + l1Signer: Signer + ): Promise { return { tx: L1TransactionReceipt.monkeyPatchContractCallWait( await l1Signer.sendTransaction(depositRequest.txRequest.txRequest) @@ -1176,7 +1198,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { l1Signer: Signer, l2Provider: Provider, l3Provider: Provider - ): Promise { + ): Promise { const txRequest = await this.getDepositRequest( params, l1Signer, @@ -1184,8 +1206,18 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { l3Provider ) - return L1TransactionReceipt.monkeyPatchEthDepositWait( - await l1Signer.sendTransaction(txRequest.txRequest) + return this.executeDepositRequest(txRequest, l1Signer) + } + + /** + * Execute a deposit request to L3 via a double retryable ticket + */ + public async executeDepositRequest( + depositRequest: L1ToL2TransactionRequest, + l1Signer: Signer + ): Promise { + return L1TransactionReceipt.monkeyPatchContractCallWait( + await l1Signer.sendTransaction(depositRequest.txRequest) ) } From d3c858e8f85d37a943bd96a4d35d8dae2033f99c Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:52:02 -0600 Subject: [PATCH 79/80] use release nitro-testnode --- .github/workflows/build-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 15ab293f1a..5371b00db7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -115,7 +115,6 @@ jobs: with: no-token-bridge: true l3-node: true - nitro-testnode-ref: more-traffic - name: Copy .env run: cp ./.env-sample ./.env From 80342593f201fffff14a83e6d9295501dcac368b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:09:48 -0600 Subject: [PATCH 80/80] ... --- src/lib/assetBridger/l1l3Bridger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/assetBridger/l1l3Bridger.ts b/src/lib/assetBridger/l1l3Bridger.ts index 21e9bc3d2d..d2e978a435 100644 --- a/src/lib/assetBridger/l1l3Bridger.ts +++ b/src/lib/assetBridger/l1l3Bridger.ts @@ -1236,7 +1236,7 @@ export class EthL1L3Bridger extends BaseL1L3Bridger { * The retryable to L3 must be manually redeemed. */ public async getDepositStatus( - l1TxReceipt: L1EthDepositTransactionReceipt, + l1TxReceipt: L1ContractCallTransactionReceipt, l2Provider: Provider, l3Provider: Provider ): Promise {