Skip to content
This repository has been archived by the owner on Oct 16, 2023. It is now read-only.

Borrow & round up division #70

Merged
merged 1 commit into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions contracts/credit-manager/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use cw721::OwnerOfResponse;
use cw721_base::QueryMsg;

use mars_rover::error::{ContractError, ContractResult};
use mars_rover::math::CeilRatio;
use mars_rover::msg::execute::CallbackMsg;
use mars_rover::msg::query::CoinValue;
use mars_rover::msg::ExecuteMsg;
Expand Down Expand Up @@ -136,10 +137,8 @@ pub fn debt_shares_to_amount(
let red_bank = RED_BANK.load(deps.storage)?;
let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?;

// amount of debt for token's position
// NOTE: Given the nature of integers, the debt is rounded down. This means that the
// remaining share owners will take a small hit of the remainder.
let amount = total_debt_amount.checked_multiply_ratio(shares, total_debt_shares)?;
// Amount of debt for token's position. Rounded up to favor participants in the debt pool.
let amount = total_debt_amount.multiply_ratio_ceil(shares, total_debt_shares)?;

Ok(Coin {
denom: denom.to_string(),
Expand Down
4 changes: 3 additions & 1 deletion contracts/credit-manager/tests/test_health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128};
use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED;
use mars_mock_oracle::msg::CoinPrice;
use mars_rover::error::ContractError;
use mars_rover::math::CeilRatio;
use mars_rover::msg::execute::Action::{Borrow, Deposit};
use mars_rover::msg::instantiate::ConfigUpdates;
use mars_rover::msg::query::DebtAmount;
Expand Down Expand Up @@ -575,7 +576,8 @@ fn test_debt_value() {

let user_a_owed_atom = red_bank_atom_debt
.amount
.multiply_ratio(user_a_debt_shares_atom, red_bank_atom_res.shares);
.multiply_ratio_ceil(user_a_debt_shares_atom, red_bank_atom_res.shares)
.unwrap();
let user_a_owed_atom_value = uatom_info.price * user_a_owed_atom.to_dec().unwrap();

let osmo_borrowed_amount_dec = (user_a_borrowed_amount_osmo + Uint128::one())
Expand Down
2 changes: 1 addition & 1 deletion contracts/credit-manager/tests/test_liquidate_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ fn test_liquidator_left_in_unhealthy_state() {
res,
AboveMaxLTV {
account_id: liquidator_account_id,
max_ltv_health_factor: "0.805".to_string(),
max_ltv_health_factor: "0.731818181818181818".to_string(),
},
)
}
Expand Down
1 change: 1 addition & 0 deletions packages/rover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod adapters;
pub mod coins;
pub mod error;
pub mod extensions;
pub mod math;
pub mod msg;
pub mod traits;
43 changes: 43 additions & 0 deletions packages/rover/src/math/ceil_ratio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use cosmwasm_std::{CheckedMultiplyRatioError, Uint128, Uint256};

pub trait CeilRatio {
fn multiply_ratio_ceil(
&self,
numerator: Uint128,
denominator: Uint128,
) -> Result<Uint128, CheckedMultiplyRatioError>;
}

impl CeilRatio for Uint128 {
/// Using `checked_multiply_ratio()` results in a rounding down due to the nature of integer math.
/// This function performs the same math, but rounds up. The is particularly useful in ensuring
/// safety in certain situations (e.g. calculating what an account owes)
fn multiply_ratio_ceil(
&self,
numerator: Uint128,
denominator: Uint128,
) -> Result<Uint128, CheckedMultiplyRatioError> {
// Perform the normal multiply ratio.
// Converts to Uint256 to reduce likeliness of overflow errors
let new_numerator = self.full_mul(numerator);
let denom_256 = Uint256::from(denominator);
let mut result = new_numerator
.checked_div(denom_256)
.map_err(|_| CheckedMultiplyRatioError::DivideByZero)?;

// Check if there's a remainder with that same division.
// If so, round up (by adding one).
if !new_numerator
.checked_rem(denom_256)
.map_err(|_| CheckedMultiplyRatioError::DivideByZero)?
.is_zero()
{
result += Uint256::one();
}

match result.try_into() {
Ok(ratio) => Ok(ratio),
Err(_) => Err(CheckedMultiplyRatioError::Overflow),
}
}
}
3 changes: 3 additions & 0 deletions packages/rover/src/math/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod ceil_ratio;

pub use ceil_ratio::*;
54 changes: 54 additions & 0 deletions packages/rover/tests/test_ceil_ratio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use cosmwasm_std::{CheckedMultiplyRatioError, Uint128};
use mars_rover::math::CeilRatio;

const MAX_UINT128_SIZE: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455;

#[test]
fn test_divide_by_zero() {
let err = Uint128::new(123)
.multiply_ratio_ceil(Uint128::new(12), Uint128::zero())
.unwrap_err();
assert_eq!(err, CheckedMultiplyRatioError::DivideByZero)
}

#[test]
fn test_result_exceeds_128_bit_capacity() {
let err = Uint128::new(MAX_UINT128_SIZE)
.multiply_ratio_ceil(Uint128::new(2), Uint128::new(1))
.unwrap_err();
assert_eq!(err, CheckedMultiplyRatioError::Overflow)
}

#[test]
fn test_works_with_zero() {
let res = Uint128::zero()
.multiply_ratio_ceil(Uint128::new(1), Uint128::new(10))
.unwrap();
assert_eq!(res, Uint128::zero())
}

#[test]
fn test_works_with_one() {
let res = Uint128::one()
.multiply_ratio_ceil(Uint128::new(1), Uint128::new(10))
.unwrap();
assert_eq!(res, Uint128::one())
}

#[test]
fn test_not_increment_if_divides_cleanly() {
// 56088 / 123 = 456
let res = Uint128::new(56088)
.multiply_ratio_ceil(Uint128::new(1), Uint128::new(123))
.unwrap();
assert_eq!(res, Uint128::new(456))
}

#[test]
fn test_rounds_up() {
// 56000 / 123 = 455.28455284
let res = Uint128::new(56000)
.multiply_ratio_ceil(Uint128::new(1), Uint128::new(123))
.unwrap();
assert_eq!(res, Uint128::new(456))
}