Platform: Code4rena
Start Date: 05/05/2022
Pot Size: $125,000 DAI
Total HM: 17
Participants: 62
Period: 14 days
Judge: leastwood
Total Solo HM: 15
Id: 120
League: ETH
Rank: 16/62
Findings: 2
Award: $666.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xkatana, 0xsomeone, AuditsAreUS, BouSalman, BowTiedWardens, Cityscape, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Picodes, Ruhum, TerrierLover, WatchPug, Waze, bobirichman, catchup, cccz, cryptphi, csanuragjain, delfin454000, ellahi, fatherOfBlocks, hake, horsefacts, hyh, jayjonah8, joestakey, kebabsec, kenta, mics, oyc_109, robee, samruna, shenwilly, sikorico, simon135, throttle, tintin
310.858 DAI - $310.86
Few vulnerabilities were found examining the contracts. The main concerns are with setters not emitting events
Setters should also check the input value before updating a storage variable.
in AlchemicTokenV1.sol
, the sentinel can add addresses to a blacklist, preventing them from minting. It is an irreversible process as there is no "unblacklist" function.
Low
Example: The sentinel calls setBlacklist
, passing the address to blacklist, but they make a mistake in typing the address and blacklist someone else instead. There is no way to remove that address from the blacklist
Manual Analysis
You could add a removeBlacklist
function that would perform blacklist[minter] = false
, but effectively you can also remove setBlacklist
altogether: instead, just call setCeiling
with maximum = 0
, which does the same thing.
It is not recommended to emit events before the end of the computations, as the function might revert based on conditions ahead of the event emission
Non-Critical
Instances include:
gALCX.sol:77: emit ExchangeRateChange(exchangeRate); //emitted before restaking happens line 78.
Manual Analysis
Place the event emission in the last position in the function.
Setters should emit an event so that Dapps can detect important changes to storage. In particular, it is important that access control changes can be tracked off-chain.
Low
Instances include:
AlchemicTokenV1.sol:92: setWhiteList() AlchemicTokenV1.sol:101: setSentinel() AlchemicTokenV1.sol:110: setBlacklist() AlchemicTokenV1.sol:131: setCeiling()
AlchemicTokenV2.sol:119: setWhiteList() AlchemicTokenV2.sol:128: setSentinel() AlchemicTokenV2.sol:164: setMaxFlashLoan()
AlchemicTokenV2Base.sol:132: setWhiteList() AlchemicTokenV2Base.sol:141: setSentinel() AlchemicTokenV2Base.sol:151: setCeiling() AlchemicTokenV2Base.sol:196: setMaxFlashLoan()
gALCX.sol:39: transferOwnership() gALCX.sol:46: migrateSource()
TransmuterBuffer.sol:178: setWeights()
TransmuterV2.sol:158: initialize() //state variable: buffer TransmuterV2.sol:196: setCollateralSource()
Manual Analysis
Emit an event in all setters.
When there are mappings that use the same key value, having separate fields is error prone, for instance in case of deletion or with future new fields.
Non-Critical
Instances include:
AlchemicTokenV1.sol:37: mapping (address => uint256) public ceiling; AlchemicTokenV1.sol:40: mapping (address => uint256) public hasMinted;
AlchemicTokenV2Base.sol:40: mapping (address => uint256) public mintCeiling; AlchemicTokenV2Base.sol:43: mapping (address => uint256) public hasMinted;
AlchemistV2.sol:34: mapping (address => uint256) balances; AlchemistV2.sol:36: mapping (address => uint256) lastAccruedWeights;
Manual Analysis
Group the related data in a struct and use one mapping. For instance, for the AlchemicTokenV1.sol
mappings, the mitigation could be:
struct MintInfo { uint256 ceiling; uint256 hasMinted; }
And it would be used as a state variable:
mapping(address => MintInfo) mintInfo;
unused imports should be removed
Non-Critical
Instances include:
AlchemicTokenV2.sol:6: import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
StakingPools.sol:4: import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
Manual Analysis
Remove unused imports
uint
is an alias for uint256
.
It is better to use uint256: it brings readability and consistency in the code, and it future proofs it in case of any changes to the alias of uint
Non-Critical
Instances include:
AlchemicTokenV2.sol:164: uint _maxFlashLoanAmount
AlchemicTokenV2Base.sol:196: uint _maxFlashLoanAmount
CrossChainCanonicalBase.sol:141: uint i CrossChainCanonicalBase.sol:189: uint bridgeToCanonical CrossChainCanonicalBase.sol:189: uint canonicalToOld
gALCX.sol:14: uint public poolId = 1 gALCX.sol:15: uint public constant exchangeRatePrecision = 1e18 gALCX.sol:16: uint public exchangeRate = exchangeRatePrecision gALCX.sol:19: uint _exchangeRate gALCX.sol:20: uint _gAmount gALCX.sol:20: uint _amount gALCX.sol:21: uint _gAmount gALCX.sol:21: uint _amount gALCX.sol:46: uint _poolId gALCX.sol:50: uint poolBalance gALCX.sol:56: uint balance gALCX.sol:73: uint balance gALCX.sol:85: uint amount gALCX.sol:93: uint gAmount gALCX.sol:100: uint gAmount gALCX.sol:102: uint amount
Manual Analysis
replace uint
with
uint256
Setters should check the input value - ie make revert if it is the zero address or zero
Low
Instances include:
AlchemicTokenV1.sol:92:setWhitelist(address minter) AlchemicTokenV1.sol:101:setSentinel(address sentinel) AlchemicTokenV1.sol:110:setBlacklist(address minter) AlchemicTokenV1.sol:131:setCeiling(address minter)
AlchemicTokenV2.sol:119:setWhitelist(address minter) AlchemicTokenV2.sol:128:setSentinel(address sentinel)
AlchemicTokenV2Base.sol:132:setWhitelist(address minter) AlchemicTokenV2Base.sol:141:setSentinel(address sentinel) AlchemicTokenV2Base.sol:151:setCeiling(address minter)
AlchemistV2.sol:281:setPendingAdmin(address value) AlchemistV2.sol:303:setSentinel(address sentinel) AlchemistV2.sol:310:setKeeper(address keeper)
EthAssetManager.sol:293:setPendingAdmin(address value) EthAssetManager.sol:324:setOperator(address value) EthAssetManager.sol:332:setRewardReceiver(address value) EthAssetManager.sol:340:setTransmuterBuffer(address value)
ThreePoolAssetManager.sol:445:setPendingAdmin(address value) ThreePoolAssetManager.sol:476:setOperator(address value) ThreePoolAssetManager.sol:484:setRewardReceiver(address value) ThreePoolAssetManager.sol:492:setTransmuterBuffer(address value)
TransmuterBuffer.sol:221:setTransmuter(address newTransmuter) TransmuterBuffer.sol:251:setAmo(address amo)
TransmuterConduit.sol:21:constructor(address _token, address _source, address _sink)
TransmuterV2.sol:21:initialize( address _syntheticToken, address _underlyingToken, address _buffer, address _whitelist ) TransmuterV2.sol:196:setCollateralSource
Manual Analysis
Add zero address checks
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xf15ers, 0xkatana, 0xsomeone, AlleyCat, BowTiedWardens, Cityscape, Fitraldys, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Randyyy, TerrierLover, Tomio, UnusualTurtle, WatchPug, Waze, _Adam, augustg, bobirichman, catchup, csanuragjain, ellahi, fatherOfBlocks, hake, hansfriese, horsefacts, ignacio, joestakey, kenta, mics, oyc_109, robee, samruna, sashik_eth, sikorico, simon135, throttle
356.1254 DAI - $356.13
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in for
loops, when using the length of a storage array as the condition being checked after each loop, caching the array length in memory can yield significant gas savings if the array length is high
Instances include:
scope: mint()
hasMinted[msg.sender]
is read twice:AlchemicTokenV1.sol:80 AlchemicTokenV1.sol:82
scope: acceptAdmin()
pendingAdmin
is read 3 times:AlchemistV2.sol:289 AlchemistV2.sol:291 AlchemistV2.sol:295
scope: constructor()
weth
is read NUM_META_COINS times:EthAssetManager.sol:217
scope: acceptAdmin()
pendingAdmin
is read 3 times:EthAssetManager.sol:304 EthAssetManager.sol:308 EthAssetManager.sol:312
scope: claimRewards()
rewardReceiver
is read twice:EthAssetManager.sol:428 EthAssetManager.sol:429
scope: reclaimEth()
weth
is read 4 times:EthAssetManager.sol:496 EthAssetManager.sol:496 EthAssetManager.sol:498 EthAssetManager.sol:500
transmuterBuffer
is read twice:EthAssetManager.sol:498 EthAssetManager.sol:500
scope: _claimRewards()
rewardReceiver
is read twice:EthAssetManager.sol:698 EthAssetManager.sol:699
scope: migrateSource()
pools
is read twice:gALCX.sol:50 gALCX.sol:51
poolId
is read 3 times:gALCX.sol:50 gALCX.sol:51 gALCX.sol:58 //this last variable can be replaced with the _poolId argument.
scope: bumpExchangeRate()
pools
is read twice:gALCX.sol:71 gALCX.sol:79
poolId
is read twice:gALCX.sol:71 gALCX.sol:79
scope: acceptGovernance()
pendingGovernance
is read twice:StakingPools.sol:131 StakingPools.sol:133
scope: acceptAdmin()
pendingAdmin
is read 3 times:ThreePoolAssetManager.sol:456 ThreePoolAssetManager.sol:460 ThreePoolAssetManager.sol:464
scope: claimRewards()
rewardReceiver
is read twice:ThreePoolAssetManager.sol:632 ThreePoolAssetManager.sol:633
scope: reclaimThreePoolAsset()
transmuterBuffer
is read twice:ThreePoolAssetManager.sol:719 ThreePoolAssetManager.sol:721
scope: _claimRewards()
rewardReceiver
is read twice:ThreePoolAssetManager.sol:1014 ThreePoolAssetManager.sol:1015
scope: getAvailableFlow()
flowAvailable[underlyingToken]
is read twice:TransmuterBuffer.sol:151 TransmuterBuffer.sol:154
scope: getTotalUnderlyingBuffered()
_yieldTokens[underlyingToken]
is read (_yieldTokens[underlyingToken].length
) times:
number of reads depending on _yieldTokens
[underlyingToken] as it is in a for loopTransmuterBuffer.sol:172
scope: setAlchemist()
alchemist
is read (6 + registeredUnderlyings
.length) times: number of reads depending on registeredUnderlyings
as it is in a for loopTransmuterBuffer.sol:231 TransmuterBuffer.sol:234 TransmuterBuffer.sol:236 TransmuterBuffer.sol:238 TransmuterBuffer.sol:243 //from this point, alchemist can be replaced by the function argument _alchemist TransmuterBuffer.sol:245 TransmuterBuffer.sol:247
registeredUnderlyings.length
is read (registeredUnderlyings
.length) times: number of reads depending on registeredUnderlyings
as it is in a for loopTransmuterBuffer.sol:242
debtToken
is read twice:TransmuterBuffer.sol:238 TransmuterBuffer.sol:245
scope: refreshStrategies()
alchemist
is read (2 + supportedYieldTokens.length
) times: number of reads depending on supportedYieldTokens.length
as it is in a for loopTransmuterBuffer.sol:372 TransmuterBuffer.sol:374 TransmuterBuffer.sol:390
registeredUnderlyings
.length is read (1 + registeredUnderlyings.length
) times: number of reads depending on registeredUnderlyings
as it is in a for loopTransmuterBuffer.sol:377 TransmuterBuffer.sol:382
scope: burnCredit()
alchemist
is read twice:TransmuterBuffer.sol:401 TransmuterBuffer.sol:406
scope: _getTotalBuffered()
alchemist
is read 3 times:TransmuterBuffer.sol:443 TransmuterBuffer.sol:444 TransmuterBuffer.sol:446
scope: _alchemistWithdraw()
alchemist
is read 3 times:TransmuterBuffer.sol:513 TransmuterBuffer.sol:515 TransmuterBuffer.sol:522
scope: _exchange()
flowAvailable[underlyingToken]
is read 3 times if totalUnderlyingBuffered >= flowAvailable[underlyingToken]
, 4 times if initialLocalBalance < flowAvailable[underlyingToken]
, 5 times if localBalance > flowAvailable[underlyingToken]
:TransmuterBuffer.sol:536 TransmuterBuffer.sol:539 TransmuterBuffer.sol:541 TransmuterBuffer.sol:550 TransmuterBuffer.sol:551
currentExchanged[underlyingToken]
is read twice (3 instances but only read once in the first two depending on localBalance > flowAvailable[underlyingToken]
):TransmuterBuffer.sol:551 TransmuterBuffer.sol:553 TransmuterBuffer.sol:557
scope: _flushToAmo()
amos[underlyingToken]
is read twice:TransmuterBuffer.sol:567 TransmuterBuffer.sol:568
scope: distribute()
token
is read twice:TransmuterConduit.sol:35 TransmuterConduit.sol:36
sinkTransmuter
is read twice:TransmuterConduit.sol:35 TransmuterConduit.sol:36
scope: exchange()
totalUnexchanged
is read twice:TransmuterV2.sol:253 TransmuterV2.sol:263
examineTickData.totalBalance
is read twice:TransmuterV2.sol:315 TransmuterV2.sol:318
Manual Analysis
cache these storage variables in memory
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
Instances include:
scope: initialize()
AlchemistV2.sol:255: InitializationParams memory params
scope: initialize()
CrossChainCanonicalAlchemicTokenV2.sol:9: string memory name CrossChainCanonicalAlchemicTokenV2.sol:10: string memory symbol CrossChainCanonicalAlchemicTokenV2.sol:11: address[] memory _bridgeTokens
scope: __CrossChainCanonicalBase_init()
CrossChainCanonicalBase.sol:43: string memory _name CrossChainCanonicalBase.sol:44: string memory _symbol CrossChainCanonicalBase.sol:45: address[] memory _bridgeTokens
scope: initialize()
CrossChainCanonicalGALCX.sol:8: string memory name CrossChainCanonicalGALCX.sol:9: string memory symbol CrossChainCanonicalGALCX.sol:10: address[] memory _bridgeTokens
scope: setWeights()
TransmuterBuffer.sol:180: address[] memory tokens TransmuterBuffer.sol:181: uint256[] memory weights
scope: _updateAccount()
TransmuterV2.sol:381: UpdateAccountParams memory params
Manual Analysis
Replace memory
with calldata
In the EVM, there is no opcode for >=
or <=
.
When using greater than or equal, two operations are performed: >
and =
.
Using strict comparison operators hence saves gas
Instances include:
AlchemicTokenV1.sol:81
AlchemistV2.sol:256 AlchemistV2.sol:324 AlchemistV2.sol:352 AlchemistV2.sol:442 AlchemistV2.sol:494 AlchemistV2.sol:1010 AlchemistV2.sol:1441 AlchemistV2.sol:1565
EthAssetManager.sol:281 EthAssetManager.sol:547
ThreePoolAssetManager.sol:420 ThreePoolAssetManager.sol:433 ThreePoolAssetManager.sol:749
TransmuterBuffer.sol:161
TransmuterV2.sol:353 TransmuterV2.sol:409 TransmuterV2.sol:557
Manual Analysis
Replace <=
with <
, and >=
with >
. Do not forget to increment/decrement the compared variable
example:
-total <= ceiling[msg.sender]; +total <= ceiling[msg.sender] + 1;
However, if 1
is negligible compared to the value of the variable, we can omit the increment.
Constructor parameters are expensive. The contract deployment will be cheaper in gas if they are hard coded instead of using constructor parameters.
Instances include:
AlchemicTokenV2.sol:60 flashMintFee = _flashFee
EthAssetManager.sol:201: admin = params.admin; EthAssetManager.sol:202: operator = params.operator; EthAssetManager.sol:203: rewardReceiver = params.rewardReceiver; EthAssetManager.sol:204: transmuterBuffer = params.transmuterBuffer; EthAssetManager.sol:205: weth = params.weth; EthAssetManager.sol:208: metaPoolSlippage = params.metaPoolSlippage
StakingPools.sol:108 reward = _reward StakingPools.sol:109 governance = _governance
ThreePoolAssetManager.sol:236: admin = params.admin; ThreePoolAssetManager.sol:237: operator = params.operator; ThreePoolAssetManager.sol:238: rewardReceiver = params.rewardReceiver; ThreePoolAssetManager.sol:239: transmuterBuffer = params.transmuterBuffer; ThreePoolAssetManager.sol:243: threePoolSlippage = params.threePoolSlippage; ThreePoolAssetManager.sol:244: metaPoolSlippage = params.metaPoolSlippage
TransmuterConduit.sol:22 token = _token TransmuterConduit.sol:23 sourceTransmuter = _source TransmuterConduit.sol:24 sinkTransmuter = _sink
WETHGateway.sol:24 whitelist = _whitelist
Manual Analysis
Hardcode the state variable with their initial values instead of writing them during contract deployment with constructor parameters.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here
Custom errors are defined using the error statement
Instances include:
AlchemicTokenV1.sol:77: require(!blacklist[msg.sender], "AlUSD: Alchemist is blacklisted."); AlchemicTokenV1.sol:78: require(!paused[msg.sender], "AlUSD: Currently paused."); AlchemicTokenV1.sol:81: require(total <= ceiling[msg.sender], "AlUSD: Alchemist's ceiling was breached.");
gALCX.sol:90: require(success, "Transfer failed"); gALCX.sol:107: require(success, "Transfer failed");
StakingPools.sol:106: require(_governance != address(0), "StakingPools: governance address cannot be 0x0"); StakingPools.sol:114: require(msg.sender == governance, "StakingPools: only governance"); StakingPools.sol:124: require(_pendingGovernance != address(0), "StakingPools: pending governance address cannot be 0x0"); StakingPools.sol:131: require(msg.sender == pendingGovernance, "StakingPools: only pending governance"); StakingPools.sol:160: require(tokenPoolIds[_token] == 0, "StakingPools: token already has a pool"); StakingPools.sol:183: require(_rewardWeights.length == _pools.length(), "StakingPools: weights length mismatch");
Manual Analysis
Replace require and revert statements with custom errors.
For instance, in StakingPools.sol
:
Replace
require(msg.sender == pendingGovernance, "StakingPools: only pending governance");
with
if (msg.sender != pendingGovernance) { revert IsNotPendingGovernance(msg.sender); }
and define the custom error in the contract
error IsNotPendingGovernance(address _address);
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
Instances include:
AlchemistV2.sol:990: uint256 i = 0; AlchemistV2.sol:1282: uint256 i = 0; AlchemistV2.sol:1355: uint256 i = 0; AlchemistV2.sol:1458: uint256 totalValue = 0; AlchemistV2.sol:1461: uint256 i = 0; AlchemistV2.sol:1524: uint256 i = 0;
CrossChainCanonicalBase.sol:57: uint256 i = 0; CrossChainCanonicalBase.sol:141: uint256 i = 0;
EthAssetManager.sol:214: uint256 i = 0; EthAssetManager.sol:566: uint256 total = 0; EthAssetManager.sol:567: uint256 i = 0;
StakingPools.sol:188: uint256 _poolId = 0; StakingPools.sol:363: uint256 _poolId = 0;
ThreePoolAssetManager.sol:250: uint256 i = 0; ThreePoolAssetManager.sol:254: uint256 i = 0; ThreePoolAssetManager.sol:353: uint256 i = 0; ThreePoolAssetManager.sol:771: uint256 normalizedTotal = 0; ThreePoolAssetManager.sol:773: uint256 i = 0; ThreePoolAssetManager.sol:901: uint256 total = 0; ThreePoolAssetManager.sol:902: uint256 i = 0;
TransmuterBuffer.sol:171: uint256 i= 0; TransmuterBuffer.sol:186: uint256 i= 0; TransmuterBuffer.sol:235: uint256 i= 0; TransmuterBuffer.sol:242: uint256 i= 0; TransmuterBuffer.sol:272: uint256 i= 0; TransmuterBuffer.sol:382: uint256 j= 0; TransmuterBuffer.sol:387: uint256 i= 0; TransmuterBuffer.sol:479: uint256 j= 0; TransmuterBuffer.sol:534: uint256 want = 0; TransmuterBuffer.sol:549: uint256 exchangeDelta = 0;
TransmuterV2.sol:162: isPaused = false;
Manual Analysis
Remove explicit initialization for default values.
When emitting an event, using a local variable instead of a storage variable saves gas.
Instances include:
AlchemicTokenV2.sol:94: emit SetFlashMintFee(flashMintFee);
AlchemicTokenV2Base.sol:100: emit SetFlashMintFee(flashMintFee);
AlchemistV2.sol:272: emit AdminUpdated(admin); AlchemistV2.sol:273: emit TransmuterUpdated(transmuter); AlchemistV2.sol:274: emit MinimumCollateralizationUpdated(minimumCollateralization); AlchemistV2.sol:275: emit ProtocolFeeUpdated(protocolFee); AlchemistV2.sol:276: emit ProtocolFeeReceiverUpdated(protocolFeeReceiver); AlchemistV2.sol:298: emit AdminUpdated(admin);
EthAssetManager.sol:221: emit AdminUpdated(admin); EthAssetManager.sol:222: emit OperatorUpdated(operator); EthAssetManager.sol:223: emit RewardReceiverUpdated(rewardReceiver); EthAssetManager.sol:224: emit TransmuterBufferUpdated(transmuterBuffer); EthAssetManager.sol:225: emit MetaPoolSlippageUpdated(metaPoolSlippage); EthAssetManager.sol:315: emit AdminUpdated(admin);
gALCX.sol:77: emit ExchangeRateChange(exchangeRate);
ThreePoolAssetManager.sol:258: emit AdminUpdated(admin); ThreePoolAssetManager.sol:259: emit OperatorUpdated(operator); ThreePoolAssetManager.sol:260: emit RewardReceiverUpdated(rewardReceiver); ThreePoolAssetManager.sol:261: emit TransmuterBufferUpdated(transmuterBuffer); ThreePoolAssetManager.sol:262: emit ThreePoolSlippageUpdated(threePoolSlippage); ThreePoolAssetManager.sol:263: emit MetaPoolSlippageUpdated(metaPoolSlippage); ThreePoolAssetManager.sol:467: emit AdminUpdated(admin);
TransmuterBuffer.sol:247: emit SetAlchemist(alchemist);
TransmuterV2.sol:203: emit Paused(isPaused);
Manual Analysis
Emit the function argument instead of the state variable where possible.
In some instances, the storage variable is read multiple times in the function and it is recommended to cache them into memory, then passing these cached variables in the emit
statement, as explained in the cache paragraph
If a storage variable is never changed, it's possible to avoid storage access and save gas by making it immutable.
Instances include:
EthAssetManager.sol:157: IWETH9 public weth EthAssetManager.sol:182: IERC20[NUM_META_COINS] private _metaPoolAssetCache
gALCX.sol:188: IERC20 public alcx gALCX.sol:188: uint public exchangeRate gALCX.sol:363: address public owner
StakingPools.sol:188: IERC20Mintable public reward;
TransmuterConduit.sol:13: address public token; TransmuterConduit.sol:16: address public sourceTransmuter; TransmuterConduit.sol:19: address public sinkTransmuter;
TransmuterV2.sol:108: address public syntheticToken;
Manual Analysis
add the immutable
keyword to these variables.
Prefix increments are cheaper than postfix increments.
Instances include:
AlchemistV2.sol:990: i++ AlchemistV2.sol:1282: i++ AlchemistV2.sol:1355: i++ AlchemistV2.sol:1461: i++ AlchemistV2.sol:1524: i++
CrossChainCanonicalBase.sol:57: i++ CrossChainCanonicalBase.sol:141: i++
EthAssetManager.sol:214: i++ EthAssetManager.sol:567: i++
StakingPools.sol:188: _poolId + 1; StakingPools.sol:188: _poolId++ StakingPools.sol:363: _poolId++
ThreePoolAssetManager.sol:250: i++ ThreePoolAssetManager.sol:254: i++ ThreePoolAssetManager.sol:353: i++ ThreePoolAssetManager.sol:773: i++ ThreePoolAssetManager.sol:902: i++
TransmuterBuffer.sol:172: i++ TransmuterBuffer.sol:186: i++ TransmuterBuffer.sol:235: i++ TransmuterBuffer.sol:242: i++ TransmuterBuffer.sol:272: i++ TransmuterBuffer.sol:382: j++ TransmuterBuffer.sol:387: i++ TransmuterBuffer.sol:479: j++
Manual Analysis
change variable + 1
and variable++
to ++variable
.
A division by 2 can be calculated by shifting one to the right. While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.
Instances include:
ThreePoolAssetManager:355: (v.maximum + v.minimum) / 2)
Manual Analysis
-(v.maximum + v.minimum) / 2); +(v.maximum + v.minimum) >> 1);
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked
block will save gas
Instances include:
AlchemistV2.sol:933: feeAmount bounded by amountUnderlyingTokens (see line 256, protocolFee < BPS), underflow check unnecessary AlchemistV2.sol:1014: expectedValue bounded by currentValue (see line 1010), underflow check unnecessary AlchemistV2.sol:1569: expectedValue bounded by currentValue (see line 1565), underflow check unnecessary
EthAssetManager.sol:496: balance bounded by amount, underflow check unnecessary EthAssetManager.sol:552: cliff bounded by totalCliffs (see line 547), underflow check unnecessary EthAssetManager.sol:589: address(this).balance bounded by value, underflow check unnecessary EthAssetManager.sol:629: address(this).balance bounded by value, underflow check unnecessary
ThreePoolAssetManager.sol:399: because of the condition check, underflow checks are unnecessary ThreePoolAssetManager.sol:751: cliff bounded by totalCliffs (see line 749), underflow check unnecessary
Manual Analysis
Place the arithmetic operations in an unchecked
block
#0 - 0xfoobar
2022-05-30T06:59:47Z
Helpful details on removing SLOADs