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: 4/80
Findings: 3
Award: $2,969.45
🌟 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 https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L425 https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L429
The return amount in StandardPolicyERC1155.sol it's always 1 (ask, bid)
When the BlurExchange.sol execute
the orders the trader always receive one ERC1155 asset
A seller can make an order with high amount of ERC1155 token and the buyer will only receive one
Review
Use the Order
ask/bid amount
File: /contracts/matchingPolicies/StandardPolicyERC1155.sol From: 33 1, To: 33 makerAsk.amount, From: 59 1, To: 59 makerBid.amount,
#0 - blur-io-toad
2022-10-16T23:39:22Z
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 - trust1995
2022-10-27T23:59:10Z
Dup #666
#2 - GalloDaSballo
2022-10-28T00:08:23Z
🌟 Selected for report: Soosh
Also found by: 0x1f8b, 0x4non, 0xc0ffEE, RaymondFam, Ruhum, RustyRabbit, Trust, arcoun, cccz, d3e4, minhtrng, rotcivegaf, saian, sakshamguruji
The owner can approve(approveContract(address)) any address(contract and EOA) and steal approved assets(ERC721, ERC1155 and ERC20) of the users who they have approved the contract(ExecutionDelegate.sol) Also the BlurExchange.sol contract could change the ExecutionDelegate.sol and users could forget to revoke the approval or even not know it
setApprovalForAll
function) the ExecutionDelegate.sol to manipulate all his ERC721 tokensapproveContract
functiontransferERC721Unsafe
functionReview
Use directly the BlurExchange.sol to manipulate the assets(ERC721, ERC1155 and ERC20)
#0 - GalloDaSballo
2022-10-27T15:46:20Z
R
#1 - GalloDaSballo
2022-11-15T23:33:24Z
Dup of #235
🌟 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
416.821 USDC - $416.82
L-N | Issue | Instances |
---|---|---|
[L‑01] | Outdated versions of @openzeppelin/contracts and @openzeppelin/contracts-upgradeable | 2 |
[L‑02] | Use standard libraries | 6 |
[L‑03] | Do not pass the chainid as constructor parameter | 1 |
[L‑04] | Missing address(0) checks | 5 |
[L‑05] | ecrecover return address(0) if signature it's invalid | 1 |
Total: 15 instances over 5 issues
N-N | Issue | Instances |
---|---|---|
[N‑01] | Typo | 1 |
[N‑02] | Lint | 8 |
[N‑03] | Missing error message in require statement | 3 |
[N‑04] | Unused imports | 2 |
[N‑05] | Using pragma abicoder v2 | 2 |
[N‑06] | Send ether with call instead of transfer | 1 |
[N‑07] | Mark as abstract | 1 |
[N‑08] | Overdeclare variables | 1 |
Total: 19 instances over 8 issues
@openzeppelin/contracts
and @openzeppelin/contracts-upgradeable
The latest version of @openzeppelin/contracts
is v4.7.3 and of @openzeppelin/contracts-upgradeable
is v4.7.3
File: /package.json 63 "@openzeppelin/contracts": "4.4.1", 64 "@openzeppelin/contracts-upgradeable": "^4.6.0",
Use the libraries of @openzeppelin/contracts
to avoid mistake, this libraries there are heavy tested and are used in severals oncahin contracts
File: /contracts/BlurExchange.sol 10 import "./lib/ReentrancyGuarded.sol"; 30 contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, UUPSUpgradeable { 131 reentrancyGuard
File: /contracts/BlurExchange.sol 12 import "./lib/MerkleVerifier.sol"; 344 function _validateUserAuthorization( 345 bytes32 orderHash, 346 address trader, 347 uint8 v, 348 bytes32 r, 349 bytes32 s, 350 SignatureVersion signatureVersion, 351 bytes calldata extraSignature 352 ) internal view returns (bool) { 353 bytes32 hashToSign; 354 if (signatureVersion == SignatureVersion.Single) { 355 /* Single-listing authentication: Order signed by trader */ 356 hashToSign = _hashToSign(orderHash); 357 } else if (signatureVersion == SignatureVersion.Bulk) { 358 /* Bulk-listing authentication: Merkle root of orders signed by trader */ 359 (bytes32[] memory merklePath) = abi.decode(extraSignature, (bytes32[])); 360 361 bytes32 computedRoot = MerkleVerifier._computeRoot(orderHash, merklePath); 362 hashToSign = _hashToSignRoot(computedRoot); 363 } 364 365 return _recover(hashToSign, v, r, s) == trader; 366 }
File: /contracts/BlurExchange.sol 534 require(_exists(collection), "Collection does not exist"); 548 function _exists(address what) 549 internal 550 view 551 returns (bool) 552 { 553 uint size; 554 assembly { 555 size := extcodesize(what) 556 } 557 return size > 0; 558 }
File: /contracts/BlurExchange.sol 33 uint256 public isOpen; 35 modifier whenOpen() { 36 require(isOpen == 1, "Closed"); 37 _; 38 } 40 event Opened(); 41 event Closed(); 43 function open() external onlyOwner { 45 isOpen = 1; 46 emit Opened(); 47 } 48 function close() external onlyOwner { 49 isOpen = 0; 50 emit Closed(); 51 } 104 isOpen = 1; 132 whenOpen
Instead of copy create an contract who heredit from ERC1967Proxy.sol openzeppelin contract:
pragma solidity 0.8.13; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract ERC1967Proxy_ is ERC1967Proxy { constructor(address _logic, bytes memory _data) payable ERC1967Proxy(_logic, _data) { } }
File: /contracts/BlurExchange.sol 365 return _recover(hashToSign, v, r, s) == trader; 392 return _recover(oracleHash, v, r, s) == oracle; 401 function _recover( 402 bytes32 digest, 403 uint8 v, 404 bytes32 r, 405 bytes32 s 406 ) internal pure returns (address) { 407 require(v == 27 || v == 28, "Invalid v parameter"); 408 return ecrecover(digest, v, r, s); 409 }
chainid
as constructor
parameterUse block.chainid
instead of chainid
File: /contracts/BlurExchange.sol 96 uint chainId,
address(0)
checksFile: /contracts/BlurExchange.sol 97 address _weth, 98 IExecutionDelegate _executionDelegate, 99 IPolicyManager _policyManager, 100 address _oracle,
File: /contracts/PolicyManager.sol 25 function addPolicy(address policy) external override onlyOwner {
ecrecover
return address(0)
if signature it's invalidIn the BlurExchange.sol initialize
the _oracle
could be the address(0)
If the _oracle
is the address(0)
, an user can provide an invalid order.extraSignature
and pass the _validateOracleAuthorization
check because the ecrecover
returns address(0)
when the signature is invalid
File: /contracts/BlurExchange.sol 365 return _recover(hashToSign, v, r, s) == trader; 392 return _recover(oracleHash, v, r, s) == oracle; 408 return ecrecover(digest, v, r, s);
File: /contracts/token/VariableSupplyERC20Token.sol From: 19 * function call, and allows initializating the storage of the proxy like a Solidity constructor. To: 19 * function call, and allows initializing the storage of the proxy like a Solidity constructor.
Remove space:
File: /contracts/lib/EIP712.sol 11 \n 82 \n
File: /contracts/ExecutionDelegate.sol 17 \n
File: /contracts/BlurExchange.sol 31 \n 296 \n
Wrong identation:
File: /contracts/lib/EIP712.sol 92 ORDER_TYPEHASH, 93 order.trader, 94 order.side, 95 order.matchingPolicy, 96 order.collection, 97 order.tokenId, 98 order.amount, 99 order.paymentToken, 100 order.price, 101 order.listingTime, 102 order.expirationTime, 103 _packFees(order.fees), 104 order.salt, 105 keccak256(order.extraParams)
File: /contracts/BlurExchange.sol 298 return true;
Don't use extra parenthesis:
File: /contracts/BlurExchange.sol 486 return (receiveAmount);
require
statementsFile: /contracts/BlurExchange.sol 134 require(sell.order.side == Side.Sell); 183 require(msg.sender == order.trader); 452 require(msg.value == price);
File: /contracts/BlurExchange.sol 5 import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 8 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
pragma abicoder v2
In the later Solidity versions it is no longer necessary to use the "experimental" version. Using experimental constructions is not recommended for production code.
See: abiencoderv2
File: /contracts/BlurExchange.sol 3 pragma abicoder v2;
File: /contracts/ExecutionDelegate.sol 3 pragma abicoder v2;
call
instead of transfer
Use call instead of transfer to send ether. And return value must be checked if sending ether is successful or not. Sending ether with the transfer is no longer recommended.
This transaction will fail inevitably when:
_to
smart contract does not implement a payable function._to
smart contract does implement a payable fallback which uses more than 2300 gas unit._to
smart contract implements a payable fallback function that needs less than 2300 gas units but is called through proxy, raising the call’s gas usage above 2300.File: /contracts/BlurExchange.sol From: 508 payable(to).transfer(amount); To: 508 (bool result, ) = to.call{value: amount}(""); 509 require(result, "Failed to send Ether");
The /contracts/lib/EIP712.sol.sol could be mark as a abstract contract
File: /contracts/BlurExchange.sol From: 388 (bytes32[] memory merklePath, uint8 _v, bytes32 _r, bytes32 _s) = abi.decode(extraSignature, (bytes32[], uint8, bytes32, bytes32)); 389 v = _v; r = _r; s = _s; To: 388 (, v, r, s) = abi.decode(extraSignature, (bytes32[], uint8, bytes32, bytes32)); 389 <REMOVE THIS LINE>
#0 - GalloDaSballo
2022-10-23T23:36:23Z
[L‑01] | Outdated versions of @openzeppelin/contracts and @openzeppelin/contracts-upgradeable | 2 R
[L‑02] | Use standard libraries | 6 Disagree
[L‑03] | Do not pass the chainid as constructor parameter | 1 L
[L‑04] | Missing address(0) checks | 5 L
[L‑05] | ecrecover return address(0) if signature it's invalid R
[N‑01] | Typo | 1 NC [N‑02] | Lint | 8 Disagree with pretty random comment without context
[N‑03] | Missing error message in require statement | 3 NC
[N‑04] | Unused imports | 2 R [N‑05] | Using pragma abicoder v2 | 2 NC
[N‑06] | Send ether with call instead of transfer | 1 L
[N‑07] | Mark as abstract | 1 R
[N‑08] | Overdeclare variables R
#1 - GalloDaSballo
2022-11-07T20:35:22Z
3L 4R 3NC