Skip to content

Commit

Permalink
fix: conversion should not overflow (#25)
Browse files Browse the repository at this point in the history
Co-authored-by: MantisClone <[email protected]>
  • Loading branch information
yomarion and MantisClone authored Sep 29, 2023
1 parent 9014387 commit b83599b
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 11 deletions.
38 changes: 31 additions & 7 deletions conversion_proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,35 @@ impl ConversionProxy {
return 0_u128;
}

/// Recursive util to convert an `amount` with a `converion_rate` having `decimals`.
/// The `precision` is used to work around overflows, a precision of 10^n means that the result has a precision of n yocto digits.
/// Said another way, a precision of 1000 will give a result rounded to the closest 1000 yoctos, ending with "...000" in yocto.
#[private]
pub fn apply_conversion_with_precision(
amount: U128,
decimals: u32,
conversion_rate: u128,
precision: u128,
) -> u128 {
let (main_payment, flag) = (Balance::from(amount) * ONE_NEAR / ONE_FIAT / precision)
.overflowing_mul(10u128.pow(u32::from(decimals)));
if flag {
return Self::apply_conversion_with_precision(
amount,
decimals,
conversion_rate,
precision * 10,
);
}
let main_payment = (main_payment / conversion_rate) * precision;
return main_payment;
}

#[private]
pub fn apply_conversion(amount: U128, decimals: u32, conversion_rate: u128) -> u128 {
return Self::apply_conversion_with_precision(amount, decimals, conversion_rate, 1);
}

#[private]
#[payable]
pub fn rate_callback(
Expand Down Expand Up @@ -363,13 +392,8 @@ impl ConversionProxy {
let conversion_rate = 0_u128
.checked_add_signed(rate.result.mantissa)
.expect("The conversion rate should be positive");
let decimals = u32::from(rate.result.scale);
let main_payment = (Balance::from(amount) * ONE_NEAR * 10u128.pow(decimals)
/ conversion_rate
/ ONE_FIAT) as u128;
let fee_payment = Balance::from(fee_amount) * ONE_NEAR * 10u128.pow(decimals)
/ conversion_rate
/ ONE_FIAT;
let main_payment = Self::apply_conversion(amount, rate.result.scale, conversion_rate);
let fee_payment = Self::apply_conversion(fee_amount, rate.result.scale, conversion_rate);

let total_payment = main_payment + fee_payment;
// Check deposit
Expand Down
8 changes: 4 additions & 4 deletions mocks/src/switchboard_feed_parser_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ impl SwitchboardFeedParser {
match ix.address {
VALID_FEED_ADDRESS => Some(PriceEntry {
result: SwitchboardDecimal {
mantissa: i128::from(1234000),
scale: u8::from(6).into(),
mantissa: i128::from(1234000000),
scale: u8::from(9).into(),
},
num_success: 1,
num_error: 0,
Expand Down Expand Up @@ -106,8 +106,8 @@ mod tests {
address: [0; 32],
payer: [1; 32],
}) {
assert_eq!(result.result.mantissa, i128::from(1234000));
assert_eq!(result.result.scale, 6);
assert_eq!(result.result.mantissa, i128::from(1234000000));
assert_eq!(result.result.scale, 9);
} else {
panic!("NEAR/USD mock returned None")
}
Expand Down
59 changes: 59 additions & 0 deletions tests/sim/conversion_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,65 @@ fn test_transfer_with_low_deposit() {
);
}

#[test]
fn test_transfer_high_amounts() {
let (alice, bob, builder, proxy, root) = init();
let transfer_amount = to_yocto("20000000");
// This high amount require a balance greater than the default one
root.transfer(alice.account_id(), transfer_amount);
let initial_alice_balance = alice.account().unwrap().amount;
let initial_bob_balance = bob.account().unwrap().amount;
let initial_builder_balance = builder.account().unwrap().amount;
let payment_address = bob.account_id().try_into().unwrap();
let fee_address = builder.account_id().try_into().unwrap();

let result = call!(
alice,
proxy.transfer_with_reference(
PAYMENT_REF.into(),
payment_address,
// 1'200'00.00 USD (main)
U128::from(120000000),
USD.into(),
fee_address,
// 1.00 USD (fee)
U128::from(100),
U64::from(0)
),
deposit = transfer_amount
);
result.assert_success();
println!("{:?}", result);

let alice_balance = alice.account().unwrap().amount;
assert!(alice_balance < initial_alice_balance);
let spent_amount = initial_alice_balance - alice_balance;
// 1'200'001.00 USD worth of NEAR / 1.234
let expected_spent = to_yocto("1200001") * 1000 / 1234;
assert!(
yocto_almost_eq(spent_amount, expected_spent),
"\nSpent: {spent_amount} \nExpected: {expected_spent} : Alice should have spent 1'200'000 + 1 USD worth of NEAR.",
);

assert!(bob.account().unwrap().amount > initial_bob_balance);
let received_amount = bob.account().unwrap().amount - initial_bob_balance;
assert_eq!(
received_amount,
// 1'200'000 USD / rate mocked
to_yocto("1200000") * 1000 / 1234,
"Bob should receive exactly 1'200'000 USD worth of NEAR."
);

assert!(builder.account().unwrap().amount > initial_builder_balance);
let received_amount = builder.account().unwrap().amount - initial_builder_balance;
assert_eq!(
received_amount,
// 1 USD / rate mocked
to_yocto("1") * 1000 / 1234,
"Builder should receive exactly 1 USD worth of NEAR"
);
}

#[test]
fn test_transfer_with_wrong_feed_address() {
let (alice, bob, builder, proxy, root) = init();
Expand Down

0 comments on commit b83599b

Please sign in to comment.