Yield Witch v2 contest - hake'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: 7/63

Findings: 2

Award: $138.16

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

39.0709 USDC - $39.07

Labels

bug
QA (Quality Assurance)
sponsor confirmed

External Links

Low Risk Findings Overview

FindingInstances
[L-01]Missing address(0) check3

Non-critical Findings Overview

FindingInstances
[N-01]The use of magic numbers is not recommended5
[N-02]Typo2
[N-03]Remove TODO’s1

QA overview per contract

ContractTotal InstancesTotal FindingsLow FindingsLow InstancesNC FindingsNC Instances
Witch.sol1041337
Witch.sol110011

Low Risk Findings

[L-01] Missing address(0) check

Funds might be lost by accident if check is not implemented. 3 instances of this issue have been found:

[L-01] Witch.sol#L286-L301


    function payBase(
        bytes12 vaultId,
        address to,
        uint128 minInkOut,
        uint128 maxBaseIn
    )
        external
        returns (
            uint256 liquidatorCut,
            uint256 auctioneerCut,
            uint256 baseIn
        )
    {
        DataTypes.Auction memory auction_ = auctions[vaultId];
        require(auction_.start > 0, "Vault not under auction");

[L-01b] Witch.sol#L176-L177


    function auction(bytes12 vaultId, address to)

[L-01c] Witch.sol#L344-L359


    function payFYToken(
        bytes12 vaultId,
        address to,
        uint128 minInkOut,
        uint128 maxArtIn
    )
        external
        returns (
            uint256 liquidatorCut,
            uint256 auctioneerCut,
            uint256 artIn
        )
    {
        DataTypes.Auction memory auction_ = auctions[vaultId];
        require(auction_.start > 0, "Vault not under auction");

Non-critical Findings

[N-01] The use of magic numbers is not recommended

Consider setting constant numbers as a constant variable for better readability and clarity. 5 instances of this issue have been found:

[N-01] Witch.sol#L587-L588


            proportionNow = 1e18;

[N-01b] Witch.sol#L105-L106


            initialOffer == 0 || initialOffer >= 0.01e18,

[N-01c] Witch.sol#L108-L109


        require(proportion >= 0.01e18, "Proportion below 1%");

[N-01d] Witch.sol#L162-L163


        if (auctioneerReward_ > 1e18) {

[N-01e] Witch.sol#L591-L592


                uint256(1e18 - initialProportion).wmul(elapsed.wdiv(duration));

[N-02] Typo

Please fix typos. 2 instances of this issue have been found:

[N-02] Witch.sol#L81


/// @param param Name of parameter to set (must be "ladle")

[N-02b] Witch.sol#L14


uncollateralized -> undercollateralized

[N-03] Remove TODO’s

They add unnecessary cluttler and harm readbility for auditors. 1 instance of this issue has been found:

[N-03] Witch.sol#L577-L578


        // TODO: Replace this contract before then 😰 -> This is funny though

#0 - alcueca

2022-07-22T13:57:51Z

Thanks for finding the typo, the other three are not useful. Please read the README in the future.

Gas Optimizations

FindingInstances
[G-01]Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate1
[G-02]require()/revert() checks used multiple times should be turned into a function or modifier7
[G-03]uints smaller than uint256 cost more gas4
[G-04]Use custom errors rather than revert()/require() strings to save gas11

Gas overview per contract

ContractInstancesGas Ops
Witch.sol224
Witch.sol11

Gas Optimizations

[G-01] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operatio 1 instance of this issue has been found:

[G-01] Witch.sol#L65-L69


    mapping(bytes12 => DataTypes.Auction) public auctions;
    mapping(bytes6 => mapping(bytes6 => DataTypes.Line)) public lines;
    mapping(bytes6 => mapping(bytes6 => DataTypes.Limits)) public limits;
    mapping(address => bool) public otherWitches;
    mapping(bytes6 => mapping(bytes6 => bool)) public ignoredPairs;

[G-02] require()/revert() checks used multiple times should be turned into a function or modifier

Doing so increases code readability decreases number of instructions for the compiler. 7 instances of this issue have been found:

[G-02] Witch.sol#L328-L329


            require(baseJoin != IJoin(address(0)), "Join not found");

[G-02b] Witch.sol#L365-L366


        require(liquidatorCut >= minInkOut, "Not enough bought");

[G-02c] Witch.sol#L313-L314


        require(liquidatorCut >= minInkOut, "Not enough bought");

[G-02d] Witch.sol#L416-L417


        require(auction_.start > 0, "Vault not under auction");

[G-02e] Witch.sol#L358-L359


        require(auction_.start > 0, "Vault not under auction");

[G-02f] Witch.sol#L300-L301


        require(auction_.start > 0, "Vault not under auction");

[G-02g] Witch.sol#L255-L256


        require(auction_.start > 0, "Vault not under auction");

[G-03] uints smaller than uint256 cost more gas

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. Solidity docs 4 instances of this issue have been found:

[G-03] Witch.sol#L63


uint128 public auctioneerReward = 0.01e18;

[G-03b] Witch.sol#L232-L233


        uint128 art = uint256(balances.art).wmul(line.proportion).u128();

[G-03c] Witch.sol#L234-L235


        uint128 ink = (art == balances.art)

[G-03d] Witch.sol#L303-L304


        uint128 artIn = uint128(

[G-04] Use custom errors rather than revert()/require() strings to save gas

Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. 11 instances of this issue have been found:

[G-04] Witch.sol#L437-L440


               require(
                    auction_.art - artIn >= debt.min * (10**debt.dec),
                    "Leaves dust"
                );

[G-04b] Witch.sol#L416-L417


        require(auction_.start > 0, "Vault not under auction");

[G-04c] Witch.sol#L395-L396


            require(ilkJoin != IJoin(address(0)), "Join not found");

[G-04d] Witch.sol#L365-L366


        require(liquidatorCut >= minInkOut, "Not enough bought");

[G-04e] Witch.sol#L358-L359


        require(auction_.start > 0, "Vault not under auction");

[G-04f] Witch.sol#L328-L329


            require(baseJoin != IJoin(address(0)), "Join not found");

[G-04g] Witch.sol#L313-L314


        require(liquidatorCut >= minInkOut, "Not enough bought");

[G-04h] Witch.sol#L300-L301


        require(auction_.start > 0, "Vault not under auction");

[G-04i] Witch.sol#L102-L108


require(initialOffer <= 1e18, "InitialOffer above 100%");
        require(proportion <= 1e18, "Proportion above 100%");
        require(
            initialOffer == 0 || initialOffer >= 0.01e18,
            "InitialOffer below 1%"
        );
        require(proportion >= 0.01e18, "Proportion below 1%");

[G-04j] Witch.sol#L189-L190


        require(cauldron.level(vaultId) < 0, "Not undercollateralized");

[G-04k] Witch.sol#L200-L201


        require(limits_.sum <= limits_.max, "Collateral limit reached");
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