Skip to content

Commit 2105b6a

Browse files
authored
No template op stack script (#42)
* bump sdk * Revert "bump sdk" This reverts commit d17fded. * abstract class * add op redeemer * op test passing * update devnode addresses and dont use waitToProve
1 parent 48378d3 commit 2105b6a

File tree

6 files changed

+674
-62
lines changed

6 files changed

+674
-62
lines changed

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"prepare": "forge install && cd lib/arbitrum-sdk && yarn",
3131
"prepublishOnly": "make clean && make build",
3232
"gen-recipients": "make install && hardhat run src-ts/getRecipientData.ts",
33-
"test:e2e": "./test/e2e/test-e2e.bash"
33+
"test:e2e": "./test/e2e/test-e2e.bash",
34+
"test:op-e2e": "mocha test/op-e2e/OpChildToParentRewardRouter.test.ts --timeout 300000"
3435
},
3536
"private": false,
3637
"devDependencies": {
@@ -52,7 +53,8 @@
5253
"solidity-coverage": "^0.8.5",
5354
"ts-node": "^10.9.1",
5455
"typechain": "^8.1.0",
55-
"typescript": "^5.2.2"
56+
"typescript": "^5.2.2",
57+
"viem": "^2.22.8"
5658
},
5759
"dependencies": {
5860
"@types/yargs": "^17.0.32",
+183-36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { JsonRpcProvider } from "@ethersproject/providers";
1+
import { JsonRpcProvider, Log } from "@ethersproject/providers";
22
import { Wallet } from "ethers";
33
import {
44
ChildToParentRewardRouter__factory,
@@ -12,72 +12,112 @@ import {
1212
L2ToL1Message,
1313
L2ToL1MessageStatus,
1414
} from "../../lib/arbitrum-sdk/src";
15+
16+
import {
17+
Chain,
18+
ChainContract,
19+
createPublicClient,
20+
createWalletClient,
21+
Hex,
22+
http,
23+
publicActions,
24+
} from 'viem'
25+
import { privateKeyToAccount } from 'viem/accounts'
26+
import {
27+
getWithdrawals,
28+
GetWithdrawalStatusReturnType,
29+
publicActionsL1,
30+
publicActionsL2,
31+
walletActionsL1,
32+
} from 'viem/op-stack'
33+
1534
const wait = async (ms: number) => new Promise((res) => setTimeout(res, ms));
1635

17-
export default class ChildToParentMessageRedeemer {
18-
public startBlock: number;
19-
public childToParentRewardRouter: ChildToParentRewardRouter;
20-
public readonly retryDelay: number;
36+
export abstract class ChildToParentMessageRedeemer {
2137
constructor(
22-
public readonly childChainProvider: JsonRpcProvider,
23-
public readonly parentChainSigner: Wallet,
38+
public readonly childChainRpc: string,
39+
public readonly parentChainRpc: string,
40+
protected readonly parentChainPrivateKey: string,
2441
public readonly childToParentRewardRouterAddr: string,
2542
public readonly blockLag: number,
26-
initialStartBlock: number,
27-
retryDelay = 1000 * 60 * 10
28-
) {
29-
this.startBlock = initialStartBlock;
30-
this.childToParentRewardRouter = ChildToParentRewardRouter__factory.connect(
31-
childToParentRewardRouterAddr,
32-
childChainProvider
33-
);
34-
this.retryDelay = retryDelay;
35-
}
43+
public startBlock: number = 0,
44+
public readonly retryDelay = 1000 * 60 * 10
45+
) {}
46+
47+
protected abstract _handleLogs(logs: Log[], oneOff: boolean): Promise<void>;
3648

3749
public async redeemChildToParentMessages(oneOff = false) {
50+
const childChainProvider = new JsonRpcProvider(this.childChainRpc);
51+
3852
const toBlock =
39-
(await this.childChainProvider.getBlockNumber()) - this.blockLag;
40-
const logs = await this.childChainProvider.getLogs({
53+
(await childChainProvider.getBlockNumber()) - this.blockLag;
54+
const logs = await childChainProvider.getLogs({
4155
fromBlock: this.startBlock,
4256
toBlock: toBlock,
43-
...this.childToParentRewardRouter.filters.FundsRouted(),
57+
address: this.childToParentRewardRouterAddr,
58+
topics: [ChildToParentRewardRouter__factory.createInterface().getEventTopic('FundsRouted')],
4459
});
4560
if (logs.length) {
4661
console.log(
4762
`Found ${logs.length} route events between blocks ${this.startBlock} and ${toBlock}`
4863
);
4964
}
65+
await this._handleLogs(logs, oneOff);
66+
return toBlock
67+
}
5068

69+
public async run(oneOff = false) {
70+
while (true) {
71+
let toBlock = 0
72+
try {
73+
toBlock = await this.redeemChildToParentMessages(oneOff);
74+
} catch (err) {
75+
console.log("err", err);
76+
}
77+
if (oneOff) {
78+
break;
79+
} else {
80+
this.startBlock = toBlock + 1;
81+
await wait(1000 * 60 * 60);
82+
}
83+
}
84+
}
85+
}
86+
87+
export class ArbChildToParentMessageRedeemer extends ChildToParentMessageRedeemer {
88+
protected async _handleLogs(logs: Log[], oneOff: boolean): Promise<void> {
89+
const childChainProvider = new JsonRpcProvider(this.childChainRpc);
90+
const parentChainSigner = new Wallet(this.parentChainPrivateKey, new JsonRpcProvider(this.parentChainRpc));
5191
for (let log of logs) {
5292
const arbTransactionRec = new L2TransactionReceipt(
53-
await this.childChainProvider.getTransactionReceipt(log.transactionHash)
93+
await childChainProvider.getTransactionReceipt(log.transactionHash)
5494
);
5595
let l2ToL1Events =
56-
(await arbTransactionRec.getL2ToL1Events()) as EventArgs<L2ToL1TxEvent>[];
96+
arbTransactionRec.getL2ToL1Events() as EventArgs<L2ToL1TxEvent>[];
5797

5898
if (l2ToL1Events.length != 1) {
5999
throw new Error("Only 1 l2 to l1 message per tx supported");
60100
}
61101

62102
for (let l2ToL1Event of l2ToL1Events) {
63103
const l2ToL1Message = L2ToL1Message.fromEvent(
64-
this.parentChainSigner,
104+
parentChainSigner,
65105
l2ToL1Event
66106
);
67107
if (!oneOff) {
68108
console.log(`Waiting for ${l2ToL1Event.hash} to be ready:`);
69109
await l2ToL1Message.waitUntilReadyToExecute(
70-
this.childChainProvider,
110+
childChainProvider,
71111
this.retryDelay
72112
);
73113
}
74114

75-
const status = await l2ToL1Message.status(this.childChainProvider);
115+
const status = await l2ToL1Message.status(childChainProvider);
76116
switch (status) {
77117
case L2ToL1MessageStatus.CONFIRMED: {
78118
console.log(l2ToL1Event.hash, "confirmed; executing:");
79119
const rec = await (
80-
await l2ToL1Message.execute(this.childChainProvider)
120+
await l2ToL1Message.execute(childChainProvider)
81121
).wait(2);
82122
console.log(`${l2ToL1Event.hash} executed:`, rec.transactionHash);
83123
break;
@@ -96,21 +136,128 @@ export default class ChildToParentMessageRedeemer {
96136
}
97137
}
98138
}
99-
this.startBlock = toBlock;
100139
}
140+
}
101141

102-
public async run(oneOff = false) {
103-
while (true) {
142+
143+
export type OpChildChainConfig = Chain & {
144+
contracts: {
145+
portal: { [x: number]: ChainContract }
146+
disputeGameFactory: { [x: number]: ChainContract }
147+
}
148+
}
149+
150+
export class OpChildToParentMessageRedeemer extends ChildToParentMessageRedeemer {
151+
public readonly childChainViemProvider
152+
public readonly parentChainViemSigner
153+
154+
constructor(
155+
childChainRpc: string,
156+
parentChainRpc: string,
157+
parentChainPrivateKey: string,
158+
childToParentRewardRouterAddr: string,
159+
blockLag: number,
160+
startBlock: number = 0,
161+
public readonly childChainViem: OpChildChainConfig,
162+
public readonly parentChainViem: Chain,
163+
retryDelay = 1000 * 60 * 10,
164+
) {
165+
super(
166+
childChainRpc,
167+
parentChainRpc,
168+
parentChainPrivateKey,
169+
childToParentRewardRouterAddr,
170+
blockLag,
171+
startBlock,
172+
retryDelay
173+
)
174+
175+
this.childChainViemProvider = createPublicClient({
176+
chain: childChainViem,
177+
transport: http(childChainRpc),
178+
}).extend(publicActionsL2())
179+
180+
this.parentChainViemSigner = createWalletClient({
181+
chain: parentChainViem,
182+
account: privateKeyToAccount(
183+
parentChainPrivateKey as `0x${string}`
184+
),
185+
transport: http(parentChainRpc),
186+
})
187+
.extend(publicActions)
188+
.extend(walletActionsL1())
189+
.extend(publicActionsL1())
190+
}
191+
192+
protected async _handleLogs(logs: Log[], oneOff: boolean): Promise<void> {
193+
if (!oneOff) throw new Error('OpChildToParentMessageRedeemer only supports one-off mode')
194+
for (const log of logs) {
195+
const receipt = await this.childChainViemProvider.getTransactionReceipt({
196+
hash: log.transactionHash as Hex,
197+
})
198+
199+
// 'waiting-to-prove'
200+
// 'ready-to-prove'
201+
// 'waiting-to-finalize'
202+
// 'ready-to-finalize'
203+
// 'finalized'
204+
let status: GetWithdrawalStatusReturnType;
104205
try {
105-
await this.redeemChildToParentMessages(oneOff);
106-
} catch (err) {
107-
console.log("err", err);
206+
status = await this.parentChainViemSigner.getWithdrawalStatus({
207+
receipt,
208+
targetChain: this.childChainViemProvider.chain,
209+
})
210+
} catch (e: any) {
211+
// workaround
212+
if (e.metaMessages[0] === 'Error: Unproven()') {
213+
status = 'ready-to-prove'
214+
}
215+
else {
216+
throw e;
217+
}
108218
}
109-
if (oneOff) {
110-
break;
111-
} else {
112-
await wait(1000 * 60 * 60);
219+
220+
console.log(`${log.transactionHash} ${status}`)
221+
222+
if (status === 'ready-to-prove') {
223+
// 1. Get withdrawal information
224+
const [withdrawal] = getWithdrawals(receipt)
225+
const output = await this.parentChainViemSigner.getL2Output({
226+
l2BlockNumber: receipt.blockNumber,
227+
targetChain: this.childChainViem,
228+
})
229+
// 2. Build parameters to prove the withdrawal on the L2.
230+
const args = await this.childChainViemProvider.buildProveWithdrawal({
231+
output,
232+
withdrawal,
233+
})
234+
// 3. Prove the withdrawal on the L1.
235+
const hash = await this.parentChainViemSigner.proveWithdrawal(args)
236+
// 4. Wait until the prove withdrawal is processed.
237+
await this.parentChainViemSigner.waitForTransactionReceipt({
238+
hash,
239+
})
240+
241+
console.log(`${log.transactionHash} proved:`, hash)
242+
} else if (status === 'ready-to-finalize') {
243+
const [withdrawal] = getWithdrawals(receipt)
244+
245+
// 1. Wait until the withdrawal is ready to finalize. (done)
246+
247+
// 2. Finalize the withdrawal.
248+
const hash = await this.parentChainViemSigner.finalizeWithdrawal({
249+
targetChain: this.childChainViemProvider.chain,
250+
withdrawal,
251+
})
252+
253+
// 3. Wait until the withdrawal is finalized.
254+
await this.parentChainViemSigner.waitForTransactionReceipt({
255+
hash,
256+
})
257+
258+
console.log(`${log.transactionHash} finalized:`, hash)
113259
}
114260
}
115261
}
116262
}
263+

src-ts/cli/childToParentRedeemer.ts

+45-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import dotenv from "dotenv";
2-
import yargs from "yargs";
3-
import ChildToParentMessageRedeemer from "../FeeRouter/ChildToParentMessageRedeemer";
4-
import { JsonRpcProvider } from "@ethersproject/providers";
2+
import yargs, { option } from "yargs";
3+
import {
4+
ArbChildToParentMessageRedeemer,
5+
OpChildChainConfig,
6+
OpChildToParentMessageRedeemer,
7+
ChildToParentMessageRedeemer
8+
} from '../FeeRouter/ChildToParentMessageRedeemer';
59
import { Wallet } from "ethers";
10+
import { JsonRpcProvider } from "@ethersproject/providers";
11+
import chains from 'viem/chains';
612

713
dotenv.config();
814

@@ -24,23 +30,45 @@ const options = yargs(process.argv.slice(2))
2430
description:
2531
"Runs continuously if false, runs once and terminates if true",
2632
},
33+
opStack: { type: 'boolean', demandOption: false, default: false },
2734
})
2835
.parseSync();
2936

3037
(async () => {
31-
const parentChildSigner = new Wallet(
32-
PARENT_CHAIN_PK,
33-
new JsonRpcProvider(options.parentRPCUrl)
34-
);
35-
console.log(`Signing with ${parentChildSigner.address} on parent chain
36-
${(await parentChildSigner.provider.getNetwork()).chainId}'`);
37-
38-
const redeemer = new ChildToParentMessageRedeemer(
39-
new JsonRpcProvider(options.childRPCUrl),
40-
parentChildSigner,
41-
options.childToParentRewardRouterAddr,
42-
options.blockLag,
43-
options.childChainStartBlock
44-
);
38+
const parentChildSigner = new Wallet(PARENT_CHAIN_PK, new JsonRpcProvider(options.parentRPCUrl));
39+
const childChainProvider = new JsonRpcProvider(options.childRPCUrl);
40+
const parentChainId = (await parentChildSigner.provider.getNetwork()).chainId;
41+
const childChainId = (await childChainProvider.getNetwork()).chainId;
42+
console.log(`Signing with ${parentChildSigner.address} on parent chain ${parentChainId}'`);
43+
44+
let redeemer: ChildToParentMessageRedeemer;
45+
if (options.opStack) {
46+
const childChain = Object.values(chains).find(c => c.id === childChainId)
47+
const parentChain = Object.values(chains).find(c => c.id === parentChainId)
48+
49+
if (!childChain || !parentChain) {
50+
throw new Error('Unsupported chain')
51+
}
52+
53+
redeemer = new OpChildToParentMessageRedeemer(
54+
options.childRPCUrl,
55+
options.parentRPCUrl,
56+
PARENT_CHAIN_PK,
57+
options.childToParentRewardRouterAddr,
58+
options.blockLag,
59+
options.childChainStartBlock,
60+
childChain as OpChildChainConfig,
61+
parentChain
62+
)
63+
} else {
64+
redeemer = new ArbChildToParentMessageRedeemer(
65+
options.childRPCUrl,
66+
options.parentRPCUrl,
67+
PARENT_CHAIN_PK,
68+
options.childToParentRewardRouterAddr,
69+
options.blockLag,
70+
options.childChainStartBlock
71+
);
72+
}
4573
await redeemer.run(options.oneOff);
4674
})();

0 commit comments

Comments
 (0)