Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions src/chainIsArbitrum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as chains from './chains';

export function chainIsArbitrum(chainId: bigint): boolean {
switch (chainId) {
case BigInt(chains.arbitrumOne.id):
case BigInt(chains.arbitrumNova.id):
case BigInt(chains.arbitrumSepolia.id):
case BigInt(chains.nitroTestnodeL2.id):
case BigInt(chains.nitroTestnodeL3.id):
return true;

default:
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exports[`creates a config for a chain on top of a custom parent chain 1`] = `
"futureSeconds": 5n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand All @@ -38,7 +38,7 @@ exports[`creates config for a chain on top of arbitrum one with defaults 1`] = `
"futureSeconds": 3600n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -80,7 +80,7 @@ exports[`creates config for a chain on top of arbitrum sepolia with defaults 1`]
"futureSeconds": 3600n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -122,7 +122,7 @@ exports[`creates config for a chain on top of base sepolia with defaults 1`] = `
"futureSeconds": 3600n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand All @@ -143,6 +143,6 @@ exports[`creates config for a chain on top of base with defaults 1`] = `
"futureSeconds": 3600n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ exports[`creates a config for a chain on top of a custom parent chain 1`] = `
},
"stakeToken": "0xabaF3D9f5cbDcE54cDc43b429d8f7154A3E19Afa",
"validatorAfkBlocks": 4n,
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -100,7 +100,7 @@ exports[`creates config for a chain on top of arbitrum one with defaults 1`] = `
},
"stakeToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
"validatorAfkBlocks": 201600n,
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -204,7 +204,7 @@ exports[`creates config for a chain on top of arbitrum sepolia with defaults 1`]
},
"stakeToken": "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73",
"validatorAfkBlocks": 201600n,
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -308,7 +308,7 @@ exports[`creates config for a chain on top of base sepolia with defaults 1`] = `
},
"stakeToken": "0x4200000000000000000000000000000000000006",
"validatorAfkBlocks": 1209600n,
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;

Expand Down Expand Up @@ -360,6 +360,6 @@ exports[`creates config for a chain on top of base with defaults 1`] = `
},
"stakeToken": "0x4200000000000000000000000000000000000006",
"validatorAfkBlocks": 1209600n,
"wasmModuleRoot": "0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6",
"wasmModuleRoot": "0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c",
}
`;
77 changes: 76 additions & 1 deletion src/createRollupPrepareTransactionRequest-v2.1.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { it, expect } from 'vitest';
import { createPublicClient, http, zeroAddress } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
import { arbitrum, arbitrumSepolia } from 'viem/chains';

import { generateChainId } from './utils';
import { prepareChainConfig } from './prepareChainConfig';
Expand Down Expand Up @@ -353,6 +353,45 @@ it(`fails to prepare transaction request if "params.maxDataSize" is not provided
).rejects.toThrowError(`"params.maxDataSize" must be provided when using a custom parent chain.`);
});

it(`fails to prepare transaction request if "params.wasmModuleRoot" is blacklisted`, async () => {
const chainId = 123456;

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

const wasmModuleRoot = '0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6';
const arbOSVersion = chainConfig.arbitrum.InitialArbOSVersion;

await expect(
createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(
publicClient,
{
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
wasmModuleRoot,
},
'v2.1',
),
batchPosters: [deployer.address],
validators: [deployer.address],
},
value: createRollupDefaultRetryablesFees,
account: deployer.address,
publicClient,
gasOverrides: { gasLimit: { base: 1_000n } },
rollupCreatorVersion: 'v2.1',
}),
).rejects.toThrowError(
`Wasm module root ${wasmModuleRoot} is not supported. Please update your "wasmModuleRoot" to that of a Consensus version compatible with ArbOS ${arbOSVersion}.`,
);
});

it(`successfully prepares a transaction request with the default rollup creator and a gas limit override`, async () => {
// generate a random chain id
const chainId = generateChainId();
Expand Down Expand Up @@ -525,3 +564,39 @@ it(`successfully prepare transaction request if "params.nativeToken" uses suppor
expect(txRequest.chainId).toEqual(arbitrumSepolia.id);
expect(txRequest.gas).toEqual(1_000n);
});

it(`successfully prepares a transaction request if "params.wasmModuleRoot" is blacklisted but chain is Arbitrum`, async () => {
const chainId = arbitrum.id;

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

const wasmModuleRoot = '0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6';

await expect(
createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(
publicClient,
{
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
wasmModuleRoot,
},
'v2.1',
),
batchPosters: [deployer.address],
validators: [deployer.address],
},
value: createRollupDefaultRetryablesFees,
account: deployer.address,
publicClient,
gasOverrides: { gasLimit: { base: 1_000n } },
rollupCreatorVersion: 'v2.1',
}),
).resolves.toBeDefined();
});
67 changes: 66 additions & 1 deletion src/createRollupPrepareTransactionRequest-v3.1.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { it, expect } from 'vitest';
import { createPublicClient, http, zeroAddress } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
import { arbitrum, arbitrumSepolia } from 'viem/chains';

import { generateChainId } from './utils';
import { prepareChainConfig } from './prepareChainConfig';
Expand Down Expand Up @@ -311,6 +311,40 @@ it(`fails to prepare transaction request if "params.maxDataSize" is not provided
).rejects.toThrowError(`"params.maxDataSize" must be provided when using a custom parent chain.`);
});

it(`fails to prepare transaction request if "params.wasmModuleRoot" is blacklisted`, async () => {
const chainId = 123456;

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

const wasmModuleRoot = '0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6';
const arbOSVersion = chainConfig.arbitrum.InitialArbOSVersion;

await expect(
createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(publicClient, {
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
wasmModuleRoot,
}),
batchPosters: [deployer.address],
validators: [deployer.address],
},
value: createRollupDefaultRetryablesFees,
account: deployer.address,
publicClient,
gasOverrides: { gasLimit: { base: 1_000n } },
}),
).rejects.toThrowError(
`Wasm module root ${wasmModuleRoot} is not supported. Please update your "wasmModuleRoot" to that of a Consensus version compatible with ArbOS ${arbOSVersion}.`,
);
});

it(`successfully prepares a transaction request with the default rollup creator and a gas limit override`, async () => {
// generate a random chain id
const chainId = generateChainId();
Expand Down Expand Up @@ -501,3 +535,34 @@ it(`successfully prepares a transaction request with a custom gas token Rollup c
expect(txRequest.chainId).toEqual(arbitrumSepolia.id);
expect(txRequest.gas).toEqual(1_000n);
});

it(`successfully prepares a transaction request if "params.wasmModuleRoot" is blacklisted but chain is Arbitrum`, async () => {
const chainId = arbitrum.id;

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

const wasmModuleRoot = '0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6';

await expect(
createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(publicClient, {
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
wasmModuleRoot,
}),
batchPosters: [deployer.address],
validators: [deployer.address],
},
value: createRollupDefaultRetryablesFees,
account: deployer.address,
publicClient,
gasOverrides: { gasLimit: { base: 1_000n } },
}),
).resolves.toBeDefined();
});
10 changes: 10 additions & 0 deletions src/createRollupPrepareTransactionRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ChainConfig } from './types/ChainConfig';
import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress';
import { fetchDecimals } from './utils/erc20';
import { TransactionRequestGasOverrides, applyPercentIncrease } from './utils/gasOverrides';
import { isBlacklistedWasmModuleRoot } from './wasmModuleRoot';
import { chainIsArbitrum } from './chainIsArbitrum';

import {
CreateRollupParams,
Expand Down Expand Up @@ -126,6 +128,8 @@ export async function createRollupPrepareTransactionRequest<TChain extends Chain
);
}

const chainId = params.config.chainId;
Copy link
Member

@spsjvc spsjvc Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we're using this anymore


if (isKnownWasmModuleRoot(wasmModuleRoot)) {
const consensusRelease = getConsensusReleaseByWasmModuleRoot(wasmModuleRoot);

Expand All @@ -136,6 +140,12 @@ export async function createRollupPrepareTransactionRequest<TChain extends Chain
}
}

if (!chainIsArbitrum(chainId) && isBlacklistedWasmModuleRoot(wasmModuleRoot)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the chainIsArbitrum check, since this is only for deployment of brand new chains. It will never evaluate to true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Should we however prevent upgrades to one of these wasm roots ? Prod arbitrum chains won't ever upgrade to these older wasm roots but what about internal tests ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could cover the upgrade path in another PR once we start working on upgrades. I wouldn't worry about it too much as only RaaS-es might do so. But they also wait on a heads-up from us before upgrading so I think we are ok

throw new Error(
`Wasm module root ${wasmModuleRoot} is not supported. Please update your "wasmModuleRoot" to that of a Consensus version compatible with ArbOS ${arbOSVersion}.`,
);
}

const paramsWithDefaults = { ...defaults, ...params, maxDataSize };
const createRollupGetCallValueParams = { ...paramsWithDefaults, account };

Expand Down
19 changes: 19 additions & 0 deletions src/wasmModuleRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ const consensusReleases = [
},
{
// https://github.com/OffchainLabs/nitro/releases/tag/consensus-v51.1
version: 51.1,
wasmModuleRoot: '0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14c',
maxArbOSVersion: 51.1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is actually an error from before but we might as well fix it here

Suggested change
maxArbOSVersion: 51.1,
maxArbOSVersion: 51,

},
] as const satisfies readonly ConsensusRelease[];

const blacklistedConsensusReleases = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add an optional disabled?: boolean property on the ConsensusRelease type that we only add to those that we do not allow. Then the check in src/createRollupPrepareTransactionRequest.ts would look something like this:

if (wasmModuleRoot.disabled) {
    throw new Error();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's definitely cleaner. However getConsensusReleaseByVersion needs a refactor in that case since we may have multiple wasm roots for a single consensus version, as we have with 51.1. I'll have a look at it.

Copy link
Member

@spsjvc spsjvc Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see what you mean. I think the 51.1 situation is unique and we hopefully won't have a similar one in the future. We could just add a special case in getConsensusReleaseByVersion for 51.1 to return the proper one, and for the standard case just find by version

{
version: 51,
wasmModuleRoot: '0x8a7513bf7bb3e3db04b0d982d0e973bcf57bf8b88aef7c6d03dba3a81a56a499',
maxArbOSVersion: 51,
},
{
version: 51.1,
wasmModuleRoot: '0x28b6ad83ed87b21a87c73f7a0296a135ebc7074e449efb289ececccad771ccd6',
maxArbOSVersion: 51.1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Suggested change
maxArbOSVersion: 51.1,
maxArbOSVersion: 51,

Expand Down Expand Up @@ -129,3 +142,9 @@ export function isKnownWasmModuleRoot(wasmModuleRoot: Hex): wasmModuleRoot is Wa
.includes(wasmModuleRoot)
);
}

export function isBlacklistedWasmModuleRoot(wasmModuleRoot: Hex): boolean {
return blacklistedConsensusReleases
.map((release) => release.wasmModuleRoot.toLowerCase())
.includes(wasmModuleRoot.toLowerCase());
}
19 changes: 19 additions & 0 deletions src/wasmModuleRoot.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';

import { isKnownWasmModuleRoot } from './wasmModuleRoot';
import { isBlacklistedWasmModuleRoot } from './wasmModuleRoot';

describe('isKnownWasmModuleRoot', () => {
it('returns true for a known wasm module root', () => {
Expand Down Expand Up @@ -28,3 +29,21 @@ describe('isKnownWasmModuleRoot', () => {
).toEqual(false);
});
});

describe('isBlacklistedWasmModuleRoot', () => {
it('returns true for a blacklisted wasm module root', () => {
expect(
isBlacklistedWasmModuleRoot(
'0x8a7513bf7bb3e3db04b0d982d0e973bcf57bf8b88aef7c6d03dba3a81a56a499',
),
).toEqual(true);
});

it('returns false for a non-blacklisted wasm module root', () => {
expect(
isBlacklistedWasmModuleRoot(
'0xdb698a2576298f25448bc092e52cf13b1e24141c997135d70f217d674bbeb69a',
),
).toEqual(false);
});
});
Loading