Platform: Code4rena
Start Date: 08/11/2022
Pot Size: $60,500 USDC
Total HM: 6
Participants: 72
Period: 5 days
Judge: Picodes
Total Solo HM: 2
Id: 178
League: ETH
Rank: 22/72
Findings: 3
Award: $264.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
151.3257 USDC - $151.33
Detailed description of the impact of this finding.
The function execute()
in LooksRareAggregator
never checks the return value when transferring the ETH back to the user. As a result, the transaction will complete even though ETH has not been successfully transferred back to the user. The user will lose his ETH forever to the contract or to next user since there is no accounting for his ETH balance.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
First, function _returnETHIfAny()
will not revert when it fails
function _returnETHIfAny(address recipient) internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0) } } }
then in the following line, the return value has never been checked https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L109 As a result, even the transfer of ETH fails, the transaction will still proceed and completes. The user will lose his ETH in the contract or to other users forever as there is no accounting for his ETH balance in the account.
Remix
Reimplement function _returnETHIfAny
to revert when it fails and also return a false boolean value when it fails.
#0 - c4-judge
2022-11-19T10:22:38Z
Picodes marked the issue as duplicate of #241
#1 - c4-judge
2022-12-16T13:48:37Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2022-12-16T13:48:41Z
Picodes changed the severity to 2 (Med Risk)
77.2215 USDC - $77.22
Detailed description of the impact of this finding.
L108 in LooksRareAggregator
can be exploited to drain all ERC20 in the contract.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
A malicious user Eve can monitor the balances of ERC20 tokens in contract LooksRareAggregator
and if there are any, he will immediately call function
execute( TokenTransfer[] calldata tokenTransfers, ILooksRareAggregator.TradeData[] calldata tradeData, address recipient, bool isAtomic )
in contract ERC20EnabledLooksRareAggregator
, which will call execute()
in LooksRareAggregator
. Note that we do not need to transfer any ERC20 to the contract since there is no check of amount > 0, finally via line 108. Alice will make sure
all ERC20 tokens available in the contract will be listed in the argument TokenTransfer[]
. In this way, L108 will return all ERC20 tokens that the contract LooksRareAggregator
owns to the originator, attacker Alice.
Remix
We need to account for the balance for each ERC20 for each user in contract LooksRareAggregator
. In this way, we will never refund a user with ERC20 tokens that belong to another user.
#0 - c4-judge
2022-11-19T10:05:33Z
Picodes marked the issue as duplicate of #277
#1 - c4-judge
2022-12-16T13:53:45Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2022-12-16T13:53:50Z
Picodes changed the severity to 2 (Med Risk)
🌟 Selected for report: RaymondFam
Also found by: 0x1f8b, 0x52, 0xSmartContract, 0xc0ffEE, 0xhacksmithh, 8olidity, Awesome, BClabs, Bnke0x0, Chom, Deivitto, Hashlock, IllIllI, Josiah, KingNFT, Nyx, R2, ReyAdmirado, Rolezn, SamGMK, Sathish9098, SinceJuly, V_B, Vadis, Waze, a12jmx, adriro, ajtra, aphak5010, bearonbike, bin, brgltd, carlitox477, carrotsmuggler, cccz, ch0bu, chaduke, datapunk, delfin454000, erictee, fatherOfBlocks, fs0c, horsefacts, jayphbee, ktg, ladboy233, pashov, perseverancesuccess, rbserver, ret2basic, tnevler, zaskoh
36.3434 USDC - $36.34
QA1. Lock the most recent version of solidity 0.8.17consistently (currrently, some use 0.8.14, some use 0.8.17).
QA2. https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/proxies/SeaportProxy.sol#L88 https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/proxies/SeaportProxy.sol#L178
There is no need to implement the function _executeAtomicOrders
separately, instead one can easily add a revert statement into the catch{}
part in _executeNonAtomicOrders
to support the atomic case, in which we need to revert the whole transaction when the execution of one order fails as follows:
try{ } catch{ if(isAtomic) revert ExecuteAtomicOrdersFailure(); }
This will save much audit effort and code duplication. As a result, the fucntion _handleFees
can be eliminated as well.
QA3: Simply the function _executeSingleOrder()
as follows
function _executeSingleOrder( OrderTypes.TakerOrder memory takerBid, OrderTypes.MakerOrder memory makerAsk, address recipient, CollectionType collectionType, bool isAtomic ) private { try marketplace.matchAskWithTakerBidUsingETHAndWETH{value: takerBid.price}(takerBid, makerAsk) { _transferTokenToRecipient( collectionType, recipient, makerAsk.collection, makerAsk.tokenId, makerAsk.amount ); } catch { if(isAtomic) revert ExecuteSingleOrderFailure(); } }
QA3: https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/ERC20EnabledLooksRareAggregator.sol#L21
It will be helpful to make the contract pausible in case the owner of the LooksRareAggregator
contract is compromised.
QA3: the LooksRareAggregator
gives all power to ONE owner, although the owner could be a mult-sig contract, the management is still too centralized and risky. It is recommended to have a balance-and-check mechanism: for example, 1) setERC20EnabledLooksRareAggregator
is manged by one role; 2) the rescue functions are managed by a second role; and 3) those that are related to business, such as setFee() and market setup by a third role.
QA4: ERC20EnabledLooksRareAggregator
needs a whitelist mechanism for ERC20 tokens to avoid people use fake tokens to exchange for good tokens. One way of doing that is right before _pullERC20Tokens(tokenTransfers, msg.sender);
, call a function isWhiteListed(tokenTransfers)
in LooksRareAggregator
to verify.
QA5: the low level transfer functions at https://github.com/code-423n4/2022-11-looksrare/tree/main/contracts/lowLevelCallers have not checked if the token address is a valid smart contract. If there is no code at the target address, all transfers will still return a success return even though no operation has been performed.
QA6: https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/ReentrancyGuard.sol#L12
https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L51
The execute()
of the LookRareAggregator
is protected from reentrancy attack by the modifier nonReentrant
which relies on the settting and resetting of state variable _status
. Unfortunately, via delegate all in line 88, the callee contract might also change this state variable _status
. The reason is that in a delegatecall, both the the callee contract can only read and write to the state variables in the calling contract. In this case, these two contracts do not have the same state variable layout, and the _status
variable corresponds to the slot taken by marketplace
in proxy contracts, SeapotProxy
and LooksRareProxy
, which will be initialized to the corresponding market address. In this case, the modifier nonReentrant
will check the wrong status and its funcionality will be compromised.
Mitigation: make sure the calling contract and the callee contract via a delegatecal always have the same state variable layout, a common pattern to use is the AppStorage pattern:
https://eip2535diamonds.substack.com/p/understanding-delegatecall-and-how
QA7: the owner of LooksRareAggregator
should be prevented from calling the execute()
function in LooksRareAggregator
and in erc20EnabledLooksRareAggregator
due to its excessive power, in particular the power to call approve
on any address, currency and amount. This can be performed by inserting a check in the execute()
function as follows
if(msg.sender == owner) || originator == owner) revert NotcallableByOwner();
This should prevent many attacks in the case that the owner's account is compromised, an compromised owner with approve privilege and calling of proxy contract with delegatecall is very troubling.
QA8:
https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L171
marketplace
must be whitelisted, otherwise, this approve
function can approve any marketplace/address as the spender.
QA9: https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L44
erc20EnabledLooksRareAggregator
should be declared as immutable and maybe private as well
address private immutable erc20EnabledLooksRareAggregator;
#0 - c4-judge
2022-11-21T19:55:42Z
Picodes marked the issue as grade-b
#1 - 0xhiroshi
2022-11-22T17:34:59Z
QA1. All contracts with floating pragma come from our contracts-libs repo and they are meant to be shared by our other projects.
QA2. For atomic orders, we decided to go with a single fulfillAvailableAdvancedOrders
call instead of calling fulfillAdvancedOrder
n times as external calls are expensive.
QA3. _executeSingleOrder
-> If we follow this suggestion, it is cheaper when all the orders go through, but more expensive when the orders are partially filled. So it's good for low volume collections but not so good when it's the first day of a very hot collection. (TBD, @0xJurassicPunk wdyt?)
QA3. Pausable
-> we don't store any funds, if the contract is compromised we will just ask people to stop using it and revoke approvals
QA3. Centralization risk
-> we are not a DAO or a team with many signers
QA4. I am not following how fake ERC20 tokens can be exchanged for legit ERC20 tokens.
QA5. Ok, we will check the code size before making the transfer.
QA6. The proxies have no storage variables so there isn't a storage layout issue. The marketplace and aggregator variables are immutable so they are not written to slots.
QA7. I don't understand what the issue is, the report isn't being very clear.
#2 - c4-sponsor
2022-11-22T17:35:15Z
0xhiroshi requested judge review
#3 - 0xhiroshi
2022-12-12T23:02:42Z
@Picodes what is the edict of this report?