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: 2/80
Findings: 3
Award: $3,094.50
🌟 Selected for report: 1
🚀 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/BlurExchange.sol#L425 https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L429 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
The amount in the response of functions canMatchMakerAsk
and canMatchMakerBid
in amount is always 1
. But ERC1155 can have different amounts.
So if you are selling 5 ERC1155 the buyer will only get 1, but pay for 5 items.
matchingPolicies/StandardPolicyERC1155.sol#L33
matchingPolicies/StandardPolicyERC1155.sol#L59
Lets say that Bob is selling an amount of 5 ERC1155 of "Gold".
Now Alice craft a signature to buy all of Bobs "Gold", then triggers the execute
and the takes Alice WETH or ETH to Bob for 5 ERC1155 of "Gold".
After the deal is closed Alice checks hers wallet to find out that only has 1 ERC1155 of "Gold"
manual revision
Use signature amount on the Policy;
--- a/contracts/matchingPolicies/StandardPolicyERC1155.sol +++ b/contracts/matchingPolicies/StandardPolicyERC1155.sol @@ -30,7 +30,7 @@ contract StandardPolicyERC1155 is IMatchingPolicy { (makerAsk.price == takerBid.price), makerAsk.price, makerAsk.tokenId, - 1, + makerAsk.amount, AssetType.ERC1155 ); } @@ -56,7 +56,7 @@ contract StandardPolicyERC1155 is IMatchingPolicy { (makerBid.price == takerAsk.price), makerBid.price, makerBid.tokenId, - 1, + makerBid.amount, AssetType.ERC1155 ); }
Also you may need to fix the policy tests;
--- a/tests/policy.test.ts +++ b/tests/policy.test.ts @@ -126,6 +126,7 @@ export function runMatchingPolicyTests(setupTest: any) { sell = generateOrder(alice, { side: Side.Sell, tokenId, + amount: 1, matchingPolicy: matchingPolicies.standardPolicyERC1155.address, }).parameters; buy = generateOrder(bob, { @@ -180,6 +181,7 @@ export function runMatchingPolicyTests(setupTest: any) { describe('buy is maker', () => { beforeEach(() => { buy.listingTime = '1'; + buy.amount = '1'; sell.listingTime = '2'; }); it('should match', async () => {
#0 - GalloDaSballo
2022-10-13T22:27:09Z
🌟 Selected for report: Soosh
Also found by: 0x1f8b, 0x4non, 0xc0ffEE, RaymondFam, Ruhum, RustyRabbit, Trust, arcoun, cccz, d3e4, minhtrng, rotcivegaf, saian, sakshamguruji
https://github.com/code-423n4/2022-10-blur/blob/main/contracts/ExecutionDelegate.sol#L73-L126
The owner can add any contract and leverage on the functions to steal ERC20, ERC721 and ERC1155.
Deploy a simple contract that can call transferERC721Unsafe
, transferERC721
, transferERC1155
or transferERC20
. Then add it has an approved contract with approveContract
Now you can take any user approved ERC20, ERC721 ot ERC1155
Manual revision
I think that using approvedContract
mapping is an overkill, you should only allow the exchange to manage the tokens and nfts. Also since the Exchange is an UUPS proxy the address of the contract will be always the same, so you should remove all the approveContracts logic and use an immutable address of BlurExchange UUPS.
Upgrades behind the proxy should be manage with a timelock mechanism to avoid adding backdoor functions to the implementation.
#0 - GalloDaSballo
2022-10-27T15:45:04Z
R
#1 - GalloDaSballo
2022-11-15T23:33:59Z
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
541.8673 USDC - $541.87
pragma abicoder v2
You are using solidity v0.8 since that version and above the ABIEncoderV2 is not experimental anymore - it is actually enabled by default by the compiler. Remove lines; BlurExchange.sol#L3 ExecutionDelegate.sol#L3 interfaces/IBlurExchange.sol#L3 test/TestBlurExchange.sol#L3
Your current version of @openzeppelin/contracts
is 4.4.1 and latest version is 4.7.3
Your current version of @openzeppelin/contracts-upgradeable
is ^4.6.0 and latest version is ^4.7.3
You use regular imports on lines: BlurExchange.sol/#L5-L16 ExecutionDelegate.sol#L5-L8 lib/ERC1967Proxy.sol#L5-L6
Instead of this use named imports as you do on for example; PolicyManager.sol#L4-L7 lib/EIP712.sol#L4 BlurExchange.sol#L17-L24
On import lib/ReentrancyGuarded.sol and lib/EIP712.sol add this lines;
/** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap;
In case you need to add variables for an upgrade you will have reserved space.
BlurExchange.sol
ERC20.sol
On line contracts/BlurExchange.sol#L8
Openzeppellin already provides you a reentrancy guard, use this instead of BlurExchange.sol/#L10 Replace
import "./lib/ReentrancyGuarded.sol";
with;
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
Take notes that you should also add __ReentrancyGuard_init()
to you initialize
function.
Using a battle test library is better, and also will be less line to audit.
Please see; https://docs.openzeppelin.com/contracts/4.x/upgradeable https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard
ERC1967Proxy
implementation its a copy of openzepellin implementationUso openzepellin implementation instead of copy the file. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Proxy.sol
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"
Pausable
featureThis lines creates are to make the contract Pausable
;
contracts/BlurExchange.sol#L33-L50
This is already implement in openzepellin on contracts/security/PausableUpgradeable.sol use this instead of your on implementation.
Please see; https://docs.openzeppelin.com/contracts/4.x/upgradeable https://docs.openzeppelin.com/contracts/4.x/api/security#Pausable
For readability, it is better to use scientific notation. On BlurExchange.sol/#L59 Replace 10000 with 1e4.
On BlurExchange.sol/#L96 and BlurExchange.sol/#L101 instead of uint
use uint256
chainid
On BlurExchange.sol/#L96.
Instead of passing chainid
as reference you could get current chainid
using block.chainid
Please see;
https://docs.soliditylang.org/en/v0.8.0/units-and-global-variables.html#block-and-transaction-properties
address(0)
checks for _executionDelegate
, _policyManager
and _oracle
By mistake any of these could be address(0)
the could be chaged later by and admin, however is a good practice to check for address(0)
BlurExchange.sol/#L98-L100
Recommendation, add address(0) check;
require(address(_VARIABLE) != address(0), "Address cannot be zero");
address(0)
for _weth
_weth
variable on contracts/BlurExchange.sol/#L97 could be set as address(0)
as mistake and there is no way to change it.
Recommendation, add address(0) check;
require(address(_weth) != address(0), "Address cannot be zero");
blockRange
is 0
, _validateSignatures
will always fail updating oracleInspecting the functions that set blockRange
on BlurExchange.sol#L117 and BlurExchange.sol#L246 it seems that blockRange
can be 0
.
But if you set blockRange
to 0
the condition that checks oracle authoriozation in line 318 will always fail;
require(block.number - order.blockNumber < blockRange, "Signed block number out of range");
Recommendation add <=
to the require, or create a minimal blockRange required.
ecrecover
is address(0)On BlurExchange.sol#L408 add a revert that triggers if the response is address(0), this means that signature its not valid.
Example, by definition oracle
could be initialized with address(0), then you will always can pass this line (oracle validation);
BlurExchange.sol#L392
return _recover(oracleHash, v, r, s) == oracle;
And also you could end up stealing, because is used on _validateSignatures
BlurExchange.sol#L320 and this is also used on the main execute
function that transfers nfts and tokens BlurExchange.sol#L128
ecrecover
Use OZ library ECDSA that its battle tested to avoid classic errors. contracts/utils/cryptography/ECDSA.sol https://docs.openzeppelin.com/contracts/4.x/api/utils#ECDSA
On BlurExchange.sol/#L134, BlurExchange.sol#L183 and BlurExchange.sol#L452 there is no revert message, is very important to add a message, so the user has enough information to know the reason of failure.
_transferFees
function loopThis loop could drain all user gas and revert; https://github.com/code-423n4/2022-10-blur/blob/main/contracts/BlurExchange.sol#L476-L479
fees
Fees can have any desiree amount. Recommendation create a threashold to avoid excessive fees. BlurExchange.sol/#L477
_exist(address)
method use OZ isContract(address)
Openzeppellin already has checks for contract, intead of reimplementing use openzeppellin implementation; https://docs.openzeppelin.com/contracts/4.x/api/utils#Address-isContract-address-
The function transferERC20
on BlurExchange.sol#L511
returns a boolean, but its not checked in this line, add a require or remove the return.
execute
Just move the cancelledOrFilled
setting to stick to the Checks-effects-interactions pattern.
--- a/contracts/BlurExchange.sol +++ b/contracts/BlurExchange.sol @@ -144,6 +144,10 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad (uint256 price, uint256 tokenId, uint256 amount, AssetType assetType) = _canMatchOrders(sell.order, buy.order); + /* Mark orders as filled. */ + cancelledOrFilled[sellHash] = true; + cancelledOrFilled[buyHash] = true; + _executeFundsTransfer( sell.order.trader, buy.order.trader, @@ -160,10 +164,6 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad assetType ); - /* Mark orders as filled. */ - cancelledOrFilled[sellHash] = true; - cancelledOrFilled[buyHash] = true; - emit OrdersMatched( sell.order.listingTime <= buy.order.listingTime ? sell.order.trader : buy.order.trader, sell.order.listingTime > buy.order.listingTime ? sell.order.trader : buy.order.trader,
Instead of your own merkle tree lib, BlurExchange.sol#L12 Use openzeppelin implementation; https://docs.openzeppelin.com/contracts/4.x/api/utils#MerkleProof
Use openzepellin implementation, and save a lot of possible bugs by writing your onw implementation; Code is in; https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/cryptography/EIP712Upgradeable.sol
Just;
import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
https://docs.openzeppelin.com/contracts/4.x/api/utils#EIP712 (docs could be old, its not on draft anymore)
contract EIP712 should be abstract
#0 - GalloDaSballo
2022-10-22T20:11:49Z
NC
NC
R
L
NC
##Â Avoid reinvent a reentrancy guard Disagree as the modifier is really basic
Disagree as well, Sponsor is free to use whatever code they chose, you need to point an issue with the in scope code
Disputed with same reasoning
Disagree on this specific instance, would agree with more zeroes
Valid NC
L
L
NC
R
R
##Â Empty revert message NC
L
L
Disagree
TODO M-01
##Â Use Checks-effects-interactions pattern on execute R
Disagree with the "Use OZ" advice without explanation (marked the one about ECDSA as valid because it had some validity and detail)
R
Pretty good thorough report
#1 - eugenioclrc
2022-10-28T19:46:54Z
Thanks for the feedback! what you mean with the TODO flag?
#2 - GalloDaSballo
2022-11-03T20:27:43Z
TODO means I need to finish typically because I will bulk judge later or may raise severity
#3 - GalloDaSballo
2022-11-07T20:42:57Z
5L 5R 6NC
#4 - GalloDaSballo
2022-11-07T22:08:48Z
After adding the downgraded findings this report won by quite a strong margin (10+ points, 20%)
Well played!