
Loading...
Loading
Loading...
LoadingLoading audit report...

WalletGuard.ai, powered by Gestalt Labs
Findings selected for deep verification. Where possible we generated a Solidity proof-of-concept and executed it against a forked mainnet.
src/MagicSpend/MagicSpend.solFunction: postOpLines: 130-137The analyzed contract is a smart contract wallet system combining CoinbaseSmartWallet (a UUPS-upgradeable ERC-4337 account with WebAuthn and multi-owner support), MagicSpend (an ERC-4337 paymaster), MultiOwnable, and supporting cryptographic libraries. The analysis identified 0 critical, 4 high, 6 medium, 5 low, and 3 informational findings after deduplication and scope gating. The single most dangerous pattern is the inclusion of upgradeToAndCall in the cross-chain replay whitelist, which allows a signed upgrade operation from one chain to be replayed on every other chain where the wallet is deployed, potentially pointing the wallet to a malicious or nonexistent implementation without the owner's consent. Overall, the contract system carries moderate-to-high risk; several logic ordering errors in MagicSpend and the upgrade replay exposure require remediation before deployment at scale.
When a wallet owner upgrades their wallet on one blockchain (say Ethereum mainnet), that exact upgrade transaction can be replayed by anyone on every other chain where the same wallet address exists. If the upgrade target contract does not exist or contains malicious code on another chain, the wallet on that chain can be hijacked or bricked without the owner ever authorizing it there.
When a user tries to withdraw funds using MagicSpend, the contract records that a one-time withdrawal code (nonce) has been used before it checks whether the withdrawal request has expired. If the request is expired or if an attacker submits it after expiry, the code is permanently burned and the user gets nothing, forcing them to request a brand-new withdrawal code from the operator.
The WebAuthn signature verification code reads the 33rd byte of a security blob without first confirming the blob is at least 33 bytes long. An attacker can send a deliberately short blob that causes the wallet's signature check to crash rather than simply fail, which could freeze the wallet's ability to process transactions entirely.
Finding 1 and Finding 4 both describe the same core vulnerability from different analytical angles: upgradeToAndCall is whitelisted in canSkipChainIdValidation. An attacker monitors an upgrade UserOperation signed on mainnet (where the target implementation is legitimate), then replays the same signed operation on a secondary chain (Base, Optimism, etc.) where the wallet address exists but the implementation address either does not exist, is uninitialized, or has been deployed by the attacker. Because executeWithoutChainIdValidation accepts the mainnet-signed operation on any chain, the wallet on the secondary chain is upgraded to the attacker-controlled address, granting full control without any additional authorization.
Finding 15 identifies that WebAuthn.verify() panics rather than returning false when authenticatorData is fewer than 33 bytes. Finding 2 identifies that challengeIndex and typeIndex are not bounds-checked. Together, a caller submitting a malformed WebAuthnAuth struct (short authenticatorData combined with crafted indices) causes _validateSignature in CoinbaseSmartWallet to revert entirely rather than returning the ERC-4337 failure sentinel (1). This means the EntryPoint receives a revert rather than a signature failure code, which can DoS the account's ability to process UserOperations through the 4337 flow.
Finding 5/9/11 (nonce consumed before expiry check in withdraw) and Finding 3 (nonce consumed before signature validation in validatePaymasterUserOp) collectively mean that MagicSpend's nonce management is vulnerable to griefing on both code paths. An attacker who observes a pending withdraw transaction in the mempool can front-run it after the expiry timestamp, burning the nonce and preventing the legitimate withdrawal. In the paymaster path, a bundler submitting an operation with a bad signature still consumes the nonce. The combination makes it straightforward to systematically invalidate a user's signed withdrawal requests.
| Agent | Status | Findings | Severity | Confidence | Duration | Coverage |
|---|---|---|---|---|---|---|
| reentrancy | success | 3 | 67% | 44.2s | Classic reentrancy in MagicSpend withdraw/withdrawGasExcess/postOp, Cross-function reentrancy between validatePaymasterUserOp and postOp, ERC-4337 paymaster validation logic and state management, Cross-chain replay via executeWithoutChainIdValidation and REPLAYABLE_NONCE_KEY, WebAuthn signature verification logic including index bounds and challenge validation, MultiOwnable owner management and access control, UUPS upgrade authorization in CoinbaseSmartWallet._authorizeUpgrade, ERC-1271 signature validation and replay protection, FCL cryptographic library for correctness issues, CoinbaseSmartWalletFactory deterministic deployment, Nonce management in MagicSpend to prevent replay, ETH transfer safety via SafeTransferLib, ERC-777 callback risks (not applicable - uses ETH only), Read-only reentrancy on view functions | |
| access control | success | 5 | 1H2M1L | 75% | 1.0m | MagicSpend access control: onlyEntryPoint, onlyOwner modifiers on all admin functions, MagicSpend signature validation: getHash includes chainId, address(this), nonce, expiry, MagicSpend nonce management and replay protection, MagicSpend withdraw() ordering of validation steps vs state changes, MagicSpend postOp() gas accounting and potential over/underpayment, CoinbaseSmartWallet UUPS upgrade authorization (_authorizeUpgrade), CoinbaseSmartWallet initialize() protection against re-initialization, CoinbaseSmartWallet constructor disableInitializers pattern, CoinbaseSmartWallet cross-chain replay via executeWithoutChainIdValidation, CoinbaseSmartWallet canSkipChainIdValidation whitelist contents, MultiOwnable access control on addOwner/removeOwner, MultiOwnable owner self-grant/removal edge cases, ERC1271 replay-safe hash construction, domain separator, WebAuthn signature verification and malleability guard (s > P256_N_DIV_2 check), WebAuthn challengeIndex/typeIndex user-supplied offset validation, WebAuthn fallback from RIP-7212 precompile to FCL, FCL ecrecover return value checks (not applicable, uses ecZZ not ecrecover), CoinbaseSmartWalletFactory createAccount deterministic deployment, delegatecall usage in _call() - targets are not user-controlled without owner auth, tx.origin authorization usage (none found), Reentrancy patterns in withdraw flows |
| economic | success | 6 | 1L | 68% | 1.4m | Flash loan attack vectors - no token balance pricing dependencies found, Oracle manipulation - no price feed reads found, Governance attacks - multi-owner management, cross-chain replay for owner operations, MEV/sandwich exposure - no swap operations found, Reward/supply manipulation - no staking rewards found, ERC4337 paymaster logic in MagicSpend - balance checks, nonce management, postOp mode handling, WebAuthn signature verification - challenge/type index validation, malleability guard, FCL cryptographic library - memory safety, ECDSA verification, UUPS upgrade authorization - _authorizeUpgrade access control, MultiOwnable access control - owner addition/removal, index management, Cross-chain replay protection - executeWithoutChainIdValidation, REPLAYABLE_NONCE_KEY, ERC1271 signature validation - replay-safe hash construction, Reentrancy - CEI pattern in withdraw functions, Integer overflow/underflow - Solidity 0.8 checked arithmetic used throughout, Signature validation correctness in isValidWithdrawSignature |
| logic validation | success | 6 | 3M2L | 72% | 1.1m | MagicSpend input validation (withdraw request nonce, expiry, signature ordering), MagicSpend postOp assert() vs require() gas behavior, MagicSpend validatePaymasterUserOp balance and withdrawal accounting, CoinbaseSmartWallet UUPS upgrade authorization, CoinbaseSmartWallet cross-chain replay via executeWithoutChainIdValidation, CoinbaseSmartWallet signature validation and owner index lookup, CoinbaseSmartWallet initialize() re-initialization guard, MultiOwnable owner management (add/remove) and storage layout, ERC1271 domain separator computation (uses block.chainid dynamically - no caching issue), WebAuthn challengeIndex and typeIndex caller-controlled offset validation, WebAuthn signature malleability guard (s > P256_N_DIV_2), FCL ECDSA verification arithmetic in unchecked blocks, CoinbaseSmartWalletFactory salt computation with abi.encode (no dynamic type hash collision since encode is used not encodePacked), Integer overflow/underflow in arithmetic operations, Reentrancy in MagicSpend ETH transfers, EIP-712 domain separator includes chainId and verifyingContract |
| code quality | success | 8 | 1L | 69% | 1.4m | ERC-4337 paymaster validation logic (MagicSpend), Cross-chain replay protection in CoinbaseSmartWallet, UUPS upgrade authorization, MultiOwnable access control and owner management, ERC-1271 signature validation, WebAuthn verification logic including typeIndex/challengeIndex manipulation, FCL ECDSA cryptographic library correctness, Nonce management and replay protection in MagicSpend, ETH transfer safety (SafeTransferLib usage), Memory safety in inline assembly (FCL library), ERC-1967 proxy storage slot compliance, Integer overflow/underflow in arithmetic operations, Reentrancy vectors in withdraw and postOp flows, Signature malleability protection in WebAuthn (P256_N_DIV_2 guard) |
| compiler bugs | success | 4 | 2H | 75% | 56.7s | Reentrancy in MagicSpend withdraw, withdrawGasExcess, and postOp flows, ERC4337 paymaster validation logic and nonce handling, Cross-chain replay protection via REPLAYABLE_NONCE_KEY and executeWithoutChainIdValidation, canSkipChainIdValidation whitelist analysis, WebAuthn signature verification logic including challengeIndex/typeIndex manipulation, UUPS upgrade authorization (_authorizeUpgrade onlyOwner check), MultiOwnable access control and owner management, MagicSpend assert() vs revert() in postOp, FCL elliptic curve library correctness (curve order checks, point-on-curve validation), ERC1271 replay protection via replaySafeHash, CoinbaseSmartWalletFactory deterministic address derivation, Integer overflow/underflow in Solidity 0.8.x (protected by default), Compiler bug patterns for pragma versions used (0.8.23, ^0.8.4, ^0.8.0) |
| assembly safety | success | 7 | 1M | 72% | 1.5m | Full source codepoint-by-codepoint scan for non-ASCII characters (RTLO U+202E, zero-width joiners, Cyrillic homoglyphs) - none found, Assembly blocks in FCL.sol: FCL_nModInv, FCL_pModInv, ecZZ_mulmuladd_S_asm, ecZZ_Dbl, ecZZ_AddN - checked for free memory pointer handling, shift operation order, return vs leave, sstore in view context, Assembly blocks in CoinbaseSmartWallet.sol: payPrefund modifier, _call function, _validateSignature, implementation() - checked for return vs leave, delegatecall patterns, Assembly blocks in MultiOwnable.sol: _getMultiOwnableStorage - named slot pattern, correct ERC-7201 usage, MagicSpend.sol: nonce handling order, signature validation flow, reentrancy in withdraw/postOp, ETH transfer safety, CoinbaseSmartWallet.sol: REPLAYABLE_NONCE_KEY logic, canSkipChainIdValidation whitelist, UserOperation validation, ERC-1271 implementation, WebAuthn.sol: challengeIndex/typeIndex bounds, signature malleability guard, precompile fallback logic, ERC1271.sol: domain separator, replay-safe hash construction, MultiOwnable.sol: owner management, access control, CoinbaseSmartWalletFactory.sol: deterministic deployment, salt computation, Cross-function reentrancy between withdraw, withdrawGasExcess, and postOp, Keyword obfuscation scan for lookalike assembly/selfdestruct/delegatecall identifiers - none found, Yul shl/shr argument order in ecZZ_mulmuladd_S_asm assembly, delegatecall success check in all assembly blocks, staticcall usage with gas forwarding via not(0) |
| l2 specific | success | 7 | 2M1L | 76% | 1.5m | MagicSpend ERC-4337 paymaster validation flow (validatePaymasterUserOp, postOp), MagicSpend withdraw nonce consumption ordering relative to validation, MagicSpend signature validation and owner check (isValidWithdrawSignature), CoinbaseSmartWallet UUPS upgrade authorization (_authorizeUpgrade), CoinbaseSmartWallet cross-chain replay via executeWithoutChainIdValidation and canSkipChainIdValidation, CoinbaseSmartWallet signature validation (_validateSignature) for both ECDSA and WebAuthn owners, CoinbaseSmartWallet initialization protection (initialize, constructor), MultiOwnable access control (addOwnerAddress, addOwnerPublicKey, removeOwnerAtIndex), MultiOwnable storage layout (ERC-7201 namespaced storage), WebAuthn verify function - input validation, index bounds, signature malleability guard, FCL secp256r1 ECDSA verification library - curve point validation, inversion, ERC1271 replay-safe hash implementation - domain separator, chain ID inclusion, CoinbaseSmartWalletFactory deterministic deployment (createAccount, getAddress), L2 compatibility: block.chainid usage (dynamic, not hardcoded - correct), EntryPoint address hardcoding (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 - ERC-4337 v0.6 canonical), Reentrancy paths in MagicSpend (withdrawGasExcess, withdraw, postOp), Gas stipend usage in postOp ETH transfer (GAS_STIPEND_NO_STORAGE_WRITES) |
| upgrade | success | 6 | 68% | 1.2m | UUPS proxy pattern - _authorizeUpgrade access control, Initialization protection in CoinbaseSmartWallet constructor, Storage layout - MultiOwnable uses ERC-7201 namespaced storage (safe), Cross-chain replay protection in executeWithoutChainIdValidation, canSkipChainIdValidation whitelist including upgradeToAndCall, MagicSpend signature validation ordering and nonce consumption, MagicSpend ETH balance accounting across concurrent operations, WebAuthn user-controlled challengeIndex and typeIndex bounds, WebAuthn signature malleability guard (s > P256_N_DIV_2), FCL ECDSA verification correctness, ERC-1271 signature validation and replay protection, Access control on owner management functions, postOp assert() usage and gas implications, selfdestruct presence - not found, delegatecall to untrusted addresses - not found, Immutable variables in proxy implementation - implementation address in factory only |
This automated audit has inherent limitations. The following areas are not covered.
This report is an automated point-in-time assessment and does not guarantee protection against all possible attacks. It does not cover off-chain components, economic modeling, or business logic correctness unless explicitly noted. Changes to the contract after the audit commit are not reviewed. This is not financial or legal advice. WalletGuard, powered by Gestalt Labs, provides this analysis as-is with no warranty of completeness.
[](https://walletguard.ai/audit/4d4030dc-8992-4299-a7c5-48ed7eecface)
<a href="https://walletguard.ai/audit/4d4030dc-8992-4299-a7c5-48ed7eecface"> <img src="https://walletguard.ai/api/badge/4d4030dc-8992-4299-a7c5-48ed7eecface" alt="WalletGuard Audit Badge" /> </a>