#35908 [SC-Low] If the collateral token''s decimal is <= the base token decimal in a market, `collat

Submitted on Oct 12th 2024 at 17:50:20 UTC by @SeveritySquad for IOP | Swaylend

  • Report ID: #35908

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/Swaylend/swaylend-monorepo/blob/9132747331188b86dd8cbf9a1ca37b811d08dddb/contracts/market/src/main.sw

  • Impacts:

    • Temporary (1 hr) freezing of funds

    • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Brief/Intro

On the line to get the scale which is later used to calculate the value of assets, in ` collateral_value_to_sell()` & `available_to_borrow()`, the operation to subtract the base decimal from the collateral decimal will cause the entire function to revert if the base decimal is greater than the collateral decimal, this will likely occur in markets where the some of the collaterals have lesser decimals than the base token of the market.

Vulnerability Details

using ` collateral_value_to_sell()` as an instance:

  • https://github.com/Swaylend/swaylend-monorepo/blob/9132747331188b86dd8cbf9a1ca37b811d08dddb/contracts/market/src/main.sw#L882C3-L888C11 ```rust let scale = u256::from(10_u64).pow( collateral_configuration .decimals - storage .market_configuration .read() .base_token_decimals, ); ``` Supposing the base decimal is greater than the collateral, this will lead to a steady revert for such collateral (In sway overflow and underflow will lead to a revert for safety).

using `available_to_borrow()` as an instance :

  • https://github.com/Swaylend/swaylend-monorepo/blob/9132747331188b86dd8cbf9a1ca37b811d08dddb/contracts/market/src/main.sw#L707C1-L712C24

```rust let scale = u256::from(10_u64).pow( collateral_configuration.decimals + price_exponent - market_configuration.base_token_decimals, );

```

This will lead to a wrong `borrow_limit` being calculated. eg.

  • if the base decimal is 8.

  • and the collateral decimal is 6.

  • due to the `price_exponet` the subtraction might not revert here but

  • the scale generated will be far large than supposed causing the `borrow_limit` to be lesser than supposed, causing a wrong amount to be used.

Impact Details

The functions are not used internally but are used by outside actors to calculate their borrows and collateral to sell for liquidators, liquidations require speed to gain profit and prevent a bad debt if time is wasted the opportunity will be taken by another liquidator and gas fees will be wasted for bots due to the revert when calculating the collateral to sell via the API, and in worst scenarios if quick liquidations are needed to avoid the market from entering a bad debt the revert will waste time causing the market to enter bad debt.

Mitigation

use an if-else block checking which is greater in order to get the right scale for the amount of value calculated and in case if where the scale is `0` there is no division needed.

Proof of Concept

Proof of Concept

To test this simply. make a simple change to the `tokens.json` file making the value of ETH less than the value of `USDC's` decimal(in the context of the tests it is the base token)

  • https://github.com/Swaylend/swaylend-monorepo/blob/9132747331188b86dd8cbf9a1ca37b811d08dddb/contracts/market/tests/tokens.json#L19 ```diff

  • "decimals": 5, ```

You can see that `liquidation::absorb_and_liquidate` will always fail via this reason. ```bash local_tests::scenarios::liquidation::absorb_and_liquidate' panicked at contracts/market/tests/local_tests/scenarios/liquidation.rs:232:10: called `Result::unwrap()` on an `Err` value: transaction reverted: ArithmeticOverflow, ```

This is just an example scenario to show that the way the scale is calculated can lead to issues. ```rust use crate::utils::{print_case_title, setup, TestData}; use chrono::Utc; use fuels::{ accounts::ViewOnlyAccount, programs::{ calls::{CallHandler, CallParameters}, responses::CallResponse, }, types::{transaction::TxPolicies, transaction_builders::VariableOutputPolicy}, }; use market::PriceDataUpdate; use market_sdk::{ convert_i256_to_i128, convert_i256_to_u64, convert_u256_to_u128, format_units_u128, is_i256_negative, parse_units, };

const AMOUNT_COEFFICIENT: u64 = 10u64.pow(0); const SCALE_6: f64 = 10u64.pow(6) as f64; const SCALE_9: f64 = 10u64.pow(9) as f64;

#[tokio::test] async fn absorb_and_liquidate() { let TestData { wallets, alice, alice_account, bob, bob_account, chad, market, assets, usdc, eth, oracle, price_feed_ids, publish_time, prices, usdc_contract, .. } = setup(None).await;

}

#[tokio::test] async fn all_assets_liquidated() { let TestData { wallets, alice, alice_account, bob, bob_account, chad, market, assets, usdc, eth, oracle, price_feed_ids, publish_time, prices, usdc_contract, .. } = setup(None).await;

}

#[tokio::test] async fn is_liquidatable_internal_uses_correct_index() { let TestData { wallets, alice, alice_account, bob, bob_account, chad, market, assets, usdc, uni, oracle, price_feed_ids, publish_time, prices, usdc_contract, uni_contract, .. } = setup(Some(100_000_000)).await;

}

```

Last updated

Was this helpful?