60450 sc insight code optimizations and enhancemets for efficient gas usage in several functions
Submitted on Nov 22nd 2025 at 20:10:35 UTC by @KKam86 for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #60450
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/StargateNFT/StargateNFT.sol
Impacts:
Description
Brief/Intro
Following code optimizations may reduce gas costs when making calls to several functions in the contracts:
a) caching storage value outside the loop and then reading it in each iteration
b) correct order of the validations from more basic to more specific
c) using cached output values from one external call rather than making next external calls to the same function
d) unused named return variables which are more gas efficient than no named returns
e) early validations before making writes to the storage
Vulnerability Details
Cache storage value as local variable outside of the loop:
When the length of an storage array or storage value used for iterating is not cached outside of a loop, the Solidity compiler reads these values from storage during each iteration. This results in extra SLOAD operations which are more costly than reading from function local variables. Following functions in Levels library reads storage value during each iteration in their for loops:
Consider caching storage $.MAX_LEVEL_ID variable before declaring memory array and then use cached value instead reading from storage.
Also caching length of memory array to local variable and then reading this variable is more efficient and bit cheaper than reading the length from memory:
Prefer early validation before reading and writing to storage:
Currently function addLevel() in Levels library reads and writes from storage before making validation:
Consider moving $.MAX_LEVEL_ID++; and _levelAndSupply.level.id = $.MAX_LEVEL_ID; operations behind validations. Function _validateLevel() only validates name and vetAmountRequiredToStake of the level (id is not validated):
Early validation is more gas efficient. If function reverts used gas is not returned to the caller. Currently gas is used for incrementing the storage MAX_LEVEL_ID value and then reading it when overriding level ID.
Incorrect order of validations in
TokenManager.removeTokenManager()function:
Currently the order of the validation in removeTokenManager() is as follows:
Checking currentManager address for not being address(0) should be first because:
a) msg.sender will never be empty/zero address
b) this will be more gas efficient. Now if function reverts, gas is used first for external call to ownerOf() function
Unused named return variable in
Stargate.stake():
Function stake() declares uint256 tokenId return variable but not using it when returning:
Consider using declared uint256 tokenId variable and change the return statement from:
to:
Also according to https://x.com/DevDacian/status/1796396988659093968 using named return variables is more gas efficient.
Use cached balance value instead of another call to
balanceOf()function:
In function MintingLogic.boostOnBehalfOf() output value from balanceOf() call is cached to memory value:
Next this cached value is not used in the if check and instead next external call to balanceOf() is used:
Consider using cached balance instead of making another call to balanceOf(). If the result of an external call is used multiple times, making the call each time is inefficient and costly.
Impact Details
Every unnecessary external call or storage write/read operation performed by the function is expensive and makes entire transaction more costly. This should be avoided by applying proper optimisation methods.
Proof of Concept
Proof of Concept
For example getter function
getLevels()inStargateNFTcontract calls externalgetLevels()function from libraryLevels:
Levels.getLevels()constructs memory levels array with the use offorloop. Number of iterations depends on storage variable$.MAX_LEVEL_ID.$.MAX_LEVEL_IDvariable is type ofuint8so theoretically the max iterations can be almost255. Now each iteration reads $.MAX_LEVEL_ID which isSLOADoperation in EVM. Minimum gas for this operation according to (for the purpose of this example) https://www.evm.codes/?fork=prague#54 is100while for comparisonMLOADminimum gas is only3https://www.evm.codes/?fork=prague#51 .
As we can see reading from memory is much cheaper than reading from storage and reading from local variables is also bit cheaper than reading from memory. In result for loop in getLevels() should be more efficient when reading $.MAX_LEVEL_ID from cached local function variable.
Was this helpful?