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
Rank: 5/27
Findings: 7
Award: $3,873.46
🌟 Selected for report: 6
🚀 Solo Findings: 4
🌟 Selected for report: jonah1005
1619.075 USDC - $1,619.07
jonah1005
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.
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.
9891000
.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.
728.5837 USDC - $728.58
jonah1005
createPool
is a permissionless transaction.
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.
VaderPoolFactory.sol#L43-L89 VaderPoolV2.sol#L115-L167
None
Restrict users from minting synth from a new and illiquid pool. Some thoughts about the fix:
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.
🌟 Selected for report: jonah1005
485.7225 USDC - $485.72
jonah1005
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.
That says a user wants to provide 1M ETH in the pool. Attackers can sandwich this trade as follows:
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.
🌟 Selected for report: jonah1005
485.7225 USDC - $485.72
jonah1005
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.
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
None
Users should be able to protect themselves when burning lp.
Some possible fixes:
reserve.reimburseImpermanentLoss(msg.sender, coveredloss);
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.
🌟 Selected for report: jonah1005
485.7225 USDC - $485.72
jonah1005
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.
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()
((totalPoolUnits * poolUnitFactor) / denominator) * slip
Since the scale of the total supply is (10**18)^2, the operation would overflow.
None
Set a minimum deposit amount (both asset amount and native amount) for the first lp provider.
🌟 Selected for report: jonah1005
68.651 USDC - $68.65
jonah1005
The internal function _addLiquidity
seems to be unnecessary.
The internal function only create a Pool and returns (amountA, amountB) = (amountADesired, amountBDesired);
It's misleading and unnecessary.
None
Recommend to make it simple and transparent.
function addLiquidity( IERC20 tokenA, IERC20 tokenB, uint256 amountADesired, uint256 amountBDesired, address to, uint256 deadline ) public override ensure(deadline) returns ( uint256 amountA, uint256 amountB, uint256 liquidity ) { IVaderPool pool; pool = factory.getPool(tokenA, tokenB); if (pool == IVaderPool(_ZERO_ADDRESS)) { pool = factory.createPool(tokenA, tokenB); } tokenA.safeTransferFrom(msg.sender, address(pool), amountADesired); tokenB.safeTransferFrom(msg.sender, address(pool), amountBDesired); liquidity = pool.mint(to); }