Yield Witch v2 contest - Limbooo's results

Fixed-rate borrowing and lending on Ethereum

General Information

Platform: Code4rena

Start Date: 14/07/2022

Pot Size: $25,000 USDC

Total HM: 2

Participants: 63

Period: 3 days

Judge: PierrickGT

Total Solo HM: 1

Id: 147

League: ETH

Yield

Findings Distribution

Researcher Performance

Rank: 45/63

Findings: 1

Award: $20.97

🌟 Selected for report: 0

🚀 Solo Findings: 0

Table of Contents

Gas Optimizations

[G-1] Compering unsigned integer to zero

> 0 is less gas efficient than != 0 while using the optimizer at 10k AND used in a require statement. Detailed explanation with the opcodes here.

Instances (4)

contracts/Witch.sol:
  255:         require(auction_.start > 0, "Vault not under auction");
  300:         require(auction_.start > 0, "Vault not under auction");
  358:         require(auction_.start > 0, "Vault not under auction");
  416:         require(auction_.start > 0, "Vault not under auction");

use != 0 when comparisons with 0 in require

-   require(auction_.start > 0, "Vault not under auction");
+   require(auction_.start != 0, "Vault not under auction");

[G-2] Cashing storage variables in memory

Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read.

1. DataTypes.Line storage line

Cashing it in memory save gas for 2 read instances; line.proportion

contracts/Witch.sol: 223 function _calcAuction( ... 229 ) internal view returns (DataTypes.Auction memory) { ... 231: DataTypes.Line storage line = lines[vault.ilkId][series.baseId]; //@gas 232 uint128 art = uint256(balances.art).wmul(line.proportion).u128(); ... 236 : uint256(balances.ink).wmul(line.proportion).u128(); ... 249 }
use uint64 lineProportion
-   DataTypes.Line storage line = lines[vault.ilkId][series.baseId];
+   uint64 lineProportion = lines[vault.ilkId][series.baseId].proportion;
-       line.proportion
+       lineProportion

2. DataTypes.Auction storage auction_

Cashing it in memory save gas for 5 read instances; auction_.start, auction_.ilkId, auction_.baseId, auction_.ink, auction_.owner

contracts/Witch.sol: 253 function cancel(bytes12 vaultId) external { 254: DataTypes.Auction storage auction_ = auctions[vaultId]; 255 require(auction_.start > 0, "Vault not under auction"); ... 259 limits[auction_.ilkId][auction_.baseId].sum -= auction_.ink; 260 261 _auctionEnded(vaultId, auction_.owner); ... 264 }
use DataTypes.Auction memory auction_
-   DataTypes.Auction storage auction_ = auctions[vaultId];
+   DataTypes.Auction memory auction_ = auctions[vaultId];

[G-3] Comparison operators

There is no opcode for >= and <=, when using them 2 operation are preformed; for (> or < ) and =.

Instances using >= (6)

contracts/Witch.sol: 105: initialOffer == 0 || initialOffer >= 0.01e18, 108: require(proportion >= 0.01e18, "Proportion below 1%"); 256: require(cauldron.level(vaultId) >= 0, "Undercollateralized"); 313: require(liquidatorCut >= minInkOut, "Not enough bought"); 365: require(liquidatorCut >= minInkOut, "Not enough bought"); 438: auction_.art - artIn >= debt.min * (10**debt.dec),
use only >, decrement the compered

Replace >= with >, decrement the compered variable as needed.

Instances using <= (3)

ontracts/Witch.sol: 102: require(initialOffer <= 1e18, "InitialOffer above 100%"); 103: require(proportion <= 1e18, "Proportion above 100%"); 200: require(limits_.sum <= limits_.max, "Collateral limit reached");
use only <, increment the compered

Replace <= with <, increment the compered variable as needed.

[G-4] Custom errors preferred over require

In Solidity ^0.8.4, custom errors are cheaper than revert string from require as explained here. Also it can be customizable using intermediate internal functions to build a generic errors.

Instances (17), 6 duplicated

contracts/Witch.sol: 84: require(param == "ladle", "Unrecognized"); 102: require(initialOffer <= 1e18, "InitialOffer above 100%"); 103: require(proportion <= 1e18, "Proportion above 100%"); 104: require( 105 initialOffer == 0 || initialOffer >= 0.01e18, 106 "InitialOffer below 1%" 107 ); 108: require(proportion >= 0.01e18, "Proportion below 1%"); 189: require(cauldron.level(vaultId) < 0, "Not undercollateralized"); 200: require(limits_.sum <= limits_.max, "Collateral limit reached"); 255: require(auction_.start > 0, "Vault not under auction"); 256: require(cauldron.level(vaultId) >= 0, "Undercollateralized"); 300: require(auction_.start > 0, "Vault not under auction"); 313: require(liquidatorCut >= minInkOut, "Not enough bought"); 328: require(baseJoin != IJoin(address(0)), "Join not found"); 358: require(auction_.start > 0, "Vault not under auction"); 365: require(liquidatorCut >= minInkOut, "Not enough bought"); 395: require(ilkJoin != IJoin(address(0)), "Join not found"); 416: require(auction_.start > 0, "Vault not under auction"); 437: require( 438 auction_.art - artIn >= debt.min * (10**debt.dec), 439 "Leaves dust" 440 );

use revert with custom error only

Replace require with revert predefine custom errors. Also, combined the same error logics with one custom error type, it can be used with intermediate functions.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter