Account Abstraction (ERC-4337) Remediation
Overview
ERC-4337 introduces a separate mempool for user operations whose validation phase runs off-chain in bundlers. A smart account that uses banned opcodes, reads unrestricted storage, or verifies signatures incorrectly during validateUserOp is open to mempool griefing and to replay or unauthorized execution. The fix restricts validation to associated storage, expresses time bounds through the return value, and recovers signatures against the EntryPoint-provided userOpHash.
Related Detector: Account Abstraction (ERC-4337)
Recommended Fix
Before (Vulnerable)
contract VulnerableAccount {
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256) {
// Reads external contract storage -- violates ERC-4337 storage rules
require(registry.isAllowed(userOp.sender), "Not allowed");
// Signature check vulnerable to malleability
bytes32 hash = keccak256(abi.encode(userOp));
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Bad sig");
return 0; // No time validity bounds
}
}
After (Fixed)
contract SafeAccount {
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256) {
// Only access associated storage (own slots, EntryPoint deposit)
require(msg.sender == ENTRY_POINT, "Not EntryPoint");
address signer = ECDSA.recover(userOpHash, userOp.signature);
bool valid = signer == owner;
if (missingAccountFunds > 0) {
payable(msg.sender).call{value: missingAccountFunds}("");
}
return _packValidationData(!valid, validUntil, validAfter);
}
}
Recovering against userOpHash gives chain and account binding for free, the EntryPoint check rejects spoofed callers, and packing the time bounds into validationData keeps validation deterministic for bundler simulation.
Alternative Mitigations
- Use an aggregator for shared signature schemes so each account does not re-implement verification.
- Run paymaster checks (
validatePaymasterUserOp) under the same opcode and storage restrictions. - Stake the account factory or paymaster when broader storage access is unavoidable, following the validation scope rules.
Common Mistakes
- Returning
0regardless of signature validity, which signals success to the EntryPoint. - Reading
TIMESTAMP,BLOCKHASH, orGASPRICEduring validation instead of returning time bounds. - Re-deriving a hash from operation fields and dropping the chain ID, enabling cross-chain replay.