Quantstamp reviewed Alchemy ERC (Alchemy ERC-4337 Paymasters) on 20 May 2025: 9 findings, 0 critical; 3 marked fixed, 6 acknowledged.
Report hosted on certificate.quantstamp.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 0 | 0 | 1 |
| Low | 0 | 6 | 2 |
For `mode 1, 2 and 3` the `paymasterData` field is structured in the following way: ``` For v0.6 // ERC-20 format: // - [0:20] 20 bytes for paymaster // - [20:52] 32 bytes for sigTimeRange // - [52] 1 byte for mode (0: pre-approval, 1: post-op, 2: permit) // - [53:85] 32 bytes for conversion factor (scaled token price, calculated off-chain) // PRICE_SCALE * ethPriceUsd * 10^tokenDecimals / (tokenPriceUsd * 10^(ethDecimals)) // - [85:105] 20 bytes for token address // - [105:125] 20 bytes for receiver address // - [125:190] 65 bytes for signature // - [190:] variable bytes for permit (if mode == 2) For v0.7 // ERC-20 format for paymaster data: // - [0] 1 byte for mode (0: pre-approval, 1: post-op, 2: permit) // - [1:33] 32 bytes for conversion factor (scaled token price, calculated off-chain) // PRICE_SCALE * ethPriceUsd * 10^tokenDecimals / (tokenPriceUsd * 10^(ethDecimals)) // - [33:53] 20 bytes for token address // - [53:73] 20 bytes for receiver address // - [73:138] 65 bytes for signature // - [138:] variable bytes for permit (if mode == 2) ``` For example, in the context of paymaster v0.7, the bits 138 onwards are only used in `mode = 2`, but there is no enforcement that the field is not used during `modes = 0` and `1`, which could lead to wasted gas due to byte stuffing. The only check currently enforces the `paymasterData ` to be at least 138 bits. There are no direct exploits here, as this byte stuffing would cause a loss for the user because both the paymaster and the user have to sign this data. There is only one case where the paymaster loses some funds, and that is in `mode 1`, where the user does not pay in `postOp`. However, it would be a good practice to avoid this griefing vector.
The paymaster implementation for EntryPointv0.6 contains a `POST_OP_GAS ` constant that is a snapshot measure of the gas-costs of the `postOp()` call with USDC as the ERC-20-payment token. This is used as this version of the EntryPoint lacks a granular parameter for limiting the `postOp` gas that is needed for the cost calculations. The single value is slightly simplifying the mechanics of the different modes of operation. In `mode = 0`, there is for example not just one, but two `safeTransfers()` compared to `mode = 1`. Some tokens might have differing gas costs for their transfers, especially if e.g. ERC-777 or ERC-1155 tokens with callbacks are used. Different chains might also have different gas costs for certain opcodes that are used in this call. Furthermore, in the version supporting EntryPointv0.6, there is also the possibility of reverts, leading to a second invocation of the `postOp()` call that incurs additional gas costs not captured by this constant. We also want to mention that there are very select few chains that adjust the gas costs of certain opcodes most notably [ZKSync](https://docs.zksync.io/zksync-protocol/rollup/fee-model#different-opcode-pricing), that could lead to inaccuracies with the same value between different chain deployments.
The `VerifyingPaymaster` contract applies a 10% gas penalty in the `postOp()` function based on the **total gas limit**, rather than strictly the **unused execution gas**, contrary to the ERC-4337 specification. The penalty is currently calculated as: ```solidity uint256 gasPenalty = (totalGasLimit * actualUserOpFeePerGas - actualGasCostWithPostOp) / 10; ``` This calculation incorrectly penalizes users for unused pre-operation gas, inflating their final charge. According to the [ERC-4337 specification – Required EntryPoint Contract Functionality](https://eips.ethereum.org/EIPS/eip-4337#required-entrypoint-contract-functionality), the `UNUSED_GAS_PENALTY_PERCENT` (10%) should be applied only to unused gas from `callGasLimit` and `paymasterPostOpGasLimit`.
In the flow of using this paymaster, the `UserOperation` sender proposes a `UserOperation` to the paymaster signer API, which calculates the appropriate user token payment and encodes these values into the `paymasterAndData` field of the `UserOperation` struct. The user then confirms this data again by providing a passing validation for the full `UserOperation`. Apart from these off-chain calculations by the Paymaster, the actual gas costs of the `UserOperation` and the token amount are not associated with each other, so the users essentially opt in to some token payment, no matter the actual execution costs. The gas costs could be significantly differ from the underlying ERC-20-payment if the `conversionFactor`, the value indicating the conversion between ETH and the token the user is paying in, is wrongly calculated. In volatile token price conditions, it could also be the case that the signer API signs off a significantly lower `conversionFactor`, resulting in a monetary loss of the paymaster. A compromised signer API could also withdraw the full approval from the `UserOp.sender` in a requested `UserOperation`. If the end user does not detect that when providing the validation for the `UserOperation`, they might be left at a significant financial loss.
The standard specification of ERC-7597 uses a `block.timestamp` check to check for freshness of the forwarded `deadline` parameter. However, as defined by ERC-7562, this is a forbidden opcode for the validation phase of `UserOperations` according to OP-011, which would result in default bundlers dropping such `UserOperations`, ultimately not including them in bundles on-chain. Some token implementations support ERC-7597, but skip the `block.timestamp` check if a `uint256.max` value is encoded as part of the deadline of the permit. An example of such a token is USDC. Such implementations would not lead to ERC-7562 complications.
The `_validatePaymasterUserOp()` function includes state-dependent reverts that might change in outcome between after second validation and final simulation/execution on the chain, such as paymaster being paused, signer being updated, or allowances/permits being consumed. If validation produces different results for the same `UserOperation` after the second validation simulation, bundlers may ban the paymaster, stopping UserOperations including this paymaster from being propagated. This aligns with [GREP-40](https://eips.ethereum.org/EIPS/eip-7562#general-reputation-rules) from ERC-7562, which emphasizes deterministic validation to prevent bans. Here is a non-exhaustive list of where this might occur: 1. Both Paymasters could be paused via `pause()`. Depending on when the function is called, there can be a case where `UserOperation` is invalidated just before on-chain execution. 2. Signer can be updated via `updateVerifyingSigner()`, which could lead to signature validation failing and there can be a case where `UserOperation` is invalidated just before on-chain execution. 3. Permit or approval can be withdrawn just before the `UserOperation` is executed on-chain.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (4)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us