Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-cats-sign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zerodev/weighted-validator": patch
---

Handle missing initial UserOperation signatures and replace the current signer's stub without dropping co-signer signatures.
92 changes: 92 additions & 0 deletions plugins/weighted-r1-k1/toWeightedValidatorPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { describe, expect, test } from "bun:test"
import { createClient, custom } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { mainnet } from "viem/chains"
import {
WeightedValidatorContractVersion,
createWeightedValidator
} from "./toWeightedValidatorPlugin.js"
import { decodeSignatures, encodeSignatures } from "./utils.js"

const owner = privateKeyToAccount(
"0x2827b876ee775816460ab6eb4481352a752101f950899831702ccead54000001"
)

const entryPointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"

const getValidator = async () => {
const signer = {
account: owner,
getPublicKey: () => owner.address,
getDummySignature: () => `0x${"00".repeat(65)}` as `0x${string}`,
type: "0x01" as never
}
const client = createClient({
chain: mainnet,
transport: custom({
request: async ({ method }) => {
if (method === "eth_chainId") return "0x1"
throw new Error(`Unexpected RPC method: ${method}`)
}
})
})

return createWeightedValidator(client, {
entryPoint: {
address: entryPointAddress,
version: "0.7"
},
kernelVersion: "0.3.1",
validatorContractVersion:
WeightedValidatorContractVersion.V0_0_2_PATCHED,
signer,
config: {
threshold: 1,
signers: [{ publicKey: owner.address, weight: 1 }]
}
})
}

const userOperation = {
sender: "0x0000000000000000000000000000000000000001",
nonce: 0n,
callData: "0x",
callGasLimit: 100_000n,
verificationGasLimit: 100_000n,
preVerificationGas: 50_000n,
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
signature: "0x"
} as const

describe("createWeightedValidator", () => {
test("creates a stub signature when the UserOperation signature is undefined", async () => {
const validator = await getValidator()

const signature = await validator.getStubSignature({
...userOperation,
signature: undefined
} as never)

expect(decodeSignatures(signature)).toHaveLength(1)
})

test("replaces its stub while preserving co-signer signatures", async () => {
const validator = await getValidator()
const cosignerSignature = `0x01${"11".repeat(65)}` as `0x${string}`
const stubSignature = await validator.getStubSignature({
...userOperation,
signature: encodeSignatures([cosignerSignature])
})

const signature = await validator.signUserOperation({
...userOperation,
signature: stubSignature
})

const signatures = decodeSignatures(signature)
expect(signatures).toHaveLength(2)
expect(signatures[0]).toBe(cosignerSignature)
expect(signatures[1]).not.toBe(decodeSignatures(stubSignature)[1])
})
})
15 changes: 11 additions & 4 deletions plugins/weighted-r1-k1/toWeightedValidatorPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,16 @@ export async function createWeightedValidator<
// Sign a user operation
async signUserOperation(userOperation) {
let signatures: readonly Hex[] = []
if (userOperation.signature !== "0x") {
signatures = decodeSignatures(userOperation.signature)
if (userOperation.signature && userOperation.signature !== "0x") {
const ownStubSignature = concatHex([
toHex(getIndexOfSigner(), { size: 1 }),
await signer.getDummySignature()
])
signatures = decodeSignatures(userOperation.signature).filter(
(signature) =>
signature.toLowerCase() !==
ownStubSignature.toLowerCase()
)
}
// last signer signs for userOpHash
const userOpHash = getUserOperationHash({
Expand All @@ -230,8 +238,7 @@ export async function createWeightedValidator<

async getStubSignature(userOperation) {
let signatures: readonly Hex[] = []
if (userOperation.signature !== "0x") {
console.log(userOperation.signature)
if (userOperation.signature && userOperation.signature !== "0x") {
signatures = decodeSignatures(userOperation.signature)
}

Expand Down