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

Findings: 1

Award: $23.75

🌟 Selected for report: 0

🚀 Solo Findings: 0

2022-07-yield

Gas Optimisations Report

FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE

If a function modifier such as auth is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 6 instances of this issue:

83:    function point(bytes32 param, address value) external auth {


95:    function setLine(
96:        bytes6 ilkId,
97:        bytes6 baseId,
98:        uint32 duration,
99:        uint64 proportion,
100:       uint64 initialOffer
101:   ) external auth {


126:   function setLimit(
127:       bytes6 ilkId,
128:       bytes6 baseId,
129:       uint128 max,
130:   ) external auth {


141:   function setAnotherWitch(address value, bool isWitch) external auth {


150:   function setIgnoredPair(
151:       bytes6 ilkId,
152:       bytes6 baseId,
153:       bool ignore
154:   ) external auth {


161:   function setAuctioneerReward(uint128 auctioneerReward_) external auth {

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L83

STATE VARIABLES SHOULD BE CACHED IN STACK VARIABLES RATHER THAN RE-READING THEM FROM STORAGE

One of most the expensive opcodes is SLOAD. In order to save gas from checking the value of storage-located variable they can be cached in memory. That way the MLOAD opcode is used each time we want to access the variable which is much cheaper than SLOAD.

There are 11 instances of this issue:

180:   DataTypes.Vault memory vault = cauldron.vaults(vaultId);

184:   DataTypes.Series memory series = cauldron.series(vault.seriesId);

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

191:   DataTypes.Balances memory balances = cauldron.balances(vaultId);

192:   DataTypes.Debt memory debt = cauldron.debt(series.baseId, vault.ilkId);

304:   cauldron.debtFromBase(auction_.seriesId, maxBaseIn)

309:   baseIn = cauldron.debtToBase(auction_.seriesId, artIn);

542:   DataTypes.Vault memory vault = cauldron.vaults(vaultId);
         
546:   DataTypes.Series memory series = cauldron.series(vault.seriesId);

547:   DataTypes.Balances memory balances = cauldron.balances(vaultId);

548:   DataTypes.Debt memory debt = cauldron.debt(
549:       series.baseId,
550:       vault.ilkId
551:   );

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L180

USING > 0 COSTS LESS GAS THAN != 0 WHEN USED ON A UINT

This change saves 3 gas per instance

There are 2 instances of this issue:

325:   if (baseIn != 0) {

377:   if (artIn != 0) {

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L325

USING PRIVATE RATHER THAN PUBLIC FOR CONSTANTS, SAVES GAS

If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table

There is 1 instances of this issue:

59:    ICauldron public immutable cauldron;

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L59

X += Y COSTS MORE GAS THAN X = X + Y FOR STATE VARIABLES (SAME FOR X -= Y)

There are 7 instances of this issue:

204:   limits_.sum += auction_.ink;

259:   limits[auction_.ilkId][auction_.baseId].sum -= auction_.ink;

430:   limits_.sum -= auction_.ink;

443:   auction_.ink -= inkOut.u128();

444:   auction_.art -= artIn.u128();

450:   limits_.sum -= inkOut.u128();

598:   liquidatorCut -= auctioneerCut;

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L204

USAGE OF UINTS/INTS SMALLER THAN 32 BYTES (256 BITS) INCURS OVERHEAD

"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."

https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html
Use a larger size then downcast where needed

There are 14 instances of this issue:

47:    uint64 proportion,

48:    uint64 initialOffer

63:    uint128 public auctioneerReward = 0.01e18;

99:    uint64 proportion,

100:   uint64 initialOffer

129:   uint128 max

161:   function setAuctioneerReward(uint128 auctioneerReward_) external auth {

241:   start: uint32(block.timestamp),

289:   uint128 minInkOut,

290:   uint128 maxBaseIn

303:   uint128 artIn = uint128(

347:   uint128 minInkOut,

348:   uint128 maxArtIn,

582:   elapsed = uint32(block.timestamp) - uint256(auction_.start);

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#L47

USE CUSTOM ERRORS RATHER THAN REVERT()/REQUIRE() STRINGS

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information. In order to get rid of the multiple custom errors, a single universal custom error Exception(uint c) can be used. It would accept a single parameter that represents an error code used to make a reference to an off-chain dictionary of the errors' descriptions.

There are 17 instances of this issue:

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:   );

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#84

Replace x <= y with x < y + 1, and x >= y with x + 1 > y.

In the EVM, there is no opcode for >= or <=. When using greater than or equal, two operations are performed: > and =. Using strict comparison operators hence saves gas

There are 9 instances of this issue:

102:   require(initialOffer <= 1e18, "InitialOffer above 100%");

103:   require(proportion <= 1e18, "Proportion above 100%");

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

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

200:   require(limits_.sum <= limits_.max, "Collateral limit reached");

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),

https://github.com/code-423n4/2022-07-yield/blob/main/contracts/Witch.sol#102

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