Alchemy ERC security record

Ethereumalchemy.com

As of 30 Oct 2026, Alchemy ERC has 6 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 20 May 2025. Those reports list no open critical findings.

Critical findings
No open critical findings
Across 6 reports.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.

Audits6

Newest first

AuditSecurity firmDateScopeCritical findingsReport
Alchemy ERC-4337 PaymastersSecurity firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com
Alchemy - Modular Account V2Security firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com
Alchemy - LightAccount v2Security firm: QuantstampDate: Scope: Repository at commit 93f46a2No open critical findingsOpen · certificate.quantstamp.com
Alchemy - Multisig PluginSecurity firm: QuantstampDate: Scope: Repository at commit 70089a2No open critical findingsOpen · certificate.quantstamp.com
Alchemy Modular AccountSecurity firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com
Alchemy Light AccountSecurity firm: QuantstampDate: Scope: Repository at commit aa8196bNo open critical findingsOpen · certificate.quantstamp.com

Findings85

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical000
High004
Medium124
Low33536
High`NativeTokenLimitModule` Can Be BypassedAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The `NativeTokenLimitModule` is intended to limit the native tokens some validation can access from the account. In case paymasters provided, the check is regularly skipped, as the paymaster is covering the gas costs, unless it is some special paymaster that has some existing ERC20 token allowance that withdraws e.g. ERC20 tokens equivalent to the gas consumed from the account. However, the decoded address is missing a check that it is a non-zero length `bytes` array of just zeroes. This enables a bypass in the `paymasters` registry, as in this case `userOp.paymasterAndData.length != 0` as well as `specialPaymasters[address(0x0)][msg.sender] == 0 `, if `address(0x0)` is not specifically registered as a `specialPaymaster`, which does not seem intended. EthInfinitism's EntryPoint internally treats a `paymasterAndData` field of zero length [the same way](https://github.com/eth-infinitism/account-abstraction/blob/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9/contracts/core/EntryPoint.sol#L391) as `paymaster = address(0x0)`, `paymasterVerificationGasLimit = 0`and `paymasterPostOpGasLimit = 0`. The unpacking of the paymasterAndData variable [here](https://github.com/eth-infinitism/account-abstraction/blob/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9/contracts/core/UserOperationLib.sol#L120) does not do any reverts on zero-values either. This enables all UserOperation-validations installed on the account that have this validation hook installed to fully drain the underlying account by simply encoding a 52 byte long `paymasterAndData` field containing only zeroes. Furthermore, in the `PaymasterGuardModule`, a similar check is performed. If this module were to be installed without the `onInstall()` callback or with an `installData` parameter encoding the zero address, native funds could be drained in the same way.

Open the report
HighValidation Modules' Validation Can Be Fully Bypassed if Signature Validation Is Also EnabledAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The modular account system supports deferred actions, enabling operations such as installing validation modules to be delayed until execution. Deferred actions, embedded in the `UserOperation`'s signature field, will likely only be used within the same `UserOperation` for validation installation. However, their design allows for arbitrary use, provided the outer `ValidationConfig` validates the selector. Currently, deferred action execution relies solely on signature verification through the outer `ValidationConfig`'s `validateSignature()` function. This bypasses critical validation steps, including pre-validation (`preUserOpValidationHook()`) and execution hooks, which enforce security policies like access control. Without these hooks, attackers can bypass the full validation process, enabling unauthorized operations and privilege escalation. In other words, as soon as a validation module is installed with `isSignatureValidation = true`, all validation-associated hooks and the main validation step that the module also has installed can be skipped by embedding a call to a deferred action and simply validating it via the signature validation path. We argue that this situation is significantly different from the intentional skipping of related hooks when calling `executeBatch()` (ALC-16), as `executeBatch()` is well understood as a powerful selector with very restricted access. Additionally, it should be noted that due to this issue, any validation module with access to `executeBatch()` and signature validation enables the checks imposed on `_checkExecuteBatchValidationApplicability()` to be bypassed, as the calldata validation is also skipped. Checking only that a `ValidationConfig` provides a valid signature is insufficient; deferred actions must trigger all associated validation hooks to maintain security.

Open the report
HighSession Keys Share And Overwrite Each Other's PermissionsAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `SessionKeyData` struct of a session key defines the permission access of registered session keys. As this information has to be accessible as part of UserOp validation, it has to comply with the ERC-4337 storage restriction rules. The client developed a `PluginStorageLib` library that follows the `keccak256(A || X) + n` (`A` being the address of the account and `X` being an additional arbitrary key) storage access rule defined in the EIP, which essentially enables access to address-associated storage slots in the plugin's storage. See MSCA-30 for concerns regarding ERC-4337 storage access compliance. The key derivations to get to the address associated storage slots are defined in `SessionKeyPermissionsBase.sol#L70-L91` and implemented in the underlying `_sessionKeyIdOf()` function. The implementation of the `SessionKeyData` storage key differs slightly from the specs, resulting in a critical issue for the storage management of `SessionKeyData` structs. While the `associatedStorageKey` follows the spec with `12 padding zeros || associated address || SESSION_KEY_DATA_PREFIX || batch index`, it is not just appended with the `sessionKeyId` as the specs suggest, but with `bytes32(abi.encodePacked(SESSION_KEY_DATA_PREFIX, SessionKeyId.unwrap(id)))` (`SessionKeyPermissionsBase.sol#L133`). This statement will incorrectly slice off the 4 right-most bytes of the `sessionKeyId`, which turns out to be a `uint256` counter variable, incremented upwards from zero for each account. In the EVM, `uint256` variables are stored in little-endian notation, with the least significant bytes stored in the right-most bytes of the storage slot. Therefore, as a result of incorrectly slicing off the 4 least significant bytes of the key ID, meaning the first 0xffffffff = 4294967295 registered keys within each account share the same storage slot, meaning that they share the same `SessionKeyData` object defining a key's permissions. As a consequence, the entire access control around session keys is fully compromised for all MSCAs, as practically all keys an individual MSCA would ever issue share the same permissions. **SessionKeyData**: Specs: `... SESSION_KEY_DATA_PREFIX || batch index || sessionKeyId` Code: `... SESSION_KEY_DATA_PREFIX || batch index || **SESSION_KEY_DATA_PREFIX** || sessionKeyId with righ-most 4 bytes sliced off`

Open the report
HighSession Keys with ERC-20 Spend Limit Can Drain MSCA of Limited TokensAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

Part of the permissions that can be set for session keys is an ERC-20 spend limit, with which the owner of an MSCA can grant certain permissions to holders of the session key to spend tokens in possession of the MSCA. Notably, multiple contract calls can be executed within a single `executeWithSessionKey()` user operation through multiple entries in the array of `Call` structs. In a pre-execution hook, the calldata of each of the calls encoded in the user operation is analyzed for the `transfer()` and `approve()` selector, where the spending limit is adjusted based on the full `amount` parameter for transfers and on the supposed increase in allowance of the given address for approvals. However, the current implementation of the pre-execution hook incorrectly assumes that no prior calls part of this user operation have adjusted the allowance. The result of `IERC20(token).allowance(account, spender)` could incorrectly reflect the state at the time of execution of the current `Call` being validated. This enables any malicious holder of a session key with a non-zero ERC-20 token limit for a certain token to fully drain the MSCA's token balance.

Open the report
MediumPostOp Reverts Can Lead to Uncompleted Repayment of Paymaster And Stuck FundsAlchemy ERC-4337 Paymasters · Quantstamp · 20 May 2025Fixed

In a `mode` of `0`, `1` and `2`, the paymaster expects repayment in some ERC20 token and possibly returns excessive prepayments back to the `UserOp.sender`. However, reverts of the `postOp()` call can result in this process to no being properly completed, as the state from the execution phase, but not from the passed validation phase, is rolled back in those cases. * `mode 0` and `mode 2`: user fully pays upper bound pre-payment, but is not getting potential excess funds back. In these modes, the Paymaster temporarily holds the funds, so reverts are limited to strange, non-standard ERC-20 behavior. However, if such a case were to happen, these ERC-20 tokens would now be stuck in the Paymaster of a possibly significantly overcharged `maxTokenAmount` amount of tokens. There is no mechanism to redeem those tokens again for anyone. They can also not be withdrawn from the paymaster, as the paymaster itself is currently not designed to be holding ERC-20 tokens permanently, instead it normally forwards the charged amount to the `receiver` address. * `mode 1`: This mode expects an approval to be set as part of the execution phase. If that does not happen as simulated or if the tokens get sent out the account before the `postOp()` call, the Paymaster does not receive any reimbursement from the user, as in this mode, the transfer is only completed in the `postOp()` call after the execution phase. In the V0.6 Paymaster, the user therefore fully gets away with a free UserOperation, as reverts are not performed as that would harm Paymaster reputation. In the V0.7 Paymaster, there is a revert, rolling back the execution phase of the UserOperation, but this still poses a griefing attack on the Paymaster, as the Paymaster is still required to pay for the reverting UserOperation without getting reimbursed.

Open the report
MediumAccess Control Bypass on Owner-Only Function CallsAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `MultiOwnerPlugin` defines the `runtimeValidationFunction()` function to protect the runtime calls to the native functions of MSCA. This function ensures that the caller is either the MSCA itself or one of the authorized owners. The choice of allowing MSCA to call the owner-only functions seems to be designed to allow the owner to uninstall the owner plugin and install another simultaneously in an `executeBatch()` call. However, such a design could pose risks to the MSCA and potentially allow an unauthorized party to execute the owner-only functions due to the design of plugin external calls. A plugin can be allowed to perform calls to external contracts, including the MSCA itself, via the `executeFromPluginExternal()` function. If a plugin is allowed to do so, it can, for example, execute `MSCA.upgradeToAndCall()`, to upgrade the account implementation without being an authorized owner. Note that `SessionKeyPlugin` is allowed to execute calls to any external contracts. While the `SessionKeyPermissionsPlugin` enforces specific permissions on the session keys, if any user with a session key with `ContractAccessControlType` of `NONE` type (i.e., no access control enforced on the called external contracts), they would be able to perform arbitrarily calls to the owner-only functions.

Open the report
MediumNon-Idempotent Pre-Execution Hook in `SessionKeyPermissionsPlugin`Alchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

According to the MSCA specification, hooks are assumed to be idempotent, so deduplication can be performed when multiple hooks overlap. However, the pre-execution hook defined in `SessionKeyPermissionsPlugin` is not idempotent, specifically the `_updateLimitsPreExec()` function. If the `_updateLimitsPreExec()` function is executed more than once during the `executeWithSessionKey()` call, the spend limits will be reduced multiple times. Any plugin can add another plugin's hooks to the MSCA without permission from the providing plugin (except in the case of injected hooks). Therefore, a hook can be executed multiple times even though the providing plugin specifies it once in the manifest, which may lead to unexpected results for non-idempotent hooks.

Open the report
MediumSelf-Calls Are Allowed to Execute Functions without a Runtime Validation FunctionAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

In numerous instances, the MSCA is able to perform a call on itself ("self-call"). We identified three distinct ways in which self-calls can be achieved: 1. Via the successful call to the `execute()` and `executeBatch()` selectors, which require a passing `preNativeFunction()` check. In the reference implementation, these selectors are protected by the `MultiOwnerPlugin`'s runtime validation, so require the `msg.sender` to provide a valid signature or require the signer of the user operation to be a registered owner. 1. Via the `executeWithPluginExternal()` selector, self-calls can be achieved by all plugins with specifically the MSCA itself listed as a permitted address or with a plugin specifying an activated `permitAnyExternalAddress` flag in its manifest. For example, any holder of a session key registered in an MSCA with access control enabling calling the MSCA itself can do self-calls via `executeWithSessionKey()`, as it calls the `executeWIthPluginExternal()` selector. Such a key would need to be enabled to call the MSCA via `accessControlType=ALLOWLIST` and the MSCA specifically being registered as a callable address, or if the MSCA itself is not being registered in a configured `accesscontrolType=DENYLIST` or if the key can call arbitrary addresses via `accesscontrolType=NONE`. 1. If the MSCA itself were to add the `IPlugin` interface to its `supportedInterface` mapping through a plugin installation (see MSCA-23), self-calls could be achieved through `executeWithPlugin()` calls. The `_doRuntimeValidation()` function looks up the runtime validation function associated with the given function selector and performs the runtime validation call to the corresponding plugin. While generally, attempted runtime validation fails if no associated runtime validation is found, a special condition at `UpgradeableModularAccount.sol#L617-L625` is evaluated to check if the call should be allowed. According to the comments at `UpgradeableModularAccount.sol#L626-L630`, the MSCA should prohibit any runtime call with no associated runtime validation function except the cases of self-calls to `installPlugin()` or `upgradeToAndCall()`. However, due to the use of `||` at L622, which seems to be a logical error, any self-calls to the MSCA are allowed, causing a divergence between the code and the comments. As a result, a permitted plugin or owner can execute functions that should not be executed in runtime context due to this logical error in the code. For example, the `executeWithSessionKey()` function provided by the `SessionKeyPlugin` does not have a runtime validation function, as session keys are intended to be used in user operation context only. However, an owner of the MSCA can execute the `executeWithSessionKey()` function in runtime using self-calls via `executeBatch()` and potentially affect the token or gas usages of an arbitrary session key without obtaining a valid signature of the key. However, note that in the current design, the owner of MSCA can arbitrarily control the spending limits of session keys.

Open the report
LowNotes on `PostOpMode.postOpReverted`Alchemy ERC-4337 Paymasters · Quantstamp · 20 May 2025Fixed

As of V0.7, the EntryPoint will no longer forward a `PostOpMode` enum of type `PostOpMode.postOpReverted`to the paymaster; hence, it's check in the V0.7 paymaster in the `if` statement can be skipped. However, in V0.6, that parameter value would indicate that something malfunctioned in the intended flow of this Paymaster. Especially reverts in the first `postOp()` call in case `mode = 1`, i.e. in the case that the ERC-20-payment is supposed to be collected in the `postOp()` call, would indicate that the user was not billed for the sponsored transaction. Therefore, a special event should be emitted here that indicates to enable monitoring for potential abuses.

Open the report
LowPaymaster Can Accidentally Cause The Whole Bundle To Revert Due to Bug in EntryPointV0.6Alchemy ERC-4337 Paymasters · Quantstamp · 20 May 2025Fixed

The EntryPoint v0.6 implementation contains a bug in the invocation of the Paymaster’s `postOp()` function, where reverts with less than 32 bytes of revert data cause the entire bundle to revert. This can result in significant financial loss for bundlers and a banned reputation for the reverting paymaster. The used `safeTransferFrom()` function would revert with a shorter revert message, causing the bug to get triggered. For more info see this [PR](https://github.com/eth-infinitism/account-abstraction/pull/293). In `mode = 0`, the paymaster only performs the `safeTransferFrom()` in the `postOp()` call, which could technically revert, leading to the whole transaction from the bundler reverting.

Open the report
Show 10 more
Low`getExecutionData()` Incorrectly Marks Native View Functions as Unlockable with Global ValidationAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

In the `getExecutionData()` function, all selectors that return `true` for `_isNativeFunction()` are marked as unlockable by global validation (`allowGlobalValidation = true`). However, this logic is flawed because `_isNativeFunction()` also returns `true` for many view functions, which are permissionless and not intended to be unlocked for global validation.

Open the report
Low`NativeTokenLimitModule` Does Not Return `IValidationHookModule` As Supported InterfaceAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The `NativeTokenLimitModule.supportsInterface()`does not return `true` for `interfaceId == type(IValidationHookModule).interfaceId`, even though it provides validation hooks.

Open the report
LowIncomplete List of Native Selectors Returned From `isNativeFunction()`Alchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

`NativeFunctionDelegate.isNativeFunction()` does not include all native selectors of the `ModularBase` contract. Crucial selectors are missing such as: * `invalidateDeferredValidationInstallNonce()`, * `performCreate()` and * `isValidSignature()` are missing, and also less crucial ones, such as: * `IERC721Receiver.onERC721Received.selector` * `IERC1155Receiver.onERC1155Received.selector` * `IERC1155Receiver.onERC1155BatchReceived.selector` and * ` IAccountExecute.executeUserOp.selector` Furthermore, `SemiModularAccountStorageOnly` also exposes an `initialize()` function and `ModularAccount` exposes an `initializeWithValidation()` that should technically instead be exposed by an overridden `_isNativeFunction()`.

Open the report
LowIncorrect Casting in `toSetValue` FunctionsAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The `LinkedListSetLib` library from the reference implementation has been adjusted to work with an underlying `SetValue` of type `bytes31` instead of `bytes30`. However, the castings in the `AccountStorage.toSetValue()` function have not been adjusted. Importantly, no information gets lost due to the erroneous casting, as the wrapped values are always less than 25 bytes long and also of type bytes, so only unused padding is omitted.

Open the report
LowIncorrect Masking of `deadline`Alchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

In `ModularAccountBase._computeDeferredValidationInstallTypedDataHash()`, the `deadline` is incorrectly masked to keep the least significant 4 bytes rather 6, truncating the upper 2 bytes. This limits possible deadline encodings to 2^32 - 1 UNIX seconds, some time in the year 2106.

Open the report
LowMissing Function Selector Check in `isIModuleFunction()`Alchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The `KnownSelectorsLib.isIModuleFunction()` is missing a check for `preSignatureValidationHook()`.

Open the report
LowPotential Denial of Service Due to Misconfigured Time RangesAlchemy - Modular Account V2 · Quantstamp · 6 Dec 2024Fixed

The module allows account owners to set `validUntil` and `validAfter` timestamps for their operations. However, there is no validation to ensure that `validAfter` is less than or equal to `validUntil`. If an account owner inadvertently sets `validAfter` to a value greater than `validUntil`, the condition in `preRuntimeValidationHook()` will always revert.

Open the report
Low`SIG_VALIDATION_FAILED` Shouldn't Be Returned in All the Cases of ECDSA FailureAlchemy - LightAccount v2 · Quantstamp · 9 Apr 2024Fixed

When an EOA signature is submitted, it is decoded via `digest.tryRecover(signature)`. This function call returns the address recovered and an error of type `ECDSA.RecoverError`. There are currently 4 entries in the `ECDSA.RecoverError` enum: ``` 1. NoError, 2. InvalidSignature, 3. InvalidSignatureLength, 4. InvalidSignatureS ``` In the current iteration of the code base, in the case of the 3 invalid errors, `SIG_VALIDATION_FAILED` is returned, which is not entirely correct, as it should only be returned if a signature is correctly formed but the validation fails. The only case where `SIG_VALIDATION_FAILED` should be returned is `NoError`, but the signer does not match the expected `owner`, which means that the signature is correctly formed, but the validation fails. In the case of `InvalidSignatureLength`, `InvalidSignatureS` and `InvalidSignature`, the signature itself is invalid; in the first case, the signature is not of length `65`. In the second error, the `s` value is in the upper range, and in the last error, either `r = 0`, `s = 0`, or `r` is too large.

Open the report
LowMissing Input ValidationAlchemy - LightAccount v2 · Quantstamp · 9 Apr 2024Fixed

It is important to validate inputs, even if they only come from trusted addresses, to avoid human error. Specifically, in the following functions: 1. `BaseLightAccount.withdrawDepositTo()` should check that `withdrawAddress` is not `address(0) `. 2. `BaseLightAccountFactory.withdrawStake()` should check that `to` is not `address(0) `. 3. `BaseLightAccountFactory.withdraw()` should check that `to` is not `address(0) `.

Open the report
LowPotential Gas Griefing by Changing the Signature Verification ModeAlchemy - LightAccount v2 · Quantstamp · 9 Apr 2024Fixed

The `_validateSignature()` function of `MultiOwnerLightAccount` allows the user operation to be validated in three different modes based on the first byte of `userOp.signature`. The mode `SignatureType.CONTRACT` iterates through the owner list until it finds an owner contract accepting the given signature. On the other hand, the mode `SignatureType.CONTRACT_WITH_ADDR` only checks if one owner contract accepts the signature, which provides potential gas savings as the iteration through the owner list is avoided. However, an adversary who listens to the user operation requests, such as a bundler, can modify the first byte of `userOp.signature` and force the account to use a more gas-expensive signature validation mode by switching from `CONTRACT_WITH_ADDR` to `CONTRACT`. If the specified verification gas limit in the user operation is sufficiently high, the signature validation can still pass since the only-provided signature is correct. Note that the user operation signer is usually not incentivized to set an overly high verification gas limit. Also, the incentive for an adversary to perform such an attack seems low since they do not directly gain profit. Due to the above reasons, this issue is considered Info severity.

Open the report
Show 10 more
Low`SIG_VALIDATION_FAILED` Returned in Incorrect CaseAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

The `checkNSignatures()` function returns a signature validation failure if the `digest.tryRecover()` call to verify an EOA's signature fails with an error. As a result, the `RecoverError.InvalidSignatureS` (when the `s` value is in the upper range) will be treated as a signature validation failure. This behavior differs from the Modular Account's `SessionKeyPlugin`, which reverts the transaction if a `RecoverError.InvalidSignatureS` error occurs. See [SessionKeyPlugin.sol#L186-L189](https://github.com/alchemyplatform/modular-account/blob/9c2c03197ef4240e32f4d882c4164971e658d639/src/plugins/session/SessionKeyPlugin.sol#L186-L189).

Open the report
LowAccounts Can Be Created with More than `_MAX_OWNERS_ON_CREATION` OwnersAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

`MultisigModularAccountFactory.getAddress()` reverts in case of `owners.length` exceeds `_MAX_OWNERS_ON_CREATION`. However, `createAccount()` does not perform a similar check, potentially leading to block gas limit-related issues if the number of owners is too high.

Open the report
LowLack Of Constraints On Signature Enable Gas Griefing Attack VectorAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

The composite signature doesn't specify a maximum length that valid signatures must have. This opens up a potential vector for griefing by appending bytes to the end of the composite signature. Since the `signature` field in the `UserOp` is inherently malleable, a malicious bundler or middle man could insert additional bytes, maxing out the specified `verificationGasLimit `.

Open the report
LowNo Reverting Runtime Validation Added for `UserOp` Function SelectorsAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

According to the plugin manifest, the multisig validation scheme is only expected to be used in the context of a `UserOp`. Therefore, `MultisigPlugin` does not define the runtime validation function for owner-only functions, assuming that any function call in runtime to them will revert due to the lack of validation function. However, another installed plugin can add a runtime validation function to the owner-only functions, allowing them to be executed in runtime. As a result, one may bypass the multisig verification and execute the owner-only functions as long as the runtime verification allows. It may be the intended choice if the account owners want to configure two different verification schemes in `UserOp` and runtime contexts, but it may increase the complexity of managing privileged roles.

Open the report
LowPlugin Is Incompatible with the Latest `UserOp` Validation RulesAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

When validating a `UserOp`, the multisig plugin's `userOpValidationFunction()` function is called with the corresponding parameters. The plugin compares the gas limits specified in the `UserOp` with the upper limits encoded in the signature. If any gas limits differ, it recalculates the `UserOp` hash with the upper limits by calling `ENTRYPOINT.getUserOpHash()`. However, the function call `getUserOpHash()` to `ENTRYPOINT` is not allowed, as specified in [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) (a draft EIP for ERC-4337 validation rules). According to OP-051 to OP-054, only calls to the `depositTo()` or fallback function are allowed, and any other function calls are forbidden. Our experiments with the [Infinitism's bundler](https://github.com/eth-infinitism/bundler) showed that the bundler rejects a `UserOp` that calls `ENTRYPOINT.getUserOpHash()` during the validation phase with the following error: ``` Error: processing response error [...] reason: 'processing response error', code: 'SERVER_ERROR', body: '{"jsonrpc":"2.0","id":43,"error":{"message":"illegal call into EntryPoint during validation getUserOpHash","code":-32502}}', ```

Open the report
LowUnsafe Type CastingAlchemy - Multisig Plugin · Quantstamp · 29 Mar 2024Fixed

The `updateOwnership()` function allows the account owners to update the multisig threshold, specified as the `newThreshold` parameter. If the parameter's value is zero, it indicates the threshold should not be updated. If not, the parameter will be type-casted from `uint256` to `uint128` and stored in the contract. As a result, the final stored threshold value may become zero if the last 128 bits of a positive `newThreshold` are zero, bypassing the multisig-verification scheme after the change since no signature is required anymore.

Open the report
Low`SIG_VALIDATION_FAILED` Returned in Incorrect CasesAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The ERC-4337 specification states the following: > If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the `userOpHash`, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error should revert. Currently, there are cases in the signature verification process where `SIG_VALIDATION_FAILED` is returned even in the case of a malformed signature, which should not be the case. Currently, in 2 cases, the function does not revert when it should: 1. `MultiOwnerPlugin.userOpValidationFunction()` 2. `SessionKeyPlugin.userOpValidationFunction()`

Open the report
LowA Plugin with an Injected Hook to Itself Cannot Be UninstalledAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

Users can provide additional injected hooks not specified in the plugin manifest during the plugin installation. Injected hooks have the same behavior as permitted call hooks, which are executed when the plugin executes a permitted call to another plugin or an external contract. However, plugins with injected hooks that execute functions defined by themselves cannot be uninstalled after installation. While adding the injected hooks, the plugin creates a dependency on itself. Therefore, the plugin cannot be uninstalled since the plugin’s dependency count will be at least one at the time of the uninstall call, essentially causing a deadlock. This issue can further impact other plugins that the uninstallable plugin depends on, as they would also become uninstallable.

Open the report
LowCertain Assumptions Are Made in the `PluginStorageLib` that Could Lead to IssuesAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

`PluginStorageLib` is a library for allocating and accessing ERC-4337 address-associated storage within plugins. There are multiple assumptions that are used through the library but are not enforced in the library code. In the current state of the codebase, the assumptions are followed, but as more plugins are developed, this cannot be guaranteed. Below is a list of current assumptions that will cause issues if not enforced: 1. The library allows a `keySize` of 256, which corresponds to how many words of memory are needed to look up a specific value. The `keySize` of max 2 is currently used, but the size of the key is not enforced. For example, if an address-associated storage key with `keySize = 2` is allocated, there is nothing stopping storage lookup with that memory allocation with only `input1` and no `input2`. Assure that the `keySize` corresponds to the number of key arguments provided. 2. The `key` variable size is currently unbound, which is not clear as it stores a slot in the memory and hence should be `bytes32,` or it will lead to unexpected outcomes. 3. One should not be allowed to have keys of a value of 0, as this could lead to collisions and unexpected behavior. Make sure that the `input` keys are checked and are not 0 inside the library. 4. Upper bits of assembly input are assumed to be 0s in `allocateAssociatedStorageKey()`, which might not be the case in `addr`, as it is an `address` and hence the upper bits are not guaranteed to be 0. Assure that the input is properly cleared in a similar manner to the `_allocateTempKeyBuffer()` function in `AssociatedLinkedListSetLib`. According to the latest [Solidity Inline Assembly documentation](https://docs.soliditylang.org/en/0.8.23/assembly.html) at the time of writing: > _"If you access variables of a type that spans less than 256 bits (for example `uint64`, `address`, or `bytes16`), you cannot make any assumptions about bits not part of the encoding of the type. Especially, do not assume them to be zero. To be safe, always clear the data properly before you use it in a context where this is important [...]"_

Open the report
LowDifferent Order of Owners Can Produce Different MSCAsAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `MultiOwnerMSCAFactory` uses `CREATE2` to generate the contract. As part of that call, the factory is passing the hash of a salt (provided by the caller) and the encoding of the list of owners. This means that if the owners are given in different orders, the addresses will be different. It is unclear if this is a problem or not

Open the report
Show 10 more
LowIncorrect Storage Prefix ConstantAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

According to the inline documentation, the `_ASSOCIATED_STORAGE_PREFIX` should have a value calculated using `bytes4(keccak256("AssociatedLinkedListSet"))`. This value should be `0xf938c976`, but the current value is `0x9cc6c923`. It is unclear where this value came from.

Open the report
LowLack of Function Call Context for Post-Execution HooksAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

Unlike the other hooks, the parameters of post-execution hooks do not include the call data of the execution function but only the `functionId` and the returned data from the pre-execution hook. Such a design causes an issue: A post-execution hook cannot validate if it is being applied on a recognized execution function selector. The `functionId` parameter is not enough for validation purposes, as any random plugin can add a hook to an arbitrary execution function with the corresponding `functionId`.

Open the report
LowMissing Validation for Execution Function in `SessionKeyPermissionsPlugin` HooksAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The pre-execution hook defined by `SessionKeyPermissionsPlugin` updates the spending limit of a session key according to the provided call data. However, it does not validate that the selector (the first 4 bytes of the calldata) is exactly `executeWithSessionKey.selector`. Since a plugin can reuse another plugin's hook (by specifying it in the manifest or by injected hooks), if a plugin adds the pre-hook from `SessionKeyPermissionsPlugin` to a random execution function, whenever that function is executed, the pre-hook is executed as well. This can cause an issue as the pre-hook is designed to be applied only to the `executeWithSessionKey()` function. Otherwise, the spending limit of a session key may be reduced without the user actually spending any token or gas.

Open the report
LowMisuse of Functions in the `CastLib` Library Could Lead to Loss of InformationAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `LinkedListSet` and `AssociatedLinkedListSet` collections return values as a user-defined type `SetValue` when retrieving items. The underlying value of this type is always a `bytes30`, regardless of the actual data stored. To facilitate working with these values, the `CastLib` offers two functions: `toFunctionReferenceArray` and `toAddressArray`. These convert an array of `SetValue` into an array of function references or addresses, respectively. However, these methods do not verify if the underlying value corresponds to `FunctionReferences` (21 bytes long) or `Addresses` (20 bytes). Consequently, if an incorrect type is used, information may be lost and the returned value may be incorrect.

Open the report
LowNon-Reverting Execution Hooks In Case Of No `functionId` MatchAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The reference implementation provides only one instance of a pre-execution hook in the `SessionKeyPermissionsPlugin`. It implements one `functionId` `PRE_EXECUTION_HOOK_UPDATE_LIMITS` that, if provided in the `functionId` parameter, will lead to the `_updateLimitsPreExec()` hook being executed. Notably, the pre-execution hook does not revert if the provided parameter does not match the `PRE_EXECUTION_HOOK_UPDATE_LIMITS` value and instead returns an empty string. This is in contrast to the implemented (pre-)userop-validations and (pre-)runtime validations of the codebase that revert in case no implemented `functionId` is provided. In the current state of EIP-6900, there is no requirement listed for pre-execution hooks to revert in such a scenario; it only states that “To indicate the entire call should revert, the function MUST revert.”, which can also be found in the same wording in the comments for the (pre-)runtime validation functions. A malicious plugin could seem like it integrates with a plugin's (pre-) execution hook but actually does not because it provides a non-matching functionId in its `ManifestFunction` definition.

Open the report
LowOwnership Can Be RenouncedAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

If the owner renounces their ownership, the following contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed, which would remove the possibility of maintaining and withdrawing the factory's stake.

Open the report
LowPlugins May Add the `IPlugin` Interface to MSCAAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

A plugin can add the `IPlugin` interface in the `interfaceIds[]` array in its manifest. As a result, the `IPlugin` interface will be added to the `supportedInterfaces` of the MSCA. This would cause any future self-calls via `execute()` or `executeBatch()` to be blocked since the MSCA will check whether the call target supports the `IPlugin` interface and, if so, reverts the call. It could also technically enable another possibility of self-calls, as outlined in MSCA-3.

Open the report
LowPotential EVM Compatibility Issue in Solidity Version >=0.8.21Alchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `foundry.toml` configuration file shows that the MSCA code is compiled using version `0.8.21` of the Solidity compiler. By default, this compiler version uses the `Shanghai` version of the EVM. As a result, the contract bytecode may contain `PUSH0` opcodes, which some EVM chains, like **[Arbitrum](https://docs.arbitrum.io/solidity-support)** and **[Optimism](https://community.optimism.io/docs/developers/build/differences/)**, don't support. This difference could cause the MSCA's deployment or execution to fail on these chains due to the use of an unsupported opcode.

Open the report
LowPotential Reentrancy Issue During Plugin UninstallationAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `_uninstallPlugin()` function in `PluginManagerInternals` performs a series of actions to remove the storage data corresponding to the plugin being uninstalled. Near the end of the function, the `onHookUnapply()` call is invoked on all providing plugins of the injected hooks, then the `pluginData` structure is deleted, followed by the `onUninstall()` call to the plugin being uninstalled. However, this pattern does not follow the best practice of reentrancy protection. During the `onHookUnapply()`, the to-be-installed plugin's `canSpendNativeToken`, `manifestHash`, and `dependencies` still exist in the storage, which may cause inconsistencies if these data are accessed (e.g., being used as a dependency since the manifest hash still exists).

Open the report
LowSeparation of Key Storage Between `SessionKeyPermissionsPlugin` and `SessionKeyPlugin` Leads to ComplicationsAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

`SessionKeyPermissionsPlugin` and `SessionKeyPlugin` are supposed to maintain a database of the keys that are used through the plugin system. These 2 datasets of keys are supposed to match up; otherwise, it leads to inconsistent states between the 2 plugins. When one calls `registerKey()` and hence registers it, the condition of having the same key registered in the `SessionKeyPlugin` is not enforced. So if a user already has a key registered with both plugins, they could pass the user op validation function; one then can register random keys inside `SessionKeyPermissionsPlugin`, which could not be used as they are not registered with the other plugin. A similar but different issue occurs if a user uninstalls the `SessionKeyPermissionsPlugin` and reinstalls it at a later point without removing any keys `SessionKeyPlugin`, all the session keys that were present at the time of uninstallation of the `SessionKeyPermissionsPlugin` would be re-instantiated with the same permissions. This is because in the `onUninstall()` callback, the storage slots associated with the session keys are not reset. However, the `SessionKeyPermissionsPlugin` itself does not have access to an iterateable list of registered session keys from which the storage slots would be derived because that data is stored in the `SessionKeyPlugin ` This is a side effect of having 2 coexisting data structures for storing the key values.

Open the report
Show 4 more
LowSession Key ID Can Be Overwritten During Key RotationAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

The `SessionKeyPermissionsPlugin.rotateKey()` function allows the owner to assign a `keyId` to a new session key. However, it does not ensure that the new session key, which can be already assigned to an existing `keyId`, is not in use. If so, the existing `keyId` will be overwritten, and this action is irreversible.

Open the report
LowUse of Solidity `transfer()` FunctionAlchemy Modular Account · Quantstamp · 30 Jan 2024Fixed

Solidity's `transfer()` function forwards a fixed amount of 2300 gas to the recipient when transferring native tokens to them, which causes several limitations if the recipient is a contract. First, the limited amount of gas prevents the contract from executing more complicated logic (e.g., external calls) in its `fallback()` or `receive()` function. Second, the gas costs of each opcode are subject to change; therefore, it is not guaranteed that currently successful transfers will not fail in the future. The `withdraw()` functions in the listed contracts use `.transfer()` to transfer native tokens from the factory contracts.

Open the report
LowImprovements for the `CustomSlotInitializable` ContractAlchemy Light Account · Quantstamp · 27 Sep 2023Fixed

A recent version of OpenZeppelin's `Initializable` contract introduced an update to the definition of the `InitializableStorage` structure. The `_initialized` field was modified to be of type `uint64`. As a result, contracts inheriting from `Initializable` can now be initialized or re-initialized up to a maximum of `2 ** 64` times. However, the `CustomSlotInitializable` contract currently defines the `_initialized` field as type `uint8`, resulting in a discrepancy with OpenZeppelin's contract implementation.

Open the report
LowMissing Input ValidationAlchemy Light Account · Quantstamp · 27 Sep 2023Fixed

It is important to validate inputs, even if they only come from trusted addresses, to avoid human error. Here is a non-exhaustive list of input validation checks that should be added: 1. `LightAccount._transferOwnership()` should check that `newOwner != oldOwner`. 2. `LightAccount._initialize()` should check that `anOwner` is not the zero address.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="alchemy-erc"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

Something 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