yAxis contest - cmichel's results

The trusted #DeFi platform to earn reliable returns on digital assets.

General Information

Platform: Code4rena

Start Date: 09/09/2021

Pot Size: $60,000 USDC

Total HM: 24

Participants: 12

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 14

Id: 30

League: ETH

yAxis

Findings Distribution

Researcher Performance

Rank: 1/12

Findings: 14

Award: $8,607.90

🌟 Selected for report: 19

🚀 Solo Findings: 6

Findings Information

🌟 Selected for report: cmichel

Labels

bug
duplicate
3 (High Risk)

Awards

465.1532 YAXIS - $1,814.10

External Links

Handle

cmichel

Vulnerability details

The Controller.setCap function sets a cap for a strategy and withdraws any excess amounts (_diff). The vault balance is decreased by the entire strategy balance instead of by this _diff:

// @audit why not sub _diff? _vaultDetails[_vault].balance = _vaultDetails[_vault].balance.sub(_balance);

Impact

The _vaultDetails[_vault].balance variable does not correctly track the actual vault balances anymore, it will usually underestimate the vault balance. This variable is used in Controller.balanceOf(), which in turn is used in Vault.balance(), which in turn is used to determine how many shares to mint / amount to receive when redeeming shares. If the value is less, users will lose money as they can redeem fewer tokens. Also, an attacker can deposit and will receive more shares than they should receive. They can then wait until the balance is correctly updated again and withdraw their shares for a higher amount than they deposited. This leads to the vault losing tokens.

Sub the _diff instead of the balance: _vaultDetails[_vault].balance = _vaultDetails[_vault].balance.sub(_diff);

#0 - Haz077

2021-09-30T20:52:21Z

Already fixed in code-423n4/2021-09-yaxis#1

#1 - GalloDaSballo

2021-10-14T16:50:49Z

Finding is valid, has been mitigated by sponsor as of 14 days ago

Findings Information

🌟 Selected for report: jonah1005

Also found by: cmichel, itsmeSTYJ

Labels

bug
duplicate
3 (High Risk)

Awards

125.5914 YAXIS - $489.81

External Links

Handle

cmichel

Vulnerability details

The Vault.balanceOfThis function values all tokens equally. They are normalized to 18 decimals and then simply added up:

for (uint8 i; i < _tokens.length; i++) {
    address _token = _tokens[i];
    // adds up different tokens here, treating them as exactly equal value 1.0 A = 1.0 B
    _balance = _balance.add(_normalizeDecimals(_token, IERC20(_token).balanceOf(address(this))));
}

Impact

The balanceOfThis function does not take into account the actual market prices these tokens trade at. It would treat 1.0 USDC as 1.0 WETH and simply add these values. Even if only stablecoins are used, they can still differ in value, especially with non-reserve-backed algorithmic stablecoins like DAI.

If DAI is valued at 0.99$ but USDC is valued at 1.00$, then arbitragers will deposit(DAI, amount), receive shares, withdraw the shares for USDC (withdraw(shares, USDC)) and make a profit (assuming the _withdrawalProtectionFee is set to an adequate value). The contract will run out of the higher-valued USDC asset immediately, and overall lose total value.

Use a price oracle for each token with a common denominating asset, iterate over the tokens, convert them with the oracle price, and then add them up.

Alternatively, set a high withdrawal fee _withdrawalProtectionFee but this is bad for the user and could turn off users from depositing anything. Assume one gets 12% APY but the withdrawal fee is set to 2%, a user will lose money if they withdraw before 2 months.

#0 - Haz077

2021-09-30T19:58:33Z

Duplicate of #2.

#1 - GalloDaSballo

2021-10-14T16:49:32Z

Duplicate of #2

Findings Information

🌟 Selected for report: cmichel

Labels

bug
3 (High Risk)
sponsor acknowledged

Awards

465.1532 YAXIS - $1,814.10

External Links

Handle

cmichel

Vulnerability details

The Vault.balance function uses the balanceOfThis function which scales ("normalizes") all balances to 18 decimals.

for (uint8 i; i < _tokens.length; i++) { address _token = _tokens[i]; // everything is padded to 18 decimals _balance = _balance.add(_normalizeDecimals(_token, IERC20(_token).balanceOf(address(this)))); }

Note that balance()'s second term IController(manager.controllers(address(this))).balanceOf() is not normalized. The code is adding a non-normalized amount (for example 6 decimals only for USDC) to a normalized (18 decimals).

Impact

The result is that the balance() will be under-reported. This leads to receiving wrong shares when depositing tokens, and a wrong amount when redeeming tokens.

The second term IController(manager.controllers(address(this))).balanceOf() must also be normalized before adding it. IController(manager.controllers(address(this))).balanceOf() uses _vaultDetails[msg.sender].balance which directly uses the raw token amounts which are not normalized.

#0 - GalloDaSballo

2021-10-14T16:05:12Z

balance and balanceOfThis mixes the usage of decimals by alternatingly using _normalizeDecimals This can break accounting as well as create opportunities for abuse A consistent usage of _normalizeDecimals would mitigate

Findings Information

🌟 Selected for report: cmichel

Also found by: hickuphh3, jonah1005

Labels

bug
duplicate
3 (High Risk)

Awards

125.5914 YAXIS - $489.81

External Links

Handle

cmichel

Vulnerability details

The Vault.balance function uses the balanceOfThis function which scales ("normalizes") all balances to 18 decimals.

for (uint8 i; i < _tokens.length; i++) { address _token = _tokens[i]; // everything is padded to 18 decimals _balance = _balance.add(_normalizeDecimals(_token, IERC20(_token).balanceOf(address(this)))); }

Note that balance()'s second term IController(manager.controllers(address(this))).balanceOf() is not normalized, but it must be.

This leads to many issues through the contracts that use balance but don't treat these values as normalized values. For example, in Vault.withdraw, the computed _amount value is normalized (in 18 decimals). But the uint256 _balance = IERC20(_output).balanceOf(address(this)); value is not normalized but compared to the normalized _amount and even subtracted:

// @audit compares unnormalzied output to normalized output
if (_balance < _amount) {
    IController _controller = IController(manager.controllers(address(this)));
    // @audit cannot directly subtract unnormalized
    uint256 _toWithdraw = _amount.sub(_balance);
    if (_controller.strategies() > 0) {
        _controller.withdraw(_output, _toWithdraw);
    }
    uint256 _after = IERC20(_output).balanceOf(address(this));
    uint256 _diff = _after.sub(_balance);
    if (_diff < _toWithdraw) {
        _amount = _balance.add(_diff);
    }
}

Impact

Imagine in withdraw, the output is USDC with 6 decimals, then the normalized _toWithdraw with 18 decimals (due to using _amount) will be a huge number and attempt to withdraw an inflated amount. An attacker can steal tokens this way by withdrawing a tiny amount of shares and receive an inflated USDC or USDT amount (or any _output token with less than 18 decimals).

Whenever using anything involving vault.balanceOfThis() or vault.balance() one needs to be sure that any derived token amount needs to be denormalized again before using them.

#0 - GalloDaSballo

2021-10-14T16:08:28Z

An inconsistent usage of _normalizeDecimals will cause accounting issues and potentially paths for an exploit

Findings Information

🌟 Selected for report: WatchPug

Also found by: cmichel, jonah1005

Labels

bug
duplicate
3 (High Risk)

Awards

125.5914 YAXIS - $489.81

External Links

Handle

cmichel

Vulnerability details

The Controller.withdrawAll decreases the vault balance by _amount, the want token amount that has been withdrawn from the strategy and transferred to the vault. Note that _amount gets overwritten in the _convert != address(0) branch and is a _convert token value instead of a _want token value:

if (_convert != address(0)) { // @audit _amount is overwritten here with the traded return amount of _convert token! _amount = _converter.convert(_want, _convert, _amount, 1); IERC20(_convert).safeTransfer(_vault, _amount); } else { IERC20(_want).safeTransfer(_vault, _amount); } if (_balance >= _amount) { // @audit they subtract the wrong _amount here if _convert was set _vaultDetails[_vault].balance = _vaultDetails[_vault].balance.sub(_amount); }

Impact

The _vaultDetails[_vault].balance variable does not correctly track the actual vault balances anymore as the traded amount of _convert token could have a completely different value and decimals than the want token. It could heavily over-or-underestimate the actual withdrawn want amount.

This variable is used in Controller.balanceOf(), which in turn is used in Vault.balance(), which in turn is used to determine how many shares to mint / amount to receive when redeeming shares. If the value is less, users will lose money as they can redeem fewer tokens. If the value is more, users can redeem their existing shares for more value, leading to a bankrun until the vault is emptied and other users cannot redeem anymore.

Do not overwrite the _amount variable in the if branch.

#0 - GainsGoblin

2021-10-06T19:40:27Z

Duplicate of #8

#1 - GalloDaSballo

2021-10-14T16:51:43Z

Duplicate of #77

Also adding slippage checks in controller would mitigate the specific attack mentioned here

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xsanson

Labels

bug
2 (Med Risk)

Awards

62.7957 YAXIS - $244.90

External Links

Handle

cmichel

Vulnerability details

There are ERC20 tokens that may make certain customizations to their ERC20 contracts. One type of these tokens is deflationary tokens that charge a certain fee for every transfer() or transferFrom(). Others are rebasing tokens that increase in value over time like Aave's aTokens (balanceOf changes over time).

Impact

The VaultHelper's depositVault() and depositMultipleVault functions transfer _amount to this contract using IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);. This could have a fee, and less than _amount ends up in the contract. The next actual vault deposit using IVault(_vault).deposit(_token, _amount); will then try to transfer more than the this contract actually has and will revert the transaction.

One possible mitigation is to measure the asset change right before and after the asset-transferring routines. This is already done correctly in the Vault.deposit function.

#0 - Haz077

2021-09-27T18:45:35Z

Same as #62 & #13 in my opinion

#1 - GalloDaSballo

2021-10-12T23:25:22Z

Agree with finding, checking actual balance of contract would mitigate vulnerability Additionally ensuring the protocol never uses rebasing or tokens with feeOnTransfer can be enough to mitigate

The vulnerability can brick the protocol However it can be sidestepped by simply not using feeOnTransfer tokens Downgrading to medium

Findings Information

🌟 Selected for report: cmichel

Also found by: defsec, jonah1005, tensors

Labels

bug
duplicate
2 (Med Risk)

Awards

37.6774 YAXIS - $146.94

External Links

Handle

cmichel

Vulnerability details

The ERC20.transfer() and ERC20.transferFrom() functions return a boolean value indicating success. This parameter needs to be checked for success. Some tokens do not revert if the transfer failed but return false instead.

The Manager.recoverToken function does not check the return value of this function.

Impact

Tokens that don't actually perform the transfer and return false are still counted as a correct transfer. Furthermore, tokens that do not correctly implement the EIP20 standard, like USDT which does not return a success boolean, will revert.

We recommend using OpenZeppelin’s SafeERC20 versions with the safeTransfer and safeTransferFrom functions that handle the return value check as well as non-standard-compliant tokens.

#0 - Haz077

2021-09-27T17:47:14Z

Same as #5

#1 - GalloDaSballo

2021-10-13T23:03:06Z

Agree with finding, using a non reverting token can potentially cause issues to the protocol accounting

Sponsor can check each token on a case by case basis, or simply use OpenZeppelin's safeERC20

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xRajeev, 0xsanson

Labels

bug
duplicate
2 (Med Risk)

Awards

37.6774 YAXIS - $146.94

External Links

Handle

cmichel

Vulnerability details

The Vault.withdraw function attempts to withdraw funds from the controller if there are not enough in the vault already. In the case the controller could not withdraw enough, i.e., where _diff < _toWithdraw, the user will receive less output tokens than their fair share would entitle them to (the initial _amount).

if (_diff < _toWithdraw) {
    // @audit burns too many shares for a below fair-share amount
    _amount = _balance.add(_diff);
}

Impact

The withdrawer receives fewer output tokens than they were entitled to.

In the mentioned case, the _shares should be recomputed to match the actual withdrawn _amount tokens:

if (_diff < _toWithdraw) {
    _amount = _balance.add(_diff);
    // recompute _shares to burn based on the lower payout
    // should be something like this, better to cache balance() once at the start and use that cached value
    _shares = (totalSupply().mul(_amount)).div(_balance);
}

Only these shares should then be burned.

#1 - GalloDaSballo

2021-10-13T23:19:14Z

Agree with the finding

Anytime the strategy incurs a loss during withdrawal, the person that triggered that withdrawal will get less for their shares than what they may expect.

Since amount of shares is computed by checking balance in strategy, and controller enacts this withdrawal, adding a check in the controller to compare expected withdrawal vs actual shares received would be a clean way to mitigate

Findings Information

🌟 Selected for report: cmichel

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

139.546 YAXIS - $544.23

External Links

Handle

cmichel

Vulnerability details

The Controller.inCaseStrategyGetStuck withdraws from a strategy but does not call updateBalance(_vault, _strategy) afterwards.

Impact

The _vaultDetails[_vault].balances[_strategy] variable does not correctly track the actual strategy balance anymore. I'm not sure what exactly this field is used for besides getting the withdraw amounts per strategy in getBestStrategyWithdraw. As the strategy contains a lower amount than stored in the field, Controller.withdraw will attempt to withdraw too much.

Call updateBalance(_vault, _strategy) in inCaseStrategyGetStuck.

#0 - GalloDaSballo

2021-10-14T14:14:38Z

Agree with finding, I also believe inCaseStrategyGetStuck and inCaseTokenGetStuck are vectors for admin rugging, may want to add checks to ensure only non strategy token can be withdrawn from the vaults and strats

Findings Information

🌟 Selected for report: cmichel

Labels

bug
duplicate
2 (Med Risk)

Awards

139.546 YAXIS - $544.23

External Links

Handle

cmichel

Vulnerability details

One vault can have many tokens, but each token should only be assigned to a single vault. The Manager contract keeps a mapping of tokens to vaults in the vaults[_token] => _vault map, and a mapping of vault to tokens in tokens[vault] => token[].

The addToken function can assign any token to a single vault and allows overwriting an existing vaults[_token] map entry with a different vault. This indirectly disassociates the previous vault for the token. Note that the previous vault's tokens[_previousVault] map still contains the token.

Impact

The token disappears from the system for the previous vault but the actual tokens are still in there, getting stuck. Only the new vault is considered for the token anymore, which leads to many issues, see Controller.getBestStrategyWithdraw and the onlyVault modifier that doesn't work correctly anymore.

It should check if the token is already used in a map, and either revert or correctly remove the token from the vault - from the tokens array. It should do the same cleanup procedure as in removeToken:

if (found) {
    // remove the token from the vault
    tokens[_vault][index] = tokens[_vault][k-1];
    tokens[_vault].pop();
    delete vaults[_token];
    emit TokenRemoved(_vault, _token);
}

addToken should also check that the token is not already in the tokens[_vault] array.

#0 - GalloDaSballo

2021-10-14T15:19:11Z

Mapping mismatch can cause undefined behaviour

Recommend having one source of truth to keep things simple

Findings Information

🌟 Selected for report: cmichel

Labels

bug
documentation
2 (Med Risk)

Awards

139.546 YAXIS - $544.23

External Links

Handle

cmichel

Vulnerability details

The YAxisVotePower.balanceOf contract uses the Uniswap pool reserves to compute a _lpStakingYax reward:

(uint256 _yaxReserves,,) = yaxisEthUniswapV2Pair.getReserves();
int256 _lpStakingYax = _yaxReserves
      .mul(_stakeAmount)
      .div(_supply)
      .add(rewardsYaxisEth.earned(_voter));

The pool can be temporarily manipulated to increase the _yaxReserves amount.

Impact

If this voting power is used for governance proposals, an attacker can increase their voting power and pass a proposal.

One could build a TWAP-style contract that tracks a time-weighted-average reserve amount (instead of the price in traditional TWAPs). This can then not be manipulated by flashloans.

#0 - uN2RVw5q

2021-10-01T15:55:03Z

I disagree with the "sponsor disputed" tag.

I think this is a valid issue and makes balanceOf(_voter) susceptible to flashloan attacks. However, as long as balanceOf(_voter) is always called by a trusted EOA during governance vote counts, this should not be a problem. I assume this is the case for governance proposals. If that is not the case, I would recommend changing the code. Otherwise, changing the risk to "documentation" would be reasonable.

#1 - GalloDaSballo

2021-10-14T15:22:27Z

Agree with original warden finding, as well as severity

The ability to trigger the count at any time does prevent a flashloan attack (as flashloans are atomic) It would allow the privilege of the flashloan attack to the trusted EOA (admin privilege)

Additionally the voting power can still be frontrun, while you cannot manipulate that voting power via a flashloan, you can just buy and sell your position on the same block as when the count is being taken

Due to this I will up the severity back to medium as this is a legitimate vector to extract value

Findings Information

🌟 Selected for report: cmichel

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

139.546 YAXIS - $544.23

External Links

Handle

cmichel

Vulnerability details

The Harvester.getEstimates contract tries to estimate a YAXIS amount but uses the wrong path and/or amount.

It currently uses a WETH input amount to compute a YAXIS -> WETH trade.

address[] memory _path;
_path[0] = IStrategy(_strategy).want();
_path[1] = IStrategy(_strategy).weth();
// ...

_path[0] = manager.yaxis();
// path is YAXIS -> WETH now
// fee is a WETH precision value
uint256 _fee = _estimatedWETH.mul(manager.treasuryFee()).div(ONE_HUNDRED_PERCENT);
// this will return wrong trade amounts
_amounts = _router.getAmountsOut(_fee, _path);
_estimatedYAXIS = _amounts[1];

Impact

The estimations from getEstimates are wrong. They seem to be used to provide min. amount slippage values (_estimatedWETH, _estimatedYAXIS) for the harvester when calling Controller._estimatedYAXIS. These are then used in BaseStrategy._payHarvestFees and can revert the harvest transactions if the wrongly computed _estimatedYAXIS value is above the actual trade value. Or they can allow a large slippage if the _estimatedYAXIS value is below the actual trade value, which can then be used for a sandwich attack.

Fix the estimations computations.

As the estimations are used in BaseStrategy._payHarvestFees, the expected behavior seems to be trading WETH to YAXIS. The path should therefore be changed to path[0] = WETH; path[1] = YAXIS in Harvester.getEstimates.

#0 - GalloDaSballo

2021-10-14T15:35:17Z

Price estimates on Uniswap are dependent on which side of the swap you're making

Sponsor has mitigated in later PR

Findings Information

🌟 Selected for report: cmichel

Labels

bug
duplicate
1 (Low Risk)

Awards

46.5153 YAXIS - $181.41

External Links

Handle

cmichel

Vulnerability details

Many contracts iterate over the entire strategies or tokens array of a vault

For example:

  • Harvester.removeStrategy
  • Harvester.harvestNextStrategy
  • Manager.removeToken

Impact

The transactions can fail if the arrays get too big and the transaction would consume more gas than the block limit. This will then result in a denial of service for the desired functionality and break core functionality.

Keep the number of strategies and tokens per pool small. Alternatively use an enumerable set for better efficiency. It does not require iterating over the entire array when trying to find an element in removeStrategy/Token.

#0 - GalloDaSballo

2021-10-13T23:58:38Z

Agree with finding, this shows a different exploit that can be mitigated by using OpenZeppelin's EnumerableSet

Findings Information

🌟 Selected for report: cmichel

Labels

bug
1 (Low Risk)
sponsor acknowledged
sponsor confirmed

Awards

46.5153 YAXIS - $181.41

External Links

Handle

cmichel

Vulnerability details

The Withdraw event in LegacyController.withdraw emits the _amount variable which is the initial, desired amount to withdraw. It should emit the actual withdrawn amount instead, which is transferred in the last token.balanceOf(address(this)) call.

Impact

The actual withdrawn amount, which can be lower than _amount, is part of the event. This is usually not what you want (and it can already be decoded from the function argument).

Use it or remove it.

#0 - GalloDaSballo

2021-10-14T00:00:06Z

Nice catch, sponsor mitigated

Findings Information

🌟 Selected for report: cmichel

Labels

bug
1 (Low Risk)
sponsor disputed

Awards

46.5153 YAXIS - $181.41

External Links

Handle

cmichel

Vulnerability details

Several functions trade without using any slippage protection, their min. return amount is set to 1:

  • Controller.setCap: _balance = _converter.convert(_want, _convert, _balance, 1);
  • Controller.withdrawAll: _amount = _converter.convert(_want, _convert, _amount, 1);
  • NativeStrategyCurve3Crv._swapTokens: _swapTokens(weth, _stableCoin, _remainingWeth, 1);

Combined with the fact that anyone can call Harvester.harvestNextStrategy, it's easy to sandwich-attack these.

A common attack in DeFi is the sandwich attack. Upon observing a trade of asset X for asset Y, an attacker frontruns the victim trade by also buying asset Y, lets the victim execute the trade, and then backruns (executes after) the victim by trading back the amount gained in the first trade. Intuitively, one uses the knowledge that someone’s going to buy an asset, and that this trade will increase its price, to make a profit. The attacker’s plan is to buy this asset cheap, let the victim buy at an increased price, and then sell the received amount again at a higher price afterwards.

Impact

The protocol makes a bad trade and loses tokens. The profit is not maximized.

Accept a function parameter that can be chosen by the transaction sender, then check that the actually received amount is above this parameter instead of using the hardcoded 1.

harvestNextStrategy may not be called by anyone as they could set the slippage parameter very low. Let the strategist call harvestNextStrategy.

Check if it's feasible to send these transactions directly to a miner (flashbots) such that they are not visible in the public mempool.

#0 - BobbyYaxis

2021-09-18T21:19:07Z

harvestNextStrategy cannot be called by non-whitelisted addresses

#1 - GalloDaSballo

2021-10-14T14:13:02Z

Warden findings are correct, but they are not properly articulated. Other findings point to risk of front-running, sandwiching and Single Sided Exposure risk

Downgrading to Low Risk

For Sponsor: The fact you can call the function doesn't prevent the trade from being front-run as once you sign the transaction it is publicly available in the mempool, frontrunner can then write sandwiching txs with the goal of maximing MEV out of your trade

Findings Information

🌟 Selected for report: cmichel

Also found by: itsmeSTYJ

Labels

bug
G (Gas Optimization)

Awards

6.7734 YAXIS - $26.42

External Links

Handle

cmichel

Vulnerability details

The Manager.removeToken function iterates over all tokens to check for existence of an element in the tokens[_vault] array.

A more efficient solution would be to use OpenZeppelin's EnumerableSet. It allows iterating (enumerating) over all entries, as well as constant-time existence checks using contains, as well as a constant time remove function.

The trade-off is that modifying an element requires two sstores due to the additional index it needs to keep track of.

#0 - GalloDaSballo

2021-10-12T22:24:00Z

Agree, EnumerableSet is cleaner and typically saves gas as well

#1 - GalloDaSballo

2021-10-12T22:25:07Z

Disagree that it's a duplicate as the finding is specific to a particular functionality different from #117

Findings Information

🌟 Selected for report: cmichel

Also found by: itsmeSTYJ

Labels

bug
G (Gas Optimization)

Awards

6.7734 YAXIS - $26.42

External Links

Handle

cmichel

Vulnerability details

The Harvester.removeStrategy function iterates over all tokens to check for existence of an element in the strategies[_vault].addresses[_vault] array.

A more efficient solution would be to use OpenZeppelin's EnumerableSet (or EnumerableMap). It allows iterating (enumerating) over all entries, as well as constant-time existence checks using contains, as well as a constant time remove function.

The trade-off is that modifying an element requires two sstores due to the additional index it needs to keep track of.

#0 - GalloDaSballo

2021-10-12T22:25:24Z

Agree, EnumerableSet is cleaner and typically saves gas as well

Disagree that it's a duplicate as the finding is specific to a particular functionality different from #116

Findings Information

🌟 Selected for report: cmichel

Also found by: hickuphh3, jonah1005, pauliax

Labels

bug
G (Gas Optimization)

Awards

2.7432 YAXIS - $10.70

External Links

Handle

cmichel

Vulnerability details

The Vault.deposit function performs an unnecessary add here as _shares is always zero at this point:

if (_amount > 0) {
    _amount = _normalizeDecimals(_token, _amount);

    if (totalSupply() > 0) {
        _amount = (_amount.mul(totalSupply())).div(_balance);
    }
    // @audit gas: just set _shares = _amount, no .add needed
    _shares = _shares.add(_amount);
}

Set _shares = _amount directly instead of adding amount to the always zero-valued _shares.

#0 - Haz077

2021-09-27T17:53:49Z

Same as #6

#1 - GalloDaSballo

2021-10-12T22:26:37Z

Agree with finding, sponsor mitigated, see progress in #116

Findings Information

🌟 Selected for report: hickuphh3

Also found by: cmichel, hrkrshnn, pauliax

Labels

bug
duplicate
G (Gas Optimization)

Awards

2.7432 YAXIS - $10.70

External Links

Handle

cmichel

Vulnerability details

The Vault.depositMultiple function has a notHalted multiplier that the subcalls to deposit already check. Furthermore, all repeated deposit calls check the notHalted modifier each time.

Consider refactoring the code such that the notHalted modifier is only evaluated once instead of _tokens.length + 1 times.

#0 - Haz077

2021-09-27T17:56:14Z

Same as #70

#1 - GalloDaSballo

2021-10-12T22:41:40Z

Duplicate of #70

Findings Information

🌟 Selected for report: cmichel

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

15.052 YAXIS - $58.70

External Links

Handle

cmichel

Vulnerability details

The two loops in StablesConverter.convert can be avoided by using the indices mapping.

Instead of the for loop, simply get the index i by doing:

if (_output == address(token3CRV)) {
  uint256 i = indices[_input];
  amounts[i] = _inputAmount;
} else if (_input == address(token3CRV)) {
  uint256 i = indices[_output];
  remove_liquidity_one_coin(_, i);
}

This replaces all the tokens[i] sloads with a single one.

#0 - GalloDaSballo

2021-10-12T22:44:30Z

Using indices instead of looping will save gas, Sponsor mitigated, LG

Findings Information

🌟 Selected for report: cmichel

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

15.052 YAXIS - $58.70

External Links

Handle

cmichel

Vulnerability details

The two loops in StablesConverter.expected can be avoided by using the indices mapping.

Instead of the for loop, simply get the index i by doing:

if (_output == address(token3CRV)) {
  uint256 i = indices[_input];
  amounts[i] = _inputAmount;
} else if (_input == address(token3CRV)) {
  uint256 i = indices[_output];
  return stableSwap3Pool.calc_withdraw_one_coin(_inputAmount, i);
}

This replaces all the tokens[i] sloads with a single one.

#0 - GalloDaSballo

2021-10-12T22:45:32Z

Similarly to #123 , sponsor mitigated, LG

Findings Information

🌟 Selected for report: cmichel

Labels

bug
G (Gas Optimization)
sponsor acknowledged
sponsor confirmed

Awards

15.052 YAXIS - $58.70

External Links

Handle

cmichel

Vulnerability details

When doing swaps with a Uniswap router from within a contract, there's no need to compute any offset from the current block for the deadline parameter. The router just checks if deadline >= block.timestamp.

See BaseStrategy._swapTokens which does an unnecessary block.timestamp read and another unnecessary addition of 1800.

The most efficient way to provide deadlines for a router swap is to use a hardcoded value that is far in the future, for example, 1e10.

#0 - Haz077

2021-09-27T18:57:28Z

Added in code-423n4/2021-09-yaxis#18

#1 - GalloDaSballo

2021-10-12T22:47:20Z

Sponsor does acknowledge and has mitigated

Not sure why they are using 1e10 when now would work (may be the case of avoiding a listing message)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter