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

Commit

Permalink
Borrow & round up division (#70)
Browse files Browse the repository at this point in the history
ceil ratio
  • Loading branch information
grod220 authored Dec 13, 2022
1 parent af63f65 commit 7506d1c
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 12 deletions.
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))
}

0 comments on commit 7506d1c

Please sign in to comment.