Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa54664
feat: add enqueueTokenBridgeDeployment and enqueueSetWethGateway
yahgwai Mar 19, 2026
7ffcb0b
refactor: address PR review feedback
yahgwai Mar 19, 2026
fb3506d
refactor: rename to PrepareTransactionRequest convention, extract sha…
yahgwai Mar 19, 2026
e3f85a7
refactor: use parseGwei for enqueueDefaultMaxGasPrice
yahgwai Mar 19, 2026
9b54a85
refactor: split submission costs, rename weth gateway to match conven…
yahgwai Mar 19, 2026
efa5df7
chore: fix prettier formatting
yahgwai Mar 19, 2026
54ee7d0
fix: replace dynamic import with static import in weth gateway test
yahgwai Mar 19, 2026
9161513
test: add integration test for enqueue token bridge
yahgwai Mar 19, 2026
8e120c2
test: remove brittle unit tests in favor of integration test
yahgwai Mar 19, 2026
9333677
fix: register network before waitForRetryables in integration test
yahgwai Mar 20, 2026
b3f16f1
docs: add JSDoc to enqueue prepare functions
yahgwai Mar 23, 2026
ba3e4e8
Add prepare script to src/ for git-based installation
douglance Mar 23, 2026
cdf6550
Merge branch 'feat/enqueue-token-bridge-deployment' into feat/add-pre…
TucksonDev Mar 23, 2026
543b8e3
Add payable mutators to upgrade executor functions
TucksonDev Mar 24, 2026
014e79f
feat: added default gas limits for enqueue token bridge variant
yahgwai Mar 25, 2026
e0e2d4e
Merge branch 'feat/enqueue-token-bridge-deployment' into feat/add-pre…
TucksonDev Mar 25, 2026
b895006
refactor: extract shared data-size helpers for retryable submission cost
yahgwai Mar 25, 2026
2adcbc3
feat: add calculateRetryableSubmissionFee for enqueue variants
yahgwai Mar 25, 2026
867a3f3
feat: calculate retryable submission costs internally in enqueue func…
yahgwai Mar 25, 2026
8479125
fix: swap default gas limits for factory and contracts retryables
yahgwai Mar 25, 2026
192a9cf
refactor: read factory gas limit from TokenBridgeCreator contract
yahgwai Mar 25, 2026
3d9ef73
Merge branch 'feat/enqueue-token-bridge-deployment' into feat/add-pre…
TucksonDev Mar 25, 2026
33b7b6b
chore: fix Prettier formatting and update audit-ci allowlist
douglance Apr 1, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/sbom-export.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: SBOM Export & Centralize

on:
push:
branches: [ "main" ]
branches: ['main']
schedule:
- cron: '36 8 * * 1'

Expand Down
38 changes: 37 additions & 1 deletion audit-ci.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,43 @@
// elliptic has no patched release yet (affected: <=6.6.1)
// transitive via ethereumjs-util -> ethereum-cryptography -> secp256k1 -> elliptic
// from: @safe-global/protocol-kit>ethereumjs-util>ethereum-cryptography>secp256k1>elliptic
"GHSA-848j-6mx2-7j84"
"GHSA-848j-6mx2-7j84",

// flatted
////////////
// https://github.com/advisories/GHSA-25h7-pfq9-p65f
// flatted ReDoS - transitive dev dep via eslint>file-entry-cache>flat-cache>flatted
// not a runtime dependency
"GHSA-25h7-pfq9-p65f",
// https://github.com/advisories/GHSA-rf6f-7fwh-wjgh
// flatted DoS - transitive dev dep via eslint>file-entry-cache>flat-cache>flatted
// not a runtime dependency
"GHSA-rf6f-7fwh-wjgh",

// picomatch
////////////
// https://github.com/advisories/GHSA-3v7f-55p6-f55p
// picomatch method injection - transitive dev dep via @wagmi/cli>chokidar and typescript-eslint
// not a runtime dependency
"GHSA-3v7f-55p6-f55p",
// https://github.com/advisories/GHSA-c2c7-rcm5-vvqj
// picomatch ReDoS - transitive dev dep via @wagmi/cli>chokidar and typescript-eslint
// not a runtime dependency
"GHSA-c2c7-rcm5-vvqj",

// brace-expansion
////////////
// https://github.com/advisories/GHSA-f886-m6hf-6m8v
// brace-expansion infinite loop - transitive dev dep via eslint>minimatch, ts-morph, typescript-eslint
// not a runtime dependency
"GHSA-f886-m6hf-6m8v",

// yaml
////////////
// https://github.com/advisories/GHSA-48c2-rrv3-qjmp
// yaml stack overflow via deeply nested collections
// transitive dev dep via patch-package>yaml and @arbitrum/nitro-contracts>patch-package>yaml
// not a runtime dependency
"GHSA-48c2-rrv3-qjmp"
]
}
6 changes: 3 additions & 3 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defineConfig } from 'eslint/config'
import tseslint from 'typescript-eslint'
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig(
{
ignores: ['node_modules/**', 'src/dist/**', 'coverage/**'],
},
...tseslint.configs.recommended,
)
);
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;
4 changes: 2 additions & 2 deletions src/contracts/UpgradeExecutor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { parseAbi } from 'viem';

export const upgradeExecutorABI = parseAbi([
'function execute(address upgrade, bytes upgradeCallData)',
'function executeCall(address target, bytes targetCallData)',
'function execute(address upgrade, bytes upgradeCallData) payable',
'function executeCall(address target, bytes targetCallData) payable',
'function hasRole(bytes32 role, address account) public view returns (bool)',
'function grantRole(bytes32 role, address account)',
'function revokeRole(bytes32 role, address account)',
Expand Down
127 changes: 59 additions & 68 deletions src/createTokenBridge-ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export async function createTokenBridgeGetInputs<
TParentChain extends Chain | undefined,
TOrbitChain extends Chain | undefined,
>(
l1DeployerAddress: string,
l1PublicClient: PublicClient<Transport, TParentChain>,
l2PublicClient: PublicClient<Transport, TOrbitChain>,
l1TokenBridgeCreatorAddress: string,
Expand All @@ -51,22 +50,12 @@ export async function createTokenBridgeGetInputs<
const {
maxSubmissionCost: maxSubmissionCostForFactoryEstimation,
maxGas: maxGasForFactoryEstimation,
} = await getEstimateForDeployingFactory(
l1DeployerAddress,
l1TokenBridgeCreatorAddress,
l1Provider,
l2Provider,
);
} = await getEstimateForDeployingFactory(l1TokenBridgeCreatorAddress, l1Provider, l2Provider);

const {
maxSubmissionCost: maxSubmissionCostForContractsEstimation,
maxGas: maxGasForContractsEstimation,
} = await getEstimateForDeployingContracts(
l1DeployerAddress,
l1TokenBridgeCreatorAddress,
l1Provider,
l2Provider,
);
} = await getEstimateForDeployingContracts(l1TokenBridgeCreatorAddress, l1Provider, l2Provider);

//// apply gas overrides
const maxSubmissionCostForFactory =
Expand Down Expand Up @@ -138,65 +127,23 @@ export async function createTokenBridgeGetInputs<
};
}

const getEstimateForDeployingFactory = async (
l1DeployerAddress: string,
l1TokenBridgeCreatorAddress: string,
l1Provider: ethers.providers.Provider,
l2Provider: ethers.providers.Provider,
): Promise<{
maxSubmissionCost: BigNumber;
maxGas: BigNumber;
}> => {
const L1AtomicTokenBridgeCreator__factory = new ethers.Contract(
l1TokenBridgeCreatorAddress,
L1AtomicTokenBridgeCreator.abi,
);
const l1TokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect(l1Provider);

//// run retryable estimate for deploying L2 factory
const l1ToL2MsgGasEstimate = new ParentToChildMessageGasEstimator(l2Provider);

const { maxSubmissionCost } = await l1ToL2MsgGasEstimate.estimateAll(
{
from: ethers.Wallet.createRandom().address,
to: ethers.constants.AddressZero,
l2CallValue: BigNumber.from(0),
excessFeeRefundAddress: l1DeployerAddress,
callValueRefundAddress: l1DeployerAddress,
data: L2AtomicTokenBridgeFactory__factory.bytecode,
},
await getBaseFee(l1Provider),
l1Provider,
);

const maxGas = (await l1TokenBridgeCreator.gasLimitForL2FactoryDeployment()) as BigNumber;

return {
// there's already a 300% increase buffer in the SDK
// https://github.com/OffchainLabs/arbitrum-sdk/blob/main/src/lib/message/ParentToChildMessageGasEstimator.ts#L27
maxSubmissionCost,
maxGas,
};
};
export function getFactoryDeploymentDataSize(): number {
return ethers.utils.hexDataLength(L2AtomicTokenBridgeFactory__factory.bytecode);
}

async function getEstimateForDeployingContracts(
l1DeployerAddress: string,
export async function getContractsDeploymentData(
l1TokenBridgeCreatorAddress: string,
l1Provider: ethers.providers.Provider,
l2Provider: ethers.providers.Provider,
): Promise<{
maxSubmissionCost: BigNumber;
maxGas: BigNumber;
}> {
const L1AtomicTokenBridgeCreator__factory = new ethers.Contract(
) {
const l1TokenBridgeCreator = new ethers.Contract(
l1TokenBridgeCreatorAddress,
L1AtomicTokenBridgeCreator.abi,
);
const l1TokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect(l1Provider);
).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(
Expand All @@ -213,8 +160,6 @@ async function getEstimateForDeployingContracts(
multicall: await l1Provider.getCode(await l1TokenBridgeCreator.l2MulticallTemplate()),
};

const l1ToL2MsgGasEstimate = new ParentToChildMessageGasEstimator(l2Provider);

const calldata = l2FactoryTemplate.interface.encodeFunctionData('deployL2Contracts', [
l2Code,
ethers.Wallet.createRandom().address,
Expand All @@ -227,10 +172,58 @@ async function getEstimateForDeployingContracts(
ethers.Wallet.createRandom().address,
]);

return {
dataSize: ethers.utils.hexDataLength(calldata),
l2Code,
l2FactoryTemplate,
};
}

const getEstimateForDeployingFactory = async (
l1TokenBridgeCreatorAddress: string,
l1Provider: ethers.providers.Provider,
l2Provider: ethers.providers.Provider,
): Promise<{
maxSubmissionCost: BigNumber;
maxGas: BigNumber;
}> => {
const l1TokenBridgeCreator = new ethers.Contract(
l1TokenBridgeCreatorAddress,
L1AtomicTokenBridgeCreator.abi,
).connect(l1Provider);

const l1ToL2MsgGasEstimate = new ParentToChildMessageGasEstimator(l2Provider);
// 300% increase buffer is applied by the SDK
const maxSubmissionCost = await l1ToL2MsgGasEstimate.estimateSubmissionFee(
l1Provider,
await getBaseFee(l1Provider),
getFactoryDeploymentDataSize(),
);

const maxGas = (await l1TokenBridgeCreator.gasLimitForL2FactoryDeployment()) as BigNumber;

return { maxSubmissionCost, maxGas };
};

async function getEstimateForDeployingContracts(
l1TokenBridgeCreatorAddress: string,
l1Provider: ethers.providers.Provider,
l2Provider: ethers.providers.Provider,
): Promise<{
maxSubmissionCost: BigNumber;
maxGas: BigNumber;
}> {
const { dataSize, l2Code, l2FactoryTemplate } = await getContractsDeploymentData(
l1TokenBridgeCreatorAddress,
l1Provider,
);

const l1ToL2MsgGasEstimate = new ParentToChildMessageGasEstimator(l2Provider);
// 300% increase buffer is applied by the SDK
const maxSubmissionCost = await l1ToL2MsgGasEstimate.estimateSubmissionFee(
l1Provider,
await l1Provider.getGasPrice(),
ethers.utils.hexDataLength(calldata),
dataSize,
);

const maxGas = await l2FactoryTemplate.estimateGas.deployL2Contracts(
Expand All @@ -246,8 +239,6 @@ async function getEstimateForDeployingContracts(
);

return {
// there's already a 300% increase buffer in the SDK
// https://github.com/OffchainLabs/arbitrum-sdk/blob/main/src/lib/message/ParentToChildMessageGasEstimator.ts#L27
maxSubmissionCost,
maxGas: maxGas.mul(2),
};
Expand Down
Loading
Loading