Vader Protocol contest - jonah1005's results

Liquidity Protocol anchored by Native Stablecoin with Slip-Based Fees AMM, IL protection and Synthetics.

General Information

Platform: Code4rena

Start Date: 09/11/2021

Pot Size: $75,000 USDC

Total HM: 57

Participants: 27

Period: 7 days

Judge: alcueca

Total Solo HM: 49

Id: 52

League: ETH

Vader Protocol

Findings Distribution

Researcher Performance

Rank: 5/27

Findings: 7

Award: $3,873.46

🌟 Selected for report: 6

🚀 Solo Findings: 4

Findings Information

🌟 Selected for report: jonah1005

Labels

bug
3 (High Risk)
sponsor disputed
VaderMath

Awards

1619.075 USDC - $1,619.07

External Links

Handle

jonah1005

Vulnerability details

calculate Loss is vulnerable to flashloan attack

Impact

The VaderPool would compensate users' IL. The formula it uses to calculate lp value is vulnerable to manipulation.

The formula to calculate the lp value is similar to warp finance which is known to be unsafe. warpfinance-incident-root-cause-analysis (Please to refer to the POC section)

The Attacker can purchase an old lp position, manipulate price, take IL compensation and drain the reserve. I consider this is a high-risk issue.

Proof of Concept

VaderMath.sol#L69-L93

The lp value is calculated as [(A0 * P1) + V0] and // [(A1 * P1) + V1]. Assume that there's an ETH pool and there's 100 ETH and 100 Vader in the pool.

  1. Attacker deposit 1 ETH and 1 Vader and own 1% of the liquidity.
  2. Wait 1 year
  3. Start flash loan and buy a lot ETH with 99900 Vader.
  4. There's 0.1 ETH 100,000 Vader in the pool.
  5. Burn 1 % lp at the price 1 ETH = 1,000,000 Vader.
  6. A0 * P1 + V0 = 1 (eth) * 1,000,000 (price) + 100 (vader)
  7. A1 * P1 + V1 = 0.001 (eth) * 1,000,000 (price) + 10,000 (vader)
  8. IL compensation would be around 9891000.

Tools Used

None

Please use the fair lp pricing formula from alpha finance instead. fair-lp-token-pricing

#0 - SamSteinGG

2021-11-25T11:54:31Z

The described attack scenario can not be executed as the pool would actually consume the flash loan. The CLP model follows a non-linear curve that actually diminishes in value as the trade size increases, meaning that at most 25% of the total assets in the pool can be drained at a given iteration. This, on top with the fees of each transaction render this attack vector impossible. Please request a tangible attack test from the warden if this is meant to be accepted as valid.

#1 - alcueca

2021-12-11T06:56:42Z

The CLP model isn't mentioned in the readme or the whitepaper. The issue is valid according to the materials supplied.

#2 - SamSteinGG

2021-12-16T12:10:32Z

@alcueca As the grading guidelines of C4 state, a documentation issue cannot constitute more than a low risk finding. We advise the severity to be lowered.

Findings Information

🌟 Selected for report: jonah1005

Also found by: defsec

Labels

bug
3 (High Risk)
sponsor disputed
VaderPoolV2
VaderPoolFactory

Awards

728.5837 USDC - $728.58

External Links

Handle

jonah1005

Vulnerability details

Impact

createPool is a permissionless transaction.

  1. Anyone can create a token pool.
  2. Token price is set by the first lp provider.
  3. User can get a synthetic asset.

Assume a new popular coin that the DAO decides to add to the protocol. The attacker can create the pool and set it to be extremely cheap. (By depositing 1 wei coin and 10^18 wei Vader.) The attacker can mint a lot of synth by providing another 10^18 wei Vader.

There's no way to revoke the pool. The coin pool would be invalid since the attacker can drain all the lp in the pool.

I consider this is a high-risk issue.

Proof of Concept

VaderPoolFactory.sol#L43-L89 VaderPoolV2.sol#L115-L167

Tools Used

None

Restrict users from minting synth from a new and illiquid pool. Some thoughts about the fix:

  1. Decide minimum liquidity for a synthetic asset (e.g 1M Vader in the pool)
  2. Once there's enough liquidity pool, anyone can deploy a synthetic asset after a cool down. (e.g. 3 days

The pool can remain permissionless and safe.

#0 - SamSteinGG

2021-11-25T12:05:27Z

This is an invalid finding as creating pools is not a permissionless operation, the token must be in the supported list of assets.

#1 - alcueca

2021-12-11T06:41:20Z

I can't see a check for a token to be in a supported list of assets.

#2 - SamSteinGG

2021-12-16T12:02:08Z

@alcueca There seems to be some confusion. The submitted of the bounty links the Vader Pool Factory of DEX V1 and the Pool of DEX V2 which are not interacting between them. As such, the finding is invalid.

Findings Information

🌟 Selected for report: jonah1005

Labels

bug
2 (Med Risk)
sponsor disputed
VaderRouterV2

Awards

485.7225 USDC - $485.72

External Links

Handle

jonah1005

Vulnerability details

add liquidity is vulnerable to MEV

Impact

addLiquidity in the VaderRouter and VaderRouterV2 contract does not check the minimum liquidity amount. This makes users' funds vulnerable to sandwich attacks.

The team says a minimum amount is not required as the VaderPool supports imbalanced mint. However, imbalanced mint is a helper function of buying tokens and providing to lp. A sandwich attack would take over more than 50% of a transaction in an illiquid pool.

Given the current network environment, most transactions in the mempool would be sandwiched. However, users may avoid this attack if they only send tx through flashbot RPC. I consider this is a medium-risk issue.

Proof of Concept

VaderRouterV2.sol#L77-L96

That says a user wants to provide 1M ETH in the pool. Attackers can sandwich this trade as follows:

  1. Buy Vader with 10M ETH and makes ETH extremely cheap
  2. Put user's tx here User's tx would first buy a lot Vader and deposit to the pool.
  3. Since ETH becomes even cheaper in the pool. The MEV attacker buyback ETH and get profit.

Tools Used

None

Always check how much liquidity a user received in a transaction. A tx would not be sandwiched if it's not profitable.

We could learn a lot about MEV from Robert Miller'tweets.

#0 - SamSteinGG

2021-11-25T11:56:31Z

The design of the Thorchain CLP model is meant to prevent flash-loan based attacks as it allows a maximum trade size of 25% on a given iteration with a high-enough fee to render the attack unprofitable. Please request a tangible test case from the warden to consider this exhibit valid.

#1 - alcueca

2021-12-11T05:30:52Z

There is no documentation stating that the deployment of Vader is restricted to Thorchain. The issue is valid.

#2 - SamSteinGG

2021-12-16T12:08:15Z

@alcueca The model itself is what has this trait, it does not relate to the blockchain implementation itself. It is intended functionality as with its parent implementation.

Findings Information

🌟 Selected for report: jonah1005

Labels

bug
2 (Med Risk)
sponsor disputed
VaderReserve

Awards

485.7225 USDC - $485.72

External Links

Handle

jonah1005

Vulnerability details

Impact

The user would not get full IL compensation if there's not enough funds in the reserve. VaderReserve.sol#L76-L91

VaderReserve.sol#L85

        uint256 actualAmount = _min(reserve(), amount);

While this is reasonable, users should be able to specify the minimum received amount in the transaction. Otherwise, it's vulnerable to some kind of MEV attack.

I consider this is a medium-risk issue.

Proof of Concept

The user can not specify a minimum IL compensation in the router. VaderRouter.sol#L169-L207

The user may not receive the full amount of compensation. VaderReserve.sol#L76-L91

Tools Used

None

Users should be able to protect themselves when burning lp.

Some possible fixes:

  1. Return the actual amount this line reserve.reimburseImpermanentLoss(msg.sender, coveredloss);
  2. Checks whether there's slippage. Revert if the user doesn't receive the full amount.

We can add a new parameter in the function.

    require(actualCompensation > minimumCompensation);

Or we can check the amountAMin after receiving the compensation. ( assume tokenA is native token)

    require(amountA +actualCompensation > amountAMin);

#0 - SamSteinGG

2021-11-25T12:06:20Z

This is intended functionality of the protocol to account for rounding errors and is a principle similar to SushiSwap's MasterChef contract.

#1 - alcueca

2021-12-11T06:39:28Z

The sponsor acknowledges the issue.

#2 - SamSteinGG

2021-12-16T12:00:22Z

@alcueca They are never meant however it should still be possible to do so. With Uniswap V2, it is possible to interact with the contracts directly however doing so (without using a smart contract) will result in the same vulnerabilities as described in the issue. This is not intended usage and as such does not constitute an issue unless it is implied that the Uniswap V2 implementation is incorrect. Intended functionality of the protocol cannot constitute a risk issue. This has been classified as a medium risk issue but it follows the standardized conventions of implementations like Sushiswap which are live.

Findings Information

🌟 Selected for report: jonah1005

Labels

bug
2 (Med Risk)
resolved
sponsor confirmed
VaderMath

Awards

485.7225 USDC - $485.72

External Links

Handle

jonah1005

Vulnerability details

Impact

First lp provider received liquidity amount same as the nativeDeposit amount and decides the rate. If the first lp sets the pool's rate to an extreme value no one can deposit to the pool afterward. (please refer to the proof of concept section)

A malicious attacker can DOS the system by back-running the setTokenSupport and setting the pools' price to the extreme. I consider this is a medium-risk issue.

Proof of Concept


    deposit_amount = 1000 * 10**18
    get_token(dai, user, deposit_amount*3)
    get_token(vader, user, deposit_amount*3)
    dai.functions.approve(pool.address, deposit_amount*3).transact()
    link.functions.approve(pool.address, deposit_amount*3).transact()


    # deposit_amount = 1000 * 10**18

    # # first deposit 1 wei Dai and 1 vader to the pool
    router.functions.addLiquidity(dai.address, vader.address, 1, 10**18, user, 10**18).transact()
    print('received liquidity', pool.functions.positions(0).call()[2])
    # output log:
    # 1000000000000000000

    # normally deposit to the pool
    router.functions.addLiquidity(dai.address, vader.address, deposit_amount, deposit_amount, user, 10**18).transact()
    print('received liquidity', pool.functions.positions(1).call()[2])

    # output log:
    # 500000000000000000500000000000000000000

    # no one can deposit to the pool now
    # there would be revert

    router.functions.addLiquidity(dai.address, vader.address, 1, deposit_amount, user, 10**18).transact()

VaderMath.sol#L42

    ((totalPoolUnits * poolUnitFactor) / denominator) * slip

Since the scale of the total supply is (10**18)^2, the operation would overflow.

Tools Used

None

Set a minimum deposit amount (both asset amount and native amount) for the first lp provider.

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