Platform: Code4rena
Start Date: 14/03/2024
Pot Size: $49,000 USDC
Total HM: 3
Participants: 51
Period: 7 days
Judge: 3docSec
Id: 350
League: ETH
Rank: 34/51
Findings: 2
Award: $28.23
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xmystery
Also found by: 0xbrett8571, 0xhacksmithh, 7ashraf, Bigsam, Circolors, IceBear, Jorgect, Koala, Limbooo, SBSecurity, Tigerfrake, ZanyBonzy, aycozynfada, cheatc0d3, cryptphi, d3e4, doublespending, foxb868, gpersoon, imare, jesjupyter, lsaudit, robriks, shealtielanz, y4y
6.9492 USDC - $6.95
Count | Title |
---|---|
[L-01] | Upgrade to EntryPoint 0.7 |
[L-02] | _initializeOwners is not capped at reasonable bounds |
[L-03] | executeBatch is not capped at reasonable bounds |
Total Low Risk Issues | 3 |
---|
Count | Title |
---|---|
[NC-01] | Pass only nonce to _validateRequest |
[NC-02] | MultiOwnable contract need to be marked abstract |
[NC-03] | Wallets with same owners and same nonce can be deployed |
[NC-04] | Do not explicitly compare bool variables to bool expression |
[NC-05] | Typos |
Total Non-Critical Issues | 5 |
---|
Issue Description: CoinbaseSmartWallet uses EntryPoint 0.6, although EntryPoint 0.7 was released in the past months. EntryPoint 0.7 introduce more gas optimization, security and other interesting enhancements.
Can check here: https://blog.smart-contracts-developer.com/entrypoint-v0-7-0-the-new-era-of-account-abstraction-854f9f82912e
Recommendation: Upgrade the CoinbaseSmartWallet to use EntryPoint 0.7
_initializeOwners
is not capped at reasonable boundsIssue Description: _initializeOwners
is not capped and if the array is big can result in out of gas.
function _initializeOwners(bytes[] memory owners) internal virtual { for (uint256 i; i < owners.length; i++) { if (owners[i].length != 32 && owners[i].length != 64) { revert InvalidOwnerBytesLength(owners[i]); } if (owners[i].length == 32 && uint256(bytes32(owners[i])) > type(uint160).max) { revert InvalidEthereumAddressOwner(owners[i]); } console.log(_getMultiOwnableStorage().nextOwnerIndex); _addOwnerAtIndex(owners[i], _getMultiOwnableStorage().nextOwnerIndex++); } }
Recommendation: Cap the function at reasonable bounds, since then can add owners and always can add if miss someone at initialization.
executeBatch
is not capped at reasonable boundsIssue Description: executeBatch
is not capped and if the array of Calls
is big can result in out of gas.
function executeBatch(Call[] calldata calls) public payable virtual onlyEntryPointOrOwner { for (uint256 i; i < calls.length;) { _call(calls[i].target, calls[i].value, calls[i].data); unchecked { ++i; } } }
Recommendation: Cap the function at reasonable bounds.
_validateRequest
Issue Description: In MagicSpend::_validateRequest
only the nonce from WithdrawRequest
was used.
function _validateRequest(address account, WithdrawRequest memory withdrawRequest) internal { if (_nonceUsed[withdrawRequest.nonce][account]) { revert InvalidNonce(withdrawRequest.nonce); } _nonceUsed[withdrawRequest.nonce][account] = true; // This is emitted ahead of fund transfer, but allows a consolidated code path emit MagicSpendWithdrawal(account, withdrawRequest.asset, withdrawRequest.amount, withdrawRequest.nonce); }
Recommendation: In validatePaymasterUserOp()
and withdraw()
pass only nonce to _validateRequest
and move the event in the upper function.
Issue Description: MultiOwnable
contract is inherited from CoinbaseSmartWallet
and all its functions are used there as inherited. No one will use MultiOwnable
independently.
contract MultiOwnable {
Recommendation: Mark MultiOwnable
abstract.
Issue Description: When deploy wallets, owners and nonce is passed. Nonce is used to allow multiple accounts with same owners to be deployed, because its used to generate the salt.
function createAccount(bytes[] calldata owners, uint256 nonce)
But if reorder the owners, another wallet can be deployed with same nonce, since when generate the salt, owners and nonce is used.
function _getSalt(bytes[] calldata owners, uint256 nonce) internal pure returns (bytes32 salt) { salt = keccak256(abi.encode(owners, nonce)); }
Recommendation: Change the way salt is generated or limit to pass the nonce from the deployer and user auto-incrementing nonce.
Issue Description: In createAccount
there is check if the account is already deployed and if so, it doesn’t deploy it, but just return the address. The if statement explicitly compare the alreadyDeployed
with false
.
if (alreadyDeployed == false) { // @audit - !alreadyDeployed account.initialize(owners); }
Recommendation: Remove == false
, and change to if (!alreadyDeployed)
.
Issue Description: There are several typos in the codebase:
MultiOwner.sol
bytes32 private constant MUTLI_OWNABLE_STORAGE_LOCATION = 0x97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f00; // @audit - MUTLI -> MULTI
/// @notice Checks if the sender is an owner of this contract or the contract itself. /// /// @dev Revert if the sender is not an owner fo the contract itself. // @audit - typo | fo -> of function _checkOwner() internal view virtual { if (isOwnerAddress(msg.sender) || (msg.sender == address(this))) { return; } revert Unauthorized(); }
ERC1271.sol
/// @dev To prevent the same signature from being validated on different accounts owned by the samer signer, // @audit - samer -> same
#0 - raymondfam
2024-03-23T00:29:39Z
3 L and 5 NC.
#1 - c4-pre-sort
2024-03-23T00:29:44Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-27T13:43:01Z
3docSec marked the issue as grade-b
🌟 Selected for report: roguereggiant
Also found by: 0xbrett8571, 0xepley, Circolors, JCK, JcFichtner, LinKenji, MSK, Myd, SAQ, SBSecurity, albahaca, cheatc0d3, clara, emerald7017, fouzantanveer, foxb868, hunter_w3b, kaveyjoe, popeye, unique
21.2754 USDC - $21.28
Coinbase Smart Wallet contracts are focused mainly on ERC4337
and making it compliant with the Coinbase infrastructure. With his custom Paymaster
implementation, the flow where sponsoring transactions from the executor’s off-chain wallet is already possible. The end goal of the product is to completely remove the need for EOAs, thus attracting non-technical users without exposing them to the underlying complexity. WebAuthnSol
is a crucial contract validating the signatures provided off-chain by passkey owners (passwords, biometrics, and PINs).
Key contracts of Coinbase Smart Wallet for this audit are:
CoinbaseSmartWallet
: ERC4337-compliant smart wallet supporting multiple owners and two types of signaturesCoinbaseSmartWalletFactory
: ERC4337-compliant smart wallet factory, used to deploy CoinbaseSmartWallet
deterministically using the UUPS pattern.WebAuthn
: Library for verifying WebAuthn Authentication assertions (off-chain chain biometric data validation).MagicSpend
: ERC4337-compliant Paymaster contract, used to sponsor user transactions from Coinbase
wallet.We can observe 3 main parts in the Coinbase Smart Wallet system:
CoinbaseSmartWallet
- Constructed from:
MultiOwnable
- Contract that makes wallet to support multiple owners and their management, also ensures that storage is consistent throughout the upgrades, follows ERC-7201
.UUPSUpgradeable
- Solady
implementation contract makes it possible to deploy many instances in a gas-efficient manner and upgrade the deployable implementation when needed.Receiver
- Solady
fallback extension to support token transfers with callbacks (ERC721, ERC1155).ERC1271
- Signature validation contract, defining the domain and version of the contract to prevent signature replays.Is a smart wallet implementation with added functionality to have repayable admin transactions across different chains.
CoinbaseSmartWalletFactory
- Standard factory contract that can precompute address and deploy them through CREATE2
when needed.MagicSpend
- Paymaster implementation to sponsor smart wallet transactions and allowing users to deposit and withdraw from EntryPoint
.WebAuthn
- Biometric validation contract, used to verify and backup through various options (fingerprints, Face ID, password).Stage | Action | Details | Information |
---|---|---|---|
1 | Compile and Run Test | Installation | Easy setup, standard test execution, |
2 | Documentation review | Documentation | Provides general use-case details as well as structures used and most important functions. |
3 | Contest details | Audit Details | Contains simple contract explanations, and external helper links. Gives additional context and potential attack ideas. |
4 | Diagramming | Excalidraw | Drawing diagrams through the entire process of codebase evaluation. |
5 | Test Suits | Tests | Consisting mainly of happy path tests, without going beyond the code and testing complex scenarios. |
6 | Manual Code Review | Scope | Reviewed all the contracts in scope. |
7 | Special focus on Areas of Concern | Areas of concern | Observing the known issues and bot report. |
Developers did a great job, maximally sticking to the EIP4337, without introducing unnecessary complexity. Additions, such as multi-owners and signatures without chainId
are well implemented, using assembly
where possible. EntryPoint
address is hardcoded, but this does not pose any problem since the wallet is upgradeable.
All access modifiers are applied properly, owner management is as described. Signatures cannot be replayed, containing all the crucial information hashed, only exception is for admins.
Notes for improvement:
address(0)
with disableInitializer()
in the constructor as it is more widely used:constructor() { // Implementation should not be initializable (does not affect proxies that use their storage). bytes[] memory owners = new bytes[](1); owners[0] = abi.encode(address(0)); _initializeOwners(owners); //disableInitializer() }
bytes32 private constant MUTLI_OWNABLE_STORAGE_LOCATION //MULTI
canSkipChainIdValidation
to return the result without explicitly use true/false.MultiOwner
can be made abstract since there is no purpose to be deployed separately.EntryPoint
can be added instead of relying on users to call them on behalf of the smart wallet.executeBatch
is not capped to reasonable amount of transactions potentially leading to out of gas exceptions.Standard implementation of ERC4337 factory, has all the needed functions to precompute and deploy the wallet when needed, as well as support just in time ETH transfers for new wallets.
Salt creation can be more specific as now same nonce and different owners combination will deploy new wallet every time, eventually misleading non-technical users.
Notes for improvement:
Simple paymaster implementation, contains all the needed functions to operate effectively and integrate with EntryPoint
, paying the smart wallet transactions.
Notes for improvement:
asset
from the WithdrawRequest
, since only ETH is supported as payment token for now.1:1 with Daimo’s implementation, with some modified steps. Following the official w3
documentation.
Notes for improvement:
verify()
remove step 17 as the requireUV
is always passed as false and no need to check for the user verification flag.// 17. If user verification is required for this assertion, verify that the User Verified bit of the flags in authData is set. if (requireUV && (webAuthnAuth.authenticatorData[32] & AUTH_DATA_FLAGS_UV) != AUTH_DATA_FLAGS_UV) { return false; }
We consider the quality of Coinbase Smart Wallet
codebase as a exceptional, without introducing unnecessary complexity and error prone code.
Contracts strictly follow the desired standards (4337, 1271, 712).
Unique feature represented by WebAuthn
and FCL
, used to solve the problem of the signatures is hard to be understood but on the other hand, there is no way to be changed due to the increased gas cost.
Tests are not well developed, with validating mainly the happy path and not testing for use cases that might cause harm to the contracts in scope. They are modular with a good structure and setup, but due to not utilizing CoinbaseSmartWalletFactory
in SmartWalletTestBase
to deploy accounts, tests for UUPS upgrades were failing, because of using a concrete implementation instead of a proxy.
Codebase Quality Categories | Comments |
---|---|
Code Maintainability and Reliability | The Coinbase Smart Wallet contracts demonstrate good maintainability through modular structure, consistent naming, and efficient use of libraries. It also prioritizes reliability by handling errors, and conducting security checks. However, for enhanced reliability, consider professional security audits and documentation improvements. Regular updates are crucial in the evolving space. |
Code Comments | Comments provided high-level context for certain constructs, lower-level logic flows and variables explained within the code itself. As an auditor, it is easier to analyze code sections with the provided information. |
Documentation | Technical documentation is provided on the Coinbase Smart Wallet page and in their Github and a decent amount of attack ideas, as well as, main invariants are on the contest page. |
Code Structure and Formatting | The codebase contracts are well-structured and formatted. Their consist of consistent comments and notes making the review easier. |
The overall test coverage is high, most of them covering only happy path scenarios. Not all the functions were tested. For example upgradeAndCall
called from EntryPoint
contract was missing.
It was failing because onlyProxy
modifier was reverting since the account
was not deployed through factory, test was indicating passed but when the verbosity was increased it appeared.
Consider updating the setUp
function in SmartWalletTestBase
in order to properly test the functionality and avoid scenarios where concrete implementations work, but proxies don’t.
function setUp() public virtual { CoinbaseSmartWalletFactory factory = new CoinbaseSmartWalletFactory(address(new CoinbaseSmartWallet())); owners.push(abi.encode(signer)); owners.push(passkeyOwner); account = factory.createAccount(owners, 1); }
It will be beneficial for the project if end-to-end tests are introduced validating example use cases of the wallet-paymaster integration.
Regarding the centralization risks involved in the CoinbaseSmartWallet
contract, it is important to mention that owners can remove each other freely, potentially leading to scenarios where if one of them gets compromised it can handily remove other owners and steal the assets held in the wallet.
Functions allowed to have replayable signatures are well protected with proper access modifiers applied.
Important _authorizeUpgrade
function is overriden with applied onlyOwner
modifier, but the risk of a one of the admins to upgrade the wallet to a non-ERC1967 compliant contract is presented which can have serious security implications.
Another inconsistency can be the non-sequential nonce tracking in MagicSpend
allowing bundlers to execute multiple userOp
transactions of the same sender in their desired order, possibly extracting value from them.
Notes for improvements:
upgradeToAndCall
function from UUPSUpgradeable
Severity | Findings |
---|---|
High | 0 |
Medium | 1 |
Low/NC | 8 |
Learned about:
In general, CoinbaseSmartWallet
protocol presents a well-designed architecture. It exhibits, such as modularity, entity separation, also strictly following ERCs and we believe the team has done a good job regarding the code. However, there are areas for improvement, including tests, and some minor inconsistencies, mentioned in the Architecture Recommendations section. Systemic and Centralization risks do not pose any significant impact to the system.
Main code recommendations include simplifying functions, fixing typos, adding functions to ease the usage of the contracts.
It is also highly recommended that the team continues to invest in security measures such as mitigation reviews, audits, and bug bounty programs to maintain the security and reliability of the project.
25 hours
#0 - c4-pre-sort
2024-03-22T21:12:26Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2024-03-27T12:42:53Z
3docSec marked the issue as grade-b