Platform: Code4rena
Start Date: 02/02/2024
Pot Size: $100,000 USDC
Total HM: 11
Participants: 27
Period: 28 days
Judge: Lambda
Total Solo HM: 4
Id: 327
League:
Rank: 12/27
Findings: 2
Award: $302.93
π Selected for report: 0
π Solo Findings: 0
π Selected for report: carrotsmuggler
Also found by: 3docSec, Aymen0909, DadeKuma, Franfran, J4X, QiuhaoLi, ZanyBonzy, carrotsmuggler, emerald7017, erebus, oakcobalt, zhaojie
152.7401 USDC - $152.74
https://github.com/code-423n4/2024-02-hydradx/blob/603187123a20e0cb8a7ea85c6a6d718429caad8d/HydraDX-node/pallets/omnipool/src/lib.rs#L577 https://github.com/code-423n4/2024-02-hydradx/blob/603187123a20e0cb8a7ea85c6a6d718429caad8d/HydraDX-node/pallets/stableswap/src/lib.rs#L995 https://github.com/code-423n4/2024-02-hydradx/blob/603187123a20e0cb8a7ea85c6a6d718429caad8d/HydraDX-node/pallets/stableswap/src/lib.rs#L1118
Some of the functions in omnipool
and stableswap
lack slippage protection, and users may not expect the same results when they add liquidity.
For example :
omnipool#add_liquidity
,hub_asset_liquidity
is variable,
The share amount available to the user is calculated using calculate_add_liquidity_state_changes
, which relies on hub_asset_liquidity
, so the user may receive less share amount than expected.
omnipool and stableswap's user-facing function,buy sell, have slippage protection added, for example: omnipool's buy
function can set max_sell_amount
.
slippage protection is also added to add_liquidity_shares
in the stablepool
, and max_asset_amount
can be set:
#[require_transactional] fn do_add_liquidity_shares( who: &T::AccountId, pool_id: T::AssetId, shares: Balance, asset_id: T::AssetId, max_asset_amount: Balance, ) -> Result<Balance, DispatchError> { ..... ensure!(amount_in <= max_asset_amount, Error::<T>::SlippageLimit); }
The problem is that there are other functions that add liquidity that don't have slippage protection:
stableswap#do_add_liquidity
do_add_liquidity
functions similarly to do_add_liquidity_shares
, adding liquidity to stablepool
,
do_add_liquidity
can add liquidity to multiple assets,
However, do_add_liquidity
does not set SlippageLimit
as do_add_liquidity_shares
does.
omnipool#add_liquidity
in omnipool#add_liquidity
,hub_asset_liquidity
is variable,
The share amount available to the user is calculated using calculate_add_liquidity_state_changes
, which relies on hub_asset_liquidity
, so the user may receive less share amount than expected.
#[require_transactional] fn do_add_liquidity( who: &T::AccountId, pool_id: T::AssetId, assets: &[AssetAmount<T::AssetId>], ) -> Result<Balance, DispatchError> { @> let current_hub_asset_liquidity = T::Currency::free_balance(T::HubAssetId::get(), &Self::protocol_account()); let state_changes = hydra_dx_math::omnipool::calculate_add_liquidity_state_changes( &(&asset_state).into(), amount, I129 { value: current_imbalance.value, negative: current_imbalance.negative, }, @> current_hub_asset_liquidity, ) .ok_or(ArithmeticError::Overflow)?; .... let lp_position = Position::<Balance, T::AssetId> { asset_id: asset, amount, @> shares: *state_changes.asset.delta_shares, // Note: position needs price after asset state is updated. price: (new_asset_state.hub_reserve, new_asset_state.reserve), }; ..... }
vscode, manual
Add slippage protection to all necessary functions
MEV
#0 - c4-pre-sort
2024-03-03T06:25:42Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-03T06:25:44Z
0xRobocop marked the issue as primary issue
#2 - 0xRobocop
2024-03-03T07:04:30Z
Omnipool provides slippage protection via PriceBarrier::ensure_price
. Leaving for sponsor review.
#3 - c4-sponsor
2024-03-06T14:06:12Z
enthusiastmartin (sponsor) confirmed
#4 - c4-sponsor
2024-03-06T14:06:38Z
enthusiastmartin marked the issue as disagree with severity
#5 - OpenCoreCH
2024-03-07T21:11:41Z
Valid medium because there is a hypothetical path to leak values and in-line with other contests were missing slippage checks were medium.
#6 - c4-judge
2024-03-07T21:12:37Z
OpenCoreCH marked issue #93 as primary and marked this issue as a duplicate of 93
#7 - c4-judge
2024-03-08T10:37:09Z
OpenCoreCH marked the issue as satisfactory
π Selected for report: castle_chain
Also found by: 3docSec, Aymen0909, Franfran, J4X, Ocean_Sky, QiuhaoLi, TheSchnilch, ZanyBonzy, alix40, alkrrrrp, bin2chen, carrotsmuggler, ihtishamsudo, oakcobalt, peachtea, tsvetanovv, zhaojie
150.1907 USDC - $150.19
The administrator may not be able to remove tokens from the omnipool due to restrictions in omnipool#remove_token
In remove_token, we need to make sure that asset_state.shares == asset_state.protocol_shares, This restriction is to ensure that asset no shares owned by LPs.
#[transactional] pub fn remove_token(origin: OriginFor<T>, asset_id: T::AssetId, beneficiary: T::AccountId) -> DispatchResult { ..... ensure!( asset_state.shares == asset_state.protocol_shares, Error::<T>::SharesRemaining ); .... }
The problem is that this condition may never be met, There are two reasons:
pub fn add_token( origin: OriginFor<T>, asset: T::AssetId, initial_price: Price, weight_cap: Permill, position_owner: T::AccountId, ) -> DispatchResult { ...... let amount = T::Currency::free_balance(asset, &Self::protocol_account()); .... // Initial state of asset let state = AssetState::<Balance> { hub_reserve, @> shares: amount, @> protocol_shares: Balance::zero(), cap: FixedU128::from(weight_cap).into_inner(), tradable: Tradability::default(), }; ..... <Assets<T>>::insert(asset, state); }
vscode, manual
Use another way to determine whether the LPs owns the asset shares
Other
#0 - c4-pre-sort
2024-03-03T05:24:03Z
0xRobocop marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-03-03T05:26:20Z
0xRobocop marked the issue as duplicate of #180
#2 - c4-judge
2024-03-08T09:59:21Z
OpenCoreCH changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-03-09T10:58:21Z
OpenCoreCH marked the issue as grade-a