yAxis contest - hickuphh3'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: 6/12

Findings: 5

Award: $2,106.31

🌟 Selected for report: 7

šŸš€ Solo Findings: 2

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

hickuphh3

Vulnerability details

Impact

In withdraw(), the withdrawal amount is the proportion of the normalized amounts of all the tokens in the vault and its strategies. However, this amount isn't un-normalized to the output token's decimals, thus leading to an erroneous token amount being withdrawn.

Proof of Concept

We take USDC as an example, since it has 6 decimals.

  1. deposit(USDC, 1000e6): Deposit 1000 USDC into the vault. The user receives 1000 shares.

  2. Attempt a full withdrawal withdraw(1000e18, USDC)

    _amount
    = (balance().mul(_shares)).div(totalSupply());
    = (1000e18) * 1000e18 / 1000e18
    = 1000e18

The expected _amount is 1000e6 as per the deposited amount. While the withdrawal will technically work because of the following lines:

if (_balance < _amount) {
  IController _controller = IController(manager.controllers(address(this)));
  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);
  }
}

we note that this logic should only be applied under normal circumstances, where the _amount is assumed to be in the token decimals.

_amount needs to be un-normalised back to its native token decimals.

#0 - GalloDaSballo

2021-10-14T16:53:22Z

Duplicate of #131

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
2 (Med Risk)

Awards

139.546 YAXIS - $544.23

External Links

Handle

hickuphh3

Vulnerability details

Impact

The vault treats all assets to be of the same price. Given that one can also deposit and withdraw in the same transaction, this offers users the ability to swap available funds held in the vault at parity, with the withdrawal protection fee (0.1%) effectively being the swap fee.

Due care and consideration should therefore be placed if new stablecoins are to be added to the vault (eg. algorithmic ones that tend to occasionally be off-peg), or when lowering the withdrawal protection fee.

  • Prevent users from depositing and withdrawing in the same transaction. This would help prevent potential flash loan attacks as well
  • setWithdrawalProtectionFee() could have a requirement for the value to be non-zero. Zero withdrawal fee could be set in setHalted() whereby only withdrawals will be allowed.
  • Use price oracles to accurately calculate the shares to be transferred to users for deposits, or token amounts to be sent for withdrawals

#0 - GainsGoblin

2021-10-01T14:39:36Z

Duplicate of #2

#1 - GalloDaSballo

2021-10-14T00:05:36Z

Agree with finding, this vault accounting can be used for arbitrage opportunities as tokens are treated at exact value while they may have imbalances in price

This is not a duplicate as it's explaining a specific attack vector

#2 - GalloDaSballo

2021-10-14T00:06:06Z

Also raising risk valuation as this WILL be used to extract value from the system

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
duplicate
2 (Med Risk)
sponsor acknowledged

Awards

139.546 YAXIS - $544.23

External Links

Handle

hickuphh3

Vulnerability details

Impact

Let us assume either of the following cases:

  1. The vault / protocol is to be winded down or migrated, where either the protocol is halted and withdrawAll() has been called on all active strategies to transfer funds into the vault.
  2. There are 0 strategies. Specifically, _controller.strategies() = 0

Attempted withdrawals can be frontrun such that users will receive less, or even no funds in exchange for burning vault tokens. This is primarily enabled by the feature of having deposits in multiple stablecoins.

Proof of Concept

  1. Assume getPricePerFullShare() of 1e18 (1 vault token = 1 stablecoin). Alice has 1000 vault tokens, while Mallory has 2000 vault tokens, with the vault holdings being 1000 USDC, 1000 USDT and 1000 DAI.
  2. Alice attempts to withdraw her deposit in a desired stablecoin (Eg. USDC).
  3. Mallory frontruns Alice's transaction and exchanges 1000 vault tokens for the targeted stablecoin (USDC). The vault now holds 1000 USDT and 1000 DAI.
  4. Alice receives nothing in return for her deposit because the vault no longer has any USDC. getPricePerFullShare() now returns 2e18.
  5. Mallory splits his withdrawals evenly, by burning 500 vault tokens for 1000 USDT and the other 500 vault tokens for 1000 DAI.

Hence, Mallory is able to steal Alice's funds by frontrunning her withdrawal transaction.

The withdrawal amount could be checked against getPricePerFullShare(), perhaps with reasonable slippage.

#0 - GainsGoblin

2021-09-30T22:35:22Z

Duplicate of #28

#1 - GalloDaSballo

2021-10-14T13:59:28Z

Disagree with duplicate label as this shows a Value Extraction, front-running exploit. Medium severity as it's a way to "leak value"

This can be mitigated through addressing the "Vault value all tokens equally" issue

#2 - GainsGoblin

2021-10-14T20:31:18Z

The issue is exactly the same as #28. Both issues present the exact same front-running example.

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
1 (Low Risk)

Awards

46.5153 YAXIS - $181.41

External Links

Handle

hickuphh3

Vulnerability details

Impact

Whenever a user withdraws from a vault, a withdrawal fee is applied, and is distributed proportionally to the remaining vault token holders.

Should the protocol be halted, if the withdrawal fee remains non-zero, we would have a "last man standing" situation since users who withdraw later will benefit from withdrawals before them.

There would also be the issue of unaccounted withdrawal fees when the last vault token holder withdraws his funds.

  • Set withdrawal fee to 0 in setHalted()

#0 - Haz077

2021-09-22T18:22:45Z

Suggested by #71

#1 - GalloDaSballo

2021-10-14T00:07:59Z

Agree with finding Would recommend to tweak architecture to issue shares on withdrawal free accrual, or simply transfer those fees out to avoid this type of situation

Mitigation suggested by warden seems simple enough as well

Is not a duplicate from what I can tell

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

46.5153 YAXIS - $181.41

External Links

Handle

hickuphh3

Vulnerability details

Impact

A token that has been added and subsequently removed through the manager will cause users' deposited funds in that token to be trapped in the vault.

The vault's deposit and withdraw modifier should be separate, such that users are still able to withdraw previously added (but subsequently removed) tokens from the vault.

#0 - uN2RVw5q

2021-10-03T19:51:42Z

The vault's deposit and withdraw modifier should be separate, such that users are still able to withdraw previously added (but subsequently removed) tokens from the vault.

Allowing this doesn't feel right to me.

As long as there is some token available, the user will be able to withdraw their share in an equivalent token, even if that token is different from the deposited token.

I would consider this to be a non-issue. Perhaps this can be converted into a documentation tag. Add some documentation about checking before a token is removed in Manager contract and see if an equivalent token is present in the vault sounds reasonable. In the worst case, a removed token can still be added back by the manager, and therefore, the tokens should be safe.

#1 - GalloDaSballo

2021-10-14T14:10:11Z

Agree with the original warden finding reduced severity to Low

The admin strategist has the ability to remove tokens, this removal can impact accounting, notably Vault.sol'sĀ balanceOfThis

May want to add a check on removeToken

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: hickuphh3

Labels

bug
duplicate
G (Gas Optimization)

Awards

6.7734 YAXIS - $26.42

External Links

Handle

hickuphh3

Vulnerability details

Impact

L141: The safemath subtraction in strategies[_vault].addresses[index] = strategies[_vault].addresses[tail.sub(1)]; is unnecessary because this code block will not be executed in the case tail = strategies[_vault].addresses.length = 0 (vault has no strategies, found will be false).

strategies[_vault].addresses[index] = strategies[_vault].addresses[tail - 1];

#0 - GainsGoblin

2021-09-18T11:34:02Z

Duplicate of #46

#1 - GalloDaSballo

2021-10-12T22:08:31Z

Duplicate of #46

Findings Information

🌟 Selected for report: cmichel

Also found by: hickuphh3, jonah1005, pauliax

Labels

bug
duplicate
G (Gas Optimization)

Awards

2.7432 YAXIS - $10.70

External Links

Handle

hickuphh3

Vulnerability details

Impact

L172: _shares = _shares.add(_amount); can simply be _shares = _amount because the prior value of _shares is always 0.

#0 - Haz077

2021-09-22T18:19:15Z

Same as #6

#1 - GalloDaSballo

2021-10-12T22:50:33Z

Duplicate of #118

Findings Information

🌟 Selected for report: hickuphh3

Also found by: cmichel, hrkrshnn, pauliax

Labels

bug
G (Gas Optimization)
sponsor acknowledged
sponsor confirmed

Awards

2.7432 YAXIS - $10.70

External Links

Handle

hickuphh3

Vulnerability details

Impact

The notHalted modifier in depositMultiple() is redundant because it is checked (multiple times) by the underlying function call to deposit().

Further optimizations may be done to implement an internal _deposit() function that will be called by both deposit() and depositMultiple() so that notHalted is only checked once.

Remove the notHalted modifier in depositMultiple().

#0 - GalloDaSballo

2021-10-12T22:41:48Z

Sponsor mitigated

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
G (Gas Optimization)
sponsor acknowledged
sponsor confirmed

Awards

15.052 YAXIS - $58.70

External Links

Handle

hickuphh3

Vulnerability details

Impact

_vaultDetails[_vault].balance in L367 can be changed to the already fetched value _balance.

_vaultDetails[_vault].balance = _vaultDetails[_vault].balance.sub(_amount);

#0 - GalloDaSballo

2021-10-12T22:48:39Z

Sponsor acknowledge and mitigated, LG

Findings Information

🌟 Selected for report: hickuphh3

Labels

bug
G (Gas Optimization)
sponsor acknowledged
sponsor confirmed

Awards

15.052 YAXIS - $58.70

External Links

Handle

hickuphh3

Vulnerability details

The negation of the disjunction form of the canHarvest() function will help save gas. In other words, instead of !(A || B), return (!A && !B).

function canHarvest(
	address _vault
)
  public
  view
  returns (bool)
{
  Strategy storage strategy = strategies[_vault];
  // only can harvest if there are strategies, and when sufficient time has elapsed
  // solhint-disable-next-line not-rely-on-time
	return (strategy.addresses.length > 0 && strategy.lastCalled <= block.timestamp.sub(strategy.timeout));
}

#0 - GalloDaSballo

2021-10-12T22:49:55Z

Nice gas optimization by using boolean short circuit

#1 - GalloDaSballo

2021-10-12T22:50:00Z

Sponsor has mitigated

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