Skip to content

Commit

Permalink
Merge pull request #17 from OffchainLabs/fund-router-scripts
Browse files Browse the repository at this point in the history
add fee router scripts / cli commands
  • Loading branch information
DZGoldman authored Dec 14, 2023
2 parents 19f7fe6 + dc3a99a commit 72a4653
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CHILD_CHAIN_PK=xyx
PARENT_CHAIN_PK=xyz
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"repository": "[email protected]:OffchainLabs/fund-distribution-contracts.git",
"author": "dzgoldman",
"license": "MIT",
"scripts":{
"gen-recipients": "make install && hardhat run src-ts/getRecipientData.ts"
"scripts": {
"gen-recipients": "make install && hardhat run src-ts/getRecipientData.ts"
},
"devDependencies": {
"@ethersproject/providers": "^5.7.2",
Expand All @@ -17,7 +17,7 @@
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.1",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat":"^6.1.3",
"@typechain/hardhat": "^6.1.3",
"@types/chai": "^4.3.10",
"@types/mocha": "^10.0.4",
"chai": "^4.3.10",
Expand All @@ -30,6 +30,10 @@
"typescript": "^5.2.2"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.0"
"@arbitrum/sdk": "^3.1.13",
"@openzeppelin/contracts": "^5.0.0",
"@types/yargs": "^17.0.32",
"dotenv": "^16.3.1",
"yargs": "^17.7.2"
}
}
120 changes: 120 additions & 0 deletions src-ts/FeeRouter/ChildToParentMessageRedeemer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import { Wallet } from "ethers";
import {
ChildToParentRewardRouter__factory,
ChildToParentRewardRouter,
} from "../../typechain-types";
import { L2ToL1TxEvent } from "@arbitrum/sdk/dist/lib/abi/ArbSys";
import { EventArgs } from "@arbitrum/sdk/dist/lib/dataEntities/event";

import {
L2TransactionReceipt,
L2ToL1Message,
L2ToL1MessageStatus,
} from "@arbitrum/sdk";
const wait = async (ms: number) => new Promise((res) => setTimeout(res, ms));

export default class ChildToParentMessageRedeemer {
public startBlock: number;
public childToParentRewardRouter: ChildToParentRewardRouter;
constructor(
public readonly childChainProvider: JsonRpcProvider,
public readonly parentChainSigner: Wallet,
public readonly childToParentRewardRouterAddr: string,
public readonly blockLag: number,
initialStartBlock: number
) {
this.startBlock = initialStartBlock;
this.childToParentRewardRouter = ChildToParentRewardRouter__factory.connect(
childToParentRewardRouterAddr,
childChainProvider
);
}

public async redeemChildToParentMessages(oneOff = false) {
const toBlock =
(await this.childChainProvider.getBlockNumber()) - this.blockLag;
const logs = await this.childChainProvider.getLogs({
fromBlock: this.startBlock,
toBlock: toBlock,
...this.childToParentRewardRouter.filters.FundsRouted(),
});
if (logs.length) {
console.log(
`Found ${logs.length} route events between blocks ${this.startBlock} and ${toBlock}`
);
}

for (let log of logs) {
const arbTransactionRec = new L2TransactionReceipt(
await this.childChainProvider.getTransactionReceipt(log.transactionHash)
);
const l2ToL1Events = (
(await arbTransactionRec.getL2ToL1Events()) as EventArgs<L2ToL1TxEvent>[]
)
// Filter out any other L2 to L1 messages initiated in this transaction
.filter(
(l2ToL1Event) =>
l2ToL1Event.caller.toLowerCase() ===
this.childToParentRewardRouter.address.toLowerCase()
);
// sanity check
if (l2ToL1Events.length === 0) {
throw new Error("Impossible result: L2 to L1 msg not found");
}

for (let l2ToL1Event of l2ToL1Events) {
const l2ToL1Message = L2ToL1Message.fromEvent(
this.parentChainSigner,
l2ToL1Event
);
if (!oneOff) {
console.log(`Waiting for ${l2ToL1Event.hash} to be ready:`);
await l2ToL1Message.waitUntilReadyToExecute(
this.childChainProvider,
1000 * 60 * 30
);
}

const status = await l2ToL1Message.status(this.childChainProvider);
switch (status) {
case L2ToL1MessageStatus.CONFIRMED: {
console.log(l2ToL1Event.hash, "confirmed; executing:");
const rec = await (
await l2ToL1Message.execute(this.childChainProvider)
).wait(2);
console.log(`${l2ToL1Event.hash} executed:`, rec.transactionHash);
break;
}
case L2ToL1MessageStatus.EXECUTED: {
console.log(`${l2ToL1Event.hash} already executed`);
break;
}
case L2ToL1MessageStatus.UNCONFIRMED: {
console.log(`${l2ToL1Event.hash} not yet confirmed`);
break;
}
default: {
throw new Error(`Unhandled L2ToL1MessageStatus case: ${status}`);
}
}
}
}
this.startBlock = toBlock;
}

public async run(oneOff = false) {
while (true) {
try {
await this.redeemChildToParentMessages(oneOff);
} catch (err) {
console.log("err", err);
}
if (oneOff) {
break;
} else {
await wait(1000 * 60 * 60);
}
}
}
}
88 changes: 88 additions & 0 deletions src-ts/FeeRouter/ParentToChildRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Wallet, BigNumber } from "ethers";
import {
ParentToChildRewardRouter__factory,
ParentToChildRewardRouter,
} from "../../typechain-types";
import { Inbox__factory } from "@arbitrum/sdk/dist/lib/abi/factories/Inbox__factory";
import {
L1TransactionReceipt,
L1ToL2MessageStatus,
} from "@arbitrum/sdk";

export const checkAndRouteFunds = async (
parentChainSigner: Wallet,
childChainSigner: Wallet,
parentToChildRewardRouterAddr: string,
minBalance: BigNumber
) => {
if (
(await parentChainSigner.provider.getBalance(parentToChildRewardRouterAddr)).lt(
minBalance
)
) {
return;
}

const parentToChildRewardRouter: ParentToChildRewardRouter =
ParentToChildRewardRouter__factory.connect(
parentToChildRewardRouterAddr,
parentChainSigner
);

// check if it's time to trigger
if (!(await parentToChildRewardRouter.canDistribute())) {
return;
}
console.log("Calling parent to child router:");

const inbox = Inbox__factory.connect(
await parentToChildRewardRouter.inbox(),
parentChainSigner.provider
);
// retryable has 0 calldata (simple transfer). 0 in second paramt uses current L1 basefee
const _submissionFee = await inbox.calculateRetryableSubmissionFee(0, 0);

// add a 10% increase for insurance
const submissionFee = _submissionFee.add(_submissionFee.mul(.1))

const gasPrice = await childChainSigner.getGasPrice();

// we use the minimum gas limit set in the contract (we presume it's more than enough)
const gasLimit = await parentToChildRewardRouter.minGasLimit();

const value = submissionFee.add(gasPrice.mul(gasLimit));

const rec = await (
await parentToChildRewardRouter.routeFunds(
submissionFee,
gasLimit,
gasPrice,
{
value,
}
)
).wait(1);
console.log('Retryable created', rec.transactionHash);

const l1TxRec = new L1TransactionReceipt(rec);
const l1ToL2Msgs = await l1TxRec.getL1ToL2Messages(childChainSigner);
if (l1ToL2Msgs.length != 1) throw new Error("Unexpected messages length");

const l1ToL2Msg = l1ToL2Msgs[0]
console.log('Waiting for result:');
const result = await l1ToL2Msg.waitForStatus()
if(result.status == L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2 ){
console.log('Retryable failed; retrying:');

const rec = await (await l1ToL2Msg.redeem()).wait()
console.log('Successfully redeemed:', rec.transactionHash);

} else if (result.status == L1ToL2MessageStatus.REDEEMED){
console.log('Successfully redeemed');

} else {
throw new Error("Error: unexpected retryable status")
}


};
53 changes: 53 additions & 0 deletions src-ts/cli/childToParentRedeemer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import dotenv from "dotenv";
import yargs from "yargs";
import ChildToParentMessageRedeemer from "../FeeRouter/ChildToParentMessageRedeemer";
import { JsonRpcProvider } from "@ethersproject/providers";
import { Wallet } from "ethers";

dotenv.config();

const PARENT_CHAIN_PK = process.env.PARENT_CHAIN_PK;

if (!PARENT_CHAIN_PK) throw new Error("Need PARENT_CHAIN_PK");

const options = yargs(process.argv.slice(2))
.options({
parentRPCUrl: { type: "string", demandOption: true },
childRPCUrl: { type: "string", demandOption: true },
childToParentRewardRouterAddr: { type: "string", demandOption: true },
blockLag: { type: "number", demandOption: false, default: 5 },
childChainStartBlock: { type: "number", demandOption: false, default: 0 },
oneOff: {
type: "boolean",
demandOption: false,
default: false,
description:
"Runs continuously if false, runs once and terminates if true",
},
})
.parseSync() as {
parentRPCUrl: string;
childRPCUrl: string;
childToParentRewardRouterAddr: string;
blockLag: number;
childChainStartBlock: number;
oneOff: boolean;
};

(async () => {
const parentChildSigner = new Wallet(
PARENT_CHAIN_PK,
new JsonRpcProvider(options.parentRPCUrl)
);
console.log(`Signing with ${parentChildSigner.address} on parent chain
${(await parentChildSigner.provider.getNetwork()).chainId}'`);

const redeemer = new ChildToParentMessageRedeemer(
new JsonRpcProvider(options.childRPCUrl),
parentChildSigner,
options.childToParentRewardRouterAddr,
options.blockLag,
options.childChainStartBlock
);
await redeemer.run(options.oneOff);
})();
51 changes: 51 additions & 0 deletions src-ts/cli/routeParentToChild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import dotenv from "dotenv";
import yargs from "yargs";
import { JsonRpcProvider } from "@ethersproject/providers";
import { Wallet, utils } from "ethers";
import { checkAndRouteFunds } from "../FeeRouter/ParentToChildRouter";

dotenv.config();

const PARENT_CHAIN_PK = process.env.PARENT_CHAIN_PK;
if (!PARENT_CHAIN_PK) throw new Error("Need PARENT_CHAIN_PK");

const CHILD_CHAIN_PK = process.env.CHILD_CHAIN_PK;
if (!CHILD_CHAIN_PK) throw new Error("Need CHILD_CHAIN_PK");

const options = yargs(process.argv.slice(2))
.options({
parentRPCUrl: { type: "string", demandOption: true },
childRPCUrl: { type: "string", demandOption: true },
parentToChildRewardRouterAddr: { type: "string", demandOption: true },
minBalanceEther: { type: "number", demandOption: false, default: 0 },
})
.parseSync() as {
parentRPCUrl: string;
childRPCUrl: string;
parentToChildRewardRouterAddr: string;
minBalanceEther: number;
};

(async () => {
const parentChildSigner = new Wallet(
PARENT_CHAIN_PK,
new JsonRpcProvider(options.parentRPCUrl)
);
console.log(`Signing with ${parentChildSigner.address} on parent chain
${(await parentChildSigner.provider.getNetwork()).chainId}'`);

const childChainSigner = new Wallet(
PARENT_CHAIN_PK,
new JsonRpcProvider(options.childRPCUrl)
);

console.log(`Signing with ${childChainSigner.address} on child chain
${(await childChainSigner.provider.getNetwork()).chainId}'`);
await checkAndRouteFunds(
parentChildSigner,
childChainSigner,
options.parentToChildRewardRouterAddr,
utils.parseEther(String(options.minBalanceEther))
);
console.log("done");
})();
Loading

0 comments on commit 72a4653

Please sign in to comment.