Ethos Reserve contest - Madalad's results

A CDP-backed stablecoin platform designed to generate yield on underlying assets to establish a sustainable DeFi stable interest rate.

General Information

Platform: Code4rena

Start Date: 16/02/2023

Pot Size: $144,750 USDC

Total HM: 17

Participants: 154

Period: 19 days

Judge: Trust

Total Solo HM: 5

Id: 216

League: ETH

Ethos Reserve

Findings Distribution

Researcher Performance

Rank: 149/154

Findings: 1

Award: $42.07

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Gas Report

abi.encodePacked is more gas efficient than abi.encode

abi.encode pads all elementary types to 32 bytes, whereas abi.encodePacked will only use the minimal required memory to encode the data. See here for more info.

Instances: 2

<br>

Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner 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 (2400 per instance).

Instances: 14

<br>

Use assembly to check for address(0)

Saves 16000 deployment gas per instance and 6 runtime gas per instance.

assembly { if iszero(_addr) { mstore(0x00, "zero address") revert(0x00, 0x20) } }

Instances: 12

<br>

Use assembly to calculate hashes

Saves 5000 deployment gas per instance and 80 runtime gas per instance.

Unoptimized

function solidityHash(uint256 a, uint256 b) public view { //unoptimized keccak256(abi.encodePacked(a, b)); }

Optimized

function assemblyHash(uint256 a, uint256 b) public view { //optimized assembly { mstore(0x00, a) mstore(0x20, b) let hashedVal := keccak256(0x00, 0x40) } }

Instances: 13

<br>

Cache storage variables rather than re-reading from storage

Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read.

Caching a mapping’s value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations. Caching an array’s struct avoids recalculating the array offsets into memory/calldata.

Instances: 25

<br>

Use calldata instead of memory for function arguments that are read only

Instances: 11

<br>

Initializing variable to default value costs unnecessary gas

Instances: 10

<br>

Duplicated require/revert checks should be refactored to a modifier or function.

Instances: 6

<br>

require/revert strings longer than 32 bytes cost extra gas

Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met. If the contract(s) in scope allow using Solidity >=0.8.4, consider using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using NatSpec.

Instances: 35

<br>

Use indexed to save gas

Using indexed for value type event parameters (address, uint, bool) saves at least 80 gas in emitting the event (https://gist.github.com/Tomosuke0930/9bf61e01a8c3e214d95a9b84dcb41d97).

Note that for other types however (string, bytes), it is more expensive.

Instances: 113

<br>

Use constant, immutable where applicable

Use immutable if you want to assign a permanent value at construction. Use constants if you already know the permanent value. Both get directly embedded in bytecode, saving SLOAD. Variables only set in the constructor and never edited afterwards should be marked as immutable, as it would avoid the expensive storage-writing operation in the constructor (around 20 000 gas per variable) and replace the expensive storage-reading operations (around 2100 gas per reading) to a less expensive value reading (3 gas).

Instances: 1

<br>

Inline internal functions that are only called once

Saves 20-40 gas per instance. See https://blog.soliditylang.org/2021/03/02/saving-gas-with-simple-inliner/ for more details.

Instances: 45

<br>

Expressions for constant values such as a call to keccak256 should use immutable rather than constant

See this issue for a detail description of the issue.

Instances: 8

<br>

Use unchecked for operations that cannot overflow/underflow

By bypassing Solidity's built in overflow/underflow checks using unchecked, we can save gas. This is especially beneficial for the index variable within for loops (saves 120 gas per iteration).

Instances: 15

<br>

Use private rather than public for constants

Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table. If needed to be viewed externally, the values can be read from the verified contract source code.

Instances: 27

<br>

Change public functions to external

Functions marked as public that are not called internally should be set to external to save gas and improve code quality. External call cost is less expensive than of public functions.

Instances: 4

<br>

Use a more recent version of Solidity

Use a Solidity version of at least 0.8.2 to get simple compiler automatic inlining.

Use a Solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads.

Use a Solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings.

Use a Solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.

Use a Solidity version of at least 0.8.12 to get string.concat() to be used instead of abi.encodePacked(<str>,<str>).

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions.

Instances: 12

<br>

Naming a return variable and still calling the return keyword wastes gas

Instances: 18

<br>

Split require statements using &&

Replacing with two separate require statements saves 16 gas per instance.

Instances: 1

<br>

x += y costs more gas than x = x + y for state variables

Instances: 9

<br>

Usage of uint 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.

Consider using a larger size then downcasting where needed.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html

Instances: 6

<br>

++i costs less gas than i++

True for --i/i-- as well, and is especially important in for loops. Saves 5 gas per iteration.

Instances: 17

<br>

#0 - c4-judge

2023-03-09T18:05:52Z

trust1995 marked the issue as grade-b

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