Skip to content

Commit

Permalink
feat: 5069 testnet onboarding lh agents works (#5074)
Browse files Browse the repository at this point in the history
* feat: hh task for claiming linea message

* feat: gas cost for linea propagate

* test: LH tests for linea
  • Loading branch information
liu-zhipeng authored Oct 28, 2023
1 parent 92dff94 commit c00fc1e
Show file tree
Hide file tree
Showing 8 changed files with 767 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/agents/lighthouse/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@connext/nxtp-adapters-subgraph": "workspace:*",
"@connext/nxtp-txservice": "workspace:*",
"@connext/nxtp-utils": "workspace:*",
"@consensys/linea-sdk": "0.1.6",
"@eth-optimism/sdk": "2.1.0",
"@sinclair/typebox": "0.25.21",
"@types/aws-lambda": "8.10.110",
Expand Down
3 changes: 3 additions & 0 deletions packages/agents/lighthouse/src/mockable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { sendWithRelayerWithBackup as _sendWithRelayerWithBackup } from "@connex
import { EventFetcher as _EventFetcher, L2TransactionReceipt as _L2TransactionReceipt } from "@arbitrum/sdk";
import { L1ToL2MessageGasEstimator } from "@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator";
import { getBaseFee as _getBaseFee } from "@arbitrum/sdk/dist/lib/utils/lib";
import { LineaSDK as _LineaSDK } from "@consensys/linea-sdk";
import {
RollupUserLogic__factory as _RollupUserLogic__factory,
Outbox__factory as _Outbox__factory,
Expand Down Expand Up @@ -43,6 +44,8 @@ export const JsonRpcProvider = providers.JsonRpcProvider;

export const ZkSyncWeb3Provider = zk.Provider;

export const LineaSDK = _LineaSDK;

export const encodePropagate = (abi: any[], args: any[]): string => {
const encodedData = new utils.Interface(abi as string[]).encodeFunctionData("propagate", args);
return encodedData;
Expand Down
40 changes: 35 additions & 5 deletions packages/agents/lighthouse/src/tasks/propagate/helpers/linea.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
import { createLoggingContext, RequestContext } from "@connext/nxtp-utils";
import { utils } from "ethers";
import { BigNumber } from "ethers";

import { LineaSDK, getBestProvider } from "../../../mockable";
import { getContext } from "../propagate";
import { ExtraPropagateParam } from "../operations/propagate";
import { NoProviderForDomain } from "../errors";

// example at https://github.com/OffchainLabs/arbitrum-tutorials/blob/master/packages/greeter/scripts/exec.js
// https://docs.linea.build/use-mainnet/bridges-of-linea#manual-vs-automatic-claiming
export const getPropagateParams = async (
l2domain: string,
l2ChainId: number,
l1ChainId: number,
_requestContext: RequestContext,
): Promise<ExtraPropagateParam> => {
const { logger } = getContext();
const { config, logger } = getContext();
const { methodContext, requestContext } = createLoggingContext(getPropagateParams.name, _requestContext);
logger.info("Getting propagate params for Linea", requestContext, methodContext, {
l2domain,
l1ChainId,
l2ChainId,
});

// the additional optional "postman" fee = 0 currently
const _fee = utils.parseEther("0").toString();
const l2RpcUrl = await getBestProvider(config.chains[l2domain]?.providers ?? []);
if (!l2RpcUrl) {
throw new NoProviderForDomain(l2domain, requestContext, methodContext);
}
const l1RpcUrl = await getBestProvider(config.chains[config.hubDomain]?.providers ?? []);
if (!l1RpcUrl) {
throw new NoProviderForDomain(config.hubDomain, requestContext, methodContext);
}

// Postman Fee = target layer gas price * (gas estimated + gas limit surplus) * margin
// where target layer gas price is eth_gasPrice on the target layer, gas estimated = 100,000, gas limit surplus = 6000, and margin = 2.
const sdk = new LineaSDK({
l1RpcUrl: l1RpcUrl, // L1 rpc url
l2RpcUrl: l2RpcUrl, // L2 rpc url
network: config.network === "mainnet" ? "linea-mainnet" : "linea-goerli", // network you want to interact with (either linea-mainnet or linea-goerli)
mode: "read-only", // contract wrapper class mode (read-only or read-write), read-only: only read contracts state, read-write: read contracts state and claim messages
});
const gasPrice = (await sdk.getL2Contract().get1559Fees()).maxFeePerGas;

// On linea-goerli claimMessage gasLimit was 83717
// https://goerli.lineascan.build/tx/0x4c477dfcbc22cd99b461cfe714a6ad60796331d3c13e55a74a6de51c3cd9aab6
const gasLimit = BigNumber.from("120000");

const _fee = gasPrice.mul(gasLimit).toString();

logger.info("Got propagate params for Linea", requestContext, methodContext, {
gasPrice: gasPrice.toString(),
gasLimit: gasLimit.toString(),
fee: _fee.toString(),
});

return { _connector: "", _fee, _encodedData: "0x" };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createRequestContext, expect, mkAddress } from "@connext/nxtp-utils";
import { SinonStub, stub, createStubInstance, SinonStubbedInstance } from "sinon";
import { NoProviderForDomain } from "../../../../src/tasks/propagate/errors";

import { getPropagateParams } from "../../../../src/tasks/propagate/helpers/linea";
import * as Mockable from "../../../../src/mockable";
import { getBestProviderMock, propagateCtxMock } from "../../../globalTestHook";
import { mock } from "../../../mock";
import { BigNumber } from "ethers";

const requestContext = createRequestContext("test");

let getL2Contract: SinonStub;

class MockLineaSDK {
public getL2Contract = getL2Contract;
}

describe("Helpers: Linea", () => {
describe("#getPropagateParams", () => {
beforeEach(() => {
stub(Mockable, "LineaSDK").value(MockLineaSDK);
getL2Contract = stub().returns({
get1559Fees: stub().resolves({
maxFeePerGas: BigNumber.from(10),
maxPriorityFeePerGas: BigNumber.from(1),
}),
} as any);
});

it("should throw an error if no provider for spoke domain", async () => {
delete propagateCtxMock.config.chains[mock.domain.B];
getBestProviderMock.resolves(undefined);
await expect(
getPropagateParams(mock.domain.B, +mock.chain.B, +mock.chain.A, requestContext),
).to.eventually.be.rejectedWith(NoProviderForDomain);
});

it("should throw an error if no provider for hub domain", async () => {
delete propagateCtxMock.config.chains[mock.domain.A];
getBestProviderMock.resolves(undefined);
await expect(
getPropagateParams(mock.domain.B, +mock.chain.B, +mock.chain.A, requestContext),
).to.eventually.be.rejectedWith(NoProviderForDomain);
});

it("should return necessary data successfully", async () => {
const data = await getPropagateParams(mock.domain.B, +mock.chain.B, +mock.chain.A, requestContext);
expect(data).to.deep.eq({
_connector: "",
_fee: BigNumber.from(10).mul(BigNumber.from(120000)).toString(),
_encodedData: "0x",
});
});
});
});
1 change: 1 addition & 0 deletions packages/deployments/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import "./tasks/bumpTransfer";
import "./tasks/rootmanager/enrollAdminConnector";
import "./tasks/connector/addSpokeRootToAggregate";
import "./tasks/connector/wormholeDeliver";
import "./tasks/connector/claimLinea";
import { hardhatNetworks } from "./src/config";

tdly.setup({
Expand Down
1 change: 1 addition & 0 deletions packages/deployments/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@arbitrum/sdk": "3.1.3",
"@certusone/wormhole-sdk": "0.9.21",
"@connext/nxtp-utils": "workspace:*",
"@consensys/linea-sdk": "^0.1.6",
"@gelatonetwork/relay-context": "2.1.0",
"@matterlabs/hardhat-zksync-deploy": "0.6.3",
"@matterlabs/hardhat-zksync-solc": "0.3.17",
Expand Down
79 changes: 79 additions & 0 deletions packages/deployments/contracts/tasks/connector/claimLinea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Wallet } from "ethers";
import { task } from "hardhat/config";
import { LineaSDK } from "@consensys/linea-sdk";

import { Env, getMessagingProtocolConfig, mustGetEnv, ProtocolNetwork } from "../../src/utils";

import { getProviderUrlFromHardhatConfig } from "../../src";

type TaskArgs = {
hash: string;
networkType?: ProtocolNetwork;
env?: Env;
};

export default task("claim-linea", "Claim messages on both of L1 and L2")
.addParam("hash", "The transaction hash of the message sent. network should be origin side.")
.addOptionalParam("env", "Environment of contracts")
.addOptionalParam("networkType", "Type of network of contracts")
.setAction(async ({ hash, networkType: _networkType, env: _env }: TaskArgs, hre) => {
const deployer = Wallet.fromMnemonic(process.env.MNEMONIC!);

const env = mustGetEnv(_env);
const networkType = _networkType ?? ProtocolNetwork.TESTNET;
console.log("networkType: ", networkType);
console.log("env:", env);
console.log("transaction hash", hash);
console.log("deployer", deployer.address);

// get config
const protocolConfig = getMessagingProtocolConfig(networkType);
const hub = protocolConfig.hub;
const chainId = hre.network.config.chainId!;

// Right now this only works on arbitrum, error if that is not the correct network
if (chainId != hub && protocolConfig.configs[chainId].prefix != "Linea") {
throw new Error(`Only linea / linea goerli supported`);
}

const spoke = hub == 1 ? 59144 : 59140;
const sdk = new LineaSDK({
l1RpcUrl: getProviderUrlFromHardhatConfig(hub), // L1 rpc url
l2RpcUrl: getProviderUrlFromHardhatConfig(spoke), // L2 rpc url
l1SignerPrivateKey: deployer.privateKey ?? "", // L1 account private key (optional if you use mode = read-only)
l2SignerPrivateKey: deployer.privateKey ?? "", // L2 account private key (optional if you use mode = read-only)
network: hub == 1 ? "linea-mainnet" : "linea-goerli", // network you want to interact with (either linea-mainnet or linea-goerli)
mode: "read-write", // contract wrapper class mode (read-only or read-write), read-only: only read contracts state, read-write: read contracts state and claim messages
});

// get L1/L2 contract
const originContract = chainId == hub ? sdk.getL1Contract() : sdk.getL2Contract();

// get Message Status
const messages = await originContract.getMessagesByTransactionHash(hash);

if (!messages?.length) {
throw new Error(`${hash} has no message sent`);
}
console.log("message: ", messages[0]);

const destContract = chainId == hub ? sdk.getL2Contract() : sdk.getL1Contract();

// returns on-chain message status by message hash
const messageStatus = await destContract.getMessageStatus(messages[0].messageHash);
console.log("message status: ", messageStatus);

if (messageStatus === "CLAIMED") {
console.log("message already claimed!! skipping...");
} else if (messageStatus === "CLAIMABLE") {
console.log("Claimable message status. ");
let claimMessage = await destContract.claim({
// claims message by message
...messages[0],
feeRecipient: deployer.address, // address that will receive fees. by default it is the message sender
});
console.log(claimMessage);
} else {
console.log("unknown message status. skipping...");
}
});
Loading

0 comments on commit c00fc1e

Please sign in to comment.