Skip to content

Commit

Permalink
check if existing userOperation is already in internal mempool (#346)
Browse files Browse the repository at this point in the history
* check if existing userOperation is already in internal mempool

* remove error message padding

---------

Co-authored-by: mouseless <[email protected]>
  • Loading branch information
mouseless0x and mouseless0x authored Oct 29, 2024
1 parent e3cd69b commit e614196
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 3 deletions.
10 changes: 10 additions & 0 deletions src/mempool/mempool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ export class MemoryMempool {
.map(({ userOperation }) => userOperation)
]

// Check if the exact same userOperation is already in the mempool.
const existingUserOperation = [
...outstandingOps,
...processedOrSubmittedOps
].find(({ userOperationHash }) => userOperationHash === opHash)

if (existingUserOperation) {
return [false, "Already known"]
}

if (
processedOrSubmittedOps.find(({ mempoolUserOperation }) => {
const userOperation = deriveUserOperation(mempoolUserOperation)
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/rpcHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ export class RpcHandler implements IRpcEndpoint {
getAAError(errorReason)
)
throw new RpcError(
`UserOperation reverted during simulation with reason: ${errorReason}`,
errorReason,
ValidationErrors.InvalidFields
)
}
Expand Down Expand Up @@ -952,7 +952,7 @@ export class RpcHandler implements IRpcEndpoint {
getAAError(errorReason)
)
throw new RpcError(
`UserOperation reverted during simulation with reason: ${errorReason}`,
errorReason,
ValidationErrors.InvalidFields
)
}
Expand Down
37 changes: 36 additions & 1 deletion test/e2e/tests/eth_sendUserOperation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
type EntryPointVersion,
UserOperationReceiptNotFoundError,
entryPoint06Address,
entryPoint07Address
entryPoint07Address,
UserOperation
} from "viem/account-abstraction"
import { generatePrivateKey } from "viem/accounts"
import { foundry } from "viem/chains"
Expand Down Expand Up @@ -383,5 +384,39 @@ describe.each([
)
).toEqual(true)
})

test("Should throw 'already known' if same userOperation is sent twice", async () => {
const smartAccountClient = await getSmartAccountClient({
entryPointVersion
})

const to = "0x23B608675a2B2fB1890d3ABBd85c5775c51691d5"
const value = parseEther("0.15")

const userOperation =
(await smartAccountClient.prepareUserOperation({
calls: [
{
to,
value,
data: "0x"
}
]
})) as UserOperation
userOperation.signature =
await smartAccountClient.account.signUserOperation(
userOperation
)

await smartAccountClient.sendUserOperation(userOperation) // first should go through

await expect(async () => {
await smartAccountClient.sendUserOperation(userOperation) // second should fail due to "already known"
}).rejects.toThrowError(
expect.objectContaining({
details: expect.stringContaining("Already known")
})
)
})
}
)

0 comments on commit e614196

Please sign in to comment.