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: 25/36
Findings: 2
Award: $184.90
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
Number | Issue | Instances |
---|---|---|
L‑1 | Avoid using tx.origin | 5 |
L‑2 | Consider bounding input array length | 1 |
L‑3 | Consider implementing two-step procedure for updating protocol addresses | 8 |
L‑4 | constructor /initialize function lacks parameter validation | 6 |
L‑5 | Contracts use infinite approvals with no means to revoke | 2 |
L‑6 | Critical functions should be controlled by time locks | 12 |
L‑7 | decimals() is not a part of the ERC-20 standard | 9 |
L‑8 | Division by zero not prevented | 9 |
L‑9 | Do not use deprecated library functions | 3 |
L‑10 | External calls in an un-bounded for -loop may result in a DOS | 2 |
L‑11 | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 31 |
L‑12 | latestAnswer() is deprecated | 4 |
L‑13 | Low level calls to custom address es | 1 |
L‑14 | Missing checks for address(0) when assigning values to address state variables | 2 |
L‑15 | Missing contract-existence checks before low-level calls | 1 |
L‑16 | Missing zero address check in functions with address parameters | 97 |
L‑17 | Multiplication on the result of a division | 4 |
L‑18 | Owner or single user with a role can renounce while system is paused | 2 |
L‑19 | SafeTransferLib does not ensure that the token contract exists | 5 |
L‑20 | Setters should prevent re-setting of the same value | 5 |
L‑21 | Some tokens may revert when zero value transfers are made | 26 |
L‑22 | State variables not capped at reasonable values | 1 |
L‑23 | symbol() is not a part of the ERC-20 standard | 2 |
L‑24 | TransferOwnership should be two step | 1 |
L‑25 | unchecked blocks with additions/multiplications may overflow | 1 |
L‑26 | Unused/empty receive()/fallback() function | 2 |
L‑27 | Using > />= without specifying an upper bound is unsafe | 20 |
Number | Issue | Instances |
---|---|---|
NC‑1 | 2**<n> - 1 should be re-written as type(uint<n>).max | 2 |
NC‑2 | Adding a return statement when the function defines a named return variable, is redundant | 1 |
NC‑3 | address shouldn't be hard-coded | 5 |
NC‑4 | Avoid external calls in modifiers | 3 |
NC‑5 | Avoid mutating function /modifier parameters | 8 |
NC‑6 | Complicated functions should have explicit comments | 1 |
NC‑7 | Consider disallowing transfers to address(this) | 1 |
NC‑8 | Consider making contracts Upgradeable | 19 |
NC‑9 | Consider providing a ranged getter for array state variables | 2 |
NC‑10 | Consider using delete rather than assigning zero to clear values | 2 |
NC‑11 | Consider using named function arguments | 14 |
NC‑12 | Consider using named mappings | 3 |
NC‑13 | Constants in comparisons should appear on the left side | 42 |
NC‑14 | constant s should be defined rather than using magic numbers | 22 |
NC‑15 | constant s/immutable s redefined elsewhere | 31 |
NC‑16 | constructor should emit an event | 15 |
NC‑17 | Contracts containing only utility functions should be made into libraries | 1 |
NC‑18 | Contracts should each be defined in separate files | 7 |
NC‑19 | Contracts should have all public /external functions exposed by interface s | 14 |
NC‑20 | Contracts/libraries/interfaces should each be defined in separate files | 7 |
NC‑21 | Control structures do not follow the Solidity Style Guide | 15 |
NC‑22 | Do not use underscore at the end of variable name | 75 |
NC‑23 | else -block not required | 4 |
NC‑24 | Empty bytes check is missing | 9 |
NC‑25 | Enum values should be used instead of constant array indexes | 3 |
NC‑26 | Events are missing sender information | 26 |
NC‑27 | Events should be emitted before external calls | 38 |
NC‑28 | Events that mark critical parameter changes should contain both the old and the new value | 11 |
NC‑29 | Expressions for constant values should use immutable rather than constant | 6 |
NC‑30 | Extraneous whitespace | 3 |
NC‑31 | For loops in public or external functions should be avoided due to high gas costs and possible DOS | 2 |
NC‑32 | Function ordering in the contract does not follow the Solidity style guide | 100 |
NC‑33 | Functions contain the same code | 11 |
NC‑34 | Functions should be named in mixedCase style | 23 |
NC‑35 | High cyclomatic complexity | 15 |
NC‑36 | if -statement can be converted to a ternary | 9 |
NC‑37 | Imports could be organized more systematically | 7 |
NC‑38 | Inconsistent spacing in comments | 17 |
NC‑39 | Large multiples of ten should use scientific notation | 3 |
NC‑40 | Large numeric literals should use underscores for readability | 8 |
NC‑41 | Layout order does not comply with best practices | 61 |
NC‑42 | Lines are too long | 54 |
NC‑43 | Long functions should be refactored into multiple, smaller, functions | 5 |
NC‑44 | Make use of Solidity's using keyword | 97 |
NC‑45 | Misplaced SPDX identifier | 5 |
NC‑46 | Mixed usage of int/uint with int256/uint256 | 2 |
NC‑47 | Multiple mappings with same keys can be combined into a single struct mapping for readability | 17 |
NC‑48 | Named imports of parent contracts are missing | 2 |
NC‑49 | Names of structs, events, enums and errors should use CapWords style | 2 |
NC‑50 | Non-external /public function names should begin with an underscore | 25 |
NC‑51 | Not using the latest versions of project dependencies | 1 |
NC‑52 | Not using the named return variables anywhere in the function is confusing | 19 |
NC‑53 | Outdated Solidity version | 20 |
NC‑54 | Overly complicated arithmetic | 3 |
NC‑55 | Overridden function has no body | 1 |
NC‑56 | Parameter change does not emit event | 2 |
NC‑57 | Polymorphic functions make security audits more time-consuming and error-prone | 4 |
NC‑58 | Prefer skip over revert model in iteration | 2 |
NC‑59 | public functions not called by the contract should be declared external instead | 17 |
NC‑60 | Public functions shouldn't have a preceding _ in their name | 1 |
NC‑61 | Public state variables shouldn't have a preceding _ in their name | 13 |
NC‑62 | Returning a struct instead of a bunch of variables is better | 11 |
NC‑63 | Some contracts can be abstract | 1 |
NC‑64 | Some events are never emitted | 3 |
NC‑65 | Some variables have a implicit default visibility | 1 |
NC‑66 | Top-level declarations should be separated by at least two lines | 168 |
NC‑67 | Typos | 1 |
NC‑68 | Unnecessary cast | 29 |
NC‑69 | Unnecessary struct attribute prefix | 2 |
NC‑70 | Unspecific compiler version pragma | 20 |
NC‑71 | Unused error definition | 6 |
NC‑72 | Unused event definition | 3 |
NC‑73 | Unused modifier definition | 2 |
NC‑74 | Use a single file for system wide constants | 15 |
NC‑75 | Use a struct to encapsulate multiple function parameters | 25 |
NC‑76 | Use CamelCase for contract and library names | 3 |
NC‑77 | Use delete instead of assigning values to false | 1 |
NC‑78 | Use scientific notation (e.g. 1e18 ) rather than exponentiation (e.g. 10**18 ) | 5 |
NC‑79 | Use UPPER_CASE for immutable | 16 |
NC‑80 | Variable names that consist of all capital letters should be reserved for constant /immutable variables | 22 |
NC‑81 | Variables need not be initialized to zero | 2 |
NC‑82 | Variables should be named in mixedCase style | 164 |
tx.origin
tx.origin
is a global variable in Solidity that returns the address of the account that sent the transaction.
Using the variable could make a contract vulnerable if an authorized account calls a malicious contract. You can impersonate a user using a third party contract.
This can make it easier to create a vault on behalf of another user with an external administrator (by receiving it as an argument).
📁 File: src/mimswap/MagicLP.sol 250: (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput); 273: (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(tx.origin, quoteInput); 311: tx.origin, 333: tx.origin,
</details>📁 File: src/mimswap/periphery/Factory.sol 82: address creator = tx.origin;
The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a nicer user experience to require()
that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.
<i>There is one instance of this issue:</i>
📁 File: src/blast/BlastOnboarding.sol /// @audit tokens.length not bounded 165: for (uint256 i = 0; i < tokens.length; i++) { 166: if (!supportedTokens[tokens[i]]) { 167: revert ErrUnsupportedToken(); 168: } 169: if (registry.nativeYieldTokens(tokens[i])) { 170: BlastYields.claimAllTokenYields(tokens[i], feeTo); 171: } 172: }
A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the 'set' functions ensure that the recipient is of the right interface type.
<details> <summary><i>There are 8 instances of this issue:</i></summary>📁 File: src/blast/BlastBox.sol /// @audit line 77 72: function setFeeTo(address feeTo_) external onlyOwner { 73: if (feeTo_ == address(0)) { 74: revert ErrZeroAddress(); 75: } 76: 77: feeTo = feeTo_; 78: emit LogFeeToChanged(feeTo_); 79: }
📁 File: src/blast/BlastGovernor.sol /// @audit line 45 40: function setFeeTo(address _feeTo) external onlyOwner { 41: if(_feeTo == address(0)) { 42: revert ErrZeroAddress(); 43: } 44: 45: feeTo = _feeTo; 46: emit LogFeeToChanged(_feeTo); 47: }
📁 File: src/blast/BlastMagicLP.sol /// @audit line 85 80: function setFeeTo(address feeTo_) external onlyImplementation onlyImplementationOwner { 81: if (feeTo_ == address(0)) { 82: revert ErrZeroAddress(); 83: } 84: 85: feeTo = feeTo_; 86: emit LogFeeToChanged(feeTo_); 87: }
📁 File: src/blast/BlastOnboarding.sol /// @audit line 152 147: function setFeeTo(address feeTo_) external onlyOwner { 148: if (feeTo_ == address(0)) { 149: revert ErrZeroAddress(); 150: } 151: 152: feeTo = feeTo_; 153: emit LogFeeToChanged(feeTo_); 154: } /// @audit line 191 190: function setBootstrapper(address bootstrapper_) external onlyOwner { 191: bootstrapper = bootstrapper_; 192: emit LogBootstrapperChanged(bootstrapper_); 193: }
📁 File: src/mimswap/auxiliary/FeeRateModel.sol /// @audit line 51 46: function setMaintainer(address maintainer_) external onlyOwner { 47: if (maintainer_ == address(0)) { 48: revert ErrZeroAddress(); 49: } 50: 51: maintainer = maintainer_; 52: emit LogMaintainerChanged(maintainer_); 53: } /// @audit line 58 57: function setImplementation(address implementation_) public onlyOwner { 58: implementation = implementation_; 59: emit LogImplementationChanged(implementation_); 60: }
</details>📁 File: src/mimswap/periphery/Factory.sol /// @audit line 101 96: function setLpImplementation(address implementation_) external onlyOwner { 97: if (implementation_ == address(0)) { 98: revert ErrZeroAddress(); 99: } 100: 101: implementation = implementation_; 102: emit LogSetImplementation(implementation_); 103: }
#0 - c4-pre-sort
2024-03-15T15:00:37Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-16T01:06:12Z
110 Bozho l r nc 3 0 57
L 1 i L 2 n L 3 i L 4 l L 5 n L 6 l L 7 i L 8 i L 9 i L 10 i L 11 l L 12 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/82 L 13 i L 14 i L 15 i L 16 i L 17 n L 18 i L 19 n L 20 n L 21 n L 22 i L 23 i L 24 i L 25 n L 26 n L 27 n
#2 - c4-judge
2024-03-29T16:50:03Z
thereksfour marked the issue as grade-a
#3 - c4-judge
2024-04-06T06:58:59Z
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
Number | Issue | Instances | Estimated Gas Saved |
---|---|---|---|
GAS‑1 | ++i costs less gas than i++ /i += 1 (same for --i vs i-- /i -+ 1 ) | 1 | 5 |
GAS‑2 | ++i /i++ should be unchecked when it is not possible for them to overflow | 1 | 60 |
GAS‑3 | >= /<= costs less gas than > /< | 82 | 246 |
GAS‑4 | address(this) should be cached when used multiple times | 3 | - |
GAS‑5 | Avoid fetching a low-level call's return data by using assembly | 1 | 159 |
GAS‑6 | Avoid unnecessary public variables | 49 | 1,078,000 |
GAS‑7 | Avoid updating storage when the value hasn't changed | 11 | 18,700 |
GAS‑8 | Avoid zero to non-zero storage writes where possible | 13 | 287,300 |
GAS‑9 | Consider activating via-ir for deploying | 0 | 250 |
GAS‑10 | Consider caching repeated computations | 2 | 120 |
GAS‑11 | Consider pre-calculating the address of address(this) | 49 | - |
GAS‑12 | Consider using OpenZeppelin's EnumerateSet instead of nested mappings | 4 | 4,000 |
GAS‑13 | Consider using Solady's gas optimized lib for Math | 39 | - |
GAS‑14 | Constructors can be marked payable | 16 | 336 |
GAS‑15 | Counting down in for statements is more gas efficient | 8 | 128 |
GAS‑16 | Declare variables outside of loops | 15 | 225 |
GAS‑17 | Do not calculate constants | 4 | - |
GAS‑18 | do -while is cheaper than for -loops when the initial check can be skipped | 8 | 2,040 |
GAS‑19 | Don't transfer with zero amount to save gas | 26 | 520 |
GAS‑20 | Emitting storage values instead of the memory one. | 8 | 800 |
GAS‑21 | Empty blocks should be removed or emit something | 3 | - |
GAS‑22 | Function names can be optimized | 11 | 1,408 |
GAS‑23 | Functions not used internally could be marked external to save gas | 15 | - |
GAS‑24 | Initializers can be marked payable | 3 | 63 |
GAS‑25 | internal functions only used once can be inlined so save gas | 3 | 90 |
GAS‑26 | Low-level call can be optimized with assembly | 1 | 159 |
GAS‑27 | Mappings are cheaper to use than storage arrays | 5 | 10,500 |
GAS‑28 | Merge events to save gas | 9 | 3,375 |
GAS‑29 | Multiple accesses of the same mapping/array key/index should be cached | 23 | 966 |
GAS‑30 | Multiple address /ID mappings can be combined into a single mapping of an address /ID to a struct | 9 | 180,000 |
GAS‑31 | Nested for loops should be avoided due to high gas costs resulting from O^2 time complexity | 1 | - |
GAS‑32 | Nesting if -statements is cheaper than using && | 11 | 330 |
GAS‑33 | Newer versions of solidity are more gas efficient | 20 | - |
GAS‑34 | Only emit event in setter function if the state variable was changed | 13 | - |
GAS‑35 | Optimize Deployment Size by Fine-tuning IPFS Hash | 20 | 212,000 |
GAS‑36 | Order of initial checks in a function can be optimized | 2 | 4,200 |
GAS‑37 | Pre-increments/pre-decrements are cheaper than +1 /-1 | 1 | 6 |
GAS‑38 | Remove or replace unused state variables | 7 | 80,150 |
GAS‑39 | Simple checks for zero can be done using assembly to save gas | 74 | 444 |
GAS‑40 | Simplify modulo operations | 1 | 101 |
GAS‑41 | Sort Solidity operations using short-circuit mode | 36 | - |
GAS‑42 | Stack variable is only used once | 28 | 84 |
GAS‑43 | State variable read in a loop | 7 | 679 |
GAS‑44 | State variable written in a loop | 2 | 11,588 |
GAS‑45 | State variables can be packed into fewer storage slots by truncating timestamp bytes | 1 | 140,000 |
GAS‑46 | State variables can be reordered to fit into fewer storage slots | 1 | 20,000 |
GAS‑47 | State variables should be cached in stack variables rather than re-reading them from storage | 53 | 5,300 |
GAS‑48 | Structs can be packed into fewer storage slots by truncating timestamp bytes | 1 | 20,000 |
GAS‑49 | Superfluous event fields | 2 | 70 |
GAS‑50 | unchecked {} can be used on the division of two uint s in order to save gas | 36 | 2,160 |
GAS‑51 | Update OpenZeppelin dependency to the latest version | 8 | - |
GAS‑52 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 12 | 72 |
GAS‑53 | Use Array.unsafeAccess() to avoid repeated array length checks | 10 | 21,000 |
GAS‑54 | Use assembly for math equations | 6 | 1,560 |
GAS‑55 | Use assembly for small keccak256 hashes, in order to save gas | 1 | 80 |
GAS‑56 | Use assembly scratch space to build calldata for external calls | 40 | 8,800 |
GAS‑57 | Use assembly to perform efficient back-to-back calls | 2 | 600 |
GAS‑58 | Use assembly to validate msg.sender | 2 | 24 |
GAS‑59 | Use assembly to write address/contract storage values | 38 | 1,900 |
GAS‑60 | Use calldata instead of memory for function arguments that do not get mutated | 2 | 600 |
GAS‑61 | Use constants instead of type(uint<n>).max / .min | 6 | 24 |
GAS‑62 | Use if statements instead of ternary operators | 6 | - |
GAS‑63 | Use immutable when you have storage variable that is not going to change | 2 | - |
GAS‑64 | Use modifiers rather than invoking functions to perform checks | 7 | 280 |
GAS‑65 | Use of emit inside a loop | 5 | 1,875 |
GAS‑66 | Use scratch space when building emitted events with two data arguments | 36 | 1,368 |
GAS‑67 | Use the inputs/results of assignments rather than re-reading state variables | 28 | 2,716 |
GAS‑68 | Use unchecked for divisions on constant or immutable values | 6 | 360 |
GAS‑69 | Using bool s for storage incurs overhead | 3 | 51,300 |
GAS‑70 | Using constant s instead of enum can save gas | 2 | - |
GAS‑71 | Using globals directly is cheaper than assigning them to variables | 1 | 5 |
GAS‑72 | Using storage instead of memory for structs/arrays saves gas | 3 | 6,300 |
GAS‑73 | x + y is more efficient than using += for state variables (likewise for -=) | 35 | 8,680 |
❗ Disclaimer: Gas totals are estimates based on data from the Ethereum Yellowpaper. The estimates use the lower bounds of ranges and count two iterations of each for
-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions.
++i
costs less gas than i++
/i += 1
(same for --i
vs i--
/i -+ 1
)Gas saved per Instance: ~5
<i>There is one instance of this issue:</i>
📁 File: src/blast/BlastOnboarding.sol 165: for (uint256 i = 0; i < tokens.length; i++) {
++i
/i++
should be unchecked
when it is not possible for them to overflowThe unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
Gas saved per Instance: ~60
<i>There is one instance of this issue:</i>
📁 File: src/blast/BlastOnboarding.sol 165: for (uint256 i = 0; i < tokens.length; i++) {
>=
/<=
costs less gas than >
/<
The compiler uses opcodes GT
and ISZERO
for code that uses >
, but only requires LT
for >=
. A similar behaviour applies for >
, which uses opcodes LT
and ISZERO
, but only requires GT
for <=
.
Gas saved per Instance: ~3 (Total: ~246)
<details> <summary><i>There are 82 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboarding.sol 114: if (caps[token] > 0 && totals[token].total > caps[token]) { 165: for (uint256 i = 0; i < tokens.length; i++) {
📁 File: src/blast/BlastOnboardingBoot.sol 108: if (totalPoolShares < minAmountOut) {
📁 File: src/mimswap/MagicLP.sol 108: if (i == 0 || i > MAX_I) { 111: if (k > MAX_K) { 114: if (lpFeeRate < MIN_LP_FEE_RATE || lpFeeRate > MAX_LP_FEE_RATE) { 139: if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_ < _BASE_TARGET_) { 144: if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_ < _QUOTE_TARGET_) { 294: if (data.length > 0) { 302: if (baseBalance < _BASE_RESERVE_ && quoteBalance < _QUOTE_RESERVE_) { 308: if (baseBalance < _BASE_RESERVE_) { 315: if (uint256(_BASE_RESERVE_) - baseBalance > receiveBaseAmount) { 330: if (quoteBalance < _QUOTE_RESERVE_) { 337: if (uint256(_QUOTE_RESERVE_) - quoteBalance > receiveQuoteAmount) { 381: shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_) ? DecimalMath.divFloor(quoteBalance, _I_) : baseBalance; 395: } else if (baseReserve > 0 && quoteReserve > 0) { 399: uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio; 421: if (deadline < block.timestamp) { 424: if (shareAmount > balanceOf(msg.sender)) { 441: if (baseAmount < baseMinAmount || quoteAmount < quoteMinAmount) { 450: if (data.length > 0) { 480: if (_BASE_RESERVE_ < minBaseReserve || _QUOTE_RESERVE_ < minQuoteReserve) { 483: if (newI == 0 || newI > MAX_I) { 486: if (newK > MAX_K) { 489: if (newLpFeeRate < MIN_LP_FEE_RATE || newLpFeeRate > MAX_LP_FEE_RATE) { 508: if (baseBalance > type(uint112).max || quoteBalance > type(uint112).max) { 532: if (baseBalance > type(uint112).max || quoteBalance > type(uint112).max) { 549: if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) { 582: if (amount > 0) { 588: if (amount > 0) {
108, 111, 114, 139, 144, 294, 302, 308, 315, 330, 337, 381, 395, 399, 421, 424, 441, 450, 480, 483, 486, 489, 508, 532, 549, 582, 588
📁 File: src/mimswap/libraries/Math.sol 22: if (remainder > 0) { 32: while (z < y) { 141: return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta); 200: if (V2 > V1) {
📁 File: src/mimswap/libraries/PMMPricing.sol 50: if (payBaseAmount < backToOnePayBase) { 54: if (receiveQuoteAmount > backToOneReceiveQuote) { 86: if (payQuoteAmount < backToOnePayQuote) { 89: if (receiveBaseAmount > backToOneReceiveBase) {
📁 File: src/mimswap/periphery/Router.sol 48: if (block.timestamp > deadline) { 101: shares = quoteInAmount < DecimalMath.mulFloor(baseInAmount, i) ? DecimalMath.divFloor(quoteInAmount, i) : baseInAmount; 138: shares = quoteBalance < DecimalMath.mulFloor(baseBalance, i) ? DecimalMath.divFloor(quoteBalance, i) : baseBalance; 147: } else if (baseReserve > 0 && quoteReserve > 0) { 220: if (msg.value > wethAdjustedAmount) { 502: if (shares < minimumShares) { 523: uint256 shares = quoteInAmount < DecimalMath.mulFloor(baseInAmount, i) ? DecimalMath.divFloor(quoteInAmount, i) : baseInAmount; 527: if (quoteReserve > 0 && baseReserve > 0) { 544: for (uint256 i = 0; i < iterations; ) { 566: if (amountOut < minimumOut) { 573: if (amountOut < minimumOut) { 581: if (amountOut < minimumOut) { 590: if (pathLength > 256) { 602: if (quoteDecimals - baseDecimals > MAX_BASE_QUOTE_DECIMALS_DIFFERENCE) {
48, 101, 138, 147, 220, 502, 523, 527, 544, 566, 573, 581, 590, 602
📁 File: src/oracles/aggregators/MagicLpAggregator.sol 40: uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized;
📁 File: src/staking/LockingMultiRewards.sol 123: if (_lockDuration < MIN_LOCK_DURATION) { 127: if (_rewardsDuration < MIN_REWARDS_DURATION) { 326: if (tokenAddress == stakingToken && tokenAmount > stakingToken.balanceOf(address(this)) - stakingTokenBalance) { 374: if (_remainingRewardTime < minRemainingTime) { 379: if (block.timestamp < reward.periodFinish) { 384: if (amount < _remainingRewardTime) { 405: for (uint256 i; i < users.length; ) { 417: if (index == lastLockIndex[user] && locks.length > 1) { 422: if (locks[index].unlockTime > block.timestamp) { 490: if (lockCount == 0 || _userLocks[user][_lastLockIndex].unlockTime < _nextUnlockTime) { 497: if (amount < minLockAmount) { 547: for (uint256 i; i < rewardTokens.length; ) { 561: for (uint256 i; i < rewardTokens.length; ) { 575: for (uint256 i; i < rewardTokens.length; ) { 580: for (uint256 j; j < users.length; ) { 614: for (uint256 i; i < rewardTokens.length; ) { 623: if (i < rewardItemLength) { 636: if (amount > 0) {
123, 127, 326, 374, 379, 384, 405, 417, 422, 490, 497, 547, 561, 575, 580, 614, 623, 636
</details>address(this)
should be cached when used multiple times📁 File: src/blast/BlastOnboardingBoot.sol /// @audit 'address(this)' used 3 times 96: function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) {
📁 File: src/mimswap/MagicLP.sol /// @audit 'address(this)' used 3 times 413: function sellShares( 414: uint256 shareAmount, 415: address to, 416: uint256 baseMinAmount, 417: uint256 quoteMinAmount, 418: bytes calldata data, 419: uint256 deadline 420: ) external nonReentrant returns (uint256 baseAmount, uint256 quoteAmount) {
</details>📁 File: src/mimswap/periphery/Router.sol /// @audit 'address(this)' used 3 times 274: function removeLiquidityETH( 275: address lp, 276: address to, 277: uint256 sharesIn, 278: uint256 minimumETHAmount, 279: uint256 minimumTokenAmount, 280: uint256 deadline 281: ) external returns (uint256 ethAmountOut, uint256 tokenAmountOut) {
Even if you don't assign the call's second return value, it still gets copied to memory. Use assembly instead to prevent this and save 159 gas:
Gas saved per Instance: ~159
<i>There is one instance of this issue:</i>
📁 File: src/blast/BlastGovernor.sol 54: (success, result) = to.call{value: value}(data);
public
variablesPublic state variables in Solidity automatically generate getter functions, increasing contract size and potentially leading to higher deployment and interaction costs. To optimize gas usage and contract efficiency, minimize the use of public variables unless external access is necessary. Instead, use internal or private visibility combined with explicit getter functions when required. This practice not only reduces contract size but also provides better control over data access and manipulation, enhancing security and readability. Prioritize lean, efficient contracts to ensure cost-effectiveness and better performance on the blockchain.
Gas saved per Instance: ~22,000 (Total: ~1,078,000)
<details> <summary><i>There are 49 instances of this issue:</i></summary>📁 File: src/blast/BlastBox.sol 20: BlastTokenRegistry public immutable registry; 22: address public feeTo;
📁 File: src/blast/BlastGovernor.sol 11: address public feeTo;
📁 File: src/blast/BlastMagicLP.sol 17: BlastTokenRegistry public immutable registry;
📁 File: src/blast/BlastOnboarding.sol 30: State public state; 31: address public bootstrapper; 32: address public feeTo; 33: BlastTokenRegistry public registry;
📁 File: src/blast/BlastOnboardingBoot.sol 24: address public pool; 25: Router public router; 26: IFactory public factory; 27: uint256 public totalPoolShares; 28: bool public ready; 29: LockingMultiRewards public staking;
📁 File: src/mimswap/MagicLP.sol 61: MagicLP public immutable implementation; 70: address public _BASE_TOKEN_; 71: address public _QUOTE_TOKEN_; 72: uint112 public _BASE_RESERVE_; 73: uint112 public _QUOTE_RESERVE_; 74: uint32 public _BLOCK_TIMESTAMP_LAST_; 75: uint256 public _BASE_PRICE_CUMULATIVE_LAST_; 76: uint112 public _BASE_TARGET_; 77: uint112 public _QUOTE_TARGET_; 78: uint32 public _RState_; 79: IFeeRateModel public _MT_FEE_RATE_MODEL_; 80: uint256 public _LP_FEE_RATE_; 81: uint256 public _K_; 82: uint256 public _I_;
📁 File: src/mimswap/auxiliary/FeeRateModel.sol 19: address public maintainer; 20: address public implementation;
📁 File: src/mimswap/periphery/Factory.sol 32: address public implementation; 33: IFeeRateModel public maintainerFeeRateModel;
📁 File: src/mimswap/periphery/Router.sol 33: IWETH public immutable weth; 34: IFactory public immutable factory;
📁 File: src/oracles/aggregators/MagicLpAggregator.sol 10: IMagicLP public immutable pair; 11: IAggregator public immutable baseOracle; 12: IAggregator public immutable quoteOracle; 13: uint8 public immutable baseDecimals; 14: uint8 public immutable quoteDecimals;
</details>📁 File: src/staking/LockingMultiRewards.sol 83: uint256 public immutable maxLocks; 84: uint256 public immutable lockingBoostMultiplerInBips; 85: uint256 public immutable rewardsDuration; 86: uint256 public immutable lockDuration; 87: address public immutable stakingToken; 98: address[] public rewardTokens; 100: uint256 public lockedSupply; // all locked boosted deposits 101: uint256 public unlockedSupply; // all unlocked unboosted deposits 102: uint256 public minLockAmount; // minimum amount allowed to lock 103: uint256 public stakingTokenBalance; // total staking token balance
If the old value is equal to the new value, not re-storing the value will avoid a Gsreset (2900 gas), potentially at the expense of a Gcoldsload (2100 gas) or a Gwarmaccess (100 gas)
Gas saved per Instance: ~1,700 (Total: ~18,700)
<details> <summary><i>There are 11 instances of this issue:</i></summary>📁 File: src/blast/BlastBox.sol 72: function setFeeTo(address feeTo_) external onlyOwner { 73: if (feeTo_ == address(0)) { 74: revert ErrZeroAddress(); 75: } 76: 77: feeTo = feeTo_; 78: emit LogFeeToChanged(feeTo_); 79: }
📁 File: src/blast/BlastGovernor.sol 40: function setFeeTo(address _feeTo) external onlyOwner { 41: if(_feeTo == address(0)) { 42: revert ErrZeroAddress(); 43: } 44: 45: feeTo = _feeTo; 46: emit LogFeeToChanged(_feeTo); 47: }
📁 File: src/blast/BlastMagicLP.sol 80: function setFeeTo(address feeTo_) external onlyImplementation onlyImplementationOwner { 81: if (feeTo_ == address(0)) { 82: revert ErrZeroAddress(); 83: } 84: 85: feeTo = feeTo_; 86: emit LogFeeToChanged(feeTo_); 87: }
📁 File: src/blast/BlastOnboarding.sol 147: function setFeeTo(address feeTo_) external onlyOwner { 148: if (feeTo_ == address(0)) { 149: revert ErrZeroAddress(); 150: } 151: 152: feeTo = feeTo_; 153: emit LogFeeToChanged(feeTo_); 154: } 190: function setBootstrapper(address bootstrapper_) external onlyOwner { 191: bootstrapper = bootstrapper_; 192: emit LogBootstrapperChanged(bootstrapper_); 193: }
📁 File: src/blast/BlastOnboardingBoot.sol 137: function setStaking(LockingMultiRewards _staking) external onlyOwner { 138: if (ready) { 139: revert ErrCannotChangeOnceReady(); 140: } 141: 142: staking = _staking; 143: emit LogStakingChanged(address(_staking)); 144: } 146: function setReady(bool _ready) external onlyOwner onlyState(State.Closed) { 147: ready = _ready; 148: emit LogReadyChanged(ready); 149: }
📁 File: src/mimswap/auxiliary/FeeRateModel.sol 46: function setMaintainer(address maintainer_) external onlyOwner { 47: if (maintainer_ == address(0)) { 48: revert ErrZeroAddress(); 49: } 50: 51: maintainer = maintainer_; 52: emit LogMaintainerChanged(maintainer_); 53: } 57: function setImplementation(address implementation_) public onlyOwner { 58: implementation = implementation_; 59: emit LogImplementationChanged(implementation_); 60: }
</details>📁 File: src/mimswap/periphery/Factory.sol 96: function setLpImplementation(address implementation_) external onlyOwner { 97: if (implementation_ == address(0)) { 98: revert ErrZeroAddress(); 99: } 100: 101: implementation = implementation_; 102: emit LogSetImplementation(implementation_); 103: } 105: function setMaintainerFeeRateModel(IFeeRateModel maintainerFeeRateModel_) external onlyOwner { 106: if (address(maintainerFeeRateModel_) == address(0)) { 107: revert ErrZeroAddress(); 108: } 109: 110: maintainerFeeRateModel = maintainerFeeRateModel_; 111: emit LogSetMaintainerFeeRateModel(maintainerFeeRateModel_); 112: }
Changing a storage variable from zero to non-zero costs 22,100 gas in total. (20,000 gas for a zero to non-zero write and 2,100 for a cold storage access)
Consider using non-zero architecture to avoid high gas costs for zero to non-zero storage writes.
Gas saved per Instance: ~22,100 (Total: ~287,300)
<details> <summary><i>There are 13 instances of this issue:</i></summary>📁 File: src/mimswap/MagicLP.sol 121: _I_ = i; 122: _K_ = k; 123: _LP_FEE_RATE_ = lpFeeRate;
📁 File: src/staking/LockingMultiRewards.sol 163: unlockedSupply -= amount; 178: unlockedSupply -= amount; 181: stakingTokenBalance -= amount; 319: minLockAmount = _minLockAmount; 380: amount += _remainingRewardTime * reward.rewardRate; 441: unlockedSupply += amount; 442: lockedSupply -= amount; 465: stakingTokenBalance += amount; 473: unlockedSupply += amount; 486: lockedSupply += amount;
163, 178, 181, 319, 380, 441, 465, 473, 486
</details>via-ir
for deployingThe IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.You can enable it on the command line using --via-ir
or with the option {"viaIR": true}
.This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy commandMore on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html
The result of repeated computations can be cached to avoid calculating it multiple times and wasting gas.
Gas saved per Instance: ~60 (Total: ~120)
<details> <summary><i>There are 2 instances of this issue:</i></summary>📁 File: src/mimswap/libraries/Math.sol /// @audit DecimalMath.ONE-k is seen 3 times 131: function _SolveQuadraticFunctionForTrade(uint256 V0, uint256 V1, uint256 delta, uint256 i, uint256 k) internal pure returns (uint256) { 132: if (V0 == 0) { 133: revert ErrIsZero(); 134: } 135: 136: if (delta == 0) { 137: return 0; 138: } 139: 140: if (k == 0) { 141: return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta); 142: } 143: 144: if (k == DecimalMath.ONE) { 145: // if k==1 146: // Q2=Q1/(1+ideltaBQ1/Q0/Q0) 147: // temp = ideltaBQ1/Q0/Q0 148: // Q2 = Q1/(1+temp) 149: // Q1-Q2 = Q1*(1-1/(1+temp)) = Q1*(temp/(1+temp)) 150: // uint256 temp = i.mul(delta).mul(V1).div(V0.mul(V0)); 151: uint256 temp; 152: uint256 idelta = i * delta; 153: if (idelta == 0) { 154: temp = 0; 155: } else if ((idelta * V1) / idelta == V1) { 156: temp = (idelta * V1) / (V0 * V0); 157: } else { 158: temp = (((delta * V1) / V0) * i) / V0; 159: } 160: return (V1 * temp) / (temp + DecimalMath.ONE); 161: } 162: 163: // calculate -b value and sig 164: // b = kQ0^2/Q1-i*deltaB-(1-k)Q1 165: // part1 = (1-k)Q1 >=0 166: // part2 = kQ0^2/Q1-i*deltaB >=0 167: // bAbs = abs(part1-part2) 168: // if part1>part2 => b is negative => bSig is false 169: // if part2>part1 => b is positive => bSig is true 170: uint256 part2 = (((k * V0) / V1) * V0) + (i * delta); // kQ0^2/Q1-i*deltaB 171: uint256 bAbs = (DecimalMath.ONE - k) * V1; // (1-k)Q1 172: 173: bool bSig; 174: if (bAbs >= part2) { 175: bAbs = bAbs - part2; 176: bSig = false; 177: } else { 178: bAbs = part2 - bAbs; 179: bSig = true; 180: } 181: bAbs = bAbs / DecimalMath.ONE; 182: 183: // calculate sqrt 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) 189: uint256 numerator; 190: if (bSig) { 191: numerator = squareRoot - bAbs; 192: if (numerator == 0) { 193: revert ErrIsZero(); 194: } 195: } else { 196: numerator = bAbs + squareRoot; 197: } 198: 199: uint256 V2 = DecimalMath.divCeil(numerator, denominator); 200: if (V2 > V1) { 201: return 0; 202: } else { 203: return V1 - V2; 204: } 205: }
</details>📁 File: src/mimswap/libraries/PMMPricing.sol /// @audit (DecimalMath.ONE-state.K)+DecimalMath.mulFloor(state.K,R) is seen 2 times 203: function getMidPrice(PMMState memory state) internal pure returns (uint256) { 204: if (state.R == RState.BELOW_ONE) { 205: uint256 R = DecimalMath.divFloor((state.Q0 * state.Q0) / state.Q, state.Q); 206: R = (DecimalMath.ONE - state.K) + DecimalMath.mulFloor(state.K, R); 207: return DecimalMath.divFloor(state.i, R); 208: } else { 209: uint256 R = DecimalMath.divFloor((state.B0 * state.B0) / state.B, state.B); 210: R = (DecimalMath.ONE - state.K) + DecimalMath.mulFloor(state.K, R); 211: return DecimalMath.mulFloor(state.i, R); 212: } 213: }
address(this)
It can be more gas-efficient to use a hardcoded address instead of the address(this)
expression, especially if you need to use the same address multiple times in your contract.
The reason for this, is that using address(this)
requires an additional EXTCODESIZE
operation to retrieve the contract’s address from its bytecode, which can increase the gas cost of your contract. By pre-calculating and using a hardcoded address, you can avoid this additional operation and reduce the overall gas cost of your contract.
📁 File: src/blast/BlastBox.sol 99: BlastYields.configureDefaultClaimables(address(this));
📁 File: src/blast/BlastGovernor.sol 21: BlastYields.configureDefaultClaimables(address(this));
📁 File: src/blast/BlastMagicLP.sol 99: BlastYields.configureDefaultClaimables(address(this));
📁 File: src/blast/BlastOnboarding.sol 59: BlastYields.configureDefaultClaimables(address(this)); 102: token.safeTransferFrom(msg.sender, address(this), amount);
📁 File: src/blast/BlastOnboardingBoot.sol 106: (pool, totalPoolShares) = router.createPool(MIM, USDB, FEE_RATE, I, K, address(this), baseAmount, quoteAmount); 117: staking = new LockingMultiRewards(pool, 30_000, 7 days, 13 weeks, address(this)); 118: staking.setOperator(address(this), true);
📁 File: src/blast/BlastWrappers.sol 51: if (governor_ == address(this)) { 60: if (_governor == address(this)) {
📁 File: src/blast/libraries/BlastYields.sol 21: if (IERC20Rebasing(token).getConfiguration(address(this)) == YieldMode.CLAIMABLE) { 26: emit LogBlastTokenClaimableEnabled(address(this), token); 31: emit LogBlastNativeClaimableEnabled(address(this)); 39: return claimMaxGasYields(address(this), recipient); 52: return claimAllNativeYields(address(this), recipient); 71: amount = IERC20Rebasing(token).claim(recipient, IERC20Rebasing(token).getClaimableAmount(address(this)));
📁 File: src/mimswap/MagicLP.sol 229: return _BASE_TOKEN_.balanceOf(address(this)) - uint256(_BASE_RESERVE_); 233: return _QUOTE_TOKEN_.balanceOf(address(this)) - uint256(_QUOTE_RESERVE_); 245: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 262: _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); 268: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 285: _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); 298: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 299: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 361: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 362: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 427: if (to == address(this)) { 431: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 432: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 505: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 506: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 529: baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 530: quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 568: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 569: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 615: if (address(this) == address(implementation)) { 622: if (address(this) != address(implementation)) {
229, 233, 245, 262, 268, 285, 298, 361, 427, 431, 505, 529, 568, 615, 622
📁 File: src/mimswap/periphery/Factory.sol 77: address(this)
📁 File: src/mimswap/periphery/Router.sol 92: address(weth).safeTransferFrom(address(this), clone, msg.value); 269: lp.safeTransferFrom(msg.sender, address(this), sharesIn); 282: lp.safeTransferFrom(msg.sender, address(this), sharesIn); 289: address(this), 298: address(this), 398: amountOut = _swap(address(this), path, directions, minimumOut); 444: amountOut = _sellBase(lp, address(this), minimumOut); 490: amountOut = _sellQuote(lp, address(this), minimumOut);
92, 269, 282, 289, 298, 398, 444, 490
</details>📁 File: src/staking/LockingMultiRewards.sol 326: if (tokenAddress == stakingToken && tokenAmount > stakingToken.balanceOf(address(this)) - stakingTokenBalance) { 367: rewardToken.safeTransferFrom(msg.sender, address(this), amount); 464: stakingToken.safeTransferFrom(msg.sender, address(this), amount);
EnumerateSet
instead of nested mappingsNested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, wherein the original storage slot and the first key are concatenated and hashed, and then this hash is again concatenated with the second key and hashed. This process can be quite gas expensive due to the double-hashing operation and subsequent storage operation (sstore).
A possible optimization involves manually concatenating the keys followed by a single hash operation and an sstore. However, this technique introduces the risk of storage collision, especially when there are other nested hash maps in the contract that use the same key types. Because Solidity is unaware of the number and structure of nested hash maps in a contract, it follows a conservative approach in computing the storage slot to avoid possible collisions.
OpenZeppelin's EnumerableSet
provides a potential solution to this problem. It creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, which is not natively available in Solidity. EnumerableSet handles the element uniqueness internally and can therefore provide a more gas-efficient and collision-resistant alternative to nested mappings or multi-dimensional arrays in certain scenarios.
Gas saved per Instance: ~1,000 (Total: ~4,000)
<details> <summary><i>There are 4 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboarding.sol 41: mapping(address user => mapping(address token => Balances)) public balances;
📁 File: src/mimswap/periphery/Factory.sol 35: mapping(address base => mapping(address quote => address[] pools)) public pools;
</details>📁 File: src/staking/LockingMultiRewards.sol 94: mapping(address user => mapping(address token => uint256 amount)) public userRewardPerTokenPaid; 95: mapping(address user => mapping(address token => uint256 amount)) public rewards;
Utilizing gas-optimized math functions from libraries like Solady can lead to more efficient smart contracts. This is particularly beneficial in contracts where these operations are frequently used.
<details> <summary><i>There are 39 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboardingBoot.sol 163: return (userLocked * totalPoolShares) / totalLocked;
📁 File: src/mimswap/MagicLP.sol 125: _BLOCK_TIMESTAMP_LAST_ = uint32(block.timestamp % 2 ** 32); 435: baseAmount = (baseBalance * shareAmount) / totalShares; 436: quoteAmount = (quoteBalance * shareAmount) / totalShares; 513: _BASE_TARGET_ = uint112((uint256(_BASE_TARGET_) * baseBalance) / uint256(_BASE_RESERVE_)); 517: _QUOTE_TARGET_ = uint112((uint256(_QUOTE_TARGET_) * quoteBalance) / uint256(_QUOTE_RESERVE_)); 546: uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
📁 File: src/mimswap/libraries/DecimalMath.sol 24: return (target * d) / ONE; 32: return (target * ONE) / d; 54: p = (p * p) / ONE; 56: p = (p * target) / ONE;
📁 File: src/mimswap/libraries/Math.sol 21: uint256 remainder = a - quotient * b; 30: uint256 z = x / 2 + 1; 34: z = (x / z + z) / 2; 56: uint256 fairAmount = i * (V1 - V2); // i*delta 62: uint256 V0V0V1V2 = DecimalMath.divFloor((V0 * V0) / V1, V2); 64: return (((DecimalMath.ONE - k) + penalty) * fairAmount) / DecimalMath.ONE2; 93: uint256 ki = (4 * k) * i; 96: } else if ((ki * delta) / ki == delta) { 97: _sqrt = sqrt(((ki * delta) / V1) + DecimalMath.ONE2); 99: _sqrt = sqrt(((ki / V1) * delta) + DecimalMath.ONE2); 101: uint256 premium = DecimalMath.divFloor(_sqrt - DecimalMath.ONE, k * 2) + DecimalMath.ONE; 155: } else if ((idelta * V1) / idelta == V1) { 156: temp = (idelta * V1) / (V0 * V0); 158: temp = (((delta * V1) / V0) * i) / V0; 160: return (V1 * temp) / (temp + DecimalMath.ONE); 170: uint256 part2 = (((k * V0) / V1) * V0) + (i * delta); // kQ0^2/Q1-i*deltaB 171: uint256 bAbs = (DecimalMath.ONE - k) * V1; // (1-k)Q1 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) 188: uint256 denominator = (DecimalMath.ONE - k) * 2; // 2(1-k)
21, 30, 34, 56, 62, 64, 93, 96, 99, 101, 155, 158, 160, 170, 184, 188
📁 File: src/mimswap/periphery/Router.sol 257: baseAmountOut = (baseBalance * sharesIn) / totalShares; 258: quoteAmountOut = (quoteBalance * sharesIn) / totalShares; 379: if ((directions >> lastLpIndex) & 1 == 0) {
</details>📁 File: src/staking/LockingMultiRewards.sol 236: return unlockedSupply + ((lockedSupply * lockingBoostMultiplerInBips) / BIPS); 241: return bal.unlocked + ((bal.locked * lockingBoostMultiplerInBips) / BIPS); 258: return (block.timestamp / rewardsDuration) * rewardsDuration; 283: uint256 pendingRewardsPerToken = (timeElapsed * _rewardData[rewardToken].rewardRate * 1e18) / totalSupply_; 294: return ((balance_ * pendingUserRewardsPerToken) / 1e18) + rewards[user][rewardToken];
payable
Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.
Gas saved per Instance: ~21 (Total: ~336)
<details> <summary><i>There are 16 instances of this issue:</i></summary>📁 File: src/blast/BlastBox.sol 24: constructor(IERC20 weth_, BlastTokenRegistry registry_, address feeTo_) DegenBox(weth_) {
📁 File: src/blast/BlastDapp.sol 7: constructor() {
📁 File: src/blast/BlastGovernor.sol 15: constructor(address feeTo_, address _owner) OperatableV2(_owner) {
📁 File: src/blast/BlastMagicLP.sol 23: constructor(BlastTokenRegistry registry_, address feeTo_, address owner_) MagicLP(owner_) {
📁 File: src/blast/BlastOnboarding.sol 58: constructor() Owned(msg.sender) { 84: constructor(BlastTokenRegistry registry_, address feeTo_) {
📁 File: src/blast/BlastTokenRegistry.sol 12: constructor(address _owner) Owned(_owner) {}
📁 File: src/blast/BlastWrappers.sol 20: constructor(IWETH weth_, IFactory factory, address governor_) Router(weth_, factory) { 29: constructor( 47: constructor(address box_, address mim_, address governor_) CauldronV4(IBentoBoxV1(box_), IERC20(mim_)) {
📁 File: src/mimswap/MagicLP.sol 84: constructor(address owner_) Owned(owner_) {
📁 File: src/mimswap/auxiliary/FeeRateModel.sol 22: constructor(address maintainer_, address owner_) Owned(owner_) {
📁 File: src/mimswap/periphery/Factory.sol 38: constructor(address implementation_, IFeeRateModel maintainerFeeRateModel_, address owner_) Owned(owner_) {
📁 File: src/mimswap/periphery/Router.sol 38: constructor(IWETH weth_, IFactory factory_) {
📁 File: src/oracles/aggregators/MagicLpAggregator.sol 21: constructor(IMagicLP pair_, IAggregator baseOracle_, IAggregator quoteOracle_) {
</details>📁 File: src/staking/LockingMultiRewards.sol 112: constructor(
for
statements is more gas efficientCounting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable. More info
Gas saved per Instance: ~16 (Total: ~128)
<details> <summary><i>There are 8 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboarding.sol 165: for (uint256 i = 0; i < tokens.length; i++) {
📁 File: src/mimswap/periphery/Router.sol /// @audit line 556 544: for (uint256 i = 0; i < iterations; ) { 545: if (directions & 1 == 0) { 546: // Sell base 547: IMagicLP(path[i]).sellBase(address(path[i + 1])); 548: } else { 549: // Sell quote 550: IMagicLP(path[i]).sellQuote(address(path[i + 1])); 551: } 552: 553: directions >>= 1; 554: 555: unchecked { 556: ++i; 557: } 558: }
</details>📁 File: src/staking/LockingMultiRewards.sol /// @audit line 450 405: for (uint256 i; i < users.length; ) { 406: address user = users[i]; 407: Balances storage bal = _balances[user]; 408: LockedBalance[] storage locks = _userLocks[user]; 409: 410: if (locks.length == 0) { 411: revert ErrNoLocks(); 412: } 413: 414: uint256 index = lockIndexes[i]; 415: 416: // Prevents processing `lastLockIndex` out of order 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: } 425: 426: uint256 amount = locks[index].amount; 427: uint256 lastIndex = locks.length - 1; 428: 429: /// Last lock index changed place with the one we just swapped. 430: if (lastLockIndex[user] == lastIndex) { 431: lastLockIndex[user] = index; 432: } 433: 434: if (index != lastIndex) { 435: locks[index] = locks[lastIndex]; 436: emit LogLockIndexChanged(user, lastIndex, index); 437: } 438: 439: locks.pop(); 440: 441: unlockedSupply += amount; 442: lockedSupply -= amount; 443: 444: bal.unlocked += amount; 445: bal.locked -= amount; 446: 447: emit LogUnlocked(user, amount, index); 448: 449: unchecked { 450: ++i; 451: } 452: } /// @audit line 550 547: for (uint256 i; i < rewardTokens.length; ) { 548: _updateRewardsGlobal(rewardTokens[i], totalSupply_); 549: unchecked { 550: ++i; 551: } 552: } /// @audit line 566 561: for (uint256 i; i < rewardTokens.length; ) { 562: address token = rewardTokens[i]; 563: _udpateUserRewards(user, balance, token, _updateRewardsGlobal(token, totalSupply_)); 564: 565: unchecked { 566: ++i; 567: } 568: } /// @audit line 590 575: for (uint256 i; i < rewardTokens.length; ) { 576: address token = rewardTokens[i]; 577: uint256 rewardPerToken_ = _updateRewardsGlobal(token, totalSupply_); 578: 579: // Record each user's rewards /// @audit line 585 580: for (uint256 j; j < users.length; ) { 581: address user = users[j]; 582: _udpateUserRewards(user, balanceOf(user), token, rewardPerToken_); 583: 584: unchecked { 585: ++j; 586: } 587: } 588: 589: unchecked { 590: ++i; 591: } 592: } /// @audit line 654 614: for (uint256 i; i < rewardTokens.length; ) { 615: address rewardToken = rewardTokens[i]; 616: uint256 rewardAmount = rewards[user][rewardToken]; 617: 618: // in all scenario, reset the reward amount immediately 619: rewards[user][rewardToken] = 0; 620: 621: // don't assume the rewardTokens array is always the same length as the items array 622: // as new reward tokens can be added by the owner 623: if (i < rewardItemLength) { 624: RewardLockItem storage item = _rewardLock.items[i]; 625: 626: // expired lock, claim existing unlocked rewards if any 627: if (expired) { 628: uint256 amount = item.amount; 629: 630: // since this current lock is expired and that item index 631: // matches the reward index, override the current amount 632: // with the new locked amount. 633: item.amount = rewardAmount; 634: 635: // use cached amount 636: if (amount > 0) { 637: rewardToken.safeTransfer(user, amount); 638: emit LogRewardPaid(user, rewardToken, amount); 639: } 640: } else { 641: // not expired, just add to the existing lock 642: item.amount += rewardAmount; 643: } 644: } 645: // new reward token, create a new lock item 646: // could mean it's adding to an existing lock or creating a new one 647: else { 648: _userRewardLock[user].items.push(RewardLockItem({token: rewardToken, amount: rewardAmount})); 649: } 650: 651: emit LogRewardLocked(user, rewardToken, rewardAmount); 652: 653: unchecked { 654: ++i; 655: } 656: }
Variables should be declared outside of loops, and get overriden with each iteration of loop, By doing so we save gas cost for memory variable declaration in each iteration.
Gas saved per Instance: ~15 (Total: ~225)
<i>There are 15 instaces of this issue:</i>
📁 File: src/staking/LockingMultiRewards.sol /// @audit loop from line 405 to line 452 406: address user = users[i]; /// @audit loop from line 405 to line 452 407: Balances storage bal = _balances[user]; /// @audit loop from line 405 to line 452 408: LockedBalance[] storage locks = _userLocks[user]; /// @audit loop from line 405 to line 452 414: uint256 index = lockIndexes[i]; /// @audit loop from line 405 to line 452 426: uint256 amount = locks[index].amount; /// @audit loop from line 405 to line 452 427: uint256 lastIndex = locks.length - 1; /// @audit loop from line 561 to line 568 562: address token = rewardTokens[i]; /// @audit loop from line 575 to line 592 576: address token = rewardTokens[i]; /// @audit loop from line 575 to line 592 577: uint256 rewardPerToken_ = _updateRewardsGlobal(token, totalSupply_); /// @audit loop from line 575 to line 592 /// @audit loop from line 580 to line 587 581: address user = users[j]; /// @audit loop from line 614 to line 656 615: address rewardToken = rewardTokens[i]; /// @audit loop from line 614 to line 656 616: uint256 rewardAmount = rewards[user][rewardToken]; /// @audit loop from line 614 to line 656 624: RewardLockItem storage item = _rewardLock.items[i]; /// @audit loop from line 614 to line 656 628: uint256 amount = item.amount;
406, 414, 426, 562, 576, 581, 615, 624, 628
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
<details> <summary><i>There are 4 instances of this issue:</i></summary>📁 File: src/mimswap/MagicLP.sol 63: uint256 public constant MAX_I = 10 ** 36; 64: uint256 public constant MAX_K = 10 ** 18;
</details>📁 File: src/mimswap/libraries/DecimalMath.sol 20: uint256 internal constant ONE = 10 ** 18; 21: uint256 internal constant ONE2 = 10 ** 36;
do
-while
is cheaper than for
-loops when the initial check can be skippedUsing do-while
loops instead of for
loops can be more gas-efficient.
Even if you add an if
condition to account for the case where the loop doesn't execute at all, a do-while
loop can still be cheaper in terms of gas.
Gas saved per Instance: ~255 (Total: ~2,040)
<details> <summary><i>There are 8 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboarding.sol 165: for (uint256 i = 0; i < tokens.length; i++) {
📁 File: src/mimswap/periphery/Router.sol 544: for (uint256 i = 0; i < iterations; ) {
</details>📁 File: src/staking/LockingMultiRewards.sol 405: for (uint256 i; i < users.length; ) { 547: for (uint256 i; i < rewardTokens.length; ) { 561: for (uint256 i; i < rewardTokens.length; ) { 575: for (uint256 i; i < rewardTokens.length; ) { 580: for (uint256 j; j < users.length; ) { 614: for (uint256 i; i < rewardTokens.length; ) {
In Solidity, unnecessary operations can waste gas. For example, a transfer function without a zero amount check uses gas even if called with a zero amount, since the contract state remains unchanged. Implementing a zero amount check avoids these unnecessary function calls, saving gas and improving efficiency.
Gas saved per Instance: ~20 (Total: ~520)
<details> <summary><i>There are 26 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboarding.sol /// @audit check for zero amount on the 'amount' variable 102: token.safeTransferFrom(msg.sender, address(this), amount); /// @audit check for zero amount on the 'amount' variable 138: token.safeTransfer(msg.sender, amount); /// @audit check for zero amount on the 'amount' variable 210: token.safeTransfer(to, amount);
📁 File: src/mimswap/MagicLP.sol /// @audit check for zero amount on the 'amount' variable 466: token.safeTransfer(to, amount);
📁 File: src/mimswap/periphery/Router.sol /// @audit check for zero amount on the 'baseInAmount' variable 68: baseToken.safeTransferFrom(msg.sender, clone, baseInAmount); /// @audit check for zero amount on the 'quoteInAmount' variable 69: quoteToken.safeTransferFrom(msg.sender, clone, quoteInAmount); /// @audit check for zero amount on the 'tokenInAmount' variable 91: token.safeTransferFrom(msg.sender, clone, tokenInAmount); /// @audit check for zero amount on the 'baseAdjustedInAmount' variable 172: IMagicLP(lp)._BASE_TOKEN_().safeTransferFrom(msg.sender, lp, baseAdjustedInAmount); /// @audit check for zero amount on the 'quoteAdjustedInAmount' variable 173: IMagicLP(lp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, lp, quoteAdjustedInAmount); /// @audit check for zero amount on the 'baseInAmount' variable 186: IMagicLP(lp)._BASE_TOKEN_().safeTransferFrom(msg.sender, lp, baseInAmount); /// @audit check for zero amount on the 'quoteInAmount' variable 187: IMagicLP(lp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, lp, quoteInAmount); /// @audit check for zero amount on the 'wethAdjustedAmount' variable 217: address(weth).safeTransfer(lp, wethAdjustedAmount); /// @audit check for zero amount on the 'tokenAdjustedAmount' variable 224: token.safeTransferFrom(msg.sender, lp, tokenAdjustedAmount); /// @audit check for zero amount on the 'tokenInAmount' variable 246: token.safeTransferFrom(msg.sender, lp, tokenInAmount); /// @audit check for zero amount on the 'sharesIn' variable 269: lp.safeTransferFrom(msg.sender, address(this), sharesIn); /// @audit check for zero amount on the 'sharesIn' variable 282: lp.safeTransferFrom(msg.sender, address(this), sharesIn); /// @audit check for zero amount on the 'tokenAmountOut' variable 311: token.safeTransfer(to, tokenAmountOut); /// @audit check for zero amount on the 'amountIn' variable 328: IMagicLP(firstLp)._BASE_TOKEN_().safeTransferFrom(msg.sender, address(firstLp), amountIn); /// @audit check for zero amount on the 'amountIn' variable 330: IMagicLP(firstLp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, address(firstLp), amountIn); /// @audit check for zero amount on the 'amountIn' variable 393: IMagicLP(firstLp)._BASE_TOKEN_().safeTransferFrom(msg.sender, firstLp, amountIn); /// @audit check for zero amount on the 'amountIn' variable 395: IMagicLP(firstLp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, firstLp, amountIn); /// @audit check for zero amount on the 'amountIn' variable 411: IMagicLP(lp)._BASE_TOKEN_().safeTransferFrom(msg.sender, lp, amountIn); /// @audit check for zero amount on the 'amountIn' variable 443: IMagicLP(lp)._BASE_TOKEN_().safeTransferFrom(msg.sender, lp, amountIn); /// @audit check for zero amount on the 'amountIn' variable 456: IMagicLP(lp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, lp, amountIn); /// @audit check for zero amount on the 'amountIn' variable 489: IMagicLP(lp)._QUOTE_TOKEN_().safeTransferFrom(msg.sender, lp, amountIn);
68, 91, 172, 186, 217, 224, 246, 269, 282, 311, 328, 330, 393, 395, 411, 443, 456, 489
</details>📁 File: src/staking/LockingMultiRewards.sol /// @audit check for zero amount on the 'amount' variable 367: rewardToken.safeTransferFrom(msg.sender, address(this), amount);
Emitted values should not be read from storage again. Instead, the existing values from memory should be used.
Gas saved per Instance: ~100 (Total: ~800)
<details> <summary><i>There are 8 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboardingBoot.sol /// @audit pool, staking, totalPoolShares 124: emit LogLiquidityBootstrapped(pool, address(staking), totalPoolShares); /// @audit ready 148: emit LogReadyChanged(ready);
📁 File: src/mimswap/MagicLP.sol /// @audit _BASE_TOKEN_, _QUOTE_TOKEN_ 264: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, to); /// @audit _QUOTE_TOKEN_, _BASE_TOKEN_ 287: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, to); /// @audit _QUOTE_TOKEN_, _BASE_TOKEN_ 325: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, assetTo); /// @audit _BASE_TOKEN_, _QUOTE_TOKEN_ 347: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, assetTo);
📁 File: src/mimswap/periphery/Factory.sol /// @audit maintainerFeeRateModel 88: emit LogCreated(clone, baseToken_, quoteToken_, creator, lpFeeRate_, maintainerFeeRateModel, i_, k_);
</details>📁 File: src/staking/LockingMultiRewards.sol /// @audit minLockAmount 318: emit LogSetMinLockAmount(minLockAmount, _minLockAmount);
Some functions don't have a body: consider commenting why, or add some logic. Otherwise, refactor the code and remove these functions.
<details> <summary><i>There are 3 instances of this issue:</i></summary>📁 File: src/blast/BlastGovernor.sol 13: receive() external payable {}
📁 File: src/mimswap/MagicLP.sol 601: function _afterInitialized() internal virtual {}
</details>📁 File: src/mimswap/periphery/Router.sol 36: receive() external payable {}
Function that are public
/external
and public
state variable names can be optimized to save gas.
Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted. Reference
Gas saved per Instance: ~128 (Total: ~1,408)
<details> <summary><i>There are 11 instances of this issue:</i></summary>📁 File: src/blast/BlastBox.sol /// @audit optimized order: _onBeforeDeposit(), setTokenEnabled(), setFeeTo(), callBlastPrecompile(), claimTokenYields(), claimGasYields(), _configure(), isOwner() 13: contract BlastBox is DegenBox, OperatableV3 {
📁 File: src/blast/BlastGovernor.sol /// @audit optimized order: setFeeTo(), claimNativeYields(), callBlastPrecompile(), execute(), claimMaxGasYields() 7: contract BlastGovernor is OperatableV2 {
📁 File: src/blast/BlastMagicLP.sol /// @audit optimized order: setFeeTo(), callBlastPrecompile(), updateTokenClaimables(), setOperator(), version(), claimTokenYields(), claimGasYields(), _afterInitialized(), _updateTokenClaimables() 10: contract BlastMagicLP is MagicLP {
📁 File: src/blast/BlastOnboarding.sol /// @audit optimized order: claimTokenYields(), open(), setFeeTo(), withdraw(), callBlastPrecompile(), setTokenSupported(), pause(), setCap(), setBootstrapper(), close(), unpause(), deposit(), claimGasYields(), lock(), rescue(), _implementation() 64: contract BlastOnboarding is BlastOnboardingData, Proxy {
📁 File: src/blast/BlastOnboardingBoot.sol /// @audit optimized order: previewTotalPoolShares(), setReady(), initialize(), setStaking(), claimable(), bootstrap(), claim(), _claimable() 34: contract BlastOnboardingBoot is BlastOnboardingBootDataV1 {
📁 File: src/mimswap/MagicLP.sol /// @audit optimized order: sync(), getPMMStateForCall(), getMidPrice(), sellQuote(), flashLoan(), ratioSync(), sellBase(), sellShares(), init(), getPMMState(), symbol(), querySellBase(), correctRState(), getQuoteInput(), querySellQuote(), getBaseInput(), version(), buyShares(), getUserFeeRate(), decimals(), setParameters(), rescue(), getReserves(), name(), _resetTargetAndReserve(), _twapUpdate(), _setReserve(), _sync(), _transferBaseOut(), _transferQuoteOut(), _mint(), _afterInitialized() 25: contract MagicLP is ERC20, ReentrancyGuard, Owned {
📁 File: src/mimswap/auxiliary/FeeRateModel.sol /// @audit optimized order: getFeeRate(), setImplementation(), setMaintainer() 13: contract FeeRateModel is Owned {
📁 File: src/mimswap/periphery/Factory.sol /// @audit optimized order: getPoolCount(), create(), removePool(), getUserPoolCount(), setLpImplementation(), addPool(), predictDeterministicAddress(), setMaintainerFeeRateModel(), _addPool(), _computeSalt() 11: contract Factory is Owned {
📁 File: src/mimswap/periphery/Router.sol /// @audit optimized order: removeLiquidityETH(), swapTokensForTokens(), addLiquidityETH(), sellQuoteETHForTokens(), sellBaseTokensForTokens(), swapTokensForETH(), createPool(), addLiquidityUnsafe(), swapETHForTokens(), addLiquidityETHUnsafe(), createPoolETH(), previewRemoveLiquidity(), sellBaseTokensForETH(), previewAddLiquidity(), removeLiquidity(), previewCreatePool(), sellQuoteTokensForTokens(), sellBaseETHForTokens(), addLiquidity(), sellQuoteTokensForETH(), _addLiquidity(), _adjustAddLiquidity(), _swap(), _sellBase(), _sellQuote(), _validatePath(), _validateDecimals() 12: contract Router {
📁 File: src/oracles/aggregators/MagicLpAggregator.sol /// @audit optimized order: decimals(), _getReserves(), latestRoundData(), latestAnswer() 9: contract MagicLpAggregator is IAggregator {
</details>📁 File: src/staking/LockingMultiRewards.sol /// @audit optimized order: nextUnlockTime(), rewardPerToken(), lock(), remainingEpochTime(), stakeFor(), unlocked(), locked(), userLocksLength(), rewardTokensLength(), nextEpoch(), stake(), userRewardLock(), notifyRewardAmount(), withdrawWithRewards(), processExpiredLocks(), addReward(), epoch(), pause(), balanceOf(), lastTimeRewardApplicable(), recover(), _rewardPerToken(), rewardData(), unpause(), withdraw(), balances(), earned(), totalSupply(), setMinLockAmount(), rewardsForDuration(), getRewards(), userLocks(), _earned(), _stakeFor(), _createLock(), _updateRewardsGlobal(), _udpateUserRewards(), _updateRewards(), _updateRewardsForUser(), _updateRewardsForUsers(), _getRewards() 14: contract LockingMultiRewards is OperatableV2, Pausable {
Before Solidity version 0.6.9
, external functions are cheaper than public ones
📁 File: src/blast/BlastWrappers.sol 59: function init(bytes calldata data) public payable override {
📁 File: src/mimswap/MagicLP.sol 155: function name() public view override returns (string memory) { 159: function symbol() public pure override returns (string memory) { 163: function decimals() public view override returns (uint8) { 228: function getBaseInput() public view returns (uint256 input) { 232: function getQuoteInput() public view returns (uint256 input) { 470: function setParameters( 471: address assetTo, 472: uint256 newLpFeeRate, 473: uint256 newI, 474: uint256 newK, 475: uint256 baseOutAmount, 476: uint256 quoteOutAmount, 477: uint256 minBaseReserve, 478: uint256 minQuoteReserve 479: ) public nonReentrant onlyImplementationOwner {
📁 File: src/mimswap/auxiliary/FeeRateModel.sol 57: function setImplementation(address implementation_) public onlyOwner {
📁 File: src/mimswap/periphery/Factory.sol 65: function predictDeterministicAddress( 66: address creator, 67: address baseToken_, 68: address quoteToken_, 69: uint256 lpFeeRate_, 70: uint256 i_, 71: uint256 k_ 72: ) public view returns (address) {
</details>📁 File: src/staking/LockingMultiRewards.sol 150: function stake(uint256 amount, bool lock_) public whenNotPaused { 155: function lock(uint256 amount) public whenNotPaused { 265: function remainingEpochTime() public view returns (uint256) { 288: function earned(address user, address rewardToken) public view returns (uint256) { 300: function addReward(address rewardToken) public onlyOwner { 361: function notifyRewardAmount(address rewardToken, uint256 amount, uint minRemainingTime) public onlyOperators {
payable
Gas saved per Instance: ~21 (Total: ~63)
<details> <summary><i>There are 3 instances of this issue:</i></summary>📁 File: src/blast/BlastOnboardingBoot.sol 129: function initialize(Router _router) external onlyOwner {
</details>📁 File: src/mimswap/MagicLP.sol 91: function init( 92: address baseTokenAddress, 93: address quoteTokenAddress, 94: uint256 lpFeeRate, 95: address mtFeeRateModel, 96: uint256 i, 97: uint256 k 98: ) external {
internal
functions only used once can be inlined so save gasIf a internal function is only used once it doesn't make sense to modularise it unless the function which does call the function would be overly long and complex otherwise
Gas saved per Instance: ~30 (Total: ~90)
<details> <summary><i>There are 3 instances of this issue:</i></summary>📁 File: src/mimswap/MagicLP.sol 528: function _resetTargetAndReserve() internal returns (uint256 baseBalance, uint256 quoteBalance) {
</details>📁 File: src/staking/LockingMultiRewards.sol 544: function _updateRewards() internal { 572: function _updateRewardsForUsers(address[] memory users) internal {
call
can be optimized with assemblyreturnData
is copied to memory even if the variable is not utilized: the proper way to handle this is through a low level assembly call and save 159 gas.
// before (bool success,) = payable(receiver).call{gas: gas, value: value}(""); //after bool success; assembly { success := call(gas, receiver, value, 0, 0, 0, 0) }
Gas saved per Instance: ~159
<i>There is one instance of this issue:</i>
📁 File: src/blast/BlastGovernor.sol 54: (success, result) = to.call{value: value}(data);
When using storage arrays, solidity adds an internal lookup of the array's length (a Gcoldsload 2100 gas) to ensure you don't read past the array's end. You can avoid this lookup by using a mapping
and storing the number of entries in a separate storage variable. In cases where you have sentinel values (e.g. 'zero' means invalid), you can avoid length checks
Gas saved per Instance: ~2,100 (Total: ~10,500)
<details> <summary><i>There are 5 instances of this issue:</i></summary>📁 File: src/mimswap/periphery/Factory.sol 35: mapping(address base => mapping(address quote => address[] pools)) public pools; 36: mapping(address creator => address[] pools) public userPools;
</details>📁 File: src/staking/LockingMultiRewards.sol 74: RewardLockItem[] items; 91: mapping(address user => LockedBalance[] locks) private _userLocks; 98: address[] public rewardTokens;
Consolidating multiple event emissions into a single event in Solidity can result in significant gas savings. Each event emission in Ethereum involves a gas cost, specifically for the topics logged with the event. By merging sequential events into a singular event, you can save on the Glogtopic cost, which is incurred for each topic of each event. This approach can save around 375 gas per additional topic. This strategy is particularly beneficial in functions where multiple related events are emitted in sequence. However, it's crucial to balance gas optimization with the clarity and utility of the event data for off-chain consumers.
Gas saved per Instance: ~375 (Total: ~3,375)
<details> <summary><i>There are 9 instances of this issue:</i></summary>📁 File: src/mimswap/MagicLP.sol /// @audit 'Swap', 'RChange' 244: function sellBase(address to) external nonReentrant returns (uint256 receiveQuoteAmount) { 245: uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); 246: uint256 baseInput = baseBalance - uint256(_BASE_RESERVE_); 247: uint256 mtFee; 248: uint256 newBaseTarget; 249: PMMPricing.RState newRState; 250: (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput); 251: 252: _transferQuoteOut(to, receiveQuoteAmount); 253: _transferQuoteOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); 254: 255: // update TARGET 256: if (_RState_ != uint32(newRState)) { 257: _BASE_TARGET_ = newBaseTarget.toUint112(); 258: _RState_ = uint32(newRState); 259: emit RChange(newRState); 260: } 261: 262: _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); 263: 264: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, to); 265: } /// @audit 'Swap', 'RChange' 267: function sellQuote(address to) external nonReentrant returns (uint256 receiveBaseAmount) { 268: uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); 269: uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_); 270: uint256 mtFee; 271: uint256 newQuoteTarget; 272: PMMPricing.RState newRState; 273: (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(tx.origin, quoteInput); 274: 275: _transferBaseOut(to, receiveBaseAmount); 276: _transferBaseOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); 277: 278: // update TARGET 279: if (_RState_ != uint32(newRState)) { 280: _QUOTE_TARGET_ = newQuoteTarget.toUint112(); 281: _RState_ = uint32(newRState); 282: emit RChange(newRState); 283: } 284: 285: _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); 286: 287: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, to); 288: } /// @audit 'FlashLoan', 'Swap', 'Swap', 'RChange', 'RChange' 290: function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external nonReentrant { 291: _transferBaseOut(assetTo, baseAmount); 292: _transferQuoteOut(assetTo, quoteAmount); 293: 294: if (data.length > 0) { 295: ICallee(assetTo).FlashLoanCall(msg.sender, baseAmount, quoteAmount, data); 296: } 297: 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 /// @audit 'Swap', 'RChange' 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: } 318: 319: _transferBaseOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); 320: if (_RState_ != uint32(newRState)) { 321: _QUOTE_TARGET_ = newQuoteTarget.toUint112(); 322: _RState_ = uint32(newRState); 323: emit RChange(newRState); 324: } 325: emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, assetTo); 326: } 327: 328: // sell base case 329: // base input + quote output /// @audit 'Swap', 'RChange' 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: } 340: 341: _transferQuoteOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee); 342: if (_RState_ != uint32(newRState)) { 343: _BASE_TARGET_ = newBaseTarget.toUint112(); 344: _RState_ = uint32(newRState); 345: emit RChange(newRState); 346: } 347: emit Swap(address(_BASE_TOKEN_), address(_QUOTE_TOKEN_), baseInput, receiveQuoteAmount, msg.sender, assetTo); 348: } 349: 350: _sync(); 351: 352: emit FlashLoan(msg.sender, assetTo, baseAmount, quoteAmount); 353: }
</details>📁 File: src/staking/LockingMultiRewards.sol /// @audit 'LogUnlocked', 'LogLockIndexChanged' 397: function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators { 398: if (users.length != lockIndexes.length) { 399: revert ErrLengthMismatch(); 400: } 401: 402: _updateRewardsForUsers(users); 403: 404: // Release all expired users' locks /// @audit 'LogUnlocked', 'LogLockIndexChanged' 405: for (uint256 i; i < users.length; ) { 406: address user = users[i]; 407: Balances storage bal = _balances[user]; 408: LockedBalance[] storage locks = _userLocks[user]; 409: 410: if (locks.length == 0) { 411: revert ErrNoLocks(); 412: } 413: 414: uint256 index = lockIndexes[i]; 415: 416: // Prevents processing `lastLockIndex` out of order 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: } 425: 426: uint256 amount = locks[index].amount; 427: uint256 lastIndex = locks.length - 1; 428: 429: /// Last lock index changed place with the one we just swapped. 430: if (lastLockIndex[user] == lastIndex) { 431: lastLockIndex[user] = index; 432: } 433: 434: if (index != lastIndex) { 435: locks[index] = locks[lastIndex]; 436: emit LogLockIndexChanged(user, lastIndex, index); 437: } 438: 439: locks.pop(); 440: 441: unlockedSupply += amount; 442: lockedSupply -= amount; 443: 444: bal.unlocked += amount; 445: bal.locked -= amount; 446: 447: emit LogUnlocked(user, amount, index); 448: 449: unchecked { 450: ++i; 451: } 452: } 453: } /// @audit 'LogRewardLockCreated', 'LogRewardLocked', 'LogRewardPaid' 597: function _getRewards(address user) internal { 598: RewardLock storage _rewardLock = _userRewardLock[user]; 599: 600: // first ever lock is always expired because `unlockTime` is 0 601: // unlock time is aligned to epoch 602: bool expired = _rewardLock.unlockTime <= block.timestamp; 603: 604: // cache the length here since the loop will be modifying the array 605: uint256 rewardItemLength = _rewardLock.items.length; 606: 607: // expired lock 608: // existing lock items will be reused 609: if (expired) { 610: _rewardLock.unlockTime = nextEpoch(); 611: emit LogRewardLockCreated(user, _rewardLock.unlockTime); 612: } 613: /// @audit 'LogRewardLocked', 'LogRewardPaid' 614: for (uint256 i; i < rewardTokens.length; ) { 615: address rewardToken = rewardTokens[i]; 616: uint256 rewardAmount = rewards[user][rewardToken]; 617: 618: // in all scenario, reset the reward amount immediately 619: rewards[user][rewardToken] = 0; 620: 621: // don't assume the rewardTokens array is always the same length as the items array 622: // as new reward tokens can be added by the owner 623: if (i < rewardItemLength) { 624: RewardLockItem storage item = _rewardLock.items[i]; 625: 626: // expired lock, claim existing unlocked rewards if any 627: if (expired) { 628: uint256 amount = item.amount; 629: 630: // since this current lock is expired and that item index 631: // matches the reward index, override the current amount 632: // with the new locked amount. 633: item.amount = rewardAmount; 634: 635: // use cached amount 636: if (amount > 0) { 637: rewardToken.safeTransfer(user, amount); 638: emit LogRewardPaid(user, rewardToken, amount); 639: } 640: } else { 641: // not expired, just add to the existing lock 642: item.amount += rewardAmount; 643: } 644: } 645: // new reward token, create a new lock item 646: // could mean it's adding to an existing lock or creating a new one 647: else { 648: _userRewardLock[user].items.push(RewardLockItem({token: rewardToken, amount: rewardAmount})); 649: } 650: 651: emit LogRewardLocked(user, rewardToken, rewardAmount); 652: 653: unchecked { 654: ++i; 655: } 656: } 657: }
#0 - c4-pre-sort
2024-03-15T14:58:01Z
141345 marked the issue as high quality report
#1 - 141345
2024-03-16T00:21:17Z
35 Bozho l r nc 3 1 50
G 1 i G 2 i G 3 i G 4 n G 5 n G 6 n G 7 n G 8 n G 9 n G 10 n G 11 n G 12 n G 13 n G 14 n G 15 n G 16 n G 17 n G 18 n G 19 n G 20 n G 21 n G 22 n G 23 n G 24 i G 25 i G 26 n G 27 n G 28 n G 29 n G 30 n G 31 i G 32 n G 33 n G 34 i G 35 n G 36 n G 37 i G 38 n G 39 i G 40 n G 41 n G 42 n G 43 l G 44 i G 45 r G 46 l G 47 l G 48 i G 49 n G 50 n G 51 n G 52 n G 53 n G 54 i G 55 i G 56 i G 57 i G 58 i G 59 i G 60 n G 61 n G 62 n G 63 n G 64 n G 65 n G 66 n G 67 n G 68 n G 69 i G 70 n G 71 n G 72 n G 73 i
#2 - 0xCalibur
2024-03-17T14:24:23Z
no factor
#3 - c4-sponsor
2024-03-28T16:58:05Z
0xCalibur (sponsor) disputed
#4 - c4-judge
2024-03-29T16:36:30Z
thereksfour marked the issue as selected for report
#5 - c4-judge
2024-04-06T07:47:29Z
thereksfour marked the issue as not selected for report
#6 - c4-judge
2024-04-06T08:00:37Z
thereksfour marked the issue as grade-a