Skip to content
Draft
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
28 changes: 28 additions & 0 deletions src/calculateRetryableSubmissionFee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Address, PublicClient, Transport, Chain, parseAbi } from 'viem';

// Matches DEFAULT_SUBMISSION_FEE_PERCENT_INCREASE in @arbitrum/sdk's ParentToChildMessageGasEstimator
const SUBMISSION_FEE_PERCENT_INCREASE = 300n;

const inboxABI = parseAbi([
'function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) view returns (uint256)',
]);

export async function calculateRetryableSubmissionFee<TChain extends Chain | undefined>(
parentChainPublicClient: PublicClient<Transport, TChain>,
inbox: Address,
dataLength: bigint,
): Promise<bigint> {
const block = await parentChainPublicClient.getBlock();
if (!block.baseFeePerGas) {
throw new Error('Latest block did not contain base fee');
}

const submissionFee = await parentChainPublicClient.readContract({
address: inbox,
abi: inboxABI,
functionName: 'calculateRetryableSubmissionFee',
args: [dataLength, block.baseFeePerGas],
});

return submissionFee + (submissionFee * SUBMISSION_FEE_PERCENT_INCREASE) / 100n;
}
11 changes: 10 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseEther } from 'viem';
import { parseEther, parseGwei } from 'viem';

/**
* Approximate value necessary to pay for retryables fees for `createRollup`.
Expand All @@ -9,3 +9,12 @@ export const createRollupDefaultRetryablesFees = parseEther('0.125');
* Approximate value necessary to pay for retryables fees for `createTokenBridge`.
*/
export const createTokenBridgeDefaultRetryablesFees = parseEther('0.02');

/**
* 0.1 gwei is a standard default to start the chain with. here we double that for some margin
*/
export const enqueueDefaultMaxGasPrice = parseGwei('0.2');

// ~30% headroom over observed gas usage for token bridge retryables
export const enqueueDefaultMaxGasForContracts = 20_000_000n;
export const enqueueDefaultGasLimitForWethGateway = 100_000n;
60 changes: 60 additions & 0 deletions src/contracts/GatewayRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export const gatewayRouterABI = [
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'l1TokenToGateway',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address[]',
name: '_token',
type: 'address[]',
},
{
internalType: 'address[]',
name: '_gateway',
type: 'address[]',
},
{
internalType: 'uint256',
name: '_maxGas',
type: 'uint256',
},
{
internalType: 'uint256',
name: '_gasPriceBid',
type: 'uint256',
},
{
internalType: 'uint256',
name: '_maxSubmissionCost',
type: 'uint256',
},
],
name: 'setGateways',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'payable',
type: 'function',
},
] as const;
52 changes: 52 additions & 0 deletions src/createTokenBridge-ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,55 @@ export async function getEstimateForSettingGateway<
deposit: setGatewaysGasParams.deposit.toBigInt(),
};
}

export function getFactoryDeploymentDataSize(): number {
return ethers.utils.hexDataLength(L2AtomicTokenBridgeFactory__factory.bytecode);
}

export async function getContractsDeploymentData(
l1TokenBridgeCreatorAddress: string,
l1Provider: ethers.providers.Provider,
) {
const l1TokenBridgeCreator = new ethers.Contract(
l1TokenBridgeCreatorAddress,
L1AtomicTokenBridgeCreator.abi,
).connect(l1Provider);

const l2FactoryTemplate = L2AtomicTokenBridgeFactory__factory.attach(
await l1TokenBridgeCreator.l2TokenBridgeFactoryTemplate(),
).connect(l1Provider);

const l2Code = {
router: await l1Provider.getCode(await l1TokenBridgeCreator.l2RouterTemplate()),
standardGateway: await l1Provider.getCode(
await l1TokenBridgeCreator.l2StandardGatewayTemplate(),
),
customGateway: await l1Provider.getCode(await l1TokenBridgeCreator.l2CustomGatewayTemplate()),
wethGateway: await l1Provider.getCode(await l1TokenBridgeCreator.l2WethGatewayTemplate()),
aeWeth: await l1Provider.getCode(await l1TokenBridgeCreator.l2WethTemplate()),
upgradeExecutor: await l1Provider.getCode(
(
await l1TokenBridgeCreator.l1Templates()
).upgradeExecutor,
),
multicall: await l1Provider.getCode(await l1TokenBridgeCreator.l2MulticallTemplate()),
};

const calldata = l2FactoryTemplate.interface.encodeFunctionData('deployL2Contracts', [
l2Code,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
ethers.Wallet.createRandom().address,
]);

return {
dataSize: ethers.utils.hexDataLength(calldata),
l2Code,
l2FactoryTemplate,
};
}
139 changes: 139 additions & 0 deletions src/enqueueTokenBridgePrepareSetWethGatewayTransactionRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { Address, PublicClient, Transport, Chain, encodeFunctionData, parseAbi } from 'viem';

import { validateParentChain } from './types/ParentChain';
import { isCustomFeeTokenChain } from './utils/isCustomFeeTokenChain';
import { createTokenBridgeFetchTokenBridgeContracts } from './createTokenBridgeFetchTokenBridgeContracts';
import { createRollupFetchCoreContracts } from './createRollupFetchCoreContracts';
import { upgradeExecutorEncodeFunctionData } from './upgradeExecutorEncodeFunctionData';
import { gatewayRouterABI } from './contracts/GatewayRouter';
import { Prettify } from './types/utils';
import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes';
import { enqueueDefaultMaxGasPrice, enqueueDefaultGasLimitForWethGateway } from './constants';
import { calculateRetryableSubmissionFee } from './calculateRetryableSubmissionFee';

export type EnqueueTokenBridgePrepareSetWethGatewayTransactionRequestParams<
TParentChain extends Chain | undefined,
> = Prettify<
WithTokenBridgeCreatorAddressOverride<{
/**
* Address of the Rollup contract.
*/
rollup: Address;
account: Address;
/**
* Number of the block in which the Rollup contract was deployed.
*
* This parameter is used to reduce the span of blocks to query, so it doesn't have to be exactly the right block number.
* However, for the query to work properly, it has to be **less than or equal to** the right block number.
*/
rollupDeploymentBlockNumber?: bigint;
parentChainPublicClient: PublicClient<Transport, TParentChain>;
gasLimit?: bigint;
maxGasPrice?: bigint;
}>
>;

/**
* Prepares the transaction to register the WETH gateway on the parent chain router via the
* UpgradeExecutor. Must be called after the `enqueueTokenBridgePrepareTransactionRequest` transaction
* has confirmed on the parent chain. Unlike {@link createTokenBridgePrepareSetWethGatewayTransactionRequest},
* this function does not require an orbit chain connection -- retryable gas parameters are estimated
* from parent chain state.
*/
export async function enqueueTokenBridgePrepareSetWethGatewayTransactionRequest<
TParentChain extends Chain | undefined,
>({
rollup,
account,
rollupDeploymentBlockNumber,
parentChainPublicClient,
gasLimit = enqueueDefaultGasLimitForWethGateway,
maxGasPrice = enqueueDefaultMaxGasPrice,
tokenBridgeCreatorAddressOverride,
}: EnqueueTokenBridgePrepareSetWethGatewayTransactionRequestParams<TParentChain>) {
const { chainId } = validateParentChain(parentChainPublicClient);

if (
await isCustomFeeTokenChain({
rollup,
parentChainPublicClient,
})
) {
throw new Error('chain is custom fee token chain, no need to register the weth gateway.');
}

const inbox = await parentChainPublicClient.readContract({
address: rollup,
abi: parseAbi(['function inbox() view returns (address)']),
functionName: 'inbox',
});

const tokenBridgeContracts = await createTokenBridgeFetchTokenBridgeContracts({
inbox,
parentChainPublicClient,
tokenBridgeCreatorAddressOverride,
});

const registeredWethGateway = await parentChainPublicClient.readContract({
address: tokenBridgeContracts.parentChainContracts.router,
abi: gatewayRouterABI,
functionName: 'l1TokenToGateway',
args: [tokenBridgeContracts.parentChainContracts.weth],
});
if (registeredWethGateway === tokenBridgeContracts.parentChainContracts.wethGateway) {
throw new Error('weth gateway is already registered in the router.');
}

const rollupCoreContracts = await createRollupFetchCoreContracts({
rollup,
rollupDeploymentBlockNumber,
publicClient: parentChainPublicClient,
});

// Encode with placeholder values to measure data size (uint256 values are always 32 bytes in ABI encoding)
const dummyCalldata = encodeFunctionData({
abi: gatewayRouterABI,
functionName: 'setGateways',
args: [
[tokenBridgeContracts.parentChainContracts.weth],
[tokenBridgeContracts.parentChainContracts.wethGateway],
0n,
0n,
0n,
],
});
const calldataSize = BigInt((dummyCalldata.length - 2) / 2);
const maxSubmissionCost = await calculateRetryableSubmissionFee(
parentChainPublicClient,
inbox,
calldataSize,
);

const deposit = gasLimit * maxGasPrice + maxSubmissionCost;

const setGatewaysCalldata = encodeFunctionData({
abi: gatewayRouterABI,
functionName: 'setGateways',
args: [
[tokenBridgeContracts.parentChainContracts.weth],
[tokenBridgeContracts.parentChainContracts.wethGateway],
gasLimit, // _maxGas
maxGasPrice, // _gasPriceBid
maxSubmissionCost, // _maxSubmissionCost
],
});

// @ts-expect-error -- todo: fix viem type issue
const request = await parentChainPublicClient.prepareTransactionRequest({
chain: parentChainPublicClient.chain,
to: rollupCoreContracts.upgradeExecutor,
data: upgradeExecutorEncodeFunctionData({
functionName: 'executeCall',
args: [tokenBridgeContracts.parentChainContracts.router, setGatewaysCalldata],
}),
value: deposit,
account,
});

return { ...request, chainId };
}
Loading
Loading