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: 3/62
Findings: 3
Award: $6,803.57
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: TerrierLover
6389.4401 DAI - $6,389.44
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L73-L76
An attacker can make the contract unusable when totalSupply is 0. Specifically, bumpExchangeRate
function does not work correctly which results in making stake
, unstake
and migrateSource
functions that do not work as expected.
Here are steps on how the gALCX
contract can be unusable.
gALCX
contract is deployed
The attacker sends the ALCX
token to the deployed gALCX
contract directly instead of using stake
function so that the following balance
variable has value.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L73-L75
uint balance = alcx.balanceOf(address(this)); if (balance > 0) {
ALCX
token is given to the gALCX
contract directly, totalSupply == 0
and alcx.balanceOf(address(this)) > 0
becomes true.https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L76
exchangeRate += (balance * exchangeRatePrecision) / totalSupply;
Non attackers try to call stake
function, but bumpExchangeRate
function fails because of (balance * exchangeRatePrecision) / totalSupply
when totalSupply is 0.
Owner cannot call migrateSource
function since bumpExchangeRate
will be in the same situation mentioned in the step4 above
Static code analysis
Add handling when totalSupply
is 0 but alcx.balanceOf(address(this))
is more than 0.
#0 - 0xfoobar
2022-05-22T22:10:04Z
Sponsor acknowledged
Given that the gALCX deployment has 412 unique tokenholders on mainnet, this series of events is extraordinarily unlikely to occur. But we will keep it in mind for future deployments.
#1 - 0xleastwood
2022-06-05T22:34:19Z
Nice find! Early stakers can DoS new contract deployments, making it impossible for other users to participate in the protocol. As this does not lead to lost funds and is recoverable through redeployment, I believe medium severity to be justified by the warden.
🌟 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
Here are QA findings per file.
AlchemistV2.sol defines constructor() as follows, but if this is upgradeable contract, this constructor is not needed.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L110
constructor() initializer {}
It simply can remove the definition of constructor.
_convertYieldTokensToShares and _convertSharesToYieldTokens functions have similar code structure, but _convertYieldTokensToShares accesses _yieldTokens[yieldToken].totalShares
twice.
function _convertYieldTokensToShares(address yieldToken, uint256 amount) internal view returns (uint256) { if (_yieldTokens[yieldToken].totalShares == 0) { return amount; } return amount * _yieldTokens[yieldToken].totalShares / _calculateUnrealizedActiveBalance(yieldToken); }
On the other hand, _convertSharesToYieldTokens function use uint256 totalShares
variable to prevent the storage access.
function _convertSharesToYieldTokens(address yieldToken, uint256 shares) internal view returns (uint256) { uint256 totalShares = _yieldTokens[yieldToken].totalShares; if (totalShares == 0) { return shares; } return (shares * _calculateUnrealizedActiveBalance(yieldToken)) / totalShares; }
_convertYieldTokensToShares function can follow _convertSharesToYieldTokens function.
function _convertYieldTokensToShares(address yieldToken, uint256 amount) internal view returns (uint256) { uint256 totalShares = _yieldTokens[yieldToken].totalShares; if (totalShares == 0) { return amount; } return amount * totalShares / _calculateUnrealizedActiveBalance(yieldToken); }
withdrawUnderlyingFrom and withdrawUnderlying functions have different coding styls compared with other functions in AlchemistV2.sol file.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L639-L645
function withdrawUnderlying( address yieldToken, uint256 shares, address recipient, uint256 minimumAmountOut ) external override lock returns (uint256) { _onlyWhitelisted(); _checkArgument(recipient != address(0)); _checkSupportedYieldToken(yieldToken); _checkLoss(yieldToken); uint256 amountYieldTokens = _withdraw(yieldToken, msg.sender, shares, recipient);
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L653-L668
function withdrawUnderlyingFrom( address owner, address yieldToken, uint256 shares, address recipient, uint256 minimumAmountOut ) external override lock returns (uint256) { _onlyWhitelisted(); _checkArgument(recipient != address(0)); _checkSupportedYieldToken(yieldToken); _checkLoss(yieldToken); _decreaseWithdrawAllowance(owner, msg.sender, yieldToken, shares);
withdrawFrom or other functions pack the codes where _only...
and _check...
functions are called as follows.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L609-L617
function withdrawFrom( address owner, address yieldToken, uint256 shares, address recipient ) external override lock returns (uint256) { _onlyWhitelisted(); _checkArgument(recipient != address(0)); _checkSupportedYieldToken(yieldToken);
For the better readability and consistency, withdrawUnderlying and withdrawUnderlyingFrom functions can be rewritten as follows:
function withdrawUnderlying( address yieldToken, uint256 shares, address recipient, uint256 minimumAmountOut ) external override lock returns (uint256) { _onlyWhitelisted(); _checkArgument(recipient != address(0)); _checkSupportedYieldToken(yieldToken); _checkLoss(yieldToken); uint256 amountYieldTokens = _withdraw(yieldToken, msg.sender, shares, recipient);
function withdrawUnderlyingFrom( address owner, address yieldToken, uint256 shares, address recipient, uint256 minimumAmountOut ) external override lock returns (uint256) { _onlyWhitelisted(); _checkArgument(recipient != address(0)); _checkSupportedYieldToken(yieldToken); _checkLoss(yieldToken); _decreaseWithdrawAllowance(owner, msg.sender, yieldToken, shares);
Here is mint function in AlchemicTokenV2.sol.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemicTokenV2.sol#L101
/// @notice Mints tokens to `a recipient.` /// /// @notice This function reverts if `msg.sender` is not whitelisted. /// @notice This function reverts if `msg.sender` is paused. /// @notice This function reverts if `msg.sender` has exceeded their mintable ceiling. /// /// @param recipient The address to mint the tokens to. /// @param amount The amount of tokens to mint. function mint(address recipient, uint256 amount) external onlyWhitelisted {
The comment says @notice This function reverts if `msg.sender` has exceeded their mintable ceiling.
, but there is no such logic in AlchemicTokenV2.sol.
mint function in AlchemicTokenV2Base.sol has similar comment, which implements the logic related to the mintable ceiling.
It should fix AlchemicTokenV2.sol based on the specification of this contract.
Following code has extra parenthesis.
bridgeTokensOut -= ((bridgeTokensOut * swapFees[bridgeTokenAddress][1]) / FEE_PRECISION);
It can be rewritten like this:
bridgeTokensOut -= bridgeTokensOut * swapFees[bridgeTokenAddress][1] / FEE_PRECISION;
Events are defined at the bottom of the code in CrossChainCanonicalBase.sol.
At other files, it follows the style guide of solidity link.
Inside each contract, library or interface, use the following order: 1. Type declarations 2. State variables 3. Events 4. Modifiers 5. Functions
So CrossChainCanonicalBase.sol should follow this rule by putting events definitions between state variables and modifiers.
name and symbol does not have prefix _
while _bridgeTokens has one.
function initialize( string memory name, string memory symbol, address[] memory _bridgeTokens ) public initializer {
function initialize( string memory name, string memory symbol, address[] memory _bridgeTokens ) public initializer {
__CrossChainCanonicalBase_init function adds prefix _
so initialize should follow __CrossChainCanonicalBase_init.
function __CrossChainCanonicalBase_init( string memory _name, string memory _symbol, address _creatorAddress, address[] memory _bridgeTokens ) internal {
StakingPools.sol adds _
prefix at local variables. Here are some examples:
_totalRewardWeight
, _pool
and _poolId
at setRewardWeights function.
uint256 _totalRewardWeight = _ctx.totalRewardWeight; for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) { Pool.Data storage _pool = _pools.get(_poolId); uint256 _currentRewardWeight = _pool.rewardWeight;
_pool
and _stake
at deposit function.
Pool.Data storage _pool = _pools.get(_poolId); _pool.update(_ctx); Stake.Data storage _stake = _stakes[msg.sender][_poolId]; _stake.update(_pool, _ctx);
_pool
and _stake
at withdraw function.
Pool.Data storage _pool = _pools.get(_poolId); _pool.update(_ctx); Stake.Data storage _stake = _stakes[msg.sender][_poolId]; _stake.update(_pool, _ctx);
Other files do not add _
previx for the local variables, so StakingPools.sol should follow this pattern as well like this:
Pool.Data storage pool = _pools.get(_poolId); pool.update(_ctx); Stake.Data storage stake = _stakes[msg.sender][_poolId]; stake.update(_pool, _ctx);
Here is a reference of other files not using _
for local variable.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L183
Weighting storage weighting = weightings[weightToken];
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L312
uint256 localBalance = TokenUtils.safeBalanceOf(underlyingToken, address(this));
gALCX.sol defines the logic silmiar to Ownable.sol provided by OpenZeppelin.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L30-L41
// OWNERSHIP modifier onlyOwner { require(msg.sender == owner, "Not owner"); _; } /// @notice Transfer contract ownership /// @param _owner The new owner address function transferOwnership(address _owner) external onlyOwner { owner = _owner; }
What the above mentioned logic does is very similar to OZ's Ownable.sol, so it is worth considering using the OZ one. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
gALCX.sol does not check if _owner is address(0) or not at transferOwnership function, so it has a risk of setting address(0) which makes the admin operations of gALCX.sol unusable. If gALCX.sol keeps using the custom logic similar to OZ's Ownable.sol, it should at least check if the given address is address(0) at transferOwnership function to reduce the risk.
reApprove
function is not usedreApprove function uses alcx.approve which returns bool success
, but this success
variable is not used at all.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L62-L64
function reApprove() public { bool success = alcx.approve(address(pools), type(uint).max); }
It should remove setting the success boolean variable or add check if approval operation is successful or not.
bool success = alcx.approve(address(pools), type(uint).max); if (!success) { revert SetAppropriateCustomError(); }
reApprove
function is not defined below // PUBLIC FUNCTIONS
commentreApprove function is public, so it should be defined below the // PUBLIC FUNCTIONS
comment.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L66
After the modification, the code should look like this:
// PUBLIC FUNCTIONS /// @notice Approve the staking pool to move funds in this address, can be called by anyone function reApprove() public { bool success = alcx.approve(address(pools), type(uint).max); } /// @notice Claim and autocompound rewards function bumpExchangeRate() public {
The arguments of stake and unstake functions do not have _
at their prefix.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L85
function stake(uint amount) external {
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L100
function unstake(uint gAmount) external {
other part of this file adds _
on their argument as follows.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L46
function migrateSource(address _pools, uint _poolId) external onlyOwner {
take and unstake functions should follow this pattern.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L76
exchangeRate += (balance * exchangeRatePrecision) / totalSupply;
It can write as follows:
exchangeRate += balance * exchangeRatePrecision / totalSupply;
TransmuterV2.sol has slightly different definitions of _onlyWhitelisted compared with other files.
function _onlyWhitelisted() internal view { // Check if the message sender is an EOA. In the future, this potentially may break. It is important that // functions which rely on the whitelist not be explicitly vulnerable in the situation where this no longer // holds true. if (tx.origin != msg.sender) { // Only check the whitelist for calls from contracts. if (!IWhitelist(whitelist).isWhitelisted(msg.sender)) { revert Unauthorized(); } } }
For the reference, this is _onlyWhitelisted function defined in AlchemistV2.sol.
function _onlyWhitelisted() internal view { // Check if the message sender is an EOA. In the future, this potentially may break. It is important that functions // which rely on the whitelist not be explicitly vulnerable in the situation where this no longer holds true. if (tx.origin == msg.sender) { return; } // Only check the whitelist for calls from contracts. if (!IWhitelist(whitelist).isWhitelisted(msg.sender)) { revert Unauthorized(); } }
The behavior is same, but for the consistency, it is worh adjusting _onlyWhitelisted function at TransmuterV2.sol to follow other codebase.
#0 - 0xleastwood
2022-06-11T16:15:18Z
QA-1 is invalid. initializer
is there to ensure implementation contract deployments cannot be front-run
#1 - 0xleastwood
2022-06-11T16:17:05Z
QA-2 is a gas optimisation. QA-3 is code-style and provides little benefit. Rest is non-critical.
🌟 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
103.2651 DAI - $103.27
Here are gas optimizations per file.
constructor is defined, but this is upgradeable contract and the constructor will not be used.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L110
constructor() initializer {}
Removing the definition of constructor may reduce the deployment gas cost.
!= 0
instead of > 0
on uint256 variablesFollowing codes use != 0
for the zero value check like this.
_checkArgument(config.creditUnlockBlocks > 0);
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L353 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L466 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L678 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L692 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L707 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L810 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L846 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1103 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1267 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1531
uint256 variables will not be less than 0, so it simply needs to check != 0
like this which results in decreasing the gas cost.
_checkArgument(config.creditUnlockBlocks != 0);
Following codes set 0 on uint256 variables. The default value of uint256 variable is 0, so there is no need to set 0. Not setting 0 can reduce the deployment gas fee.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L990 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1282 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1355 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1458 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1461 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemistV2.sol#L1524
For loop can be rewritten like this:
for (uint256 i; i < depositedTokens.values.length; i++) { _preemptivelyHarvest(depositedTokens.values[i]); }
For the local uint256 variable, it can be defined like this:
function _totalValue(address owner) internal view returns (uint256) { uint256 totalValue;
uint is used at the argument of setMaxFlashLoan function.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/AlchemicTokenV2.sol#L164
function setMaxFlashLoan(uint _maxFlashLoanAmount) external onlyAdmin {
Other parts of code use uint256 consistently, so setMaxFlashLoan should use uint256 at its argument as well.
function setMaxFlashLoan(uint256 _maxFlashLoanAmount) external onlyAdmin {
uint256 newAllowance = allowance(account, msg.sender) - amount; _approve(account, msg.sender, newAllowance);
newAllowance is defined, but it is only used once. Removing the definition of newAllowance can reduce the gas cost as follows:
_approve(account, msg.sender, allowance(account, msg.sender) - amount);
Following codes set 0 on uint256 variables. The default value of uint256 variable is 0, so there is no need to set 0. Not setting 0 can reduce the deployment gas fee.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/CrossChainCanonicalBase.sol#L57 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/CrossChainCanonicalBase.sol#L141
It can be rewritten like this:
for (uint i; i < bridgeTokensArray.length; i++){
Following codes use uint, but it can use uint256.
mapping(address => bool) public bridgeTokens
seems not be used at allmapping(address => bool) public bridgeTokens
is defined but it is not used by anywhere except for the __CrossChainCanonicalBase_init
.
bridgeTokensArray
state variable is used instead. It should either remove bridgeTokens
state variable and relevant codebase or use bridgeTokens
state variable more efficiently.
If bridgeTokens
state variable is updated appropriately, bridgeTokens
may be able to be used when checking if the target token is present or not instead of looping through the bridgeTokensArray
.
// Make sure the token is not already present for (uint i = 0; i < bridgeTokensArray.length; i++){ if (bridgeTokensArray[i] == bridgeTokenAddress) { revert IllegalState(); } }
Only StakingPools.sol uses require and does not use custome errors as follows while other files use it.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L106 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L114 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L124 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L131 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L160 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L183
Using the custom errors can reduce the gas cost, so it is worth using at StakingPools.sol as well.
_pendingGovernance
variable seems not needed at acceptGovernance function_pendingGovernance
variable is defined at acceptGovernance function but this seems not needed.
function acceptGovernance() external { require(msg.sender == pendingGovernance, "StakingPools: only pending governance"); address _pendingGovernance = pendingGovernance; governance = _pendingGovernance; emit GovernanceUpdated(_pendingGovernance); }
It can be rewritten like this which can decrease the gas cost:
function acceptGovernance() external { require(msg.sender == pendingGovernance, "StakingPools: only pending governance"); governance = pendingGovernance; emit GovernanceUpdated(pendingGovernance); }
Following codes set 0 on uint256 variables. The default value of uint256 variable is 0, so there is no need to set 0. Not setting 0 can reduce the deployment gas fee.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L188 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/StakingPools.sol#L363
It can rewrite like this:
for (uint256 _poolId; _poolId < _pools.length(); _poolId++) {
Throughout the codebase, it uses uint256. However, following parts use uint.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L50 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L56 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L73 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L93 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L85 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L100
It should consider using uint256 instead of uint.
balance is uint256 so it can use != 0 instead of > 0.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L75
if (balance > 0) {
constructor is defined, but this is upgradeable contract and the constructor will not be used.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterV2.sol#L141
constructor() initializer {}
Removing the definition of constructor may reduce the deployment gas cost.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterV2.sol#L162
isPaused = false;
The default value of isPaused
state variable is false. So there is no need to set false at initialize function.
weight is defined at getWeight function, but it is not needed.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L134
/// @inheritdoc ITransmuterBuffer function getWeight(address weightToken, address token) external view override returns (uint256 weight) { return weightings[weightToken].weights[token]; }
It can write like this:
/// @inheritdoc ITransmuterBuffer function getWeight(address weightToken, address token) external view override returns (uint256) { return weightings[weightToken].weights[token]; }
minimumAmountOut is only needed when wantShares is more than 0.
uint256 minimumAmountOut = amountUnderlying - amountUnderlying * 100 / 10000; if (wantShares > 0) { IAlchemistV2(alchemist).withdrawUnderlying(token, wantShares, address(this), minimumAmountOut); }
It can put the logic to get minimumAmountOut inside the if statement.
if (wantShares > 0) { IAlchemistV2(alchemist).withdrawUnderlying(token, wantShares, address(this), amountUnderlying - amountUnderlying * 100 / 10000); }
Following codes set 0 on uint256 variables. The default value of uint256 variable is 0, so there is no need to set 0. Not setting 0 can reduce the deployment gas fee.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L172 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L186 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L235 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L242 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L272 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L382 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L387 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L534 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L549
!= 0
instead of > 0
on uint256 variablesFollowing codes use > 0 on uint256 variables. uint256 variables will not be less than 0, so it simply needs to check != 0
to reduce the gas cost.
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L521 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L544 https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/TransmuterBuffer.sol#L556
#0 - 0xfoobar
2022-05-30T06:55:15Z
Gas-17 is useful