Skip to content

Commit ba51856

Browse files
committed
test: add l3 node to harness
1 parent c629325 commit ba51856

10 files changed

+217
-22
lines changed

src/actions/sequencerInbox.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ if (env) {
4141
deployer = env.l2.accounts.deployer;
4242
l3SequencerInbox = env.l3.sequencerInbox;
4343
l3BatchPoster = env.l3.batchPoster;
44-
l3UpgradeExecutor = env.l3.upgradeExecutor;
44+
l3UpgradeExecutor = env.l3.parentChainUpgradeExecutor;
4545
} else {
4646
const testnodeAccounts = getNitroTestnodePrivateKeyAccounts();
4747
l3RollupOwner = testnodeAccounts.l3RollupOwner;

src/contracts/IERC20Inbox.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const erc20InboxABI = [
2+
{
3+
stateMutability: 'nonpayable',
4+
type: 'function',
5+
inputs: [{ name: 'amount', type: 'uint256' }],
6+
name: 'depositERC20',
7+
outputs: [],
8+
},
9+
] as const;

src/decorators/rollupAdminLogicPublicActions.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let l3UpgradeExecutor: Address;
3131
if (env) {
3232
l3RollupOwner = env.l3.accounts.rollupOwner;
3333
l3Rollup = env.l3.rollup;
34-
l3UpgradeExecutor = env.l3.upgradeExecutor;
34+
l3UpgradeExecutor = env.l3.parentChainUpgradeExecutor;
3535
} else {
3636
l3RollupOwner = getNitroTestnodePrivateKeyAccounts().l3RollupOwner;
3737

src/decorators/sequencerInboxActions.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ if (env) {
3434
l3Bridge = env.l3.bridge;
3535
l3Rollup = env.l3.rollup;
3636
l3BatchPoster = env.l3.batchPoster;
37-
l3UpgradeExecutor = env.l3.upgradeExecutor;
37+
l3UpgradeExecutor = env.l3.parentChainUpgradeExecutor;
3838
} else {
3939
const testnodeAccounts = getNitroTestnodePrivateKeyAccounts();
4040
l3RollupOwner = testnodeAccounts.l3RollupOwner;

src/getBatchPosters.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let l3SequencerInbox: Address;
2222
if (env) {
2323
l3RollupOwner = env.l3.accounts.rollupOwner;
2424
l3Rollup = env.l3.rollup;
25-
l3UpgradeExecutor = env.l3.upgradeExecutor;
25+
l3UpgradeExecutor = env.l3.parentChainUpgradeExecutor;
2626
l3SequencerInbox = env.l3.sequencerInbox;
2727
} else {
2828
l3RollupOwner = getNitroTestnodePrivateKeyAccounts().l3RollupOwner;

src/getValidators.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ let l3UpgradeExecutor: Address;
2828
if (env) {
2929
l3RollupOwner = env.l3.accounts.rollupOwner;
3030
l3Rollup = env.l3.rollup;
31-
l3UpgradeExecutor = env.l3.upgradeExecutor;
31+
l3UpgradeExecutor = env.l3.parentChainUpgradeExecutor;
3232
} else {
3333
l3RollupOwner = getNitroTestnodePrivateKeyAccounts().l3RollupOwner;
3434

src/integrationTestHelpers/anvilHarness.ts

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ import { prepareChainConfig } from '../prepareChainConfig';
3232
import { prepareNodeConfig } from '../prepareNodeConfig';
3333
import { ChainConfig } from '../types/ChainConfig';
3434
import { CreateRollupParams, RollupCreatorSupportedVersion } from '../types/createRollupTypes';
35+
import type { ParentChainId } from '../types/ParentChain';
3536
import { testConstants } from './constants';
3637
import {
3738
cleanupCurrentHarnessResources,
3839
cleanupStaleHarnessContainers,
3940
cleanupStaleHarnessNetworks,
4041
createDockerNetwork,
4142
startL1AnvilContainer,
42-
startL2NitroContainer,
43+
startNitroContainer,
4344
waitForRpc,
4445
} from './dockerHelpers';
4546
import {
47+
bridgeNativeTokenToOrbitChain,
4648
ContractArtifact,
4749
configureL2Fees,
4850
createAccount,
@@ -73,13 +75,16 @@ type BaseStack<L2Accounts, L3Accounts> = {
7375
upgradeExecutor: Address;
7476
};
7577
l3: {
78+
rpcUrl: string;
79+
chain: Chain;
7680
accounts: L3Accounts;
7781
timingParams: CustomTimingParams;
7882
nativeToken: Address;
7983
rollup: Address;
8084
bridge: Address;
8185
sequencerInbox: Address;
82-
upgradeExecutor: Address;
86+
parentChainUpgradeExecutor: Address;
87+
childChainUpgradeExecutor: Address;
8388
batchPoster: Address;
8489
};
8590
};
@@ -112,6 +117,7 @@ let runtimeDir: string | undefined;
112117
let dockerNetworkName: string | undefined;
113118
let l1ContainerName: string | undefined;
114119
let l2ContainerName: string | undefined;
120+
let l3ContainerName: string | undefined;
115121
let cleanupHookRegistered = false;
116122
let teardownStarted = false;
117123
let l1RpcCachingProxy: RpcCachingProxy | undefined;
@@ -121,8 +127,11 @@ const NITRO_TESTNODE_VALIDATOR_SIGNER = '0x6A568afe0f82d34759347bb36F14A6bB171d2
121127

122128
function prepareNitroRuntimeDir(runtimeDir: string) {
123129
chmodSync(runtimeDir, 0o777);
124-
mkdirSync(join(runtimeDir, 'nitro-data'), { recursive: true, mode: 0o777 });
125-
chmodSync(join(runtimeDir, 'nitro-data'), 0o777);
130+
131+
for (const dataDir of ['nitro-data-l2', 'nitro-data-l3']) {
132+
mkdirSync(join(runtimeDir, dataDir), { recursive: true, mode: 0o777 });
133+
chmodSync(join(runtimeDir, dataDir), 0o777);
134+
}
126135
}
127136

128137
async function getNitroTestnodeStyleValidators(
@@ -174,6 +183,7 @@ export function dehydrateAnvilTestStack(env: AnvilTestStack): InjectedAnvilTestS
174183
export function hydrateAnvilTestStack(env: InjectedAnvilTestStack): AnvilTestStack {
175184
const l2Chain = defineChain(env.l2.chain) as RegisteredParentChain;
176185
registerCustomParentChain(l2Chain);
186+
const l3Chain = defineChain(env.l3.chain);
177187

178188
return {
179189
...env,
@@ -187,6 +197,7 @@ export function hydrateAnvilTestStack(env: InjectedAnvilTestStack): AnvilTestSta
187197
},
188198
l3: {
189199
...env.l3,
200+
chain: l3Chain,
190201
accounts: {
191202
rollupOwner: createAccount(env.l3.accounts.rollupOwnerPrivateKey),
192203
tokenBridgeDeployer: createAccount(env.l3.accounts.tokenBridgeDeployerPrivateKey),
@@ -223,6 +234,7 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
223234
const l3ChainId = l2ChainId + 1;
224235
const l1RpcPort = testConstants.DEFAULT_L1_RPC_PORT;
225236
const l2RpcPort = testConstants.DEFAULT_L2_RPC_PORT;
237+
const l3RpcPort = testConstants.DEFAULT_L3_RPC_PORT;
226238
const anvilImage = testConstants.DEFAULT_ANVIL_IMAGE;
227239
const nitroImage = testConstants.DEFAULT_NITRO_IMAGE;
228240
const sepoliaBeaconRpc = testConstants.DEFAULT_SEPOLIA_BEACON_RPC;
@@ -347,7 +359,11 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
347359
parentChainBeaconRpcUrl: sepoliaBeaconRpc,
348360
});
349361

350-
if (l2NodeConfig.node === undefined || l2NodeConfig.node['batch-poster'] === undefined) {
362+
if (
363+
l2NodeConfig.node === undefined ||
364+
l2NodeConfig.node['batch-poster'] === undefined ||
365+
l2NodeConfig.node.staker === undefined
366+
) {
351367
throw new Error('L2 node config batch poster is undefined');
352368
}
353369

@@ -360,20 +376,23 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
360376
'wait-for-l1-finality': false,
361377
};
362378

363-
l2NodeConfig.node!.staker!.enable = false;
364-
const nodeConfigPath = join(runtimeDir, 'l2-node-config.json');
365-
writeFileSync(nodeConfigPath, JSON.stringify(l2NodeConfig, null, 2), { mode: 0o644 });
379+
l2NodeConfig.node.staker.enable = false;
380+
381+
const l2NodeConfigPath = join(runtimeDir, 'l2-node-config.json');
382+
writeFileSync(l2NodeConfigPath, JSON.stringify(l2NodeConfig, null, 2), { mode: 0o644 });
366383

367384
// Starting L2 node (Nitro)
368385
console.log('Starting L2 Nitro node...');
369386
l2ContainerName = `chain-sdk-int-test-l2-${Date.now()}`;
370387

371-
startL2NitroContainer({
388+
startNitroContainer({
372389
containerName: l2ContainerName,
373390
networkName: dockerNetworkName,
374-
l2RpcPort,
391+
rpcPort: l2RpcPort,
375392
runtimeDir,
376393
nitroImage,
394+
configFilePath: '/runtime/l2-node-config.json',
395+
persistentChainPath: '/runtime/nitro-data-l2',
377396
});
378397

379398
const l2RpcUrl = `http://127.0.0.1:${l2RpcPort}`;
@@ -552,6 +571,97 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
552571
});
553572
console.log('L3 rollup contracts deployed on L2\n');
554573

574+
const l3ChainConfig = JSON.parse(l3RollupConfig.chainConfig) as ChainConfig;
575+
const l3NodeConfig = prepareNodeConfig({
576+
chainName: 'Chain SDK Int Test L3',
577+
chainConfig: l3ChainConfig,
578+
coreContracts: l3Rollup.coreContracts,
579+
batchPosterPrivateKey: harnessDeployer.privateKey,
580+
validatorPrivateKey: harnessDeployer.privateKey,
581+
stakeToken: l3RollupConfig.stakeToken,
582+
parentChainId: l2ChainId as ParentChainId,
583+
parentChainIsArbitrum: true,
584+
parentChainRpcUrl: `http://${l2ContainerName}:8449`,
585+
});
586+
587+
if (
588+
l3NodeConfig.node === undefined ||
589+
l3NodeConfig.node['batch-poster'] === undefined ||
590+
l3NodeConfig.node.staker === undefined
591+
) {
592+
throw new Error('L3 node config is undefined');
593+
}
594+
595+
// The test harness only needs a local L3 sequencer/read node.
596+
// Disable services that require extra parent-chain plumbing.
597+
l3NodeConfig.node['batch-poster'].enable = false;
598+
l3NodeConfig.node.staker.enable = false;
599+
if (l3ChainConfig.arbitrum.DataAvailabilityCommittee) {
600+
delete l3NodeConfig.node['data-availability']?.['rpc-aggregator'];
601+
}
602+
603+
const l3NodeConfigPath = join(runtimeDir, 'l3-node-config.json');
604+
writeFileSync(l3NodeConfigPath, JSON.stringify(l3NodeConfig, null, 2), { mode: 0o644 });
605+
606+
// Starting L3 node (Nitro)
607+
console.log('Starting L3 Nitro node...');
608+
l3ContainerName = `chain-sdk-int-test-l3-${Date.now()}`;
609+
610+
startNitroContainer({
611+
containerName: l3ContainerName,
612+
networkName: dockerNetworkName,
613+
rpcPort: l3RpcPort,
614+
runtimeDir,
615+
nitroImage,
616+
configFilePath: '/runtime/l3-node-config.json',
617+
persistentChainPath: '/runtime/nitro-data-l3',
618+
});
619+
620+
const l3RpcUrl = `http://127.0.0.1:${l3RpcPort}`;
621+
const l3Chain = defineChain({
622+
id: l3ChainId,
623+
network: 'chain-sdk-int-test-l3',
624+
name: 'Chain SDK Int Test L3',
625+
nativeCurrency: { name: 'Orbit Test Token', symbol: 'ORBT', decimals: 18 },
626+
rpcUrls: {
627+
default: { http: [l3RpcUrl] },
628+
public: { http: [l3RpcUrl] },
629+
},
630+
testnet: true,
631+
});
632+
633+
await waitForRpc({
634+
rpcUrl: l3RpcUrl,
635+
timeoutMs: 60_000,
636+
failIfContainerExited: l3ContainerName,
637+
});
638+
console.log('L3 Nitro node is ready\n');
639+
const l3Client = createPublicClient({
640+
chain: l3Chain,
641+
transport: http(l3RpcUrl),
642+
});
643+
644+
await (
645+
await customGasToken.deposit({
646+
value: parseEther('100'),
647+
...testConstants.LOW_L2_FEE_OVERRIDES,
648+
})
649+
).wait();
650+
651+
console.log('Funding deployer on L3...');
652+
await bridgeNativeTokenToOrbitChain({
653+
parentPublicClient: l2Client,
654+
parentWalletClient: l2WalletClient,
655+
childPublicClient: l3Client,
656+
depositor: harnessDeployer,
657+
inbox: l3Rollup.coreContracts.inbox,
658+
nativeToken: customGasToken.address as Address,
659+
amount: parseEther('100'),
660+
});
661+
console.log('Deployer funded on L3\n');
662+
663+
const l3ChildChainUpgradeExecutor = l3Rollup.coreContracts.upgradeExecutor;
664+
555665
initializedEnv = {
556666
l1: {
557667
rpcUrl: l1RpcUrl,
@@ -569,6 +679,8 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
569679
upgradeExecutor: l2Rollup.coreContracts.upgradeExecutor,
570680
},
571681
l3: {
682+
rpcUrl: l3RpcUrl,
683+
chain: l3Chain,
572684
accounts: {
573685
rollupOwner: harnessDeployer,
574686
tokenBridgeDeployer: harnessDeployer,
@@ -578,7 +690,8 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
578690
rollup: l3Rollup.coreContracts.rollup,
579691
bridge: l3Rollup.coreContracts.bridge,
580692
sequencerInbox: l3Rollup.coreContracts.sequencerInbox,
581-
upgradeExecutor: l3Rollup.coreContracts.upgradeExecutor,
693+
parentChainUpgradeExecutor: l3Rollup.coreContracts.upgradeExecutor,
694+
childChainUpgradeExecutor: l3ChildChainUpgradeExecutor,
582695
batchPoster: harnessDeployer.address,
583696
},
584697
};
@@ -615,12 +728,14 @@ export function teardownAnvilTestStack() {
615728
}
616729

617730
cleanupCurrentHarnessResources({
731+
l3ContainerName: l3ContainerName,
618732
l2ContainerName: l2ContainerName,
619733
l1ContainerName: l1ContainerName,
620734
dockerNetworkName: dockerNetworkName,
621735
runtimeDir: runtimeDir,
622736
});
623737

738+
l3ContainerName = undefined;
624739
l2ContainerName = undefined;
625740
l1ContainerName = undefined;
626741
dockerNetworkName = undefined;

src/integrationTestHelpers/anvilHarnessHelpers.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { sepolia } from 'viem/chains';
1313
import { ethers } from 'ethers';
1414

1515
import { arbOwnerABI, arbOwnerAddress } from '../contracts/ArbOwner';
16+
import { erc20ABI } from '../contracts/ERC20';
17+
import { erc20InboxABI } from '../contracts/IERC20Inbox';
1618
import { testConstants } from './constants';
1719
import {
1820
dockerAsync,
@@ -218,6 +220,67 @@ export async function fundL2Deployer(params: {
218220
);
219221
}
220222

223+
export async function bridgeNativeTokenToOrbitChain(params: {
224+
parentPublicClient: PublicClient;
225+
parentWalletClient: WalletClient;
226+
childPublicClient: PublicClient;
227+
depositor: PrivateKeyAccountWithPrivateKey;
228+
inbox: Address;
229+
nativeToken: Address;
230+
amount: bigint;
231+
}) {
232+
const {
233+
parentPublicClient,
234+
parentWalletClient,
235+
childPublicClient,
236+
depositor,
237+
inbox,
238+
nativeToken,
239+
amount,
240+
} = params;
241+
242+
const balanceBefore = await childPublicClient.getBalance({ address: depositor.address });
243+
const targetBalance = balanceBefore + amount;
244+
245+
const approveHash = await parentWalletClient.writeContract({
246+
account: depositor,
247+
chain: parentWalletClient.chain,
248+
address: nativeToken,
249+
abi: erc20ABI,
250+
functionName: 'approve',
251+
args: [inbox, amount],
252+
});
253+
254+
await parentPublicClient.waitForTransactionReceipt({ hash: approveHash });
255+
256+
const depositHash = await parentWalletClient.writeContract({
257+
account: depositor,
258+
chain: parentWalletClient.chain,
259+
address: inbox,
260+
abi: erc20InboxABI,
261+
functionName: 'depositERC20',
262+
args: [amount],
263+
});
264+
265+
await parentPublicClient.waitForTransactionReceipt({ hash: depositHash });
266+
267+
const startedAt = Date.now();
268+
269+
while (Date.now() - startedAt < 60_000) {
270+
const updatedBalance = await childPublicClient.getBalance({ address: depositor.address });
271+
if (updatedBalance >= targetBalance) {
272+
return updatedBalance;
273+
}
274+
275+
await sleep(1000);
276+
}
277+
278+
const finalBalance = await childPublicClient.getBalance({ address: depositor.address });
279+
throw new Error(
280+
`Timed out bridging native token to ${depositor.address} on orbit chain through inbox ${inbox}. Current balance: ${finalBalance}`,
281+
);
282+
}
283+
221284
export async function configureL2Fees(
222285
publicClient: PublicClient,
223286
walletClient: WalletClient,

src/integrationTestHelpers/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const testConstants = {
1313
DEFAULT_L3_CHAIN_ID: 421_338,
1414
DEFAULT_L1_RPC_PORT: 9645,
1515
DEFAULT_L2_RPC_PORT: 8747,
16+
DEFAULT_L3_RPC_PORT: 8749,
1617
DEFAULT_SEPOLIA_FORK_BLOCK_NUMBER: 10_490_000,
1718
DEFAULT_SOURCE_DEPLOYER_PRIVATE_KEY:
1819
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',

0 commit comments

Comments
 (0)