Skip to content

Commit 9d8bd01

Browse files
authored
Merge pull request #526 from everclearorg/fix/review_514
feat: enhance route validation for Linea, Zircuit, and zkSync adapters
2 parents 0dcf904 + b68a148 commit 9d8bd01

File tree

7 files changed

+257
-273
lines changed

7 files changed

+257
-273
lines changed

packages/adapters/rebalance/src/adapters/linea/linea.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
lineaMessageServiceAbi,
2727
lineaTokenBridgeAbi,
2828
} from './constants';
29-
import { LineaSDK, OnChainMessageStatus } from '@consensys/linea-sdk';
29+
import { LineaSDK } from '@consensys/linea-sdk';
3030

3131
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
3232

@@ -71,6 +71,10 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
7171
): Promise<MemoizedTransactionRequest[]> {
7272
try {
7373
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === LINEA_CHAIN_ID;
74+
const isL2ToL1 = route.origin === LINEA_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
75+
if (!isL1ToL2 && !isL2ToL1) {
76+
throw new Error(`Unsupported Linea route: ${route.origin}->${route.destination}`);
77+
}
7478
const isETH = route.asset.toLowerCase() === ZERO_ADDRESS;
7579
const transactions: MemoizedTransactionRequest[] = [];
7680

@@ -205,6 +209,10 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
205209
): Promise<boolean> {
206210
try {
207211
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === LINEA_CHAIN_ID;
212+
const isL2ToL1 = route.origin === LINEA_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
213+
if (!isL1ToL2 && !isL2ToL1) {
214+
throw new Error(`Unsupported Linea route: ${route.origin}->${route.destination}`);
215+
}
208216

209217
if (isL1ToL2) {
210218
// L1→L2: Auto-claimed by Linea postman service
@@ -261,7 +269,11 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
261269
originTransaction: TransactionReceipt,
262270
): Promise<MemoizedTransactionRequest | void> {
263271
try {
272+
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === LINEA_CHAIN_ID;
264273
const isL2ToL1 = route.origin === LINEA_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
274+
if (!isL1ToL2 && !isL2ToL1) {
275+
throw new Error(`Unsupported Linea route: ${route.origin}->${route.destination}`);
276+
}
265277

266278
if (isL2ToL1) {
267279
const l1Client = await this.getClient(ETHEREUM_CHAIN_ID);
@@ -374,9 +386,7 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
374386
}
375387
}
376388

377-
private async getMessageProof(
378-
originTransaction: TransactionReceipt,
379-
): Promise<
389+
private async getMessageProof(originTransaction: TransactionReceipt): Promise<
380390
| {
381391
proof: `0x${string}`[];
382392
messageNumber: bigint;
@@ -450,10 +460,7 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
450460
// which commercial providers like Alchemy reject due to block range limits.
451461
// Use configured L1 providers first, then fall back to public RPCs.
452462
const l1Providers = this.chains[ETHEREUM_CHAIN_ID.toString()]?.providers ?? [];
453-
const l1RpcCandidates = [
454-
...l1Providers,
455-
...LINEA_SDK_FALLBACK_L1_RPCS,
456-
];
463+
const l1RpcCandidates = [...l1Providers, ...LINEA_SDK_FALLBACK_L1_RPCS];
457464

458465
for (const l1RpcUrl of l1RpcCandidates) {
459466
try {

packages/adapters/rebalance/src/adapters/zircuit/zircuit.ts

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
ZIRCUIT_L2_STANDARD_BRIDGE,
2323
ZIRCUIT_OPTIMISM_PORTAL,
2424
ZIRCUIT_L2_OUTPUT_ORACLE,
25-
ZIRCUIT_L2_TO_L1_MESSAGE_PASSER,
2625
ETHEREUM_CHAIN_ID,
2726
ZIRCUIT_CHAIN_ID,
2827
CHALLENGE_PERIOD_SECONDS,
@@ -31,7 +30,6 @@ import {
3130
zircuitOptimismPortalAbi,
3231
zircuitL2OutputOracleAbi,
3332
zircuitL2ToL1MessagePasserAbi,
34-
L2_ETH_TOKEN,
3533
ZERO_ADDRESS,
3634
} from './constants';
3735

@@ -51,6 +49,12 @@ interface OutputRootProof {
5149
latestBlockhash: `0x${string}`;
5250
}
5351

52+
interface ZircuitProofResult {
53+
l2OutputIndex: bigint;
54+
outputRootProof: OutputRootProof;
55+
withdrawalProof: `0x${string}`[];
56+
}
57+
5458
export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
5559
constructor(
5660
protected readonly chains: Record<string, ChainConfiguration>,
@@ -82,6 +86,10 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
8286
): Promise<MemoizedTransactionRequest[]> {
8387
try {
8488
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === ZIRCUIT_CHAIN_ID;
89+
const isL2ToL1 = route.origin === ZIRCUIT_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
90+
if (!isL1ToL2 && !isL2ToL1) {
91+
throw new Error(`Unsupported Zircuit route: ${route.origin}->${route.destination}`);
92+
}
8593
const isETH = route.asset.toLowerCase() === ZERO_ADDRESS;
8694
const transactions: MemoizedTransactionRequest[] = [];
8795

@@ -128,7 +136,13 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
128136
}
129137

130138
// Resolve the L2 token address via tickerHash mapping
131-
const l2Token = getDestinationAssetAddress(route.asset, route.origin, route.destination, this.chains, this.logger);
139+
const l2Token = getDestinationAssetAddress(
140+
route.asset,
141+
route.origin,
142+
route.destination,
143+
this.chains,
144+
this.logger,
145+
);
132146
if (!l2Token) {
133147
throw new Error(`No L2 token mapping found for ${route.asset} on chain ${route.destination}`);
134148
}
@@ -195,7 +209,13 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
195209
}
196210

197211
// Resolve the L1 token address via tickerHash mapping
198-
const l1Token = getDestinationAssetAddress(route.asset, route.origin, route.destination, this.chains, this.logger);
212+
const l1Token = getDestinationAssetAddress(
213+
route.asset,
214+
route.origin,
215+
route.destination,
216+
this.chains,
217+
this.logger,
218+
);
199219
if (!l1Token) {
200220
throw new Error(`No L1 token mapping found for ${route.asset} on chain ${route.destination}`);
201221
}
@@ -235,6 +255,10 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
235255
): Promise<boolean> {
236256
try {
237257
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === ZIRCUIT_CHAIN_ID;
258+
const isL2ToL1 = route.origin === ZIRCUIT_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
259+
if (!isL1ToL2 && !isL2ToL1) {
260+
throw new Error(`Unsupported Zircuit route: ${route.origin}->${route.destination}`);
261+
}
238262

239263
if (isL1ToL2) {
240264
// L1→L2: Auto-relayed by the sequencer
@@ -279,11 +303,7 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
279303
args: [withdrawalHash],
280304
});
281305

282-
const [outputRoot, timestamp, l2OutputIndex] = provenWithdrawal as [
283-
`0x${string}`,
284-
bigint,
285-
bigint,
286-
];
306+
const [, timestamp] = provenWithdrawal as [`0x${string}`, bigint, bigint];
287307

288308
if (timestamp > 0) {
289309
// Withdrawal is proven, check if challenge period has passed
@@ -339,7 +359,11 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
339359
originTransaction: TransactionReceipt,
340360
): Promise<MemoizedTransactionRequest | void> {
341361
try {
362+
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === ZIRCUIT_CHAIN_ID;
342363
const isL2ToL1 = route.origin === ZIRCUIT_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
364+
if (!isL1ToL2 && !isL2ToL1) {
365+
throw new Error(`Unsupported Zircuit route: ${route.origin}->${route.destination}`);
366+
}
343367

344368
if (isL2ToL1) {
345369
const l1Client = await this.getClient(ETHEREUM_CHAIN_ID);
@@ -378,11 +402,7 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
378402
args: [withdrawalHash],
379403
});
380404

381-
const [outputRoot, timestamp, l2OutputIndex] = provenWithdrawal as [
382-
`0x${string}`,
383-
bigint,
384-
bigint,
385-
];
405+
const [, timestamp] = provenWithdrawal as [`0x${string}`, bigint, bigint];
386406

387407
if (timestamp > 0) {
388408
// Withdrawal is proven, check if we can finalize
@@ -459,6 +479,10 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
459479

460480
async isCallbackComplete(route: RebalanceRoute, originTransaction: TransactionReceipt): Promise<boolean> {
461481
const isL1ToL2 = route.origin === ETHEREUM_CHAIN_ID && route.destination === ZIRCUIT_CHAIN_ID;
482+
const isL2ToL1 = route.origin === ZIRCUIT_CHAIN_ID && route.destination === ETHEREUM_CHAIN_ID;
483+
if (!isL1ToL2 && !isL2ToL1) {
484+
throw new Error(`Unsupported Zircuit route: ${route.origin}->${route.destination}`);
485+
}
462486
if (isL1ToL2) {
463487
return true;
464488
}
@@ -546,10 +570,14 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
546570

547571
private hashWithdrawal(tx: WithdrawalTransaction): `0x${string}` {
548572
return keccak256(
549-
encodeAbiParameters(
550-
parseAbiParameters('uint256, address, address, uint256, uint256, bytes'),
551-
[tx.nonce, tx.sender, tx.target, tx.value, tx.gasLimit, tx.data],
552-
),
573+
encodeAbiParameters(parseAbiParameters('uint256, address, address, uint256, uint256, bytes'), [
574+
tx.nonce,
575+
tx.sender,
576+
tx.target,
577+
tx.value,
578+
tx.gasLimit,
579+
tx.data,
580+
]),
553581
);
554582
}
555583

@@ -564,21 +592,22 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
564592
l2Client: PublicClient,
565593
l1Client: PublicClient,
566594
originTransaction: TransactionReceipt,
567-
): Promise<
568-
| {
569-
l2OutputIndex: bigint;
570-
outputRootProof: OutputRootProof;
571-
withdrawalProof: `0x${string}`[];
572-
}
573-
| undefined
574-
> {
595+
): Promise<ZircuitProofResult | undefined> {
575596
try {
576-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
577-
const result = await buildProveZircuitWithdrawal(l2Client as any, {
597+
const buildProof = buildProveZircuitWithdrawal as unknown as (
598+
l2Client: PublicClient,
599+
params: {
600+
receipt: TransactionReceipt;
601+
l1Client: PublicClient;
602+
l2OutputOracleAddress: `0x${string}`;
603+
},
604+
) => Promise<ZircuitProofResult>;
605+
606+
const result = await buildProof(l2Client, {
578607
receipt: originTransaction,
579-
l1Client: l1Client as any,
608+
l1Client,
580609
l2OutputOracleAddress: ZIRCUIT_L2_OUTPUT_ORACLE as `0x${string}`,
581-
} as any);
610+
});
582611

583612
this.logger.info('Zircuit proof built successfully', {
584613
txHash: originTransaction.transactionHash,
@@ -591,9 +620,9 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
591620
});
592621

593622
return {
594-
l2OutputIndex: result.l2OutputIndex as bigint,
595-
outputRootProof: result.outputRootProof as OutputRootProof,
596-
withdrawalProof: result.withdrawalProof as `0x${string}`[],
623+
l2OutputIndex: result.l2OutputIndex,
624+
outputRootProof: result.outputRootProof,
625+
withdrawalProof: result.withdrawalProof,
597626
};
598627
} catch (error) {
599628
this.logger.warn('Failed to build Zircuit withdrawal proof', {

packages/adapters/rebalance/src/adapters/zksync/constants.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export const zkSyncL2BridgeAbi = parseAbi([
2323
'event WithdrawalInitiated(address indexed l2Sender, address indexed l1Receiver, address indexed l2Token, uint256 amount)',
2424
]);
2525

26-
export const zkSyncL2EthTokenAbi = parseAbi([
27-
'function withdraw(address _l1Receiver) payable',
28-
]);
26+
export const zkSyncL2EthTokenAbi = parseAbi(['function withdraw(address _l1Receiver) payable']);
2927

3028
export const zkSyncDiamondProxyAbi = parseAbi([
3129
'function getTotalBatchesExecuted() view returns (uint256)',

0 commit comments

Comments
 (0)