Skip to content

Commit

Permalink
chore: rename L1 and L2 to parent and child chain in inbox (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
douglance authored Oct 19, 2023
1 parent b568467 commit 5710ca2
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 72 deletions.
4 changes: 2 additions & 2 deletions scripts/sendL2SignedMsg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const sendSignedMsg = async () => {
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()
Expand Down
146 changes: 78 additions & 68 deletions src/lib/inbox/inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ 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 { l1Networks, L2Network } from '../dataEntities/networks'
import {
l1Networks as parentChains,
L2Network as ChildChain,
} from '../dataEntities/networks'
import { SignerProviderUtils } from '../dataEntities/signerOrProvider'
import { FetchedEvent, EventFetcher } from '../utils/eventFetcher'
import { MultiCaller, CallInput } from '../utils/multicall'
Expand All @@ -42,12 +45,12 @@ type ForceInclusionParams = FetchedEvent<MessageDeliveredEvent> & {
delayedAcc: string
}

type GasComponentsWithL2Part = {
type GasComponentsWithChildChainPart = {
gasEstimate: BigNumber
gasEstimateForL1: BigNumber
baseFee: BigNumber
l1BaseFeeEstimate: BigNumber
gasEstimateForL2: BigNumber
gasEstimateForChildChain: BigNumber
}
type RequiredTransactionRequestType = RequiredPick<
TransactionRequest,
Expand All @@ -57,18 +60,20 @@ type RequiredTransactionRequestType = RequiredPick<
* Tools for interacting with the inbox and bridge contracts
*/
export class InboxTools {
private readonly l1Provider
private readonly l1Network
private readonly parentChainProvider
private readonly parentChain

constructor(
private readonly l1Signer: Signer,
private readonly l2Network: L2Network
private readonly parentChainSigner: Signer,
private readonly childChain: ChildChain
) {
this.l1Provider = SignerProviderUtils.getProviderOrThrow(this.l1Signer)
this.l1Network = l1Networks[l2Network.partnerChainID]
if (!this.l1Network)
this.parentChainProvider = SignerProviderUtils.getProviderOrThrow(
this.parentChainSigner
)
this.parentChain = parentChains[childChain.partnerChainID]
if (!this.parentChain)
throw new ArbSdkError(
`L1Network not found for chain id: ${l2Network.partnerChainID}.`
`ParentChainNetwork not found for chain id: ${childChain.partnerChainID}.`
)
}

Expand All @@ -84,28 +89,31 @@ export class InboxTools {
blockNumber: number,
blockTimestamp: number
): Promise<Block> {
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)
const diffBlocks = Math.max(
Math.ceil(diff / this.parentChain.blockTime),
10
)

return await this.findFirstBlockBelow(
blockNumber - diffBlocks,
blockTimestamp
)
}

//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
}
Expand All @@ -114,32 +122,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<GasComponentsWithL2Part> {
childChainTransactionRequest: RequiredTransactionRequestType,
childChainProvider: Provider
): Promise<GasComponentsWithChildChainPart> {
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 }
}

/**
Expand All @@ -149,11 +159,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<Awaited<ReturnType<SequencerInbox['maxTimeVariation']>>>,
ReturnType<MultiCaller['getBlockNumberInput']>,
Expand Down Expand Up @@ -207,7 +217,7 @@ export class InboxTools {
maxSearchRangeBlocks: number,
rangeMultiplier: number
): Promise<FetchedEvent<MessageDeliveredEvent>[]> {
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
Expand Down Expand Up @@ -245,7 +255,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
Expand All @@ -255,11 +265,11 @@ export class InboxTools {
public async getForceIncludableEvent(
maxSearchRangeBlocks: number = 3 * 6545,
startSearchRangeBlocks = 100,
rangeMultipler = 2
rangeMultiplier = 2
): Promise<ForceInclusionParams | null> {
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
Expand All @@ -268,7 +278,7 @@ export class InboxTools {
bridge,
startSearchRangeBlocks,
maxSearchRangeBlocks,
rangeMultipler
rangeMultiplier
)

// no events appeared within that time period
Expand All @@ -277,8 +287,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()
Expand All @@ -296,7 +306,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
Expand All @@ -317,14 +327,14 @@ export class InboxTools {
overrides?: Overrides
): Promise<ContractTransaction | null> {
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),
Expand All @@ -339,19 +349,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<ContractTransaction | null> {
const delayedInbox = IInbox__factory.connect(
this.l2Network.ethBridge.inbox,
this.l1Signer
this.childChain.ethBridge.inbox,
this.parentChainSigner
)

const sendData = ethers.utils.solidityPack(
Expand All @@ -364,43 +374,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<string> {
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
Expand All @@ -409,17 +419,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)
}
}
4 changes: 2 additions & 2 deletions tests/integration/sendL2msg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ const sendSignedTx = async (testState: any, info?: any) => {
...info,
value: BigNumber.from(0),
}
const signedTx = await inbox.signL2Tx(message, l2Deployer)
const signedTx = await inbox.signChildChainTx(message, l2Deployer)

const l1Tx = await inbox.sendL2SignedTx(signedTx)
const l1Tx = await inbox.sendChildChainSignedTx(signedTx)
return {
signedMsg: signedTx,
l1TransactionReceipt: await l1Tx?.wait(),
Expand Down

0 comments on commit 5710ca2

Please sign in to comment.