Platform: Code4rena
Start Date: 07/03/2024
Pot Size: $63,000 USDC
Total HM: 20
Participants: 36
Period: 5 days
Judge: cccz
Total Solo HM: 11
Id: 349
League: BLAST
Rank: 15/36
Findings: 3
Award: $474.94
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xAadi
Also found by: 0x11singh99, bareli, hihen
290.0407 USDC - $290.04
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/oracles/aggregators/MagicLpAggregator.sol#L34 https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/oracles/aggregators/MagicLpAggregator.sol#L42
MagicLpAggregator::_getReserves
is expected to return baseReserve
and quoteReserve
values but it will always return (0,0) due to missing return statement inside this function. Neither any named returned variable declared. Which will by default return (0,0) from returns (uint256, uint256) from _getReserves
function.
This _getReserves
function is used inside latestAnswer
function to get values of baseReserve
and quoteReserve
. Since these values will always be 0 due to coming from _getReserves
function. And these values will still be 0 after multiplying at next lines(43&44). And final sum (baseReserve + quoteReserve
)of these values will be also 0 which will make whole answer 0 by multiplying in int256((minAnswer * (baseReserve + quoteReserve)) / pair.totalSupply())
return statement.
So latestAnswer
will always return 0 since these both values sum is always be 0. Which will break whole purpose of getting price wherever it is used to get the price.
src/oracles/aggregators/MagicLpAggregator.sol#L33-L46
33: function _getReserves() internal view virtual returns (uint256, uint256) { (uint256 baseReserve, uint256 quoteReserve) = pair.getReserves(); //@audit missing return statement to return these values so function will return 0,0 all the times. Named return also neither declared nor used. } 37: function latestAnswer() public view override returns (int256) { uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals())); uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized; 42: (uint256 baseReserve, uint256 quoteReserve) = _getReserves(); //@audit will always return 0,0 43: baseReserve = baseReserve * (10 ** (WAD - baseDecimals)); //still 0 44: quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals)); //still 0 45: return int256((minAnswer * (baseReserve + quoteReserve)) / pair.totalSupply()); //final sum of 0 will be multiplied here will make whole answer 0 }
latestAnswer
will always return 0 which will defeat whole purpose of getting price wherever it is used to get the price. SInce it will return always 0 price which is not expected from here.
Manual Review
Add return
statement into _getReserves
function. Which will return proper values of baseReserve
and quoteReserve
received from pair.getReserves()
not default 0.
File: src/oracles/aggregators/MagicLpAggregator.sol 33: function _getReserves() internal view virtual returns (uint256, uint256) { 34: (uint256 baseReserve, uint256 quoteReserve) = pair.getReserves(); + 35 return (baseReserve , quoteReserve); }
Other
#0 - 0xm3rlin
2024-03-15T00:58:59Z
confirmed duplicate
#1 - c4-pre-sort
2024-03-15T12:23:16Z
141345 marked the issue as duplicate of #146
#2 - c4-judge
2024-03-29T16:52:50Z
thereksfour marked the issue as satisfactory
#3 - c4-judge
2024-03-29T17:04:19Z
thereksfour changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-03-31T06:51:12Z
thereksfour changed the severity to 3 (High Risk)
#5 - c4-judge
2024-04-05T11:13:05Z
thereksfour changed the severity to 2 (Med Risk)
🌟 Selected for report: ether_sky
Also found by: 0x11singh99, 0xE1, 0xJaeger, Bauchibred, Bigsam, Bozho, Breeje, DarkTower, HChang26, SpicyMeatball, Trust, ZanyBonzy, albahaca, bareli, blutorque, grearlake, hals, hassan-truscova, hihen, oualidpro, pfapostol, ravikiranweb3, slvDev, zhaojie
15.328 USDC - $15.33
address(0)
check in constructor
Not checking for address(0)
in the constructor
could lead to scenarios where the contract is deployed with invalid or uninitialized parameters, which might cause re-deployment or in worst case always set to 0 which can cause undesired behavior or even security vulnerabilities.
_stakingToken
for address(0)
File : src/staking/LockingMultiRewards.sol 112: constructor( ... 135: stakingToken = _stakingToken;
pair_
, baseOracle_
and quoteOracle_
for address(0)
File : src/oracles/aggregators/MagicLpAggregator.sol 21: constructor(IMagicLP pair_, IAggregator baseOracle_, IAggregator quoteOracle_) { 22: pair = pair_; 23: baseOracle = baseOracle_; 24: quoteOracle = quoteOracle_;
_rewardPerToken
appears to be intended for internal
use within the contract
The function _rewardPerToken
appears to be intended for internal
use within the contract, as indicated by the leading underscore (_
). However, it has been mistakenly marked as public
, which means it is accessible from outside the contract. Fortunately, marking it as public
with a view modifier doesn't pose any security risks, but it does expose an internal
function to external
callers unnecessarily.
File : src/staking/LockingMultiRewards.sol 277: function _rewardPerToken(address rewardToken, uint256 lastTimeRewardApplicable_, uint256 totalSupply_) public view returns (uint256) {
safeTransfer
doesn't check for address(0)
before transferSince safeTransfer
doesn't check for address(0) it totally upto the ERC20 contract's transfer
function definition. Some libraries like solmate ERC20 transfer
doesn't check for address 0 and amount will be transferred if address 0 passed. So it always better to check for address 0 before transferring tokens to it where safeTransfer/transfer used.
to
for address(0)
in rescue functionFile : src/mimswap/MagicLP.sol 461: function rescue(address token, address to, uint256 amount) external onlyImplementationOwner { if (token == _BASE_TOKEN_ || token == _QUOTE_TOKEN_) { revert ErrNotAllowed(); } 466: token.safeTransfer(to, amount); emit TokenRescue(token, to, amount); }
to
for address(0)
in _transferBaseOut functionFile : src/mimswap/MagicLP.sol 581: function _transferBaseOut(address to, uint256 amount) internal { if (amount > 0) { 583: _BASE_TOKEN_.safeTransfer(to, amount); } }
to
for address(0)
File : src/mimswap/MagicLP.sol 587: function _transferQuoteOut(address to, uint256 amount) internal { if (amount > 0) { 589: _QUOTE_TOKEN_.safeTransfer(to, amount); } }
execute
function check success
for true/false
when calling low level call
If success
is false
then revert just after low level call fails immediately is safer approach rather than relying on the caller of this execute
function, since execute
is external
function so it is safe to revert whenever low level call
failed.
File : src/blast/BlastGovernor.sol 53: function execute(address to, uint256 value, bytes calldata data) external onlyOwner returns (bool success, bytes memory result) { 54: (success, result) = to.call{value: value}(data); 55: }
LockingMultiRewards::withdraw
functionUpdating state var. after external call when state var. is not depends on that call. So it is safe to update it before to follow CEI. To avoid reentrancy possibilities. Although stakingToken
is immutable and fixed contract lesser chances to reenter still it is best practice to follow CEI as much as possible to avoid any kind of reentrancy.
This issue can be mitigated by following the Checks-Effects-Interactions Pattern which suggests that a smart contract should first do the relevant Checks, then make the relevant internal changes to its state (Effects), and only then interact with other smart contracts which may well be malicious.
File : src/staking/LockingMultiRewards.sol 170: function withdraw(uint256 amount) public virtual { if (amount == 0) { revert ErrZeroAmount(); } _updateRewardsForUser(msg.sender); _balances[msg.sender].unlocked -= amount; unlockedSupply -= amount; stakingToken.safeTransfer(msg.sender, amount); stakingTokenBalance -= amount; //@audit updating state var. after external call when this is not depends on that call. So it is safe to update it before to follow CEI. emit LogWithdrawn(msg.sender, amount); }
Note: N-03 covers only those instances which were missed by analyzer.
It is always better to write right spellings to improve readability and avoids confusion when writing function or variable names inside contract.
Multipler
in lockingBoostMultiplerInBips
File : src/staking/LockingMultiRewards.sol -84: uint256 public immutable lockingBoostMultiplerInBips; +84: uint256 public immutable lockingBoostMultiplierInBips;
_udpateUserRewards
function name can create confusion or other logic breakingFile : src/staking/LockingMultiRewards -536: function _udpateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal { +536: function _updateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal {
Nomalized
in baseAnswerNomalized
File : src/oracles/aggregators/MagicLpAggregator.sol 37: function latestAnswer() public view override returns (int256) { -38: uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); +38: uint256 baseAnswerNormalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); 39: uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals())); -40: uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized; +40: uint256 minAnswer = baseAnswerNormalized < quoteAnswerNormalized ? baseAnswerNormalized : quoteAnswerNormalized;
locked
and unlocked
functionsChanging the names of the locked
and unlocked
functions to userLockedBalance
and userUnlockedBalance
, respectively, makes the purpose of these functions more explicit and understandable. Improves code readability and clarity.
File : src/staking/LockingMultiRewards.sol 227: function locked(address user) external view returns (uint256) { return _balances[user].locked; } 231: function unlocked(address user) external view returns (uint256) { return _balances[user].unlocked; }
By replacing 1e18 with a named constant, you make your code more readable, maintainable, and less prone to errors.
File : src/staking/LockingMultiRewards.sol 292: function _earned( ... 294: return ((balance_ * pendingUserRewardsPerToken) / 1e18) + rewards[user][rewardToken];
_rewardLock
instead of _userRewardLock[user]
improves readability and maintains a single flow in the codeBy assigning _userRewardLock[user]
to _rewardLock
as storage pointer at the beginning of the function, you establish a single point of reference for the reward lock associated with the user. It is better to use _rewardLock
throughout the function to improve readability. Since both are pointing to same storage variable.
File : src/staking/LockingMultiRewards.sol 597: function _getRewards(address user) internal { 598: RewardLock storage _rewardLock = _userRewardLock[user]; ... 648: _userRewardLock[user].items.push(RewardLockItem({token: rewardToken, amount: rewardAmount}));
rescue
before calling safeTransfer
check amount
for zero
safeTransfer doesn't check for amount 0. so it better to check that before. Since amount 0 transferring has no effect.
File : src/mimswap/MagicLP.sol 461: function rescue(address token, address to, uint256 amount) external onlyImplementationOwner { if (token == _BASE_TOKEN_ || token == _QUOTE_TOKEN_) { revert ErrNotAllowed(); } 466: token.safeTransfer(to, amount); emit TokenRescue(token, to, amount); }
_
) before internal function names is a convention to visually distinguish them from external or public functionsIt is a general convention to use _ before private/internal function to improve clarity.
File : src/blast/BlastBox.sol -103: function isOwner(address _account) internal view override returns (bool) { +103: function _isOwner(address _account) internal view override returns (bool) {
It creates the confusion and reduce readability to use both named return variables and explicit returns. Use one of that method consistently to maintain readability.
File : src/blast/BlastOnboardingBoot.sol 78: function claimable(address user) external view returns (uint256 shares) { ... 83: return _claimable(user); 86: function previewTotalPoolShares() external view returns (uint256 baseAdjustedInAmount, uint256 quoteAdjustedInAmount, uint256 shares) { ... 89: return router.previewCreatePool(I, baseAmount, quoteAmount); 155: function _claimable(address user) internal view returns (uint256 shares) { ... 163: return (userLocked * totalPoolShares) / totalLocked;
#0 - c4-pre-sort
2024-03-15T15:00:44Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-16T01:07:05Z
173 0x11singh99 l r nc 2 0 7
L 1 l L 2 n L 3 n L 4 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/102 L 5 l
NC 1 n NC 2 n NC 3 i NC 4 n NC 5 n NC 6 i NC 7 n
#2 - c4-judge
2024-03-29T16:53:09Z
thereksfour marked the issue as grade-b
🌟 Selected for report: hihen
Also found by: 0x11singh99, Bozho, Sathish9098, albahaca, clara, dharma09, oualidpro, pfapostol, slvDev
169.5732 USDC - $169.57
Note: These all are unique finding are not present in analyzer report.Gas Mentioned in these findings title is total gas saved by all instances of that finding covered in this report.
[G-01] State variables can be packed into fewer storage slot by reducing their size (Saves ~4000 Gas)
[G-02] Reduce the size of struct variables and pack them together to save storage slots (Saves ~2000 Gas)
[G-03] Cache external function call to save gas instead of re-calling avoid 1 external call
[G-04] State variable only set in the constructor should be declare immutable (Gas Saved ~2100 Gas)
[G-05] Check before updating bool state variable to avoid with same bool value(Saves ~800 Gas)
[G-06] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct
[G-08] Use calldata
instead of memory
if parameter not get mutated(Gas Saved ~100 Gas)
[G-10] Within contract's function call should be cached instead of re-calling the same function
[G-11] Nesting if
-statements is cheaper than using &&
(Gas Saved ~24 Gas)
[G-12] Switch
the order of if
statement to fail early saves gas(~2000 Gas of Gcoldsload) half of the time
[G-13] Refactor _getRewards
function to save 1 SLOAD
by caching function call
[G-15] Do not cache external call in stack variable if stack variable used only once
All these findings are good findings and 100% safe to implement at no security/logic risk. They all are found by thorough manual review.
The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if values combined are <= 32 bytes). If the variables packed together are retrieved together in functions (more likely with structs), we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).
SAVE: ~4000 GAS, 2 SLOT
_LP_FEE_RATE_
, _K_
and _I_
can be packed in single slot SAVES: ~4000 Gas, 2 storage SlotSince _LP_FEE_RATE_
,_K_
and _I_
can not be more than from their max values:
Here is the constant max values ::
When _LP_FEE_RATE_
,_K_
and _I_
updated inside init
function and setParameters
function they are updated only inside those functions and before assigning values in these variables ,values are checked with these values respective MAX constants so that their value can never be more than those defined constants.
That means _LP_FEE_RATE_
can be max. 1e16, _K_
can be max. 1e18 and _I_
can be maximum 1e36.
So their sizes can be reduced to uint64
, uint64
and uint128
. So these three can be packed into one slot and saves Total 2 Storage slots.
uint64
can hold up to 1.8e19 and uint128
can hold up to ~3.4e38. So they are sufficient enough to hold above values.
File : mimswap/MagicLP.sol 63: uint256 public constant MAX_I = 10 ** 36; 64: uint256 public constant MAX_K = 10 ** 18; ... 66: uint256 public constant MAX_LP_FEE_RATE = 1e16; // 1%
So we can easily reduce there sizes and pack them into single slot.
File : mimswap/MagicLP.sol 80: uint256 public _LP_FEE_RATE_; 81: uint256 public _K_; 82: uint256 public _I_;
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol -80: uint256 public _LP_FEE_RATE_; -81: uint256 public _K_; -82: uint256 public _I_; +80: uint64 public _LP_FEE_RATE_; +81: uint64 public _K_; +82: uint128 public _I_;
SAVE: ~2000 GAS, 1 SLOT
on every key-value pair in _rewardData
mappingperiodFinish
and lastUpdateTime
can be reduced to uint64
and packed with bool exists
in single slotSince periodFinish
and lastUpdateTime
hold timestamp so these two can be easily reduced to uint64
.
uint64
is very big number to hold any realistic timestamp. So it is completely safe to reduce time holding variables to this size. And save slot.
File : staking/LockingMultiRewards.sol 50: struct Reward { 51: uint256 periodFinish; 52: uint256 rewardRate; 53: uint256 rewardPerTokenStored; 54: bool exists; 55: uint248 lastUpdateTime; 56: }
LockingMultiRewards.sol#L50-L56
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol 50: struct Reward { - 51: uint256 periodFinish; 52: uint256 rewardRate; 53: uint256 rewardPerTokenStored; 54: bool exists; + 51: uint64 periodFinish; + 55: uint64 lastUpdateTime; - 55: uint248 lastUpdateTime; 56: }
_MT_FEE_RATE_MODEL_.maintainer()
_MT_FEE_RATE_MODEL_.maintainer()
will return same value both times in flashLoan
function. Since it's value haven't impacted by the flashLoan function flow from 319 to 342 line. So it is reduntant and gas consuming to call this external function again for same value. It is better to cache _MT_FEE_RATE_MODEL_.maintainer()
return value first time and use that value both times when needed. Avoids 1 external call.
File : mimswap/MagicLP.sol 290: function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external nonReentrant { ... 319: _transferBaseOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); ... 341: _transferQuoteOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); ... }
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol + IFeeRateModel _MT_FEE_RATE_MODEL_maintainer = _MT_FEE_RATE_MODEL_.maintainer(); -319: _transferBaseOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); +319: _transferBaseOut(_MT_FEE_RATE_MODEL_maintainer, mtFee); ... -341: _transferQuoteOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); +341: _transferQuoteOut(_MT_FEE_RATE_MODEL_maintainer, mtFee);
State variables only set in the constructor should be declared immutable as it avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).
registry
immutable will save 1 Storage slot ( ~2100 Gas)registry
can be marked immutable. Since it is only set in its child contract BlastOnboarding
constructor. And apart from this it is never changed. Neither it is set in any BlastOnboardingData
function where this variable defined nor it is set in child BlastOnboarding
function apart from it's constructor. Also BlastOnboarding
not inherited further. So it is completely safe to make this variable immutable so save 1 storage slot.
File : blast/BlastOnboarding.sol 93: constructor(BlastTokenRegistry registry_, address feeTo_) { ... 84: registry = registry_; 85: feeTo = feeTo_; 86: }
File : blast/BlastOnboarding.sol 33: BlastTokenRegistry public registry;
Recommended Mitigation Steps:
File : blast/BlastOnboarding.sol -33: BlastTokenRegistry public registry; +33: BlastTokenRegistry public immutable registry;
Check state variable before updating to avoid updating with same bool value. It is highly possible that passed value to change can be same since bool can hold only 2 values. So it better to check that updating value is different from previous value. To avoid SSTORE opcode.
File : blast/BlastOnboardingBoot.sol 146: function setReady(bool _ready) external onlyOwner onlyState(State.Closed) { 147: ready = _ready; 148: emit LogReadyChanged(ready); 149: }
BlastOnboardingBoot.sol#L146-L149
Recommended Mitigation Steps:
File : blast/BlastOnboardingBoot.sol 146: function setReady(bool _ready) external onlyOwner onlyState(State.Closed) { + if(_ready == ready){ + revert(); + } 147: ready = _ready; 148: emit LogReadyChanged(ready); 149: }
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
File : staking/LockingMultiRewards.sol 90: mapping(address user => Balances balances) private _balances; 91: mapping(address user => LockedBalance[] locks) private _userLocks; 92: mapping(address user => RewardLock rewardLock) private _userRewardLock; 93: 94: mapping(address user => mapping(address token => uint256 amount)) public userRewardPerTokenPaid; 95: mapping(address user => mapping(address token => uint256 amount)) public rewards; 96: mapping(address user => uint256 index) public lastLockIndex;
LockingMultiRewards.sol#L90-L96
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol // Define a struct to hold all necessary data + struct UserData { + Balances balances; + LockedBalance[] locks; + RewardLock rewardLock; + mapping(address => uint256) userRewardPerTokenPaid; + mapping(address => uint256) rewards; + uint256 lastLockIndex; + } // Define a single mapping to store instances of the struct + mapping(address => UserData) private userData; -90: mapping(address user => Balances balances) private _balances; -91: mapping(address user => LockedBalance[] locks) private _userLocks; -92: mapping(address user => RewardLock rewardLock) private _userRewardLock; -93: -94: mapping(address user => mapping(address token => uint256 amount)) public userRewardPerTokenPaid; -95: mapping(address user => mapping(address token => uint256 amount)) public rewards; -96: mapping(address user => uint256 index) public lastLockIndex;
loop
should be cached(Gas Saved: ~300 Gas per iteration)Caching repeated accesses to the same mapping or array key/index in smart contracts can lead to significant gas savings. In Solidity, each read operation from storage (like accessing a value in a mapping or array using a key or index) costs gas. By storing the accessed value in a local variable and reusing it within the function, you avoid multiple expensive storage read operations. This practice is particularly beneficial in loops or functions with multiple reads of the same data. Implementing this caching approach enhances efficiency and reduces transaction costs, which is crucial for optimizing smart contract performance and user experience on the blockchain.
locks.length
and lastLockIndex[user]
can be cached SAVES: 3 SLOADs per iteration
.Since they are dependent on loop index so they cant be cached outside loop. But reading them in single iteration more than once can be more costly so it is better to cache them inside loop to avoid 3 SLOADs per iteration. And use cached value in other part of the loop instead re-reading from storage more than once inside loop.
Note: This locks.length
is not loop iteration length which is covered by analyzer it is compltely diffrent and loop are not running this length times
File : staking/LockingMultiRewards.sol 397: function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators { ... 403: 404: // Release all expired users' locks 405: for (uint256 i; i < users.length; ) { ... 409: 410: if (locks.length == 0) {//@audit locks.length read 1st 411: revert ErrNoLocks(); 412: } ... 415: 416: // Prevents processing `lastLockIndex` out of order 417: if (index == lastLockIndex[user] && locks.length > 1) {//@audit locks.length read 2nd time and lastLockIndex[user] read 418: revert ErrInvalidLockIndex(); 419: } ... 427: uint256 lastIndex = locks.length - 1;//@audit locks.length read 3rd time ... 430: if (lastLockIndex[user] == lastIndex) { //@audit lastLockIndex[user] read 431: lastLockIndex[user] = index; 432: }
LockingMultiRewards.sol#L397-L432
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol 397: function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators { ... 403: 404: // Release all expired users' locks 405: for (uint256 i; i < users.length; ) { ... + uint256 locks_length = locks.length; 409: -410: if (locks.length == 0) { +410: if (locks_length == 0) { 411: revert ErrNoLocks(); 412: } ... + uint256 _lastLockIndex_user = lastLockIndex[user]; 415: 416: // Prevents processing `lastLockIndex` out of order -417: if (index == lastLockIndex[user] && locks.length > 1) { +417: if (index == _lastLockIndex_user && locks_length > 1) { 418: revert ErrInvalidLockIndex(); 419: } ... -427: uint256 lastIndex = locks.length - 1; +427: uint256 lastIndex = locks_length - 1; ... -430: if (lastLockIndex[user] == lastIndex) { +430: if (_lastLockIndex_user == lastIndex) { 431: lastLockIndex[user] = index; 432: }
calldata
instead of memory
if parameter not get mutated(Gas Saved: ~100 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, removes the need for such a loop in the contract code and runtime execution.
If the array is passed to an internal
function which passes the array to another internal
function where the array is modified and therefore memory
is used in the external
call, it's still more gas-efficient to use calldata
when the external function uses modifiers, since the modifiers may prevent the internal
functions from being called. Structs
have the same overhead as an array of length one.
Change address[] memory users
to calldata since it is not mutated.
File : staking/LockingMultiRewards.sol 397: function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators {
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol -397: function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators { +397: function processExpiredLocks(address[] calldata users, uint256[] calldata lockIndexes) external onlyOperators {
File : blast/BlastOnboarding.sol 164: function claimTokenYields(address[] memory tokens) external onlyOwner {
Recommended Mitigation Steps:
File : blast/BlastOnboarding.sol -164: function claimTokenYields(address[] memory tokens) external onlyOwner { +164: function claimTokenYields(address[] calldata tokens) external onlyOwner {
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces 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.
_BASE_RESERVE_
and _QUOTE_RESERVE_
in correctRState
function saves 4 SLOADs(~400 Gas)
File : mimswap/MagicLP.sol 139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_ < _BASE_TARGET_) { 140: _RState_ = uint32(PMMPricing.RState.ONE); 141: _BASE_TARGET_ = _BASE_RESERVE_; 142: _QUOTE_TARGET_ = _QUOTE_RESERVE_; 143: } 144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_ < _QUOTE_TARGET_) { 145: _RState_ = uint32(PMMPricing.RState.ONE); 146: _BASE_TARGET_ = _BASE_RESERVE_; 147: _QUOTE_TARGET_ = _QUOTE_RESERVE_; 148: }
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol + uint112 _BASE_RESERVE = _BASE_RESERVE_; + uint112 _QUOTE_RESERVE = _QUOTE_RESERVE_; -139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_ < _BASE_TARGET_) { +139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE < _BASE_TARGET_) { 140: _RState_ = uint32(PMMPricing.RState.ONE); -141: _BASE_TARGET_ = _BASE_RESERVE_; -142: _QUOTE_TARGET_ = _QUOTE_RESERVE_; +141: _BASE_TARGET_ = _BASE_RESERVE; +142: _QUOTE_TARGET_ = _QUOTE_RESERVE; 143: } -144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_ < _QUOTE_TARGET_) { +144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE < _QUOTE_TARGET_) { 145: _RState_ = uint32(PMMPricing.RState.ONE); -146: _BASE_TARGET_ = _BASE_RESERVE_; -147: _QUOTE_TARGET_ = _QUOTE_RESERVE_; +146: _BASE_TARGET_ = _BASE_RESERVE; +147: _QUOTE_TARGET_ = _QUOTE_RESERVE; 148: }
_QUOTE_TOKEN_
and _BASE_TOKEN_
in sellBase
function saves 2 SLOADs(~200 Gas)
File : mimswap/MagicLP.sol 245: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); ... 262: _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); ... 264: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, to);
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol + address _BASE_TOKEN = _BASE_TOKEN_; + address _QUOTE_TOKEN = _QUOTE_TOKEN_; -245: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); +245: uint256 baseBalance = _BASE_TOKEN.balanceOf(address(this)); ... -262: _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); +262: _setReserve(baseBalance, _QUOTE_TOKEN.balanceOf(address(this))); ... -264: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, to); +264: emit Swap(address(_BASE_TOKEN), address(_QUOTE_TOKEN), baseInput, receiveQuoteAmount, msg.sender, to);
_QUOTE_TOKEN_
and _BASE_TOKEN_
in sellQuote
function saves 2 SLOADs(~200 Gas)
File : mimswap/MagicLP.sol 268: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); ... 285: _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); ... 287: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, to);
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol + address _BASE_TOKEN = _BASE_TOKEN_; + address _QUOTE_TOKEN = _QUOTE_TOKEN_; -268: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); +268: uint256 quoteBalance = _QUOTE_TOKEN.balanceOf(address(this)); ... -285: _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); +285: _setReserve(_BASE_TOKEN.balanceOf(address(this)), quoteBalance); ... -287: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, to); +287: emit Swap(address(_QUOTE_TOKEN), address(_BASE_TOKEN), quoteInput, receiveBaseAmount, msg.sender, to);
_BASE_TOKEN_
, _QUOTE_TOKEN_
, _BASE_RESERVE_
and _QUOTE_RESERVE_
in flashLoan
function saves 10 SLOADs(~1000 Gas)
File : mimswap/MagicLP.sol 290: function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external nonReentrant { ... 298: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 299: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 300: 301: // no input -> pure loss 302: if (baseBalance < _BASE_RESERVE_ && quoteBalance < _QUOTE_RESERVE_) { 303: revert ErrFlashLoanFailed(); 304: } 305: 306: // sell quote case 307: // quote input + base output 308: if (baseBalance < _BASE_RESERVE_) { 309: uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_); 310: (uint256 receiveBaseAmount, uint256 mtFee, PMMPricing.RState newRState, uint256 newQuoteTarget) = querySellQuote( 311: tx.origin, 312: quoteInput 313: ); 314: 315: if (uint256(_BASE_RESERVE_) - baseBalance > receiveBaseAmount) { 316: revert ErrFlashLoanFailed(); 317: } ... 325: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, assetTo); 326: } ... 330: if (quoteBalance < _QUOTE_RESERVE_) { 331: uint256 baseInput = baseBalance - uint256(_BASE_RESERVE_); 332: (uint256 receiveQuoteAmount, uint256 mtFee, PMMPricing.RState newRState, uint256 newBaseTarget) = querySellBase( 333: tx.origin, 334: baseInput 335: ); 336: 337: if (uint256(_QUOTE_RESERVE_) - quoteBalance > receiveQuoteAmount) { 338: revert ErrFlashLoanFailed(); 339: } ... 347: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, assetTo); 348: }
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol 290: function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external nonReentrant { ... + address _BASE_TOKEN = _BASE_TOKEN_; + address _QUOTE_TOKEN = _QUOTE_TOKEN_; + uint112 _BASE_RESERVE = _BASE_RESERVE_; + uint112 _QUOTE_RESERVE = _QUOTE_RESERVE_; -298: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); +298: uint256 baseBalance = _BASE_TOKEN.balanceOf(address(this)); -299: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); +299: uint256 quoteBalance = _QUOTE_TOKEN.balanceOf(address(this)); 300: 301: // no input -> pure loss -302: if (baseBalance < _BASE_RESERVE_ && quoteBalance < _QUOTE_RESERVE_) { +302: if (baseBalance < _BASE_RESERVE && quoteBalance < _QUOTE_RESERVE) { 303: revert ErrFlashLoanFailed(); 304: } 305: 306: // sell quote case 307: // quote input + base output -308: if (baseBalance < _BASE_RESERVE_) { +308: if (baseBalance < _BASE_RESERVE) { -309: uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_); +309: uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE); 310: (uint256 receiveBaseAmount, uint256 mtFee, PMMPricing.RState newRState, uint256 newQuoteTarget) = querySellQuote( 311: tx.origin, 312: quoteInput 313: ); 314: -315: if (uint256(_BASE_RESERVE_) - baseBalance > receiveBaseAmount) { +315: if (uint256(_BASE_RESERVE) - baseBalance > receiveBaseAmount) { 316: revert ErrFlashLoanFailed(); 317: } ... -325: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, assetTo); +325: emit Swap(address(_QUOTE_TOKEN), address(_BASE_TOKEN), quoteInput, receiveBaseAmount, msg.sender, assetTo); 326: } ... -330: if (quoteBalance < _QUOTE_RESERVE_) { +330: if (quoteBalance < _QUOTE_RESERVE) { -331: uint256 baseInput = baseBalance - uint256(_BASE_RESERVE_); +331: uint256 baseInput = baseBalance - uint256(_BASE_RESERVE); 332: (uint256 receiveQuoteAmount, uint256 mtFee, PMMPricing.RState newRState, uint256 newBaseTarget) = querySellBase( 333: tx.origin, 334: baseInput 335: ); 336: -337: if (uint256(_QUOTE_RESERVE_) - quoteBalance > receiveQuoteAmount) { +337: if (uint256(_QUOTE_RESERVE) - quoteBalance > receiveQuoteAmount) { 338: revert ErrFlashLoanFailed(); 339: } ... -347: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, assetTo); +347: emit Swap(address(_BASE_TOKEN), address(_QUOTE_TOKEN), baseInput, receiveQuoteAmount, msg.sender, assetTo); 348: }
Since totalSupply()
will return same value both times when call from inside a same function 2nd time when it doesn't impact the totalSupply() returning value yet. So it is better to cache it to avoid 1 function call.
totalSupply()
function call can be cachedFile : mimswap/MagicLP.sol 375: if (totalSupply() == 0) { ... 400: shares = DecimalMath.mulFloor(totalSupply(), mintRatio);
MagicLP.sol#L375, MagicLP.sol#L400
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol + uint256 _totalSupply = totalSupply(); -375: if (totalSupply() == 0) { +375: if (_totalSupply == 0) { ... -400: shares = DecimalMath.mulFloor(totalSupply(), mintRatio); +400: shares = DecimalMath.mulFloor(_totalSupply, mintRatio);
if
-statements is cheaper than using &&
(Gas Saved ~24 Gas)Nesting if-statements avoids the stack operations of setting up and using an extra jumpdest and saves 6 gas
File : staking/LockingMultiRewards.sol 326: if (tokenAddress == stakingToken && tokenAmount > stakingToken.balanceOf(address(this)) - stakingTokenBalance) { 327: revert ErrSkimmingTooMuch(); 328: }
LockingMultiRewards.sol#L326-L328
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol -326: if (tokenAddress == stakingToken && tokenAmount > stakingToken.balanceOf(address(this)) - stakingTokenBalance) { -327: revert ErrSkimmingTooMuch(); -328: } + if (tokenAddress == stakingToken) { + if (tokenAmount > stakingToken.balanceOf(address(this)) - stakingTokenBalance) { + revert ErrSkimmingTooMuch(); + } + }
File : staking/LockingMultiRewards.sol 417: if (index == lastLockIndex[user] && locks.length > 1) { 418: revert ErrInvalidLockIndex(); 419: }
LockingMultiRewards.sol#L417-L419
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol -417: if (index == lastLockIndex[user] && locks.length > 1) { -418: revert ErrInvalidLockIndex(); -419: } + if (index == lastLockIndex[user]){ + if(locks.length > 1){ + revert ErrInvalidLockIndex(); + } + }
File : mimswap/MagicLP.sol 139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_ < _BASE_TARGET_) { 140: _RState_ = uint32(PMMPricing.RState.ONE); 141: _BASE_TARGET_ = _BASE_RESERVE_; 142: _QUOTE_TARGET_ = _QUOTE_RESERVE_; 143: } 144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_ < _QUOTE_TARGET_) { 145: _RState_ = uint32(PMMPricing.RState.ONE); 146: _BASE_TARGET_ = _BASE_RESERVE_; 147: _QUOTE_TARGET_ = _QUOTE_RESERVE_; 148: }
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol -139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_ < _BASE_TARGET_) { -140: _RState_ = uint32(PMMPricing.RState.ONE); -141: _BASE_TARGET_ = _BASE_RESERVE_; -142: _QUOTE_TARGET_ = _QUOTE_RESERVE_; -143: } +139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE)) { + if(_BASE_RESERVE_ < _BASE_TARGET_){ +140: _RState_ = uint32(PMMPricing.RState.ONE); +141: _BASE_TARGET_ = _BASE_RESERVE_; +142: _QUOTE_TARGET_ = _QUOTE_RESERVE_; +143: } + } -144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_ < _QUOTE_TARGET_) { -145: _RState_ = uint32(PMMPricing.RState.ONE); -146: _BASE_TARGET_ = _BASE_RESERVE_; -147: _QUOTE_TARGET_ = _QUOTE_RESERVE_; -148: } +144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE)) { + if(_QUOTE_RESERVE_ < _QUOTE_TARGET_){ +145: _RState_ = uint32(PMMPricing.RState.ONE); +146: _BASE_TARGET_ = _BASE_RESERVE_; +147: _QUOTE_TARGET_ = _QUOTE_RESERVE_; +148: } + }
Switch
the order of if
statement to fail early saves gas(~2000 Gas of Gcoldsload) half of the timesIt is recommended to order checks from low to high gas consuming to revert early.
First if statement reads 2 state variables lastLockIndex[user]
and locks.length
and second reads only 1 state variable locks[index].unlockTime
so we switch these two statements to save 1 state variable read.
File : staking/LockingMultiRewards.sol 417: if (index == lastLockIndex[user] && locks.length > 1) { 418: revert ErrInvalidLockIndex(); 419: } 420: 421: // prohibit releasing non-expired locks 422: if (locks[index].unlockTime > block.timestamp) { 423: revert ErrLockNotExpired(); 424: }
LockingMultiRewards.sol#L417-L424
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol + // prohibit releasing non-expired locks + if (locks[index].unlockTime > block.timestamp) { + revert ErrLockNotExpired(); + } 417: if (index == lastLockIndex[user] && locks.length > 1) { 418: revert ErrInvalidLockIndex(); 419: } 420: -421: // prohibit releasing non-expired locks -422: if (locks[index].unlockTime > block.timestamp) { -423: revert ErrLockNotExpired(); -424: }
We can place the first if statement in the last because it is reading _INITIALIZED_
state variable and no one else also _INITIALIZED_
used after all if statements
File : mimswap/MagicLP.sol 99: if (_INITIALIZED_) { 100: revert ErrInitialized(); 101: } 102: if (mtFeeRateModel == address(0) || baseTokenAddress == address(0) || quoteTokenAddress == address(0)) { 103: revert ErrZeroAddress(); 104: } 105: if (baseTokenAddress == quoteTokenAddress) { 106: revert ErrBaseQuoteSame(); 107: } 108: if (i == 0 || i > MAX_I) { 109: revert ErrInvalidI(); 110: } 111: if (k > MAX_K) { 112: revert ErrInvalidK(); 113: } 114: if (lpFeeRate < MIN_LP_FEE_RATE || lpFeeRate > MAX_LP_FEE_RATE) { 115: revert ErrInvalidLPFeeRate(); 116: }
Recommended Mitigation Steps:
File : mimswap/MagicLP.sol -99: if (_INITIALIZED_) { -100: revert ErrInitialized(); -101: } 102: if (mtFeeRateModel == address(0) || baseTokenAddress == address(0) || quoteTokenAddress == address(0)) { 103: revert ErrZeroAddress(); 104: } 105: if (baseTokenAddress == quoteTokenAddress) { 106: revert ErrBaseQuoteSame(); 107: } 108: if (i == 0 || i > MAX_I) { 109: revert ErrInvalidI(); 110: } 111: if (k > MAX_K) { 112: revert ErrInvalidK(); 113: } 114: if (lpFeeRate < MIN_LP_FEE_RATE || lpFeeRate > MAX_LP_FEE_RATE) { 115: revert ErrInvalidLPFeeRate(); 116: } +99: if (_INITIALIZED_) { +100: revert ErrInitialized(); +101: } ## [G-05] Avoid emitting event on every iteration Emitting events within a loop can cause significant gas consumption due to repeated I/O operations. Instead, accumulate changes in memory and emit a single event post-loop with aggregated data. This approach improves contract efficiency, reduces gas costs, and simplifies event tracking for event listeners. #### Instance 1 ```solidity File : staking/LockingMultiRewards.sol 405: for (uint256 i; i < users.length; ) { ... 434: if (index != lastIndex) { 435: locks[index] = locks[lastIndex]; 436: emit LogLockIndexChanged(user, lastIndex, index); 437: } ... 447: emit LogUnlocked(user, amount, index); ... 449: unchecked { 450: ++i; 451: } 452: }
LockingMultiRewards.sol#L405-L452
File : staking/LockingMultiRewards.sol 614: for (uint256 i; i < rewardTokens.length; ) { 636: if (amount > 0) { 637: rewardToken.safeTransfer(user, amount); 638: emit LogRewardPaid(user, rewardToken, amount); 639: } 651: emit LogRewardLocked(user, rewardToken, rewardAmount); 653: unchecked { 654: ++i; 655: } 656: }
LockingMultiRewards.sol#L614-L656
_getRewards
function to save 1 SLOAD
by caching function callCache nextEpoch()
function call and use it for emitting event instead of _rewardLock.unlockTime
saves 1 SLOAD.
File : staking/LockingMultiRewards.sol 609: if (expired) { 610: _rewardLock.unlockTime = nextEpoch(); 611: emit LogRewardLockCreated(user, _rewardLock.unlockTime); 612: }
LockingMultiRewards.sol#L609-L612
Recommended Mitigation Steps:
File : staking/LockingMultiRewards.sol 609: if (expired) { + uint256 _nextEpoch = nextEpoch(); -610: _rewardLock.unlockTime = nextEpoch(); -611: emit LogRewardLockCreated(user, _rewardLock.unlockTime); +610: _rewardLock.unlockTime = _nextEpoch; +611: emit LogRewardLockCreated(user, _nextEpoch); 612: }
idelta * V1
and DecimalMath.ONE - k
saves checked multiplication and checked subtraction. It can save upto ~150 GAS
File : mimswap/libraries/Math.sol 155: } else if ((idelta * V1) / idelta == V1) { 156: temp = (idelta * V1) / (V0 * V0); ... 184: uint256 squareRoot = DecimalMath.mulFloor((DecimalMath.ONE - k) * 4, DecimalMath.mulFloor(k, V0) * V0); // 4(1-k)kQ0^2 185: squareRoot = sqrt((bAbs * bAbs) + squareRoot); // sqrt(b*b+4(1-k)kQ0*Q0) 186: 187: // final res 188: uint256 denominator = (DecimalMath.ONE - k) * 2; // 2(1-k)
Math.sol#L155-L156, Math.sol#L184-L188
Recommended Mitigation Steps:
File : mimswap/libraries/Math.sol + uint256 ideltaxV1 = idelta * V1; + uint256 DecimalMath_k = DecimalMath.ONE - k; -155: } else if ((idelta * V1) / idelta == V1) { -156: temp = (idelta * V1) / (V0 * V0); +155: } else if ((ideltaxV1) / idelta == V1) { +156: temp = (ideltaxV1) / (V0 * V0); ... -184: uint256 squareRoot = DecimalMath.mulFloor((DecimalMath.ONE - k) * 4, DecimalMath.mulFloor(k, V0) * V0); // 4(1-k)kQ0^2 +184: uint256 squareRoot = DecimalMath.mulFloor((DecimalMath_k) * 4, DecimalMath.mulFloor(k, V0) * V0); // 4(1-k)kQ0^2 185: squareRoot = sqrt((bAbs * bAbs) + squareRoot); // sqrt(b*b+4(1-k)kQ0*Q0) 186: 187: // final res -188: uint256 denominator = (DecimalMath.ONE - k) * 2; // 2(1-k) +188: uint256 denominator = (DecimalMath_k) * 2; // 2(1-k)
IMagicLP(lp)._BASE_TOKEN_().balanceOf(address(lp)) + baseInAmount
and IMagicLP(lp)._QUOTE_TOKEN_().balanceOf(address(lp)) + quoteInAmount
since they are used only once.File : mimswap/periphery/Router.sol 515: uint256 baseBalance = IMagicLP(lp)._BASE_TOKEN_().balanceOf(address(lp)) + baseInAmount; 516: uint256 quoteBalance = IMagicLP(lp)._QUOTE_TOKEN_().balanceOf(address(lp)) + quoteInAmount; 517: 518: baseInAmount = baseBalance - baseReserve; 519: quoteInAmount = quoteBalance - quoteReserve;
Recommended Mitigation Steps:
File : mimswap/periphery/Router.sol -515: uint256 baseBalance = IMagicLP(lp)._BASE_TOKEN_().balanceOf(address(lp)) + baseInAmount; -516: uint256 quoteBalance = IMagicLP(lp)._QUOTE_TOKEN_().balanceOf(address(lp)) + quoteInAmount; 517: -518: baseInAmount = baseBalance - baseReserve; -519: quoteInAmount = quoteBalance - quoteReserve; +518: baseInAmount = (IMagicLP(lp)._BASE_TOKEN_().balanceOf(address(lp)) + baseInAmount) - baseReserve; +519: quoteInAmount = (IMagicLP(lp)._QUOTE_TOKEN_().balanceOf(address(lp)) + quoteInAmount) - quoteReserve;
#0 - c4-pre-sort
2024-03-15T14:59:42Z
141345 marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-03-15T15:30:15Z
141345 marked the issue as grade-c
#2 - thenua3bhai
2024-04-03T03:59:24Z
Hi @thereksfour Thanks for Judging
I request you to please re-evaluate this gas report. I believe this should be marked grade-a. Since it contains 15 good gas findings and majority of them are high quality significant gas savers and re-factoring findings found by manual review
which can not be even found by bot. Since I find these findings by manual review
that's why they are less in numbers. I usually don't include trivial findings in my report since they are easily found by bot and doesn't save much gas. Since this contest doesn't had bot race so many bot participated and submitted findings in big numbers. But in terms of gas savings and score my findings weight similar or more than higher number of bot findings. And this report provides much more good amount of value to the protocol by saving significant gas in their code.
Lookout marked this c by just seeing less number of findings compared to others.
But in terms of scoring this contains 10 L grade-findings
and 5 R/NC level findings
. All findings are correct and 100% safe to implement. Which makes this report score more than 55 which makes this report eligible for grade-a since current threshold score for grade-a is 50.
I have summarized significant gas saver findings and listed out L level findings
from this report. They are explained in detail in my gas report.
[G-01] State variables can be packed into fewer storage slots by reducing their size - Saves 2 storage slots (Saves ~4000 Gas) == L
[G-02] Reduce the size of struct variables and pack them together to save storage slots - Saves 1 storage slots(Saves ~2000 Gas) == L
[G-03] Cache external function call to save gas instead of re-calling avoid 1 external call - Saves 1 external function call == L
[G-04] State variable only set in the constructor should be declare immutable - Saves 1 storage slot(Gas Saved ~2100 Gas) == L
[G-05] Check before updating bool state variable to avoid SSTORE
with same bool value- ***Saves 1 SSTORE ***(Saves ~2200 Gas) == L
[G-07] Multiple accesses of the same mapping/array key/index inside loop
should be cached - Saves 3 SLOAD Per iteration (Gas Saved ~300 Gas Per iteration) == L
[G-09] State variables should be cached in stack variables rather than re-reading them from storage - Saves 18 SLOAD(Gas Saved ~1800 Gas) == L
[G-10] Within contract's function function call should be cached instead of re-calling the same function Saves 1 function call == L
[G-12] Switch the order of if statements inside loop
to fail early if failing - Saves 3 Gcoldsload per loop iteration
(Saves ~2100 Gas of Gcoldsload per loop iteration
) half of the times == L
[G-13] Refactor _getRewards
function to save 1 SLOAD by caching function call Saves 1 SLOAD every time after refactoring== L
Apart from these above 10 L findings my report also have 5 other good findings
which is mentioned in report which can also be considered as R/NC. Considering 3 other findings as R and 2 other as NC out of those other 5.
Since 1 L gives 5 score , 1 R gives 2 score and 1 NC gives 1 score in gas reports.
So Total avg. score of this report = 10 * 5 + 3 * 2 + 2 * 1 = 57
Which is much higher than current threshold score for grade-a which is 50.
So I request you to please re-evaluate this report and update this to grade-a since based on above score and facts this report should be marked as grade-a.
Thanks
#3 - c4-judge
2024-04-06T07:30:55Z
thereksfour marked the issue as grade-a