Skip to content

Commit

Permalink
use viem in utils and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith committed Jul 2, 2024
1 parent 4e68981 commit 08ca718
Show file tree
Hide file tree
Showing 10 changed files with 1,969 additions and 69 deletions.
12 changes: 12 additions & 0 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"preset": "ts-jest",
"testEnvironment": "node",
"coverageThreshold": {
"global": {
"branches": 60,
"functions": 60,
"lines": 60,
"statements": 60
}
}
}
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"files": [
"dist/**/*"
],
"type": "module",
"private": true,
"scripts": {
"build": "rm -rf ./dist && tsc",
"example": "tsx examples/send-tx.ts",
"lint": "eslint . --ignore-pattern dist/",
"test": "jest",
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}'",
"all": "yarn fmt && yarn lint && yarn build && yarn ex"
},
Expand All @@ -21,16 +23,20 @@
"ethers": "^6.13.1",
"ethers-multisend": "^3.1.0",
"near-ca": "^0.1.0",
"viem": "^2.16.5",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.14.9",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"dotenv": "^16.4.5",
"eslint": "^9.6.0",
"jest": "^29.7.0",
"prettier": "^3.3.2",
"ts-jest": "^29.1.5",
"tsx": "^4.16.0",
"typescript": "^5.5.2"
}
Expand Down
7 changes: 4 additions & 3 deletions src/lib/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
UserOperationReceipt,
} from "../types.js";
import { PLACEHOLDER_SIG } from "../util.js";
import { toHex } from "viem";

export class Erc4337Bundler {
provider: ethers.JsonRpcProvider;
Expand Down Expand Up @@ -66,8 +67,8 @@ export class Erc4337Bundler {
// TODO(bh2smith) Should probably get reasonable estimates here:
const defaultPaymasterData = (safeNotDeployed: boolean): PaymasterData => {
return {
verificationGasLimit: ethers.toBeHex(safeNotDeployed ? 500000 : 100000),
callGasLimit: ethers.toBeHex(100000),
preVerificationGas: ethers.toBeHex(100000),
verificationGasLimit: toHex(safeNotDeployed ? 500000 : 100000),
callGasLimit: toHex(100000),
preVerificationGas: toHex(100000),
};
};
8 changes: 4 additions & 4 deletions src/lib/near.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ethers } from "ethers";
import { NearEthAdapter } from "near-ca";
import { Hash, Hex } from "viem";

export async function getNearSignature(
adapter: NearEthAdapter,
hash: ethers.BytesLike
): Promise<string> {
const viemHash = typeof hash === "string" ? (hash as `0x${string}`) : hash;
hash: Hash
): Promise<Hex> {
// MPC Contract produces two possible signatures.
const signatures = await adapter.sign(viemHash);
const signatures = await adapter.sign(hash);
for (const sig of signatures) {
if (
ethers.recoverAddress(hash, sig).toLocaleLowerCase() ===
Expand Down
2 changes: 1 addition & 1 deletion src/lib/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class ContractSuite {
async getOpHash(
unsignedUserOp: UserOperation,
paymasterData: PaymasterData
): Promise<string> {
): Promise<`0x${string}`> {
return this.m4337.getOperationHash({
...unsignedUserOp,
initCode: unsignedUserOp.factory
Expand Down
5 changes: 3 additions & 2 deletions src/tx-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getNearSignature } from "./lib/near.js";
import { UserOperation, UserOperationReceipt, UserOptions } from "./types.js";
import { MetaTransaction, encodeMulti } from "ethers-multisend";
import { ContractSuite } from "./lib/safe.js";
import { Hex } from "viem";

export class TransactionManager {
readonly provider: ethers.JsonRpcProvider;
Expand Down Expand Up @@ -90,7 +91,7 @@ export class TransactionManager {
async buildTransaction(args: {
transactions: MetaTransaction[];
options: UserOptions;
}): Promise<{ safeOpHash: string; unsignedUserOp: UserOperation }> {
}): Promise<{ safeOpHash: `0x${string}`; unsignedUserOp: UserOperation }> {
const { transactions, options } = args;
const gasFees = await this.provider.getFeeData();
// Build Singular MetaTransaction for Multisend from transaction list.
Expand Down Expand Up @@ -123,7 +124,7 @@ export class TransactionManager {
};
}

async signTransaction(safeOpHash: string): Promise<string> {
async signTransaction(safeOpHash: Hex): Promise<string> {
const signature = await getNearSignature(this.nearAdapter, safeOpHash);
return packSignature(signature);
}
Expand Down
16 changes: 8 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ethers } from "ethers";
import { Address } from "viem";

export interface UnsignedUserOperation {
sender: ethers.AddressLike;
Expand All @@ -21,13 +22,13 @@ export interface UserOperation extends UnsignedUserOperation {
}

export interface PaymasterData {
paymaster?: string;
paymasterData?: string;
paymasterVerificationGasLimit?: string;
paymasterPostOpGasLimit?: string;
verificationGasLimit: string;
callGasLimit: string;
preVerificationGas: string;
paymaster?: Address;
paymasterData?: Hex;
paymasterVerificationGasLimit?: Hex;
paymasterPostOpGasLimit?: Hex;
verificationGasLimit: Hex;
callGasLimit: Hex;
preVerificationGas: Hex;
}

export interface UserOptions {
Expand All @@ -37,7 +38,6 @@ export interface UserOptions {
}

export type TStatus = "success" | "reverted";
export type Address = ethers.AddressLike;
export type Hex = `0x${string}`;
export type Hash = `0x${string}`;

Expand Down
39 changes: 18 additions & 21 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { ethers } from "ethers";
import { PaymasterData } from "./types.js";
import { MetaTransaction } from "ethers-multisend";
import { Hex, concatHex, encodePacked, toHex } from "viem";

export const PLACEHOLDER_SIG = ethers.solidityPacked(
["uint48", "uint48"],
[0, 0]
);
export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);

export const packGas = (
hi: ethers.BigNumberish,
lo: ethers.BigNumberish
): string => ethers.solidityPacked(["uint128", "uint128"], [hi, lo]);
type IntLike = Hex | bigint | string | number;

export const packGas = (hi: IntLike, lo: IntLike): string =>
encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);

export function packSignature(
signature: string,
signature: `0x${string}`,
validFrom: number = 0,
validTo: number = 0
): string {
return ethers.solidityPacked(
): Hex {
return encodePacked(
["uint48", "uint48", "bytes"],
[validFrom, validTo, signature]
);
}

export function packPaymasterData(data: PaymasterData): string {
return data.paymaster
? ethers.hexlify(
ethers.concat([
data.paymaster,
ethers.toBeHex(data.paymasterVerificationGasLimit || "0x", 16),
ethers.toBeHex(data.paymasterPostOpGasLimit || "0x", 16),
export function packPaymasterData(data: PaymasterData): Hex {
return (
data.paymaster
? concatHex([
data.paymaster!,
toHex(BigInt(data.paymasterVerificationGasLimit || 0n), { size: 16 }),
toHex(BigInt(data.paymasterPostOpGasLimit || 0n), { size: 16 }),
data.paymasterData || "0x",
])
)
: "0x";
: "0x"
) as Hex;
}

export function containsValue(transactions: MetaTransaction[]): boolean {
Expand Down
67 changes: 67 additions & 0 deletions tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ethers } from "ethers";
import {
PLACEHOLDER_SIG,
containsValue,
packGas,
packPaymasterData,
packSignature,
} from "../src/util";
import { PaymasterData } from "../src";

describe("Utility Functions (mostly byte packing)", () => {
it("PLACE_HOLDER_SIG", () => {
expect(PLACEHOLDER_SIG).toEqual(
ethers.solidityPacked(["uint48", "uint48"], [0, 0])
);
});
it("packGas", () => {
let [hi, lo] = [15n, 255n];
expect(packGas(hi, lo)).toEqual(
ethers.solidityPacked(["uint128", "uint128"], [hi, lo])
);
// Random input
expect(packGas(15, "255")).toEqual(
ethers.solidityPacked(["uint128", "uint128"], ["15", "0xff"])
);
});
it("packSignature", () => {
const x =
"0x491e245db3914b85807f3807f2125b9ed9722d0e9f3fa0fe325b31893fa5e693387178ae4a51f304556c1b2e9dd24f1120d073f93017af006ad801a639214ea61b";
expect(packSignature(x, 1, 2)).toEqual(
ethers.solidityPacked(["uint48", "uint48", "bytes"], [1, 2, x])
);
});

it("packPaymasterData", () => {
const data: PaymasterData = {
paymaster: "0x4685d9587a7F72Da32dc323bfFF17627aa632C61",
paymasterData:
"0x000000000000000000000000000000000000000000000000000000006682e19400000000000000000000000000000000000000000000000000000000000000007eacbfaa696a236960b8eac0a9725f96c941665b893aa80b2ae3a41814f10b813d07db0b07b89080e4fd436d1966bc2ff7002a686087a310348391db8e9d44881c",
preVerificationGas: "0xd87a",
verificationGasLimit: "0x114b5",
callGasLimit: "0x14a6a",
paymasterVerificationGasLimit: "0x4e17",
paymasterPostOpGasLimit: "0x1",
};

expect(packPaymasterData(data).toLowerCase()).toEqual(
ethers.hexlify(
ethers.concat([
data.paymaster!,
ethers.toBeHex(data.paymasterVerificationGasLimit || "0x", 16),
ethers.toBeHex(data.paymasterPostOpGasLimit || "0x", 16),
data.paymasterData || "0x",
])
)
);
});

it("containsValue", () => {
expect(containsValue([])).toBe(false);
const NO_VALUE_TX = { to: "0x", value: "0", data: "0x" };
const VALUE_TX = { to: "0x", value: "1", data: "0x" };
expect(containsValue([NO_VALUE_TX])).toBe(false);
expect(containsValue([VALUE_TX])).toBe(true);
expect(containsValue([VALUE_TX, NO_VALUE_TX])).toBe(true);
});
});
Loading

0 comments on commit 08ca718

Please sign in to comment.