#35760 [SC-Low] `market::available_to_borrow()` compares the collateral in USD against the borrow in base units

Submitted on Oct 6th 2024 at 23:12:49 UTC by @SimaoAmaro for IOP | Swaylend

  • Report ID: #35760

  • Report Type: Smart Contract

  • Report severity: Low

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

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

`market::available_to_borrow()` returns the available amount to borrow by comparing the collateral amount in USD with the already borrowed amount.

However, the borrowed amount is not multiplied by the base price to get the value in USD, similarly to the `market::is_borrow_collateralized()`, so it ends up comparing a collateral amount in USD with a borrow amount in base units.

This leads to an over/underestimation of the amount available to borrow if the base price is bigger/smaller than 1 USD, respectively.

Vulnerability Details

`market::available_to_borrow()` gets the collateral amounts and multiplies each by the price, getting USD units. The borrowed amount is fetched from market::get_user_supply_borrow_internal()`, which returns an amount in base units, not USD.

Thus, in the end it compares `borrow_limit` in USD with `borrow` in base units.

Impact Details

Whenever the base asset price is different than 1 USD, the available base to borrow will return more or less than it should.

References

https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/market/src/main.sw?utm_source=immunefi#L544 https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/market/src/main.sw?utm_source=immunefi#L574 https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/market/src/main.sw?utm_source=immunefi#L578-L583

Proof of Concept

Proof of Concept

Add the following diffs and run `cargo test main_test_no_debug --release -- --nocapture`. The base asset (USDC) was modified to be worth 2 USD. Alice supplies 40 UNI, being worth 200 USD and is able to borrow initially 100 USD (50 USDC, 2 USD each, the collateral factor is 0.5). Then, Alice withdraws 50 USDC, which is 100 USD. When available to borrow is called again, it returns that alice can borrow 50 USD: however, she has already borrowed 100 USD, the maximum, so it should return 0 instead. ```diff diff --git a/contracts/market/tests/tokens.json b/contracts/market/tests/tokens.json index 1b581b0..ba04e2f 100644 --- a/contracts/market/tests/tokens.json +++ b/contracts/market/tests/tokens.json @@ -5,7 +5,7 @@ "price_feed_decimals": 7, "name": "USD Coin", "symbol": "USDC",

  • "default_price": 1,

  • "default_price": 2, "decimals": 6, "mint_amount": 10000 },

diff --git a/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs b/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs index 6976dc8..54ecf23 100644 --- a/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs +++ b/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs @@ -129,6 +129,10 @@ async fn main_test_no_debug() { let log_amount = format!("{} USDC", amount as f64 / scale_6); print_case_title(2, "Alice", "withdraw_base", log_amount.as_str());

  • let available_to_borrow = market.available_to_borrow(&[&oracle.instance], alice_account).await.unwrap();

  • let log_amount = format!("{} USDC", available_to_borrow as f64 / scale_6);

  • print_case_title(3, "Alice", "available to borrow initial", log_amount.as_str());

  • // Alice calls withdraw_base market .with_account(&alice) @@ -142,6 +146,10 @@ async fn main_test_no_debug() { let balance = alice.get_asset_balance(&usdc.asset_id).await.unwrap(); assert!(balance == amount);

  • let available_to_borrow = market.available_to_borrow(&[&oracle.instance], alice_account).await.unwrap();

  • let log_amount = format!("{} USDC", available_to_borrow as f64 / scale_6);

  • print_case_title(4, "Alice", "available to borrow final", log_amount.as_str());

  • market .print_debug_state(&wallets, &usdc, &uni) ``.