Open c4-bot-2 opened 7 months ago
No mitigation provided. Seems similar to #59
0xRobocop marked the issue as primary issue
0xRobocop marked the issue as sufficient quality report
0xRobocop marked the issue as high quality report
enthusiastmartin (sponsor) disputed
This is by design, math cannot be precised enough. We deliberately implemented so using this function, users gets little bit less of shares to ensure that there is no impact for protocol.
Does not break any invariant.
What we would like to see instead is that using this function, users can get same amount if shares for less liquidity.
See #59, very small rounding difference.
OpenCoreCH changed the severity to QA (Quality Assurance)
OpenCoreCH marked the issue as grade-a
Lines of code
https://github.com/code-423n4/2024-02-hydradx/blob/603187123a20e0cb8a7ea85c6a6d718429caad8d/HydraDX-node/math/src/stableswap/math.rs#L173-L230 https://github.com/code-423n4/2024-02-hydradx/blob/603187123a20e0cb8a7ea85c6a6d718429caad8d/HydraDX-node/math/src/stableswap/math.rs#L234-L314
Vulnerability details
Impact
When testing stableswap it turned out that if you use remove_liquidity_one_asset you get a little less than with withdraw_asset_amount if you use the same number of shares for both.
Proof of Concept
To prove this there is this POC that can be inserted into integration-tests/src/omnipool_init.rs.
Description
Now you can run the POC with this command
SKIP_WASM_BUILD=1 cargo test -p runtime-integration-tests stableswap_test -- --nocapture
and see how many DAI Alice gets for how many shares based on the log in the terminal. To compare the two functions you can now comment out remove_liquidity_one_asset and execute withdraw_asset_amount and compare the logs in the terminal. You will notice that with the withdraw_asset_amount function, Alice gets a little more for the same number of shares as with remove_liquidity_one_asset.This difference can be explained as follows: remove_liquidity_one_asset uses calculate_withdraw_one_asset to calculate how much of a reserve a user gets for a certain number of shares. withdraw_asset_amount uses calculate_shares_for_amount to calculate how many shares a user must pay to get a certain amount of reserve. Both functions have a part that calculates the fees:
calculate_withdraw_one_asset
calculate_shares_for_amount
Here you can see that for both functions a difference is calculated (for calculate_withdraw_one_asset it is called expected and for calculate_shares_for_amount it is called diff) which is then used to calculate the fees. The problem is that d1 is different for the two functions.
Here we calculate how many assets the pool still has after the amount for all shares that are burned has been removed. So in this case the fees have already been deducted from d1.
With calculate_shares_for_amount for amount, d1 is calculated by deducting the amount that the user gets at the end from the reserves, but the fees are not yet deducted:
Since the user gets more out of one function than the other, the same d1 should be used for both functions. So for calculate_withdraw_one_asset you should also use the d1 where the fees have not yet been deducted because they are calculated in this function.
Tools Used
VSCode
Assessed type
Math