Platform: Code4rena
Start Date: 05/10/2022
Pot Size: $50,000 USDC
Total HM: 2
Participants: 80
Period: 5 days
Judge: GalloDaSballo
Id: 168
League: ETH
Rank: 23/80
Findings: 3
Award: $197.95
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: dipp
Also found by: 0x4non, 0x52, 0xRobocop, 0xc0ffEE, 8olidity, Ch_301, Jeiwan, Junnon, KIntern_NA, Lambda, M4TZ1P, MiloTruck, Nyx, PaludoX0, Ruhum, RustyRabbit, Soosh, TomJ, Trust, arcoun, aviggiano, bardamu, cryptonue, csanuragjain, d3e4, enckrish, exd0tpy, hansfriese, jayphbee, joestakey, ladboy233, minhquanym, minhtrng, nicobevi, obront, polymorphism, rokinot, romand, rotcivegaf, rvierdiiev, saian, serial-coder, trustindistrust, zzykxx
114.8239 USDC - $114.82
https://github.com/code-423n4/2022-10-blur/blob/main/contracts/matchingPolicies/StandardPolicyERC1155.sol#L33 https://github.com/code-423n4/2022-10-blur/blob/main/contracts/matchingPolicies/StandardPolicyERC1155.sol#L59
BlurExchange.execute
fetches the amount to transfer from the matching policies. StandardPolicyERC1155
doesn't take into account Order.amount
and always returns 1 for the amount. Due to this only 1 token is transferred. This results in buyer paying for N tokens but getting only 1.
Only (suboptimal) way to get by with this in the current setting will be:
Order.price
to be per token.execute
call, if they want to buy more tokens.https://github.com/code-423n4/2022-10-blur/blob/main/contracts/matchingPolicies/StandardPolicyERC1155.sol#L33 https://github.com/code-423n4/2022-10-blur/blob/main/contracts/matchingPolicies/StandardPolicyERC1155.sol#L59
Manual inspection
Return Order.amount
in StandardPolicyERC1155.sol
instead of 1 for amount.
#0 - blur-io-toad
2022-10-16T23:37:41Z
This was an oversight on my part for not putting this contract as out-of-scope. Our marketplace does not handle ERC1155 yet and so we haven't concluded what the matching critieria for those orders will be. This contract was mainly created to test ERC1155 transfers through the rest of the exchange, but shouldn't be deployed initially. When we are prepared to handle ERC1155 orders we will have to develop a new matching policy that determines the amount from the order parameters. Acknowledging that it's incorrect, but won't be making any changes as the contract won't be deployed.
#1 - GalloDaSballo
2022-10-28T00:05:16Z
🌟 Selected for report: 0x4non
Also found by: 0x1f8b, 0xNazgul, 0xSmartContract, Deivitto, IllIllI, Lambda, RaymondFam, Rolezn, RustyRabbit, Trust, arcoun, bin2chen, brgltd, csanuragjain, d3e4, enckrish, exd0tpy, ladboy233, nicobevi, rbserver, rotcivegaf, simon135, zzykxx
50.4817 USDC - $50.48
BlurExchange.initialize
doesn't check for zero address for _weth
, _executionDelegate
, _policyManager
, and _oracle
.
transferFrom
value not checkedtransferFrom
return value, which is then returned by ExecutionDelegate.transferERC20
is not checked to be true
in BlurExchange._transferTo
. Some tokens like ZRX, return false
instead of reverting. Since the contracts are likely to use only WETH, this should not affect the proper functioning.
ExecutionDelegate.transferERC20
may fail for non-standard tokensExecutionDelegate.transferERC20
assumes that transferFrom
will always return a bool
value. Tokens like USDT don't return a bool
, so transferERC20
will be reverted. Consider using a low-level call for transferFrom
and returning the success status if it is necessary to return a bool
. It will not be an issue if only WETH is used, which follows the standard.
Few tokens choose to revert when trying to transfer zero tokens. When using those tokens, the transaction may get reverted in _transferFees
if any recipient gets zero tokens. WETH doesn't revert on zero token transfer. To be on the safe side, consider checking if amount > 0 before transfers.
The contracts use ownerOnly
modifier for many crucial setter functions and can break the protocol if someone gets the access to the owner wallet. Consider using a multi-sig wallet or making a DAO the owner to decrease the risks.
type(uint256).max
for Order.expirationTime
for oracle cancellationsCurrently orders with expirationTime set to 0 are settleable at any time subject to fulfillment of other conditions. Since by default it is set to 0 only, missing initialization will cause issues. It is suggested to set it to type(uint256).max
explicitly, for oracle cancellations to prevent this.
pragma abicoder v2
may be omittedAbicoder v2 is enabled by default in Solidity 0.8.0 and above.
block.chainid
to access the chain IDIn BlurExchange.initialize
, it is recommended to use block.chainid
instead of passing it as a parameter. It reduces risk of manual error and saves about 700 gas.
Revert reason is not specified in BlurExchange.execute
https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L134
BlurExchange.cancelOrder
should revert if failsA user cannot easily know if BlurExchange.cancelOrder
failed without noticing a missing log. It is recommended to revert with a suitable message on failing. To ensure proper functioning of cancelOrders
, the core logic can be extracted to an internal function that returns bool
for success status, which can be used to revert in cancelOrder
if it returns false
, or can be ignored in cancelOrders
.
There are 5 instances in ExecutionDelegate.sol
and BlurExchange.sol
where there is an unnecessary comparison with boolean constants. Boolean variables need not be explicitly compared using a ==
operator. An example of this is:
https://github.com/code-423n4/2022-10-blur/blob/main/contracts/ExecutionDelegate.sol#L77
merklePath
in BlurExchange._validateOracleAuthorization
Unused variable at: https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L388. Also saves 400 gas at deployment.
ExecutionDelegate.contracts
should be renamed to something like contractsApproved
to better reflect the use.
payable
OrderStructs.Fee.recipient
is declared of type address payable
even though it is explicitly converted to payable
in BlurExchange._transferTo
. payable
should be removed to increase consistency across other address members in other structs.
While there is no risk due to the usage of a reentrancy-guard, BlurExchange.execute
updates cancelledOrFilled
after potentially dangerous external calls, which is not recommended practice. Consider moving these state changes above the _execute*transfer
calls.
https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L164-165
#0 - GalloDaSballo
2022-10-22T22:38:50Z
L
TODO - M-01
TODO - M-01
NC
Cannot be falsified
NC
NC
NC
NC
NC
NC
NC
##Â [Q-7] Use appropriate names for variables NC
NC
R
1L 1R 10NC
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xSmartContract, Aymen0909, Heuss, Lambda, Pheonix, RaymondFam, ReyAdmirado, Ruhum, Shinchan, Shishigami, __141345__, adriro, ajtra, c3phas, ch0bu, cryptostellar5, d3e4, enckrish, gogo, halden, lucacez, mcwildy, medikko, neko_nyaa, pedr02b2, pfapostol, ret2basic, rvierdiiev, saian, sakman, sakshamguruji
32.6464 USDC - $32.65
sell.order.listingTime <= buy.order.listingTime
Caching this result in BlurExchange.execute (https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L168) reduces the deploy gas usage by 6300. An example implementation would be:
bool sellFirst = sell.order.listingTime <= buy.order.listingTime; emit OrdersMatched( sellFirst ? sell.order.trader : buy.order.trader, !sellFirst ? sell.order.trader : buy.order.trader, ... );
EnumerableSet.contains
call before adding/removing policiesEnumerableSet.add
and EnumerableSet.remove
, both internally call contains
and return false if the operation failed based on contain
's return value. Removal of the two redundant checks in PolicyManager
will result in decrease of 8200 gas at deployment of BlurExchange and about 260 when calling the involved external/public functions. Example implementation:
function addPolicy(address policy) external override onlyOwner { require(_whitelistedPolicies.add(policy), "Already whitelisted"); ... function removePolicy(address policy) external override onlyOwner { require(_whitelistedPolicies.remove(policy), "Not whitelisted"); ...
uint256
for reentrancyLock
Using a bool
for reentrancyLock
increases the gas costs, as compared to uint256. Changing the type will result in a decrease of 4000 gas at deployment of BlurExchange and 250 gas for every execute
call.
In general, it is not recommended to use smaller variables since they use more gas. More details can be found here:
https://ethereum.stackexchange.com/questions/3067/why-does-uint8-cost-more-gas-than-uint256.
Example implementation:
uint reentrancyLock = 0; /* Prevent a contract function from being reentrant-called. */ modifier reentrancyGuard { require(reentrancyLock == 0, "Reentrancy detected"); reentrancyLock = 1; _; reentrancyLock = 0; }
unchecked
blockThere are 5 instances across the codebase, where the loop is bounded to the length of an array. In these cases, the loop variable can not overflow, and can be safely incremented inside a unchecked
block to save gas.
#0 - GalloDaSballo
2022-10-26T21:55:14Z
We optimize for end-users, optimizing for deploy cost doesn't make sense, the sponsor just spent 100k to make the best contracts possible for people to use
G-2 200 gas
G-3 5k
G-4 50
Pretty good but next time focus on End-User gas savings
5250