Platform: Code4rena
Start Date: 10/11/2023
Pot Size: $28,000 USDC
Total HM: 5
Participants: 185
Period: 5 days
Judge: 0xDjango
Id: 305
League: ETH
Rank: 91/185
Findings: 1
Award: $9.97
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0xAnah, 0xhex, 0xta, ABAIKUNANBAEV, JCK, Raihan, Rickard, Sathish9098, ZanyBonzy, hunter_w3b, lsaudit, rumen, shamsulhaq123, tala7985, unique
9.97 USDC - $9.97
initializer
modifierIf we can just ensure that the initialize()
function can only be called from within the constructor, we shouldn’t need to worry about it getting called again.
src/LRTConfig.sol 41: function initialize( 42: address admin, 43: address stETH, 44: address rETH, 45: address cbETH, 46: address rsETH_ 47: ) 48: external 49: initializer 50: {
src/LRTDepositPool.sol 31: function initialize(address lrtConfigAddr) external initializer {
src/LRTOracle.sol 29: function initialize(address lrtConfigAddr) external initializer {
src/NodeDelegator.sol 26: function initialize(address lrtConfigAddr) external initializer {
src/RSETH.sol 32: function initialize(address admin, address lrtConfigAddr) external initializer {
src/oracles/ChainlinkPriceOracle.sol 27: function initialize(address lrtConfig_) external initializer {
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 perform the same offset calculation every time. Help the Optimizer by saving a storage variable's reference instead of repeatedly fetching it.
To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. As an example, instead of repeatedly calling someMap[someIndex]
, save its reference like this: SomeStruct storage someStruct = someMap[someIndex]
and use it.
src/LRTConfig.sol 29: if (!isSupportedAsset[asset]) { 82: if (isSupportedAsset[asset]) { 85: isSupportedAsset[asset] = true;
src/LRTConfig.sol 87: depositLimitByAsset[asset] = depositLimit; 102: depositLimitByAsset[asset] = depositLimit;
src/LRTConfig.sol 118: if (assetStrategy[asset] == strategy) { 121: assetStrategy[asset] = strategy;
src/LRTConfig.sol 158: if (tokenMap[key] == val) { 161: tokenMap[key] = val;
src/LRTConfig.sol 174: if (contractMap[key] == val) { 177: contractMap[key] = val;
src/LRTOracle.sol 46: return IPriceFetcher(assetPriceOracle[asset]).getAssetPrice(asset); 97: assetPriceOracle[asset] = priceOracle;
src/oracles/ChainlinkPriceOracle.sol 38: return AggregatorInterface(assetPriceFeed[asset]).latestAnswer(); 47: assetPriceFeed[asset] = priceFeed;
address/bytes32
mappings can be combined into a single mapping of an address/bytes32
to a struct
, where appropriatesrc/LRTConfig.sol 13: mapping(bytes32 tokenKey => address tokenAddress) public tokenMap; 14: mapping(bytes32 contractKey => address contractAddress) public contractMap;
src/LRTConfig.sol 15: mapping(address token => bool isSupported) public isSupportedAsset; 16: mapping(address token => uint256 amount) public depositLimitByAsset; 17: mapping(address token => address strategy) public override assetStrategy;
Internal functions in Solidity are only intended to be invoked within the contract or by other internal functions. If an internal function is not called anywhere within the contract, it serves no purpose and contributes unnecessary overhead during deployment. Removing such functions can lead to substantial gas savings.
11: function checkNonZeroAddress(address address_) internal pure { 12: if (address_ == address(0)) revert ZeroAddressNotAllowed(); 13: }
+=
and -=
are more expensiveLRTDepositPool.sol line 83 -> assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
LRTDepositPool.sol line 83 -> assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);
LRTOracle.sol line 71 -> totalETHInPool += totalAssetAmt * assetER;
In computation, the form x= x + y
is cheaper than x += y
, and x= x - y
is cheaper than x -= y
; Not sure why but have seen this in many audit reports. I guess its related to below : x +=y
can be seen as x += 1
(most expensive) about y times and we know that x+=1
is most expensive form versus x++
(6 gas less than x+=1
) and ++x
(5 gas less than x++
)
Gas: If we look at all the instances the gas saved adds up. However, there is careful consideration as the x+=y
format is more readable so it's important protocol plugs the numbers to see gas savings and see if worth the readability. My take is readability is not that harmed there are code parts longer than the x = x + y
form in functions, plus I believe it's best to put gas more important to save deployment and user costs.
Manual Analysis
It is recommended to use the form x = x + y;
or x = x-y;
See the example below:
assetLyingInNDCs = assetLyingInNDCs + IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
src/LRTDepositPool.sol 109: rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
src/LRTOracle.sol 78: return totalETHInPool / rsEthSupply;
#0 - c4-pre-sort
2023-11-17T03:50:45Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-12-01T16:17:14Z
fatherGoose1 marked the issue as grade-b