LSD Network - Stakehouse contest - ReyAdmirado's results

A permissionless 3 pool liquid staking solution for Ethereum.

General Information

Platform: Code4rena

Start Date: 11/11/2022

Pot Size: $90,500 USDC

Total HM: 52

Participants: 92

Period: 7 days

Judge: LSDan

Total Solo HM: 20

Id: 182

League: ETH

Stakehouse Protocol

Findings Distribution

Researcher Performance

Rank: 25/92

Findings: 3

Award: $546.83

QA:
grade-a
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

3.1274 USDC - $3.13

Labels

2 (Med Risk)
partial-50
satisfactory
duplicate-378

External Links

Judge has assessed an item in Issue #38 as M risk. The relevant finding follows:

  1. require check will always be false!! the require check will always be false so it the function will not do what it was designed to do and it will always revert Unnecessary update to same status

LiquidStakingManager.sol#L280

#0 - c4-judge

2022-11-29T15:51:15Z

dmvt marked the issue as duplicate of #67

#1 - c4-judge

2022-11-30T11:40:17Z

dmvt marked the issue as satisfactory

#2 - c4-judge

2022-11-30T11:40:22Z

dmvt marked the issue as partial-50

#3 - C4-Staff

2022-12-21T00:11:18Z

JeeberC4 marked the issue as duplicate of #378

1. typo

Instane --> Instance

depoistor --> depositor

trigerringAddress --> triggeringAddress

admiting --> admitting

Unrecognised --> Unrecognized

validtor --> validator

initals --> initials

overriden --> overridden

customise --> customize

Inconsisent --> Inconsistent

determins --> determines

publice --> public

collatearlized --> collateralized

amongs --> amongst

2. use of floating pragma

Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.

3. event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it’s not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

4. _safemint() should be used rather than _mint() wherever possible

5. constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

6. require check will always be false!!

the require check will always be false so it the function will not do what it was designed to do and it will always revert Unnecessary update to same status

7. state var should be constant

MODULO

8. lines are too long

Usually lines in source code are limited to 80 characters. Its advised to keep lines lower than 120 characters. Today’s screens are much larger so it’s reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

9. open todos

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

10. Outdated compiler version

Using very old versions of Solidity prevents benefits of bug fixes and newer security checks. Using the latest versions might make contracts susceptible to undiscovered compiler bugs

11. incorrect functions visibility

Whenever a function is not being called internally in the code, it can be easily declared as external. While the entire code base have explicit visibilities for every function, some of them can be changed to be external.

totalRewardsReceived

isKnotDeregistered

deployNewLiquidStakingDerivativeNetwork

withdrawETHForStaking

withdrawETH

12. 2**<n> - 1 should be re-written as type(uint<n>).max

Earlier versions of solidity can use uint<n>(-1) instead. Expressions not including the - 1 can often be re-written to accommodate the change (e.g. by using a > rather than a >=, which will also save some gas)

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

14. Unused/empty receive()/fallback() function

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth))). Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds

15. Use Underscores for Number Literals

There are multiple occasions where certain numbers have been hardcoded, either in variables or in the code itself. Large numbers can become hard to read.

starting from right put underscores every 3 numbers

16. Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract’s value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don’t get out of sync. https://medium.com/coinmonks/gas-cost-of-solidity-library-functions-dbe0cedd4678

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

many require()s are being used twice but some are used over 3 times:

require(smartWallet != address(0), "No smart wallet"); // used 4 times

18. abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode instead

#0 - c4-judge

2022-11-29T15:50:28Z

dmvt marked the issue as grade-a

Findings Information

Awards

68.1383 USDC - $68.14

Labels

bug
G (Gas Optimization)
grade-b
edited-by-warden
G-01

External Links

Gas

1. State variables only set in the constructor should be declared immutable.

Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas).

2. state variables can be packed into fewer storage slots

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper.

put it near one of the state vars that have less than 32 bytes (like address ones)

3. Multiple address/ID mappings can be combined into a single mapping of an address/ID 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 operations.

consider putting them with other mappings that with them would be under 32byte to save 20000 gas each

here

more possibilities here if checked carefully

4. state variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

because it if statement will be true cache transferHookProcessor first

cache before if statement lpTokenETH

cache enableWhitelisting before emit

cache smartWalletRepresentative[smartWallet]before if statement, highly possible gas save

gatekeeper

stakehouse

5. Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a } if(a <= b); x = b - a => if(a <= b); unchecked { x = b - a } this will stop the check for overflow and underflow so it will save gas

6. <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves gas

7. not using the named return variables when a function returns, wastes deployment gas

also making 2 stack variables without being used

8. can make the variable outside the loop to save gas

9. ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for-loop and while-loops

In Solidity 0.8+, there’s a default overflow check on unsigned integers. It’s possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.

10. require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

11. splitting require() statements that use && saves gas

this will have a large deployment gas cost but with enough runtime calls the split require version will be 3 gas cheaper

12. using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.

13. internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

_onWithdraw(its inherited but still used once in the whole project)

_assertUserHasEnoughGiantLPToClaimVaultLP

_onDepositETH(its inherited but still used once in the whole project)

_isNodeRunnerValid

_stake

_joinLSDNStakehouse

_createLSDNStakehouse

_initSavETHVault

_initStakingFundsVault

_calculateCommission

_assertEtherIsReadyForValidatorStaking

14. internal or private functions not called by the contract or inherited ones should be removed to save deployment gas

_beforeTokenTransfer

_afterTokenTransfer

15. bytes constants are more efficient than string constants

If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity.

16. public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents’ functions and change the visibility from external to public and can save gas by doing so.

totalRewardsReceived

isKnotDeregistered

deployNewLiquidStakingDerivativeNetwork

withdrawETHForStaking

withdrawETH

17. should use arguments instead of state variable

This will save near 97 gas

18. Don’t compare boolean expressions to boolean literals

if (<x> == true) => if (<x>) if (<x> == false) => if (!<x>) require(<x> == true) => require(<x>) require(<x> == false) => require(!<x>)

#0 - c4-judge

2022-11-29T15:47:05Z

dmvt 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