Platform: Code4rena
Start Date: 04/01/2023
Pot Size: $60,500 USDC
Total HM: 15
Participants: 105
Period: 5 days
Judge: gzeon
Total Solo HM: 1
Id: 200
League: ETH
Rank: 80/105
Findings: 1
Award: $36.50
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0xAgro, 0xdeadbeef0x, 0xhacksmithh, 2997ms, Atarpara, Bnke0x0, Diana, HE1M, IllIllI, Josiah, Kalzak, Lirios, MalfurionWhitehat, MyFDsYours, Raiders, RaymondFam, Rolezn, SaharDevep, Sathish9098, Udsen, Viktor_Cortess, adriro, ast3ros, betweenETHlines, btk, chaduke, chrisdior4, cryptostellar5, csanuragjain, giovannidisiena, gz627, hl_, horsefacts, joestakey, juancito, ladboy233, lukris02, nadin, oyc_109, pauliax, peanuts, prady, sorrynotsorry, zaskoh
36.5015 USDC - $36.50
Issue | |
---|---|
L-01 | SmartAccountFactory.deployWallet does not fully comply with EIP-4337 |
L-02 | Wrong error string |
L-03 | The "no smart contract" check in VerifyingSingletonPaymaster.depositFor() can be bypassed |
Issue | |
---|---|
NC-01 | Typos |
NC-02 | Tautologies |
NC-03 | Redundant check |
NC-04 | Open TODOs |
SmartAccountFactory.deployWallet
does not fully comply with EIP-4337This function uses CREATE to deploy the account.
As per EIP-4337:
The wallet creation itself is done by a “factory” contract, with wallet-specific data. The factory is expected to use CREATE2 (not CREATE) to create the wallet, so that the order of creation of wallets doesn’t interfere with the generated addresses
Consider removing this function, as deployCounterFactualWallet()
already allows creation of wallets.
There is a wrong error string in SmartAccount.init()
. This can be misleading if there is a zero _handler
passed to SmartAccountFactory.deployCounterFactualWallet
, as the error wrongfully states the problem is with entryPoint
.
171: require(_handler != address(0), "Invalid Entrypoint");
This should be:
-171: require(_handler != address(0), "Invalid Entrypoint"); +171: require(_handler != address(0), "Invalid Handler");
VerifyingSingletonPaymaster.depositFor()
can be bypassedVerifyingSingletonPaymaster.depositFor()
has a check to ensure paymasterId
is not a smart contract.
This check can be circumvented easily as it checks account.code.length > 0
:
For instance, by calling VerifyingSingletonPaymaster.depositFor()
with paymasterId
as a not-yet-existing contract (ie a contract deployed using CREATE2
, whose address can be known in advance)
48: function depositFor(address paymasterId) public payable { 49: require(!Address.isContract(paymasterId), "Paymaster Id can not be smart contract address"); 50: require(paymasterId != address(0), "Paymaster Id can not be zero address"); 51: paymasterIdBalances[paymasterId] += msg.value; 52: entryPoint.depositTo{value : msg.value}(address(this)); 53: }
There is no simple mitigation here, but you can perhaps consider depositFor()
automatically setting paymasterId
as msg.sender
74: * @notice Throws if the sender is not an the owner. //@audit - replace with : Throws if the sender is not the owner
382: /// @param targetTxGas Fas that should be used for the safe transaction. //@audit - replace with: Gas that should be used for the safe transaction
In SmartAccount
, execute()
and executeBatch()
have the onlyOwner
modifier, meaning they can only be called by the owner
.
76: modifier onlyOwner { 77: require(msg.sender == owner, "Smart Account:: Sender is not authorized"); 78: _; 79: }
The _requireFromEntryPointOrOwner()
call is hence redundant and should be removed.
460: function execute(address dest, uint value, bytes calldata func) external onlyOwner{ 461: _requireFromEntryPointOrOwner(); 462: _call(dest, value, func); 463: }
465: function executeBatch(address[] calldata dest, bytes[] calldata func) external onlyOwner{ 466: _requireFromEntryPointOrOwner(); 467: require(dest.length == func.length, "wrong array lengths"); 468: for (uint i = 0; i < dest.length;) { 469: _call(dest[i], 0, func[i]); 470: unchecked { 471: ++i; 472: } 473: } 474: }
In SmartAccount.init()
, there is a check that _handler != address(0)
before calling internalSetFallbackHandler(_handler)
here.
This check is redundant as a previous check ensures _handler != address(0)
166: function init(address _owner, address _entryPointAddress, address _handler) public override initializer { 167: require(owner == address(0), "Already initialized"); 168: require(address(_entryPoint) == address(0), "Already initialized"); 169: require(_owner != address(0),"Invalid owner"); 170: require(_entryPointAddress != address(0), "Invalid Entrypoint"); 171: require(_handler != address(0), "Invalid Entrypoint"); 172: owner = _owner; 173: _entryPoint = IEntryPoint(payable(_entryPointAddress)); 174: if (_handler != address(0)) internalSetFallbackHandler(_handler); 175: setupModules(address(0), bytes("")); 176: }
Change to
-174: if (_handler != address(0)) internalSetFallbackHandler(_handler); +174: internalSetFallbackHandler(_handler);
255: // TODO: copy logic of gasPrice?
#0 - c4-judge
2023-01-22T15:50:24Z
gzeon-c4 marked the issue as grade-b
#1 - c4-sponsor
2023-02-09T12:24:31Z
livingrockrises marked the issue as sponsor confirmed