Platform: Code4rena
Start Date: 14/06/2022
Pot Size: $50,000 USDC
Total HM: 19
Participants: 99
Period: 5 days
Judge: HardlyDifficult
Total Solo HM: 4
Id: 136
League: ETH
Rank: 69/99
Findings: 3
Award: $63.50
π Selected for report: 0
π Solo Findings: 0
π Selected for report: hyh
Also found by: 0x29A, 0xNineDec, 0xf15ers, 0xkowloon, GreyArt, IllIllI, KIntern, Kenshin, Lambda, WatchPug, Wayne, berndartmueller, byterocket, cccz, codexploder, horsefacts, kenzo, obront, obtarian, oyc_109, peritoflores, rajatbeladiya, rfa, saian, unforgiven, zer0dot
11.084 USDC - $11.08
The rescueETH()
function seemingly incorrectly uses the msg.value
transferred along with the function as the amount to send to the destination
address. This means that the actual contract's balance can never be retrieved.
See the concerned code here.
Note that the function is payable when it doesn't need to be, and uses the msg.value
transaction property as the amount to withdraw.
Manual review.
Replace the msg.value
parameter with address(this).balance
and optionally remove the payable
modifier.
#0 - nneverlander
2022-06-22T11:17:02Z
Duplicate
#1 - nneverlander
2022-07-05T11:41:39Z
#2 - HardlyDifficult
2022-07-09T16:50:38Z
π Selected for report: WatchPug
Also found by: BowTiedWardens, GreyArt, Ruhum, berndartmueller, cccz, csanuragjain, defsec, joestakey, m9800, peritoflores, reassor, shenwilly, throttle, zer0dot
21.1924 USDC - $21.19
Similarly to another finding I submitted involving a compromised owner being able to frontrun a transaction and steal the entire sale price by setting the protocol fee to 10000 bps, a compromised owner can also set the fee greater than 10000 bps.
This would cause every (variable) - protocolFee
calculation to revert, as well as in the _transferFees()
function called within taker functions.
See the concerned code here.
An example of where this would break functionality would be here.
This same issue exists for all order matching or taking functions.
Manual review.
Just like the other issue with frontrunning, include an upper limit to the protocol fee.
#0 - HardlyDifficult
2022-07-11T00:00:29Z
π Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xAsm0d3us, 0xDjango, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xkowloon, BowTiedWardens, Chom, ElKu, FSchmoede, Funen, GimelSec, Kaiziron, Kenshin, Lambda, MadWookie, MiloTruck, PPrieditis, Picodes, PwnedNoMore, StErMi, Tadashi, TerrierLover, TomJ, Tomio, Wayne, Waze, _Adam, antonttc, apostle0x01, asutorufos, c3phas, codexploder, defsec, delfin454000, fatherOfBlocks, hake, hansfriese, hyh, joestakey, k, kenta, oyc_109, peritoflores, reassor, rfa, robee, sach1r0, simon135, slywaters, zer0dot
31.2335 USDC - $31.23
The optimizations are applied additively, so every change is applied on top of the last!
Optimize line 583 of the InfinityExchange Contract by combining the ternary operators into a single if statement.
matchOneToOneOrders()
average gas 221896 => 221773, also saves 27 bytes of contract code size.
InfinityExchange._matchOneToOneOrders():
... OrderTypes.MakerOrder calldata sell; OrderTypes.MakerOrder calldata buy; if (makerOrder1.isSellOrder) { sell = makerOrder1; buy = makerOrder2; } else { sell = makerOrder2; buy = makerOrder1; } ...
Remove line 523 of the InfinityExchange contract since it's redundant.
The order.signer
parameter is checked against address(0)
, but this check is already done in the SignatureChecker's recover()
function, which is called from the SignatureChecker's verify()
function prior to the aforementioned zero address check. More specifically, if the signer returned from the ecrecover
call is the zero address, an explicit revert is thrown. Otherwise, if the order.signer
is zero, then it forcibly cannot equal the recovered non-zero address, reverting due to an invalid sig.
This should affect all calls to isOrderValid()
but for reference, the matchOneToOneOrders()
average gas cost went from 221773 (with the prior change) to 221531. Also saves 41 more bytes of contract code size.
InfinityExchange.isOrderValid():
... if ( orderExpired || !sigValid || !_complications.contains(order.execParams[0]) || !_currencies.contains(order.execParams[1]) ) { return false; } return true; ...
Revert in the verify()
function instead of returning a boolean at line 50 of the signatureChecker contract.
verify
returns whether the signature is indeed valid (though reverting on zero address signers), then the result is compared in the isOrderValid()
function. Reverting directly in the verify()
function saves a bit of gas at the cost of increased code size.
This should affect all calls to isOrderValid()
but for reference, the matchOneToOneOrders()
average gas cost went from
221531 to 221480. However, the code size increased by 105 bytes, which brings the net code size increase to 37 bytes, I think it's a good tradeoff!
InfinityExchange.isOrderValid():
... if ( orderExpired || !_complications.contains(order.execParams[0]) || !_currencies.contains(order.execParams[1]) ) { return false; } return true; ...
SignatureChecker.verify():
... if (Address.isContract(signer)) { // 0x1626ba7e is the interfaceId for signature contracts (see IERC1271) require(IERC1271(signer).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e, "Invalid sig"); } else { require(recover(digest, r, s, v) == signer, "Invalid sig"); } ...
#0 - nneverlander
2022-06-22T19:16:47Z
Thank you