Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: rename L1 and L2 to parent and child chain in inbox #339

Merged
merged 6 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
spsjvc marked this conversation as resolved.
Show resolved Hide resolved
baseFee: BigNumber
l1BaseFeeEstimate: BigNumber
spsjvc marked this conversation as resolved.
Show resolved Hide resolved
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