diff --git a/README.md b/README.md index 09dd971682..a787f212a5 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ Below is an overview of the Arbitrum SDK functionality. See the [tutorials](http - ##### Deposit Ether Into Arbitrum ```ts -import { getL2Network, EthBridger } from '@arbitrum/sdk' +import { getArbitrumNetwork, EthBridger } from '@arbitrum/sdk' -const l2Network = await getL2Network( +const l2Network = await getArbitrumNetwork( l2ChainID /** <-- chain id of target Arbitrum chain */ ) const ethBridger = new EthBridger(l2Network) diff --git a/package.json b/package.json index ee0bc1cb48..47b5fd31f1 100644 --- a/package.json +++ b/package.json @@ -95,4 +95,4 @@ "resolutions": { "lodash.pick": "https://github.com/lodash/lodash/archive/refs/tags/4.17.21.tar.gz" } -} +} \ No newline at end of file diff --git a/scripts/cancelRetryable.ts b/scripts/cancelRetryable.ts index b4623c1aab..939a341f35 100644 --- a/scripts/cancelRetryable.ts +++ b/scripts/cancelRetryable.ts @@ -20,7 +20,7 @@ import { ContractReceipt } from '@ethersproject/contracts' import { instantiateBridge } from './instantiate_bridge' import args from './getCLargs' -import { L1TransactionReceipt } from '../src/lib/message/L1Transaction' +import { L1TransactionReceipt } from '../src/lib/message/ParentTransaction' if (!args.txid) { throw new Error('Include txid (--txid 0xmytxid)') diff --git a/scripts/deployStandard.ts b/scripts/deployStandard.ts index addf09a074..fd66b2b680 100644 --- a/scripts/deployStandard.ts +++ b/scripts/deployStandard.ts @@ -102,7 +102,7 @@ const main = async () => { } /* check token not yet deployed */ - const l2TokenAddress = await erc20Bridger.getL2ERC20Address( + const l2TokenAddress = await erc20Bridger.getChildERC20Address( l1TokenAddress, l1Provider ) diff --git a/scripts/genNetwork.ts b/scripts/genNetwork.ts index c704ee622c..d0b7fdff45 100644 --- a/scripts/genNetwork.ts +++ b/scripts/genNetwork.ts @@ -2,10 +2,14 @@ import * as dotenv from 'dotenv' dotenv.config() import { execSync } from 'child_process' import * as fs from 'fs' -import { L2Network } from '../src' -import { ARB_MINIMUM_BLOCK_TIME_IN_SECONDS } from '../src/lib/dataEntities/constants' + import { IERC20Bridge__factory } from '../src/lib/abi/factories/IERC20Bridge__factory' import { ethers } from 'ethers' +import { + L2Network, + ArbitrumNetwork, + mapL2NetworkToArbitrumNetwork, +} from '../src/lib/dataEntities/networks' const isTestingOrbitChains = process.env.ORBIT_TEST === '1' @@ -39,24 +43,29 @@ async function patchNetworks( l2Network: L2Network, l3Network: L2Network | undefined, l2Provider: ethers.providers.Provider | undefined -) { - // we need to add partnerChainIDs to the L2 network - l2Network.partnerChainIDs = l3Network ? [l3Network.chainID] : [] - l2Network.blockTime = ARB_MINIMUM_BLOCK_TIME_IN_SECONDS +): Promise<{ + patchedL2Network: ArbitrumNetwork + patchedL3Network?: ArbitrumNetwork +}> { + const patchedL2Network = mapL2NetworkToArbitrumNetwork(l2Network) // native token for l3 if (l3Network && l2Provider) { - l3Network.partnerChainIDs = [] - l3Network.blockTime = ARB_MINIMUM_BLOCK_TIME_IN_SECONDS + const patchedL3Network = mapL2NetworkToArbitrumNetwork(l3Network) + try { - l3Network.nativeToken = await IERC20Bridge__factory.connect( + patchedL3Network.nativeToken = await IERC20Bridge__factory.connect( l3Network.ethBridge.bridge, l2Provider ).nativeToken() } catch (e) { // l3 network doesn't have a native token } + + return { patchedL2Network, patchedL3Network } } + + return { patchedL2Network } } async function main() { @@ -66,17 +75,24 @@ async function main() { if (isTestingOrbitChains) { const { l2Network: l3Network } = getLocalNetworksFromContainer('l2l3') - await patchNetworks( + const { patchedL2Network, patchedL3Network } = await patchNetworks( output.l2Network, l3Network, new ethers.providers.JsonRpcProvider(process.env['ARB_URL']) ) + output = { - l1Network: output.l2Network, - l2Network: l3Network, + l1Network: patchedL2Network, + l2Network: patchedL3Network, } } else { - await patchNetworks(output.l2Network, undefined, undefined) + const { patchedL2Network } = await patchNetworks( + output.l2Network, + undefined, + undefined + ) + + output.l2Network = patchedL2Network } fs.writeFileSync('localNetwork.json', JSON.stringify(output, null, 2)) diff --git a/scripts/instantiate_bridge.ts b/scripts/instantiate_bridge.ts index 25bd767ad2..747a81e8b6 100644 --- a/scripts/instantiate_bridge.ts +++ b/scripts/instantiate_bridge.ts @@ -23,14 +23,11 @@ import dotenv from 'dotenv' import args from './getCLargs' import { EthBridger, InboxTools, Erc20Bridger } from '../src' import { - l1Networks, - l2Networks, - L1Network, - L2Network, + ArbitrumNetwork, + getArbitrumNetwork, } from '../src/lib/dataEntities/networks' import { Signer } from 'ethers' import { AdminErc20Bridger } from '../src/lib/assetBridger/erc20Bridger' -import { isDefined } from '../src/lib/utils/lib' dotenv.config() @@ -39,19 +36,18 @@ const ethKey = process.env['ETH_KEY'] as string const defaultNetworkId = 421614 -export const instantiateBridge = ( +export const instantiateBridge = async ( l1PkParam?: string, l2PkParam?: string -): { - l1Network: L1Network - l2Network: L2Network +): Promise<{ + l2Network: ArbitrumNetwork l1Signer: Signer l2Signer: Signer erc20Bridger: Erc20Bridger ethBridger: EthBridger adminErc20Bridger: AdminErc20Bridger inboxTools: InboxTools -} => { +}> => { if (!l1PkParam && !ethKey) { throw new Error('need ARB_KEY var') } @@ -68,23 +64,8 @@ export const instantiateBridge = ( l2NetworkID = defaultNetworkId } - const isL1 = isDefined(l1Networks[l2NetworkID]) - const isL2 = isDefined(l2Networks[l2NetworkID]) - if (!isL1 && !isL2) { - throw new Error(`Unrecognized network ID: ${l2NetworkID}`) - } - if (!isL2) { - throw new Error(`Tests must specify an L2 network ID: ${l2NetworkID}`) - } - - const l2Network = l2Networks[l2NetworkID] - const l1Network = l1Networks[l2Network.partnerChainID] - if (!l1Network) { - throw new Error( - `Unrecognised partner chain id: ${l2Network.partnerChainID}` - ) - } + const l2Network = await getArbitrumNetwork(l2NetworkID) const l1Rpc = (() => { if (l2NetworkID === 42161) return process.env['MAINNET_RPC'] as string @@ -140,7 +121,6 @@ export const instantiateBridge = ( const inboxTools = new InboxTools(l1Signer, l2Network) return { - l1Network, l2Network, l1Signer, l2Signer, diff --git a/scripts/lib.ts b/scripts/lib.ts index 4fe83cc892..6870536b1d 100644 --- a/scripts/lib.ts +++ b/scripts/lib.ts @@ -19,8 +19,8 @@ import { ContractReceipt } from '@ethersproject/contracts' import { ERC20__factory } from '../src/lib/abi/factories/ERC20__factory' -import { L1ToL2MessageStatus } from '../src/lib/message/L1ToL2Message' -import { L1TransactionReceipt } from '../src/lib/message/L1Transaction' +import { L1ToL2MessageStatus } from '../src/lib/message/ParentToChildMessage' +import { L1TransactionReceipt } from '../src/lib/message/ParentTransaction' import { testSetup } from '../scripts/testSetup' export const setStandardGateWays = async ( @@ -39,9 +39,10 @@ export const setGateWays = async ( type: 'standard' | 'arbCustom', overrideGateways: string[] = [] ): Promise => { - const { adminErc20Bridger, l1Signer, l2Network, l2Signer } = await testSetup() - const l1Provider = l1Signer.provider! - const l2Provider = l2Signer.provider! + const { adminErc20Bridger, parentSigner, childChain, childSigner } = + await testSetup() + const parentProvider = parentSigner.provider! + const childProvider = childSigner.provider! if (tokens.length === 0) { throw new Error('Include some tokens to set') } @@ -55,7 +56,7 @@ export const setGateWays = async ( for (const tokenAddress of tokens) { try { - const token = await ERC20__factory.connect(tokenAddress, l1Provider) + const token = await ERC20__factory.connect(tokenAddress, parentProvider) console.warn('calling name for ', tokenAddress) const symbol = await token.symbol() @@ -85,17 +86,17 @@ export const setGateWays = async ( if (overrideGateways.length > 0) { return overrideGateways } else if (type === 'standard') { - return tokens.map(() => l2Network.tokenBridge.l1ERC20Gateway) + return tokens.map(() => childChain.tokenBridge.l1ERC20Gateway) } else if (type === 'arbCustom') { - return tokens.map(() => l2Network.tokenBridge.l1CustomGateway) + return tokens.map(() => childChain.tokenBridge.l1CustomGateway) } else { throw new Error('Unhandled else case') } })() const res = await adminErc20Bridger.setGateways( - l1Signer, - l2Provider, + parentSigner, + childProvider, gateways.map((g, i) => ({ tokenAddr: tokens[i], gatewayAddr: gateways[i], @@ -110,7 +111,7 @@ export const setGateWays = async ( } console.log('redeeming retryable ticket:') - const l2Tx = (await rec.getL1ToL2Messages(l2Signer))[0] + const l2Tx = (await rec.getParentToChildMessages(childSigner))[0] if (!l2Tx) throw new Error('No l1 to l2 message found.') const messageRes = await l2Tx.waitForStatus() if (messageRes.status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { @@ -123,13 +124,13 @@ export const setGateWays = async ( } export const checkRetryableStatus = async (l1Hash: string): Promise => { - const { l1Signer, l2Signer } = await testSetup() - const l1Provider = l1Signer.provider! - const l2Provider = l2Signer.provider! - const rec = await l1Provider.getTransactionReceipt(l1Hash) + const { parentSigner, childSigner } = await testSetup() + const parentProvider = parentSigner.provider! + const childProvider = childSigner.provider! + const rec = await parentProvider.getTransactionReceipt(l1Hash) if (!rec) throw new Error('L1 tx not found!') - const messages = await new L1TransactionReceipt(rec).getL1ToL2Messages( - l2Provider + const messages = await new L1TransactionReceipt(rec).getParentToChildMessages( + childProvider ) for (const message of messages) { diff --git a/scripts/redeemRetryable.ts b/scripts/redeemRetryable.ts index deedb45f5d..b79cd89f0e 100644 --- a/scripts/redeemRetryable.ts +++ b/scripts/redeemRetryable.ts @@ -20,7 +20,7 @@ import { ContractReceipt } from '@ethersproject/contracts' import { testSetup } from '../scripts/testSetup' import args from './getCLargs' -import { L1TransactionReceipt } from '../src/lib/message/L1Transaction' +import { L1TransactionReceipt } from '../src/lib/message/ParentTransaction' import { L1ToL2MessageStatus, L1ToL2MessageWriter } from '../src' import { fundL2 } from '../integration_test/testHelpers' @@ -35,14 +35,14 @@ if (!l1Txn) { } ;(async () => { - const { l1Signer, l2Signer } = await testSetup() + const { parentSigner, childSigner } = await testSetup() // TODO: Should use the PRIVKEY envvar signer directly - fundL2(l2Signer) - const l1Provider = l1Signer.provider! + fundL2(childSigner) + const l1Provider = parentSigner.provider! const l1Receipt = new L1TransactionReceipt( await l1Provider.getTransactionReceipt(l1Txn) ) - const l1ToL2Message = await l1Receipt.getL1ToL2Message(l2Signer) + const l1ToL2Message = await l1Receipt.getL1ToL2Message(childSigner) if (l1ToL2Message instanceof L1ToL2MessageWriter) { const redeemStatus = (await l1ToL2Message.waitForStatus()).status if (redeemStatus == L1ToL2MessageStatus.REDEEMED) { diff --git a/scripts/sendL2SignedMsg.ts b/scripts/sendL2SignedMsg.ts index 10ac03e9e7..08ccc8966f 100644 --- a/scripts/sendL2SignedMsg.ts +++ b/scripts/sendL2SignedMsg.ts @@ -18,19 +18,19 @@ import { BigNumber } from 'ethers' import { InboxTools } from '../src/lib/inbox/inbox' -import { getL2Network } from '../src/lib/dataEntities/networks' +import { getArbitrumNetwork } from '../src/lib/dataEntities/networks' import { testSetup } from '../scripts/testSetup' const sendSignedMsg = async () => { const { l1Deployer, l2Deployer } = await testSetup() - const l2Network = await getL2Network(await l2Deployer.getChainId()) + const l2Network = await getArbitrumNetwork(await l2Deployer.getChainId()) const inbox = new InboxTools(l1Deployer, l2Network) const message = { to: await l2Deployer.getAddress(), value: BigNumber.from(0), data: '0x12', } - const signedTx = await inbox.signL2Tx(message, l2Deployer) - await inbox.sendL2SignedTx(signedTx) + const signedTx = await inbox.signChildChainTx(message, l2Deployer) + await inbox.sendChildChainSignedTx(signedTx) } sendSignedMsg() diff --git a/scripts/testSetup.ts b/scripts/testSetup.ts index b16cbc8b6c..a7a05d05c9 100644 --- a/scripts/testSetup.ts +++ b/scripts/testSetup.ts @@ -23,11 +23,11 @@ import dotenv from 'dotenv' import { EthBridger, InboxTools, Erc20Bridger } from '../src' import { - L1Network, L2Network, - getL1Network, - getL2Network, - addCustomNetwork, + ArbitrumNetwork, + mapL2NetworkToArbitrumNetwork, + getArbitrumNetwork, + addCustomArbitrumNetwork, } from '../src/lib/dataEntities/networks' import { Signer } from 'ethers' import { AdminErc20Bridger } from '../src/lib/assetBridger/erc20Bridger' @@ -35,11 +35,11 @@ import * as path from 'path' import * as fs from 'fs' import { ArbSdkError } from '../src/lib/dataEntities/errors' import { - approveL1CustomFeeToken, - fundL1CustomFeeToken, - isL2NetworkWithCustomFeeToken, + approveParentCustomFeeToken, + fundParentCustomFeeToken, + isArbitrumNetworkWithCustomFeeToken, } from '../tests/integration/custom-fee-token/customFeeTokenTestHelpers' -import { fundL1 } from '../tests/integration/testHelpers' +import { fundParentSigner } from '../tests/integration/testHelpers' dotenv.config() @@ -72,114 +72,79 @@ export const getSigner = (provider: JsonRpcProvider, key?: string) => { } export const testSetup = async (): Promise<{ - l1Network: L1Network | L2Network - l2Network: L2Network - l1Signer: Signer - l2Signer: Signer - l1Provider: Provider - l2Provider: Provider + childChain: ArbitrumNetwork + parentSigner: Signer + childSigner: Signer + parentProvider: Provider + childProvider: Provider erc20Bridger: Erc20Bridger ethBridger: EthBridger adminErc20Bridger: AdminErc20Bridger inboxTools: InboxTools - l1Deployer: Signer - l2Deployer: Signer + parentDeployer: Signer + childDeployer: Signer }> => { const ethProvider = new JsonRpcProvider(config.ethUrl) const arbProvider = new JsonRpcProvider(config.arbUrl) - const l1Deployer = getSigner(ethProvider, config.ethKey) - const l2Deployer = getSigner(arbProvider, config.arbKey) + const parentDeployer = getSigner(ethProvider, config.ethKey) + const childDeployer = getSigner(arbProvider, config.arbKey) const seed = Wallet.createRandom() - const l1Signer = seed.connect(ethProvider) - const l2Signer = seed.connect(arbProvider) + const parentSigner = seed.connect(ethProvider) + const childSigner = seed.connect(arbProvider) + + let setChildChain: ArbitrumNetwork - let setL1Network: L1Network | L2Network, setL2Network: L2Network try { - const l1Network = isTestingOrbitChains - ? await getL2Network(l1Deployer) - : await getL1Network(l1Deployer) - const l2Network = await getL2Network(l2Deployer) - setL1Network = l1Network - setL2Network = l2Network + const l2Network = await getArbitrumNetwork(childDeployer) + setChildChain = l2Network } catch (err) { // the networks havent been added yet - // check if theres an existing network available - const localNetworkFile = getLocalNetworksFromFile() - - const { l1Network, l2Network } = localNetworkFile - - if (isTestingOrbitChains) { - const _l1Network = l1Network as L2Network - const ethLocal: L1Network = { - blockTime: 10, - chainID: _l1Network.partnerChainID, - explorerUrl: '', - isCustom: true, - name: 'EthLocal', - partnerChainIDs: [_l1Network.chainID], - isArbitrum: false, - } - - addCustomNetwork({ - customL1Network: ethLocal, - customL2Network: _l1Network, - }) - - addCustomNetwork({ - customL2Network: l2Network, - }) - - setL1Network = l1Network - setL2Network = l2Network - } else { - addCustomNetwork({ - customL1Network: l1Network as L1Network, - customL2Network: l2Network, - }) - - setL1Network = l1Network - setL2Network = l2Network - } + const { l2Network: childChain } = getLocalNetworksFromFile() + + addCustomArbitrumNetwork(childChain) + setChildChain = childChain } - const erc20Bridger = new Erc20Bridger(setL2Network) - const adminErc20Bridger = new AdminErc20Bridger(setL2Network) - const ethBridger = new EthBridger(setL2Network) - const inboxTools = new InboxTools(l1Signer, setL2Network) + const erc20Bridger = new Erc20Bridger(setChildChain) + const adminErc20Bridger = new AdminErc20Bridger(setChildChain) + const ethBridger = new EthBridger(setChildChain) + const inboxTools = new InboxTools(parentSigner, setChildChain) - if (isL2NetworkWithCustomFeeToken()) { - await fundL1(l1Signer) - await fundL1CustomFeeToken(l1Signer) - await approveL1CustomFeeToken(l1Signer) + if (isArbitrumNetworkWithCustomFeeToken()) { + await fundParentSigner(parentSigner) + await fundParentCustomFeeToken(parentSigner) + await approveParentCustomFeeToken(parentSigner) } return { - l1Signer, - l2Signer, - l1Provider: ethProvider, - l2Provider: arbProvider, - l1Network: setL1Network, - l2Network: setL2Network, + parentSigner, + childSigner, + parentProvider: ethProvider, + childProvider: arbProvider, + childChain: setChildChain, erc20Bridger, adminErc20Bridger, ethBridger, inboxTools, - l1Deployer, - l2Deployer, + parentDeployer, + childDeployer, } } export function getLocalNetworksFromFile(): { - l1Network: L1Network | L2Network - l2Network: L2Network + l2Network: ArbitrumNetwork } { const pathToLocalNetworkFile = path.join(__dirname, '..', 'localNetwork.json') if (!fs.existsSync(pathToLocalNetworkFile)) { throw new ArbSdkError('localNetwork.json not found, must gen:network first') } const localNetworksFile = fs.readFileSync(pathToLocalNetworkFile, 'utf8') - return JSON.parse(localNetworksFile) + const localL2: L2Network = JSON.parse(localNetworksFile).l2Network + + return { + l2Network: mapL2NetworkToArbitrumNetwork(localL2), + } } diff --git a/src/index.ts b/src/index.ts index 99056afc27..9ab26c544b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,44 +19,41 @@ export { EthBridger } from './lib/assetBridger/ethBridger' export { Erc20Bridger } from './lib/assetBridger/erc20Bridger' export { - L2TransactionReceipt, - L2ContractTransaction, -} from './lib/message/L2Transaction' + ChildTransactionReceipt, + ChildContractTransaction, +} from './lib/message/ChildTransaction' export { - L2ToL1Message, - L2ToL1MessageWriter, - L2ToL1MessageReader, -} from './lib/message/L2ToL1Message' + ChildToParentMessage, + ChildToParentMessageWriter, + ChildToParentMessageReader, +} from './lib/message/ChildToParentMessage' export { - L1ContractTransaction, - L1TransactionReceipt, -} from './lib/message/L1Transaction' + ParentContractTransaction, + ParentTransactionReceipt, +} from './lib/message/ParentTransaction' export { - L1ToL2MessageStatus, + ParentToChildMessageStatus, EthDepositStatus, - L1ToL2Message, - L1ToL2MessageReader, - L1ToL2MessageReaderClassic, - L1ToL2MessageWriter, -} from './lib/message/L1ToL2Message' -export { L1ToL2MessageGasEstimator } from './lib/message/L1ToL2MessageGasEstimator' + ParentToChildMessage, + ParentToChildMessageReader, + ParentToChildMessageReaderClassic, + ParentToChildMessageWriter, +} from './lib/message/ParentToChildMessage' +export { ParentToChildMessageGasEstimator } from './lib/message/ParentToChildMessageGasEstimator' export { argSerializerConstructor } from './lib/utils/byte_serialize_params' export { CallInput, MultiCaller } from './lib/utils/multicall' export { - L1Networks, - L2Networks, - L1Network, - L2Network, - getL1Network, - getL2Network, - addCustomNetwork, + ArbitrumNetwork, + getArbitrumNetwork, + addCustomArbitrumNetwork, addDefaultLocalNetwork, + getChildrenForNetwork, } from './lib/dataEntities/networks' export { InboxTools } from './lib/inbox/inbox' export { EventFetcher } from './lib/utils/eventFetcher' export { ArbitrumProvider } from './lib/utils/arbProvider' export * as constants from './lib/dataEntities/constants' -export { L2ToL1MessageStatus } from './lib/dataEntities/message' +export { ChildToParentMessageStatus } from './lib/dataEntities/message' export { RetryableData, RetryableDataTools, diff --git a/src/lib/assetBridger/assetBridger.ts b/src/lib/assetBridger/assetBridger.ts index f6eeb77389..75c2962611 100644 --- a/src/lib/assetBridger/assetBridger.ts +++ b/src/lib/assetBridger/assetBridger.ts @@ -18,28 +18,19 @@ import { constants } from 'ethers' -import { L1ContractTransaction } from '../message/L1Transaction' -import { L2ContractTransaction } from '../message/L2Transaction' +import { ParentContractTransaction } from '../message/ParentTransaction' +import { ChildContractTransaction } from '../message/ChildTransaction' -import { - L1Network, - L2Network, - getParentForNetwork, -} from '../dataEntities/networks' +import { ArbitrumNetwork } from '../dataEntities/networks' import { SignerOrProvider, SignerProviderUtils, } from '../dataEntities/signerOrProvider' /** - * Base for bridging assets from l1 to l2 and back + * Base for bridging assets from parent-to-child and back */ export abstract class AssetBridger { - /** - * Parent chain for the given Arbitrum chain, can be an L1 or an L2 - */ - public readonly l1Network: L1Network | L2Network - /** * In case of a chain that uses ETH as its native/gas token, this is either `undefined` or the zero address * @@ -47,25 +38,27 @@ export abstract class AssetBridger { */ public readonly nativeToken?: string - public constructor(public readonly l2Network: L2Network) { - this.l1Network = getParentForNetwork(l2Network) - this.nativeToken = l2Network.nativeToken + public constructor(public readonly childChain: ArbitrumNetwork) { + this.nativeToken = childChain.nativeToken } /** - * Check the signer/provider matches the l1Network, throws if not + * Check the signer/provider matches the parentChain, throws if not * @param sop */ - protected async checkL1Network(sop: SignerOrProvider): Promise { - await SignerProviderUtils.checkNetworkMatches(sop, this.l1Network.chainID) + protected async checkParentChain(sop: SignerOrProvider): Promise { + await SignerProviderUtils.checkNetworkMatches( + sop, + this.childChain.parentChainId + ) } /** - * Check the signer/provider matches the l2Network, throws if not + * Check the signer/provider matches the childChain, throws if not * @param sop */ - protected async checkL2Network(sop: SignerOrProvider): Promise { - await SignerProviderUtils.checkNetworkMatches(sop, this.l2Network.chainID) + protected async checkChildChain(sop: SignerOrProvider): Promise { + await SignerProviderUtils.checkNetworkMatches(sop, this.childChain.chainId) } /** @@ -77,16 +70,18 @@ export abstract class AssetBridger { } /** - * Transfer assets from L1 to L2 + * Transfer assets from parent-to-child * @param params */ - public abstract deposit(params: DepositParams): Promise + public abstract deposit( + params: DepositParams + ): Promise /** - * Transfer assets from L2 to L1 + * Transfer assets from child-to-parent * @param params */ public abstract withdraw( params: WithdrawParams - ): Promise + ): Promise } diff --git a/src/lib/assetBridger/erc20Bridger.ts b/src/lib/assetBridger/erc20Bridger.ts index af036892fa..87e452b07f 100644 --- a/src/lib/assetBridger/erc20Bridger.ts +++ b/src/lib/assetBridger/erc20Bridger.ts @@ -42,42 +42,42 @@ import { WithdrawalInitiatedEvent } from '../abi/L2ArbitrumGateway' import { GatewaySetEvent } from '../abi/L1GatewayRouter' import { GasOverrides, - L1ToL2MessageGasEstimator, -} from '../message/L1ToL2MessageGasEstimator' + ParentToChildMessageGasEstimator, +} from '../message/ParentToChildMessageGasEstimator' import { SignerProviderUtils } from '../dataEntities/signerOrProvider' -import { L2Network, getL2Network } from '../dataEntities/networks' +import { ArbitrumNetwork, getArbitrumNetwork } from '../dataEntities/networks' import { ArbSdkError, MissingProviderArbSdkError } from '../dataEntities/errors' import { DISABLED_GATEWAY } from '../dataEntities/constants' import { EventFetcher } from '../utils/eventFetcher' import { EthDepositParams, EthWithdrawParams } from './ethBridger' import { AssetBridger } from './assetBridger' import { - L1ContractCallTransaction, - L1ContractTransaction, - L1TransactionReceipt, -} from '../message/L1Transaction' + ParentContractCallTransaction, + ParentContractTransaction, + ParentTransactionReceipt, +} from '../message/ParentTransaction' import { - L2ContractTransaction, - L2TransactionReceipt, -} from '../message/L2Transaction' + ChildContractTransaction, + ChildTransactionReceipt, +} from '../message/ChildTransaction' import { - isL1ToL2TransactionRequest, - isL2ToL1TransactionRequest, - L1ToL2TransactionRequest, - L2ToL1TransactionRequest, + isParentToChildTransactionRequest, + isChildToParentTransactionRequest, + ChildToParentTransactionRequest, + ParentToChildTransactionRequest, } from '../dataEntities/transactionRequest' import { defaultAbiCoder } from 'ethers/lib/utils' import { OmitTyped, RequiredPick } from '../utils/types' import { RetryableDataTools } from '../dataEntities/retryableData' import { EventArgs } from '../dataEntities/event' -import { L1ToL2MessageGasParams } from '../message/L1ToL2MessageCreator' +import { ParentToChildMessageGasParams } from '../message/ParentToChildMessageCreator' import { isArbitrumChain } from '../utils/lib' export interface TokenApproveParams { /** - * L1 address of the ERC20 token contract + * Parent chain address of the ERC20 token contract */ - erc20L1Address: string + erc20ParentAddress: string /** * Amount to approve. Defaults to max int. */ @@ -90,15 +90,15 @@ export interface TokenApproveParams { export interface Erc20DepositParams extends EthDepositParams { /** - * An L2 provider + * A child provider */ - l2Provider: Provider + childProvider: Provider /** - * L1 address of the token ERC20 contract + * Parent chain address of the token ERC20 contract */ - erc20L1Address: string + erc20ParentAddress: string /** - * L2 address of the entity receiving the funds. Defaults to the l1FromAddress + * Child chain address of the entity receiving the funds. Defaults to the l1FromAddress */ destinationAddress?: string /** @@ -125,40 +125,43 @@ export interface Erc20DepositParams extends EthDepositParams { export interface Erc20WithdrawParams extends EthWithdrawParams { /** - * L1 address of the token ERC20 contract + * Parent chain address of the token ERC20 contract */ - erc20l1Address: string + erc20ParentAddress: string } -export type L1ToL2TxReqAndSignerProvider = L1ToL2TransactionRequest & { - l1Signer: Signer - overrides?: Overrides -} +export type ParentToChildTxReqAndSignerProvider = + ParentToChildTransactionRequest & { + parentSigner: Signer + overrides?: Overrides + } -export type L2ToL1TxReqAndSigner = L2ToL1TransactionRequest & { - l2Signer: Signer +export type ChildToParentTxReqAndSigner = ChildToParentTransactionRequest & { + childSigner: Signer overrides?: Overrides } -type SignerTokenApproveParams = TokenApproveParams & { l1Signer: Signer } -type ProviderTokenApproveParams = TokenApproveParams & { l1Provider: Provider } +type SignerTokenApproveParams = TokenApproveParams & { parentSigner: Signer } +type ProviderTokenApproveParams = TokenApproveParams & { + parentProvider: Provider +} export type ApproveParamsOrTxRequest = | SignerTokenApproveParams | { txRequest: Required> - l1Signer: Signer + parentSigner: Signer overrides?: Overrides } /** - * The deposit request takes the same args as the actual deposit. Except we dont require a signer object + * The deposit request takes the same args as the actual deposit. Except we don't require a signer object * only a provider */ type DepositRequest = OmitTyped< Erc20DepositParams, - 'overrides' | 'l1Signer' + 'overrides' | 'parentSigner' > & { - l1Provider: Provider + parentProvider: Provider /** * Address that is depositing the assets */ @@ -171,65 +174,65 @@ type DefaultedDepositRequest = RequiredPick< > /** - * Bridger for moving ERC20 tokens back and forth between L1 to L2 + * Bridger for moving ERC20 tokens back and forth between parent-to-child */ export class Erc20Bridger extends AssetBridger< - Erc20DepositParams | L1ToL2TxReqAndSignerProvider, - OmitTyped | L2ToL1TransactionRequest + Erc20DepositParams | ParentToChildTxReqAndSignerProvider, + OmitTyped | ChildToParentTransactionRequest > { public static MAX_APPROVAL: BigNumber = MaxUint256 public static MIN_CUSTOM_DEPOSIT_GAS_LIMIT = BigNumber.from(275000) /** - * Bridger for moving ERC20 tokens back and forth between L1 to L2 + * Bridger for moving ERC20 tokens back and forth between parent-to-child */ - public constructor(l2Network: L2Network) { - super(l2Network) + public constructor(childChain: ArbitrumNetwork) { + super(childChain) } /** - * Instantiates a new Erc20Bridger from an L2 Provider - * @param l2Provider + * Instantiates a new Erc20Bridger from a child provider + * @param childProvider * @returns */ - public static async fromProvider(l2Provider: Provider) { - return new Erc20Bridger(await getL2Network(l2Provider)) + public static async fromProvider(childProvider: Provider) { + return new Erc20Bridger(await getArbitrumNetwork(childProvider)) } /** * Get the address of the l1 gateway for this token - * @param erc20L1Address - * @param l1Provider + * @param erc20ParentAddress + * @param parentProvider * @returns */ public async getL1GatewayAddress( - erc20L1Address: string, - l1Provider: Provider + erc20ParentAddress: string, + parentProvider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this.checkParentChain(parentProvider) return await L1GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l1GatewayRouter, - l1Provider - ).getGateway(erc20L1Address) + this.childChain.tokenBridge.l1GatewayRouter, + parentProvider + ).getGateway(erc20ParentAddress) } /** * Get the address of the l2 gateway for this token - * @param erc20L1Address - * @param l2Provider + * @param erc20ParentAddress + * @param childProvider * @returns */ public async getL2GatewayAddress( - erc20L1Address: string, - l2Provider: Provider + erc20ParentAddress: string, + childProvider: Provider ): Promise { - await this.checkL2Network(l2Provider) + await this.checkChildChain(childProvider) return await L2GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l2GatewayRouter, - l2Provider - ).getGateway(erc20L1Address) + this.childChain.tokenBridge.l2GatewayRouter, + childProvider + ).getGateway(erc20ParentAddress) } /** @@ -259,16 +262,18 @@ export class Erc20Bridger extends AssetBridger< throw new Error('chain uses ETH as its native/gas token') } - await this.checkL1Network(params.l1Signer) + await this.checkParentChain(params.parentSigner) const approveGasTokenRequest = this.isApproveParams(params) ? await this.getApproveGasTokenRequest({ ...params, - l1Provider: SignerProviderUtils.getProviderOrThrow(params.l1Signer), + parentProvider: SignerProviderUtils.getProviderOrThrow( + params.parentSigner + ), }) : params.txRequest - return params.l1Signer.sendTransaction({ + return params.parentSigner.sendTransaction({ ...approveGasTokenRequest, ...params.overrides, }) @@ -285,8 +290,8 @@ export class Erc20Bridger extends AssetBridger< ): Promise>> { // you approve tokens to the gateway that the router will use const gatewayAddress = await this.getL1GatewayAddress( - params.erc20L1Address, - SignerProviderUtils.getProviderOrThrow(params.l1Provider) + params.erc20ParentAddress, + SignerProviderUtils.getProviderOrThrow(params.parentProvider) ) const iErc20Interface = ERC20__factory.createInterface() @@ -296,7 +301,7 @@ export class Erc20Bridger extends AssetBridger< ]) return { - to: params.erc20L1Address, + to: params.erc20ParentAddress, data, value: BigNumber.from(0), } @@ -305,7 +310,7 @@ export class Erc20Bridger extends AssetBridger< private isApproveParams( params: ApproveParamsOrTxRequest ): params is SignerTokenApproveParams { - return (params as SignerTokenApproveParams).erc20L1Address != undefined + return (params as SignerTokenApproveParams).erc20ParentAddress != undefined } /** @@ -316,40 +321,42 @@ export class Erc20Bridger extends AssetBridger< public async approveToken( params: ApproveParamsOrTxRequest ): Promise { - await this.checkL1Network(params.l1Signer) + await this.checkParentChain(params.parentSigner) const approveRequest = this.isApproveParams(params) ? await this.getApproveTokenRequest({ ...params, - l1Provider: SignerProviderUtils.getProviderOrThrow(params.l1Signer), + parentProvider: SignerProviderUtils.getProviderOrThrow( + params.parentSigner + ), }) : params.txRequest - return await params.l1Signer.sendTransaction({ + return await params.parentSigner.sendTransaction({ ...approveRequest, ...params.overrides, }) } /** - * Get the L2 events created by a withdrawal - * @param l2Provider + * Get the child chain events created by a withdrawal + * @param childProvider * @param gatewayAddress - * @param l1TokenAddress + * @param parentTokenAddress * @param fromAddress * @param filter * @returns */ public async getL2WithdrawalEvents( - l2Provider: Provider, + childProvider: Provider, gatewayAddress: string, filter: { fromBlock: BlockTag; toBlock: BlockTag }, - l1TokenAddress?: string, + parentTokenAddress?: string, fromAddress?: string, toAddress?: string ): Promise<(EventArgs & { txHash: string })[]> { - await this.checkL2Network(l2Provider) + await this.checkChildChain(childProvider) - const eventFetcher = new EventFetcher(l2Provider) + const eventFetcher = new EventFetcher(childProvider) const events = ( await eventFetcher.getEvents( L2ArbitrumGateway__factory, @@ -363,11 +370,11 @@ export class Erc20Bridger extends AssetBridger< ) ).map(a => ({ txHash: a.transactionHash, ...a.event })) - return l1TokenAddress + return parentTokenAddress ? events.filter( log => log.l1Token.toLocaleLowerCase() === - l1TokenAddress.toLocaleLowerCase() + parentTokenAddress.toLocaleLowerCase() ) : events } @@ -375,17 +382,17 @@ export class Erc20Bridger extends AssetBridger< /** * Does the provided address look like a weth gateway * @param potentialWethGatewayAddress - * @param l1Provider + * @param parentProvider * @returns */ private async looksLikeWethGateway( potentialWethGatewayAddress: string, - l1Provider: Provider + parentProvider: Provider ) { try { const potentialWethGateway = L1WethGateway__factory.connect( potentialWethGatewayAddress, - l1Provider + parentProvider ) await potentialWethGateway.callStatic.l1Weth() return true @@ -405,17 +412,17 @@ export class Erc20Bridger extends AssetBridger< /** * Is this a known or unknown WETH gateway * @param gatewayAddress - * @param l1Provider + * @param parentProvider * @returns */ private async isWethGateway( gatewayAddress: string, - l1Provider: Provider + parentProvider: Provider ): Promise { - const wethAddress = this.l2Network.tokenBridge.l1WethGateway - if (this.l2Network.isCustom) { + const wethAddress = this.childChain.tokenBridge.l1WethGateway + if (this.childChain.isCustom) { // For custom network, we do an ad-hoc check to see if it's a WETH gateway - if (await this.looksLikeWethGateway(gatewayAddress, l1Provider)) { + if (await this.looksLikeWethGateway(gatewayAddress, parentProvider)) { return true } // ...otherwise we directly check it against the config file @@ -426,19 +433,19 @@ export class Erc20Bridger extends AssetBridger< } /** - * Get the L2 token contract at the provided address + * Get the child chain 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 childProvider * @param l2TokenAddr * @returns */ - public getL2TokenContract( - l2Provider: Provider, + public getChildTokenContract( + childProvider: Provider, l2TokenAddr: string ): L2GatewayToken { - return L2GatewayToken__factory.connect(l2TokenAddr, l2Provider) + return L2GatewayToken__factory.connect(l2TokenAddr, childProvider) } /** @@ -446,70 +453,76 @@ export class Erc20Bridger extends AssetBridger< * 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 + * @param parentProvider + * @param parentTokenAddr * @returns */ - public getL1TokenContract(l1Provider: Provider, l1TokenAddr: string): ERC20 { - return ERC20__factory.connect(l1TokenAddr, l1Provider) + public getParentTokenContract( + parentProvider: Provider, + parentTokenAddr: string + ): ERC20 { + return ERC20__factory.connect(parentTokenAddr, parentProvider) } /** - * Get the corresponding L2 for the provided L1 token - * @param erc20L1Address - * @param l1Provider + * Get the corresponding child chain token address for the provided parent chain token + * @param erc20ParentAddress + * @param parentProvider * @returns */ - public async getL2ERC20Address( - erc20L1Address: string, - l1Provider: Provider + public async getChildERC20Address( + erc20ParentAddress: string, + parentProvider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this.checkParentChain(parentProvider) const l1GatewayRouter = L1GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l1GatewayRouter, - l1Provider + this.childChain.tokenBridge.l1GatewayRouter, + parentProvider ) return await l1GatewayRouter.functions - .calculateL2TokenAddress(erc20L1Address) + .calculateL2TokenAddress(erc20ParentAddress) .then(([res]) => res) } /** - * Get the corresponding L1 for the provided L2 token - * Validates the returned address against the l2 router to ensure it is correctly mapped to the provided erc20L2Address - * @param erc20L2Address - * @param l2Provider + * Get the corresponding parent chain address for the provided child chain token + * Validates the returned address against the child chain router to ensure it is correctly mapped to the provided erc20ChildChainAddress + * @param erc20ChildChainAddress + * @param childProvider * @returns */ - public async getL1ERC20Address( - erc20L2Address: string, - l2Provider: Provider + public async getParentERC20Address( + erc20ChildChainAddress: string, + childProvider: Provider ): Promise { - await this.checkL2Network(l2Provider) + await this.checkChildChain(childProvider) - // L2 WETH contract doesn't have the l1Address method on it + // child chain WETH contract doesn't have the l1Address method on it if ( - erc20L2Address.toLowerCase() === - this.l2Network.tokenBridge.l2Weth.toLowerCase() + erc20ChildChainAddress.toLowerCase() === + this.childChain.tokenBridge.l2Weth.toLowerCase() ) { - return this.l2Network.tokenBridge.l1Weth + return this.childChain.tokenBridge.l1Weth } - const arbERC20 = L2GatewayToken__factory.connect(erc20L2Address, l2Provider) + const arbERC20 = L2GatewayToken__factory.connect( + erc20ChildChainAddress, + childProvider + ) const l1Address = await arbERC20.functions.l1Address().then(([res]) => res) - // check that this l1 address is indeed registered to this l2 token + // check that this l1 address is indeed registered to this child token const l2GatewayRouter = L2GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l2GatewayRouter, - l2Provider + this.childChain.tokenBridge.l2GatewayRouter, + childProvider ) const l2Address = await l2GatewayRouter.calculateL2TokenAddress(l1Address) - if (l2Address.toLowerCase() !== erc20L2Address.toLowerCase()) { + if (l2Address.toLowerCase() !== erc20ChildChainAddress.toLowerCase()) { throw new ArbSdkError( - `Unexpected l1 address. L1 address from token is not registered to the provided l2 address. ${l1Address} ${l2Address} ${erc20L2Address}` + `Unexpected l1 address. L1 address from token is not registered to the provided l2 address. ${l1Address} ${l2Address} ${erc20ChildChainAddress}` ) } @@ -518,23 +531,23 @@ export class Erc20Bridger extends AssetBridger< /** * Whether the token has been disabled on the router - * @param l1TokenAddress - * @param l1Provider + * @param parentTokenAddress + * @param parentProvider * @returns */ - public async l1TokenIsDisabled( - l1TokenAddress: string, - l1Provider: Provider + public async parentTokenIsDisabled( + parentTokenAddress: string, + parentProvider: Provider ): Promise { - await this.checkL1Network(l1Provider) + await this.checkParentChain(parentProvider) const l1GatewayRouter = L1GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l1GatewayRouter, - l1Provider + this.childChain.tokenBridge.l1GatewayRouter, + parentProvider ) return ( - (await l1GatewayRouter.l1TokenToGateway(l1TokenAddress)) === + (await l1GatewayRouter.l1TokenToGateway(parentTokenAddress)) === DISABLED_GATEWAY ) } @@ -556,7 +569,7 @@ export class Erc20Bridger extends AssetBridger< * @returns */ private getDepositRequestCallValue( - depositParams: OmitTyped + depositParams: OmitTyped ) { // the call value should be zero when paying with a custom gas token, // as the fee amount is packed inside the last parameter (`data`) of the call to `outboundTransfer`, see `getDepositRequestOutboundTransferInnerData` @@ -579,7 +592,7 @@ export class Erc20Bridger extends AssetBridger< * @returns */ private getDepositRequestOutboundTransferInnerData( - depositParams: OmitTyped + depositParams: OmitTyped ) { if (!this.nativeTokenIsEth) { return defaultAbiCoder.encode( @@ -615,27 +628,26 @@ export class Erc20Bridger extends AssetBridger< */ public async getDepositRequest( params: DepositRequest - ): Promise { - await this.checkL1Network(params.l1Provider) - await this.checkL2Network(params.l2Provider) + ): Promise { + await this.checkParentChain(params.parentProvider) + await this.checkChildChain(params.childProvider) const defaultedParams = this.applyDefaults(params) const { amount, destinationAddress, - erc20L1Address, - l1Provider, - l2Provider, + erc20ParentAddress, + parentProvider, + childProvider, retryableGasOverrides, } = defaultedParams const l1GatewayAddress = await this.getL1GatewayAddress( - erc20L1Address, - l1Provider + erc20ParentAddress, + parentProvider ) let tokenGasOverrides: GasOverrides | undefined = retryableGasOverrides - // we also add a hardcoded minimum gas limit for custom gateway deposits - if (l1GatewayAddress === this.l2Network.tokenBridge.l1CustomGateway) { + if (l1GatewayAddress === this.childChain.tokenBridge.l1CustomGateway) { if (!tokenGasOverrides) tokenGasOverrides = {} if (!tokenGasOverrides.gasLimit) tokenGasOverrides.gasLimit = {} if (!tokenGasOverrides.gasLimit.min) { @@ -645,7 +657,7 @@ export class Erc20Bridger extends AssetBridger< } const depositFunc = ( - depositParams: OmitTyped + depositParams: OmitTyped ) => { depositParams.maxSubmissionCost = params.maxSubmissionCost || depositParams.maxSubmissionCost @@ -657,7 +669,7 @@ export class Erc20Bridger extends AssetBridger< const functionData = defaultedParams.excessFeeRefundAddress !== defaultedParams.from ? iGatewayRouter.encodeFunctionData('outboundTransferCustomRefund', [ - erc20L1Address, + erc20ParentAddress, defaultedParams.excessFeeRefundAddress, destinationAddress, amount, @@ -666,7 +678,7 @@ export class Erc20Bridger extends AssetBridger< innerData, ]) : iGatewayRouter.encodeFunctionData('outboundTransfer', [ - erc20L1Address, + erc20ParentAddress, destinationAddress, amount, depositParams.gasLimit, @@ -676,22 +688,22 @@ export class Erc20Bridger extends AssetBridger< return { data: functionData, - to: this.l2Network.tokenBridge.l1GatewayRouter, + to: this.childChain.tokenBridge.l1GatewayRouter, from: defaultedParams.from, value: this.getDepositRequestCallValue(depositParams), } } - const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider) + const gasEstimator = new ParentToChildMessageGasEstimator(childProvider) const estimates = await gasEstimator.populateFunctionParams( depositFunc, - l1Provider, + parentProvider, tokenGasOverrides ) return { txRequest: { - to: this.l2Network.tokenBridge.l1GatewayRouter, + to: this.childChain.tokenBridge.l1GatewayRouter, data: estimates.data, value: estimates.value, from: params.from, @@ -703,10 +715,10 @@ export class Erc20Bridger extends AssetBridger< isValid: async () => { const reEstimates = await gasEstimator.populateFunctionParams( depositFunc, - l1Provider, + parentProvider, tokenGasOverrides ) - return L1ToL2MessageGasEstimator.isValid( + return ParentToChildMessageGasEstimator.isValid( estimates.estimates, reEstimates.estimates ) @@ -715,14 +727,14 @@ export class Erc20Bridger extends AssetBridger< } /** - * Execute a token deposit from L1 to L2 + * Execute a token deposit from parent to child chain * @param params * @returns */ public async deposit( - params: Erc20DepositParams | L1ToL2TxReqAndSignerProvider - ): Promise { - await this.checkL1Network(params.l1Signer) + params: Erc20DepositParams | ParentToChildTxReqAndSignerProvider + ): Promise { + await this.checkParentChain(params.parentSigner) // Although the types prevent should alert callers that value is not // a valid override, it is possible that they pass it in anyway as it's a common override @@ -733,21 +745,24 @@ export class Erc20Bridger extends AssetBridger< ) } - const l1Provider = SignerProviderUtils.getProviderOrThrow(params.l1Signer) - const tokenDeposit = isL1ToL2TransactionRequest(params) + const parentProvider = SignerProviderUtils.getProviderOrThrow( + params.parentSigner + ) + + const tokenDeposit = isParentToChildTransactionRequest(params) ? params : await this.getDepositRequest({ ...params, - l1Provider, - from: await params.l1Signer.getAddress(), + parentProvider, + from: await params.parentSigner.getAddress(), }) - const tx = await params.l1Signer.sendTransaction({ + const tx = await params.parentSigner.sendTransaction({ ...tokenDeposit.txRequest, ...params.overrides, }) - return L1TransactionReceipt.monkeyPatchContractCallWait(tx) + return ParentTransactionReceipt.monkeyPatchContractCallWait(tx) } /** @@ -757,7 +772,7 @@ export class Erc20Bridger extends AssetBridger< */ public async getWithdrawalRequest( params: Erc20WithdrawParams - ): Promise { + ): Promise { const to = params.destinationAddress const routerInterface = L2GatewayRouter__factory.createInterface() @@ -772,7 +787,7 @@ export class Erc20Bridger extends AssetBridger< ): string } ).encodeFunctionData('outboundTransfer(address,address,uint256,bytes)', [ - params.erc20l1Address, + params.erc20ParentAddress, to, params.amount, '0x', @@ -781,13 +796,13 @@ export class Erc20Bridger extends AssetBridger< return { txRequest: { data: functionData, - to: this.l2Network.tokenBridge.l2GatewayRouter, + to: this.childChain.tokenBridge.l2GatewayRouter, value: BigNumber.from(0), from: params.from, }, // todo: do proper estimation - estimateL1GasLimit: async (l1Provider: Provider) => { - if (await isArbitrumChain(l1Provider)) { + estimateParentGasLimit: async (parentProvider: Provider) => { + if (await isArbitrumChain(parentProvider)) { // values for L3 are dependent on the L1 base fee, so hardcoding can never be accurate // however, this is only an estimate used for display, so should be good enough // @@ -796,13 +811,16 @@ export class Erc20Bridger extends AssetBridger< } const l1GatewayAddress = await this.getL1GatewayAddress( - params.erc20l1Address, - l1Provider + params.erc20ParentAddress, + parentProvider ) // The WETH gateway is the only deposit that requires callvalue in the L2 user-tx (i.e., the recently un-wrapped ETH) // Here we check if this is a WETH deposit, and include the callvalue for the gas estimate query if so - const isWeth = await this.isWethGateway(l1GatewayAddress, l1Provider) + const isWeth = await this.isWethGateway( + l1GatewayAddress, + parentProvider + ) // measured 157421 - add some padding return isWeth ? BigNumber.from(190000) : BigNumber.from(160000) @@ -811,34 +829,34 @@ export class Erc20Bridger extends AssetBridger< } /** - * Withdraw tokens from L2 to L1 + * Withdraw tokens from child to parent chain * @param params * @returns */ public async withdraw( params: - | (OmitTyped & { l2Signer: Signer }) - | L2ToL1TxReqAndSigner - ): Promise { - if (!SignerProviderUtils.signerHasProvider(params.l2Signer)) { - throw new MissingProviderArbSdkError('l2Signer') + | (OmitTyped & { childSigner: Signer }) + | ChildToParentTxReqAndSigner + ): Promise { + if (!SignerProviderUtils.signerHasProvider(params.childSigner)) { + throw new MissingProviderArbSdkError('childSigner') } - await this.checkL2Network(params.l2Signer) + await this.checkChildChain(params.childSigner) - const withdrawalRequest = isL2ToL1TransactionRequest< - OmitTyped & { l2Signer: Signer } + const withdrawalRequest = isChildToParentTransactionRequest< + OmitTyped & { childSigner: Signer } >(params) ? params : await this.getWithdrawalRequest({ ...params, - from: await params.l2Signer.getAddress(), + from: await params.childSigner.getAddress(), }) - const tx = await params.l2Signer.sendTransaction({ + const tx = await params.childSigner.sendTransaction({ ...withdrawalRequest.txRequest, ...params.overrides, }) - return L2TransactionReceipt.monkeyPatchWait(tx) + return ChildTransactionReceipt.monkeyPatchWait(tx) } } @@ -857,37 +875,43 @@ export class AdminErc20Bridger extends Erc20Bridger { /** * Register a custom token on the Arbitrum bridge * See https://developer.offchainlabs.com/docs/bridging_assets#the-arbitrum-generic-custom-gateway for more details - * @param l1TokenAddress Address of the already deployed l1 token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/ethereum/icustomtoken. - * @param l2TokenAddress Address of the already deployed l2 token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/arbitrum/iarbtoken. - * @param l1Signer The signer with the rights to call registerTokenOnL2 on the l1 token - * @param l2Provider Arbitrum rpc provider + * @param parentTokenAddress Address of the already deployed parent token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/ethereum/icustomtoken. + * @param childTokenAddress Address of the already deployed child token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/arbitrum/iarbtoken. + * @param parentSigner The signer with the rights to call registerTokenOnL2 on the parent token + * @param childProvider Arbitrum rpc provider * @returns */ public async registerCustomToken( - l1TokenAddress: string, - l2TokenAddress: string, - l1Signer: Signer, - l2Provider: Provider - ): Promise { - if (!SignerProviderUtils.signerHasProvider(l1Signer)) { - throw new MissingProviderArbSdkError('l1Signer') + parentTokenAddress: string, + childTokenAddress: string, + parentSigner: Signer, + childProvider: Provider + ): Promise { + if (!SignerProviderUtils.signerHasProvider(parentSigner)) { + throw new MissingProviderArbSdkError('parentSigner') } - await this.checkL1Network(l1Signer) - await this.checkL2Network(l2Provider) + await this.checkParentChain(parentSigner) + await this.checkChildChain(childProvider) - const l1SenderAddress = await l1Signer.getAddress() + const parentSenderAddress = await parentSigner.getAddress() - const l1Token = ICustomToken__factory.connect(l1TokenAddress, l1Signer) - const l2Token = IArbToken__factory.connect(l2TokenAddress, l2Provider) + const parentToken = ICustomToken__factory.connect( + parentTokenAddress, + parentSigner + ) + const childToken = IArbToken__factory.connect( + childTokenAddress, + childProvider + ) // sanity checks - await l1Token.deployed() - await l2Token.deployed() + await parentToken.deployed() + await childToken.deployed() - const l1AddressFromL2 = await l2Token.l1Address() - if (l1AddressFromL2 !== l1TokenAddress) { + const l1AddressFromChildChain = await childToken.l1Address() + if (l1AddressFromChildChain !== parentTokenAddress) { throw new ArbSdkError( - `L2 token does not have l1 address set. Set address: ${l1AddressFromL2}, expected address: ${l1TokenAddress}.` + `L2 token does not have l1 address set. Set address: ${l1AddressFromChildChain}, expected address: ${parentTokenAddress}.` ) } @@ -895,7 +919,7 @@ export class AdminErc20Bridger extends Erc20Bridger { maxSubmissionCost: BigNumber gasLimit: BigNumber } - const from = await l1Signer.getAddress() + const from = await parentSigner.getAddress() const encodeFuncData = ( setTokenGas: GasParams, setGatewayGas: GasParams, @@ -916,30 +940,33 @@ export class AdminErc20Bridger extends Erc20Bridger { .mul(doubleFeePerGas) .add(setGatewayGas.maxSubmissionCost) - const data = l1Token.interface.encodeFunctionData('registerTokenOnL2', [ - l2TokenAddress, - setTokenGas.maxSubmissionCost, - setGatewayGas.maxSubmissionCost, - setTokenGas.gasLimit, - setGatewayGas.gasLimit, - doubleFeePerGas, - setTokenDeposit, - setGatewayDeposit, - l1SenderAddress, - ]) + const data = parentToken.interface.encodeFunctionData( + 'registerTokenOnL2', + [ + childTokenAddress, + setTokenGas.maxSubmissionCost, + setGatewayGas.maxSubmissionCost, + setTokenGas.gasLimit, + setGatewayGas.gasLimit, + doubleFeePerGas, + setTokenDeposit, + setGatewayDeposit, + parentSenderAddress, + ] + ) return { data, value: setTokenDeposit.add(setGatewayDeposit), - to: l1Token.address, + to: parentToken.address, from, } } - const l1Provider = l1Signer.provider! - const gEstimator = new L1ToL2MessageGasEstimator(l2Provider) + const parentProvider = parentSigner.provider! + const gEstimator = new ParentToChildMessageGasEstimator(childProvider) const setTokenEstimates2 = await gEstimator.populateFunctionParams( - (params: OmitTyped) => + (params: OmitTyped) => encodeFuncData( { gasLimit: params.gasLimit, @@ -951,11 +978,11 @@ export class AdminErc20Bridger extends Erc20Bridger { }, params.maxFeePerGas ), - l1Provider + parentProvider ) const setGatewayEstimates2 = await gEstimator.populateFunctionParams( - (params: OmitTyped) => + (params: OmitTyped) => encodeFuncData( { gasLimit: setTokenEstimates2.estimates.gasLimit, @@ -967,32 +994,32 @@ export class AdminErc20Bridger extends Erc20Bridger { }, params.maxFeePerGas ), - l1Provider + parentProvider ) - const registerTx = await l1Signer.sendTransaction({ - to: l1Token.address, + const registerTx = await parentSigner.sendTransaction({ + to: parentToken.address, data: setGatewayEstimates2.data, value: setGatewayEstimates2.value, }) - return L1TransactionReceipt.monkeyPatchWait(registerTx) + return ParentTransactionReceipt.monkeyPatchWait(registerTx) } /** * Get all the gateway set events on the L1 gateway router - * @param l1Provider + * @param parentProvider * @param customNetworkL1GatewayRouter * @returns */ public async getL1GatewaySetEvents( - l1Provider: Provider, + parentProvider: Provider, filter: { fromBlock: BlockTag; toBlock: BlockTag } ): Promise[]> { - await this.checkL1Network(l1Provider) + await this.checkParentChain(parentProvider) - const l1GatewayRouterAddress = this.l2Network.tokenBridge.l1GatewayRouter - const eventFetcher = new EventFetcher(l1Provider) + const l1GatewayRouterAddress = this.childChain.tokenBridge.l1GatewayRouter + const eventFetcher = new EventFetcher(parentProvider) return ( await eventFetcher.getEvents( L1GatewayRouter__factory, @@ -1004,26 +1031,27 @@ export class AdminErc20Bridger extends Erc20Bridger { /** * Get all the gateway set events on the L2 gateway router - * @param l1Provider + * @param parentProvider * @param customNetworkL1GatewayRouter * @returns */ public async getL2GatewaySetEvents( - l2Provider: Provider, + childProvider: Provider, filter: { fromBlock: BlockTag; toBlock: BlockTag }, customNetworkL2GatewayRouter?: string ): Promise[]> { - if (this.l2Network.isCustom && !customNetworkL2GatewayRouter) { + if (this.childChain.isCustom && !customNetworkL2GatewayRouter) { throw new ArbSdkError( 'Must supply customNetworkL2GatewayRouter for custom network ' ) } - await this.checkL2Network(l2Provider) + await this.checkChildChain(childProvider) const l2GatewayRouterAddress = - customNetworkL2GatewayRouter || this.l2Network.tokenBridge.l2GatewayRouter + customNetworkL2GatewayRouter || + this.childChain.tokenBridge.l2GatewayRouter - const eventFetcher = new EventFetcher(l2Provider) + const eventFetcher = new EventFetcher(childProvider) return ( await eventFetcher.getEvents( L1GatewayRouter__factory, @@ -1035,32 +1063,32 @@ export class AdminErc20Bridger extends Erc20Bridger { /** * Register the provided token addresses against the provided gateways - * @param l1Signer - * @param l2Provider + * @param parentSigner + * @param childProvider * @param tokenGateways * @returns */ public async setGateways( - l1Signer: Signer, - l2Provider: Provider, + parentSigner: Signer, + childProvider: Provider, tokenGateways: TokenAndGateway[], options?: GasOverrides - ): Promise { - if (!SignerProviderUtils.signerHasProvider(l1Signer)) { - throw new MissingProviderArbSdkError('l1Signer') + ): Promise { + if (!SignerProviderUtils.signerHasProvider(parentSigner)) { + throw new MissingProviderArbSdkError('parentSigner') } - await this.checkL1Network(l1Signer) - await this.checkL2Network(l2Provider) + await this.checkParentChain(parentSigner) + await this.checkChildChain(childProvider) - const from = await l1Signer.getAddress() + const from = await parentSigner.getAddress() const l1GatewayRouter = L1GatewayRouter__factory.connect( - this.l2Network.tokenBridge.l1GatewayRouter, - l1Signer + this.childChain.tokenBridge.l1GatewayRouter, + parentSigner ) const setGatewaysFunc = ( - params: OmitTyped + params: OmitTyped ) => { return { data: l1GatewayRouter.interface.encodeFunctionData('setGateways', [ @@ -1077,19 +1105,19 @@ export class AdminErc20Bridger extends Erc20Bridger { to: l1GatewayRouter.address, } } - const gEstimator = new L1ToL2MessageGasEstimator(l2Provider) + const gEstimator = new ParentToChildMessageGasEstimator(childProvider) const estimates = await gEstimator.populateFunctionParams( setGatewaysFunc, - l1Signer.provider, + parentSigner.provider, options ) - const res = await l1Signer.sendTransaction({ + const res = await parentSigner.sendTransaction({ to: estimates.to, data: estimates.data, value: estimates.estimates.deposit, }) - return L1TransactionReceipt.monkeyPatchContractCallWait(res) + return ParentTransactionReceipt.monkeyPatchContractCallWait(res) } } diff --git a/src/lib/assetBridger/ethBridger.ts b/src/lib/assetBridger/ethBridger.ts index 66943079c4..78b63ff2e1 100644 --- a/src/lib/assetBridger/ethBridger.ts +++ b/src/lib/assetBridger/ethBridger.ts @@ -27,26 +27,26 @@ import { ArbSys__factory } from '../abi/factories/ArbSys__factory' import { ARB_SYS_ADDRESS } from '../dataEntities/constants' import { AssetBridger } from './assetBridger' import { - L1EthDepositTransaction, - L1ContractCallTransaction, - L1TransactionReceipt, -} from '../message/L1Transaction' + ParentEthDepositTransaction, + ParentContractCallTransaction, + ParentTransactionReceipt, +} from '../message/ParentTransaction' import { - L2ContractTransaction, - L2TransactionReceipt, -} from '../message/L2Transaction' -import { L1ToL2MessageCreator } from '../message/L1ToL2MessageCreator' -import { GasOverrides } from '../message/L1ToL2MessageGasEstimator' + ChildContractTransaction, + ChildTransactionReceipt, +} from '../message/ChildTransaction' +import { ParentToChildMessageCreator } from '../message/ParentToChildMessageCreator' +import { GasOverrides } from '../message/ParentToChildMessageGasEstimator' import { - isL1ToL2TransactionRequest, - isL2ToL1TransactionRequest, - L1ToL2TransactionRequest, - L2ToL1TransactionRequest, + isParentToChildTransactionRequest, + isChildToParentTransactionRequest, + ParentToChildTransactionRequest, + ChildToParentTransactionRequest, } from '../dataEntities/transactionRequest' import { OmitTyped } from '../utils/types' import { SignerProviderUtils } from '../dataEntities/signerOrProvider' import { MissingProviderArbSdkError } from '../dataEntities/errors' -import { getL2Network } from '../dataEntities/networks' +import { getArbitrumNetwork } from '../dataEntities/networks' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { isArbitrumChain } from '../utils/lib' @@ -76,8 +76,8 @@ export type ApproveGasTokenParamsOrTxRequest = | ApproveGasTokenParams | ApproveGasTokenTxRequest -type WithL1Signer = T & { - l1Signer: Signer +type WithParentSigner = T & { + parentSigner: Signer } export interface EthWithdrawParams { @@ -86,7 +86,7 @@ export interface EthWithdrawParams { */ amount: BigNumber /** - * The L1 address to receive the value. + * The parent chain address to receive the value. */ destinationAddress: string /** @@ -101,9 +101,9 @@ export interface EthWithdrawParams { export type EthDepositParams = { /** - * The L1 provider or signer + * Parent chain provider or signer */ - l1Signer: Signer + parentSigner: Signer /** * The amount of ETH or tokens to be deposited */ @@ -116,11 +116,11 @@ export type EthDepositParams = { export type EthDepositToParams = EthDepositParams & { /** - * An L2 provider + * Child chain provider */ - l2Provider: Provider + childProvider: Provider /** - * L2 address of the entity receiving the funds + * Child chain address of the entity receiving the funds */ destinationAddress: string /** @@ -129,29 +129,29 @@ export type EthDepositToParams = EthDepositParams & { retryableGasOverrides?: GasOverrides } -export type L1ToL2TxReqAndSigner = L1ToL2TransactionRequest & { - l1Signer: Signer +export type ParentToChildTxReqAndSigner = ParentToChildTransactionRequest & { + parentSigner: Signer overrides?: Overrides } -export type L2ToL1TxReqAndSigner = L2ToL1TransactionRequest & { - l2Signer: Signer +export type ChildToParentTxReqAndSigner = ChildToParentTransactionRequest & { + childSigner: Signer overrides?: Overrides } type EthDepositRequestParams = OmitTyped< EthDepositParams, - 'overrides' | 'l1Signer' + 'overrides' | 'parentSigner' > & { from: string } type EthDepositToRequestParams = OmitTyped< EthDepositToParams, - 'overrides' | 'l1Signer' + 'overrides' | 'parentSigner' > & { /** - * The L1 provider + * Parent chain provider */ - l1Provider: Provider + parentProvider: Provider /** * Address that is depositing the ETH */ @@ -159,19 +159,19 @@ type EthDepositToRequestParams = OmitTyped< } /** - * Bridger for moving ETH back and forth between L1 to L2 + * Bridger for moving ETH back and forth between parent and child chain */ export class EthBridger extends AssetBridger< - EthDepositParams | EthDepositToParams | L1ToL2TxReqAndSigner, - EthWithdrawParams | L2ToL1TxReqAndSigner + EthDepositParams | EthDepositToParams | ParentToChildTxReqAndSigner, + EthWithdrawParams | ChildToParentTxReqAndSigner > { /** - * Instantiates a new EthBridger from an L2 Provider - * @param l2Provider + * Instantiates a new EthBridger from a child chain Provider + * @param childProvider * @returns */ - public static async fromProvider(l2Provider: Provider) { - return new EthBridger(await getL2Network(l2Provider)) + public static async fromProvider(childProvider: Provider) { + return new EthBridger(await getArbitrumNetwork(childProvider)) } /** @@ -180,7 +180,7 @@ export class EthBridger extends AssetBridger< */ private isApproveGasTokenParams( params: ApproveGasTokenParamsOrTxRequest - ): params is WithL1Signer { + ): params is WithParentSigner { return typeof (params as ApproveGasTokenTxRequest).txRequest === 'undefined' } @@ -199,7 +199,7 @@ export class EthBridger extends AssetBridger< 'approve', [ // spender - this.l2Network.ethBridge.inbox, + this.childChain.ethBridge.inbox, // value params?.amount ?? constants.MaxUint256, ] @@ -217,7 +217,7 @@ export class EthBridger extends AssetBridger< * @param params */ public async approveGasToken( - params: WithL1Signer + params: WithParentSigner ) { if (this.nativeTokenIsEth) { throw new Error('chain uses ETH as its native/gas token') @@ -227,7 +227,7 @@ export class EthBridger extends AssetBridger< ? this.getApproveGasTokenRequest(params) : params.txRequest - return params.l1Signer.sendTransaction({ + return params.parentSigner.sendTransaction({ ...approveGasTokenRequest, ...params.overrides, }) @@ -267,10 +267,10 @@ export class EthBridger extends AssetBridger< */ public async getDepositRequest( params: EthDepositRequestParams - ): Promise> { + ): Promise> { return { txRequest: { - to: this.l2Network.ethBridge.inbox, + to: this.childChain.ethBridge.inbox, value: this.nativeTokenIsEth ? params.amount : 0, data: this.getDepositRequestData(params), from: params.from, @@ -280,38 +280,38 @@ export class EthBridger extends AssetBridger< } /** - * Deposit ETH from L1 onto L2 + * Deposit ETH from Parent onto Child chain * @param params * @returns */ public async deposit( - params: EthDepositParams | L1ToL2TxReqAndSigner - ): Promise { - await this.checkL1Network(params.l1Signer) + params: EthDepositParams | ParentToChildTxReqAndSigner + ): Promise { + await this.checkParentChain(params.parentSigner) - const ethDeposit = isL1ToL2TransactionRequest(params) + const ethDeposit = isParentToChildTransactionRequest(params) ? params : await this.getDepositRequest({ ...params, - from: await params.l1Signer.getAddress(), + from: await params.parentSigner.getAddress(), }) - const tx = await params.l1Signer.sendTransaction({ + const tx = await params.parentSigner.sendTransaction({ ...ethDeposit.txRequest, ...params.overrides, }) - return L1TransactionReceipt.monkeyPatchEthDepositWait(tx) + return ParentTransactionReceipt.monkeyPatchEthDepositWait(tx) } /** - * Get a transaction request for an ETH deposit to a different L2 address using Retryables + * Get a transaction request for an ETH deposit to a different child chain address using Retryables * @param params * @returns */ public async getDepositToRequest( params: EthDepositToRequestParams - ): Promise { + ): Promise { const requestParams = { ...params, to: params.destinationAddress, @@ -323,41 +323,41 @@ export class EthBridger extends AssetBridger< // Gas overrides can be passed in the parameters const gasOverrides = params.retryableGasOverrides || undefined - return L1ToL2MessageCreator.getTicketCreationRequest( + return ParentToChildMessageCreator.getTicketCreationRequest( requestParams, - params.l1Provider, - params.l2Provider, + params.parentProvider, + params.childProvider, gasOverrides ) } /** - * Deposit ETH from L1 onto a different L2 address + * Deposit ETH from parent chain onto a different child chain address * @param params * @returns */ public async depositTo( params: | EthDepositToParams - | (L1ToL2TxReqAndSigner & { l2Provider: Provider }) - ): Promise { - await this.checkL1Network(params.l1Signer) - await this.checkL2Network(params.l2Provider) + | (ParentToChildTxReqAndSigner & { childProvider: Provider }) + ): Promise { + await this.checkParentChain(params.parentSigner) + await this.checkChildChain(params.childProvider) - const retryableTicketRequest = isL1ToL2TransactionRequest(params) + const retryableTicketRequest = isParentToChildTransactionRequest(params) ? params : await this.getDepositToRequest({ ...params, - from: await params.l1Signer.getAddress(), - l1Provider: params.l1Signer.provider!, + from: await params.parentSigner.getAddress(), + parentProvider: params.parentSigner.provider!, }) - const tx = await params.l1Signer.sendTransaction({ + const tx = await params.parentSigner.sendTransaction({ ...retryableTicketRequest.txRequest, ...params.overrides, }) - return L1TransactionReceipt.monkeyPatchContractCallWait(tx) + return ParentTransactionReceipt.monkeyPatchContractCallWait(tx) } /** @@ -367,7 +367,7 @@ export class EthBridger extends AssetBridger< */ public async getWithdrawalRequest( params: EthWithdrawParams - ): Promise { + ): Promise { const iArbSys = ArbSys__factory.createInterface() const functionData = iArbSys.encodeFunctionData('withdrawEth', [ params.destinationAddress, @@ -381,8 +381,8 @@ export class EthBridger extends AssetBridger< from: params.from, }, // todo: do proper estimation - estimateL1GasLimit: async (l1Provider: Provider) => { - if (await isArbitrumChain(l1Provider)) { + estimateParentGasLimit: async (parentProvider: Provider) => { + if (await isArbitrumChain(parentProvider)) { // values for L3 are dependent on the L1 base fee, so hardcoding can never be accurate // however, this is only an estimate used for display, so should be good enough // @@ -397,28 +397,30 @@ export class EthBridger extends AssetBridger< } /** - * Withdraw ETH from L2 onto L1 + * Withdraw ETH from child chain onto parent chain * @param params * @returns */ public async withdraw( - params: (EthWithdrawParams & { l2Signer: Signer }) | L2ToL1TxReqAndSigner - ): Promise { - if (!SignerProviderUtils.signerHasProvider(params.l2Signer)) { - throw new MissingProviderArbSdkError('l2Signer') + params: + | (EthWithdrawParams & { childSigner: Signer }) + | ChildToParentTxReqAndSigner + ): Promise { + if (!SignerProviderUtils.signerHasProvider(params.childSigner)) { + throw new MissingProviderArbSdkError('childSigner') } - await this.checkL2Network(params.l2Signer) + await this.checkChildChain(params.childSigner) - const request = isL2ToL1TransactionRequest< - EthWithdrawParams & { l2Signer: Signer } + const request = isChildToParentTransactionRequest< + EthWithdrawParams & { childSigner: Signer } >(params) ? params : await this.getWithdrawalRequest(params) - const tx = await params.l2Signer.sendTransaction({ + const tx = await params.childSigner.sendTransaction({ ...request.txRequest, ...params.overrides, }) - return L2TransactionReceipt.monkeyPatchWait(tx) + return ChildTransactionReceipt.monkeyPatchWait(tx) } } diff --git a/src/lib/dataEntities/constants.ts b/src/lib/dataEntities/constants.ts index 4fea86d645..ce055b9fc2 100644 --- a/src/lib/dataEntities/constants.ts +++ b/src/lib/dataEntities/constants.ts @@ -52,3 +52,27 @@ export const DISABLED_GATEWAY = '0x0000000000000000000000000000000000000001' export const CUSTOM_TOKEN_IS_ENABLED = 42161 export const SEVEN_DAYS_IN_SECONDS = 7 * 24 * 60 * 60 + +/** + * How long to wait (in milliseconds) for a deposit to arrive before timing out a request. + * + * Finalisation on mainnet can be up to 2 epochs = 64 blocks. + * We add 10 minutes for the system to create and redeem the ticket, plus some extra buffer of time. + * + * Total timeout: 30 minutes. + */ +export const DEFAULT_DEPOSIT_TIMEOUT = 30 * 60 * 1000 + +/** + * The L1 block at which Nitro was activated for Arbitrum One. + * + * @see https://etherscan.io/block/15447158 + */ +export const ARB1_NITRO_GENESIS_L1_BLOCK = 15447158 + +/** + * The L2 block at which Nitro was activated for Arbitrum One. + * + * @see https://arbiscan.io/block/22207817 + */ +export const ARB1_NITRO_GENESIS_L2_BLOCK = 22207817 diff --git a/src/lib/dataEntities/message.ts b/src/lib/dataEntities/message.ts index 46633d4573..ca274d9d7a 100644 --- a/src/lib/dataEntities/message.ts +++ b/src/lib/dataEntities/message.ts @@ -53,7 +53,7 @@ export enum InboxMessageKind { L2MessageType_signedTx = 4, } -export enum L2ToL1MessageStatus { +export enum ChildToParentMessageStatus { /** * ArbSys.sendTxToL1 called, but assertion not yet confirmed */ diff --git a/src/lib/dataEntities/networks.ts b/src/lib/dataEntities/networks.ts index 49d7085786..019015ba7b 100644 --- a/src/lib/dataEntities/networks.ts +++ b/src/lib/dataEntities/networks.ts @@ -14,66 +14,74 @@ * limitations under the License. */ /* eslint-env node */ -'use strict' +;('use strict') + +import { Provider } from '@ethersproject/abstract-provider' import { SignerOrProvider, SignerProviderUtils } from './signerOrProvider' import { ArbSdkError } from '../dataEntities/errors' -import { - SEVEN_DAYS_IN_SECONDS, - ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, -} from './constants' +import { ARB1_NITRO_GENESIS_L2_BLOCK } from './constants' import { RollupAdminLogic__factory } from '../abi/factories/RollupAdminLogic__factory' +import { Prettify } from '../utils/types' -export interface Network { - chainID: number +/** + * Represents an Arbitrum chain, e.g. Arbitrum One, Arbitrum Sepolia, or an L3 chain. + */ +export interface ArbitrumNetwork { + /** + * Name of the chain. + */ name: string - explorerUrl: string - gif?: string - isCustom: boolean /** - * Minimum possible block time for the chain (in seconds). + * Id of the chain. */ - blockTime: number + chainId: number /** - * Chain ids of children chains, i.e. chains that settle to this chain. + * Chain id of the parent chain, i.e. the chain on which this chain settles to. + */ + parentChainId: number + /** + * The core contracts */ - partnerChainIDs: number[] -} - -/** - * Represents an L1 chain, e.g. Ethereum Mainnet or Sepolia. - */ -export interface L1Network extends Network { - isArbitrum: false -} - -/** - * Represents an Arbitrum chain, e.g. Arbitrum One, Arbitrum Sepolia, or an L3 chain. - */ -export interface L2Network extends Network { - tokenBridge: TokenBridge ethBridge: EthBridge /** - * Chain id of the parent chain, i.e. the chain on which this chain settles to. + * The token bridge contracts. + */ + tokenBridge: TokenBridge + /** + * The time allowed for validators to dispute or challenge state assertions. Measured in L1 blocks. */ - partnerChainID: number - isArbitrum: true confirmPeriodBlocks: number - retryableLifetimeSeconds: number - nitroGenesisBlock: number - nitroGenesisL1Block: number /** - * How long to wait (ms) for a deposit to arrive on l2 before timing out a request + * Represents how long a retryable ticket lasts for before it expires (in seconds). Defaults to 7 days. */ - depositTimeout: number + retryableLifetimeSeconds?: number /** * In case of a chain that uses ETH as its native/gas token, this is either `undefined` or the zero address * * In case of a chain that uses an ERC-20 token from the parent chain as its native/gas token, this is the address of said token on the parent chain */ nativeToken?: string + /** + * Whether or not the chain was registered by the user. + */ + isCustom: boolean } +/** + * This type is only here for when you want to achieve backwards compatibility between SDK v3 and v4. + * + * Please see {@link ArbitrumNetwork} for the latest type. + * + * @deprecated since v4 + */ +export type L2Network = Prettify< + Omit & { + chainID: number + partnerChainID: number + } +> + export interface TokenBridge { l1GatewayRouter: string l2GatewayRouter: string @@ -102,16 +110,8 @@ export interface EthBridge { } } -export interface L1Networks { - [id: string]: L1Network -} - -export interface L2Networks { - [id: string]: L2Network -} - export interface Networks { - [id: string]: L1Network | L2Network + [id: string]: ArbitrumNetwork } const mainnetTokenBridge: TokenBridge = { @@ -147,66 +147,17 @@ const mainnetETHBridge: EthBridge = { * Storage for all networks, either L1, L2 or L3. */ export const networks: Networks = { - 1: { - chainID: 1, - name: 'Mainnet', - explorerUrl: 'https://etherscan.io', - partnerChainIDs: [42161, 42170], - blockTime: 14, - isCustom: false, - isArbitrum: false, - }, - 1338: { - chainID: 1338, - name: 'Hardhat_Mainnet_Fork', - explorerUrl: 'https://etherscan.io', - partnerChainIDs: [42161], - blockTime: 1, - isCustom: false, - isArbitrum: false, - }, - 11155111: { - chainID: 11155111, - name: 'Sepolia', - explorerUrl: 'https://sepolia.etherscan.io', - partnerChainIDs: [421614], - blockTime: 12, - isCustom: false, - isArbitrum: false, - }, - 17000: { - chainID: 17000, - name: 'Holesky', - explorerUrl: 'https://holesky.etherscan.io', - partnerChainIDs: [], - blockTime: 12, - isCustom: false, - isArbitrum: false, - }, 42161: { - chainID: 42161, + chainId: 42161, name: 'Arbitrum One', - explorerUrl: 'https://arbiscan.io', - partnerChainID: 1, - partnerChainIDs: [], - isArbitrum: true, + parentChainId: 1, tokenBridge: mainnetTokenBridge, ethBridge: mainnetETHBridge, confirmPeriodBlocks: 45818, isCustom: false, - retryableLifetimeSeconds: SEVEN_DAYS_IN_SECONDS, - nitroGenesisBlock: 22207817, - nitroGenesisL1Block: 15447158, - /** - * Finalisation on mainnet can be up to 2 epochs = 64 blocks on mainnet - * We add 10 minutes for the system to create and redeem the ticket, plus some extra buffer of time - * (Total timeout: 30 minutes) - */ - depositTimeout: 1800000, - blockTime: ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, }, 42170: { - chainID: 42170, + chainId: 42170, confirmPeriodBlocks: 45818, ethBridge: { bridge: '0xC1Ebd02f738644983b6C4B2d440b8e77DdE276Bd', @@ -215,13 +166,9 @@ export const networks: Networks = { rollup: '0xFb209827c58283535b744575e11953DCC4bEAD88', sequencerInbox: '0x211E1c4c7f1bF5351Ac850Ed10FD68CFfCF6c21b', }, - explorerUrl: 'https://nova.arbiscan.io', - isArbitrum: true, isCustom: false, name: 'Arbitrum Nova', - partnerChainID: 1, - partnerChainIDs: [], - retryableLifetimeSeconds: SEVEN_DAYS_IN_SECONDS, + parentChainId: 1, tokenBridge: { l1CustomGateway: '0x23122da8C581AA7E0d07A36Ff1f16F799650232f', l1ERC20Gateway: '0xB2535b988dcE19f9D71dfB22dB6da744aCac21bf', @@ -238,18 +185,9 @@ export const networks: Networks = { l2Weth: '0x722E8BdD2ce80A4422E880164f2079488e115365', l2WethGateway: '0x7626841cB6113412F9c88D3ADC720C9FAC88D9eD', }, - nitroGenesisBlock: 0, - nitroGenesisL1Block: 0, - /** - * Finalisation on mainnet can be up to 2 epochs = 64 blocks on mainnet - * We add 10 minutes for the system to create and redeem the ticket, plus some extra buffer of time - * (Total timeout: 30 minutes) - */ - depositTimeout: 1800000, - blockTime: ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, }, 421614: { - chainID: 421614, + chainId: 421614, confirmPeriodBlocks: 20, ethBridge: { bridge: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9', @@ -258,13 +196,9 @@ export const networks: Networks = { rollup: '0xd80810638dbDF9081b72C1B33c65375e807281C8', sequencerInbox: '0x6c97864CE4bEf387dE0b3310A44230f7E3F1be0D', }, - explorerUrl: 'https://sepolia-explorer.arbitrum.io', - isArbitrum: true, isCustom: false, name: 'Arbitrum Rollup Sepolia Testnet', - partnerChainID: 11155111, - partnerChainIDs: [23011913], - retryableLifetimeSeconds: SEVEN_DAYS_IN_SECONDS, + parentChainId: 11155111, tokenBridge: { l1CustomGateway: '0xba2F7B6eAe1F9d174199C5E4867b563E0eaC40F3', l1ERC20Gateway: '0x902b3E5f8F19571859F4AB1003B960a5dF693aFF', @@ -281,13 +215,9 @@ export const networks: Networks = { l2Weth: '0x980B62Da83eFf3D4576C647993b0c1D7faf17c73', l2WethGateway: '0xCFB1f08A4852699a979909e22c30263ca249556D', }, - nitroGenesisBlock: 0, - nitroGenesisL1Block: 0, - depositTimeout: 1800000, - blockTime: ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, }, 23011913: { - chainID: 23011913, + chainId: 23011913, confirmPeriodBlocks: 20, ethBridge: { bridge: '0x35aa95ac4747D928E2Cd42FE4461F6D9d1826346', @@ -296,13 +226,9 @@ export const networks: Networks = { rollup: '0x94db9E36d9336cD6F9FfcAd399dDa6Cc05299898', sequencerInbox: '0x00A0F15b79d1D3e5991929FaAbCF2AA65623530c', }, - explorerUrl: 'https://stylus-testnet-explorer.arbitrum.io', - isArbitrum: true, isCustom: false, name: 'Stylus Testnet', - partnerChainID: 421614, - partnerChainIDs: [], - retryableLifetimeSeconds: SEVEN_DAYS_IN_SECONDS, + parentChainId: 421614, tokenBridge: { l1CustomGateway: '0xd624D491A5Bc32de52a2e1481846752213bF7415', l1ERC20Gateway: '0x7348Fdf6F3e090C635b23D970945093455214F3B', @@ -319,158 +245,77 @@ export const networks: Networks = { l2Weth: '0x61Dc4b961D2165623A25EB775260785fE78BD37C', l2WethGateway: '0x7021B4Edd9f047772242fc948441d6e0b9121175', }, - nitroGenesisBlock: 0, - nitroGenesisL1Block: 0, - depositTimeout: 900000, - blockTime: ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, }, } /** * Determines if a chain is a parent of *any* other chain. Could be an L1 or an L2 chain. */ -const isParentChain = (chain: L1Network | L2Network): boolean => { - return chain.partnerChainIDs.length > 0 -} - -/** - * Determines if a chain is an Arbitrum chain. Could be an L2 or an L3 chain. - */ -const isArbitrumNetwork = ( - chain: L1Network | L2Network -): chain is L2Network => { - return chain.isArbitrum -} - -/** - * Determines if a chain is specifically an L1 chain (not L2 or L3). - */ -export const isL1Network = ( - chain: L1Network | L2Network -): chain is L1Network => { - return !chain.isArbitrum -} - -/** - * Builds an object that is a list of chains filtered by the provided predicate function indexed by their chain id - * @param filterFn - A predicate function to determine if a chain should be included. - * @return An object with only the filtered chains. - */ -const getChainsByType = ( - filterFn: (chain: L1Network | L2Network) => boolean -): T => { - return Object.entries(networks).reduce( - (accumulator, [chainId, chainData]) => { - if (filterFn(chainData)) { - accumulator[chainId] = chainData - } - return accumulator - }, - {} - ) as T +export const isParentNetwork = ( + parentChainOrChainId: ArbitrumNetwork | number +): boolean => { + const parentChainId = + typeof parentChainOrChainId === 'number' + ? parentChainOrChainId + : parentChainOrChainId.chainId + + // Check if there are any chains that have this chain as its parent chain + return [...Object.values(l2Networks)].some( + c => c.parentChainId === parentChainId + ) } -const getL1Chains = () => getChainsByType(isL1Network) -const getArbitrumChains = () => getChainsByType(isArbitrumNetwork) +const getArbitrumChains = () => networks /** - * Returns the parent chain for the given chain. + * Returns a list of children chains for the given chain or chain id. */ -export const getParentForNetwork = (chain: L1Network | L2Network) => { - if (!isArbitrumNetwork(chain)) { - throw new ArbSdkError(`Chain ${chain.chainID} is not an Arbitrum chain.`) - } - - const parentChain: L1Network | L2Network | undefined = - networks[chain.partnerChainID] - - if (!parentChain || !isParentChain(parentChain)) { - throw new ArbSdkError( - `Parent chain ${chain.partnerChainID} not recognized for chain ${chain.chainID}.` - ) - } - - return parentChain -} - -/** - * Returns a list of children chains for the given chain. - */ -const getChildrenForNetwork = (chain: L1Network | L2Network): L2Network[] => { - const arbitrumChains = getArbitrumChains() - - return Object.values(arbitrumChains).filter( - arbitrumChain => arbitrumChain.partnerChainID === chain.chainID +export const getChildrenForNetwork = ( + parentChainOrChainId: ArbitrumNetwork | number +): ArbitrumNetwork[] => { + const parentChainId = + typeof parentChainOrChainId === 'number' + ? parentChainOrChainId + : parentChainOrChainId.chainId + + return Object.values(getArbitrumChains()).filter( + arbitrumChain => arbitrumChain.parentChainId === parentChainId ) } -/** - * Index of *only* L1 chains that have been added. - */ -export let l1Networks: L1Networks = getL1Chains() - /** * Index of all Arbitrum chains that have been added. */ -export let l2Networks: L2Networks = getArbitrumChains() +export let l2Networks = getArbitrumChains() /** - * Returns the network associated with the given Signer, Provider or chain id. - * @note Throws if the chain is not recognized. + * Returns the Arbitrum chain associated with the given signer, provider or chain id. + * + * @note Throws if the chain is not an Arbitrum chain. */ -export const getNetwork = async ( - signerOrProviderOrChainID: SignerOrProvider | number, - layer: 1 | 2 -) => { - const chainID = await (async () => { - if (typeof signerOrProviderOrChainID === 'number') { - return signerOrProviderOrChainID +export const getArbitrumNetwork = async ( + signerOrProviderOrChainId: SignerOrProvider | number +): Promise => { + const chainId = await (async () => { + if (typeof signerOrProviderOrChainId === 'number') { + return signerOrProviderOrChainId } const provider = SignerProviderUtils.getProviderOrThrow( - signerOrProviderOrChainID + signerOrProviderOrChainId ) - const { chainId } = await provider.getNetwork() - return chainId + return (await provider.getNetwork()).chainId })() - let network: L1Network | L2Network | undefined = undefined - - if (layer === 1) { - network = getL1Chains()[chainID] - } else { - network = getArbitrumChains()[chainID] - } + const network: ArbitrumNetwork | undefined = getArbitrumChains()[chainId] if (!network) { - throw new ArbSdkError(`Unrecognized network ${chainID}.`) + throw new ArbSdkError(`Unrecognized network ${chainId}.`) } return network } -/** - * Returns the L1 chain associated with the given signer, provider or chain id. - * - * @note Throws if the chain is not an L1 chain. - */ -export const getL1Network = ( - signerOrProviderOrChainID: SignerOrProvider | number -): Promise => { - return getNetwork(signerOrProviderOrChainID, 1) as Promise -} - -/** - * Returns the Arbitrum chain associated with the given signer, provider or chain id. - * - * @note Throws if the chain is not an Arbitrum chain. - */ -export const getL2Network = ( - signerOrProviderOrChainID: SignerOrProvider | number -): Promise => { - return getNetwork(signerOrProviderOrChainID, 2) as Promise -} - /** * Returns the addresses of all contracts that make up the ETH bridge * @param rollupContractAddress Address of the Rollup contract @@ -503,82 +348,18 @@ export const getEthBridgeInformation = async ( } /** - * Adds any chain to the global index of networks and updates the parent/child relationships. - */ -const addNetwork = (network: L1Network | L2Network) => { - // store the network with the rest of the networks - networks[network.chainID] = network - - // if it's a parent chain (L1 or L2), assign it as parent to all the children - if (isParentChain(network)) { - const children = getChildrenForNetwork(network) - - children.forEach(child => { - child.partnerChainID = network.chainID - }) - } - - // if it's an arbitrum chain, add it to the parent's list of children - if (isArbitrumNetwork(network)) { - const parent: L1Network | L2Network | undefined = - networks[network.partnerChainID] - - if (!parent) { - throw new ArbSdkError( - `Network ${network.chainID}'s parent network ${network.partnerChainID} is not recognized` - ) - } - - parent.partnerChainIDs = [...parent.partnerChainIDs, network.chainID] - } - - l1Networks = getL1Chains() - l2Networks = getArbitrumChains() -} - -/** - * Registers a pair of custom L1 and L2 chains, or a single custom Arbitrum chain (L2 or L3). + * Registers a custom Arbitrum chain (L2 or L3). * - * @param customL1Network the custom L1 chain (optional) - * @param customL2Network the custom L2 or L3 chain + * @param network */ -export const addCustomNetwork = ({ - customL1Network, - customL2Network, -}: { - customL1Network?: L1Network - customL2Network: L2Network -}): void => { - if (customL1Network) { - if (customL1Network.chainID !== customL2Network.partnerChainID) { - throw new ArbSdkError( - `Partner chain id for L2 network ${customL2Network.chainID} doesn't match the provided L1 network. Expected ${customL1Network.chainID} but got ${customL2Network.partnerChainID}.` - ) - } - - // check the if the parent chain is in any of the lists - if (l1Networks[customL1Network.chainID]) { - throw new ArbSdkError( - `Network ${customL1Network.chainID} already included` - ) - } else if (!customL1Network.isCustom) { - throw new ArbSdkError( - `Custom network ${customL1Network.chainID} must have isCustom flag set to true` - ) - } - - addNetwork(customL1Network) +export const addCustomArbitrumNetwork = (network: ArbitrumNetwork): void => { + if (typeof networks[network.chainId] !== 'undefined') { + throw new Error(`Network ${network.chainId} already included`) } - if (l2Networks[customL2Network.chainID]) { - throw new ArbSdkError(`Network ${customL2Network.chainID} already included`) - } else if (!customL2Network.isCustom) { - throw new ArbSdkError( - `Custom network ${customL2Network.chainID} must have isCustom flag set to true` - ) - } - - addNetwork(customL2Network) + // store the network with the rest of the networks + networks[network.chainId] = network + l2Networks = getArbitrumChains() } /** @@ -586,22 +367,9 @@ export const addCustomNetwork = ({ * * @see {@link https://github.com/OffchainLabs/nitro} */ -export const addDefaultLocalNetwork = (): { - l1Network: L1Network - l2Network: L2Network -} => { - const defaultLocalL1Network: L1Network = { - blockTime: 10, - chainID: 1337, - explorerUrl: '', - isCustom: true, - name: 'EthLocal', - partnerChainIDs: [412346], - isArbitrum: false, - } - - const defaultLocalL2Network: L2Network = { - chainID: 412346, +export const addDefaultLocalNetwork = (): ArbitrumNetwork => { + const defaultLocalL2Network: ArbitrumNetwork = { + chainId: 412346, confirmPeriodBlocks: 20, ethBridge: { bridge: '0x2b360A9881F21c3d7aa0Ea6cA0De2a3341d4eF3C', @@ -610,16 +378,9 @@ export const addDefaultLocalNetwork = (): { rollup: '0x65a59D67Da8e710Ef9A01eCa37f83f84AEdeC416', sequencerInbox: '0xE7362D0787b51d8C72D504803E5B1d6DcdA89540', }, - explorerUrl: '', - isArbitrum: true, isCustom: true, name: 'ArbLocal', - partnerChainID: 1337, - partnerChainIDs: [], - retryableLifetimeSeconds: 604800, - nitroGenesisBlock: 0, - nitroGenesisL1Block: 0, - depositTimeout: 900000, + parentChainId: 1337, tokenBridge: { l1CustomGateway: '0x3DF948c956e14175f43670407d5796b95Bb219D8', l1ERC20Gateway: '0x4A2bA922052bA54e29c5417bC979Daaf7D5Fe4f4', @@ -636,18 +397,11 @@ export const addDefaultLocalNetwork = (): { l2Weth: '0x408Da76E87511429485C32E4Ad647DD14823Fdc4', l2WethGateway: '0x4A2bA922052bA54e29c5417bC979Daaf7D5Fe4f4', }, - blockTime: ARB_MINIMUM_BLOCK_TIME_IN_SECONDS, } - addCustomNetwork({ - customL1Network: defaultLocalL1Network, - customL2Network: defaultLocalL2Network, - }) + addCustomArbitrumNetwork(defaultLocalL2Network) - return { - l1Network: defaultLocalL1Network, - l2Network: defaultLocalL2Network, - } + return defaultLocalL2Network } /** @@ -660,12 +414,74 @@ const createNetworkStateHandler = () => { resetNetworksToDefault: () => { Object.keys(networks).forEach(key => delete networks[key]) Object.assign(networks, JSON.parse(JSON.stringify(initialState))) - l1Networks = getL1Chains() l2Networks = getArbitrumChains() }, } } +export function getNitroGenesisBlock( + arbitrumChainOrChainId: ArbitrumNetwork | number +) { + const arbitrumChainId = + typeof arbitrumChainOrChainId === 'number' + ? arbitrumChainOrChainId + : arbitrumChainOrChainId.chainId + + // all networks except Arbitrum One started off with Nitro + if (arbitrumChainId === 42161) { + return ARB1_NITRO_GENESIS_L2_BLOCK + } + + return 0 +} + +export async function getMulticallAddress( + providerOrChainId: Provider | number +): Promise { + const chains = [...Object.values(l2Networks)] + + const chainId = + typeof providerOrChainId === 'number' + ? providerOrChainId + : (await providerOrChainId.getNetwork()).chainId + const chain = chains.find(c => c.chainId === chainId) + + // The provided chain is found in the list + if (typeof chain !== 'undefined') { + // Return the address of Multicall on the chain + return chain.tokenBridge.l2Multicall + } + + // The provided chain is not found in the list + // Try to find a chain that references this chain as its parent + const child = chains.find(c => c.parentChainId === chainId) + + // No chains reference this chain as its parent + if (typeof child === 'undefined') { + throw new Error( + `Failed to retrieve Multicall address for chain: ${chainId}` + ) + } + + // Return the address of Multicall on the parent chain + return child.tokenBridge.l1MultiCall +} + +/** + * Maps the old {@link L2Network} (from SDK v3) to {@link ArbitrumNetwork} (from SDK v4). + */ +export function mapL2NetworkToArbitrumNetwork( + l2Network: L2Network +): ArbitrumNetwork { + return { + // Spread properties + ...l2Network, + // Map properties that were changed + chainId: l2Network.chainID, + parentChainId: l2Network.partnerChainID, + } +} + const { resetNetworksToDefault } = createNetworkStateHandler() export { resetNetworksToDefault } diff --git a/src/lib/dataEntities/transactionRequest.ts b/src/lib/dataEntities/transactionRequest.ts index 67803ddf67..e0ddc5c424 100644 --- a/src/lib/dataEntities/transactionRequest.ts +++ b/src/lib/dataEntities/transactionRequest.ts @@ -1,27 +1,27 @@ import { TransactionRequest, Provider } from '@ethersproject/providers' import { BigNumber } from 'ethers' import { - L1ToL2MessageGasParams, - L1ToL2MessageParams, -} from '../message/L1ToL2MessageCreator' + ParentToChildMessageGasParams, + ParentToChildMessageParams, +} from '../message/ParentToChildMessageCreator' import { isDefined } from '../utils/lib' /** * A transaction request for a transaction that will trigger some sort of - * execution on the L2 + * execution on the child chain */ -export interface L1ToL2TransactionRequest { +export interface ParentToChildTransactionRequest { /** - * Core fields needed to form the L1 component of the transaction request + * Core fields needed to form the parent component of the transaction request */ txRequest: Required< Pick > /** * Information about the retryable ticket, and it's subsequent execution, that - * will occur on L2 + * will occur on the child chain */ - retryableData: L1ToL2MessageParams & L1ToL2MessageGasParams + retryableData: ParentToChildMessageParams & ParentToChildMessageGasParams /** * If this request were sent now, would it have enough margin to reliably succeed */ @@ -29,44 +29,48 @@ export interface L1ToL2TransactionRequest { } /** - * A transaction request for a transaction that will trigger an L2 to L1 message + * A transaction request for a transaction that will trigger a child to parent message */ -export interface L2ToL1TransactionRequest { +export interface ChildToParentTransactionRequest { txRequest: Required< Pick > /** - * Estimate the gas limit required to execute the withdrawal on L1. + * Estimate the gas limit required to execute the withdrawal on the parent chain. * Note that this is only a rough estimate as it may not be possible to know * the exact size of the proof straight away, however the real value should be * within a few thousand gas of this estimate. */ - estimateL1GasLimit: (l1Provider: Provider) => Promise + estimateParentGasLimit: (l1Provider: Provider) => Promise } /** - * Ensure the T is not of TransactionRequest type by ensure it doesnt have a specific TransactionRequest property + * Ensure the T is not of TransactionRequest type by ensure it doesn't have a specific TransactionRequest property */ type IsNotTransactionRequest = T extends { txRequest: any } ? never : T /** - * Check if an object is of L1ToL2TransactionRequest type + * Check if an object is of ParentToChildTransactionRequest type * @param possibleRequest * @returns */ -export const isL1ToL2TransactionRequest = ( - possibleRequest: IsNotTransactionRequest | L1ToL2TransactionRequest -): possibleRequest is L1ToL2TransactionRequest => { - return isDefined((possibleRequest as L1ToL2TransactionRequest).txRequest) +export const isParentToChildTransactionRequest = ( + possibleRequest: IsNotTransactionRequest | ParentToChildTransactionRequest +): possibleRequest is ParentToChildTransactionRequest => { + return isDefined( + (possibleRequest as ParentToChildTransactionRequest).txRequest + ) } /** - * Check if an object is of L2ToL1TransactionRequest type + * Check if an object is of ChildToParentTransactionRequest type * @param possibleRequest * @returns */ -export const isL2ToL1TransactionRequest = ( - possibleRequest: IsNotTransactionRequest | L2ToL1TransactionRequest -): possibleRequest is L2ToL1TransactionRequest => { - return (possibleRequest as L2ToL1TransactionRequest).txRequest != undefined +export const isChildToParentTransactionRequest = ( + possibleRequest: IsNotTransactionRequest | ChildToParentTransactionRequest +): possibleRequest is ChildToParentTransactionRequest => { + return ( + (possibleRequest as ChildToParentTransactionRequest).txRequest != undefined + ) } diff --git a/src/lib/inbox/inbox.ts b/src/lib/inbox/inbox.ts index ceb98b0be4..ca2967f59b 100644 --- a/src/lib/inbox/inbox.ts +++ b/src/lib/inbox/inbox.ts @@ -28,11 +28,7 @@ import { SequencerInbox__factory } from '../abi/factories/SequencerInbox__factor import { IInbox__factory } from '../abi/factories/IInbox__factory' import { RequiredPick } from '../utils/types' import { MessageDeliveredEvent } from '../abi/Bridge' -import { - L1Network, - L2Network, - getParentForNetwork, -} from '../dataEntities/networks' +import { ArbitrumNetwork } from '../dataEntities/networks' import { SignerProviderUtils } from '../dataEntities/signerOrProvider' import { FetchedEvent, EventFetcher } from '../utils/eventFetcher' import { MultiCaller, CallInput } from '../utils/multicall' @@ -46,12 +42,12 @@ type ForceInclusionParams = FetchedEvent & { delayedAcc: string } -type GasComponentsWithL2Part = { +type GasComponentsWithChildChainPart = { gasEstimate: BigNumber gasEstimateForL1: BigNumber baseFee: BigNumber l1BaseFeeEstimate: BigNumber - gasEstimateForL2: BigNumber + gasEstimateForChildChain: BigNumber } type RequiredTransactionRequestType = RequiredPick< TransactionRequest, @@ -64,18 +60,15 @@ export class InboxTools { /** * Parent chain provider */ - private readonly l1Provider: Provider - /** - * Parent chain for the given Arbitrum chain, can be an L1 or an L2 - */ - private readonly l1Network: L1Network | L2Network + private readonly parentChainProvider: Provider constructor( - private readonly l1Signer: Signer, - private readonly l2Network: L2Network + private readonly parentChainSigner: Signer, + private readonly childChain: ArbitrumNetwork ) { - this.l1Provider = SignerProviderUtils.getProviderOrThrow(this.l1Signer) - this.l1Network = getParentForNetwork(l2Network) + this.parentChainProvider = SignerProviderUtils.getProviderOrThrow( + this.parentChainSigner + ) } /** @@ -90,13 +83,16 @@ export class InboxTools { blockNumber: number, blockTimestamp: number ): Promise { - const block = await this.l1Provider.getBlock(blockNumber) + const block = await this.parentChainProvider.getBlock(blockNumber) const diff = block.timestamp - blockTimestamp if (diff < 0) return block // we take a long average block time of 14s // and always move at least 10 blocks - const diffBlocks = Math.max(Math.ceil(diff / this.l1Network.blockTime), 10) + + // todo(spsjvc): do something about this + const blockTime = 12 + const diffBlocks = Math.max(Math.ceil(diff / blockTime), 10) return await this.findFirstBlockBelow( blockNumber - diffBlocks, @@ -104,14 +100,14 @@ export class InboxTools { ) } - //Check if this request is contract creation or not. + // Check if this request is contract creation or not. private isContractCreation( - transactionl2Request: TransactionRequest + childChainTransactionRequest: TransactionRequest ): boolean { if ( - transactionl2Request.to === '0x' || - !isDefined(transactionl2Request.to) || - transactionl2Request.to === ethers.constants.AddressZero + childChainTransactionRequest.to === '0x' || + !isDefined(childChainTransactionRequest.to) || + childChainTransactionRequest.to === ethers.constants.AddressZero ) { return true } @@ -120,32 +116,34 @@ export class InboxTools { /** * We should use nodeInterface to get the gas estimate is because we - * are making a delayed inbox message which doesn't need l1 calldata + * are making a delayed inbox message which doesn't need parentChain calldata * gas fee part. */ private async estimateArbitrumGas( - transactionl2Request: RequiredTransactionRequestType, - l2Provider: Provider - ): Promise { + childChainTransactionRequest: RequiredTransactionRequestType, + childChainProvider: Provider + ): Promise { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childChainProvider ) - const contractCreation = this.isContractCreation(transactionl2Request) + const contractCreation = this.isContractCreation( + childChainTransactionRequest + ) const gasComponents = await nodeInterface.callStatic.gasEstimateComponents( - transactionl2Request.to || ethers.constants.AddressZero, + childChainTransactionRequest.to || ethers.constants.AddressZero, contractCreation, - transactionl2Request.data, + childChainTransactionRequest.data, { - from: transactionl2Request.from, - value: transactionl2Request.value, + from: childChainTransactionRequest.from, + value: childChainTransactionRequest.value, } ) - const gasEstimateForL2: BigNumber = gasComponents.gasEstimate.sub( + const gasEstimateForChildChain: BigNumber = gasComponents.gasEstimate.sub( gasComponents.gasEstimateForL1 ) - return { ...gasComponents, gasEstimateForL2 } + return { ...gasComponents, gasEstimateForChildChain } } /** @@ -155,11 +153,11 @@ export class InboxTools { */ private async getForceIncludableBlockRange(blockNumberRangeSize: number) { const sequencerInbox = SequencerInbox__factory.connect( - this.l2Network.ethBridge.sequencerInbox, - this.l1Provider + this.childChain.ethBridge.sequencerInbox, + this.parentChainProvider ) - const multicall = await MultiCaller.fromProvider(this.l1Provider) + const multicall = await MultiCaller.fromProvider(this.parentChainProvider) const multicallInput: [ CallInput>>, ReturnType, @@ -213,7 +211,7 @@ export class InboxTools { maxSearchRangeBlocks: number, rangeMultiplier: number ): Promise[]> { - const eFetcher = new EventFetcher(this.l1Provider) + const eFetcher = new EventFetcher(this.parentChainProvider) // events don't become eligible until they pass a delay // find a block range which will emit eligible events @@ -251,7 +249,7 @@ export class InboxTools { /** * Find the event of the latest message that can be force include * @param maxSearchRangeBlocks The max range of blocks to search in. - * Defaults to 3 * 6545 ( = ~3 days) prior to the first eligble block + * Defaults to 3 * 6545 ( = ~3 days) prior to the first eligible block * @param startSearchRangeBlocks The start range of block to search in. * Moves incrementally up to the maxSearchRangeBlocks. Defaults to 100; * @param rangeMultiplier The multiplier to use when increasing the block range @@ -261,11 +259,11 @@ export class InboxTools { public async getForceIncludableEvent( maxSearchRangeBlocks: number = 3 * 6545, startSearchRangeBlocks = 100, - rangeMultipler = 2 + rangeMultiplier = 2 ): Promise { const bridge = Bridge__factory.connect( - this.l2Network.ethBridge.bridge, - this.l1Provider + this.childChain.ethBridge.bridge, + this.parentChainProvider ) // events dont become eligible until they pass a delay @@ -274,7 +272,7 @@ export class InboxTools { bridge, startSearchRangeBlocks, maxSearchRangeBlocks, - rangeMultipler + rangeMultiplier ) // no events appeared within that time period @@ -283,8 +281,8 @@ export class InboxTools { // take the last event - as including this one will include all previous events const eventInfo = events[events.length - 1] const sequencerInbox = SequencerInbox__factory.connect( - this.l2Network.ethBridge.sequencerInbox, - this.l1Provider + this.childChain.ethBridge.sequencerInbox, + this.parentChainProvider ) // has the sequencer inbox already read this latest message const totalDelayedRead = await sequencerInbox.totalDelayedMessagesRead() @@ -302,7 +300,7 @@ export class InboxTools { /** * Force includes all eligible messages in the delayed inbox. - * The inbox contract doesnt allow a message to be force-included + * The inbox contract doesn't allow a message to be force-included * until after a delay period has been completed. * @param messageDeliveredEvent Provide this to include all messages up to this one. Responsibility is on the caller to check the eligibility of this event. * @returns The force include transaction, or null if no eligible message were found for inclusion @@ -323,14 +321,14 @@ export class InboxTools { overrides?: Overrides ): Promise { const sequencerInbox = SequencerInbox__factory.connect( - this.l2Network.ethBridge.sequencerInbox, - this.l1Signer + this.childChain.ethBridge.sequencerInbox, + this.parentChainSigner ) const eventInfo = messageDeliveredEvent || (await this.getForceIncludableEvent()) if (!eventInfo) return null - const block = await this.l1Provider.getBlock(eventInfo.blockHash) + const block = await this.parentChainProvider.getBlock(eventInfo.blockHash) return await sequencerInbox.functions.forceInclusion( eventInfo.event.messageIndex.add(1), @@ -345,19 +343,19 @@ export class InboxTools { } /** - * Send l2 signed tx using delayed inox, which won't alias the sender's adddress - * It will be automatically included by the sequencer on l2, if it isn't included + * Send Child Chain signed tx using delayed inbox, which won't alias the sender's address + * It will be automatically included by the sequencer on Chain, if it isn't included * within 24 hours, you can force include it - * @param signedTx A signed transaction which can be sent directly to network, - * you can call inboxTools.signL2Message to get. - * @returns The l1 delayed inbox's transaction itself. + * @param signedTx A signed transaction which can be sent directly to chain, + * you can call inboxTools.signChainMessage to get. + * @returns The parentChain delayed inbox's transaction itself. */ - public async sendL2SignedTx( + public async sendChildChainSignedTx( signedTx: string ): Promise { const delayedInbox = IInbox__factory.connect( - this.l2Network.ethBridge.inbox, - this.l1Signer + this.childChain.ethBridge.inbox, + this.parentChainSigner ) const sendData = ethers.utils.solidityPack( @@ -370,43 +368,43 @@ export class InboxTools { /** * Sign a transaction with msg.to, msg.value and msg.data. - * You can use this as a helper to call inboxTools.sendL2SignedMessage + * You can use this as a helper to call inboxTools.sendChainSignedMessage * above. - * @param message A signed transaction which can be sent directly to network, + * @param message A signed transaction which can be sent directly to chain, * tx.to, tx.data, tx.value must be provided when not contract creation, if * contractCreation is true, no need provide tx.to. tx.gasPrice and tx.nonce * can be overrided. (You can also send contract creation transaction by set tx.to * to zero address or null) - * @param l2Signer ethers Signer type, used to sign l2 transaction - * @returns The l1 delayed inbox's transaction signed data. + * @param childChainSigner ethers Signer type, used to sign Chain transaction + * @returns The parentChain delayed inbox's transaction signed data. */ - public async signL2Tx( + public async signChildChainTx( txRequest: RequiredTransactionRequestType, - l2Signer: Signer + childChainSigner: Signer ): Promise { const tx: RequiredTransactionRequestType = { ...txRequest } const contractCreation = this.isContractCreation(tx) if (!isDefined(tx.nonce)) { - tx.nonce = await l2Signer.getTransactionCount() + tx.nonce = await childChainSigner.getTransactionCount() } //check transaction type (if no transaction type or gasPrice provided, use eip1559 type) if (tx.type === 1 || tx.gasPrice) { if (tx.gasPrice) { - tx.gasPrice = await l2Signer.getGasPrice() + tx.gasPrice = await childChainSigner.getGasPrice() } } else { if (!isDefined(tx.maxFeePerGas)) { - const feeData = await l2Signer.getFeeData() + const feeData = await childChainSigner.getFeeData() tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas! tx.maxFeePerGas = feeData.maxFeePerGas! } tx.type = 2 } - tx.from = await l2Signer.getAddress() - tx.chainId = await l2Signer.getChainId() + tx.from = await childChainSigner.getAddress() + tx.chainId = await childChainSigner.getChainId() // if this is contract creation, user might not input the to address, // however, it is needed when we call to estimateArbitrumGas, so @@ -415,17 +413,17 @@ export class InboxTools { tx.to = ethers.constants.AddressZero } - //estimate gas on l2 + //estimate gas on child chain try { tx.gasLimit = ( - await this.estimateArbitrumGas(tx, l2Signer.provider!) - ).gasEstimateForL2 + await this.estimateArbitrumGas(tx, childChainSigner.provider!) + ).gasEstimateForChildChain } catch (error) { throw new ArbSdkError('execution failed (estimate gas failed)') } if (contractCreation) { delete tx.to } - return await l2Signer.signTransaction(tx) + return await childChainSigner.signTransaction(tx) } } diff --git a/src/lib/message/ChildToParentMessage.ts b/src/lib/message/ChildToParentMessage.ts new file mode 100644 index 0000000000..aefe70f1e4 --- /dev/null +++ b/src/lib/message/ChildToParentMessage.ts @@ -0,0 +1,337 @@ +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-env node */ +'use strict' + +import { Provider } from '@ethersproject/abstract-provider' +import { Signer } from '@ethersproject/abstract-signer' +import { BigNumber } from '@ethersproject/bignumber' +import { BlockTag } from '@ethersproject/abstract-provider' + +import { ContractTransaction, Overrides } from 'ethers' +import { + SignerProviderUtils, + SignerOrProvider, +} from '../dataEntities/signerOrProvider' +import * as classic from './ChildToParentMessageClassic' +import * as nitro from './ChildToParentMessageNitro' +import { + L2ToL1TransactionEvent as ClassicChildToParentTransactionEvent, + L2ToL1TxEvent as NitroChildToParentTransactionEvent, +} from '../abi/ArbSys' +import { isDefined } from '../utils/lib' +import { EventArgs } from '../dataEntities/event' +import { ChildToParentMessageStatus } from '../dataEntities/message' +import { + getArbitrumNetwork, + getNitroGenesisBlock, +} from '../dataEntities/networks' +import { ArbSdkError } from '../dataEntities/errors' + +export type ChildToParentTransactionEvent = + | EventArgs + | EventArgs + +/** + * Conditional type for Signer or Provider. If T is of type Provider + * then ChildToParentMessageReaderOrWriter will be of type ChildToParentMessageReader. + * If T is of type Signer then ChildToParentMessageReaderOrWriter will be of + * type ChildToParentMessageWriter. + */ +export type ChildToParentMessageReaderOrWriter = + T extends Provider ? ChildToParentMessageReader : ChildToParentMessageWriter + +/** + * Base functionality for Chain->ParentChain messages + */ +export class ChildToParentMessage { + protected isClassic( + e: ChildToParentTransactionEvent + ): e is EventArgs { + return isDefined( + (e as EventArgs).indexInBatch + ) + } + + /** + * Instantiates a new `ChildToParentMessageWriter` or `ChildToParentMessageReader` object. + * + * @param {SignerOrProvider} ParentChainSignerOrProvider Signer or provider to be used for executing or reading the Chain-to-ParentChain message. + * @param {ChildToParentTransactionEvent} event The event containing the data of the Chain-to-ParentChain message. + * @param {Provider} [ParentChainProvider] Optional. Used to override the Provider which is attached to `ParentChainSignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. + */ + public static fromEvent( + parentChainSignerOrProvider: T, + event: ChildToParentTransactionEvent, + parentChainProvider?: Provider + ): ChildToParentMessageReaderOrWriter + static fromEvent( + parentChainSignerOrProvider: T, + event: ChildToParentTransactionEvent, + parentChainProvider?: Provider + ): ChildToParentMessageReader | ChildToParentMessageWriter { + return SignerProviderUtils.isSigner(parentChainSignerOrProvider) + ? new ChildToParentMessageWriter( + parentChainSignerOrProvider, + event, + parentChainProvider + ) + : new ChildToParentMessageReader(parentChainSignerOrProvider, event) + } + + /** + * Get event logs for ChildToParent transactions. + * @param childChainProvider + * @param filter Block range filter + * @param position The batchnumber indexed field was removed in nitro and a position indexed field was added. + * For pre-nitro events the value passed in here will be used to find events with the same batchnumber. + * For post nitro events it will be used to find events with the same position. + * @param destination The ParentChain destination of the ChildToParent message + * @param hash The uniqueId indexed field was removed in nitro and a hash indexed field was added. + * For pre-nitro events the value passed in here will be used to find events with the same uniqueId. + * For post nitro events it will be used to find events with the same hash. + * @param indexInBatch The index in the batch, only valid for pre-nitro events. This parameter is ignored post-nitro + * @returns Any classic and nitro events that match the provided filters. + */ + public static async getChildToParentEvents( + childChainProvider: Provider, + filter: { fromBlock: BlockTag; toBlock: BlockTag }, + position?: BigNumber, + destination?: string, + hash?: BigNumber, + indexInBatch?: BigNumber + ): Promise<(ChildToParentTransactionEvent & { transactionHash: string })[]> { + const childChain = await getArbitrumNetwork(childChainProvider) + const childChainNitroGenesisBlock = getNitroGenesisBlock(childChain) + + const inClassicRange = (blockTag: BlockTag, nitroGenBlock: number) => { + if (typeof blockTag === 'string') { + // taking classic of "earliest", "latest", "earliest" and the nitro gen block + // yields 0, nitro gen, nitro gen since the classic range is always between 0 and nitro gen + + switch (blockTag) { + case 'earliest': + return 0 + case 'latest': + return nitroGenBlock + case 'pending': + return nitroGenBlock + default: + throw new ArbSdkError(`Unrecognised block tag. ${blockTag}`) + } + } + return Math.min(blockTag, nitroGenBlock) + } + + const inNitroRange = (blockTag: BlockTag, nitroGenBlock: number) => { + // taking nitro range of "earliest", "latest", "earliest" and the nitro gen block + // yields nitro gen, latest, pending since the nitro range is always between nitro gen and latest/pending + + if (typeof blockTag === 'string') { + switch (blockTag) { + case 'earliest': + return nitroGenBlock + case 'latest': + return 'latest' + case 'pending': + return 'pending' + default: + throw new ArbSdkError(`Unrecognised block tag. ${blockTag}`) + } + } + + return Math.max(blockTag, nitroGenBlock) + } + + // only fetch nitro events after the genesis block + const classicFilter = { + fromBlock: inClassicRange(filter.fromBlock, childChainNitroGenesisBlock), + toBlock: inClassicRange(filter.toBlock, childChainNitroGenesisBlock), + } + const logQueries = [] + if (classicFilter.fromBlock !== classicFilter.toBlock) { + logQueries.push( + classic.ChildToParentMessageClassic.getChildToParentEvents( + childChainProvider, + classicFilter, + position, + destination, + hash, + indexInBatch + ) + ) + } + + const nitroFilter = { + fromBlock: inNitroRange(filter.fromBlock, childChainNitroGenesisBlock), + toBlock: inNitroRange(filter.toBlock, childChainNitroGenesisBlock), + } + if (nitroFilter.fromBlock !== nitroFilter.toBlock) { + logQueries.push( + nitro.ChildToParentChainMessageNitro.getChildToParentChainEvents( + childChainProvider, + nitroFilter, + position, + destination, + hash + ) + ) + } + + return (await Promise.all(logQueries)).flat(1) + } +} + +/** + * Provides read-only access for Chain-to-ParentChain-messages + */ +export class ChildToParentMessageReader extends ChildToParentMessage { + private readonly classicReader?: classic.ChildToParentMessageReaderClassic + private readonly nitroReader?: nitro.ChildToParentChainMessageReaderNitro + + constructor( + protected readonly parentChainProvider: Provider, + event: ChildToParentTransactionEvent + ) { + super() + if (this.isClassic(event)) { + this.classicReader = new classic.ChildToParentMessageReaderClassic( + parentChainProvider, + event.batchNumber, + event.indexInBatch + ) + } else { + this.nitroReader = new nitro.ChildToParentChainMessageReaderNitro( + parentChainProvider, + event + ) + } + } + + public async getOutboxProof( + childChainProvider: Provider + ): Promise { + if (this.nitroReader) { + return await this.nitroReader.getOutboxProof(childChainProvider) + } else return await this.classicReader!.tryGetProof(childChainProvider) + } + + /** + * Get the status of this message + * In order to check if the message has been executed proof info must be provided. + * @returns + */ + public async status( + childChainProvider: Provider + ): Promise { + // can we create a ChildToParentMessage here, we need to - the constructor is what we need + if (this.nitroReader) + return await this.nitroReader.status(childChainProvider) + else return await this.classicReader!.status(childChainProvider) + } + + /** + * Waits until the outbox entry has been created, and will not return until it has been. + * WARNING: Outbox entries are only created when the corresponding node is confirmed. Which + * can take 1 week+, so waiting here could be a very long operation. + * @param retryDelay + * @returns outbox entry status (either executed or confirmed but not pending) + */ + public async waitUntilReadyToExecute( + childChainProvider: Provider, + retryDelay = 500 + ): Promise< + ChildToParentMessageStatus.EXECUTED | ChildToParentMessageStatus.CONFIRMED + > { + if (this.nitroReader) + return this.nitroReader.waitUntilReadyToExecute( + childChainProvider, + retryDelay + ) + else + return this.classicReader!.waitUntilOutboxEntryCreated( + childChainProvider, + retryDelay + ) + } + + /** + * Estimates the ParentChain block number in which this Chain to ParentChain tx will be available for execution. + * If the message can or already has been executed, this returns null + * @param childChainProvider + * @returns expected ParentChain block number where the Chain to ParentChain message will be executable. Returns null if the message can or already has been executed + */ + public async getFirstExecutableBlock( + childChainProvider: Provider + ): Promise { + if (this.nitroReader) + return this.nitroReader.getFirstExecutableBlock(childChainProvider) + else return this.classicReader!.getFirstExecutableBlock(childChainProvider) + } +} + +/** + * Provides read and write access for Chain-to-ParentChain-messages + */ +export class ChildToParentMessageWriter extends ChildToParentMessageReader { + private readonly classicWriter?: classic.ChildToParentMessageWriterClassic + private readonly nitroWriter?: nitro.ChildToParentChainMessageWriterNitro + + /** + * Instantiates a new `ChildToParentMessageWriter` object. + * + * @param {Signer} ParentChainSigner The signer to be used for executing the Chain-to-ParentChain message. + * @param {ChildToParentTransactionEvent} event The event containing the data of the Chain-to-ParentChain message. + * @param {Provider} [ParentChainProvider] Optional. Used to override the Provider which is attached to `ParentChainSigner` in case you need more control. This will be a required parameter in a future major version update. + */ + constructor( + parentChainSigner: Signer, + event: ChildToParentTransactionEvent, + parentChainProvider?: Provider + ) { + super(parentChainProvider ?? parentChainSigner.provider!, event) + + if (this.isClassic(event)) { + this.classicWriter = new classic.ChildToParentMessageWriterClassic( + parentChainSigner, + event.batchNumber, + event.indexInBatch, + parentChainProvider + ) + } else { + this.nitroWriter = new nitro.ChildToParentChainMessageWriterNitro( + parentChainSigner, + event, + parentChainProvider + ) + } + } + + /** + * Executes the ChildToParentMessage on ParentChain. + * Will throw an error if the outbox entry has not been created, which happens when the + * corresponding assertion is confirmed. + * @returns + */ + public async execute( + childChainProvider: Provider, + overrides?: Overrides + ): Promise { + if (this.nitroWriter) + return this.nitroWriter.execute(childChainProvider, overrides) + else return await this.classicWriter!.execute(childChainProvider, overrides) + } +} diff --git a/src/lib/message/L2ToL1MessageClassic.ts b/src/lib/message/ChildToParentMessageClassic.ts similarity index 63% rename from src/lib/message/L2ToL1MessageClassic.ts rename to src/lib/message/ChildToParentMessageClassic.ts index 2392511e56..e646083a7d 100644 --- a/src/lib/message/L2ToL1MessageClassic.ts +++ b/src/lib/message/ChildToParentMessageClassic.ts @@ -29,7 +29,7 @@ import { ArbSys__factory } from '../abi/factories/ArbSys__factory' import { Outbox__factory } from '../abi/classic/factories/Outbox__factory' import { NodeInterface__factory } from '../abi/factories/NodeInterface__factory' -import { L2ToL1TransactionEvent } from '../abi/ArbSys' +import { L2ToL1TransactionEvent as ChildToParentTransactionEvent } from '../abi/ArbSys' import { ContractTransaction, Overrides } from 'ethers' import { EventFetcher } from '../utils/eventFetcher' import { @@ -39,8 +39,8 @@ import { import { isDefined, wait } from '../utils/lib' import { ArbSdkError } from '../dataEntities/errors' import { EventArgs } from '../dataEntities/event' -import { L2ToL1MessageStatus } from '../dataEntities/message' -import { getL2Network } from '../dataEntities/networks' +import { ChildToParentMessageStatus } from '../dataEntities/message' +import { getArbitrumNetwork } from '../dataEntities/networks' export interface MessageBatchProofInfo { /** @@ -91,14 +91,17 @@ export interface MessageBatchProofInfo { /** * Conditional type for Signer or Provider. If T is of type Provider - * then L2ToL1MessageReaderOrWriter will be of type L2ToL1MessageReader. - * If T is of type Signer then L2ToL1MessageReaderOrWriter will be of - * type L2ToL1MessageWriter. + * then ChildToParentMessageReaderOrWriter will be of type ChildToParentMessageReader. + * If T is of type Signer then ChildToParentMessageReaderOrWriter will be of + * type ChildToParentMessageWriter. */ -export type L2ToL1MessageReaderOrWriterClassic = - T extends Provider ? L2ToL1MessageReaderClassic : L2ToL1MessageWriterClassic +export type ChildToParentMessageReaderOrWriterClassic< + T extends SignerOrProvider +> = T extends Provider + ? ChildToParentMessageReaderClassic + : ChildToParentMessageWriterClassic -export class L2ToL1MessageClassic { +export class ChildToParentMessageClassic { /** * The number of the batch this message is part of */ @@ -115,50 +118,52 @@ export class L2ToL1MessageClassic { } /** - * Instantiates a new `L2ToL1MessageWriterClassic` or `L2ToL1MessageReaderClassic` object. + * Instantiates a new `ChildToParentMessageWriterClassic` or `ChildToParentMessageReaderClassic` object. * - * @param {SignerOrProvider} l1SignerOrProvider Signer or provider to be used for executing or reading the L2-to-L1 message. - * @param {BigNumber} batchNumber The number of the batch containing the L2-to-L1 message. - * @param {BigNumber} indexInBatch The index of the L2-to-L1 message within the batch. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1SignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. + * @param {SignerOrProvider} parentSignerOrProvider Signer or provider to be used for executing or reading the Child-to-Parent message. + * @param {BigNumber} batchNumber The number of the batch containing the Child-to-Parent message. + * @param {BigNumber} indexInBatch The index of the Child-to-Parent message within the batch. + * @param {Provider} [parentProvider] Optional. Used to override the Provider which is attached to `parentSignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. */ public static fromBatchNumber( - l1SignerOrProvider: T, + parentSignerOrProvider: T, batchNumber: BigNumber, indexInBatch: BigNumber, - l1Provider?: Provider - ): L2ToL1MessageReaderOrWriterClassic + parentProvider?: Provider + ): ChildToParentMessageReaderOrWriterClassic public static fromBatchNumber( - l1SignerOrProvider: T, + parentSignerOrProvider: T, batchNumber: BigNumber, indexInBatch: BigNumber, - l1Provider?: Provider - ): L2ToL1MessageReaderClassic | L2ToL1MessageWriterClassic { - return SignerProviderUtils.isSigner(l1SignerOrProvider) - ? new L2ToL1MessageWriterClassic( - l1SignerOrProvider, + parentProvider?: Provider + ): ChildToParentMessageReaderClassic | ChildToParentMessageWriterClassic { + return SignerProviderUtils.isSigner(parentSignerOrProvider) + ? new ChildToParentMessageWriterClassic( + parentSignerOrProvider, batchNumber, indexInBatch, - l1Provider + parentProvider ) - : new L2ToL1MessageReaderClassic( - l1SignerOrProvider, + : new ChildToParentMessageReaderClassic( + parentSignerOrProvider, batchNumber, indexInBatch ) } - public static async getL2ToL1Events( - l2Provider: Provider, + public static async getChildToParentEvents( + childProvider: Provider, filter: { fromBlock: BlockTag; toBlock: BlockTag }, batchNumber?: BigNumber, destination?: string, uniqueId?: BigNumber, indexInBatch?: BigNumber ): Promise< - (EventArgs & { transactionHash: string })[] + (EventArgs & { + transactionHash: string + })[] > { - const eventFetcher = new EventFetcher(l2Provider) + const eventFetcher = new EventFetcher(childProvider) const events = ( await eventFetcher.getEvents( ArbSys__factory, @@ -180,11 +185,11 @@ export class L2ToL1MessageClassic { } /** - * Provides read-only access for classic l2-to-l1-messages + * Provides read-only access for classic Child-to-Parent-messages */ -export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { +export class ChildToParentMessageReaderClassic extends ChildToParentMessageClassic { constructor( - protected readonly l1Provider: Provider, + protected readonly parentProvider: Provider, batchNumber: BigNumber, indexInBatch: BigNumber ) { @@ -199,18 +204,21 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { /** * Classic had 2 outboxes, we need to find the correct one for the provided batch number - * @param l2Provider + * @param childProvider * @param batchNumber * @returns */ - protected async getOutboxAddress(l2Provider: Provider, batchNumber: number) { + protected async getOutboxAddress( + childProvider: Provider, + batchNumber: number + ) { if (!isDefined(this.outboxAddress)) { - const l2Network = await getL2Network(l2Provider) + const childChain = await getArbitrumNetwork(childProvider) // find the outbox where the activation batch number of the next outbox // is greater than the supplied batch - const outboxes = isDefined(l2Network.ethBridge.classicOutboxes) - ? Object.entries(l2Network.ethBridge.classicOutboxes) + const outboxes = isDefined(childChain.ethBridge.classicOutboxes) + ? Object.entries(childChain.ethBridge.classicOutboxes) : [] const res = outboxes @@ -233,24 +241,24 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { return this.outboxAddress } - private async outboxEntryExists(l2Provider: Provider) { + private async outboxEntryExists(childProvider: Provider) { const outboxAddress = await this.getOutboxAddress( - l2Provider, + childProvider, this.batchNumber.toNumber() ) - const outbox = Outbox__factory.connect(outboxAddress, this.l1Provider) + const outbox = Outbox__factory.connect(outboxAddress, this.parentProvider) return await outbox.outboxEntryExists(this.batchNumber) } public static async tryGetProof( - l2Provider: Provider, + childProvider: Provider, batchNumber: BigNumber, indexInBatch: BigNumber ): Promise { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childProvider ) try { return await nodeInterface.legacyLookupMessageBatchProof( @@ -271,15 +279,15 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { /** * Get the execution proof for this message. Returns null if the batch does not exist yet. - * @param l2Provider + * @param childProvider * @returns */ public async tryGetProof( - l2Provider: Provider + childProvider: Provider ): Promise { if (!isDefined(this.proof)) { - this.proof = await L2ToL1MessageReaderClassic.tryGetProof( - l2Provider, + this.proof = await ChildToParentMessageReaderClassic.tryGetProof( + childProvider, this.batchNumber, this.indexInBatch ) @@ -290,16 +298,16 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { /** * Check if given outbox message has already been executed */ - public async hasExecuted(l2Provider: Provider): Promise { - const proofInfo = await this.tryGetProof(l2Provider) + public async hasExecuted(childProvider: Provider): Promise { + const proofInfo = await this.tryGetProof(childProvider) if (!isDefined(proofInfo)) return false const outboxAddress = await this.getOutboxAddress( - l2Provider, + childProvider, this.batchNumber.toNumber() ) - const outbox = Outbox__factory.connect(outboxAddress, this.l1Provider) + const outbox = Outbox__factory.connect(outboxAddress, this.parentProvider) try { await outbox.callStatic.executeTransaction( this.batchNumber, @@ -328,19 +336,21 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { * @param proofInfo * @returns */ - public async status(l2Provider: Provider): Promise { + public async status( + childProvider: Provider + ): Promise { try { - const messageExecuted = await this.hasExecuted(l2Provider) + const messageExecuted = await this.hasExecuted(childProvider) if (messageExecuted) { - return L2ToL1MessageStatus.EXECUTED + return ChildToParentMessageStatus.EXECUTED } - const outboxEntryExists = await this.outboxEntryExists(l2Provider) + const outboxEntryExists = await this.outboxEntryExists(childProvider) return outboxEntryExists - ? L2ToL1MessageStatus.CONFIRMED - : L2ToL1MessageStatus.UNCONFIRMED + ? ChildToParentMessageStatus.CONFIRMED + : ChildToParentMessageStatus.UNCONFIRMED } catch (e) { - return L2ToL1MessageStatus.UNCONFIRMED + return ChildToParentMessageStatus.UNCONFIRMED } } @@ -352,82 +362,84 @@ export class L2ToL1MessageReaderClassic extends L2ToL1MessageClassic { * @returns outbox entry status (either executed or confirmed but not pending) */ public async waitUntilOutboxEntryCreated( - l2Provider: Provider, + childProvider: Provider, retryDelay = 500 - ): Promise { - const exists = await this.outboxEntryExists(l2Provider) + ): Promise< + ChildToParentMessageStatus.EXECUTED | ChildToParentMessageStatus.CONFIRMED + > { + const exists = await this.outboxEntryExists(childProvider) if (exists) { - return (await this.hasExecuted(l2Provider)) - ? L2ToL1MessageStatus.EXECUTED - : L2ToL1MessageStatus.CONFIRMED + return (await this.hasExecuted(childProvider)) + ? ChildToParentMessageStatus.EXECUTED + : ChildToParentMessageStatus.CONFIRMED } else { await wait(retryDelay) - return await this.waitUntilOutboxEntryCreated(l2Provider, retryDelay) + return await this.waitUntilOutboxEntryCreated(childProvider, retryDelay) } } /** - * Estimates the L1 block number in which this L2 to L1 tx will be available for execution - * @param l2Provider - * @returns Always returns null for classic l2toL1 messages since they can be executed in any block now. + * Estimates the Parent Chain block number in which this Child-to-Parent tx will be available for execution + * @param childProvider + * @returns Always returns null for classic chainToParentChain messages since they can be executed in any block now. */ public async getFirstExecutableBlock( // eslint-disable-next-line @typescript-eslint/no-unused-vars - l2Provider: Provider + childProvider: Provider ): Promise { return null } } /** - * Provides read and write access for classic l2-to-l1-messages + * Provides read and write access for classic Child-to-Parent-messages */ -export class L2ToL1MessageWriterClassic extends L2ToL1MessageReaderClassic { +export class ChildToParentMessageWriterClassic extends ChildToParentMessageReaderClassic { /** - * Instantiates a new `L2ToL1MessageWriterClassic` object. + * Instantiates a new `ChildToParentMessageWriterClassic` object. * - * @param {Signer} l1Signer The signer to be used for executing the L2-to-L1 message. - * @param {BigNumber} batchNumber The number of the batch containing the L2-to-L1 message. - * @param {BigNumber} indexInBatch The index of the L2-to-L1 message within the batch. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1Signer` in case you need more control. This will be a required parameter in a future major version update. + * @param {Signer} parentSigner The signer to be used for executing the Child-to-Parent message. + * @param {BigNumber} batchNumber The number of the batch containing the Child-to-Parent message. + * @param {BigNumber} indexInBatch The index of the Child-to-Parent message within the batch. + * @param {Provider} [parentProvider] Optional. Used to override the Provider which is attached to `parentSigner` in case you need more control. This will be a required parameter in a future major version update. */ constructor( - private readonly l1Signer: Signer, + private readonly parentSigner: Signer, batchNumber: BigNumber, indexInBatch: BigNumber, - l1Provider?: Provider + parentProvider?: Provider ) { - super(l1Provider ?? l1Signer.provider!, batchNumber, indexInBatch) + super(parentProvider ?? parentSigner.provider!, batchNumber, indexInBatch) } /** - * Executes the L2ToL1Message on L1. + * Executes the ChildToParentMessage on Parent Chain. * Will throw an error if the outbox entry has not been created, which happens when the * corresponding assertion is confirmed. * @returns */ public async execute( - l2Provider: Provider, + childProvider: Provider, overrides?: Overrides ): Promise { - const status = await this.status(l2Provider) - if (status !== L2ToL1MessageStatus.CONFIRMED) { + const status = await this.status(childProvider) + if (status !== ChildToParentMessageStatus.CONFIRMED) { throw new ArbSdkError( - `Cannot execute message. Status is: ${status} but must be ${L2ToL1MessageStatus.CONFIRMED}.` + `Cannot execute message. Status is: ${status} but must be ${ChildToParentMessageStatus.CONFIRMED}.` ) } - const proofInfo = await this.tryGetProof(l2Provider) + const proofInfo = await this.tryGetProof(childProvider) if (!isDefined(proofInfo)) { throw new ArbSdkError( `Unexpected missing proof: ${this.batchNumber.toString()} ${this.indexInBatch.toString()}}` ) } const outboxAddress = await this.getOutboxAddress( - l2Provider, + childProvider, this.batchNumber.toNumber() ) - const outbox = Outbox__factory.connect(outboxAddress, this.l1Signer) + const outbox = Outbox__factory.connect(outboxAddress, this.parentSigner) // We can predict and print number of missing blocks // if not challenged return await outbox.functions.executeTransaction( diff --git a/src/lib/message/L2ToL1MessageNitro.ts b/src/lib/message/ChildToParentMessageNitro.ts similarity index 55% rename from src/lib/message/L2ToL1MessageNitro.ts rename to src/lib/message/ChildToParentMessageNitro.ts index 17329e99b1..7bdc98f2ad 100644 --- a/src/lib/message/L2ToL1MessageNitro.ts +++ b/src/lib/message/ChildToParentMessageNitro.ts @@ -30,7 +30,7 @@ import { RollupUserLogic__factory } from '../abi/factories/RollupUserLogic__fact import { Outbox__factory } from '../abi/factories/Outbox__factory' import { NodeInterface__factory } from '../abi/factories/NodeInterface__factory' -import { L2ToL1TxEvent } from '../abi/ArbSys' +import { L2ToL1TxEvent as ChildToParentChainTxEvent } from '../abi/ArbSys' import { ContractTransaction, Overrides } from 'ethers' import { Mutex } from 'async-mutex' import { EventFetcher, FetchedEvent } from '../utils/eventFetcher' @@ -40,122 +40,139 @@ import { SignerOrProvider, } from '../dataEntities/signerOrProvider' import { getBlockRangesForL1Block, isArbitrumChain, wait } from '../utils/lib' -import { getL2Network } from '../dataEntities/networks' +import { getArbitrumNetwork } from '../dataEntities/networks' import { NodeCreatedEvent, RollupUserLogic } from '../abi/RollupUserLogic' import { ArbitrumProvider } from '../utils/arbProvider' import { ArbBlock } from '../dataEntities/rpc' import { JsonRpcProvider } from '@ethersproject/providers' import { EventArgs } from '../dataEntities/event' -import { L2ToL1MessageStatus } from '../dataEntities/message' +import { ChildToParentMessageStatus } from '../dataEntities/message' /** * Conditional type for Signer or Provider. If T is of type Provider - * then L2ToL1MessageReaderOrWriter will be of type L2ToL1MessageReader. - * If T is of type Signer then L2ToL1MessageReaderOrWriter will be of - * type L2ToL1MessageWriter. + * then ChildToParentChainMessageReaderOrWriter will be of type ChildToParentChainMessageReader. + * If T is of type Signer then ChildToParentChainMessageReaderOrWriter will be of + * type ChildToParentChainMessageWriter. */ -export type L2ToL1MessageReaderOrWriterNitro = - T extends Provider ? L2ToL1MessageReaderNitro : L2ToL1MessageWriterNitro +export type ChildToParentChainMessageReaderOrWriterNitro< + T extends SignerOrProvider +> = T extends Provider + ? ChildToParentChainMessageReaderNitro + : ChildToParentChainMessageWriterNitro -// expected number of L1 blocks that it takes for an L2 tx to be included in a L1 assertion +// expected number of parent chain blocks that it takes for a Child chain tx to be included in a parent chain assertion const ASSERTION_CREATED_PADDING = 50 -// expected number of L1 blocks that it takes for a validator to confirm an L1 block after the node deadline is passed +// expected number of parent chain blocks that it takes for a validator to confirm a parent chain block after the node deadline is passed const ASSERTION_CONFIRMED_PADDING = 20 -const l2BlockRangeCache: { [key in string]: (number | undefined)[] } = {} +const childChainBlockRangeCache: { [key in string]: (number | undefined)[] } = + {} const mutex = new Mutex() -function getL2BlockRangeCacheKey({ - l2ChainId, - l1BlockNumber, +function getChildChainBlockRangeCacheKey({ + childChainId, + parentChainBlockNumber, }: { - l2ChainId: number - l1BlockNumber: number + childChainId: number + parentChainBlockNumber: number }) { - return `${l2ChainId}-${l1BlockNumber}` + return `${childChainId}-${parentChainBlockNumber}` } -function setL2BlockRangeCache(key: string, value: (number | undefined)[]) { - l2BlockRangeCache[key] = value +function setChildChainBlockRangeCache( + key: string, + value: (number | undefined)[] +) { + childChainBlockRangeCache[key] = value } async function getBlockRangesForL1BlockWithCache({ - l1Provider, - l2Provider, - forL1Block, + parentProvider, + childProvider, + forParentChainBlock, }: { - l1Provider: JsonRpcProvider - l2Provider: JsonRpcProvider - forL1Block: number + parentProvider: JsonRpcProvider + childProvider: JsonRpcProvider + forParentChainBlock: number }) { - const l2ChainId = (await l2Provider.getNetwork()).chainId - const key = getL2BlockRangeCacheKey({ - l2ChainId, - l1BlockNumber: forL1Block, + const childChainId = (await childProvider.getNetwork()).chainId + const key = getChildChainBlockRangeCacheKey({ + childChainId, + parentChainBlockNumber: forParentChainBlock, }) - if (l2BlockRangeCache[key]) { - return l2BlockRangeCache[key] + if (childChainBlockRangeCache[key]) { + return childChainBlockRangeCache[key] } // implements a lock that only fetches cache once const release = await mutex.acquire() // if cache has been acquired while awaiting the lock - if (l2BlockRangeCache[key]) { + if (childChainBlockRangeCache[key]) { release() - return l2BlockRangeCache[key] + return childChainBlockRangeCache[key] } try { - const l2BlockRange = await getBlockRangesForL1Block({ - forL1Block, - provider: l1Provider, + const childChainBlockRange = await getBlockRangesForL1Block({ + forL1Block: forParentChainBlock, + provider: parentProvider, }) - setL2BlockRangeCache(key, l2BlockRange) + setChildChainBlockRangeCache(key, childChainBlockRange) } finally { release() } - return l2BlockRangeCache[key] + return childChainBlockRangeCache[key] } /** - * Base functionality for nitro L2->L1 messages + * Base functionality for nitro Child->Parent messages */ -export class L2ToL1MessageNitro { - protected constructor(public readonly event: EventArgs) {} +export class ChildToParentChainMessageNitro { + protected constructor( + public readonly event: EventArgs + ) {} /** - * Instantiates a new `L2ToL1MessageWriterNitro` or `L2ToL1MessageReaderNitro` object. + * Instantiates a new `ChildToParentChainMessageWriterNitro` or `ChildToParentChainMessageReaderNitro` object. * - * @param {SignerOrProvider} l1SignerOrProvider Signer or provider to be used for executing or reading the L2-to-L1 message. - * @param {EventArgs} event The event containing the data of the L2-to-L1 message. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1SignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. + * @param {SignerOrProvider} parentSignerOrProvider Signer or provider to be used for executing or reading the Child-to-Parent message. + * @param {EventArgs} event The event containing the data of the Child-to-Parent message. + * @param {Provider} [parentProvider] Optional. Used to override the Provider which is attached to `parentSignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. */ public static fromEvent( - l1SignerOrProvider: T, - event: EventArgs, - l1Provider?: Provider - ): L2ToL1MessageReaderOrWriterNitro + parentSignerOrProvider: T, + event: EventArgs, + parentProvider?: Provider + ): ChildToParentChainMessageReaderOrWriterNitro public static fromEvent( - l1SignerOrProvider: T, - event: EventArgs, - l1Provider?: Provider - ): L2ToL1MessageReaderNitro | L2ToL1MessageWriterNitro { - return SignerProviderUtils.isSigner(l1SignerOrProvider) - ? new L2ToL1MessageWriterNitro(l1SignerOrProvider, event, l1Provider) - : new L2ToL1MessageReaderNitro(l1SignerOrProvider, event) + parentSignerOrProvider: T, + event: EventArgs, + parentProvider?: Provider + ): + | ChildToParentChainMessageReaderNitro + | ChildToParentChainMessageWriterNitro { + return SignerProviderUtils.isSigner(parentSignerOrProvider) + ? new ChildToParentChainMessageWriterNitro( + parentSignerOrProvider, + event, + parentProvider + ) + : new ChildToParentChainMessageReaderNitro(parentSignerOrProvider, event) } - public static async getL2ToL1Events( - l2Provider: Provider, + public static async getChildToParentChainEvents( + childProvider: Provider, filter: { fromBlock: BlockTag; toBlock: BlockTag }, position?: BigNumber, destination?: string, hash?: BigNumber - ): Promise<(EventArgs & { transactionHash: string })[]> { - const eventFetcher = new EventFetcher(l2Provider) + ): Promise< + (EventArgs & { transactionHash: string })[] + > { + const eventFetcher = new EventFetcher(childProvider) return ( await eventFetcher.getEvents( ArbSys__factory, @@ -167,9 +184,9 @@ export class L2ToL1MessageNitro { } /** - * Provides read-only access nitro for l2-to-l1-messages + * Provides read-only access nitro for child-to-parent-messages */ -export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { +export class ChildToParentChainMessageReaderNitro extends ChildToParentChainMessageNitro { protected sendRootHash?: string protected sendRootSize?: BigNumber protected sendRootConfirmed?: boolean @@ -177,19 +194,19 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { protected l1BatchNumber?: number constructor( - protected readonly l1Provider: Provider, - event: EventArgs + protected readonly parentProvider: Provider, + event: EventArgs ) { super(event) } - public async getOutboxProof(l2Provider: Provider) { - const { sendRootSize } = await this.getSendProps(l2Provider) + public async getOutboxProof(childProvider: Provider) { + const { sendRootSize } = await this.getSendProps(childProvider) if (!sendRootSize) throw new ArbSdkError('Node not yet created, cannot get proof.') const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childProvider ) const outboxProofParams = @@ -204,11 +221,11 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { /** * Check if this message has already been executed in the Outbox */ - protected async hasExecuted(l2Provider: Provider): Promise { - const l2Network = await getL2Network(l2Provider) + protected async hasExecuted(childProvider: Provider): Promise { + const childChain = await getArbitrumNetwork(childProvider) const outbox = Outbox__factory.connect( - l2Network.ethBridge.outbox, - this.l1Provider + childChain.ethBridge.outbox, + this.parentProvider ) return outbox.callStatic.isSpent(this.event.position) @@ -219,12 +236,14 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { * In order to check if the message has been executed proof info must be provided. * @returns */ - public async status(l2Provider: Provider): Promise { - const { sendRootConfirmed } = await this.getSendProps(l2Provider) - if (!sendRootConfirmed) return L2ToL1MessageStatus.UNCONFIRMED - return (await this.hasExecuted(l2Provider)) - ? L2ToL1MessageStatus.EXECUTED - : L2ToL1MessageStatus.CONFIRMED + public async status( + childProvider: Provider + ): Promise { + const { sendRootConfirmed } = await this.getSendProps(childProvider) + if (!sendRootConfirmed) return ChildToParentMessageStatus.UNCONFIRMED + return (await this.hasExecuted(childProvider)) + ? ChildToParentMessageStatus.EXECUTED + : ChildToParentMessageStatus.CONFIRMED } private parseNodeCreatedAssertion(event: FetchedEvent) { @@ -237,10 +256,10 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { } private async getBlockFromNodeLog( - l2Provider: JsonRpcProvider, + childProvider: JsonRpcProvider, log: FetchedEvent | undefined ) { - const arbitrumProvider = new ArbitrumProvider(l2Provider) + const arbitrumProvider = new ArbitrumProvider(childProvider) if (!log) { console.warn('No NodeCreated events found, defaulting to block 0') @@ -248,26 +267,26 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { } const parsedLog = this.parseNodeCreatedAssertion(log) - const l2Block = await arbitrumProvider.getBlock( + const childChainBlock = await arbitrumProvider.getBlock( parsedLog.afterState.blockHash ) - if (!l2Block) { + if (!childChainBlock) { throw new ArbSdkError( `Block not found. ${parsedLog.afterState.blockHash}` ) } - if (l2Block.sendRoot !== parsedLog.afterState.sendRoot) { + if (childChainBlock.sendRoot !== parsedLog.afterState.sendRoot) { throw new ArbSdkError( - `L2 block send root doesn't match parsed log. ${l2Block.sendRoot} ${parsedLog.afterState.sendRoot}` + `Child chain block send root doesn't match parsed log. ${childChainBlock.sendRoot} ${parsedLog.afterState.sendRoot}` ) } - return l2Block + return childChainBlock } private async getBlockFromNodeNum( rollup: RollupUserLogic, nodeNum: BigNumber, - l2Provider: Provider + childProvider: Provider ): Promise { const { createdAtBlock } = await rollup.getNode(nodeNum) @@ -275,11 +294,11 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { let createdToBlock = createdAtBlock // If L1 is Arbitrum, then L2 is an Orbit chain. - if (await isArbitrumChain(this.l1Provider)) { + if (await isArbitrumChain(this.parentProvider)) { try { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - this.l1Provider + this.parentProvider ) const l2BlockRangeFromNode = await nodeInterface.l2BlockRangeForL1( @@ -292,9 +311,9 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { // defaults to binary search try { const l2BlockRange = await getBlockRangesForL1BlockWithCache({ - l1Provider: this.l1Provider as JsonRpcProvider, - l2Provider: l2Provider as JsonRpcProvider, - forL1Block: createdAtBlock.toNumber(), + parentProvider: this.parentProvider as JsonRpcProvider, + childProvider: childProvider as JsonRpcProvider, + forParentChainBlock: createdAtBlock.toNumber(), }) const startBlock = l2BlockRange[0] const endBlock = l2BlockRange[1] @@ -329,18 +348,18 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { ) return await this.getBlockFromNodeLog( - l2Provider as JsonRpcProvider, + childProvider as JsonRpcProvider, logs[0] ) } - protected async getBatchNumber(l2Provider: Provider) { + protected async getBatchNumber(childProvider: Provider) { if (this.l1BatchNumber == undefined) { // findBatchContainingBlock errors if block number does not exist try { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childProvider ) const res = await nodeInterface.findBatchContainingBlock( this.event.arbBlockNum @@ -354,26 +373,28 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { return this.l1BatchNumber } - protected async getSendProps(l2Provider: Provider) { + protected async getSendProps(childProvider: Provider) { if (!this.sendRootConfirmed) { - const l2Network = await getL2Network(l2Provider) + const childChain = await getArbitrumNetwork(childProvider) const rollup = RollupUserLogic__factory.connect( - l2Network.ethBridge.rollup, - this.l1Provider + childChain.ethBridge.rollup, + this.parentProvider ) const latestConfirmedNodeNum = await rollup.callStatic.latestConfirmed() - const l2BlockConfirmed = await this.getBlockFromNodeNum( + const childChainBlockConfirmed = await this.getBlockFromNodeNum( rollup, latestConfirmedNodeNum, - l2Provider + childProvider ) - const sendRootSizeConfirmed = BigNumber.from(l2BlockConfirmed.sendCount) + const sendRootSizeConfirmed = BigNumber.from( + childChainBlockConfirmed.sendCount + ) if (sendRootSizeConfirmed.gt(this.event.position)) { this.sendRootSize = sendRootSizeConfirmed - this.sendRootHash = l2BlockConfirmed.sendRoot + this.sendRootHash = childChainBlockConfirmed.sendRoot this.sendRootConfirmed = true } else { // if the node has yet to be confirmed we'll still try to find proof info from unconfirmed nodes @@ -381,16 +402,16 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { if (latestNodeNum.gt(latestConfirmedNodeNum)) { // In rare case latestNodeNum can be equal to latestConfirmedNodeNum // eg immediately after an upgrade, or at genesis, or on a chain where confirmation time = 0 like AnyTrust may have - const l2Block = await this.getBlockFromNodeNum( + const childChainBlock = await this.getBlockFromNodeNum( rollup, latestNodeNum, - l2Provider + childProvider ) - const sendRootSize = BigNumber.from(l2Block.sendCount) + const sendRootSize = BigNumber.from(childChainBlock.sendCount) if (sendRootSize.gt(this.event.position)) { this.sendRootSize = sendRootSize - this.sendRootHash = l2Block.sendRoot + this.sendRootHash = childChainBlock.sendRoot } } } @@ -410,47 +431,49 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { * @returns outbox entry status (either executed or confirmed but not pending) */ public async waitUntilReadyToExecute( - l2Provider: Provider, + childProvider: Provider, retryDelay = 500 - ): Promise { - const status = await this.status(l2Provider) + ): Promise< + ChildToParentMessageStatus.EXECUTED | ChildToParentMessageStatus.CONFIRMED + > { + const status = await this.status(childProvider) if ( - status === L2ToL1MessageStatus.CONFIRMED || - status === L2ToL1MessageStatus.EXECUTED + status === ChildToParentMessageStatus.CONFIRMED || + status === ChildToParentMessageStatus.EXECUTED ) { return status } else { await wait(retryDelay) - return await this.waitUntilReadyToExecute(l2Provider, retryDelay) + return await this.waitUntilReadyToExecute(childProvider, retryDelay) } } /** - * Estimates the L1 block number in which this L2 to L1 tx will be available for execution. + * Estimates the parent chain block number in which this child chain to parent chain tx will be available for execution. * If the message can or already has been executed, this returns null - * @param l2Provider - * @returns expected L1 block number where the L2 to L1 message will be executable. Returns null if the message can be or already has been executed + * @param childProvider + * @returns expected parent chain block number where the child chain to parent chain message will be executable. Returns null if the message can be or already has been executed */ public async getFirstExecutableBlock( - l2Provider: Provider + childProvider: Provider ): Promise { - const l2Network = await getL2Network(l2Provider) + const childChain = await getArbitrumNetwork(childProvider) const rollup = RollupUserLogic__factory.connect( - l2Network.ethBridge.rollup, - this.l1Provider + childChain.ethBridge.rollup, + this.parentProvider ) - const status = await this.status(l2Provider) - if (status === L2ToL1MessageStatus.EXECUTED) return null - if (status === L2ToL1MessageStatus.CONFIRMED) return null + const status = await this.status(childProvider) + if (status === ChildToParentMessageStatus.EXECUTED) return null + if (status === ChildToParentMessageStatus.CONFIRMED) return null // consistency check in case we change the enum in the future - if (status !== L2ToL1MessageStatus.UNCONFIRMED) - throw new ArbSdkError('L2ToL1Msg expected to be unconfirmed') + if (status !== ChildToParentMessageStatus.UNCONFIRMED) + throw new ArbSdkError('ChildToParentChainMsg expected to be unconfirmed') - const latestBlock = await this.l1Provider.getBlockNumber() - const eventFetcher = new EventFetcher(this.l1Provider) + const latestBlock = await this.parentProvider.getBlockNumber() + const eventFetcher = new EventFetcher(this.parentProvider) const logs = ( await eventFetcher.getEvents( RollupUserLogic__factory, @@ -458,7 +481,7 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { { fromBlock: Math.max( latestBlock - - BigNumber.from(l2Network.confirmPeriodBlocks) + BigNumber.from(childChain.confirmPeriodBlocks) .add(ASSERTION_CONFIRMED_PADDING) .toNumber(), 0 @@ -469,21 +492,21 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { ) ).sort((a, b) => a.event.nodeNum.toNumber() - b.event.nodeNum.toNumber()) - const lastL2Block = + const lastChildChainBlock = logs.length === 0 ? undefined : await this.getBlockFromNodeLog( - l2Provider as JsonRpcProvider, + childProvider as JsonRpcProvider, logs[logs.length - 1] ) - const lastSendCount = lastL2Block - ? BigNumber.from(lastL2Block.sendCount) + const lastSendCount = lastChildChainBlock + ? BigNumber.from(lastChildChainBlock.sendCount) : BigNumber.from(0) - // here we assume the L2 to L1 tx is actually valid, so the user needs to wait the max time + // here we assume the Child to Parent tx is actually valid, so the user needs to wait the max time // since there isn't a pending node that includes this message yet if (lastSendCount.lte(this.event.position)) - return BigNumber.from(l2Network.confirmPeriodBlocks) + return BigNumber.from(childChain.confirmPeriodBlocks) .add(ASSERTION_CREATED_PADDING) .add(ASSERTION_CONFIRMED_PADDING) .add(latestBlock) @@ -496,11 +519,11 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { while (left <= right) { const mid = Math.floor((left + right) / 2) const log = logs[mid] - const l2Block = await this.getBlockFromNodeLog( - l2Provider as JsonRpcProvider, + const childChainBlock = await this.getBlockFromNodeLog( + childProvider as JsonRpcProvider, log ) - const sendCount = BigNumber.from(l2Block.sendCount) + const sendCount = BigNumber.from(childChainBlock.sendCount) if (sendCount.gt(this.event.position)) { foundLog = log right = mid - 1 @@ -516,45 +539,45 @@ export class L2ToL1MessageReaderNitro extends L2ToL1MessageNitro { } /** - * Provides read and write access for nitro l2-to-l1-messages + * Provides read and write access for nitro child-to-Parent-messages */ -export class L2ToL1MessageWriterNitro extends L2ToL1MessageReaderNitro { +export class ChildToParentChainMessageWriterNitro extends ChildToParentChainMessageReaderNitro { /** - * Instantiates a new `L2ToL1MessageWriterNitro` object. + * Instantiates a new `ChildToParentChainMessageWriterNitro` object. * - * @param {Signer} l1Signer The signer to be used for executing the L2-to-L1 message. - * @param {EventArgs} event The event containing the data of the L2-to-L1 message. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1Signer` in case you need more control. This will be a required parameter in a future major version update. + * @param {Signer} parentSigner The signer to be used for executing the Child-to-Parent message. + * @param {EventArgs} event The event containing the data of the Child-to-Parent message. + * @param {Provider} [parentProvider] Optional. Used to override the Provider which is attached to `parentSigner` in case you need more control. This will be a required parameter in a future major version update. */ constructor( - private readonly l1Signer: Signer, - event: EventArgs, - l1Provider?: Provider + private readonly parentSigner: Signer, + event: EventArgs, + parentProvider?: Provider ) { - super(l1Provider ?? l1Signer.provider!, event) + super(parentProvider ?? parentSigner.provider!, event) } /** - * Executes the L2ToL1Message on L1. + * Executes the ChildToParentChainMessage on Parent Chain. * Will throw an error if the outbox entry has not been created, which happens when the * corresponding assertion is confirmed. * @returns */ public async execute( - l2Provider: Provider, + childProvider: Provider, overrides?: Overrides ): Promise { - const status = await this.status(l2Provider) - if (status !== L2ToL1MessageStatus.CONFIRMED) { + const status = await this.status(childProvider) + if (status !== ChildToParentMessageStatus.CONFIRMED) { throw new ArbSdkError( - `Cannot execute message. Status is: ${status} but must be ${L2ToL1MessageStatus.CONFIRMED}.` + `Cannot execute message. Status is: ${status} but must be ${ChildToParentMessageStatus.CONFIRMED}.` ) } - const proof = await this.getOutboxProof(l2Provider) - const l2Network = await getL2Network(l2Provider) + const proof = await this.getOutboxProof(childProvider) + const childChain = await getArbitrumNetwork(childProvider) const outbox = Outbox__factory.connect( - l2Network.ethBridge.outbox, - this.l1Signer + childChain.ethBridge.outbox, + this.parentSigner ) return await outbox.executeTransaction( diff --git a/src/lib/message/L2Transaction.ts b/src/lib/message/ChildTransaction.ts similarity index 72% rename from src/lib/message/L2Transaction.ts rename to src/lib/message/ChildTransaction.ts index 51ac0fa7b2..1f29c13854 100644 --- a/src/lib/message/L2Transaction.ts +++ b/src/lib/message/ChildTransaction.ts @@ -25,12 +25,12 @@ import { SignerOrProvider, } from '../dataEntities/signerOrProvider' import { - L2ToL1MessageReader, - L2ToL1MessageReaderOrWriter, - L2ToL1Message, - L2ToL1MessageWriter, - L2ToL1TransactionEvent, -} from './L2ToL1Message' + ChildToParentMessageReader, + ChildToParentMessageReaderOrWriter, + ChildToParentMessage, + ChildToParentMessageWriter, + ChildToParentTransactionEvent, +} from './ChildToParentMessage' import { ArbSys__factory } from '../abi/factories/ArbSys__factory' import { ArbRetryableTx__factory } from '../abi/factories/ArbRetryableTx__factory' import { NodeInterface__factory } from '../abi/factories/NodeInterface__factory' @@ -40,18 +40,18 @@ import { NODE_INTERFACE_ADDRESS } from '../dataEntities/constants' import { EventArgs, parseTypedLogs } from '../dataEntities/event' import { ArbitrumProvider } from '../utils/arbProvider' -export interface L2ContractTransaction extends ContractTransaction { - wait(confirmations?: number): Promise +export interface ChildContractTransaction extends ContractTransaction { + wait(confirmations?: number): Promise } -export interface RedeemTransaction extends L2ContractTransaction { +export interface RedeemTransaction extends ChildContractTransaction { waitForRedeem: () => Promise } /** * Extension of ethers-js TransactionReceipt, adding Arbitrum-specific functionality */ -export class L2TransactionReceipt implements TransactionReceipt { +export class ChildTransactionReceipt implements TransactionReceipt { public readonly to: string public readonly from: string public readonly contractAddress: string @@ -91,10 +91,10 @@ export class L2TransactionReceipt implements TransactionReceipt { } /** - * Get an L2ToL1TxEvent events created by this transaction + * Get {@link ChildToParentTransactionEvent} events created by this transaction * @returns */ - public getL2ToL1Events(): L2ToL1TransactionEvent[] { + public getChildToParentEvents(): ChildToParentTransactionEvent[] { const classicLogs = parseTypedLogs( ArbSys__factory, this.logs, @@ -113,47 +113,47 @@ export class L2TransactionReceipt implements TransactionReceipt { } /** - * Get any l2-to-l1-messages created by this transaction + * Get any child-to-parent-messages created by this transaction * @param l2SignerOrProvider */ - public async getL2ToL1Messages( - l1SignerOrProvider: T - ): Promise[]> - public async getL2ToL1Messages( - l1SignerOrProvider: T - ): Promise { - const provider = SignerProviderUtils.getProvider(l1SignerOrProvider) + public async getChildToParentMessages( + parentSignerOrProvider: T + ): Promise[]> + public async getChildToParentMessages( + parentSignerOrProvider: T + ): Promise { + const provider = SignerProviderUtils.getProvider(parentSignerOrProvider) if (!provider) throw new ArbSdkError('Signer not connected to provider.') - return this.getL2ToL1Events().map(log => - L2ToL1Message.fromEvent(l1SignerOrProvider, log) + return this.getChildToParentEvents().map(log => + ChildToParentMessage.fromEvent(parentSignerOrProvider, log) ) } /** - * Get number of L1 confirmations that the batch including this tx has - * @param l2Provider + * Get number of parent chain confirmations that the batch including this tx has + * @param childProvider * @returns number of confirmations of batch including tx, or 0 if no batch included this tx */ - public getBatchConfirmations(l2Provider: providers.JsonRpcProvider) { + public getBatchConfirmations(childProvider: providers.JsonRpcProvider) { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childProvider ) return nodeInterface.getL1Confirmations(this.blockHash) } /** * Get the number of the batch that included this tx (will throw if no such batch exists) - * @param l2Provider + * @param childProvider * @returns number of batch in which tx was included, or errors if no batch includes the current tx */ - public async getBatchNumber(l2Provider: providers.JsonRpcProvider) { + public async getBatchNumber(childProvider: providers.JsonRpcProvider) { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - l2Provider + childProvider ) - const arbProvider = new ArbitrumProvider(l2Provider) + const arbProvider = new ArbitrumProvider(childProvider) const rec = await arbProvider.getTransactionReceipt(this.transactionHash) if (rec == null) throw new ArbSdkError( @@ -165,16 +165,16 @@ export class L2TransactionReceipt implements TransactionReceipt { /** * Whether the data associated with this transaction has been - * made available on L1 - * @param l2Provider + * made available on parent chain + * @param childProvider * @param confirmations The number of confirmations on the batch before data is to be considered available * @returns */ public async isDataAvailable( - l2Provider: providers.JsonRpcProvider, + childProvider: providers.JsonRpcProvider, confirmations = 10 ): Promise { - const res = await this.getBatchConfirmations(l2Provider) + const res = await this.getBatchConfirmations(childProvider) // is there a batch with enough confirmations return res.toNumber() > confirmations } @@ -186,28 +186,28 @@ export class L2TransactionReceipt implements TransactionReceipt { */ public static monkeyPatchWait = ( contractTransaction: ContractTransaction - ): L2ContractTransaction => { + ): ChildContractTransaction => { const wait = contractTransaction.wait contractTransaction.wait = async (_confirmations?: number) => { - // we ignore the confirmations for now since L2 transactions shouldn't re-org + // we ignore the confirmations for now since child chain transactions shouldn't re-org // in future we should give users a more fine grained way to check the finality of - // an l2 transaction - check if a batch is on L1, if an assertion has been made, and if + // an child chain transaction - check if a batch is on a parent chain, if an assertion has been made, and if // it has been confirmed. const result = await wait() - return new L2TransactionReceipt(result) + return new ChildTransactionReceipt(result) } - return contractTransaction as L2ContractTransaction + return contractTransaction as ChildContractTransaction } /** * Adds a waitForRedeem function to a redeem transaction * @param redeemTx - * @param l2Provider + * @param childProvider * @returns */ public static toRedeemTransaction( - redeemTx: L2ContractTransaction, - l2Provider: providers.Provider + redeemTx: ChildContractTransaction, + childProvider: providers.Provider ): RedeemTransaction { const returnRec = redeemTx as RedeemTransaction returnRec.waitForRedeem = async () => { @@ -221,7 +221,7 @@ export class L2TransactionReceipt implements TransactionReceipt { ) } - return await l2Provider.getTransactionReceipt( + return await childProvider.getTransactionReceipt( redeemScheduledEvents[0].retryTxHash ) } diff --git a/src/lib/message/L2ToL1Message.ts b/src/lib/message/L2ToL1Message.ts deleted file mode 100644 index eb93eeca9f..0000000000 --- a/src/lib/message/L2ToL1Message.ts +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-env node */ -'use strict' - -import { Provider } from '@ethersproject/abstract-provider' -import { Signer } from '@ethersproject/abstract-signer' -import { BigNumber } from '@ethersproject/bignumber' -import { BlockTag } from '@ethersproject/abstract-provider' - -import { ContractTransaction, Overrides } from 'ethers' -import { - SignerProviderUtils, - SignerOrProvider, -} from '../dataEntities/signerOrProvider' -import * as classic from './L2ToL1MessageClassic' -import * as nitro from './L2ToL1MessageNitro' -import { - L2ToL1TransactionEvent as ClassicL2ToL1TransactionEvent, - L2ToL1TxEvent as NitroL2ToL1TransactionEvent, -} from '../abi/ArbSys' -import { isDefined } from '../utils/lib' -import { EventArgs } from '../dataEntities/event' -import { L2ToL1MessageStatus } from '../dataEntities/message' -import { getL2Network } from '../dataEntities/networks' -import { ArbSdkError } from '../dataEntities/errors' - -export type L2ToL1TransactionEvent = - | EventArgs - | EventArgs - -/** - * Conditional type for Signer or Provider. If T is of type Provider - * then L2ToL1MessageReaderOrWriter will be of type L2ToL1MessageReader. - * If T is of type Signer then L2ToL1MessageReaderOrWriter will be of - * type L2ToL1MessageWriter. - */ -export type L2ToL1MessageReaderOrWriter = - T extends Provider ? L2ToL1MessageReader : L2ToL1MessageWriter - -/** - * Base functionality for L2->L1 messages - */ -export class L2ToL1Message { - protected isClassic( - e: L2ToL1TransactionEvent - ): e is EventArgs { - return isDefined( - (e as EventArgs).indexInBatch - ) - } - - /** - * Instantiates a new `L2ToL1MessageWriter` or `L2ToL1MessageReader` object. - * - * @param {SignerOrProvider} l1SignerOrProvider Signer or provider to be used for executing or reading the L2-to-L1 message. - * @param {L2ToL1TransactionEvent} event The event containing the data of the L2-to-L1 message. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1SignerOrProvider` in case you need more control. This will be a required parameter in a future major version update. - */ - public static fromEvent( - l1SignerOrProvider: T, - event: L2ToL1TransactionEvent, - l1Provider?: Provider - ): L2ToL1MessageReaderOrWriter - static fromEvent( - l1SignerOrProvider: T, - event: L2ToL1TransactionEvent, - l1Provider?: Provider - ): L2ToL1MessageReader | L2ToL1MessageWriter { - return SignerProviderUtils.isSigner(l1SignerOrProvider) - ? new L2ToL1MessageWriter(l1SignerOrProvider, event, l1Provider) - : new L2ToL1MessageReader(l1SignerOrProvider, event) - } - - /** - * Get event logs for L2ToL1 transactions. - * @param l2Provider - * @param filter Block range filter - * @param position The batchnumber indexed field was removed in nitro and a position indexed field was added. - * For pre-nitro events the value passed in here will be used to find events with the same batchnumber. - * For post nitro events it will be used to find events with the same position. - * @param destination The L1 destination of the L2ToL1 message - * @param hash The uniqueId indexed field was removed in nitro and a hash indexed field was added. - * For pre-nitro events the value passed in here will be used to find events with the same uniqueId. - * For post nitro events it will be used to find events with the same hash. - * @param indexInBatch The index in the batch, only valid for pre-nitro events. This parameter is ignored post-nitro - * @returns Any classic and nitro events that match the provided filters. - */ - public static async getL2ToL1Events( - l2Provider: Provider, - filter: { fromBlock: BlockTag; toBlock: BlockTag }, - position?: BigNumber, - destination?: string, - hash?: BigNumber, - indexInBatch?: BigNumber - ): Promise<(L2ToL1TransactionEvent & { transactionHash: string })[]> { - const l2Network = await getL2Network(l2Provider) - - const inClassicRange = (blockTag: BlockTag, nitroGenBlock: number) => { - if (typeof blockTag === 'string') { - // taking classic of "earliest", "latest", "earliest" and the nitro gen block - // yields 0, nitro gen, nitro gen since the classic range is always between 0 and nitro gen - - switch (blockTag) { - case 'earliest': - return 0 - case 'latest': - return nitroGenBlock - case 'pending': - return nitroGenBlock - default: - throw new ArbSdkError(`Unrecognised block tag. ${blockTag}`) - } - } - return Math.min(blockTag, nitroGenBlock) - } - - const inNitroRange = (blockTag: BlockTag, nitroGenBlock: number) => { - // taking nitro range of "earliest", "latest", "earliest" and the nitro gen block - // yields nitro gen, latest, pending since the nitro range is always between nitro gen and latest/pending - - if (typeof blockTag === 'string') { - switch (blockTag) { - case 'earliest': - return nitroGenBlock - case 'latest': - return 'latest' - case 'pending': - return 'pending' - default: - throw new ArbSdkError(`Unrecognised block tag. ${blockTag}`) - } - } - - return Math.max(blockTag, nitroGenBlock) - } - - // only fetch nitro events after the genesis block - const classicFilter = { - fromBlock: inClassicRange(filter.fromBlock, l2Network.nitroGenesisBlock), - toBlock: inClassicRange(filter.toBlock, l2Network.nitroGenesisBlock), - } - const logQueries = [] - if (classicFilter.fromBlock !== classicFilter.toBlock) { - logQueries.push( - classic.L2ToL1MessageClassic.getL2ToL1Events( - l2Provider, - classicFilter, - position, - destination, - hash, - indexInBatch - ) - ) - } - - const nitroFilter = { - fromBlock: inNitroRange(filter.fromBlock, l2Network.nitroGenesisBlock), - toBlock: inNitroRange(filter.toBlock, l2Network.nitroGenesisBlock), - } - if (nitroFilter.fromBlock !== nitroFilter.toBlock) { - logQueries.push( - nitro.L2ToL1MessageNitro.getL2ToL1Events( - l2Provider, - nitroFilter, - position, - destination, - hash - ) - ) - } - - return (await Promise.all(logQueries)).flat(1) - } -} - -/** - * Provides read-only access for l2-to-l1-messages - */ -export class L2ToL1MessageReader extends L2ToL1Message { - private readonly classicReader?: classic.L2ToL1MessageReaderClassic - private readonly nitroReader?: nitro.L2ToL1MessageReaderNitro - - constructor( - protected readonly l1Provider: Provider, - event: L2ToL1TransactionEvent - ) { - super() - if (this.isClassic(event)) { - this.classicReader = new classic.L2ToL1MessageReaderClassic( - l1Provider, - event.batchNumber, - event.indexInBatch - ) - } else { - this.nitroReader = new nitro.L2ToL1MessageReaderNitro(l1Provider, event) - } - } - - public async getOutboxProof( - l2Provider: Provider - ): Promise { - if (this.nitroReader) { - return await this.nitroReader.getOutboxProof(l2Provider) - } else return await this.classicReader!.tryGetProof(l2Provider) - } - - /** - * Get the status of this message - * In order to check if the message has been executed proof info must be provided. - * @returns - */ - public async status(l2Provider: Provider): Promise { - // can we create an l2tol1message here, we need to - the constructor is what we need - if (this.nitroReader) return await this.nitroReader.status(l2Provider) - else return await this.classicReader!.status(l2Provider) - } - - /** - * Waits until the outbox entry has been created, and will not return until it has been. - * WARNING: Outbox entries are only created when the corresponding node is confirmed. Which - * can take 1 week+, so waiting here could be a very long operation. - * @param retryDelay - * @returns outbox entry status (either executed or confirmed but not pending) - */ - public async waitUntilReadyToExecute( - l2Provider: Provider, - retryDelay = 500 - ): Promise { - if (this.nitroReader) - return this.nitroReader.waitUntilReadyToExecute(l2Provider, retryDelay) - else - return this.classicReader!.waitUntilOutboxEntryCreated( - l2Provider, - retryDelay - ) - } - - /** - * Estimates the L1 block number in which this L2 to L1 tx will be available for execution. - * If the message can or already has been executed, this returns null - * @param l2Provider - * @returns expected L1 block number where the L2 to L1 message will be executable. Returns null if the message can or already has been executed - */ - public async getFirstExecutableBlock( - l2Provider: Provider - ): Promise { - if (this.nitroReader) - return this.nitroReader.getFirstExecutableBlock(l2Provider) - else return this.classicReader!.getFirstExecutableBlock(l2Provider) - } -} - -/** - * Provides read and write access for l2-to-l1-messages - */ -export class L2ToL1MessageWriter extends L2ToL1MessageReader { - private readonly classicWriter?: classic.L2ToL1MessageWriterClassic - private readonly nitroWriter?: nitro.L2ToL1MessageWriterNitro - - /** - * Instantiates a new `L2ToL1MessageWriter` object. - * - * @param {Signer} l1Signer The signer to be used for executing the L2-to-L1 message. - * @param {L2ToL1TransactionEvent} event The event containing the data of the L2-to-L1 message. - * @param {Provider} [l1Provider] Optional. Used to override the Provider which is attached to `l1Signer` in case you need more control. This will be a required parameter in a future major version update. - */ - constructor( - l1Signer: Signer, - event: L2ToL1TransactionEvent, - l1Provider?: Provider - ) { - super(l1Provider ?? l1Signer.provider!, event) - - if (this.isClassic(event)) { - this.classicWriter = new classic.L2ToL1MessageWriterClassic( - l1Signer, - event.batchNumber, - event.indexInBatch, - l1Provider - ) - } else { - this.nitroWriter = new nitro.L2ToL1MessageWriterNitro( - l1Signer, - event, - l1Provider - ) - } - } - - /** - * Executes the L2ToL1Message on L1. - * Will throw an error if the outbox entry has not been created, which happens when the - * corresponding assertion is confirmed. - * @returns - */ - public async execute( - l2Provider: Provider, - overrides?: Overrides - ): Promise { - if (this.nitroWriter) return this.nitroWriter.execute(l2Provider, overrides) - else return await this.classicWriter!.execute(l2Provider, overrides) - } -} diff --git a/src/lib/message/L1ToL2Message.ts b/src/lib/message/ParentToChildMessage.ts similarity index 68% rename from src/lib/message/L1ToL2Message.ts rename to src/lib/message/ParentToChildMessage.ts index 2339c77576..dd9454b13f 100644 --- a/src/lib/message/L1ToL2Message.ts +++ b/src/lib/message/ParentToChildMessage.ts @@ -26,40 +26,44 @@ import { getAddress } from '@ethersproject/address' import { keccak256 } from '@ethersproject/keccak256' import { ArbRetryableTx__factory } from '../abi/factories/ArbRetryableTx__factory' -import { ARB_RETRYABLE_TX_ADDRESS } from '../dataEntities/constants' +import { + ARB_RETRYABLE_TX_ADDRESS, + DEFAULT_DEPOSIT_TIMEOUT, + SEVEN_DAYS_IN_SECONDS, +} from '../dataEntities/constants' import { SignerProviderUtils, SignerOrProvider, } from '../dataEntities/signerOrProvider' import { ArbSdkError } from '../dataEntities/errors' import { ethers, Overrides } from 'ethers' -import { L2TransactionReceipt, RedeemTransaction } from './L2Transaction' -import { getL2Network } from '../../lib/dataEntities/networks' +import { ChildTransactionReceipt, RedeemTransaction } from './ChildTransaction' +import { getArbitrumNetwork } from '../../lib/dataEntities/networks' import { RetryableMessageParams } from '../dataEntities/message' import { getTransactionReceipt, isDefined } from '../utils/lib' import { EventFetcher } from '../utils/eventFetcher' import { ErrorCode, Logger } from '@ethersproject/logger' -export enum L1ToL2MessageStatus { +export enum ParentToChildMessageStatus { /** * The retryable ticket has yet to be created */ NOT_YET_CREATED = 1, /** * An attempt was made to create the retryable ticket, but it failed. - * This could be due to not enough submission cost being paid by the L1 transaction + * This could be due to not enough submission cost being paid by the ParentChain transaction */ CREATION_FAILED = 2, /** * The retryable ticket has been created but has not been redeemed. This could be due to the - * auto redeem failing, or if the params (max l2 gas price) * (max l2 gas) = 0 then no auto + * auto redeem failing, or if the params (max chain gas price) * (max chain gas) = 0 then no auto * redeem tx is ever issued. An auto redeem is also never issued for ETH deposits. * A manual redeem is now required. */ - FUNDS_DEPOSITED_ON_L2 = 3, + FUNDS_DEPOSITED_ON_CHAIN = 3, /** * The retryable ticket has been redeemed (either by auto, or manually) and the - * l2 transaction has been executed + * chain transaction has been executed */ REDEEMED = 4, /** @@ -70,11 +74,11 @@ export enum L1ToL2MessageStatus { export enum EthDepositStatus { /** - * ETH is not deposited on L2 yet + * ETH is not deposited on Chain yet */ PENDING = 1, /** - * ETH is deposited successfully on L2 + * ETH is deposited successfully on Chain */ DEPOSITED = 2, } @@ -87,16 +91,16 @@ interface RetryableExistsError extends Error { /** * Conditional type for Signer or Provider. If T is of type Provider - * then L1ToL2MessageReaderOrWriter will be of type L1ToL2MessageReader. - * If T is of type Signer then L1ToL2MessageReaderOrWriter will be of - * type L1ToL2MessageWriter. + * then ParentToChildMessageReaderOrWriter will be of type ParentToChildMessageReader. + * If T is of type Signer then ParentToChildMessageReaderOrWriter will be of + * type ParentToChildMessageWriter. */ -export type L1ToL2MessageReaderOrWriter = - T extends Provider ? L1ToL2MessageReader : L1ToL2MessageWriter +export type ParentToChildMessageReaderOrWriter = + T extends Provider ? ParentToChildMessageReader : ParentToChildMessageWriter -export abstract class L1ToL2Message { +export abstract class ParentToChildMessage { /** - * When messages are sent from L1 to L2 a retryable ticket is created on L2. + * When messages are sent from ParentChain to Chain a retryable ticket is created on Chain. * The retryableCreationId can be used to retrieve information about the success or failure of the * creation of the retryable ticket. */ @@ -105,29 +109,29 @@ export abstract class L1ToL2Message { /** * The submit retryable transactions use the typed transaction envelope 2718. * The id of these transactions is the hash of the RLP encoded transaction. - * @param l2ChainId - * @param fromAddress the aliased address that called the L1 inbox as emitted in the bridge event. + * @param chainChainId + * @param fromAddress the aliased address that called the ParentChain inbox as emitted in the bridge event. * @param messageNumber - * @param l1BaseFee + * @param parentChainBaseFee * @param destAddress - * @param l2CallValue - * @param l1Value + * @param chainCallValue + * @param parentChainValue * @param maxSubmissionFee - * @param excessFeeRefundAddress refund address specified in the retryable creation. Note the L1 inbox aliases this address if it is a L1 smart contract. The user is expected to provide this value already aliased when needed. - * @param callValueRefundAddress refund address specified in the retryable creation. Note the L1 inbox aliases this address if it is a L1 smart contract. The user is expected to provide this value already aliased when needed. + * @param excessFeeRefundAddress refund address specified in the retryable creation. Note the ParentChain inbox aliases this address if it is a ParentChain smart contract. The user is expected to provide this value already aliased when needed. + * @param callValueRefundAddress refund address specified in the retryable creation. Note the ParentChain inbox aliases this address if it is a ParentChain smart contract. The user is expected to provide this value already aliased when needed. * @param gasLimit * @param maxFeePerGas * @param data * @returns */ public static calculateSubmitRetryableId( - l2ChainId: number, + chainChainId: number, fromAddress: string, messageNumber: BigNumber, - l1BaseFee: BigNumber, + parentChainBaseFee: BigNumber, destAddress: string, - l2CallValue: BigNumber, - l1Value: BigNumber, + chainCallValue: BigNumber, + parentChainValue: BigNumber, maxSubmissionFee: BigNumber, excessFeeRefundAddress: string, callValueRefundAddress: string, @@ -139,21 +143,21 @@ export abstract class L1ToL2Message { return ethers.utils.stripZeros(value.toHexString()) } - const chainId = BigNumber.from(l2ChainId) + const chainId = BigNumber.from(chainChainId) const msgNum = BigNumber.from(messageNumber) const fields: any[] = [ formatNumber(chainId), zeroPad(formatNumber(msgNum), 32), fromAddress, - formatNumber(l1BaseFee), + formatNumber(parentChainBaseFee), - formatNumber(l1Value), + formatNumber(parentChainValue), formatNumber(maxFeePerGas), formatNumber(gasLimit), // when destAddress is 0x0, arbos treat that as nil destAddress === ethers.constants.AddressZero ? '0x' : destAddress, - formatNumber(l2CallValue), + formatNumber(chainCallValue), callValueRefundAddress, formatNumber(maxSubmissionFee), excessFeeRefundAddress, @@ -170,36 +174,36 @@ export abstract class L1ToL2Message { } public static fromEventComponents( - l2SignerOrProvider: T, + chainSignerOrProvider: T, chainId: number, sender: string, messageNumber: BigNumber, - l1BaseFee: BigNumber, + parentChainBaseFee: BigNumber, messageData: RetryableMessageParams - ): L1ToL2MessageReaderOrWriter + ): ParentToChildMessageReaderOrWriter public static fromEventComponents( - l2SignerOrProvider: T, + chainSignerOrProvider: T, chainId: number, sender: string, messageNumber: BigNumber, - l1BaseFee: BigNumber, + parentChainBaseFee: BigNumber, messageData: RetryableMessageParams - ): L1ToL2MessageReader | L1ToL2MessageWriter { - return SignerProviderUtils.isSigner(l2SignerOrProvider) - ? new L1ToL2MessageWriter( - l2SignerOrProvider, + ): ParentToChildMessageReader | ParentToChildMessageWriter { + return SignerProviderUtils.isSigner(chainSignerOrProvider) + ? new ParentToChildMessageWriter( + chainSignerOrProvider, chainId, sender, messageNumber, - l1BaseFee, + parentChainBaseFee, messageData ) - : new L1ToL2MessageReader( - l2SignerOrProvider, + : new ParentToChildMessageReader( + chainSignerOrProvider, chainId, sender, messageNumber, - l1BaseFee, + parentChainBaseFee, messageData ) } @@ -208,14 +212,14 @@ export abstract class L1ToL2Message { public readonly chainId: number, public readonly sender: string, public readonly messageNumber: BigNumber, - public readonly l1BaseFee: BigNumber, + public readonly parentChainBaseFee: BigNumber, public readonly messageData: RetryableMessageParams ) { - this.retryableCreationId = L1ToL2Message.calculateSubmitRetryableId( + this.retryableCreationId = ParentToChildMessage.calculateSubmitRetryableId( chainId, sender, messageNumber, - l1BaseFee, + parentChainBaseFee, messageData.destAddress, messageData.l2CallValue, messageData.l1Value, @@ -230,33 +234,41 @@ export abstract class L1ToL2Message { } /** - * If the status is redeemed an l2TxReceipt is populated. - * For all other statuses l2TxReceipt is not populated + * If the status is redeemed an chainTxReceipt is populated. + * For all other statuses chainTxReceipt is not populated */ -export type L1ToL2MessageWaitResult = - | { status: L1ToL2MessageStatus.REDEEMED; l2TxReceipt: TransactionReceipt } - | { status: Exclude } +export type ParentToChildMessageWaitResult = + | { + status: ParentToChildMessageStatus.REDEEMED + chainTxReceipt: TransactionReceipt + } + | { + status: Exclude< + ParentToChildMessageStatus, + ParentToChildMessageStatus.REDEEMED + > + } export type EthDepositMessageWaitResult = { - l2TxReceipt: TransactionReceipt | null + chainTxReceipt: TransactionReceipt | null } -export class L1ToL2MessageReader extends L1ToL2Message { +export class ParentToChildMessageReader extends ParentToChildMessage { private retryableCreationReceipt: TransactionReceipt | undefined | null public constructor( - public readonly l2Provider: Provider, + public readonly chainProvider: Provider, chainId: number, sender: string, messageNumber: BigNumber, - l1BaseFee: BigNumber, + parentChainBaseFee: BigNumber, messageData: RetryableMessageParams ) { - super(chainId, sender, messageNumber, l1BaseFee, messageData) + super(chainId, sender, messageNumber, parentChainBaseFee, messageData) } /** * Try to get the receipt for the retryable ticket creation. - * This is the L2 transaction that creates the retryable ticket. + * This is the Chain transaction that creates the retryable ticket. * If confirmations or timeout is provided, this will wait for the ticket to be created * @returns Null if retryable has not been created */ @@ -266,7 +278,7 @@ export class L1ToL2MessageReader extends L1ToL2Message { ): Promise { if (!this.retryableCreationReceipt) { this.retryableCreationReceipt = await getTransactionReceipt( - this.l2Provider, + this.chainProvider, this.retryableCreationId, confirmations, timeout @@ -285,11 +297,11 @@ export class L1ToL2MessageReader extends L1ToL2Message { const creationReceipt = await this.getRetryableCreationReceipt() if (creationReceipt) { - const l2Receipt = new L2TransactionReceipt(creationReceipt) - const redeemEvents = l2Receipt.getRedeemScheduledEvents() + const chainReceipt = new ChildTransactionReceipt(creationReceipt) + const redeemEvents = chainReceipt.getRedeemScheduledEvents() if (redeemEvents.length === 1) { - return await this.l2Provider.getTransactionReceipt( + return await this.chainProvider.getTransactionReceipt( redeemEvents[0].retryTxHash ) } else if (redeemEvents.length > 1) { @@ -303,34 +315,39 @@ export class L1ToL2MessageReader extends L1ToL2Message { } /** - * Receipt for the successful l2 transaction created by this message. + * Receipt for the successful chain transaction created by this message. * @returns TransactionReceipt of the first successful redeem if exists, otherwise the current status of the message. */ - public async getSuccessfulRedeem(): Promise { - const l2Network = await getL2Network(this.l2Provider) - const eventFetcher = new EventFetcher(this.l2Provider) + public async getSuccessfulRedeem(): Promise { + const chainNetwork = await getArbitrumNetwork(this.chainProvider) + const eventFetcher = new EventFetcher(this.chainProvider) const creationReceipt = await this.getRetryableCreationReceipt() if (!isDefined(creationReceipt)) { // retryable was never created, or not created yet // therefore it cant have been redeemed or be expired - return { status: L1ToL2MessageStatus.NOT_YET_CREATED } + return { status: ParentToChildMessageStatus.NOT_YET_CREATED } } if (creationReceipt.status === 0) { - return { status: L1ToL2MessageStatus.CREATION_FAILED } + return { status: ParentToChildMessageStatus.CREATION_FAILED } } // check the auto redeem first to avoid doing costly log queries in the happy case const autoRedeem = await this.getAutoRedeemAttempt() if (autoRedeem && autoRedeem.status === 1) { - return { l2TxReceipt: autoRedeem, status: L1ToL2MessageStatus.REDEEMED } + return { + chainTxReceipt: autoRedeem, + status: ParentToChildMessageStatus.REDEEMED, + } } if (await this.retryableExists()) { // the retryable was created and still exists // therefore it cant have been redeemed or be expired - return { status: L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 } + return { + status: ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN, + } } // from this point on we know that the retryable was created but does not exist, @@ -340,10 +357,14 @@ export class L1ToL2MessageReader extends L1ToL2Message { // to do this we need to filter through the whole lifetime of the ticket looking // for relevant redeem scheduled events let increment = 1000 - let fromBlock = await this.l2Provider.getBlock(creationReceipt.blockNumber) - let timeout = fromBlock.timestamp + l2Network.retryableLifetimeSeconds + let fromBlock = await this.chainProvider.getBlock( + creationReceipt.blockNumber + ) + let timeout = + fromBlock.timestamp + + (chainNetwork.retryableLifetimeSeconds ?? SEVEN_DAYS_IN_SECONDS) const queriedRange: { from: number; to: number }[] = [] - const maxBlock = await this.l2Provider.getBlockNumber() + const maxBlock = await this.chainProvider.getBlockNumber() while (fromBlock.number < maxBlock) { const toBlockNumber = Math.min(fromBlock.number + increment, maxBlock) @@ -363,7 +384,7 @@ export class L1ToL2MessageReader extends L1ToL2Message { const successfulRedeem = ( await Promise.all( redeemEvents.map(e => - this.l2Provider.getTransactionReceipt(e.event.retryTxHash) + this.chainProvider.getTransactionReceipt(e.event.retryTxHash) ) ) ).filter(r => isDefined(r) && r.status === 1) @@ -374,11 +395,11 @@ export class L1ToL2MessageReader extends L1ToL2Message { ) if (successfulRedeem.length == 1) return { - l2TxReceipt: successfulRedeem[0], - status: L1ToL2MessageStatus.REDEEMED, + chainTxReceipt: successfulRedeem[0], + status: ParentToChildMessageStatus.REDEEMED, } - const toBlock = await this.l2Provider.getBlock(toBlockNumber) + const toBlock = await this.chainProvider.getBlock(toBlockNumber) if (toBlock.timestamp > timeout) { // Check for LifetimeExtended event while (queriedRange.length > 0) { @@ -418,7 +439,7 @@ export class L1ToL2MessageReader extends L1ToL2Message { // we know from earlier that the retryable no longer exists, so if we havent found the redemption // we know that it must have expired - return { status: L1ToL2MessageStatus.EXPIRED } + return { status: ParentToChildMessageStatus.EXPIRED } } /** @@ -432,12 +453,12 @@ export class L1ToL2MessageReader extends L1ToL2Message { private async retryableExists(): Promise { const currentTimestamp = BigNumber.from( - (await this.l2Provider.getBlock('latest')).timestamp + (await this.chainProvider.getBlock('latest')).timestamp ) try { const timeoutTimestamp = await this.getTimeout() // timeoutTimestamp returns the timestamp at which the retryable ticket expires - // it can also return revert if the ticket l2Tx does not exist + // it can also return revert if the ticket chainTx does not exist return currentTimestamp.lte(timeoutTimestamp) } catch (err) { if ( @@ -452,31 +473,27 @@ export class L1ToL2MessageReader extends L1ToL2Message { } } - public async status(): Promise { + public async status(): Promise { return (await this.getSuccessfulRedeem()).status } /** - * Wait for the retryable ticket to be created, for it to be redeemed, and for the l2Tx to be executed. - * Note: The terminal status of a transaction that only does an eth deposit is FUNDS_DEPOSITED_ON_L2 as - * no L2 transaction needs to be executed, however the terminal state of any other transaction is REDEEMED - * which represents that the retryable ticket has been redeemed and the L2 tx has been executed. + * Wait for the retryable ticket to be created, for it to be redeemed, and for the chainTx to be executed. + * Note: The terminal status of a transaction that only does an eth deposit is FUNDS_DEPOSITED_ON_CHAIN as + * no Chain transaction needs to be executed, however the terminal state of any other transaction is REDEEMED + * which represents that the retryable ticket has been redeemed and the Chain tx has been executed. * @param confirmations Amount of confirmations the retryable ticket and the auto redeem receipt should have * @param timeout Amount of time to wait for the retryable ticket to be created - * Defaults to 15 minutes, as by this time all transactions are expected to be included on L2. Throws on timeout. - * @returns The wait result contains a status, and optionally the l2TxReceipt. - * If the status is "REDEEMED" then a l2TxReceipt is also available on the result. - * If the status has any other value then l2TxReceipt is not populated. + * Defaults to 15 minutes, as by this time all transactions are expected to be included on Chain. Throws on timeout. + * @returns The wait result contains a status, and optionally the chainTxReceipt. + * If the status is "REDEEMED" then a chainTxReceipt is also available on the result. + * If the status has any other value then chainTxReceipt is not populated. */ public async waitForStatus( confirmations?: number, timeout?: number - ): Promise { - const l2Network = await getL2Network(this.chainId) - - const chosenTimeout = isDefined(timeout) - ? timeout - : l2Network.depositTimeout + ): Promise { + const chosenTimeout = isDefined(timeout) ? timeout : DEFAULT_DEPOSIT_TIMEOUT // try to wait for the retryable ticket to be created const _retryableCreationReceipt = await this.getRetryableCreationReceipt( @@ -501,10 +518,10 @@ export class L1ToL2MessageReader extends L1ToL2Message { * The minimium lifetime of a retryable tx * @returns */ - public static async getLifetime(l2Provider: Provider): Promise { + public static async getLifetime(chainProvider: Provider): Promise { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - l2Provider + chainProvider ) return await arbRetryableTx.getLifetime() } @@ -516,37 +533,41 @@ export class L1ToL2MessageReader extends L1ToL2Message { public async getTimeout(): Promise { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - this.l2Provider + this.chainProvider ) return await arbRetryableTx.getTimeout(this.retryableCreationId) } /** - * Address to which CallValue will be credited to on L2 if the retryable ticket times out or is cancelled. + * Address to which CallValue will be credited to on Chain if the retryable ticket times out or is cancelled. * The Beneficiary is also the address with the right to cancel a Retryable Ticket (if the ticket hasn’t been redeemed yet). * @returns */ public getBeneficiary(): Promise { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - this.l2Provider + this.chainProvider ) return arbRetryableTx.getBeneficiary(this.retryableCreationId) } } -export class L1ToL2MessageReaderClassic { +export class ParentToChildMessageReaderClassic { private retryableCreationReceipt: TransactionReceipt | undefined | null public readonly messageNumber: BigNumber public readonly retryableCreationId: string public readonly autoRedeemId: string - public readonly l2TxHash: string - public readonly l2Provider: Provider + public readonly chainTxHash: string + public readonly chainProvider: Provider - constructor(l2Provider: Provider, chainId: number, messageNumber: BigNumber) { + constructor( + chainProvider: Provider, + chainId: number, + messageNumber: BigNumber + ) { const bitFlip = (num: BigNumber) => num.or(BigNumber.from(1).shl(255)) this.messageNumber = messageNumber - this.l2Provider = l2Provider + this.chainProvider = chainProvider this.retryableCreationId = keccak256( concat([ @@ -562,7 +583,7 @@ export class L1ToL2MessageReaderClassic { ]) ) - this.l2TxHash = keccak256( + this.chainTxHash = keccak256( concat([ zeroPad(this.retryableCreationId, 32), zeroPad(BigNumber.from(0).toHexString(), 32), @@ -570,11 +591,11 @@ export class L1ToL2MessageReaderClassic { ) } - private calculateL2DerivedHash(retryableCreationId: string): string { + private calculateChainDerivedHash(retryableCreationId: string): string { return keccak256( concat([ zeroPad(retryableCreationId, 32), - // BN 0 meaning L2 TX + // BN 0 meaning Chain TX zeroPad(BigNumber.from(0).toHexString(), 32), ]) ) @@ -582,7 +603,7 @@ export class L1ToL2MessageReaderClassic { /** * Try to get the receipt for the retryable ticket creation. - * This is the L2 transaction that creates the retryable ticket. + * This is the Chain transaction that creates the retryable ticket. * If confirmations or timeout is provided, this will wait for the ticket to be created * @returns Null if retryable has not been created */ @@ -592,7 +613,7 @@ export class L1ToL2MessageReaderClassic { ): Promise { if (!this.retryableCreationReceipt) { this.retryableCreationReceipt = await getTransactionReceipt( - this.l2Provider, + this.chainProvider, this.retryableCreationId, confirmations, timeout @@ -602,77 +623,81 @@ export class L1ToL2MessageReaderClassic { return this.retryableCreationReceipt || null } - public async status(): Promise { + public async status(): Promise { const creationReceipt = await this.getRetryableCreationReceipt() if (!isDefined(creationReceipt)) { - return L1ToL2MessageStatus.NOT_YET_CREATED + return ParentToChildMessageStatus.NOT_YET_CREATED } if (creationReceipt.status === 0) { - return L1ToL2MessageStatus.CREATION_FAILED + return ParentToChildMessageStatus.CREATION_FAILED } - const l2DerivedHash = this.calculateL2DerivedHash(this.retryableCreationId) - const l2TxReceipt = await this.l2Provider.getTransactionReceipt( - l2DerivedHash + const chainDerivedHash = this.calculateChainDerivedHash( + this.retryableCreationId + ) + const chainTxReceipt = await this.chainProvider.getTransactionReceipt( + chainDerivedHash ) - if (l2TxReceipt && l2TxReceipt.status === 1) { - return L1ToL2MessageStatus.REDEEMED + if (chainTxReceipt && chainTxReceipt.status === 1) { + return ParentToChildMessageStatus.REDEEMED } - return L1ToL2MessageStatus.EXPIRED + return ParentToChildMessageStatus.EXPIRED } } -export class L1ToL2MessageWriter extends L1ToL2MessageReader { +export class ParentToChildMessageWriter extends ParentToChildMessageReader { public constructor( - public readonly l2Signer: Signer, + public readonly chainSigner: Signer, chainId: number, sender: string, messageNumber: BigNumber, - l1BaseFee: BigNumber, + parentChainBaseFee: BigNumber, messageData: RetryableMessageParams ) { super( - l2Signer.provider!, + chainSigner.provider!, chainId, sender, messageNumber, - l1BaseFee, + parentChainBaseFee, messageData ) - if (!l2Signer.provider) + if (!chainSigner.provider) throw new ArbSdkError('Signer not connected to provider.') } /** * Manually redeem the retryable ticket. - * Throws if message status is not L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + * Throws if message status is not ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN */ public async redeem(overrides?: Overrides): Promise { const status = await this.status() - if (status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + if (status === ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN) { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - this.l2Signer + this.chainSigner ) const redeemTx = await arbRetryableTx.redeem(this.retryableCreationId, { ...overrides, }) - return L2TransactionReceipt.toRedeemTransaction( - L2TransactionReceipt.monkeyPatchWait(redeemTx), - this.l2Provider + return ChildTransactionReceipt.toRedeemTransaction( + ChildTransactionReceipt.monkeyPatchWait(redeemTx), + this.chainProvider ) } else { throw new ArbSdkError( `Cannot redeem as retryable does not exist. Message status: ${ - L1ToL2MessageStatus[status] + ParentToChildMessageStatus[status] } must be: ${ - L1ToL2MessageStatus[L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2] + ParentToChildMessageStatus[ + ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN + ] }.` ) } @@ -680,22 +705,24 @@ export class L1ToL2MessageWriter extends L1ToL2MessageReader { /** * Cancel the retryable ticket. - * Throws if message status is not L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + * Throws if message status is not ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN */ public async cancel(overrides?: Overrides): Promise { const status = await this.status() - if (status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + if (status === ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN) { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - this.l2Signer + this.chainSigner ) return await arbRetryableTx.cancel(this.retryableCreationId, overrides) } else { throw new ArbSdkError( `Cannot cancel as retryable does not exist. Message status: ${ - L1ToL2MessageStatus[status] + ParentToChildMessageStatus[status] } must be: ${ - L1ToL2MessageStatus[L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2] + ParentToChildMessageStatus[ + ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN + ] }.` ) } @@ -703,22 +730,24 @@ export class L1ToL2MessageWriter extends L1ToL2MessageReader { /** * Increase the timeout of a retryable ticket. - * Throws if message status is not L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 + * Throws if message status is not ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN */ public async keepAlive(overrides?: Overrides): Promise { const status = await this.status() - if (status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + if (status === ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN) { const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - this.l2Signer + this.chainSigner ) return await arbRetryableTx.keepalive(this.retryableCreationId, overrides) } else { throw new ArbSdkError( `Cannot keep alive as retryable does not exist. Message status: ${ - L1ToL2MessageStatus[status] + ParentToChildMessageStatus[status] } must be: ${ - L1ToL2MessageStatus[L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2] + ParentToChildMessageStatus[ + ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN + ] }.` ) } @@ -726,14 +755,14 @@ export class L1ToL2MessageWriter extends L1ToL2MessageReader { } /** - * A message for Eth deposits from L1 to L2 + * A message for Eth deposits from ParentChain to Chain */ export class EthDepositMessage { - public readonly l2DepositTxHash: string - private l2DepositTxReceipt: TransactionReceipt | undefined | null + public readonly chainDepositTxHash: string + private chainDepositTxReceipt: TransactionReceipt | undefined | null public static calculateDepositTxId( - l2ChainId: number, + chainChainId: number, messageNumber: BigNumber, fromAddress: string, toAddress: string, @@ -743,7 +772,7 @@ export class EthDepositMessage { return ethers.utils.stripZeros(numberVal.toHexString()) } - const chainId = BigNumber.from(l2ChainId) + const chainId = BigNumber.from(chainChainId) const msgNum = BigNumber.from(messageNumber) // https://github.com/OffchainLabs/go-ethereum/blob/07e017aa73e32be92aadb52fa327c552e1b7b118/core/types/arb_types.go#L302-L308 @@ -774,7 +803,7 @@ export class EthDepositMessage { to: string value: BigNumber } { - // https://github.com/OffchainLabs/nitro/blob/aa84e899cbc902bf6da753b1d66668a1def2c106/contracts/src/bridge/Inbox.sol#L242 + // https://github.com/OffchainLabs/nitro/blob/aa84e899cbc902bf6da753b1d66668a1def2c106/contracts/src/bridge/Inbox.sol#Chain42 // ethers.defaultAbiCoder doesnt decode packed args, so we do a hardcoded parsing const addressEnd = 2 + 20 * 2 const to = getAddress('0x' + eventData.substring(2, addressEnd)) @@ -785,25 +814,25 @@ export class EthDepositMessage { /** * Create an EthDepositMessage from data emitted in event when calling ethDeposit on Inbox.sol - * @param l2Provider + * @param chainProvider * @param messageNumber The message number in the Inbox.InboxMessageDelivered event * @param senderAddr The sender address from Bridge.MessageDelivered event * @param inboxMessageEventData The data field from the Inbox.InboxMessageDelivered event * @returns */ public static async fromEventComponents( - l2Provider: Provider, + chainProvider: Provider, messageNumber: BigNumber, senderAddr: string, inboxMessageEventData: string ) { - const chainId = (await l2Provider.getNetwork()).chainId + const chainId = (await chainProvider.getNetwork()).chainId const { to, value } = EthDepositMessage.parseEthDepositData( inboxMessageEventData ) return new EthDepositMessage( - l2Provider, + chainProvider, chainId, messageNumber, senderAddr, @@ -814,22 +843,22 @@ export class EthDepositMessage { /** * - * @param l2Provider - * @param l2ChainId + * @param chainProvider + * @param chainChainId * @param messageNumber - * @param to Recipient address of the ETH on L2 + * @param to Recipient address of the ETH on Chain * @param value */ constructor( - private readonly l2Provider: Provider, - public readonly l2ChainId: number, + private readonly chainProvider: Provider, + public readonly chainChainId: number, public readonly messageNumber: BigNumber, public readonly from: string, public readonly to: string, public readonly value: BigNumber ) { - this.l2DepositTxHash = EthDepositMessage.calculateDepositTxId( - l2ChainId, + this.chainDepositTxHash = EthDepositMessage.calculateDepositTxId( + chainChainId, messageNumber, from, to, @@ -838,29 +867,25 @@ export class EthDepositMessage { } public async status(): Promise { - const receipt = await this.l2Provider.getTransactionReceipt( - this.l2DepositTxHash + const receipt = await this.chainProvider.getTransactionReceipt( + this.chainDepositTxHash ) if (receipt === null) return EthDepositStatus.PENDING else return EthDepositStatus.DEPOSITED } public async wait(confirmations?: number, timeout?: number) { - const l2Network = await getL2Network(this.l2ChainId) - - const chosenTimeout = isDefined(timeout) - ? timeout - : l2Network.depositTimeout + const chosenTimeout = isDefined(timeout) ? timeout : DEFAULT_DEPOSIT_TIMEOUT - if (!this.l2DepositTxReceipt) { - this.l2DepositTxReceipt = await getTransactionReceipt( - this.l2Provider, - this.l2DepositTxHash, + if (!this.chainDepositTxReceipt) { + this.chainDepositTxReceipt = await getTransactionReceipt( + this.chainProvider, + this.chainDepositTxHash, confirmations, chosenTimeout ) } - return this.l2DepositTxReceipt || null + return this.chainDepositTxReceipt || null } } diff --git a/src/lib/message/L1ToL2MessageCreator.ts b/src/lib/message/ParentToChildMessageCreator.ts similarity index 59% rename from src/lib/message/L1ToL2MessageCreator.ts rename to src/lib/message/ParentToChildMessageCreator.ts index 2c3223d2db..5e83c164ef 100644 --- a/src/lib/message/L1ToL2MessageCreator.ts +++ b/src/lib/message/ParentToChildMessageCreator.ts @@ -4,66 +4,75 @@ import { Provider } from '@ethersproject/abstract-provider' import { GasOverrides, - L1ToL2MessageGasEstimator, -} from './L1ToL2MessageGasEstimator' -import { L1ContractTransaction, L1TransactionReceipt } from './L1Transaction' + ParentToChildMessageGasEstimator, +} from './ParentToChildMessageGasEstimator' +import { + ParentContractTransaction, + ParentTransactionReceipt, +} from './ParentTransaction' import { Inbox__factory } from '../abi/factories/Inbox__factory' +import { getArbitrumNetwork } from '../dataEntities/networks' import { ERC20Inbox__factory } from '../abi/factories/ERC20Inbox__factory' -import { getL2Network } from '../dataEntities/networks' import { PayableOverrides } from '@ethersproject/contracts' import { SignerProviderUtils } from '../dataEntities/signerOrProvider' import { MissingProviderArbSdkError } from '../dataEntities/errors' import { getBaseFee } from '../utils/lib' import { - isL1ToL2TransactionRequest, - L1ToL2TransactionRequest, + isParentToChildTransactionRequest, + ParentToChildTransactionRequest, } from '../dataEntities/transactionRequest' import { RetryableData } from '../dataEntities/retryableData' import { OmitTyped, PartialPick } from '../utils/types' -type L1ToL2GasKeys = +type ParentToChildGasKeys = | 'maxSubmissionCost' | 'maxFeePerGas' | 'gasLimit' | 'deposit' -export type L1ToL2MessageGasParams = Pick -export type L1ToL2MessageNoGasParams = OmitTyped -export type L1ToL2MessageParams = PartialPick< - L1ToL2MessageNoGasParams, +export type ParentToChildMessageGasParams = Pick< + RetryableData, + ParentToChildGasKeys +> +export type ParentToChildMessageNoGasParams = OmitTyped< + RetryableData, + ParentToChildGasKeys +> +export type ParentToChildMessageParams = PartialPick< + ParentToChildMessageNoGasParams, 'excessFeeRefundAddress' | 'callValueRefundAddress' > /** - * Creates retryable tickets by directly calling the Inbox contract on L1 + * Creates retryable tickets by directly calling the Inbox contract on Parent chain */ -export class L1ToL2MessageCreator { - constructor(public readonly l1Signer: Signer) { - if (!SignerProviderUtils.signerHasProvider(l1Signer)) { - throw new MissingProviderArbSdkError('l1Signer') +export class ParentToChildMessageCreator { + constructor(public readonly parentSigner: Signer) { + if (!SignerProviderUtils.signerHasProvider(parentSigner)) { + throw new MissingProviderArbSdkError('parentSigner') } } /** * Gets a current estimate for the supplied params * @param params - * @param l1Provider - * @param l2Provider + * @param parentProvider + * @param childProvider * @param retryableGasOverrides * @returns */ protected static async getTicketEstimate( - params: L1ToL2MessageNoGasParams, - l1Provider: Provider, - l2Provider: Provider, + params: ParentToChildMessageNoGasParams, + parentProvider: Provider, + childProvider: Provider, retryableGasOverrides?: GasOverrides - ): Promise> { - const baseFee = await getBaseFee(l1Provider) + ): Promise> { + const baseFee = await getBaseFee(parentProvider) - const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider) + const gasEstimator = new ParentToChildMessageGasEstimator(childProvider) return await gasEstimator.estimateAll( params, baseFee, - l1Provider, + parentProvider, retryableGasOverrides ) } @@ -78,8 +87,8 @@ export class L1ToL2MessageCreator { * @returns */ protected static getTicketCreationRequestCallData( - params: L1ToL2MessageParams, - estimates: Pick, + params: ParentToChildMessageParams, + estimates: Pick, excessFeeRefundAddress: string, callValueRefundAddress: string, nativeTokenIsEth: boolean @@ -119,37 +128,37 @@ export class L1ToL2MessageCreator { /** * Generate a transaction request for creating a retryable ticket * @param params - * @param l1Provider - * @param l2Provider + * @param parentProvider + * @param childProvider * @param options * @returns */ public static async getTicketCreationRequest( - params: L1ToL2MessageParams, - l1Provider: Provider, - l2Provider: Provider, + params: ParentToChildMessageParams, + parentProvider: Provider, + childProvider: Provider, options?: GasOverrides - ): Promise { + ): Promise { const excessFeeRefundAddress = params.excessFeeRefundAddress || params.from const callValueRefundAddress = params.callValueRefundAddress || params.from - const parsedParams: L1ToL2MessageNoGasParams = { + const parsedParams: ParentToChildMessageNoGasParams = { ...params, excessFeeRefundAddress, callValueRefundAddress, } - const estimates = await L1ToL2MessageCreator.getTicketEstimate( + const estimates = await ParentToChildMessageCreator.getTicketEstimate( parsedParams, - l1Provider, - l2Provider, + parentProvider, + childProvider, options ) - const l2Network = await getL2Network(l2Provider) - const nativeTokenIsEth = typeof l2Network.nativeToken === 'undefined' + const childChain = await getArbitrumNetwork(childProvider) + const nativeTokenIsEth = typeof childChain.nativeToken === 'undefined' - const data = L1ToL2MessageCreator.getTicketCreationRequestCallData( + const data = ParentToChildMessageCreator.getTicketCreationRequestCallData( params, estimates, excessFeeRefundAddress, @@ -159,7 +168,7 @@ export class L1ToL2MessageCreator { return { txRequest: { - to: l2Network.ethBridge.inbox, + to: childChain.ethBridge.inbox, data, value: nativeTokenIsEth ? estimates.deposit : constants.Zero, from: params.from, @@ -177,42 +186,46 @@ export class L1ToL2MessageCreator { deposit: estimates.deposit, }, isValid: async () => { - const reEstimates = await L1ToL2MessageCreator.getTicketEstimate( + const reEstimates = await ParentToChildMessageCreator.getTicketEstimate( parsedParams, - l1Provider, - l2Provider, + parentProvider, + childProvider, options ) - return L1ToL2MessageGasEstimator.isValid(estimates, reEstimates) + return ParentToChildMessageGasEstimator.isValid(estimates, reEstimates) }, } } /** - * Creates a retryable ticket by directly calling the Inbox contract on L1 + * Creates a retryable ticket by directly calling the Inbox contract on Parent chain */ public async createRetryableTicket( params: - | (L1ToL2MessageParams & { overrides?: PayableOverrides }) - | (L1ToL2TransactionRequest & { overrides?: PayableOverrides }), - l2Provider: Provider, + | (ParentToChildMessageParams & { overrides?: PayableOverrides }) + | (ParentToChildTransactionRequest & { + overrides?: PayableOverrides + }), + childProvider: Provider, options?: GasOverrides - ): Promise { - const l1Provider = SignerProviderUtils.getProviderOrThrow(this.l1Signer) - const createRequest = isL1ToL2TransactionRequest(params) + ): Promise { + const parentProvider = SignerProviderUtils.getProviderOrThrow( + this.parentSigner + ) + const createRequest = isParentToChildTransactionRequest(params) ? params - : await L1ToL2MessageCreator.getTicketCreationRequest( + : await ParentToChildMessageCreator.getTicketCreationRequest( params, - l1Provider, - l2Provider, + parentProvider, + childProvider, options ) - const tx = await this.l1Signer.sendTransaction({ + const tx = await this.parentSigner.sendTransaction({ ...createRequest.txRequest, ...params.overrides, }) - return L1TransactionReceipt.monkeyPatchWait(tx) + return ParentTransactionReceipt.monkeyPatchWait(tx) } } diff --git a/src/lib/message/L1ToL2MessageGasEstimator.ts b/src/lib/message/ParentToChildMessageGasEstimator.ts similarity index 76% rename from src/lib/message/L1ToL2MessageGasEstimator.ts rename to src/lib/message/ParentToChildMessageGasEstimator.ts index 3ddd5b7c76..b4c0d487d0 100644 --- a/src/lib/message/L1ToL2MessageGasEstimator.ts +++ b/src/lib/message/ParentToChildMessageGasEstimator.ts @@ -5,22 +5,22 @@ import { Inbox__factory } from '../abi/factories/Inbox__factory' import { NodeInterface__factory } from '../abi/factories/NodeInterface__factory' import { NODE_INTERFACE_ADDRESS } from '../dataEntities/constants' import { ArbSdkError } from '../dataEntities/errors' -import { getL2Network } from '../dataEntities/networks' +import { getArbitrumNetwork } from '../dataEntities/networks' import { RetryableData, RetryableDataTools, } from '../dataEntities/retryableData' -import { L1ToL2TransactionRequest } from '../dataEntities/transactionRequest' +import { ParentToChildTransactionRequest } from '../dataEntities/transactionRequest' import { getBaseFee, isDefined } from '../utils/lib' import { OmitTyped } from '../utils/types' import { - L1ToL2MessageGasParams, - L1ToL2MessageNoGasParams, -} from './L1ToL2MessageCreator' + ParentToChildMessageGasParams, + ParentToChildMessageNoGasParams, +} from './ParentToChildMessageCreator' /** * The default amount to increase the maximum submission cost. Submission cost is calculated - * from (call data size * some const * l1 base fee). So we need to provide some leeway for + * from (call data size * some const * parent chain base fee). So we need to provide some leeway for * base fee increase. Since submission fee is a small amount it isn't too bas for UX to increase * it by a large amount, and provide better safety. */ @@ -62,16 +62,16 @@ export interface GasOverrides { deposit?: Pick } -const defaultL1ToL2MessageEstimateOptions = { +const defaultParentToChildMessageEstimateOptions = { maxSubmissionFeePercentIncrease: DEFAULT_SUBMISSION_FEE_PERCENT_INCREASE, - // gas limit for l1->l2 messages should be predictable. If it isn't due to the nature + // gas limit for Parent->Child messages should be predictable. If it isn't due to the nature // of the specific transaction, then the caller should provide a 'min' override gasLimitPercentIncrease: constants.Zero, maxFeePerGasPercentIncrease: DEFAULT_GAS_PRICE_PERCENT_INCREASE, } -export class L1ToL2MessageGasEstimator { - constructor(public readonly l2Provider: Provider) {} +export class ParentToChildMessageGasEstimator { + constructor(public readonly childProvider: Provider) {} private percentIncrease(num: BigNumber, increase: BigNumber): BigNumber { return num.add(num.mul(increase).div(100)) @@ -84,7 +84,7 @@ export class L1ToL2MessageGasEstimator { base: maxSubmissionFeeOptions?.base, percentIncrease: maxSubmissionFeeOptions?.percentIncrease || - defaultL1ToL2MessageEstimateOptions.maxSubmissionFeePercentIncrease, + defaultParentToChildMessageEstimateOptions.maxSubmissionFeePercentIncrease, } } @@ -93,7 +93,7 @@ export class L1ToL2MessageGasEstimator { base: maxFeePerGasOptions?.base, percentIncrease: maxFeePerGasOptions?.percentIncrease || - defaultL1ToL2MessageEstimateOptions.maxFeePerGasPercentIncrease, + defaultParentToChildMessageEstimateOptions.maxFeePerGasPercentIncrease, } } @@ -104,39 +104,45 @@ export class L1ToL2MessageGasEstimator { base: gasLimitDefaults?.base, percentIncrease: gasLimitDefaults?.percentIncrease || - defaultL1ToL2MessageEstimateOptions.gasLimitPercentIncrease, + defaultParentToChildMessageEstimateOptions.gasLimitPercentIncrease, min: gasLimitDefaults?.min || constants.Zero, } } /** * Return the fee, in wei, of submitting a new retryable tx with a given calldata size. - * @param l1Provider - * @param l1BaseFee + * @param parentProvider + * @param parentBaseFee * @param callDataSize * @param options * @returns */ public async estimateSubmissionFee( - l1Provider: Provider, - l1BaseFee: BigNumber, + parentProvider: Provider, + parentBaseFee: BigNumber, callDataSize: BigNumber | number, options?: PercentIncrease - ): Promise { + ): Promise { const defaultedOptions = this.applySubmissionPriceDefaults(options) - const network = await getL2Network(this.l2Provider) - const inbox = Inbox__factory.connect(network.ethBridge.inbox, l1Provider) + const network = await getArbitrumNetwork(this.childProvider) + const inbox = Inbox__factory.connect( + network.ethBridge.inbox, + parentProvider + ) return this.percentIncrease( defaultedOptions.base || - (await inbox.calculateRetryableSubmissionFee(callDataSize, l1BaseFee)), + (await inbox.calculateRetryableSubmissionFee( + callDataSize, + parentBaseFee + )), defaultedOptions.percentIncrease ) } /** - * Estimate the amount of L2 gas required for putting the transaction in the L2 inbox, and executing it. + * Estimate the amount of child chain gas required for putting the transaction in the L2 inbox, and executing it. * @param retryableData object containing retryable ticket data * @param senderDeposit we dont know how much gas the transaction will use when executing * so by default we supply a dummy amount of call value that will definately be more than we need @@ -150,12 +156,12 @@ export class L1ToL2MessageGasEstimator { excessFeeRefundAddress, callValueRefundAddress, data, - }: L1ToL2MessageNoGasParams, + }: ParentToChildMessageNoGasParams, senderDeposit: BigNumber = utils.parseEther('1').add(l2CallValue) - ): Promise { + ): Promise { const nodeInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - this.l2Provider + this.childProvider ) return await nodeInterface.estimateGas.estimateRetryableTicket( @@ -170,18 +176,18 @@ export class L1ToL2MessageGasEstimator { } /** - * Provides an estimate for the L2 maxFeePerGas, adding some margin to allow for gas price variation + * Provides an estimate for the child chain maxFeePerGas, adding some margin to allow for gas price variation * @param options * @returns */ public async estimateMaxFeePerGas( options?: PercentIncrease - ): Promise { + ): Promise { const maxFeePerGasDefaults = this.applyMaxFeePerGasDefaults(options) // estimate the l2 gas price return this.percentIncrease( - maxFeePerGasDefaults.base || (await this.l2Provider.getGasPrice()), + maxFeePerGasDefaults.base || (await this.childProvider.getGasPrice()), maxFeePerGasDefaults.percentIncrease ) } @@ -193,8 +199,8 @@ export class L1ToL2MessageGasEstimator { * @returns */ public static async isValid( - estimates: L1ToL2MessageGasParams, - reEstimates: L1ToL2MessageGasParams + estimates: ParentToChildMessageGasParams, + reEstimates: ParentToChildMessageGasParams ): Promise { // L2 base fee and minimum submission cost which affect the success of the tx return ( @@ -204,29 +210,29 @@ export class L1ToL2MessageGasEstimator { } /** - * Get gas limit, gas price and submission price estimates for sending an L1->L2 message + * Get gas limit, gas price and submission price estimates for sending a Parent->Child message * @param retryableData Data of retryable ticket transaction - * @param l1BaseFee Current l1 base fee - * @param l1Provider + * @param parentBaseFee Current parent chain base fee + * @param parentProvider * @param options * @returns */ public async estimateAll( - retryableEstimateData: L1ToL2MessageNoGasParams, - l1BaseFee: BigNumber, - l1Provider: Provider, + retryableEstimateData: ParentToChildMessageNoGasParams, + parentBaseFee: BigNumber, + parentProvider: Provider, options?: GasOverrides - ): Promise { + ): Promise { const { data } = retryableEstimateData const gasLimitDefaults = this.applyGasLimitDefaults(options?.gasLimit) - // estimate the l1 gas price + // estimate the parent chain gas price const maxFeePerGasPromise = this.estimateMaxFeePerGas(options?.maxFeePerGas) // estimate the submission fee const maxSubmissionFeePromise = this.estimateSubmissionFee( - l1Provider, - l1BaseFee, + parentProvider, + parentBaseFee, utils.hexDataLength(data), options?.maxSubmissionFee ) @@ -268,26 +274,26 @@ export class L1ToL2MessageGasEstimator { } /** - * Transactions that make an L1->L2 message need to estimate L2 gas parameters + * Transactions that make a Parent->Child message need to estimate L2 gas parameters * This function does that, and populates those parameters into a transaction request * @param dataFunc - * @param l1Provider + * @param parentProvider * @param gasOverrides * @returns */ public async populateFunctionParams( /** - * Function that will internally make an L1->L2 transaction + * Function that will internally make a Parent->Child transaction * Will initially be called with dummy values to trigger a special revert containing * the real params. Then called again with the real params to form the final data to be submitted */ dataFunc: ( - params: OmitTyped - ) => L1ToL2TransactionRequest['txRequest'], - l1Provider: Provider, + params: OmitTyped + ) => ParentToChildTransactionRequest['txRequest'], + parentProvider: Provider, gasOverrides?: GasOverrides ): Promise<{ - estimates: L1ToL2MessageGasParams + estimates: ParentToChildMessageGasParams retryable: RetryableData data: BytesLike to: string @@ -308,7 +314,7 @@ export class L1ToL2MessageGasEstimator { let retryable: RetryableData | null try { // get retryable data from the null call - const res = await l1Provider.call({ + const res = await parentProvider.call({ to: to, data: nullData, value: value, @@ -329,8 +335,7 @@ export class L1ToL2MessageGasEstimator { } // use retryable data to get gas estimates - // const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider) - const baseFee = await getBaseFee(l1Provider) + const baseFee = await getBaseFee(parentProvider) const estimates = await this.estimateAll( { from: retryable.from, @@ -341,7 +346,7 @@ export class L1ToL2MessageGasEstimator { callValueRefundAddress: retryable.callValueRefundAddress, }, baseFee, - l1Provider, + parentProvider, gasOverrides ) diff --git a/src/lib/message/L1Transaction.ts b/src/lib/message/ParentTransaction.ts similarity index 66% rename from src/lib/message/L1Transaction.ts rename to src/lib/message/ParentTransaction.ts index d2b79b3e4b..2d1a864aff 100644 --- a/src/lib/message/L1Transaction.ts +++ b/src/lib/message/ParentTransaction.ts @@ -21,16 +21,16 @@ import { Log, Provider } from '@ethersproject/abstract-provider' import { ContractTransaction } from '@ethersproject/contracts' import { BigNumber } from '@ethersproject/bignumber' import { - L1ToL2Message, - L1ToL2MessageReaderOrWriter, - L1ToL2MessageReader, - L1ToL2MessageReaderClassic, - L1ToL2MessageWriter, - L1ToL2MessageStatus, - L1ToL2MessageWaitResult, + ParentToChildMessage, + ParentToChildMessageReaderOrWriter, + ParentToChildMessageReader, + ParentToChildMessageReaderClassic, + ParentToChildMessageWriter, + ParentToChildMessageStatus, + ParentToChildMessageWaitResult, EthDepositMessage, EthDepositMessageWaitResult, -} from './L1ToL2Message' +} from './ParentToChildMessage' import { L1ERC20Gateway__factory } from '../abi/factories/L1ERC20Gateway__factory' import { @@ -46,20 +46,21 @@ import { MessageDeliveredEvent } from '../abi/Bridge' import { EventArgs, parseTypedLogs } from '../dataEntities/event' import { isDefined } from '../utils/lib' import { SubmitRetryableMessageDataParser } from './messageDataParser' -import { getL2Network } from '../dataEntities/networks' +import { getArbitrumNetwork } from '../dataEntities/networks' +import { ARB1_NITRO_GENESIS_L1_BLOCK } from '../dataEntities/constants' -export interface L1ContractTransaction< - TReceipt extends L1TransactionReceipt = L1TransactionReceipt +export interface ParentContractTransaction< + TReceipt extends ParentTransactionReceipt = ParentTransactionReceipt > extends ContractTransaction { wait(confirmations?: number): Promise } // some helper interfaces to reduce the verbosity elsewhere -export type L1EthDepositTransaction = - L1ContractTransaction -export type L1ContractCallTransaction = - L1ContractTransaction +export type ParentEthDepositTransaction = + ParentContractTransaction +export type ParentContractCallTransaction = + ParentContractTransaction -export class L1TransactionReceipt implements TransactionReceipt { +export class ParentTransactionReceipt implements TransactionReceipt { public readonly to: string public readonly from: string public readonly contractAddress: string @@ -100,14 +101,22 @@ export class L1TransactionReceipt implements TransactionReceipt { /** * Check if is a classic transaction - * @param l2SignerOrProvider + * @param childSignerOrProvider */ public async isClassic( - l2SignerOrProvider: T + childSignerOrProvider: T ): Promise { - const provider = SignerProviderUtils.getProviderOrThrow(l2SignerOrProvider) - const network = await getL2Network(provider) - return this.blockNumber < network.nitroGenesisL1Block + const provider = SignerProviderUtils.getProviderOrThrow( + childSignerOrProvider + ) + const network = await getArbitrumNetwork(provider) + + // all networks except Arbitrum One started off with Nitro + if (network.chainId === 42161) { + return this.blockNumber < ARB1_NITRO_GENESIS_L1_BLOCK + } + + return false } /** @@ -177,10 +186,10 @@ export class L1TransactionReceipt implements TransactionReceipt { /** * Get any eth deposit messages created by this transaction - * @param l2SignerOrProvider + * @param childSignerOrProvider */ public async getEthDeposits( - l2Provider: Provider + childProvider: Provider ): Promise { return Promise.all( this.getMessageEvents() @@ -191,7 +200,7 @@ export class L1TransactionReceipt implements TransactionReceipt { ) .map(m => EthDepositMessage.fromEventComponents( - l2Provider, + childProvider, m.inboxMessageEvent.messageNum, m.bridgeMessageEvent.sender, m.inboxMessageEvent.data @@ -201,20 +210,20 @@ export class L1TransactionReceipt implements TransactionReceipt { } /** - * Get classic l1tol2 messages created by this transaction - * @param l2Provider + * Get classic parent-to-child messages created by this transaction + * @param childProvider */ - public async getL1ToL2MessagesClassic( - l2Provider: Provider - ): Promise { - const network = await getL2Network(l2Provider) - const chainID = network.chainID.toString() - const isClassic = await this.isClassic(l2Provider) + public async getParentToChildMessagesClassic( + childProvider: Provider + ): Promise { + const network = await getArbitrumNetwork(childProvider) + const chainId = network.chainId.toString() + const isClassic = await this.isClassic(childProvider) // throw on nitro events if (!isClassic) { throw new Error( - "This method is only for classic transactions. Use 'getL1ToL2Messages' for nitro transactions." + "This method is only for classic transactions. Use 'getParentToChildMessages' for nitro transactions." ) } @@ -224,33 +233,35 @@ export class L1TransactionReceipt implements TransactionReceipt { return messageNums.map( messageNum => - new L1ToL2MessageReaderClassic( - l2Provider, - BigNumber.from(chainID).toNumber(), + new ParentToChildMessageReaderClassic( + childProvider, + BigNumber.from(chainId).toNumber(), messageNum ) ) } /** - * Get any l1tol2 messages created by this transaction - * @param l2SignerOrProvider + * Get any parent-to-child messages created by this transaction + * @param childSignerOrProvider */ - public async getL1ToL2Messages( - l2SignerOrProvider: T - ): Promise[]> - public async getL1ToL2Messages( - l2SignerOrProvider: T - ): Promise { - const provider = SignerProviderUtils.getProviderOrThrow(l2SignerOrProvider) - const network = await getL2Network(provider) - const chainID = network.chainID.toString() + public async getParentToChildMessages( + childSignerOrProvider: T + ): Promise[]> + public async getParentToChildMessages( + childSignerOrProvider: T + ): Promise { + const provider = SignerProviderUtils.getProviderOrThrow( + childSignerOrProvider + ) + const network = await getArbitrumNetwork(provider) + const chainId = network.chainId.toString() const isClassic = await this.isClassic(provider) // throw on classic events if (isClassic) { throw new Error( - "This method is only for nitro transactions. Use 'getL1ToL2MessagesClassic' for classic transactions." + "This method is only for nitro transactions. Use 'getParentToChildMessagesClassic' for classic transactions." ) } @@ -269,9 +280,9 @@ export class L1TransactionReceipt implements TransactionReceipt { mn.inboxMessageEvent.data ) - return L1ToL2Message.fromEventComponents( - l2SignerOrProvider, - BigNumber.from(chainID).toNumber(), + return ParentToChildMessage.fromEventComponents( + childSignerOrProvider, + BigNumber.from(chainId).toNumber(), mn.bridgeMessageEvent.sender, mn.inboxMessageEvent.messageNum, mn.bridgeMessageEvent.baseFeeL1, @@ -293,70 +304,70 @@ export class L1TransactionReceipt implements TransactionReceipt { } /** - * Replaces the wait function with one that returns an L1TransactionReceipt + * Replaces the wait function with one that returns a {@link ParentTransactionReceipt} * @param contractTransaction * @returns */ public static monkeyPatchWait = ( contractTransaction: ContractTransaction - ): L1ContractTransaction => { + ): ParentContractTransaction => { const wait = contractTransaction.wait contractTransaction.wait = async (confirmations?: number) => { const result = await wait(confirmations) - return new L1TransactionReceipt(result) + return new ParentTransactionReceipt(result) } - return contractTransaction as L1ContractTransaction + return contractTransaction as ParentContractTransaction } /** - * Replaces the wait function with one that returns an L1EthDepositTransactionReceipt + * Replaces the wait function with one that returns a {@link ParentEthDepositTransactionReceipt} * @param contractTransaction * @returns */ public static monkeyPatchEthDepositWait = ( contractTransaction: ContractTransaction - ): L1EthDepositTransaction => { + ): ParentEthDepositTransaction => { const wait = contractTransaction.wait contractTransaction.wait = async (confirmations?: number) => { const result = await wait(confirmations) - return new L1EthDepositTransactionReceipt(result) + return new ParentEthDepositTransactionReceipt(result) } - return contractTransaction as L1EthDepositTransaction + return contractTransaction as ParentEthDepositTransaction } /** - * Replaces the wait function with one that returns an L1ContractCallTransactionReceipt + * Replaces the wait function with one that returns a {@link ParentContractCallTransactionReceipt} * @param contractTransaction * @returns */ public static monkeyPatchContractCallWait = ( contractTransaction: ContractTransaction - ): L1ContractCallTransaction => { + ): ParentContractCallTransaction => { const wait = contractTransaction.wait contractTransaction.wait = async (confirmations?: number) => { const result = await wait(confirmations) - return new L1ContractCallTransactionReceipt(result) + return new ParentContractCallTransactionReceipt(result) } - return contractTransaction as L1ContractCallTransaction + return contractTransaction as ParentContractCallTransaction } } /** - * An L1TransactionReceipt with additional functionality that only exists + * A {@link ParentTransactionReceipt} with additional functionality that only exists * if the transaction created a single eth deposit. */ -export class L1EthDepositTransactionReceipt extends L1TransactionReceipt { +export class ParentEthDepositTransactionReceipt extends ParentTransactionReceipt { /** - * Wait for the funds to arrive on L2 + * Wait for the funds to arrive on the child chain * @param confirmations Amount of confirmations the retryable ticket and the auto redeem receipt should have * @param timeout Amount of time to wait for the retryable ticket to be created - * Defaults to 15 minutes, as by this time all transactions are expected to be included on L2. Throws on timeout. - * @returns The wait result contains `complete`, a `status`, the L1ToL2Message and optionally the `l2TxReceipt` + * Defaults to 15 minutes, as by this time all transactions are expected to be included on the child chain. Throws on timeout. + * @returns The wait result contains `complete`, a `status`, the ParentToChildMessage and optionally the `childChainTxReceipt` * If `complete` is true then this message is in the terminal state. * For eth deposits complete this is when the status is FUNDS_DEPOSITED, EXPIRED or REDEEMED. */ - public async waitForL2( - l2Provider: Provider, + public async waitForChildTx( + childProvider: Provider, confirmations?: number, timeout?: number ): Promise< @@ -365,50 +376,53 @@ export class L1EthDepositTransactionReceipt extends L1TransactionReceipt { message: EthDepositMessage } & EthDepositMessageWaitResult > { - const message = (await this.getEthDeposits(l2Provider))[0] + const message = (await this.getEthDeposits(childProvider))[0] if (!message) throw new ArbSdkError('Unexpected missing Eth Deposit message.') const res = await message.wait(confirmations, timeout) return { complete: isDefined(res), - l2TxReceipt: res, + chainTxReceipt: res, message, } } } /** - * An L1TransactionReceipt with additional functionality that only exists - * if the transaction created a single call to an L2 contract - this includes + * A {@link ParentTransactionReceipt} with additional functionality that only exists + * if the transaction created a single call to a child chain contract - this includes * token deposits. */ -export class L1ContractCallTransactionReceipt extends L1TransactionReceipt { +export class ParentContractCallTransactionReceipt extends ParentTransactionReceipt { /** - * Wait for the transaction to arrive and be executed on L2 + * Wait for the transaction to arrive and be executed on the child chain * @param confirmations Amount of confirmations the retryable ticket and the auto redeem receipt should have * @param timeout Amount of time to wait for the retryable ticket to be created - * Defaults to 15 minutes, as by this time all transactions are expected to be included on L2. Throws on timeout. - * @returns The wait result contains `complete`, a `status`, an L1ToL2Message and optionally the `l2TxReceipt`. + * Defaults to 15 minutes, as by this time all transactions are expected to be included on the child chain. Throws on timeout. + * @returns The wait result contains `complete`, a `status`, a {@link ParentToChildMessage} and optionally the `childChainTxReceipt`. * If `complete` is true then this message is in the terminal state. * For contract calls this is true only if the status is REDEEMED. */ - public async waitForL2( - l2SignerOrProvider: T, + public async waitForChildTx( + childSignerOrProvider: T, confirmations?: number, timeout?: number ): Promise< { complete: boolean - message: L1ToL2MessageReaderOrWriter - } & L1ToL2MessageWaitResult + message: ParentToChildMessageReaderOrWriter + } & ParentToChildMessageWaitResult > { - const message = (await this.getL1ToL2Messages(l2SignerOrProvider))[0] - if (!message) throw new ArbSdkError('Unexpected missing L1ToL2 message.') + const message = ( + await this.getParentToChildMessages(childSignerOrProvider) + )[0] + if (!message) + throw new ArbSdkError('Unexpected missing Parent-to-child message.') const res = await message.waitForStatus(confirmations, timeout) return { - complete: res.status === L1ToL2MessageStatus.REDEEMED, + complete: res.status === ParentToChildMessageStatus.REDEEMED, ...res, message, } diff --git a/src/lib/utils/lib.ts b/src/lib/utils/lib.ts index b53c6f6f29..a195ed23a2 100644 --- a/src/lib/utils/lib.ts +++ b/src/lib/utils/lib.ts @@ -2,9 +2,9 @@ import { Provider } from '@ethersproject/abstract-provider' import { TransactionReceipt, JsonRpcProvider } from '@ethersproject/providers' import { ArbSdkError } from '../dataEntities/errors' import { ArbitrumProvider } from './arbProvider' -import { l2Networks } from '../dataEntities/networks' import { ArbSys__factory } from '../abi/factories/ArbSys__factory' import { ARB_SYS_ADDRESS } from '../dataEntities/constants' +import { getNitroGenesisBlock } from '../dataEntities/networks' import { BigNumber } from 'ethers' export const wait = (ms: number): Promise => @@ -82,7 +82,7 @@ type GetFirstBlockForL1BlockProps = { * @param {JsonRpcProvider} provider - The L2 provider to use for the search. * @param {number} forL1Block - The L1 block number to search for. * @param {boolean} [allowGreater=false] - Whether to allow the search to go past the specified `forL1Block`. - * @param {number|string} minL2Block - The minimum L2 block number to start the search from. Cannot be below the network's `nitroGenesisBlock`. + * @param {number|string} minL2Block - The minimum L2 block number to start the search from. Cannot be below the network's Nitro genesis block. * @param {number|string} [maxL2Block='latest'] - The maximum L2 block number to end the search at. Can be a `number` or `'latest'`. `'latest'` is the current block. * @returns {Promise} - A Promise that resolves to a number if a block is found, or undefined otherwise. */ @@ -101,7 +101,7 @@ export async function getFirstBlockForL1Block({ const arbProvider = new ArbitrumProvider(provider) const currentArbBlock = await arbProvider.getBlockNumber() const arbitrumChainId = (await arbProvider.getNetwork()).chainId - const { nitroGenesisBlock } = l2Networks[arbitrumChainId] + const nitroGenesisBlock = getNitroGenesisBlock(arbitrumChainId) async function getL1Block(forL2Block: number) { const { l1BlockNumber } = await arbProvider.getBlock(forL2Block) @@ -124,7 +124,7 @@ export async function getFirstBlockForL1Block({ if (minL2Block < nitroGenesisBlock) { throw new Error( - `'minL2Block' (${minL2Block}) cannot be below 'nitroGenesisBlock', which is ${nitroGenesisBlock} for the current network.` + `'minL2Block' (${minL2Block}) cannot be below the Nitro genesis block, which is ${nitroGenesisBlock} for the current network.` ) } diff --git a/src/lib/utils/multicall.ts b/src/lib/utils/multicall.ts index 187b170c1f..9ca4b1996f 100644 --- a/src/lib/utils/multicall.ts +++ b/src/lib/utils/multicall.ts @@ -22,14 +22,7 @@ import { BigNumber, utils } from 'ethers' import { ERC20__factory } from '../abi/factories/ERC20__factory' import { Multicall2 } from '../abi/Multicall2' import { Multicall2__factory } from '../abi/factories/Multicall2__factory' -import { ArbSdkError } from '../dataEntities/errors' -import { - isL1Network, - L1Network, - l1Networks, - L2Network, - l2Networks, -} from '../dataEntities/networks' +import { getMulticallAddress } from '../dataEntities/networks' /** * Input to multicall aggregator @@ -130,30 +123,7 @@ export class MultiCaller { * @returns */ public static async fromProvider(provider: Provider): Promise { - const chainId = (await provider.getNetwork()).chainId - const l2Network = l2Networks[chainId] as L2Network | undefined - const l1Network = l1Networks[chainId] as L1Network | undefined - - const network = l2Network || l1Network - if (!network) { - throw new ArbSdkError( - `Unexpected network id: ${chainId}. Ensure that chain ${chainId} has been added as a network.` - ) - } - - let multiCallAddr: string - if (isL1Network(network)) { - const firstL2 = l2Networks[network.partnerChainIDs[0]] - if (!firstL2) - throw new ArbSdkError( - `No partner chain found l1 network: ${network.chainID} : partner chain ids ${network.partnerChainIDs}` - ) - multiCallAddr = firstL2.tokenBridge.l1MultiCall - } else { - multiCallAddr = network.tokenBridge.l2Multicall - } - - return new MultiCaller(provider, multiCallAddr) + return new MultiCaller(provider, await getMulticallAddress(provider)) } /** diff --git a/src/lib/utils/types.ts b/src/lib/utils/types.ts index dc0707ea5b..48d60a6a2e 100644 --- a/src/lib/utils/types.ts +++ b/src/lib/utils/types.ts @@ -14,3 +14,9 @@ export type PartialPick = OmitTyped & Partial * Make the specified properties required */ export type RequiredPick = Required> & T + +// https://twitter.com/mattpocockuk/status/1622730173446557697 +export type Prettify = { + [K in keyof T]: T[K] + // eslint-disable-next-line @typescript-eslint/ban-types +} & {} diff --git a/tests/fork/inbox.test.ts b/tests/fork/inbox.test.ts index d91c238ee2..fd3df901be 100644 --- a/tests/fork/inbox.test.ts +++ b/tests/fork/inbox.test.ts @@ -30,7 +30,10 @@ import { InboxTools } from '../../src' import { ethers, network } from 'hardhat' import { hexZeroPad } from '@ethersproject/bytes' -import { L2Network, getL2Network } from '../../src/lib/dataEntities/networks' +import { + ArbitrumNetwork, + getArbitrumNetwork, +} from '../../src/lib/dataEntities/networks' import { solidityKeccak256 } from 'ethers/lib/utils' import { ContractTransaction, Signer } from 'ethers' @@ -43,7 +46,7 @@ const submitL2Tx = async ( maxFeePerGas: BigNumber gasLimit: BigNumber }, - l2Network: L2Network, + l2Network: ArbitrumNetwork, l1Signer: Signer ): Promise => { const inbox = Inbox__factory.connect(l2Network.ethBridge.inbox, l1Signer) @@ -64,7 +67,7 @@ describe('Inbox tools', () => { const signer = signers[0] const provider = signer.provider! - const arbitrumOne = await getL2Network(42161) + const arbitrumOne = await getArbitrumNetwork(42161) const sequencerInbox = SequencerInbox__factory.connect( arbitrumOne.ethBridge.sequencerInbox, diff --git a/tests/integration/l2TransactionReceipt.test.ts b/tests/integration/childTransactionReceipt.test.ts similarity index 78% rename from tests/integration/l2TransactionReceipt.test.ts rename to tests/integration/childTransactionReceipt.test.ts index c043a069f0..b5fc17372e 100644 --- a/tests/integration/l2TransactionReceipt.test.ts +++ b/tests/integration/childTransactionReceipt.test.ts @@ -19,13 +19,13 @@ import { expect } from 'chai' import { - fundL1, - fundL2, + fundParentSigner, + fundChildSigner, mineUntilStop, skipIfMainnet, wait, } from './testHelpers' -import { L2TransactionReceipt } from '../../src' +import { ChildTransactionReceipt } from '../../src' import { JsonRpcProvider } from '@ethersproject/providers' import { BigNumber, Wallet } from 'ethers' import { parseEther } from 'ethers/lib/utils' @@ -37,24 +37,24 @@ describe('ArbProvider', () => { }) it('does find l1 batch info', async () => { - const { l2Signer, l1Signer } = await testSetup() - const l2Provider = l2Signer.provider! as JsonRpcProvider + const { childSigner, parentSigner } = await testSetup() + const l2Provider = childSigner.provider! as JsonRpcProvider // set up miners - const miner1 = Wallet.createRandom().connect(l1Signer.provider!) - const miner2 = Wallet.createRandom().connect(l2Signer.provider!) - await fundL1(miner1, parseEther('0.1')) - await fundL2(miner2, parseEther('0.1')) + const miner1 = Wallet.createRandom().connect(parentSigner.provider!) + const miner2 = Wallet.createRandom().connect(childSigner.provider!) + await fundParentSigner(miner1, parseEther('0.1')) + await fundChildSigner(miner2, parseEther('0.1')) const state = { mining: true } mineUntilStop(miner1, state) mineUntilStop(miner2, state) - await fundL2(l2Signer) + await fundChildSigner(childSigner) const randomAddress = Wallet.createRandom().address const amountToSend = parseEther('0.000005') // send an l2 transaction, and get the receipt - const tx = await l2Signer.sendTransaction({ + const tx = await childSigner.sendTransaction({ to: randomAddress, value: amountToSend, }) @@ -64,7 +64,7 @@ describe('ArbProvider', () => { // eslint-disable-next-line no-constant-condition while (true) { await wait(300) - const arbTxReceipt = new L2TransactionReceipt(rec) + const arbTxReceipt = new ChildTransactionReceipt(rec) const l1BatchNumber = ( await arbTxReceipt.getBatchNumber(l2Provider).catch(() => { diff --git a/tests/integration/custom-fee-token/customFeeTokenEthBridger.test.ts b/tests/integration/custom-fee-token/customFeeTokenEthBridger.test.ts index 0e5593bee8..4ce32939ad 100644 --- a/tests/integration/custom-fee-token/customFeeTokenEthBridger.test.ts +++ b/tests/integration/custom-fee-token/customFeeTokenEthBridger.test.ts @@ -23,13 +23,15 @@ import dotenv from 'dotenv' import { parseEther } from '@ethersproject/units' import { - fundL1 as fundL1Ether, + fundParentSigner as fundParentSignerEther, mineUntilStop, skipIfMainnet, wait, } from '../testHelpers' -import { L2ToL1Message, L2ToL1MessageStatus } from '../../../src' + import { describeOnlyWhenCustomGasToken } from './mochaExtensions' +import { ChildToParentMessageStatus } from '../../../src' +import { ChildToParentMessage } from '../../../src/lib/message/ChildToParentMessage' dotenv.config() @@ -38,9 +40,9 @@ describeOnlyWhenCustomGasToken( async () => { const { testSetup, - fundL1CustomFeeToken, - fundL2CustomFeeToken, - approveL1CustomFeeToken, + fundParentCustomFeeToken, + fundChildCustomFeeToken, + approveParentCustomFeeToken, } = await import('./customFeeTokenTestHelpers') beforeEach('skipIfMainnet', async function () { @@ -48,21 +50,22 @@ describeOnlyWhenCustomGasToken( }) it('approves the custom fee token to be spent by the Inbox on the parent chain (arbitrary amount, using params)', async function () { - const { ethBridger, nativeTokenContract, l1Signer } = await testSetup() + const { ethBridger, nativeTokenContract, parentSigner } = + await testSetup() const amount = ethers.utils.parseEther('1') - await fundL1Ether(l1Signer) - await fundL1CustomFeeToken(l1Signer) + await fundParentSignerEther(parentSigner) + await fundParentCustomFeeToken(parentSigner) const approvalTx = await ethBridger.approveGasToken({ amount, - l1Signer, + parentSigner, }) await approvalTx.wait() const allowance = await nativeTokenContract.allowance( - await l1Signer.getAddress(), - ethBridger.l2Network.ethBridge.inbox + await parentSigner.getAddress(), + ethBridger.childChain.ethBridge.inbox ) expect(allowance.toString()).to.equal( @@ -72,20 +75,21 @@ describeOnlyWhenCustomGasToken( }) it('approves the custom fee token to be spent by the Inbox on the parent chain (max amount, using tx request)', async function () { - const { ethBridger, nativeTokenContract, l1Signer } = await testSetup() + const { ethBridger, nativeTokenContract, parentSigner } = + await testSetup() - await fundL1Ether(l1Signer) - await fundL1CustomFeeToken(l1Signer) + await fundParentSignerEther(parentSigner) + await fundParentCustomFeeToken(parentSigner) const approvalTx = await ethBridger.approveGasToken({ txRequest: ethBridger.getApproveGasTokenRequest(), - l1Signer, + parentSigner, }) await approvalTx.wait() const allowance = await nativeTokenContract.allowance( - await l1Signer.getAddress(), - ethBridger.l2Network.ethBridge.inbox + await parentSigner.getAddress(), + ethBridger.childChain.ethBridge.inbox ) expect(allowance.toString()).to.equal( @@ -98,24 +102,24 @@ describeOnlyWhenCustomGasToken( const { ethBridger, nativeTokenContract, - l1Signer, - l2Signer, - l2Provider, + parentSigner, + childSigner, + childProvider, } = await testSetup() - const bridge = ethBridger.l2Network.ethBridge.bridge + const bridge = ethBridger.childChain.ethBridge.bridge const amount = parseEther('2') - await fundL1Ether(l1Signer) - await fundL1CustomFeeToken(l1Signer) - await approveL1CustomFeeToken(l1Signer) + await fundParentSignerEther(parentSigner) + await fundParentCustomFeeToken(parentSigner) + await approveParentCustomFeeToken(parentSigner) const initialBalanceBridge = await nativeTokenContract.balanceOf(bridge) - const initialBalanceDepositor = await l2Signer.getBalance() + const initialBalanceDepositor = await childSigner.getBalance() // perform the deposit const depositTx = await ethBridger.deposit({ amount, - l1Signer, + parentSigner, }) const depositTxReceipt = await depositTx.wait() expect(depositTxReceipt.status).to.equal(1, 'deposit tx failed') @@ -133,20 +137,22 @@ describeOnlyWhenCustomGasToken( await wait(30 * 1000) // check for cross-chain messages - const depositMessages = await depositTxReceipt.getEthDeposits(l2Provider) + const depositMessages = await depositTxReceipt.getEthDeposits( + childProvider + ) expect(depositMessages.length).to.equal( 1, 'failed to find deposit message' ) const [depositMessage] = depositMessages expect(depositMessage.value.toString()).to.equal(amount.toString()) - expect(depositMessage.to).to.equal(await l2Signer.getAddress()) + expect(depositMessage.to).to.equal(await childSigner.getAddress()) expect( // balance in the depositor account after the deposit - (await l2Signer.getBalance()).toString() + (await childSigner.getBalance()).toString() ).to.equal( - // balance in the depositor account after the deposit should equal to the initial balance in th depositor account + the amount deposited + // balance in the depositor account after the deposit should equal to the initial balance in the depositor account + the amount deposited initialBalanceDepositor.add(amount).toString(), 'incorrect balance in depositor account after deposit' ) @@ -154,21 +160,21 @@ describeOnlyWhenCustomGasToken( it('withdraws custom fee token', async () => { const { - l1Signer, - l1Provider, - l2Signer, - l2Provider, + parentSigner, + parentProvider, + childSigner, + childProvider, ethBridger, nativeTokenContract, } = await testSetup() - const bridge = ethBridger.l2Network.ethBridge.bridge + const bridge = ethBridger.childChain.ethBridge.bridge const amount = parseEther('0.2') - await fundL1Ether(l1Signer) - await fundL2CustomFeeToken(l2Signer) + await fundParentSignerEther(parentSigner) + await fundChildCustomFeeToken(childSigner) - const from = await l2Signer.getAddress() - const destinationAddress = await l1Signer.getAddress() + const from = await childSigner.getAddress() + const destinationAddress = await parentSigner.getAddress() const initialBalanceBridge = await nativeTokenContract.balanceOf(bridge) const initialBalanceDestination = await nativeTokenContract.balanceOf( @@ -181,13 +187,13 @@ describeOnlyWhenCustomGasToken( from, }) - const l1GasEstimate = await withdrawalTxRequest.estimateL1GasLimit( - l1Provider + const l1GasEstimate = await withdrawalTxRequest.estimateParentGasLimit( + parentProvider ) const withdrawalTx = await ethBridger.withdraw({ amount, - l2Signer: l2Signer, + childSigner, destinationAddress, from, }) @@ -198,18 +204,21 @@ describeOnlyWhenCustomGasToken( 'initiate withdrawal tx failed' ) - const messages = await withdrawalTxReceipt.getL2ToL1Messages(l1Signer) + const messages = await withdrawalTxReceipt.getChildToParentMessages( + parentSigner + ) expect(messages.length).to.equal( 1, 'custom fee token withdraw getWithdrawalsInL2Transaction query came back empty' ) - const withdrawalEvents = await L2ToL1Message.getL2ToL1Events( - l2Provider, - { fromBlock: withdrawalTxReceipt.blockNumber, toBlock: 'latest' }, - undefined, - destinationAddress - ) + const withdrawalEvents = + await ChildToParentMessage.getChildToParentEvents( + childProvider, + { fromBlock: withdrawalTxReceipt.blockNumber, toBlock: 'latest' }, + undefined, + destinationAddress + ) expect(withdrawalEvents.length).to.equal( 1, @@ -217,30 +226,30 @@ describeOnlyWhenCustomGasToken( ) const [message] = messages - const messageStatus = await message.status(l2Provider) + const messageStatus = await message.status(childProvider) expect( messageStatus, `custom fee token withdraw status returned ${messageStatus}` - ).to.be.eq(L2ToL1MessageStatus.UNCONFIRMED) + ).to.be.eq(ChildToParentMessageStatus.UNCONFIRMED) // run a miner whilst withdrawing - const miner1 = Wallet.createRandom().connect(l1Provider) - const miner2 = Wallet.createRandom().connect(l2Provider) - await fundL1Ether(miner1, parseEther('1')) - await fundL2CustomFeeToken(miner2) + const miner1 = Wallet.createRandom().connect(parentProvider) + const miner2 = Wallet.createRandom().connect(childProvider) + await fundParentSignerEther(miner1, parseEther('1')) + await fundChildCustomFeeToken(miner2) const state = { mining: true } await Promise.race([ mineUntilStop(miner1, state), mineUntilStop(miner2, state), - message.waitUntilReadyToExecute(l2Provider), + message.waitUntilReadyToExecute(childProvider), ]) state.mining = false - expect(await message.status(l2Provider), 'confirmed status') + expect(await message.status(childProvider), 'confirmed status') // - .to.eq(L2ToL1MessageStatus.CONFIRMED) + .to.eq(ChildToParentMessageStatus.CONFIRMED) - const execTx = await message.execute(l2Provider) + const execTx = await message.execute(childProvider) const execTxReceipt = await execTx.wait() expect( @@ -248,9 +257,9 @@ describeOnlyWhenCustomGasToken( 'gas used greater than estimate' ).to.be.lessThan(l1GasEstimate.toNumber()) - expect(await message.status(l2Provider), 'executed status') + expect(await message.status(childProvider), 'executed status') // - .to.eq(L2ToL1MessageStatus.EXECUTED) + .to.eq(ChildToParentMessageStatus.EXECUTED) expect( // balance in the bridge after the withdrawal diff --git a/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts b/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts index 2a9f127e08..89abe126fb 100644 --- a/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts +++ b/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts @@ -14,31 +14,36 @@ const ethProvider = () => new StaticJsonRpcProvider(config.ethUrl) const arbProvider = () => new StaticJsonRpcProvider(config.arbUrl) const localNetworks = () => getLocalNetworksFromFile() -export function isL2NetworkWithCustomFeeToken(): boolean { +export function isArbitrumNetworkWithCustomFeeToken(): boolean { const nt = localNetworks().l2Network.nativeToken return typeof nt !== 'undefined' && nt !== ethers.constants.AddressZero } export async function testSetup() { const result = await _testSetup() - const { l2Network, l1Provider } = result + const { childChain, parentProvider } = result - const nativeToken = l2Network.nativeToken! - const nativeTokenContract = ERC20__factory.connect(nativeToken, l1Provider) + const nativeToken = childChain.nativeToken! + const nativeTokenContract = ERC20__factory.connect( + nativeToken, + parentProvider + ) return { ...result, nativeTokenContract } } -export async function fundL1CustomFeeToken(l1SignerOrAddress: Signer | string) { +export async function fundParentCustomFeeToken( + parentSignerOrAddress: Signer | string +) { const nativeToken = localNetworks().l2Network.nativeToken const address = - typeof l1SignerOrAddress === 'string' - ? l1SignerOrAddress - : await l1SignerOrAddress.getAddress() + typeof parentSignerOrAddress === 'string' + ? parentSignerOrAddress + : await parentSignerOrAddress.getAddress() if (typeof nativeToken === 'undefined') { throw new Error( - `can't call "fundL1CustomFeeToken" for network that uses eth as native token` + `can't call "fundParentCustomFeeToken" for network that uses eth as native token` ) } @@ -53,14 +58,14 @@ export async function fundL1CustomFeeToken(l1SignerOrAddress: Signer | string) { await tx.wait() } -export async function approveL1CustomFeeToken(l1Signer: Signer) { +export async function approveParentCustomFeeToken(parentSigner: Signer) { const ethBridger = await EthBridger.fromProvider(arbProvider()) - const tx = await ethBridger.approveGasToken({ l1Signer }) + const tx = await ethBridger.approveGasToken({ parentSigner }) await tx.wait() } -export async function getL1CustomFeeTokenAllowance( +export async function getParentCustomFeeTokenAllowance( owner: string, spender: string ) { @@ -72,21 +77,24 @@ export async function getL1CustomFeeTokenAllowance( return nativeTokenContract.allowance(owner, spender) } -export async function approveL1CustomFeeTokenForErc20Deposit( - l1Signer: Signer, - erc20L1Address: string +export async function approveParentCustomFeeTokenForErc20Deposit( + parentSigner: Signer, + erc20ParentAddress: string ) { const erc20Bridger = await Erc20Bridger.fromProvider(arbProvider()) - const tx = await erc20Bridger.approveGasToken({ erc20L1Address, l1Signer }) + const tx = await erc20Bridger.approveGasToken({ + erc20ParentAddress: erc20ParentAddress, + parentSigner, + }) await tx.wait() } -export async function fundL2CustomFeeToken(l2Signer: Signer) { +export async function fundChildCustomFeeToken(childSigner: Signer) { const deployerWallet = new Wallet(config.arbKey, arbProvider()) const tx = await deployerWallet.sendTransaction({ - to: await l2Signer.getAddress(), + to: await childSigner.getAddress(), value: utils.parseEther('1'), }) await tx.wait() diff --git a/tests/integration/custom-fee-token/mochaExtensions.ts b/tests/integration/custom-fee-token/mochaExtensions.ts index c882700a6b..a2b8ba2ad2 100644 --- a/tests/integration/custom-fee-token/mochaExtensions.ts +++ b/tests/integration/custom-fee-token/mochaExtensions.ts @@ -1,6 +1,6 @@ -import { isL2NetworkWithCustomFeeToken } from './customFeeTokenTestHelpers' +import { isArbitrumNetworkWithCustomFeeToken } from './customFeeTokenTestHelpers' -const customGasTokenEnvironment = isL2NetworkWithCustomFeeToken() +const customGasTokenEnvironment = isArbitrumNetworkWithCustomFeeToken() /** * Only run when in an eth chain environment diff --git a/tests/integration/customerc20.test.ts b/tests/integration/customerc20.test.ts index 5ae55acb42..eb6b024b7e 100644 --- a/tests/integration/customerc20.test.ts +++ b/tests/integration/customerc20.test.ts @@ -31,20 +31,21 @@ import { TestCustomTokenL1 } from '../../src/lib/abi/TestCustomTokenL1' import { TestCustomTokenL1__factory } from '../../src/lib/abi/factories/TestCustomTokenL1__factory' import { - fundL1, - fundL2, + fundParentSigner, + fundChildSigner, skipIfMainnet, depositToken, GatewayType, withdrawToken, } from './testHelpers' -import { L1ToL2MessageStatus, L2Network } from '../../src' +import { ParentToChildMessageStatus } from '../../src' +import { ArbitrumNetwork } from '../../src/lib/dataEntities/networks' import { AdminErc20Bridger } from '../../src/lib/assetBridger/erc20Bridger' import { testSetup } from '../../scripts/testSetup' import { ERC20__factory } from '../../src/lib/abi/factories/ERC20__factory' import { - fundL1CustomFeeToken, - isL2NetworkWithCustomFeeToken, + fundParentCustomFeeToken, + isArbitrumNetworkWithCustomFeeToken, } from './custom-fee-token/customFeeTokenTestHelpers' const depositAmount = BigNumber.from(100) @@ -57,10 +58,10 @@ describe('Custom ERC20', () => { // test globals let testState: { - l1Signer: Signer - l2Signer: Signer + parentSigner: Signer + childSigner: Signer adminErc20Bridger: AdminErc20Bridger - l2Network: L2Network + childChain: ArbitrumNetwork l1CustomToken: TestCustomTokenL1 | TestOrbitCustomTokenL1 } @@ -69,19 +70,19 @@ describe('Custom ERC20', () => { ...(await testSetup()), l1CustomToken: {} as any, } - await fundL1(testState.l1Signer) - await fundL2(testState.l2Signer) + await fundParentSigner(testState.parentSigner) + await fundChildSigner(testState.childSigner) - if (isL2NetworkWithCustomFeeToken()) { - await fundL1CustomFeeToken(testState.l1Signer) + if (isArbitrumNetworkWithCustomFeeToken()) { + await fundParentCustomFeeToken(testState.parentSigner) } }) it('register custom token', async () => { const { l1CustomToken: l1Token } = await registerCustomToken( - testState.l2Network, - testState.l1Signer, - testState.l2Signer, + testState.childChain, + testState.parentSigner, + testState.childSigner, testState.adminErc20Bridger ) testState.l1CustomToken = l1Token @@ -89,15 +90,15 @@ describe('Custom ERC20', () => { it('deposit', async () => { await ( - await testState.l1CustomToken.connect(testState.l1Signer).mint() + await testState.l1CustomToken.connect(testState.parentSigner).mint() ).wait() await depositToken({ depositAmount, - l1TokenAddress: testState.l1CustomToken.address, + parentTokenAddress: testState.l1CustomToken.address, erc20Bridger: testState.adminErc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.CUSTOM, }) }) @@ -105,13 +106,15 @@ describe('Custom ERC20', () => { it('withdraws erc20', async function () { await withdrawToken({ ...testState, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, erc20Bridger: testState.adminErc20Bridger, amount: withdrawalAmount, gatewayType: GatewayType.CUSTOM, startBalance: depositAmount, - l1Token: ERC20__factory.connect( + parentToken: ERC20__factory.connect( testState.l1CustomToken.address, - testState.l1Signer.provider! + testState.parentSigner.provider! ), }) }) @@ -120,25 +123,25 @@ describe('Custom ERC20', () => { await depositToken({ depositAmount, ethDepositAmount: utils.parseEther('0.0005'), - l1TokenAddress: testState.l1CustomToken.address, + parentTokenAddress: testState.l1CustomToken.address, erc20Bridger: testState.adminErc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.CUSTOM, }) }) - it('deposits erc20 with extra ETH to a specific L2 address', async () => { + it('deposits erc20 with extra ETH to a specific child chain address', async () => { const randomAddress = Wallet.createRandom().address await depositToken({ depositAmount, ethDepositAmount: utils.parseEther('0.0005'), - l1TokenAddress: testState.l1CustomToken.address, + parentTokenAddress: testState.l1CustomToken.address, erc20Bridger: testState.adminErc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.CUSTOM, destinationAddress: randomAddress, }) @@ -146,49 +149,49 @@ describe('Custom ERC20', () => { }) const registerCustomToken = async ( - l2Network: L2Network, - l1Signer: Signer, - l2Signer: Signer, + childChain: ArbitrumNetwork, + parentSigner: Signer, + childSigner: Signer, adminErc20Bridger: AdminErc20Bridger ) => { // create a custom token on L1 and L2 - const l1CustomTokenFactory = isL2NetworkWithCustomFeeToken() - ? new TestOrbitCustomTokenL1__factory(l1Signer) - : new TestCustomTokenL1__factory(l1Signer) + const l1CustomTokenFactory = isArbitrumNetworkWithCustomFeeToken() + ? new TestOrbitCustomTokenL1__factory(parentSigner) + : new TestCustomTokenL1__factory(parentSigner) const l1CustomToken = await l1CustomTokenFactory.deploy( - l2Network.tokenBridge.l1CustomGateway, - l2Network.tokenBridge.l1GatewayRouter + childChain.tokenBridge.l1CustomGateway, + childChain.tokenBridge.l1GatewayRouter ) await l1CustomToken.deployed() const amount = ethers.utils.parseEther('1') - if (isL2NetworkWithCustomFeeToken()) { + if (isArbitrumNetworkWithCustomFeeToken()) { const approvalTx = await ERC20__factory.connect( - l2Network.nativeToken!, - l1Signer + childChain.nativeToken!, + parentSigner ).approve(l1CustomToken.address, amount) await approvalTx.wait() } - const l2CustomTokenFac = new TestArbCustomToken__factory(l2Signer) + const l2CustomTokenFac = new TestArbCustomToken__factory(childSigner) const l2CustomToken = await l2CustomTokenFac.deploy( - l2Network.tokenBridge.l2CustomGateway, + childChain.tokenBridge.l2CustomGateway, l1CustomToken.address ) await l2CustomToken.deployed() // check starting conditions - should initially use the default gateway - const l1GatewayRouter = new L1GatewayRouter__factory(l1Signer).attach( - l2Network.tokenBridge.l1GatewayRouter + const l1GatewayRouter = new L1GatewayRouter__factory(parentSigner).attach( + childChain.tokenBridge.l1GatewayRouter ) - const l2GatewayRouter = new L2GatewayRouter__factory(l2Signer).attach( - l2Network.tokenBridge.l2GatewayRouter + const l2GatewayRouter = new L2GatewayRouter__factory(childSigner).attach( + childChain.tokenBridge.l2GatewayRouter ) - const l1CustomGateway = new L1CustomGateway__factory(l1Signer).attach( - l2Network.tokenBridge.l1CustomGateway + const l1CustomGateway = new L1CustomGateway__factory(parentSigner).attach( + childChain.tokenBridge.l1CustomGateway ) - const l2CustomGateway = new L1CustomGateway__factory(l2Signer).attach( - l2Network.tokenBridge.l2CustomGateway + const l2CustomGateway = new L1CustomGateway__factory(childSigner).attach( + childChain.tokenBridge.l2CustomGateway ) const startL1GatewayAddress = await l1GatewayRouter.l1TokenToGateway( l1CustomToken.address @@ -223,23 +226,25 @@ const registerCustomToken = async ( const regTx = await adminErc20Bridger.registerCustomToken( l1CustomToken.address, l2CustomToken.address, - l1Signer, - l2Signer.provider! + parentSigner, + childSigner.provider! ) const regRec = await regTx.wait() // wait on messages - const l1ToL2Messages = await regRec.getL1ToL2Messages(l2Signer.provider!) + const l1ToL2Messages = await regRec.getParentToChildMessages( + childSigner.provider! + ) expect(l1ToL2Messages.length, 'Should be 2 messages.').to.eq(2) const setTokenTx = await l1ToL2Messages[0].waitForStatus() expect(setTokenTx.status, 'Set token not redeemed.').to.eq( - L1ToL2MessageStatus.REDEEMED + ParentToChildMessageStatus.REDEEMED ) const setGateways = await l1ToL2Messages[1].waitForStatus() expect(setGateways.status, 'Set gateways not redeemed.').to.eq( - L1ToL2MessageStatus.REDEEMED + ParentToChildMessageStatus.REDEEMED ) // check end conditions @@ -249,7 +254,7 @@ const registerCustomToken = async ( expect( endL1GatewayAddress, 'End l1GatewayAddress not equal to l1 custom gateway' - ).to.eq(l2Network.tokenBridge.l1CustomGateway) + ).to.eq(childChain.tokenBridge.l1CustomGateway) const endL2GatewayAddress = await l2GatewayRouter.l1TokenToGateway( l1CustomToken.address @@ -257,7 +262,7 @@ const registerCustomToken = async ( expect( endL2GatewayAddress, 'End l2GatewayAddress not equal to l2 custom gateway' - ).to.eq(l2Network.tokenBridge.l2CustomGateway) + ).to.eq(childChain.tokenBridge.l2CustomGateway) const endL1Erc20Address = await l1CustomGateway.l1ToL2Token( l1CustomToken.address diff --git a/tests/integration/eth.test.ts b/tests/integration/eth.test.ts index b502c82bea..ac4d3ae976 100644 --- a/tests/integration/eth.test.ts +++ b/tests/integration/eth.test.ts @@ -23,18 +23,18 @@ import { Wallet } from '@ethersproject/wallet' import { parseEther } from '@ethersproject/units' import { - fundL1, - fundL2, + fundParentSigner, + fundChildSigner, mineUntilStop, prettyLog, skipIfMainnet, } from './testHelpers' -import { L2ToL1Message } from '../../src/lib/message/L2ToL1Message' -import { L2ToL1MessageStatus } from '../../src/lib/dataEntities/message' -import { L2TransactionReceipt } from '../../src/lib/message/L2Transaction' -import { L1ToL2MessageStatus } from '../../src/lib/message/L1ToL2Message' +import { ChildToParentMessage } from '../../src/lib/message/ChildToParentMessage' +import { ChildToParentMessageStatus } from '../../src/lib/dataEntities/message' +import { ChildTransactionReceipt } from '../../src/lib/message/ChildTransaction' +import { ParentToChildMessageStatus } from '../../src/lib/message/ParentToChildMessage' import { testSetup } from '../../scripts/testSetup' -import { isL2NetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' +import { isArbitrumNetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' import { ERC20__factory } from '../../src/lib/abi/factories/ERC20__factory' import { itOnlyWhenEth } from './custom-fee-token/mochaExtensions' @@ -46,18 +46,18 @@ describe('Ether', async () => { }) it('transfers ether on l2', async () => { - const { l2Signer } = await testSetup() + const { childSigner } = await testSetup() - await fundL2(l2Signer) + await fundChildSigner(childSigner) const randomAddress = Wallet.createRandom().address const amountToSend = parseEther('0.000005') - const balanceBefore = await l2Signer.provider!.getBalance( - await l2Signer.getAddress() + const balanceBefore = await childSigner.provider!.getBalance( + await childSigner.getAddress() ) const rec = await ( - await l2Signer.sendTransaction({ + await childSigner.sendTransaction({ to: randomAddress, value: amountToSend, maxFeePerGas: 15000000000, @@ -65,10 +65,10 @@ describe('Ether', async () => { }) ).wait() - const balanceAfter = await l2Signer.provider!.getBalance( - await l2Signer.getAddress() + const balanceAfter = await childSigner.provider!.getBalance( + await childSigner.getAddress() ) - const randomBalanceAfter = await l2Signer.provider!.getBalance( + const randomBalanceAfter = await childSigner.provider!.getBalance( randomAddress ) expect(randomBalanceAfter.toString(), 'random address balance after').to.eq( @@ -85,10 +85,10 @@ describe('Ether', async () => { itOnlyWhenEth( '"EthBridger.approveGasToken" throws when eth is used as native/gas token', async () => { - const { ethBridger, l1Signer } = await testSetup() + const { ethBridger, parentSigner } = await testSetup() try { - await ethBridger.approveGasToken({ l1Signer }) + await ethBridger.approveGasToken({ parentSigner }) expect.fail(`"EthBridger.approveGasToken" should have thrown`) } catch (error: any) { expect(error.message).to.equal('chain uses ETH as its native/gas token') @@ -97,79 +97,85 @@ describe('Ether', async () => { ) it('deposits ether', async () => { - const { ethBridger, l1Signer, l2Signer } = await testSetup() + const { ethBridger, parentSigner, childSigner } = await testSetup() - await fundL1(l1Signer) - const inboxAddress = ethBridger.l2Network.ethBridge.inbox + await fundParentSigner(parentSigner) + const inboxAddress = ethBridger.childChain.ethBridge.inbox - const initialInboxBalance = await l1Signer.provider!.getBalance( + const initialInboxBalance = await parentSigner.provider!.getBalance( inboxAddress ) const ethToDeposit = parseEther('0.0002') const res = await ethBridger.deposit({ amount: ethToDeposit, - l1Signer: l1Signer, + parentSigner: parentSigner, }) const rec = await res.wait() expect(rec.status).to.equal(1, 'eth deposit L1 txn failed') - const finalInboxBalance = await l1Signer.provider!.getBalance(inboxAddress) + const finalInboxBalance = await parentSigner.provider!.getBalance( + inboxAddress + ) expect( initialInboxBalance.add(ethToDeposit).eq(finalInboxBalance), 'balance failed to update after eth deposit' ) - const waitResult = await rec.waitForL2(l2Signer.provider!) + const waitResult = await rec.waitForChildTx(childSigner.provider!) - const l1ToL2Messages = await rec.getEthDeposits(l2Signer.provider!) + const l1ToL2Messages = await rec.getEthDeposits(childSigner.provider!) expect(l1ToL2Messages.length).to.eq(1, 'failed to find 1 l1 to l2 message') const l1ToL2Message = l1ToL2Messages[0] - const walletAddress = await l1Signer.getAddress() + const walletAddress = await parentSigner.getAddress() expect(l1ToL2Message.to).to.eq(walletAddress, 'message inputs value error') expect(l1ToL2Message.value.toString(), 'message inputs value error').to.eq( ethToDeposit.toString() ) - prettyLog('l2TxHash: ' + waitResult.message.l2DepositTxHash) - prettyLog('l2 transaction found!') + prettyLog('chainTxHash: ' + waitResult.message.chainDepositTxHash) + prettyLog('chain transaction found!') expect(waitResult.complete).to.eq(true, 'eth deposit not complete') - expect(waitResult.l2TxReceipt).to.exist - expect(waitResult.l2TxReceipt).to.not.be.null + expect(waitResult.chainTxReceipt).to.exist + expect(waitResult.chainTxReceipt).to.not.be.null - const testWalletL2EthBalance = await l2Signer.getBalance() + const testWalletL2EthBalance = await childSigner.getBalance() expect(testWalletL2EthBalance.toString(), 'final balance').to.eq( ethToDeposit.toString() ) }) it('deposits ether to a specific L2 address', async () => { - const { ethBridger, l1Signer, l2Signer } = await testSetup() + const { ethBridger, parentSigner, childSigner } = await testSetup() - await fundL1(l1Signer) - const inboxAddress = ethBridger.l2Network.ethBridge.inbox + await fundParentSigner(parentSigner) + const inboxAddress = ethBridger.childChain.ethBridge.inbox const destWallet = Wallet.createRandom() - const initialInboxBalance = await l1Signer.provider!.getBalance( + const initialInboxBalance = await parentSigner.provider!.getBalance( inboxAddress ) const ethToDeposit = parseEther('0.0002') const res = await ethBridger.depositTo({ amount: ethToDeposit, - l1Signer: l1Signer, + parentSigner: parentSigner, destinationAddress: destWallet.address, - l2Provider: l2Signer.provider!, + childProvider: childSigner.provider!, }) const rec = await res.wait() expect(rec.status).to.equal(1, 'eth deposit L1 txn failed') - const finalInboxBalance = await l1Signer.provider!.getBalance(inboxAddress) + const finalInboxBalance = await parentSigner.provider!.getBalance( + inboxAddress + ) expect( initialInboxBalance.add(ethToDeposit).eq(finalInboxBalance), 'balance failed to update after eth deposit' ) - const l1ToL2Messages = await rec.getL1ToL2Messages(l2Signer.provider!) + const l1ToL2Messages = await rec.getParentToChildMessages( + childSigner.provider! + ) expect(l1ToL2Messages.length).to.eq(1, 'failed to find 1 l1 to l2 message') const l1ToL2Message = l1ToL2Messages[0] @@ -184,17 +190,18 @@ describe('Ether', async () => { const retryableTicketResult = await l1ToL2Message.waitForStatus() expect(retryableTicketResult.status).to.eq( - L1ToL2MessageStatus.REDEEMED, + ParentToChildMessageStatus.REDEEMED, 'Retryable ticket not redeemed' ) - const retryableTxReceipt = await l2Signer.provider!.getTransactionReceipt( - l1ToL2Message.retryableCreationId - ) + const retryableTxReceipt = + await childSigner.provider!.getTransactionReceipt( + l1ToL2Message.retryableCreationId + ) expect(retryableTxReceipt).to.exist expect(retryableTxReceipt).to.not.be.null - const l2RetryableTxReceipt = new L2TransactionReceipt(retryableTxReceipt) + const l2RetryableTxReceipt = new ChildTransactionReceipt(retryableTxReceipt) const ticketRedeemEvents = l2RetryableTxReceipt.getRedeemScheduledEvents() expect(ticketRedeemEvents.length).to.eq( 1, @@ -203,7 +210,7 @@ describe('Ether', async () => { expect(ticketRedeemEvents[0].retryTxHash).to.exist expect(ticketRedeemEvents[0].retryTxHash).to.not.be.null - const testWalletL2EthBalance = await l2Signer.provider!.getBalance( + const testWalletL2EthBalance = await childSigner.provider!.getBalance( destWallet.address ) expect(testWalletL2EthBalance.toString(), 'final balance').to.eq( @@ -212,9 +219,9 @@ describe('Ether', async () => { }) it('withdraw Ether transaction succeeds', async () => { - const { l2Signer, l1Signer, ethBridger } = await testSetup() - await fundL2(l2Signer) - await fundL1(l1Signer) + const { childSigner, parentSigner, ethBridger } = await testSetup() + await fundChildSigner(childSigner) + await fundParentSigner(parentSigner) const ethToWithdraw = parseEther('0.00000002') const randomAddress = Wallet.createRandom().address @@ -222,16 +229,18 @@ describe('Ether', async () => { const request = await ethBridger.getWithdrawalRequest({ amount: ethToWithdraw, destinationAddress: randomAddress, - from: await l2Signer.getAddress(), + from: await childSigner.getAddress(), }) - const l1GasEstimate = await request.estimateL1GasLimit(l1Signer.provider!) + const l1GasEstimate = await request.estimateParentGasLimit( + parentSigner.provider! + ) const withdrawEthRes = await ethBridger.withdraw({ amount: ethToWithdraw, - l2Signer: l2Signer, + childSigner: childSigner, destinationAddress: randomAddress, - from: await l2Signer.getAddress(), + from: await childSigner.getAddress(), }) const withdrawEthRec = await withdrawEthRes.wait() @@ -242,15 +251,15 @@ describe('Ether', async () => { ) const withdrawMessage = ( - await withdrawEthRec.getL2ToL1Messages(l1Signer) + await withdrawEthRec.getChildToParentMessages(parentSigner) )[0] expect( withdrawMessage, 'eth withdraw getWithdrawalsInL2Transaction query came back empty' ).to.exist - const withdrawEvents = await L2ToL1Message.getL2ToL1Events( - l2Signer.provider!, + const withdrawEvents = await ChildToParentMessage.getChildToParentEvents( + childSigner.provider!, { fromBlock: withdrawEthRec.blockNumber, toBlock: 'latest' }, undefined, randomAddress @@ -261,14 +270,14 @@ describe('Ether', async () => { 'eth withdraw getL2ToL1EventData failed' ) - const messageStatus = await withdrawMessage.status(l2Signer.provider!) + const messageStatus = await withdrawMessage.status(childSigner.provider!) expect( messageStatus, `eth withdraw status returned ${messageStatus}` - ).to.be.eq(L2ToL1MessageStatus.UNCONFIRMED) + ).to.be.eq(ChildToParentMessageStatus.UNCONFIRMED) // CHRIS: TODO: comment this back in when fixed in nitro - // const actualFinalBalance = await l2Signer.getBalance() + // const actualFinalBalance = await childSigner.getBalance() // const expectedFinalBalance = initialBalance // .sub(ethToWithdraw) // .sub(withdrawEthRec.gasUsed.mul(withdrawEthRec.effectiveGasPrice)) @@ -277,24 +286,24 @@ describe('Ether', async () => { // ) // run a miner whilst withdrawing - const miner1 = Wallet.createRandom().connect(l1Signer.provider!) - const miner2 = Wallet.createRandom().connect(l2Signer.provider!) - await fundL1(miner1, parseEther('1')) - await fundL2(miner2, parseEther('1')) + const miner1 = Wallet.createRandom().connect(parentSigner.provider!) + const miner2 = Wallet.createRandom().connect(childSigner.provider!) + await fundParentSigner(miner1, parseEther('1')) + await fundChildSigner(miner2, parseEther('1')) const state = { mining: true } await Promise.race([ mineUntilStop(miner1, state), mineUntilStop(miner2, state), - withdrawMessage.waitUntilReadyToExecute(l2Signer.provider!), + withdrawMessage.waitUntilReadyToExecute(childSigner.provider!), ]) state.mining = false expect( - await withdrawMessage.status(l2Signer.provider!), + await withdrawMessage.status(childSigner.provider!), 'confirmed status' - ).to.eq(L2ToL1MessageStatus.CONFIRMED) + ).to.eq(ChildToParentMessageStatus.CONFIRMED) - const execTx = await withdrawMessage.execute(l2Signer.provider!) + const execTx = await withdrawMessage.execute(childSigner.provider!) const execRec = await execTx.wait() expect( @@ -303,16 +312,16 @@ describe('Ether', async () => { ).to.be.lessThan(l1GasEstimate.toNumber()) expect( - await withdrawMessage.status(l2Signer.provider!), + await withdrawMessage.status(childSigner.provider!), 'executed status' - ).to.eq(L2ToL1MessageStatus.EXECUTED) + ).to.eq(ChildToParentMessageStatus.EXECUTED) - const finalRandomBalance = isL2NetworkWithCustomFeeToken() + const finalRandomBalance = isArbitrumNetworkWithCustomFeeToken() ? await ERC20__factory.connect( ethBridger.nativeToken!, - l1Signer.provider! + parentSigner.provider! ).balanceOf(randomAddress) - : await l1Signer.provider!.getBalance(randomAddress) + : await parentSigner.provider!.getBalance(randomAddress) expect(finalRandomBalance.toString(), 'L1 final balance').to.eq( ethToWithdraw.toString() ) diff --git a/tests/integration/ethBridgeAddresses.test.ts b/tests/integration/ethBridgeAddresses.test.ts index 5d9db19974..6fa8fc0671 100644 --- a/tests/integration/ethBridgeAddresses.test.ts +++ b/tests/integration/ethBridgeAddresses.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai' import { EthBridge, getEthBridgeInformation, - getL2Network, + getArbitrumNetwork, } from '../../src/lib/dataEntities/networks' import dotenv from 'dotenv' dotenv.config() @@ -14,7 +14,7 @@ dotenv.config() */ describe('Obtain deployed bridge addresses', () => { it('obtains deployed ETH Bridge addresses', async () => { - const arbOneL2Network = await getL2Network(42161) + const arbOneL2Network = await getArbitrumNetwork(42161) const ethProvider = new JsonRpcProvider( process.env['MAINNET_RPC'] as string ) diff --git a/tests/integration/l1ToL2MessageCreator.test.ts b/tests/integration/l1ToL2MessageCreator.test.ts deleted file mode 100644 index e171096e01..0000000000 --- a/tests/integration/l1ToL2MessageCreator.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-env node */ -'use strict' - -import { expect } from 'chai' -import { providers, utils } from 'ethers' -import { fundL1, skipIfMainnet } from './testHelpers' -import { testSetup } from '../../scripts/testSetup' -import { L1ToL2MessageCreator } from '../../src/lib/message/L1ToL2MessageCreator' -import { L1ToL2MessageStatus } from '../../src' - -import { - fundL1CustomFeeToken, - approveL1CustomFeeToken, - isL2NetworkWithCustomFeeToken, -} from './custom-fee-token/customFeeTokenTestHelpers' - -describe('L1ToL2MessageCreator', () => { - beforeEach('skipIfMainnet', async function () { - await skipIfMainnet(this) - }) - - // Testing amount - const testAmount = utils.parseEther('0.01') - - it('allows the creation of Retryable Tickets sending parameters', async () => { - const { l1Signer, l2Signer } = await testSetup() - const signerAddress = await l1Signer.getAddress() - const arbProvider = l2Signer.provider as providers.Provider - - // Funding L1 wallet - await fundL1(l1Signer) - - if (isL2NetworkWithCustomFeeToken()) { - await fundL1CustomFeeToken(l1Signer) - await approveL1CustomFeeToken(l1Signer) - } - - // Instantiate the object - const l1ToL2MessageCreator = new L1ToL2MessageCreator(l1Signer) - - // Getting balances - const initialL2Balance = await l2Signer.getBalance() - - // Define parameters for Retryable - const retryableTicketParams = { - from: signerAddress, - to: signerAddress, - l2CallValue: testAmount, - callValueRefundAddress: signerAddress, - data: '0x', - } - - // And submitting the ticket - const l1SubmissionTx = await l1ToL2MessageCreator.createRetryableTicket( - retryableTicketParams, - arbProvider - ) - const l1SubmissionTxReceipt = await l1SubmissionTx.wait() - - // Getting the L1ToL2Message - const l1ToL2messages = await l1SubmissionTxReceipt.getL1ToL2Messages( - arbProvider - ) - expect(l1ToL2messages.length).to.eq(1) - const l1ToL2message = l1ToL2messages[0] - - // And waiting for it to be redeemed - const retryableTicketResult = await l1ToL2message.waitForStatus() - expect(retryableTicketResult.status).to.eq(L1ToL2MessageStatus.REDEEMED) - - // Getting and checking updated balances - const finalL2Balance = await l2Signer.getBalance() - - // When sending ETH through retryables, the same address will receive the ETH sent through the callvalue - // plus any gas that was not used in the operation. - expect( - initialL2Balance.add(testAmount).lt(finalL2Balance), - 'L2 balance not updated' - ).to.be.true - }) - - it('allows the creation of Retryable Tickets sending a request', async () => { - const { l1Signer, l2Signer } = await testSetup() - const signerAddress = await l1Signer.getAddress() - const ethProvider = l1Signer.provider as providers.Provider - const arbProvider = l2Signer.provider as providers.Provider - - // Funding L1 wallet - await fundL1(l1Signer) - - if (isL2NetworkWithCustomFeeToken()) { - await fundL1CustomFeeToken(l1Signer) - await approveL1CustomFeeToken(l1Signer) - } - - // Instantiate the object - const l1ToL2MessageCreator = new L1ToL2MessageCreator(l1Signer) - - // Getting balances - const initialL2Balance = await l2Signer.getBalance() - - // In this case, we will try to send directly an L1ToL2TransactionRequest - const l1ToL2TransactionRequestParams = { - from: signerAddress, - to: signerAddress, - l2CallValue: testAmount, - callValueRefundAddress: signerAddress, - data: '0x', - } - - const l1ToL2TransactionRequest = - await L1ToL2MessageCreator.getTicketCreationRequest( - l1ToL2TransactionRequestParams, - ethProvider, - arbProvider - ) - - // And create the retryable ticket - const l1SubmissionTx = await l1ToL2MessageCreator.createRetryableTicket( - l1ToL2TransactionRequest, - arbProvider - ) - const l1SubmissionTxReceipt = await l1SubmissionTx.wait() - - // Getting the L1ToL2Message - const l1ToL2messages = await l1SubmissionTxReceipt.getL1ToL2Messages( - arbProvider - ) - expect(l1ToL2messages.length).to.eq(1) - const l1ToL2message = l1ToL2messages[0] - - // And waiting for it to be redeemed - const retryableTicketResult = await l1ToL2message.waitForStatus() - expect(retryableTicketResult.status).to.eq(L1ToL2MessageStatus.REDEEMED) - - // Getting and checking updated balances - const finalL2Balance = await l2Signer.getBalance() - - // When sending ETH through retryables, the same address will receive the ETH sent through the callvalue - // plus any gas that was not used in the operation. - expect( - initialL2Balance.add(testAmount).lt(finalL2Balance), - 'L2 balance not updated' - ).to.be.true - }) -}) diff --git a/tests/integration/parentToChildMessageCreator.test.ts b/tests/integration/parentToChildMessageCreator.test.ts new file mode 100644 index 0000000000..079ea7ea3c --- /dev/null +++ b/tests/integration/parentToChildMessageCreator.test.ts @@ -0,0 +1,169 @@ +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-env node */ +'use strict' + +import { expect } from 'chai' +import { providers, utils } from 'ethers' +import { fundParentSigner, skipIfMainnet } from './testHelpers' +import { testSetup } from '../../scripts/testSetup' +import { ParentToChildMessageCreator } from '../../src/lib/message/ParentToChildMessageCreator' +import { ParentToChildMessageStatus } from '../../src/lib/message/ParentToChildMessage' + +import { + fundParentCustomFeeToken, + approveParentCustomFeeToken, + isArbitrumNetworkWithCustomFeeToken, +} from './custom-fee-token/customFeeTokenTestHelpers' + +describe('ParentToChildMessageCreator', () => { + beforeEach('skipIfMainnet', async function () { + await skipIfMainnet(this) + }) + + // Testing amount + const testAmount = utils.parseEther('0.01') + + it('allows the creation of Retryable Tickets sending parameters', async () => { + const { parentSigner, childSigner } = await testSetup() + const signerAddress = await parentSigner.getAddress() + const arbProvider = childSigner.provider as providers.Provider + + // Funding parent chain wallet + await fundParentSigner(parentSigner) + + if (isArbitrumNetworkWithCustomFeeToken()) { + await fundParentCustomFeeToken(parentSigner) + await approveParentCustomFeeToken(parentSigner) + } + + // Instantiate the object + const parentToChildMessageCreator = new ParentToChildMessageCreator( + parentSigner + ) + + // Getting balances + const initialChildChainBalance = await childSigner.getBalance() + + // Define parameters for Retryable + const retryableTicketParams = { + from: signerAddress, + to: signerAddress, + l2CallValue: testAmount, + callValueRefundAddress: signerAddress, + data: '0x', + } + + // And submitting the ticket + const parentChainSubmissionTx = + await parentToChildMessageCreator.createRetryableTicket( + retryableTicketParams, + arbProvider + ) + const parentChainSubmissionTxReceipt = await parentChainSubmissionTx.wait() + + // Getting the ParentToChildMessage + const parentToChildMessages = + await parentChainSubmissionTxReceipt.getParentToChildMessages(arbProvider) + expect(parentToChildMessages.length).to.eq(1) + const parentToChildMessage = parentToChildMessages[0] + + // And waiting for it to be redeemed + const retryableTicketResult = await parentToChildMessage.waitForStatus() + expect(retryableTicketResult.status).to.eq( + ParentToChildMessageStatus.REDEEMED + ) + + // Getting and checking updated balances + const finalChildChainBalance = await childSigner.getBalance() + + // When sending ETH through retryables, the same address will receive the ETH sent through the callvalue + // plus any gas that was not used in the operation. + expect( + initialChildChainBalance.add(testAmount).lt(finalChildChainBalance), + 'Child chain balance not updated' + ).to.be.true + }) + + it('allows the creation of Retryable Tickets sending a request', async () => { + const { parentSigner, childSigner } = await testSetup() + const signerAddress = await parentSigner.getAddress() + const ethProvider = parentSigner.provider as providers.Provider + const arbProvider = childSigner.provider as providers.Provider + + // Funding parent chain wallet + await fundParentSigner(parentSigner) + + if (isArbitrumNetworkWithCustomFeeToken()) { + await fundParentCustomFeeToken(parentSigner) + await approveParentCustomFeeToken(parentSigner) + } + + // Instantiate the object + const parentToChildMessageCreator = new ParentToChildMessageCreator( + parentSigner + ) + + // Getting balances + const initialChildChainBalance = await childSigner.getBalance() + + // In this case, we will try to send directly a ParentToChildTransactionRequest + const parentToChildTransactionRequestParams = { + from: signerAddress, + to: signerAddress, + l2CallValue: testAmount, + callValueRefundAddress: signerAddress, + data: '0x', + } + + const parentToChildTransactionRequest = + await ParentToChildMessageCreator.getTicketCreationRequest( + parentToChildTransactionRequestParams, + ethProvider, + arbProvider + ) + + // And create the retryable ticket + const parentChainSubmissionTx = + await parentToChildMessageCreator.createRetryableTicket( + parentToChildTransactionRequest, + arbProvider + ) + const parentChainSubmissionTxReceipt = await parentChainSubmissionTx.wait() + + // Getting the ParentToChildMessage + const parentToChildMessages = + await parentChainSubmissionTxReceipt.getParentToChildMessages(arbProvider) + expect(parentToChildMessages.length).to.eq(1) + const parentToChildMessage = parentToChildMessages[0] + + // And waiting for it to be redeemed + const retryableTicketResult = await parentToChildMessage.waitForStatus() + expect(retryableTicketResult.status).to.eq( + ParentToChildMessageStatus.REDEEMED + ) + + // Getting and checking updated balances + const finalChildChainBalance = await childSigner.getBalance() + + // When sending ETH through retryables, the same address will receive the ETH sent through the callvalue + // plus any gas that was not used in the operation. + expect( + initialChildChainBalance.add(testAmount).lt(finalChildChainBalance), + 'Child chain balance not updated' + ).to.be.true + }) +}) diff --git a/tests/integration/L1ToL2MessageGasEstimator.test.ts b/tests/integration/parentToChildMessageGasEstimator.test.ts similarity index 70% rename from tests/integration/L1ToL2MessageGasEstimator.test.ts rename to tests/integration/parentToChildMessageGasEstimator.test.ts index 2ea66d7d46..56dce2fe97 100644 --- a/tests/integration/L1ToL2MessageGasEstimator.test.ts +++ b/tests/integration/parentToChildMessageGasEstimator.test.ts @@ -21,13 +21,13 @@ import { BigNumber } from 'ethers' import { skipIfMainnet } from './testHelpers' import { testSetup } from '../../scripts/testSetup' -import { L1ToL2MessageGasEstimator } from '../../src' +import { ParentToChildMessageGasEstimator } from '../../src/lib/message/ParentToChildMessageGasEstimator' import { itOnlyWhenEth, itOnlyWhenCustomGasToken, } from './custom-fee-token/mochaExtensions' -describe('L1ToL2MessageGasEstimator', () => { +describe('ParentToChildMessageGasEstimator', () => { beforeEach('skipIfMainnet', async function () { await skipIfMainnet(this) }) @@ -35,13 +35,13 @@ describe('L1ToL2MessageGasEstimator', () => { itOnlyWhenEth( `"estimateSubmissionFee" returns non-0 for eth chain`, async () => { - const { l1Provider, l2Provider } = await testSetup() + const { parentProvider, childProvider } = await testSetup() - const submissionFee = await new L1ToL2MessageGasEstimator( - l2Provider + const submissionFee = await new ParentToChildMessageGasEstimator( + childProvider ).estimateSubmissionFee( - l1Provider, - await l1Provider.getGasPrice(), + parentProvider, + await parentProvider.getGasPrice(), 123456 ) @@ -52,13 +52,13 @@ describe('L1ToL2MessageGasEstimator', () => { itOnlyWhenCustomGasToken( `"estimateSubmissionFee" returns 0 for custom gas token chain`, async () => { - const { l1Provider, l2Provider } = await testSetup() + const { parentProvider, childProvider } = await testSetup() - const submissionFee = await new L1ToL2MessageGasEstimator( - l2Provider + const submissionFee = await new ParentToChildMessageGasEstimator( + childProvider ).estimateSubmissionFee( - l1Provider, - await l1Provider.getGasPrice(), + parentProvider, + await parentProvider.getGasPrice(), 123456 ) diff --git a/tests/integration/retryableData.test.ts b/tests/integration/retryableData.test.ts index a8d6e95999..170e4836b4 100644 --- a/tests/integration/retryableData.test.ts +++ b/tests/integration/retryableData.test.ts @@ -20,16 +20,16 @@ import { assert, expect } from 'chai' import { BigNumber } from '@ethersproject/bignumber' import { hexlify } from '@ethersproject/bytes' import { TestERC20__factory } from '../../src/lib/abi/factories/TestERC20__factory' -import { fundL1, skipIfMainnet } from './testHelpers' +import { fundParentSigner, skipIfMainnet } from './testHelpers' import { RetryableDataTools } from '../../src' import { Wallet } from 'ethers' import { testSetup } from '../../scripts/testSetup' import { parseEther, randomBytes } from 'ethers/lib/utils' import { Inbox__factory } from '../../src/lib/abi/factories/Inbox__factory' -import { GasOverrides } from '../../src/lib/message/L1ToL2MessageGasEstimator' +import { GasOverrides } from '../../src/lib/message/ParentToChildMessageGasEstimator' const depositAmount = BigNumber.from(100) import { ERC20Inbox__factory } from '../../src/lib/abi/factories/ERC20Inbox__factory' -import { isL2NetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' +import { isArbitrumNetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' describe('RevertData', () => { beforeEach('skipIfMainnet', async function () { @@ -58,8 +58,8 @@ describe('RevertData', () => { const testRetryableDataParsing = async ( func: 'estimateGas' | 'callStatic' ) => { - const { l1Signer, l2Network } = await testSetup() - await fundL1(l1Signer) + const { parentSigner, childChain } = await testSetup() + await fundParentSigner(parentSigner) const { to, @@ -74,10 +74,10 @@ describe('RevertData', () => { } = createRevertParams() try { - if (isL2NetworkWithCustomFeeToken()) { + if (isArbitrumNetworkWithCustomFeeToken()) { const inbox = ERC20Inbox__factory.connect( - l2Network.ethBridge.inbox, - l1Signer + childChain.ethBridge.inbox, + parentSigner ) await inbox[func].createRetryableTicket( to, @@ -92,8 +92,8 @@ describe('RevertData', () => { ) } else { const inbox = Inbox__factory.connect( - l2Network.ethBridge.inbox, - l1Signer + childChain.ethBridge.inbox, + parentSigner ) await inbox[func].createRetryableTicket( to, @@ -121,7 +121,7 @@ describe('RevertData', () => { expect(parsed.excessFeeRefundAddress, 'excessFeeRefundAddress').to.eq( excessFeeRefundAddress ) - expect(parsed.from, 'from').to.eq(await l1Signer.getAddress()) + expect(parsed.from, 'from').to.eq(await parentSigner.getAddress()) expect(parsed.gasLimit.toString(), 'gasLimit').to.eq(gasLimit.toString()) expect(parsed.l2CallValue.toString(), 'l2CallValue').to.eq( l2CallValue.toString() @@ -145,29 +145,29 @@ describe('RevertData', () => { }) it('is the same as what we estimate in erc20Bridger', async () => { - const { erc20Bridger, l1Signer, l2Signer } = await testSetup() - await fundL1(l1Signer, parseEther('2')) + const { erc20Bridger, parentSigner, childSigner } = await testSetup() + await fundParentSigner(parentSigner, parseEther('2')) - const deployErc20 = new TestERC20__factory().connect(l1Signer) + const deployErc20 = new TestERC20__factory().connect(parentSigner) const testToken = await deployErc20.deploy() await testToken.deployed() await (await testToken.mint()).wait() - const l1TokenAddress = testToken.address + const parentTokenAddress = testToken.address await ( await erc20Bridger.approveToken({ - erc20L1Address: l1TokenAddress, - l1Signer: l1Signer, + erc20ParentAddress: parentTokenAddress, + parentSigner, }) ).wait() - if (isL2NetworkWithCustomFeeToken()) { + if (isArbitrumNetworkWithCustomFeeToken()) { // approve the custom fee token await ( await erc20Bridger.approveGasToken({ - erc20L1Address: l1TokenAddress, - l1Signer: l1Signer, + erc20ParentAddress: parentTokenAddress, + parentSigner, }) ).wait() } @@ -185,25 +185,25 @@ describe('RevertData', () => { } const erc20Params = { - l1Signer: l1Signer, - l2SignerOrProvider: l2Signer.provider!, - from: await l1Signer.getAddress(), - erc20L1Address: l1TokenAddress, + parentSigner, + childSignerOrProvider: childSigner.provider!, + from: await parentSigner.getAddress(), + erc20ParentAddress: parentTokenAddress, amount: depositAmount, retryableGasOverrides: retryableOverrides, } const depositParams = await erc20Bridger.getDepositRequest({ ...erc20Params, - l1Provider: l1Signer.provider!, - l2Provider: l2Signer.provider!, + parentProvider: parentSigner.provider!, + childProvider: childSigner.provider!, }) try { await erc20Bridger.deposit({ ...erc20Params, - l1Signer: l1Signer, - l2Provider: l2Signer.provider!, + parentSigner, + childProvider: childSigner.provider!, }) assert.fail('Expected estimateGas to fail') } catch (err) { @@ -216,7 +216,7 @@ describe('RevertData', () => { ) expect(parsed.data, 'data').to.eq(depositParams.retryableData.data) expect(parsed.deposit.toString(), 'deposit').to.eq( - isL2NetworkWithCustomFeeToken() + isArbitrumNetworkWithCustomFeeToken() ? depositParams.retryableData.deposit.toString() : depositParams.txRequest.value.toString() ) diff --git a/tests/integration/sanity.test.ts b/tests/integration/sanity.test.ts index b33ba34be1..81e6b9e138 100644 --- a/tests/integration/sanity.test.ts +++ b/tests/integration/sanity.test.ts @@ -37,14 +37,14 @@ const expectIgnoreCase = (expected: string, actual: string) => { describe('sanity checks (read-only)', async () => { it('standard gateways public storage vars properly set', async () => { - const { l1Signer, l2Signer, l2Network } = await testSetup() + const { parentSigner, childSigner, childChain } = await testSetup() const l1Gateway = await L1ERC20Gateway__factory.connect( - l2Network.tokenBridge.l1ERC20Gateway, - l1Signer + childChain.tokenBridge.l1ERC20Gateway, + parentSigner ) const l2Gateway = await L2ERC20Gateway__factory.connect( - l2Network.tokenBridge.l2ERC20Gateway, - l2Signer + childChain.tokenBridge.l2ERC20Gateway, + childSigner ) const l1ClonableProxyHash = await l1Gateway.cloneableProxyHash() @@ -56,123 +56,128 @@ describe('sanity checks (read-only)', async () => { expect(l1BeaconProxyHash).to.equal(l2BeaconProxyHash) const l1GatewayCounterParty = await l1Gateway.counterpartGateway() - expect(l1GatewayCounterParty).to.equal(l2Network.tokenBridge.l2ERC20Gateway) + expect(l1GatewayCounterParty).to.equal( + childChain.tokenBridge.l2ERC20Gateway + ) const l2GatewayCounterParty = await l2Gateway.counterpartGateway() - expect(l2GatewayCounterParty).to.equal(l2Network.tokenBridge.l1ERC20Gateway) + expect(l2GatewayCounterParty).to.equal( + childChain.tokenBridge.l1ERC20Gateway + ) const l1Router = await l1Gateway.router() - expect(l1Router).to.equal(l2Network.tokenBridge.l1GatewayRouter) + expect(l1Router).to.equal(childChain.tokenBridge.l1GatewayRouter) const l2Router = await l2Gateway.router() - expect(l2Router).to.equal(l2Network.tokenBridge.l2GatewayRouter) + expect(l2Router).to.equal(childChain.tokenBridge.l2GatewayRouter) }) it('custom gateways public storage vars properly set', async () => { - const { l1Signer, l2Signer, l2Network } = await testSetup() + const { parentSigner, childSigner, childChain } = await testSetup() const l1Gateway = await L1CustomGateway__factory.connect( - l2Network.tokenBridge.l1CustomGateway, - l1Signer + childChain.tokenBridge.l1CustomGateway, + parentSigner ) const l2Gateway = await L2CustomGateway__factory.connect( - l2Network.tokenBridge.l2CustomGateway, - l2Signer + childChain.tokenBridge.l2CustomGateway, + childSigner ) const l1GatewayCounterParty = await l1Gateway.counterpartGateway() expect(l1GatewayCounterParty).to.equal( - l2Network.tokenBridge.l2CustomGateway + childChain.tokenBridge.l2CustomGateway ) const l2GatewayCounterParty = await l2Gateway.counterpartGateway() expect(l2GatewayCounterParty).to.equal( - l2Network.tokenBridge.l1CustomGateway + childChain.tokenBridge.l1CustomGateway ) const l1Router = await l1Gateway.router() - expect(l1Router).to.equal(l2Network.tokenBridge.l1GatewayRouter) + expect(l1Router).to.equal(childChain.tokenBridge.l1GatewayRouter) const l2Router = await l2Gateway.router() - expect(l2Router).to.equal(l2Network.tokenBridge.l2GatewayRouter) + expect(l2Router).to.equal(childChain.tokenBridge.l2GatewayRouter) }) itOnlyWhenEth( 'weth gateways gateways public storage vars properly set', async () => { - const { l1Signer, l2Signer, l2Network } = await testSetup() + const { parentSigner, childSigner, childChain } = await testSetup() const l1Gateway = await L1WethGateway__factory.connect( - l2Network.tokenBridge.l1WethGateway, - l1Signer + childChain.tokenBridge.l1WethGateway, + parentSigner ) const l2Gateway = await L2WethGateway__factory.connect( - l2Network.tokenBridge.l2WethGateway, - l2Signer + childChain.tokenBridge.l2WethGateway, + childSigner ) const l1Weth = await l1Gateway.l1Weth() - expectIgnoreCase(l1Weth, l2Network.tokenBridge.l1Weth) + expectIgnoreCase(l1Weth, childChain.tokenBridge.l1Weth) const l2Weth = await l2Gateway.l2Weth() - expectIgnoreCase(l2Weth, l2Network.tokenBridge.l2Weth) + expectIgnoreCase(l2Weth, childChain.tokenBridge.l2Weth) const l1GatewayCounterParty = await l1Gateway.counterpartGateway() expectIgnoreCase( l1GatewayCounterParty, - l2Network.tokenBridge.l2WethGateway + childChain.tokenBridge.l2WethGateway ) const l2GatewayCounterParty = await l2Gateway.counterpartGateway() expectIgnoreCase( l2GatewayCounterParty, - l2Network.tokenBridge.l1WethGateway + childChain.tokenBridge.l1WethGateway ) const l1Router = await l1Gateway.router() - expectIgnoreCase(l1Router, l2Network.tokenBridge.l1GatewayRouter) + expectIgnoreCase(l1Router, childChain.tokenBridge.l1GatewayRouter) const l2Router = await l2Gateway.router() - expectIgnoreCase(l2Router, l2Network.tokenBridge.l2GatewayRouter) + expectIgnoreCase(l2Router, childChain.tokenBridge.l2GatewayRouter) } ) itOnlyWhenEth('aeWETh public vars properly set', async () => { - const { l2Signer, l2Network } = await testSetup() + const { childSigner, childChain } = await testSetup() const aeWeth = AeWETH__factory.connect( - l2Network.tokenBridge.l2Weth, - l2Signer + childChain.tokenBridge.l2Weth, + childSigner ) const l2GatewayOnAeWeth = await aeWeth.l2Gateway() - expectIgnoreCase(l2GatewayOnAeWeth, l2Network.tokenBridge.l2WethGateway) + expectIgnoreCase(l2GatewayOnAeWeth, childChain.tokenBridge.l2WethGateway) const l1AddressOnAeWeth = await aeWeth.l1Address() - expectIgnoreCase(l1AddressOnAeWeth, l2Network.tokenBridge.l1Weth) + expectIgnoreCase(l1AddressOnAeWeth, childChain.tokenBridge.l1Weth) }) itOnlyWhenEth('l1 gateway router points to right weth gateways', async () => { - const { adminErc20Bridger, l1Signer, l2Network } = await testSetup() + const { adminErc20Bridger, parentSigner, childChain } = await testSetup() const gateway = await adminErc20Bridger.getL1GatewayAddress( - l2Network.tokenBridge.l1Weth, - l1Signer.provider! + childChain.tokenBridge.l1Weth, + parentSigner.provider! ) - expect(gateway).to.equal(l2Network.tokenBridge.l1WethGateway) + expect(gateway).to.equal(childChain.tokenBridge.l1WethGateway) }) - it('L1 and L2 implementations of calculateL2ERC20Address match', async () => { - const { l1Signer, l2Signer, l2Network, erc20Bridger } = await testSetup() + it('parent and child chain implementations of calculateL2ERC20Address match', async () => { + const { parentSigner, childSigner, childChain, erc20Bridger } = + await testSetup() const address = hexlify(randomBytes(20)) - const erc20L2AddressAsPerL1 = await erc20Bridger.getL2ERC20Address( + const erc20L2AddressAsPerL1 = await erc20Bridger.getChildERC20Address( address, - l1Signer.provider! + parentSigner.provider! ) const l2gr = L2GatewayRouter__factory.connect( - l2Network.tokenBridge.l2GatewayRouter, - l2Signer.provider! + childChain.tokenBridge.l2GatewayRouter, + childSigner.provider! ) const erc20L2AddressAsPerL2 = await l2gr.calculateL2TokenAddress(address) diff --git a/tests/integration/sendChildmsg.test.ts b/tests/integration/sendChildmsg.test.ts new file mode 100644 index 0000000000..1f1d87bbd0 --- /dev/null +++ b/tests/integration/sendChildmsg.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-env node */ +// import { instantiateBridge } from './instantiate_bridge' +;('use strict') + +import { BigNumber, ethers, Signer } from 'ethers' +import { InboxTools } from '../../src/lib/inbox/inbox' +import { + getArbitrumNetwork, + ArbitrumNetwork, +} from '../../src/lib/dataEntities/networks' +import { testSetup } from '../../scripts/testSetup' +import { greeter } from './helper/greeter' +import { expect } from 'chai' +import { AdminErc20Bridger } from '../../src/lib/assetBridger/erc20Bridger' + +const sendSignedTx = async (testState: any, info?: any) => { + const { parentDeployer, childDeployer } = testState + const childChain = await getArbitrumNetwork(await childDeployer.getChainId()) + const inbox = new InboxTools(parentDeployer, childChain) + const message = { + ...info, + value: BigNumber.from(0), + } + const signedTx = await inbox.signChildChainTx(message, childDeployer) + + const parentTx = await inbox.sendChildChainSignedTx(signedTx) + return { + signedMsg: signedTx, + parentTransactionReceipt: await parentTx?.wait(), + } +} + +describe('Send signedTx to child chain using inbox', async () => { + // test globals + let testState: { + parentDeployer: Signer + childDeployer: Signer + adminErc20Bridger: AdminErc20Bridger + childChain: ArbitrumNetwork + } + + before('init', async () => { + testState = await testSetup() + }) + + it('can deploy contract', async () => { + const childDeployer = testState.childDeployer + const Greeter = new ethers.ContractFactory( + greeter.abi, + greeter.bytecode + ).connect(childDeployer) + + const info = { + value: BigNumber.from(0), + } + const contractCreationData = Greeter.getDeployTransaction(info) + const { signedMsg, parentTransactionReceipt } = await sendSignedTx( + testState, + contractCreationData + ) + const parentStatus = parentTransactionReceipt?.status + expect(parentStatus).to.equal(1, 'parent txn failed') + const childTx = ethers.utils.parseTransaction(signedMsg) + const childTxhash = childTx.hash! + const childTxReceipt = await childDeployer.provider!.waitForTransaction( + childTxhash + ) + const childStatus = childTxReceipt.status + expect(childStatus).to.equal(1, 'child txn failed') + const contractAddress = ethers.ContractFactory.getContractAddress({ + from: childTx.from!, + nonce: childTx.nonce, + }) + const greeterImp = Greeter.attach(contractAddress) + const greetResult = await greeterImp.greet() + expect(greetResult).to.equal('hello world', 'contract returns not expected') + }) + + it('should confirm the same tx on child chain', async () => { + const childDeployer = testState.childDeployer + const info = { + data: '0x12', + to: await childDeployer.getAddress(), + } + const { signedMsg, parentTransactionReceipt: parentTransactionReceipt } = + await sendSignedTx(testState, info) + const parentStatus = parentTransactionReceipt?.status + expect(parentStatus).to.equal(1) + const childTxhash = ethers.utils.parseTransaction(signedMsg).hash! + const childTxReceipt = await childDeployer.provider!.waitForTransaction( + childTxhash + ) + const childStatus = childTxReceipt.status + expect(childStatus).to.equal(1) + }) + + it('send two tx share the same nonce but with different gas price, should confirm the one which gas price higher than child base price', async () => { + const childDeployer = testState.childDeployer + const currentNonce = await childDeployer.getTransactionCount() + + const lowFeeInfo = { + data: '0x12', + nonce: currentNonce, + to: await childDeployer.getAddress(), + maxFeePerGas: BigNumber.from(10000000), //0.01gwei + maxPriorityFeePerGas: BigNumber.from(1000000), //0.001gwei + } + const lowFeeTx = await sendSignedTx(testState, lowFeeInfo) + const lowFeeParentStatus = lowFeeTx.parentTransactionReceipt?.status + expect(lowFeeParentStatus).to.equal(1) + const info = { + data: '0x12', + to: await childDeployer.getAddress(), + nonce: currentNonce, + } + const enoughFeeTx = await sendSignedTx(testState, info) + const enoughFeeParentStatus = enoughFeeTx.parentTransactionReceipt?.status + expect(enoughFeeParentStatus).to.equal(1) + const childLowFeeTxhash = ethers.utils.parseTransaction(lowFeeTx.signedMsg) + .hash! + const childEnoughFeeTxhash = ethers.utils.parseTransaction( + enoughFeeTx.signedMsg + ).hash! + + const childTEnoughFeeReceipt = + await childDeployer.provider!.waitForTransaction(childEnoughFeeTxhash) + const childStatus = childTEnoughFeeReceipt.status + expect(childStatus).to.equal(1) + const res = await childDeployer.provider?.getTransactionReceipt( + childLowFeeTxhash + ) + expect(res).to.be.null + }) +}) diff --git a/tests/integration/sendL2msg.test.ts b/tests/integration/sendL2msg.test.ts deleted file mode 100644 index e704ab0faa..0000000000 --- a/tests/integration/sendL2msg.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-env node */ -// import { instantiateBridge } from './instantiate_bridge' -;('use strict') - -import { BigNumber, ethers, Signer } from 'ethers' -import { InboxTools } from '../../src/lib/inbox/inbox' -import { getL2Network, L2Network } from '../../src/lib/dataEntities/networks' -import { testSetup } from '../../scripts/testSetup' -import { greeter } from './helper/greeter' -import { expect } from 'chai' -import { AdminErc20Bridger } from '../../src/lib/assetBridger/erc20Bridger' - -const sendSignedTx = async (testState: any, info?: any) => { - const { l1Deployer, l2Deployer } = testState - const l2Network = await getL2Network(await l2Deployer.getChainId()) - const inbox = new InboxTools(l1Deployer, l2Network) - const message = { - ...info, - value: BigNumber.from(0), - } - const signedTx = await inbox.signL2Tx(message, l2Deployer) - - const l1Tx = await inbox.sendL2SignedTx(signedTx) - return { - signedMsg: signedTx, - l1TransactionReceipt: await l1Tx?.wait(), - } -} - -describe('Send signedTx to l2 using inbox', async () => { - // test globals - let testState: { - l1Deployer: Signer - l2Deployer: Signer - adminErc20Bridger: AdminErc20Bridger - l2Network: L2Network - } - - before('init', async () => { - testState = await testSetup() - }) - - it('can deploy contract', async () => { - const l2Deployer = testState.l2Deployer - const Greeter = new ethers.ContractFactory( - greeter.abi, - greeter.bytecode - ).connect(l2Deployer) - - const info = { - value: BigNumber.from(0), - } - const contractCreationData = Greeter.getDeployTransaction(info) - const { signedMsg, l1TransactionReceipt } = await sendSignedTx( - testState, - contractCreationData - ) - const l1Status = l1TransactionReceipt?.status - expect(l1Status).to.equal(1, 'l1 txn failed') - const l2Tx = ethers.utils.parseTransaction(signedMsg) - const l2Txhash = l2Tx.hash! - const l2TxReceipt = await l2Deployer.provider!.waitForTransaction(l2Txhash) - const l2Status = l2TxReceipt.status - expect(l2Status).to.equal(1, 'l2 txn failed') - const contractAddress = ethers.ContractFactory.getContractAddress({ - from: l2Tx.from!, - nonce: l2Tx.nonce, - }) - const greeterImp = Greeter.attach(contractAddress) - const greetResult = await greeterImp.greet() - expect(greetResult).to.equal('hello world', 'contract returns not expected') - }) - - it('should confirm the same tx on l2', async () => { - const l2Deployer = testState.l2Deployer - const info = { - data: '0x12', - to: await l2Deployer.getAddress(), - } - const { signedMsg, l1TransactionReceipt } = await sendSignedTx( - testState, - info - ) - const l1Status = l1TransactionReceipt?.status - expect(l1Status).to.equal(1) - const l2Txhash = ethers.utils.parseTransaction(signedMsg).hash! - const l2TxReceipt = await l2Deployer.provider!.waitForTransaction(l2Txhash) - const l2Status = l2TxReceipt.status - expect(l2Status).to.equal(1) - }) - - it('send two tx share the same nonce but with different gas price, should confirm the one which gas price higher than l2 base price', async () => { - const l2Deployer = testState.l2Deployer - const currentNonce = await l2Deployer.getTransactionCount() - - const lowFeeInfo = { - data: '0x12', - nonce: currentNonce, - to: await l2Deployer.getAddress(), - maxFeePerGas: BigNumber.from(10000000), //0.01gwei - maxPriorityFeePerGas: BigNumber.from(1000000), //0.001gwei - } - const lowFeeTx = await sendSignedTx(testState, lowFeeInfo) - const lowFeeL1Status = lowFeeTx.l1TransactionReceipt?.status - expect(lowFeeL1Status).to.equal(1) - const info = { - data: '0x12', - to: await l2Deployer.getAddress(), - nonce: currentNonce, - } - const enoughFeeTx = await sendSignedTx(testState, info) - const enoughFeeL1Status = enoughFeeTx.l1TransactionReceipt?.status - expect(enoughFeeL1Status).to.equal(1) - const l2LowFeeTxhash = ethers.utils.parseTransaction(lowFeeTx.signedMsg) - .hash! - const l2EnoughFeeTxhash = ethers.utils.parseTransaction( - enoughFeeTx.signedMsg - ).hash! - - const l2TEnoughFeeReceipt = await l2Deployer.provider!.waitForTransaction( - l2EnoughFeeTxhash - ) - const l2Status = l2TEnoughFeeReceipt.status - expect(l2Status).to.equal(1) - const res = await l2Deployer.provider?.getTransactionReceipt(l2LowFeeTxhash) - expect(res).to.be.null - }) -}) diff --git a/tests/integration/standarderc20.test.ts b/tests/integration/standarderc20.test.ts index 6a4399983c..af08fc29ca 100644 --- a/tests/integration/standarderc20.test.ts +++ b/tests/integration/standarderc20.test.ts @@ -21,20 +21,20 @@ import { Signer, Wallet, utils, constants } from 'ethers' import { BigNumber } from '@ethersproject/bignumber' import { TestERC20__factory } from '../../src/lib/abi/factories/TestERC20__factory' import { - fundL1, + fundParentSigner, skipIfMainnet, depositToken, GatewayType, withdrawToken, - fundL2, + fundChildSigner, } from './testHelpers' import { Erc20Bridger, - L1ToL2MessageStatus, - L1ToL2MessageWriter, - L2Network, - L2TransactionReceipt, + ParentToChildMessageStatus, + ParentToChildMessageWriter, + ChildTransactionReceipt, } from '../../src' +import { ArbitrumNetwork } from '../../src/lib/dataEntities/networks' import { TestERC20 } from '../../src/lib/abi/TestERC20' import { testSetup } from '../../scripts/testSetup' import { ERC20__factory } from '../../src/lib/abi/factories/ERC20__factory' @@ -46,11 +46,12 @@ import { ArbRetryableTx__factory } from '../../src/lib/abi/factories/ArbRetryabl import { NodeInterface__factory } from '../../src/lib/abi/factories/NodeInterface__factory' import { isDefined } from '../../src/lib/utils/lib' import { - getL1CustomFeeTokenAllowance, - approveL1CustomFeeTokenForErc20Deposit, - isL2NetworkWithCustomFeeToken, + getParentCustomFeeTokenAllowance, + approveParentCustomFeeTokenForErc20Deposit, + isArbitrumNetworkWithCustomFeeToken, } from './custom-fee-token/customFeeTokenTestHelpers' import { itOnlyWhenCustomGasToken } from './custom-fee-token/mochaExtensions' + const depositAmount = BigNumber.from(100) const withdrawalAmount = BigNumber.from(10) @@ -61,38 +62,38 @@ describe('standard ERC20', () => { // test globals let testState: { - l1Signer: Signer - l2Signer: Signer + parentSigner: Signer + childSigner: Signer erc20Bridger: Erc20Bridger - l2Network: L2Network - l1Token: TestERC20 + childChain: ArbitrumNetwork + parentToken: TestERC20 } before('init', async () => { const setup = await testSetup() - await fundL1(setup.l1Signer) - await fundL2(setup.l2Signer) - const deployErc20 = new TestERC20__factory().connect(setup.l1Signer) + await fundParentSigner(setup.parentSigner) + await fundChildSigner(setup.childSigner) + const deployErc20 = new TestERC20__factory().connect(setup.parentSigner) const testToken = await deployErc20.deploy() await testToken.deployed() await (await testToken.mint()).wait() - testState = { ...setup, l1Token: testToken } + testState = { ...setup, parentToken: testToken } }) itOnlyWhenCustomGasToken( 'approves custom gas token to be spent by the relevant gateway', async () => { - const { l1Signer, erc20Bridger } = await testSetup() + const { parentSigner, erc20Bridger } = await testSetup() const gatewayAddress = await erc20Bridger.getL1GatewayAddress( - testState.l1Token.address, - l1Signer.provider! + testState.parentToken.address, + parentSigner.provider! ) - const initialAllowance = await getL1CustomFeeTokenAllowance( - await l1Signer.getAddress(), + const initialAllowance = await getParentCustomFeeTokenAllowance( + await parentSigner.getAddress(), gatewayAddress ) @@ -102,13 +103,13 @@ describe('standard ERC20', () => { ) const tx = await erc20Bridger.approveGasToken({ - l1Signer: l1Signer, - erc20L1Address: testState.l1Token.address, + parentSigner, + erc20ParentAddress: testState.parentToken.address, }) await tx.wait() - const finalAllowance = await getL1CustomFeeTokenAllowance( - await l1Signer.getAddress(), + const finalAllowance = await getParentCustomFeeTokenAllowance( + await parentSigner.getAddress(), gatewayAddress ) @@ -120,26 +121,26 @@ describe('standard ERC20', () => { ) it('deposits erc20', async () => { - if (isL2NetworkWithCustomFeeToken()) { - await approveL1CustomFeeTokenForErc20Deposit( - testState.l1Signer, - testState.l1Token.address + if (isArbitrumNetworkWithCustomFeeToken()) { + await approveParentCustomFeeTokenForErc20Deposit( + testState.parentSigner, + testState.parentToken.address ) } await depositToken({ depositAmount, - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.STANDARD, }) }) const redeemAndTest = async ( - message: L1ToL2MessageWriter, + message: ParentToChildMessageWriter, expectedStatus: 0 | 1, gasLimit?: BigNumber ) => { @@ -150,24 +151,24 @@ describe('standard ERC20', () => { expect(retryRec.blockHash, 'redeemed in same block').to.eq(blockHash) expect(retryRec.to, 'redeemed in same block').to.eq( - testState.l2Network.tokenBridge.l2ERC20Gateway + testState.childChain.tokenBridge.l2ERC20Gateway ) expect(retryRec.status, 'tx didnt fail').to.eq(expectedStatus) expect(await message.status(), 'message status').to.eq( expectedStatus === 0 - ? L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 - : L1ToL2MessageStatus.REDEEMED + ? ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN + : ParentToChildMessageStatus.REDEEMED ) } it('deposit with no funds, manual redeem', async () => { const { waitRes } = await depositToken({ depositAmount, - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN, expectedGatewayType: GatewayType.STANDARD, retryableOverrides: { gasLimit: { base: BigNumber.from(0) }, @@ -181,11 +182,11 @@ describe('standard ERC20', () => { it('deposit with low funds, manual redeem', async () => { const { waitRes } = await depositToken({ depositAmount, - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN, expectedGatewayType: GatewayType.STANDARD, retryableOverrides: { gasLimit: { base: BigNumber.from(5) }, @@ -201,11 +202,11 @@ describe('standard ERC20', () => { // redeem transaction const { waitRes } = await depositToken({ depositAmount, - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN, expectedGatewayType: GatewayType.STANDARD, retryableOverrides: { gasLimit: { base: BigNumber.from(21000) }, @@ -217,11 +218,11 @@ describe('standard ERC20', () => { await waitRes.message.getRetryableCreationReceipt() if (!isDefined(retryableCreation)) throw new Error('Missing retryable creation.') - const l2Receipt = new L2TransactionReceipt(retryableCreation) + const l2Receipt = new ChildTransactionReceipt(retryableCreation) const redeemsScheduled = l2Receipt.getRedeemScheduledEvents() expect(redeemsScheduled.length, 'Unexpected redeem length').to.eq(1) const retryReceipt = - await testState.l2Signer.provider!.getTransactionReceipt( + await testState.childSigner.provider!.getTransactionReceipt( redeemsScheduled[0].retryTxHash ) expect(isDefined(retryReceipt), 'Retry should not exist').to.be.false @@ -233,11 +234,11 @@ describe('standard ERC20', () => { it('deposit with low funds, fails first redeem, succeeds seconds', async () => { const { waitRes } = await depositToken({ depositAmount, - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHAIN, expectedGatewayType: GatewayType.STANDARD, retryableOverrides: { gasLimit: { base: BigNumber.from(5) }, @@ -246,11 +247,11 @@ describe('standard ERC20', () => { }) const arbRetryableTx = ArbRetryableTx__factory.connect( ARB_RETRYABLE_TX_ADDRESS, - testState.l2Signer.provider! + testState.childSigner.provider! ) const nInterface = NodeInterface__factory.connect( NODE_INTERFACE_ADDRESS, - testState.l2Signer.provider! + testState.childSigner.provider! ) const gasComponents = await nInterface.callStatic.gasEstimateComponents( arbRetryableTx.address, @@ -267,18 +268,18 @@ describe('standard ERC20', () => { }) it('withdraws erc20', async function () { - const l2TokenAddr = await testState.erc20Bridger.getL2ERC20Address( - testState.l1Token.address, - testState.l1Signer.provider! + const l2TokenAddr = await testState.erc20Bridger.getChildERC20Address( + testState.parentToken.address, + testState.parentSigner.provider! ) - const l2Token = testState.erc20Bridger.getL2TokenContract( - testState.l2Signer.provider!, + const l2Token = testState.erc20Bridger.getChildTokenContract( + testState.childSigner.provider!, l2TokenAddr ) // 5 deposits above - increase this number if more deposit tests added const startBalance = depositAmount.mul(5) const l2BalanceStart = await l2Token.balanceOf( - await testState.l2Signer.getAddress() + await testState.childSigner.getAddress() ) expect(l2BalanceStart.toString(), 'l2 balance start').to.eq( l2BalanceStart.toString() @@ -286,12 +287,14 @@ describe('standard ERC20', () => { await withdrawToken({ ...testState, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, amount: withdrawalAmount, gatewayType: GatewayType.STANDARD, startBalance: startBalance, - l1Token: ERC20__factory.connect( - testState.l1Token.address, - testState.l1Signer.provider! + parentToken: ERC20__factory.connect( + testState.parentToken.address, + testState.parentSigner.provider! ), }) }) @@ -300,25 +303,25 @@ describe('standard ERC20', () => { await depositToken({ depositAmount, ethDepositAmount: utils.parseEther('0.0005'), - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.STANDARD, }) }) - it('deposits erc20 with extra ETH to a specific L2 address', async () => { + it('deposits erc20 with extra ETH to a specific child chain address', async () => { const randomAddress = Wallet.createRandom().address await depositToken({ depositAmount, ethDepositAmount: utils.parseEther('0.0005'), - l1TokenAddress: testState.l1Token.address, + parentTokenAddress: testState.parentToken.address, erc20Bridger: testState.erc20Bridger, - l1Signer: testState.l1Signer, - l2Signer: testState.l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner: testState.parentSigner, + childSigner: testState.childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.STANDARD, destinationAddress: randomAddress, }) diff --git a/tests/integration/testHelpers.ts b/tests/integration/testHelpers.ts index 3d7cc0ba1f..7ad2ece9a9 100644 --- a/tests/integration/testHelpers.ts +++ b/tests/integration/testHelpers.ts @@ -26,16 +26,13 @@ import { parseEther } from '@ethersproject/units' import { config, getSigner, testSetup } from '../../scripts/testSetup' import { Signer, Wallet } from 'ethers' -import { - Erc20Bridger, - L1ToL2MessageStatus, - L2ToL1MessageStatus, -} from '../../src' -import { L2Network } from '../../src/lib/dataEntities/networks' -import { GasOverrides } from '../../src/lib/message/L1ToL2MessageGasEstimator' +import { Erc20Bridger, ChildToParentMessageStatus } from '../../src' +import { ParentToChildMessageStatus } from '../../src/lib/message/ParentToChildMessage' +import { ArbitrumNetwork } from '../../src/lib/dataEntities/networks' +import { GasOverrides } from '../../src/lib/message/ParentToChildMessageGasEstimator' import { ArbSdkError } from '../../src/lib/dataEntities/errors' import { ERC20 } from '../../src/lib/abi/ERC20' -import { isL2NetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' +import { isArbitrumNetworkWithCustomFeeToken } from './custom-fee-token/customFeeTokenTestHelpers' import { ERC20__factory } from '../../src/lib/abi/factories/ERC20__factory' export const preFundAmount = parseEther('0.1') @@ -60,9 +57,9 @@ interface WithdrawalParams { startBalance: BigNumber amount: BigNumber erc20Bridger: Erc20Bridger - l1Token: ERC20 - l2Signer: Signer - l1Signer: Signer + parentToken: ERC20 + childSigner: Signer + parentSigner: Signer gatewayType: GatewayType } @@ -88,114 +85,116 @@ export const mineUntilStop = async ( export const withdrawToken = async (params: WithdrawalParams) => { const withdrawalParams = await params.erc20Bridger.getWithdrawalRequest({ amount: params.amount, - erc20l1Address: params.l1Token.address, - destinationAddress: await params.l2Signer.getAddress(), - from: await params.l2Signer.getAddress(), + erc20ParentAddress: params.parentToken.address, + destinationAddress: await params.childSigner.getAddress(), + from: await params.childSigner.getAddress(), }) - const l1GasEstimate = await withdrawalParams.estimateL1GasLimit( - params.l1Signer.provider! + const parentChainGasEstimate = await withdrawalParams.estimateParentGasLimit( + params.parentSigner.provider! ) const withdrawRes = await params.erc20Bridger.withdraw({ - destinationAddress: await params.l2Signer.getAddress(), + destinationAddress: await params.childSigner.getAddress(), amount: params.amount, - erc20l1Address: params.l1Token.address, - l2Signer: params.l2Signer, + erc20ParentAddress: params.parentToken.address, + childSigner: params.childSigner, }) const withdrawRec = await withdrawRes.wait() expect(withdrawRec.status).to.equal(1, 'initiate token withdraw txn failed') - const message = (await withdrawRec.getL2ToL1Messages(params.l1Signer))[0] + const message = ( + await withdrawRec.getChildToParentMessages(params.parentSigner) + )[0] expect(message, 'withdraw message not found').to.exist - const messageStatus = await message.status(params.l2Signer.provider!) + const messageStatus = await message.status(params.childSigner.provider!) expect(messageStatus, `invalid withdraw status`).to.eq( - L2ToL1MessageStatus.UNCONFIRMED + ChildToParentMessageStatus.UNCONFIRMED ) - const l2TokenAddr = await params.erc20Bridger.getL2ERC20Address( - params.l1Token.address, - params.l1Signer.provider! + const childChainTokenAddr = await params.erc20Bridger.getChildERC20Address( + params.parentToken.address, + params.parentSigner.provider! ) - const l2Token = params.erc20Bridger.getL2TokenContract( - params.l2Signer.provider!, - l2TokenAddr + const childChainToken = params.erc20Bridger.getChildTokenContract( + params.childSigner.provider!, + childChainTokenAddr ) - const testWalletL2Balance = await l2Token.balanceOf( - await params.l2Signer.getAddress() + const testWalletChildChainBalance = await childChainToken.balanceOf( + await params.childSigner.getAddress() ) expect( - testWalletL2Balance.toNumber(), + testWalletChildChainBalance.toNumber(), 'token withdraw balance not deducted' ).to.eq(params.startBalance.sub(params.amount).toNumber()) - const walletAddress = await params.l1Signer.getAddress() + const walletAddress = await params.parentSigner.getAddress() const gatewayAddress = await params.erc20Bridger.getL2GatewayAddress( - params.l1Token.address, - params.l2Signer.provider! + params.parentToken.address, + params.childSigner.provider! ) const { expectedL2Gateway } = getGateways( params.gatewayType, - params.erc20Bridger.l2Network + params.erc20Bridger.childChain ) expect(gatewayAddress, 'Gateway is not custom gateway').to.eq( expectedL2Gateway ) const gatewayWithdrawEvents = await params.erc20Bridger.getL2WithdrawalEvents( - params.l2Signer.provider!, + params.childSigner.provider!, gatewayAddress, { fromBlock: withdrawRec.blockNumber, toBlock: 'latest' }, - params.l1Token.address, + params.parentToken.address, walletAddress ) expect(gatewayWithdrawEvents.length).to.equal(1, 'token query failed') - const balBefore = await params.l1Token.balanceOf( - await params.l1Signer.getAddress() + const balBefore = await params.parentToken.balanceOf( + await params.parentSigner.getAddress() ) - // whilst waiting for status we miner on both l1 and l2 - const miner1 = Wallet.createRandom().connect(params.l1Signer.provider!) - const miner2 = Wallet.createRandom().connect(params.l2Signer.provider!) - await fundL1(miner1, parseEther('1')) - await fundL2(miner2, parseEther('1')) + // whilst waiting for status we miner on both parent and child chains + const miner1 = Wallet.createRandom().connect(params.parentSigner.provider!) + const miner2 = Wallet.createRandom().connect(params.childSigner.provider!) + await fundParentSigner(miner1, parseEther('1')) + await fundChildSigner(miner2, parseEther('1')) const state = { mining: true } await Promise.race([ mineUntilStop(miner1, state), mineUntilStop(miner2, state), - message.waitUntilReadyToExecute(params.l2Signer.provider!), + message.waitUntilReadyToExecute(params.childSigner.provider!), ]) state.mining = false expect( - await message.status(params.l2Signer.provider!), + await message.status(params.childSigner.provider!), 'confirmed status' - ).to.eq(L2ToL1MessageStatus.CONFIRMED) + ).to.eq(ChildToParentMessageStatus.CONFIRMED) - const execTx = await message.execute(params.l2Signer.provider!) + const execTx = await message.execute(params.childSigner.provider!) const execRec = await execTx.wait() expect( execRec.gasUsed.toNumber(), 'Gas used greater than estimate' - ).to.be.lessThan(l1GasEstimate.toNumber()) + ).to.be.lessThan(parentChainGasEstimate.toNumber()) expect( - await message.status(params.l2Signer.provider!), + await message.status(params.childSigner.provider!), 'executed status' - ).to.eq(L2ToL1MessageStatus.EXECUTED) + ).to.eq(ChildToParentMessageStatus.EXECUTED) - const balAfter = await params.l1Token.balanceOf( - await params.l1Signer.getAddress() + const balAfter = await params.parentToken.balanceOf( + await params.parentSigner.getAddress() ) expect(balBefore.add(params.amount).toString(), 'Not withdrawn').to.eq( balAfter.toString() ) } -const getGateways = (gatewayType: GatewayType, l2Network: L2Network) => { +const getGateways = (gatewayType: GatewayType, l2Network: ArbitrumNetwork) => { switch (gatewayType) { case GatewayType.CUSTOM: return { @@ -220,18 +219,18 @@ const getGateways = (gatewayType: GatewayType, l2Network: L2Network) => { /** * Deposits a token and tests that it occurred correctly * @param depositAmount - * @param l1TokenAddress + * @param parentTokenAddress * @param erc20Bridger - * @param l1Signer - * @param l2Signer + * @param parentSigner + * @param childSigner */ export const depositToken = async ({ depositAmount, ethDepositAmount, - l1TokenAddress, + parentTokenAddress, erc20Bridger, - l1Signer, - l2Signer, + parentSigner, + childSigner, expectedStatus, expectedGatewayType, retryableOverrides, @@ -239,50 +238,54 @@ export const depositToken = async ({ }: { depositAmount: BigNumber ethDepositAmount?: BigNumber - l1TokenAddress: string + parentTokenAddress: string erc20Bridger: Erc20Bridger - l1Signer: Signer - l2Signer: Signer - expectedStatus: L1ToL2MessageStatus + parentSigner: Signer + childSigner: Signer + expectedStatus: ParentToChildMessageStatus expectedGatewayType: GatewayType retryableOverrides?: GasOverrides destinationAddress?: string }) => { await ( await erc20Bridger.approveToken({ - erc20L1Address: l1TokenAddress, - l1Signer: l1Signer, + erc20ParentAddress: parentTokenAddress, + parentSigner, }) ).wait() - const senderAddress = await l1Signer.getAddress() - const expectedL1GatewayAddress = await erc20Bridger.getL1GatewayAddress( - l1TokenAddress, - l1Signer.provider! + const senderAddress = await parentSigner.getAddress() + const expectedParentChainGatewayAddress = + await erc20Bridger.getL1GatewayAddress( + parentTokenAddress, + parentSigner.provider! + ) + const parentToken = erc20Bridger.getParentTokenContract( + parentSigner.provider!, + parentTokenAddress ) - const l1Token = erc20Bridger.getL1TokenContract( - l1Signer.provider!, - l1TokenAddress - ) - const allowance = await l1Token.allowance( + const allowance = await parentToken.allowance( senderAddress, - expectedL1GatewayAddress + expectedParentChainGatewayAddress ) expect(allowance.eq(Erc20Bridger.MAX_APPROVAL), 'set token allowance failed') .to.be.true - if (isL2NetworkWithCustomFeeToken()) { + if (isArbitrumNetworkWithCustomFeeToken()) { await ( await erc20Bridger.approveGasToken({ - l1Signer, - erc20L1Address: l1TokenAddress, + parentSigner, + erc20ParentAddress: parentTokenAddress, }) ).wait() const feeTokenAllowance = await ERC20__factory.connect( erc20Bridger.nativeToken!, - l1Signer - ).allowance(await l1Signer.getAddress(), expectedL1GatewayAddress) + parentSigner + ).allowance( + await parentSigner.getAddress(), + expectedParentChainGatewayAddress + ) expect( feeTokenAllowance.eq(Erc20Bridger.MAX_APPROVAL), @@ -290,18 +293,18 @@ export const depositToken = async ({ ).to.be.true } - const initialBridgeTokenBalance = await l1Token.balanceOf( - expectedL1GatewayAddress + const initialBridgeTokenBalance = await parentToken.balanceOf( + expectedParentChainGatewayAddress ) - const tokenBalL1Before = await l1Token.balanceOf(senderAddress) - const ethBalL2Before = await l2Signer.provider!.getBalance( + const parentTokenBalanceBefore = await parentToken.balanceOf(senderAddress) + const childChainEthBalanceBefore = await childSigner.provider!.getBalance( destinationAddress || senderAddress ) const depositRes = await erc20Bridger.deposit({ - l1Signer: l1Signer, - l2Provider: l2Signer.provider!, - erc20L1Address: l1TokenAddress, + parentSigner, + childProvider: childSigner.provider!, + erc20ParentAddress: parentTokenAddress, amount: depositAmount, retryableGasOverrides: retryableOverrides, maxSubmissionCost: ethDepositAmount, @@ -310,8 +313,8 @@ export const depositToken = async ({ }) const depositRec = await depositRes.wait() - const finalBridgeTokenBalance = await l1Token.balanceOf( - expectedL1GatewayAddress + const finalBridgeTokenBalance = await parentToken.balanceOf( + expectedParentChainGatewayAddress ) expect( finalBridgeTokenBalance.toNumber(), @@ -322,80 +325,86 @@ export const depositToken = async ({ ? 0 : initialBridgeTokenBalance.add(depositAmount).toNumber() ) - const tokenBalL1After = await l1Token.balanceOf(senderAddress) - expect(tokenBalL1After.toString(), 'user bal after').to.eq( - tokenBalL1Before.sub(depositAmount).toString() + const parentTokenBalanceAfter = await parentToken.balanceOf(senderAddress) + expect(parentTokenBalanceAfter.toString(), 'user bal after').to.eq( + parentTokenBalanceBefore.sub(depositAmount).toString() ) - const waitRes = await depositRec.waitForL2(l2Signer) + const waitRes = await depositRec.waitForChildTx(childSigner) - const ethBalL2After = await l2Signer.provider!.getBalance( + const childChainEthBalanceAfter = await childSigner.provider!.getBalance( destinationAddress || senderAddress ) expect(waitRes.status, 'Unexpected status').to.eq(expectedStatus) if (retryableOverrides) { return { - l1Token, + parentToken, waitRes, } } const { expectedL1Gateway, expectedL2Gateway } = getGateways( expectedGatewayType, - erc20Bridger.l2Network + erc20Bridger.childChain ) - const l1Gateway = await erc20Bridger.getL1GatewayAddress( - l1TokenAddress, - l1Signer.provider! + const parentChainGateway = await erc20Bridger.getL1GatewayAddress( + parentTokenAddress, + parentSigner.provider! + ) + expect(parentChainGateway, 'incorrect parent chain gateway address').to.eq( + expectedL1Gateway ) - expect(l1Gateway, 'incorrect l1 gateway address').to.eq(expectedL1Gateway) - const l2Gateway = await erc20Bridger.getL2GatewayAddress( - l1TokenAddress, - l2Signer.provider! + const childChainGateway = await erc20Bridger.getL2GatewayAddress( + parentTokenAddress, + childSigner.provider! + ) + expect(childChainGateway, 'incorrect child chain gateway address').to.eq( + expectedL2Gateway ) - expect(l2Gateway, 'incorrect l2 gateway address').to.eq(expectedL2Gateway) - const l2Erc20Addr = await erc20Bridger.getL2ERC20Address( - l1TokenAddress, - l1Signer.provider! + const childChainErc20Addr = await erc20Bridger.getChildERC20Address( + parentTokenAddress, + parentSigner.provider! ) - const l2Token = erc20Bridger.getL2TokenContract( - l2Signer.provider!, - l2Erc20Addr + const childChainToken = erc20Bridger.getChildTokenContract( + childSigner.provider!, + childChainErc20Addr ) - const l1Erc20Addr = await erc20Bridger.getL1ERC20Address( - l2Erc20Addr, - l2Signer.provider! + const parentChainErc20Addr = await erc20Bridger.getParentERC20Address( + childChainErc20Addr, + childSigner.provider! ) - expect(l1Erc20Addr).to.equal( - l1TokenAddress, + expect(parentChainErc20Addr).to.equal( + parentTokenAddress, 'getERC20L1Address/getERC20L2Address failed with proper token address' ) - const tokenBalL2After = await l2Token.balanceOf( + const tokenBalOnChildChainAfter = await childChainToken.balanceOf( destinationAddress || senderAddress ) // only check for standard deposits if (!destinationAddress && !ethDepositAmount) { expect( - tokenBalL2After.eq(depositAmount), - 'l2 wallet not updated after deposit' + tokenBalOnChildChainAfter.eq(depositAmount), + 'child wallet not updated after deposit' ).to.be.true } // batched token+eth if (ethDepositAmount) { expect( - ethBalL2After.gte(ethBalL2Before.add(ethDepositAmount)), - 'l2 wallet not updated with the extra eth deposit' + childChainEthBalanceAfter.gte( + childChainEthBalanceBefore.add(ethDepositAmount) + ), + 'child wallet not updated with the extra eth deposit' ).to.be.true } - return { l1Token, waitRes, l2Token } + return { parentToken, waitRes, childChainToken } } const fund = async ( @@ -412,18 +421,18 @@ const fund = async ( ).wait() } -export const fundL1 = async ( - l1Signer: Signer, +export const fundParentSigner = async ( + parentSigner: Signer, amount?: BigNumber ): Promise => { - await fund(l1Signer, amount, config.ethKey) + await fund(parentSigner, amount, config.ethKey) } -export const fundL2 = async ( - l2Signer: Signer, +export const fundChildSigner = async ( + childSigner: Signer, amount?: BigNumber ): Promise => { - await fund(l2Signer, amount, config.arbKey) + await fund(childSigner, amount, config.arbKey) } export const wait = (ms = 0): Promise => { @@ -434,10 +443,10 @@ export const skipIfMainnet = (() => { let chainId: number return async (testContext: Mocha.Context) => { if (!chainId) { - const { l1Network } = await testSetup() - chainId = l1Network.chainID + const { childChain } = await testSetup() + chainId = childChain.chainId } - if (chainId === 1) { + if (chainId === 42161 || chainId === 42170) { console.error("You're writing to the chain on mainnet lol stop") testContext.skip() } diff --git a/tests/integration/weth.test.ts b/tests/integration/weth.test.ts index 2a68c7eba3..1d35219ad2 100644 --- a/tests/integration/weth.test.ts +++ b/tests/integration/weth.test.ts @@ -20,14 +20,14 @@ import { expect } from 'chai' import { parseEther } from '@ethersproject/units' import { AeWETH__factory } from '../../src/lib/abi/factories/AeWETH__factory' import { - fundL1, - fundL2, + fundParentSigner, + fundChildSigner, skipIfMainnet, withdrawToken, GatewayType, depositToken, } from './testHelpers' -import { L1ToL2MessageStatus } from '../../src' +import { ParentToChildMessageStatus } from '../../src' import { Wallet } from 'ethers' import { testSetup } from '../../scripts/testSetup' import { ERC20__factory } from '../../src/lib/abi/factories/ERC20__factory' @@ -39,60 +39,61 @@ describeOnlyWhenEth('WETH', async () => { }) it('deposit WETH', async () => { - const { l2Network, l1Signer, l2Signer, erc20Bridger } = await testSetup() + const { childChain, parentSigner, childSigner, erc20Bridger } = + await testSetup() - const l1WethAddress = l2Network.tokenBridge.l1Weth + const l1WethAddress = childChain.tokenBridge.l1Weth const wethToWrap = parseEther('0.00001') const wethToDeposit = parseEther('0.0000001') - await fundL1(l1Signer, parseEther('1')) + await fundParentSigner(parentSigner, parseEther('1')) const l2WETH = AeWETH__factory.connect( - l2Network.tokenBridge.l2Weth, - l2Signer.provider! + childChain.tokenBridge.l2Weth, + childSigner.provider! ) expect( - (await l2WETH.balanceOf(await l2Signer.getAddress())).toString(), + (await l2WETH.balanceOf(await childSigner.getAddress())).toString(), 'start balance weth' ).to.eq('0') - const l1WETH = AeWETH__factory.connect(l1WethAddress, l1Signer) + const l1WETH = AeWETH__factory.connect(l1WethAddress, parentSigner) const res = await l1WETH.deposit({ value: wethToWrap, }) await res.wait() await depositToken({ depositAmount: wethToDeposit, - l1TokenAddress: l1WethAddress, + parentTokenAddress: l1WethAddress, erc20Bridger, - l1Signer, - l2Signer, - expectedStatus: L1ToL2MessageStatus.REDEEMED, + parentSigner, + childSigner, + expectedStatus: ParentToChildMessageStatus.REDEEMED, expectedGatewayType: GatewayType.WETH, }) const l2WethGateway = await erc20Bridger.getL2GatewayAddress( l1WethAddress, - l2Signer.provider! + childSigner.provider! ) expect(l2WethGateway, 'l2 weth gateway').to.eq( - l2Network.tokenBridge.l2WethGateway + childChain.tokenBridge.l2WethGateway ) - const l2Token = erc20Bridger.getL2TokenContract( - l2Signer.provider!, - l2Network.tokenBridge.l2Weth + const l2Token = erc20Bridger.getChildTokenContract( + childSigner.provider!, + childChain.tokenBridge.l2Weth ) - expect(l2Token.address, 'l2 weth').to.eq(l2Network.tokenBridge.l2Weth) + expect(l2Token.address, 'l2 weth').to.eq(childChain.tokenBridge.l2Weth) // now try to withdraw the funds - await fundL2(l2Signer) - const l2Weth = AeWETH__factory.connect(l2Token.address, l2Signer) + await fundChildSigner(childSigner) + const l2Weth = AeWETH__factory.connect(l2Token.address, childSigner) const randomAddr = Wallet.createRandom().address await ( - await l2Weth.connect(l2Signer).withdrawTo(randomAddr, wethToDeposit) + await l2Weth.connect(childSigner).withdrawTo(randomAddr, wethToDeposit) ).wait() - const afterBalance = await l2Signer.provider!.getBalance(randomAddr) + const afterBalance = await childSigner.provider!.getBalance(randomAddr) expect(afterBalance.toString(), 'balance after').to.eq( wethToDeposit.toString() @@ -103,13 +104,14 @@ describeOnlyWhenEth('WETH', async () => { const wethToWrap = parseEther('0.00001') const wethToWithdraw = parseEther('0.00000001') - const { l2Network, l1Signer, l2Signer, erc20Bridger } = await testSetup() - await fundL1(l1Signer) - await fundL2(l2Signer) + const { childChain, parentSigner, childSigner, erc20Bridger } = + await testSetup() + await fundParentSigner(parentSigner) + await fundChildSigner(childSigner) const l2Weth = AeWETH__factory.connect( - l2Network.tokenBridge.l2Weth, - l2Signer + childChain.tokenBridge.l2Weth, + childSigner ) const res = await l2Weth.deposit({ value: wethToWrap, @@ -121,12 +123,12 @@ describeOnlyWhenEth('WETH', async () => { amount: wethToWithdraw, erc20Bridger: erc20Bridger, gatewayType: GatewayType.WETH, - l1Signer: l1Signer, - l1Token: ERC20__factory.connect( - l2Network.tokenBridge.l1Weth, - l1Signer.provider! + parentSigner: parentSigner, + parentToken: ERC20__factory.connect( + childChain.tokenBridge.l1Weth, + parentSigner.provider! ), - l2Signer: l2Signer, + childSigner: childSigner, startBalance: wethToWrap, }) }) diff --git a/tests/unit/l2BlocksForL1Block.test.ts b/tests/unit/childBlocksForParentBlock.test.ts similarity index 53% rename from tests/unit/l2BlocksForL1Block.test.ts rename to tests/unit/childBlocksForParentBlock.test.ts index 2a3e1deb1a..023e669cc1 100644 --- a/tests/unit/l2BlocksForL1Block.test.ts +++ b/tests/unit/childBlocksForParentBlock.test.ts @@ -2,32 +2,32 @@ import { BigNumber } from 'ethers' import { expect } from 'chai' import { JsonRpcProvider } from '@ethersproject/providers' import { - getBlockRangesForL1Block, - getFirstBlockForL1Block, + getBlockRangesForL1Block as getBlockRangesForParentBlock, + getFirstBlockForL1Block as getFirstBlockForParentBlock, } from '../../src/lib/utils/lib' import { ArbitrumProvider } from '../../src/lib/utils/arbProvider' import { ArbBlock } from '../../src/lib/dataEntities/rpc' -describe('L2 blocks lookup for an L1 block', () => { +describe('Child blocks lookup for a Parent block', () => { const provider = new JsonRpcProvider('https://arb1.arbitrum.io/rpc') const arbProvider = new ArbitrumProvider(provider) - async function validateL2Blocks({ - l2Blocks, - l2BlocksCount, + async function validateChildBlocks({ + childBlocks, + childBlocksCount, type = 'number', }: { - l2Blocks: (number | undefined)[] - l2BlocksCount: number + childBlocks: (number | undefined)[] + childBlocksCount: number type?: 'number' | 'undefined' }) { - if (l2Blocks.length !== l2BlocksCount) { + if (childBlocks.length !== childBlocksCount) { throw new Error( - `Expected L2 block range to have the array length of ${l2BlocksCount}, got ${l2Blocks.length}.` + `Expected Child block range to have the array length of ${childBlocksCount}, got ${childBlocks.length}.` ) } - if (l2Blocks.some(block => typeof block !== type)) { + if (childBlocks.some(block => typeof block !== type)) { throw new Error(`Expected all blocks to be ${type}.`) } @@ -37,14 +37,14 @@ describe('L2 blocks lookup for an L1 block', () => { const promises: Promise[] = [] - l2Blocks.forEach((l2Block, index) => { - if (!l2Block) { - throw new Error('L2 block is undefined.') + childBlocks.forEach((childBlock, index) => { + if (!childBlock) { + throw new Error('Child block is undefined.') } const isStartBlock = index === 0 - promises.push(arbProvider.getBlock(l2Block)) + promises.push(arbProvider.getBlock(childBlock)) // Search for previous or next block. - promises.push(arbProvider.getBlock(l2Block + (isStartBlock ? -1 : 1))) + promises.push(arbProvider.getBlock(childBlock + (isStartBlock ? -1 : 1))) }) const [startBlock, blockBeforeStartBlock, endBlock, blockAfterEndBlock] = @@ -55,48 +55,52 @@ describe('L2 blocks lookup for an L1 block', () => { if (startBlock && blockBeforeStartBlock) { const startBlockCondition = startBlock.gt(blockBeforeStartBlock) - // Check if Arbitrum start block is the first block for this L1 block. + // Check if Arbitrum start block is the first block for this parent block. expect( startBlockCondition, - `L2 block is not the first block in range for L1 block.` + `Child block is not the first block in range for parent block.` ).to.be.true } if (endBlock && blockAfterEndBlock) { const endBlockCondition = endBlock.lt(blockAfterEndBlock) - // Check if Arbitrum end block is the last block for this L1 block. + // Check if Arbitrum end block is the last block for this parent block. expect( endBlockCondition, - `L2 block is not the last block in range for L1 block.` + `Child block is not the last block in range for parent block.` ).to.be.true } } - it('successfully searches for an L2 block range', async function () { - const l2Blocks = await getBlockRangesForL1Block({ + it('successfully searches for an Child block range', async function () { + const childBlocks = await getBlockRangesForParentBlock({ provider: arbProvider, forL1Block: 17926532, // Expected result: 121907680. Narrows down the range to speed up the search. minL2Block: 121800000, maxL2Block: 122000000, }) - await validateL2Blocks({ l2Blocks, l2BlocksCount: 2 }) + await validateChildBlocks({ childBlocks, childBlocksCount: 2 }) }) - it('fails to search for an L2 block range', async function () { - const l2Blocks = await getBlockRangesForL1Block({ + it('fails to search for an Child block range', async function () { + const childBlocks = await getBlockRangesForParentBlock({ provider: arbProvider, forL1Block: 17926533, minL2Block: 121800000, maxL2Block: 122000000, }) - await validateL2Blocks({ l2Blocks, l2BlocksCount: 2, type: 'undefined' }) + await validateChildBlocks({ + childBlocks, + childBlocksCount: 2, + type: 'undefined', + }) }) - it('successfully searches for the first L2 block', async function () { - const l2Blocks = [ - await getFirstBlockForL1Block({ + it('successfully searches for the first Child block', async function () { + const childBlocks = [ + await getFirstBlockForParentBlock({ provider: arbProvider, forL1Block: 17926532, // Expected result: 121907680. Narrows down the range to speed up the search. @@ -104,12 +108,12 @@ describe('L2 blocks lookup for an L1 block', () => { maxL2Block: 122000000, }), ] - await validateL2Blocks({ l2Blocks, l2BlocksCount: 1 }) + await validateChildBlocks({ childBlocks, childBlocksCount: 1 }) }) - it('fails to search for the first L2 block, while not using `allowGreater` flag', async function () { - const l2Blocks = [ - await getFirstBlockForL1Block({ + it('fails to search for the first Child block, while not using `allowGreater` flag', async function () { + const childBlocks = [ + await getFirstBlockForParentBlock({ provider: arbProvider, forL1Block: 17926533, allowGreater: false, @@ -117,12 +121,16 @@ describe('L2 blocks lookup for an L1 block', () => { maxL2Block: 122000000, }), ] - await validateL2Blocks({ l2Blocks, l2BlocksCount: 1, type: 'undefined' }) + await validateChildBlocks({ + childBlocks, + childBlocksCount: 1, + type: 'undefined', + }) }) - it('successfully searches for the first L2 block, while using `allowGreater` flag', async function () { - const l2Blocks = [ - await getFirstBlockForL1Block({ + it('successfully searches for the first Child block, while using `allowGreater` flag', async function () { + const childBlocks = [ + await getFirstBlockForParentBlock({ provider: arbProvider, forL1Block: 17926533, allowGreater: true, @@ -131,6 +139,6 @@ describe('L2 blocks lookup for an L1 block', () => { maxL2Block: 122000000, }), ] - await validateL2Blocks({ l2Blocks, l2BlocksCount: 1 }) + await validateChildBlocks({ childBlocks, childBlocksCount: 1 }) }) }) diff --git a/tests/unit/l2toL1MessageEvents.test.ts b/tests/unit/childToParentMessageEvents.test.ts similarity index 79% rename from tests/unit/l2toL1MessageEvents.test.ts rename to tests/unit/childToParentMessageEvents.test.ts index 684e4c61b7..0e5d3f7316 100644 --- a/tests/unit/l2toL1MessageEvents.test.ts +++ b/tests/unit/childToParentMessageEvents.test.ts @@ -18,29 +18,32 @@ import { Logger, LogLevel } from '@ethersproject/logger' Logger.setLogLevel(LogLevel.ERROR) -import { L2ToL1Message } from '../../src' -import { getL2Network } from '../../src/lib/dataEntities/networks' +import { ChildToParentMessage } from '../../src/lib/message/ChildToParentMessage' +import { + getArbitrumNetwork, + getNitroGenesisBlock, +} from '../../src/lib/dataEntities/networks' import { providers } from 'ethers' import { anything, deepEqual, instance, mock, verify, when } from 'ts-mockito' -describe('L2ToL1Message events', () => { - // L2ToL1Transaction +describe('ChildToParentMessage events', () => { + // ChildToParentTransaction const classicTopic = '0x5baaa87db386365b5c161be377bc3d8e317e8d98d71a3ca7ed7d555340c8f767' - // L2ToL1Tx + // ChildToParentTx const nitroTopic = '0x3e7aafa77dbf186b7fd488006beff893744caa3c4f6f299e8a709fa2087374fc' const arbSys = '0x0000000000000000000000000000000000000064' const createProviderMock = async (networkChoiceOverride?: number) => { - const l2Network = await getL2Network(networkChoiceOverride || 42161) + const l2Network = await getArbitrumNetwork(networkChoiceOverride || 42161) const l2ProviderMock = mock(providers.JsonRpcProvider) - const latestBlock = l2Network.nitroGenesisBlock + 1000 + const latestBlock = getNitroGenesisBlock(l2Network) + 1000 when(l2ProviderMock.getBlockNumber()).thenResolve(latestBlock) when(l2ProviderMock.getNetwork()).thenResolve({ - chainId: l2Network.chainID, + chainId: l2Network.chainId, } as any) when(l2ProviderMock._isProvider).thenReturn(true) when(l2ProviderMock.getLogs(anything())).thenResolve([]) @@ -59,7 +62,7 @@ describe('L2ToL1Message events', () => { const fromBlock = 0 const toBlock = 1000 - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) @@ -79,10 +82,10 @@ describe('L2ToL1Message events', () => { it('does call for nitro events', async () => { const { l2Network, l2Provider, l2ProviderMock } = await createProviderMock() - const fromBlock = l2Network.nitroGenesisBlock - const toBlock = l2Network.nitroGenesisBlock + 500 + const fromBlock = getNitroGenesisBlock(l2Network) + const toBlock = getNitroGenesisBlock(l2Network) + 500 - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) @@ -103,9 +106,9 @@ describe('L2ToL1Message events', () => { it('does call for classic and nitro events', async () => { const { l2Network, l2Provider, l2ProviderMock } = await createProviderMock() const fromBlock = 0 - const toBlock = l2Network.nitroGenesisBlock + 500 + const toBlock = getNitroGenesisBlock(l2Network) + 500 - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) @@ -117,7 +120,7 @@ describe('L2ToL1Message events', () => { address: arbSys, topics: [classicTopic], fromBlock: fromBlock, - toBlock: l2Network.nitroGenesisBlock, + toBlock: getNitroGenesisBlock(l2Network), }) ) ).once() @@ -126,7 +129,7 @@ describe('L2ToL1Message events', () => { deepEqual({ address: arbSys, topics: [nitroTopic], - fromBlock: l2Network.nitroGenesisBlock, + fromBlock: getNitroGenesisBlock(l2Network), toBlock: toBlock, }) ) @@ -138,7 +141,7 @@ describe('L2ToL1Message events', () => { const fromBlock = 'earliest' const toBlock = 'latest' - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) @@ -150,7 +153,7 @@ describe('L2ToL1Message events', () => { address: arbSys, topics: [classicTopic], fromBlock: 0, - toBlock: l2Network.nitroGenesisBlock, + toBlock: getNitroGenesisBlock(l2Network), }) ) ).once() @@ -159,7 +162,7 @@ describe('L2ToL1Message events', () => { deepEqual({ address: arbSys, topics: [nitroTopic], - fromBlock: l2Network.nitroGenesisBlock, + fromBlock: getNitroGenesisBlock(l2Network), toBlock: 'latest', }) ) @@ -168,10 +171,10 @@ describe('L2ToL1Message events', () => { it('does call for only nitro for latest', async () => { const { l2Network, l2Provider, l2ProviderMock } = await createProviderMock() - const fromBlock = l2Network.nitroGenesisBlock + 2 + const fromBlock = getNitroGenesisBlock(l2Network) + 2 const toBlock = 'latest' - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) @@ -194,7 +197,7 @@ describe('L2ToL1Message events', () => { const fromBlock = 'earliest' const toBlock = 'latest' - await L2ToL1Message.getL2ToL1Events(l2Provider, { + await ChildToParentMessage.getChildToParentEvents(l2Provider, { fromBlock: fromBlock, toBlock: toBlock, }) diff --git a/tests/unit/multicall.test.ts b/tests/unit/multicall.test.ts index 063f794ff4..04e30a3b3b 100644 --- a/tests/unit/multicall.test.ts +++ b/tests/unit/multicall.test.ts @@ -1,6 +1,9 @@ 'use strict' -import { getL2Network } from '../../src/lib/dataEntities/networks' +import { + getArbitrumNetwork, + getNitroGenesisBlock, +} from '../../src/lib/dataEntities/networks' import { providers } from 'ethers' import { mock, when, anything, instance, deepEqual } from 'ts-mockito' import { expect } from 'chai' @@ -9,18 +12,18 @@ import { MultiCaller } from '../../src' describe('Multicall', () => { const createProviderMock = async (networkChoiceOverride?: number) => { - const l2Network = await getL2Network(networkChoiceOverride || 42161) + const l2Network = await getArbitrumNetwork(networkChoiceOverride || 42161) const l2ProviderMock = mock(providers.JsonRpcProvider) - const latestBlock = l2Network.nitroGenesisBlock + 1000 + const latestBlock = getNitroGenesisBlock(l2Network) + 1000 when(l2ProviderMock.getBlockNumber()).thenResolve(latestBlock) when(l2ProviderMock.getNetwork()).thenResolve({ - chainId: l2Network.chainID, + chainId: l2Network.chainId, } as any) when(l2ProviderMock._isProvider).thenReturn(true) when(l2ProviderMock.getLogs(anything())).thenResolve([]) - /* + /* This test data is taken from an actual example of a mainnet multicall. To produce this data we do the following: 1. Pass mainnet args to the multicall class, instantiated with a mock provider 2. Capture the .call request that was made on the provider diff --git a/tests/unit/network.test.ts b/tests/unit/network.test.ts index 256b3db66f..75d41559d4 100644 --- a/tests/unit/network.test.ts +++ b/tests/unit/network.test.ts @@ -1,17 +1,17 @@ import { expect } from 'chai' import { resetNetworksToDefault, - addCustomNetwork, - getL1Network, - getL2Network, - l1Networks, - l2Networks, + addCustomArbitrumNetwork, + getArbitrumNetwork, + l2Networks as arbitrumNetworks, + getChildrenForNetwork, + isParentNetwork, + getMulticallAddress, } from '../../src/lib/dataEntities/networks' const ethereumMainnetChainId = 1 const arbitrumOneChainId = 42161 -const mockL1ChainId = 111111 const mockL2ChainId = 222222 const mockL3ChainId = 99999999 @@ -22,297 +22,187 @@ describe('Networks', async () => { describe('adding networks', () => { it('adds a custom L2 network', async function () { - const arbitrumOne = await getL2Network(arbitrumOneChainId) + const arbitrumOne = await getArbitrumNetwork(arbitrumOneChainId) - const customL2Network = { + const customArbitrumNetwork = { ...arbitrumOne, - chainID: mockL2ChainId, - partnerChainID: ethereumMainnetChainId, + chainId: mockL2ChainId, + parentChainId: ethereumMainnetChainId, isArbitrum: true, isCustom: true, } as const - addCustomNetwork({ customL2Network }) + addCustomArbitrumNetwork(customArbitrumNetwork) - expect(await getL2Network(mockL2ChainId)).to.be.ok + expect(await getArbitrumNetwork(mockL2ChainId)).to.be.ok - // assert network was added as child - const l1Network = await getL1Network(customL2Network.partnerChainID) - expect(l1Network.partnerChainIDs).to.include(mockL2ChainId) // assert network has correct parent - const l2Network = await getL2Network(customL2Network.chainID) - expect(l2Network.partnerChainID).to.equal(ethereumMainnetChainId) - }) - - it('adds a custom L1 and L2 network', async function () { - const ethereumMainnet = await getL1Network(ethereumMainnetChainId) - const arbitrumOne = await getL2Network(arbitrumOneChainId) - - const customL1Network = { - ...ethereumMainnet, - chainID: mockL1ChainId, - isArbitrum: false, - isCustom: true, - } as const - - const customL2Network = { - ...arbitrumOne, - partnerChainID: mockL1ChainId, - chainID: mockL2ChainId, - isArbitrum: true, - isCustom: true, - } as const - - addCustomNetwork({ customL1Network, customL2Network }) - - expect(await getL1Network(mockL1ChainId)).to.be.ok - expect(await getL2Network(mockL2ChainId)).to.be.ok - - // assert network was added as child - const l1Network = await getL1Network(mockL1ChainId) - expect(l1Network.partnerChainIDs).to.include(mockL2ChainId) - // assert network has correct parent - const l2Network = await getL2Network(customL2Network.chainID) - expect(l2Network.partnerChainID).to.equal(mockL1ChainId) + const arbitrumNetwork = await getArbitrumNetwork( + customArbitrumNetwork.chainId + ) + expect(arbitrumNetwork.parentChainId).to.equal(ethereumMainnetChainId) }) it('adds a custom L3 network', async function () { - const arbitrumOne = await getL2Network(arbitrumOneChainId) + const arbitrumOne = await getArbitrumNetwork(arbitrumOneChainId) - const customL2Network = { + const customArbitrumNetwork = { ...arbitrumOne, - chainID: mockL3ChainId, - partnerChainID: arbitrumOneChainId, + chainId: mockL3ChainId, + parentChainId: arbitrumOneChainId, isArbitrum: true, isCustom: true, } as const - addCustomNetwork({ customL2Network }) + addCustomArbitrumNetwork(customArbitrumNetwork) - expect(await getL2Network(mockL3ChainId)).to.be.ok + expect(await getArbitrumNetwork(mockL3ChainId)).to.be.ok - // assert network was added as child - const l2Network = await getL2Network(customL2Network.partnerChainID) - expect(l2Network.partnerChainIDs).to.include(mockL3ChainId) // assert network has correct parent - const l3Network = await getL2Network(mockL3ChainId) - expect(l3Network.partnerChainID).to.equal(arbitrumOneChainId) + const l3Network = await getArbitrumNetwork(mockL3ChainId) + expect(l3Network.parentChainId).to.equal(arbitrumOneChainId) }) + }) - it('adds a custom L1, L2, and L3 network', async function () { - const ethereumMainnet = await getL1Network(ethereumMainnetChainId) - const arbitrumOne = await getL2Network(arbitrumOneChainId) - - const customL1Network = { - ...ethereumMainnet, - chainID: mockL1ChainId, - isArbitrum: false, - isCustom: true, - } as const - - const customL2Network = { - ...arbitrumOne, - chainID: mockL2ChainId, - partnerChainID: mockL1ChainId, - isArbitrum: true, - isCustom: true, - } as const - - addCustomNetwork({ customL1Network, customL2Network }) + describe('fetching networks', () => { + it('successfully fetches an Arbitrum network with `getArbitrumNetwork`', async function () { + const network = await getArbitrumNetwork(arbitrumOneChainId) + expect(network.chainId).to.be.eq(arbitrumOneChainId) + }) - expect(await getL1Network(mockL1ChainId)).to.be.ok - expect(await getL2Network(mockL2ChainId)).to.be.ok + it('fails to fetch a registered L1 network with `getArbitrumNetwork`', async function () { + try { + await getArbitrumNetwork(ethereumMainnetChainId) + } catch (err) { + // should fail + expect(err).to.be.an('error') + expect((err as Error).message).to.be.eq( + `Unrecognized network ${ethereumMainnetChainId}.` + ) + } + }) - // assert network was added as child - const l1Network = await getL1Network(mockL1ChainId) - expect(l1Network.partnerChainIDs).to.include(mockL2ChainId) - // assert network has correct parent - const l2Network = await getL2Network(mockL2ChainId) - expect(l2Network.partnerChainID).to.equal(mockL1ChainId) + it('successfully fetches an L3 chain with `getArbitrumNetwork`', async function () { + const arbitrumOne = await getArbitrumNetwork(arbitrumOneChainId) const customL3Network = { ...arbitrumOne, - chainID: mockL3ChainId, - partnerChainID: mockL2ChainId, + chainId: mockL3ChainId, + parentChainId: arbitrumOneChainId, isArbitrum: true, isCustom: true, } as const - addCustomNetwork({ customL2Network: customL3Network }) - - expect(await getL2Network(mockL3ChainId)).to.be.ok + addCustomArbitrumNetwork(customL3Network) - // assert network was added as child - const l2NetworkAgain = await getL2Network(mockL2ChainId) - expect(l2NetworkAgain.partnerChainIDs).to.include(mockL3ChainId) + const l3Network = await getArbitrumNetwork(mockL3ChainId) + expect(l3Network.chainId).to.be.eq(mockL3ChainId) // assert network has correct parent - const l3Network = await getL2Network(mockL3ChainId) - expect(l3Network.partnerChainID).to.equal(mockL2ChainId) + expect(l3Network.parentChainId).to.equal(arbitrumOneChainId) }) - it('fails to add a custom L1 and L2 network if they do not match', async function () { - const ethereumMainnet = await getL1Network(ethereumMainnetChainId) - const arbitrumOne = await getL2Network(arbitrumOneChainId) - - const wrongPartnerChainId = 1241244 - - const customL1Network = { - ...ethereumMainnet, - chainID: mockL1ChainId, - isArbitrum: false, - isCustom: true, - } as const - - const customL2Network = { - ...arbitrumOne, - partnerChainID: wrongPartnerChainId, - chainID: mockL2ChainId, - isArbitrum: true, - isCustom: true, - } as const + it('fails to fetch an unrecognized L2/L3 network', async () => { + const chainId = 9999 try { - addCustomNetwork({ customL1Network, customL2Network }) + await getArbitrumNetwork(chainId) } catch (err) { - // should fail - expect(err).to.be.an('error') + expect(err).to.be.instanceOf(Error) expect((err as Error).message).to.be.eq( - `Partner chain id for L2 network ${customL2Network.chainID} doesn't match the provided L1 network. Expected ${customL1Network.chainID} but got ${wrongPartnerChainId}.` + `Unrecognized network ${chainId}.` ) } }) + }) - it('fails to add a custom L3 without previously registering L2', async function () { - const arbitrumOne = await getL2Network(arbitrumOneChainId) + describe('returns correct networks', () => { + // todo: this could be a snapshot test + it('returns correct Arbitrum networks', () => { + const arbitrumNetworksEntries = Object.entries(arbitrumNetworks) + const arbitrumNetworksKeys = arbitrumNetworksEntries.map(([key]) => key) - try { - addCustomNetwork({ - customL2Network: { - ...arbitrumOne, - chainID: mockL3ChainId, - partnerChainID: mockL2ChainId, - isArbitrum: true, - isCustom: true, - }, - }) - } catch (err) { - // should fail - expect(err).to.be.an('error') - expect((err as Error).message).to.be.eq( - `Network ${mockL3ChainId}'s parent network ${mockL2ChainId} is not recognized` - ) - } + const expected = [42161, 42170, 421614, 23011913] + // + .map(id => id.toString()) + + expect(arbitrumNetworksKeys).to.have.length(expected.length) + expect(arbitrumNetworksKeys).to.have.members(expected) }) }) - describe('fetching networks', () => { - it('successfully fetches an L1 network with `getL1Network`', async function () { - const network = await getL1Network(ethereumMainnetChainId) - expect(network.chainID).to.be.eq(ethereumMainnetChainId) + describe('getChildrenForNetwork', () => { + it('returns correct children for ethereum mainnet', () => { + const children = getChildrenForNetwork(1).map(c => c.chainId) + expect(children).to.have.members([42161, 42170]) }) - it('successfully fetches an L2 network with `getL2Network`', async function () { - const network = await getL2Network(arbitrumOneChainId) - expect(network.chainID).to.be.eq(arbitrumOneChainId) + it('returns correct children for arbitrum one', () => { + const children = getChildrenForNetwork(42161).map(c => c.chainId) + expect(children).to.have.members([]) }) - it('fails to fetch a registered L2 network with `getL1Network`', async function () { - try { - await getL1Network(arbitrumOneChainId) - } catch (err) { - // should fail - expect(err).to.be.an('error') - expect((err as Error).message).to.be.eq( - `Unrecognized network ${arbitrumOneChainId}.` - ) - } + it('returns correct children for arbitrum nova', () => { + const children = getChildrenForNetwork(42170).map(c => c.chainId) + expect(children).to.have.members([]) }) - it('fails to fetch a registered L1 network with `getL2Network`', async function () { - try { - await getL2Network(ethereumMainnetChainId) - } catch (err) { - // should fail - expect(err).to.be.an('error') - expect((err as Error).message).to.be.eq( - `Unrecognized network ${ethereumMainnetChainId}.` - ) - } + it('returns correct children for sepolia', () => { + const children = getChildrenForNetwork(11155111).map(c => c.chainId) + expect(children).to.have.members([421614]) }) - it('successfully fetches an L3 chain with `getL2Network`', async function () { - const arbitrumOne = await getL2Network(arbitrumOneChainId) - - const customL3Network = { - ...arbitrumOne, - chainID: mockL3ChainId, - partnerChainID: arbitrumOneChainId, - isArbitrum: true, - isCustom: true, - } as const - - addCustomNetwork({ customL2Network: customL3Network }) - - const l3Network = await getL2Network(mockL3ChainId) - expect(l3Network.chainID).to.be.eq(mockL3ChainId) - // assert network has correct parent - expect(l3Network.partnerChainID).to.equal(arbitrumOneChainId) + it('returns correct children for arbitrum sepolia', () => { + const children = getChildrenForNetwork(421614).map(c => c.chainId) + expect(children).to.have.members([23011913]) + }) + }) - // assert network was added as child - const l2Network = await getL2Network(customL3Network.partnerChainID) - expect(l2Network.partnerChainIDs).to.include(mockL3ChainId) + describe('isParentNetwork', () => { + it('returns correct value for ethereum mainnet', () => { + expect(isParentNetwork(1)).to.equal(true) }) - it('fails to fetch an unrecognized L1 network', async () => { - const chainId = 9999 + it('returns correct value for arbitrum one', () => { + expect(isParentNetwork(42161)).to.equal(false) + }) - try { - await getL1Network(chainId) - } catch (err) { - expect(err).to.be.instanceOf(Error) - expect((err as Error).message).to.be.eq( - `Unrecognized network ${chainId}.` - ) - } + it('returns correct value for arbitrum nova', () => { + expect(isParentNetwork(42170)).to.equal(false) }) - it('fails to fetch an unrecognized L2/L3 network', async () => { - const chainId = 9999 + it('returns correct value for sepolia', () => { + expect(isParentNetwork(11155111)).to.equal(true) + }) - try { - await getL2Network(chainId) - } catch (err) { - expect(err).to.be.instanceOf(Error) - expect((err as Error).message).to.be.eq( - `Unrecognized network ${chainId}.` - ) - } + it('returns correct value for arbitrum sepolia', () => { + expect(isParentNetwork(421614)).to.equal(true) }) }) - describe('returns correct networks', () => { - // todo: this could be a snapshot test - it('returns correct L1 networks', () => { - const l1NetworksEntries = Object.entries(l1Networks) - const l1NetworksKeys = l1NetworksEntries.map(([key]) => key) - - const expected = [1, 1338, 17000, 11155111].map(id => id.toString()) + describe('getMulticallAddress', () => { + it('returns correct value for ethereum mainnet', async () => { + const multicall = await getMulticallAddress(1) + expect(multicall).to.equal('0x5ba1e12693dc8f9c48aad8770482f4739beed696') + }) - expect(l1NetworksKeys).to.have.length(expected.length) - expect(l1NetworksKeys).to.have.members(expected) + it('returns correct value for arbitrum one', async () => { + const multicall = await getMulticallAddress(42161) + expect(multicall).to.equal('0x842eC2c7D803033Edf55E478F461FC547Bc54EB2') }) - // todo: this could be a snapshot test - it('returns correct L2 networks', () => { - const l2NetworksEntries = Object.entries(l2Networks) - const l2NetworksKeys = l2NetworksEntries.map(([key]) => key) + it('returns correct value for arbitrum nova', async () => { + const multicall = await getMulticallAddress(42170) + expect(multicall).to.equal('0x5e1eE626420A354BbC9a95FeA1BAd4492e3bcB86') + }) - const expected = [42161, 42170, 421614, 23011913] - // - .map(id => id.toString()) + it('returns correct value for sepolia', async () => { + const multicall = await getMulticallAddress(11155111) + expect(multicall).to.equal('0xded9AD2E65F3c4315745dD915Dbe0A4Df61b2320') + }) - expect(l2NetworksKeys).to.have.length(expected.length) - expect(l2NetworksKeys).to.have.members(expected) + it('returns correct value for arbitrum sepolia', async () => { + const multicall = await getMulticallAddress(421614) + expect(multicall).to.equal('0xA115146782b7143fAdB3065D86eACB54c169d092') }) }) }) diff --git a/tests/unit/l1toL2MessageEvents.test.ts b/tests/unit/parentToChildMessageEvents.test.ts similarity index 93% rename from tests/unit/l1toL2MessageEvents.test.ts rename to tests/unit/parentToChildMessageEvents.test.ts index a4b5e2f330..a71f707741 100644 --- a/tests/unit/l1toL2MessageEvents.test.ts +++ b/tests/unit/parentToChildMessageEvents.test.ts @@ -1,9 +1,9 @@ -import { L1TransactionReceipt } from '../../src' +import { ParentTransactionReceipt } from './../../src/lib/message/ParentTransaction' import { BigNumber, constants, providers } from 'ethers' import { JsonRpcProvider } from '@ethersproject/providers' import { expect } from 'chai' -describe('L1toL2Message events', () => { +describe('ParentToChildMessage events', () => { it('does call for nitro events', async () => { // Receipt from mainnet tx: 0x00000a61331187be51ab9ae792d74f601a5a21fb112f5b9ac5bccb23d4d5aaba const receipt: providers.TransactionReceipt = { @@ -150,17 +150,19 @@ describe('L1toL2Message events', () => { } const arbProvider = new JsonRpcProvider('https://arb1.arbitrum.io/rpc') - const l1TxnReceipt = new L1TransactionReceipt(receipt) + const parentTxnReceipt = new ParentTransactionReceipt(receipt) let txReceipt try { // Try getting classic messages using a nitro tx - txReceipt = await l1TxnReceipt.getL1ToL2MessagesClassic(arbProvider) + txReceipt = await parentTxnReceipt.getParentToChildMessagesClassic( + arbProvider + ) } catch (err) { // This call should throw an error expect(err).to.be.an('error') expect((err as Error).message).to.be.eq( - "This method is only for classic transactions. Use 'getL1ToL2Messages' for nitro transactions." + "This method is only for classic transactions. Use 'getParentToChildMessages' for nitro transactions." ) } finally { // Should not successfully get classic messages @@ -170,8 +172,10 @@ describe('L1toL2Message events', () => { ).to.be.undefined } - const isClassic = await l1TxnReceipt.isClassic(arbProvider) - const msg = (await l1TxnReceipt.getL1ToL2Messages(arbProvider))[0] + const isClassic = await parentTxnReceipt.isClassic(arbProvider) + const msg = ( + await parentTxnReceipt.getParentToChildMessages(arbProvider) + )[0] expect(isClassic, 'incorrect tx type returned by isClassic call').to.be .false @@ -184,8 +188,8 @@ describe('L1toL2Message events', () => { 'incorrect message number' ).to.be.true expect( - msg.l1BaseFee.eq(BigNumber.from('0x05e0fc4c58')), - 'incorrect l1 base fee' + msg.parentChainBaseFee.eq(BigNumber.from('0x05e0fc4c58')), + 'incorrect parent chain base fee' ).to.be.true expect( msg.messageData.destAddress, @@ -193,11 +197,11 @@ describe('L1toL2Message events', () => { ).to.be.eq('0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B') expect( msg.messageData.l2CallValue.eq(BigNumber.from('0x0853a0d2313c0000')), - 'incorrect l2 call value on messageData' + 'incorrect child chain call value on messageData' ).to.be.true expect( msg.messageData.l1Value.eq(BigNumber.from('0x0854e8ab1802ca80')), - 'incorrect l1 value on messageData' + 'incorrect parent chain value on messageData' ).to.be.true expect( msg.messageData.maxSubmissionFee.eq(BigNumber.from('0x01270f6740d880')), @@ -286,17 +290,17 @@ describe('L1toL2Message events', () => { } const arbProvider = new JsonRpcProvider('https://arb1.arbitrum.io/rpc') - const l1TxnReceipt = new L1TransactionReceipt(receipt) + const parentTxnReceipt = new ParentTransactionReceipt(receipt) let txReceipt try { // Try getting nitro messages using a classic tx - txReceipt = await l1TxnReceipt.getL1ToL2Messages(arbProvider) + txReceipt = await parentTxnReceipt.getParentToChildMessages(arbProvider) } catch (err) { // This call should throw an error expect(err).to.be.an('error') expect((err as Error).message).to.be.eq( - "This method is only for nitro transactions. Use 'getL1ToL2MessagesClassic' for classic transactions." + "This method is only for nitro transactions. Use 'getParentToChildMessagesClassic' for classic transactions." ) } finally { // Should not successfully get nitro messages @@ -306,8 +310,10 @@ describe('L1toL2Message events', () => { ).to.be.undefined } - const isClassic = await l1TxnReceipt.isClassic(arbProvider) - const msg = (await l1TxnReceipt.getL1ToL2MessagesClassic(arbProvider))[0] + const isClassic = await parentTxnReceipt.isClassic(arbProvider) + const msg = ( + await parentTxnReceipt.getParentToChildMessagesClassic(arbProvider) + )[0] const status = await msg.status() expect(isClassic, 'incorrect tx type returned by isClassic call').to.be.true @@ -322,7 +328,7 @@ describe('L1toL2Message events', () => { expect(msg.autoRedeemId, 'incorrect auto redeem id').to.be.eq( '0x38c5c31151344c7a1433a849bbc80472786ebe911630255a6e25d6a2efd39526' ) - expect(msg.l2TxHash, 'incorrect l2 tx hash').to.be.eq( + expect(msg.chainTxHash, 'incorrect child chain tx hash').to.be.eq( '0xf91e7d2e7526927e915a2357360a3f1108dce0f9c7fa88a7492669adf5c1e53b' ) })