Skip to content

Commit

Permalink
cleanup and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith committed Sep 17, 2024
1 parent c498dc0 commit eecd040
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 40 deletions.
40 changes: 25 additions & 15 deletions src/lib/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getSafe4337ModuleDeployment,
getSafeModuleSetupDeployment,
} from "@safe-global/safe-modules-deployments";
import { Network } from "near-ca";
import {
Address,
encodeFunctionData,
Expand All @@ -28,7 +27,12 @@ import {
UnsignedUserOperation,
UserOperation,
} from "../types";
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
import {
PLACEHOLDER_SIG,
getClient,
packGas,
packPaymasterData,
} from "../util";

interface DeploymentData {
abi: unknown[] | ParseAbi<readonly string[]>;
Expand All @@ -39,7 +43,8 @@ interface DeploymentData {
* All contracts used in account creation & execution
*/
export class ContractSuite {
client: PublicClient;
// Used only for stateless contract reads.
dummyClient: PublicClient;
singleton: DeploymentData;
proxyFactory: DeploymentData;
m4337: DeploymentData;
Expand All @@ -54,7 +59,7 @@ export class ContractSuite {
moduleSetup: DeploymentData,
entryPoint: DeploymentData
) {
this.client = client;
this.dummyClient = client;
this.singleton = singleton;
this.proxyFactory = proxyFactory;
this.m4337 = m4337;
Expand All @@ -64,7 +69,7 @@ export class ContractSuite {

static async init(): Promise<ContractSuite> {
// TODO - this is a cheeky hack.
const client = Network.fromChainId(11155111).client;
const client = getClient(11155111);
const safeDeployment = (fn: DeploymentFunction): Promise<DeploymentData> =>
getDeployment(fn, { version: "1.4.1" });
const m4337Deployment = async (
Expand Down Expand Up @@ -110,7 +115,8 @@ export class ContractSuite {
async addressForSetup(setup: Hex, saltNonce?: string): Promise<Address> {
// bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
const salt = keccak256(encodePacked(
const salt = keccak256(
encodePacked(
["bytes32", "uint256"],
[keccak256(setup), BigInt(saltNonce || "0")]
)
Expand All @@ -121,7 +127,7 @@ export class ContractSuite {
const initCode = encodePacked(
["bytes", "uint256"],
[
(await this.client.readContract({
(await this.dummyClient.readContract({
address: this.proxyFactory.address,
abi: this.proxyFactory.abi,
functionName: "proxyCreationCode",
Expand All @@ -136,7 +142,6 @@ export class ContractSuite {
});
}


getSetup(owners: string[]): Hex {
return encodeFunctionData({
abi: this.singleton.abi,
Expand Down Expand Up @@ -175,7 +180,7 @@ export class ContractSuite {
maxPriorityFeePerGas,
maxFeePerGas,
} = unsignedUserOp;
const opHash = await this.client.readContract({
const opHash = await this.dummyClient.readContract({
address: this.m4337.address,
abi: this.m4337.abi,
functionName: "getOperationHash",
Expand Down Expand Up @@ -213,19 +218,14 @@ export class ContractSuite {
}

async buildUserOp(
nonce: bigint,
txData: MetaTransaction,
safeAddress: Address,
feeData: GasPrice,
setup: string,
safeNotDeployed: boolean,
safeSaltNonce: string
): Promise<UnsignedUserOperation> {
const nonce = (await this.client.readContract({
abi: this.entryPoint.abi,
address: this.entryPoint.address,
functionName: "getNonce",
args: [safeAddress, 0],
})) as bigint;
return {
sender: safeAddress,
nonce: toHex(nonce),
Expand All @@ -244,6 +244,16 @@ export class ContractSuite {
...feeData,
};
}

async getNonce(address: Address, chainId: number): Promise<bigint> {
const nonce = (await getClient(chainId).readContract({
abi: this.entryPoint.abi,
address: this.entryPoint.address,
functionName: "getNonce",
args: [address, 0],
})) as bigint;
return nonce;
}
}

type DeploymentFunction = (filter?: {
Expand Down
26 changes: 15 additions & 11 deletions src/tx-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { Address, Hash, Hex, serializeSignature } from "viem";

import { Erc4337Bundler } from "./lib/bundler";
import { encodeMulti } from "./lib/multisend";
import { ContractSuite } from "./lib/viem-safe";
import { ContractSuite } from "./lib/safe";
import { MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
import { isContract, packSignature } from "./util";
import { getClient, isContract, packSignature } from "./util";

export class TransactionManager {
readonly nearAdapter: NearEthAdapter;
Expand Down Expand Up @@ -78,8 +78,7 @@ export class TransactionManager {
}

async getBalance(chainId: number): Promise<bigint> {
const provider = Network.fromChainId(chainId).client;
return await provider.getBalance({ address: this.address });
return await getClient(chainId).getBalance({ address: this.address });
}

bundlerForChainId(chainId: number): Erc4337Bundler {
Expand All @@ -96,28 +95,33 @@ export class TransactionManager {
usePaymaster: boolean;
}): Promise<UserOperation> {
const { transactions, usePaymaster, chainId } = args;
const bundler = this.bundlerForChainId(chainId);
const gasFees = (await bundler.getGasPrice()).fast;
// Build Singular MetaTransaction for Multisend from transaction list.
if (transactions.length === 0) {
throw new Error("Empty transaction set!");
}
const bundler = this.bundlerForChainId(chainId);
const [gasFees, nonce, safeDeployed] = await Promise.all([
bundler.getGasPrice(),
this.safePack.getNonce(this.address, chainId),
this.safeDeployed(chainId),
]);
// Build Singular MetaTransaction for Multisend from transaction list.
const tx =
transactions.length > 1 ? encodeMulti(transactions) : transactions[0]!;
const safeNotDeployed = !(await this.safeDeployed(chainId));

const rawUserOp = await this.safePack.buildUserOp(
nonce,
tx,
this.address,
gasFees,
gasFees.fast,
this.setup,
safeNotDeployed,
!safeDeployed,
this.safeSaltNonce
);

const paymasterData = await bundler.getPaymasterData(
rawUserOp,
usePaymaster,
safeNotDeployed
!safeDeployed
);

const unsignedUserOp = { ...rawUserOp, ...paymasterData };
Expand Down
16 changes: 13 additions & 3 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Network } from "near-ca";
import { Address, Hex, concatHex, encodePacked, toHex } from "viem";
import {
Address,
Hex,
concatHex,
encodePacked,
toHex,
PublicClient,
} from "viem";

import { PaymasterData, MetaTransaction } from "./types";

Expand Down Expand Up @@ -42,6 +49,9 @@ export async function isContract(
address: Address,
chainId: number
): Promise<boolean> {
const client = Network.fromChainId(chainId).client;
return (await client.getCode({ address })) !== undefined;
return (await getClient(chainId).getCode({ address })) !== undefined;
}

export function getClient(chainId: number): PublicClient {
return Network.fromChainId(chainId).client;
}
60 changes: 49 additions & 11 deletions tests/lib.safe.spec.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,68 @@
import { zeroAddress } from "viem";

import { ContractSuite as EthPack } from "./ethers-safe";
import { ContractSuite as ViemPack } from "../src/lib/safe";

describe("Safe Pack", () => {
let ethersPack: EthPack;
let viemPack: ViemPack;
beforeAll(async () => {
ethersPack = await EthPack.init();
viemPack = await ViemPack.init();
});

it("init", async () => {
const ethersPack = await EthPack.init();
const viemPack = await ViemPack.init();


expect(ethersPack.singleton.target).toEqual(viemPack.singleton.address);
expect(await ethersPack.singleton.getAddress()).toEqual(viemPack.singleton.address);

expect(await ethersPack.singleton.getAddress()).toEqual(
viemPack.singleton.address
);

expect(ethersPack.m4337.target).toEqual(viemPack.m4337.address);
expect(await ethersPack.m4337.getAddress()).toEqual(viemPack.m4337.address);

expect(ethersPack.moduleSetup.target).toEqual(viemPack.moduleSetup.address);
expect(await ethersPack.moduleSetup.getAddress()).toEqual(viemPack.moduleSetup.address);
expect(await ethersPack.moduleSetup.getAddress()).toEqual(
viemPack.moduleSetup.address
);

expect(ethersPack.moduleSetup.target).toEqual(viemPack.moduleSetup.address);
expect(await ethersPack.moduleSetup.getAddress()).toEqual(viemPack.moduleSetup.address);
expect(await ethersPack.moduleSetup.getAddress()).toEqual(
viemPack.moduleSetup.address
);

expect(ethersPack.entryPoint.target).toEqual(viemPack.entryPoint.address);
expect(await ethersPack.entryPoint.getAddress()).toEqual(viemPack.entryPoint.address);
expect(await ethersPack.entryPoint.getAddress()).toEqual(
viemPack.entryPoint.address
);

expect(ethersPack.proxyFactory.target).toEqual(
viemPack.proxyFactory.address
);
expect(await ethersPack.proxyFactory.getAddress()).toEqual(
viemPack.proxyFactory.address
);
});

it("addOwnerData", () => {
expect(ethersPack.addOwnerData(zeroAddress)).toEqual(
viemPack.addOwnerData(zeroAddress)
);
});
it("getSetup", () => {
expect(ethersPack.getSetup([zeroAddress])).toEqual(
viemPack.getSetup([zeroAddress])
);
});

expect(ethersPack.proxyFactory.target).toEqual(viemPack.proxyFactory.address);
expect(await ethersPack.proxyFactory.getAddress()).toEqual(viemPack.proxyFactory.address);
it("getSetup", async () => {
const setup = viemPack.getSetup([zeroAddress]);
const [eps0, vps0, eps1, vps1] = await Promise.all([
ethersPack.addressForSetup(setup),
viemPack.addressForSetup(setup),
ethersPack.addressForSetup(setup, "1"),
viemPack.addressForSetup(setup, "1"),
]);
expect(eps0).toEqual(vps0);
expect(eps1).toEqual(vps1);
});
});

0 comments on commit eecd040

Please sign in to comment.