#![recursion_limit = "128"]
#[macro_use]
extern crate tower_web;
use bytes::Bytes;
use futures::Future;
use hyper::StatusCode;
use interledger_packet::Address;
use interledger_service::Account;
use lazy_static::lazy_static;
use std::str::FromStr;
use url::Url;
mod api;
mod client;
#[cfg(test)]
mod fixtures;
mod message_service;
#[cfg(test)]
mod test_helpers;
use num_bigint::BigUint;
use std::ops::{Div, Mul};
pub use api::{scale_with_precision_loss, SettlementApi};
pub use client::SettlementClient;
pub use message_service::SettlementMessageService;
lazy_static! {
pub static ref SE_ILP_ADDRESS: Address = Address::from_str("peer.settle").unwrap();
}
#[derive(Extract, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct Quantity {
pub amount: String,
pub scale: u8,
}
impl Quantity {
pub fn new(amount: impl ToString, scale: u8) -> Self {
Quantity {
amount: amount.to_string(),
scale,
}
}
}
pub struct SettlementEngineDetails {
pub url: Url,
}
pub trait SettlementAccount: Account {
fn settlement_engine_details(&self) -> Option<SettlementEngineDetails> {
None
}
}
pub trait SettlementStore {
type Account: SettlementAccount;
fn update_balance_for_incoming_settlement(
&self,
account_id: <Self::Account as Account>::AccountId,
amount: u64,
idempotency_key: Option<String>,
) -> Box<dyn Future<Item = (), Error = ()> + Send>;
fn refund_settlement(
&self,
account_id: <Self::Account as Account>::AccountId,
settle_amount: u64,
) -> Box<dyn Future<Item = (), Error = ()> + Send>;
}
pub type IdempotentData = (StatusCode, Bytes, [u8; 32]);
pub trait IdempotentStore {
fn load_idempotent_data(
&self,
idempotency_key: String,
) -> Box<dyn Future<Item = Option<IdempotentData>, Error = ()> + Send>;
fn save_idempotent_data(
&self,
idempotency_key: String,
input_hash: [u8; 32],
status_code: StatusCode,
data: Bytes,
) -> Box<dyn Future<Item = (), Error = ()> + Send>;
}
pub trait LeftoversStore {
type AccountId;
type AssetType: ToString;
fn save_uncredited_settlement_amount(
&self,
account_id: Self::AccountId,
uncredited_settlement_amount: (Self::AssetType, u8),
) -> Box<dyn Future<Item = (), Error = ()> + Send>;
fn load_uncredited_settlement_amount(
&self,
account_id: Self::AccountId,
local_scale: u8,
) -> Box<dyn Future<Item = Self::AssetType, Error = ()> + Send>;
fn get_uncredited_settlement_amount(
&self,
account_id: Self::AccountId,
) -> Box<dyn Future<Item = (Self::AssetType, u8), Error = ()> + Send>;
}
#[derive(Debug)]
pub struct ConvertDetails {
pub from: u8,
pub to: u8,
}
pub trait Convert {
type Item: Sized;
fn normalize_scale(&self, details: ConvertDetails) -> Result<Self::Item, ()>;
}
impl Convert for u64 {
type Item = u64;
fn normalize_scale(&self, details: ConvertDetails) -> Result<Self::Item, ()> {
let scale_diff = (details.from as i8 - details.to as i8).abs() as u8;
let scale = 10u64.pow(scale_diff.into());
let (res, overflow) = if details.to >= details.from {
self.overflowing_mul(scale)
} else {
self.overflowing_div(scale)
};
if overflow {
Err(())
} else {
Ok(res)
}
}
}
impl Convert for f64 {
type Item = f64;
fn normalize_scale(&self, details: ConvertDetails) -> Result<Self::Item, ()> {
let scale_diff = (details.from as i8 - details.to as i8).abs() as u8;
let scale = 10f64.powi(scale_diff.into());
let res = if details.to >= details.from {
self * scale
} else {
self / scale
};
if res == std::f64::INFINITY {
Err(())
} else {
Ok(res)
}
}
}
impl Convert for BigUint {
type Item = BigUint;
fn normalize_scale(&self, details: ConvertDetails) -> Result<Self::Item, ()> {
let scale_diff = (details.from as i8 - details.to as i8).abs() as u8;
let scale = 10u64.pow(scale_diff.into());
if details.to >= details.from {
Ok(self.mul(scale))
} else {
Ok(self.div(scale))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::{Convert, ConvertDetails};
use num_traits::cast::FromPrimitive;
#[test]
fn biguint_test() {
let hundred_gwei = BigUint::from_str("100000000000").unwrap();
assert_eq!(
hundred_gwei
.normalize_scale(ConvertDetails { from: 18, to: 9 })
.unwrap()
.to_string(),
BigUint::from_u64(100u64).unwrap().to_string(),
);
}
#[test]
fn u64_test() {
let huge_number = std::u64::MAX / 10;
assert_eq!(
huge_number
.normalize_scale(ConvertDetails { from: 1, to: 18 })
.unwrap_err(),
(),
);
assert_eq!(
1u64.normalize_scale(ConvertDetails { from: 1, to: 1 })
.unwrap(),
1
);
assert_eq!(
1u64.normalize_scale(ConvertDetails { from: 2, to: 1 })
.unwrap(),
0
);
assert_eq!(
1u64.normalize_scale(ConvertDetails { from: 1, to: 2 })
.unwrap(),
10
);
assert_eq!(
1u64.normalize_scale(ConvertDetails { from: 9, to: 18 })
.unwrap(),
1_000_000_000
);
assert_eq!(
1_000_000_000u64
.normalize_scale(ConvertDetails { from: 18, to: 9 })
.unwrap(),
1,
);
assert_eq!(
10u64
.normalize_scale(ConvertDetails { from: 2, to: 3 })
.unwrap(),
100
);
assert_eq!(
299u64
.normalize_scale(ConvertDetails { from: 3, to: 2 })
.unwrap(),
29
);
assert_eq!(
999u64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
0
);
assert_eq!(
1000u64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
1
);
assert_eq!(
1999u64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
1
);
}
#[allow(clippy::float_cmp)]
#[test]
fn f64_test() {
assert_eq!(
std::f64::MAX
.normalize_scale(ConvertDetails {
from: 1,
to: std::u8::MAX,
})
.unwrap_err(),
()
);
assert_eq!(
1f64.normalize_scale(ConvertDetails { from: 1, to: 1 })
.unwrap(),
1.0
);
assert_eq!(
1f64.normalize_scale(ConvertDetails { from: 1, to: 2 })
.unwrap(),
10.0
);
assert_eq!(
1f64.normalize_scale(ConvertDetails { from: 9, to: 18 })
.unwrap(),
1_000_000_000.0
);
assert_eq!(
1f64.normalize_scale(ConvertDetails { from: 2, to: 1 })
.unwrap(),
0.1
);
assert_eq!(
10.5f64
.normalize_scale(ConvertDetails { from: 2, to: 1 })
.unwrap(),
1.05
);
assert_eq!(
100f64
.normalize_scale(ConvertDetails { from: 3, to: 2 })
.unwrap(),
10.0
);
assert_eq!(
299f64
.normalize_scale(ConvertDetails { from: 3, to: 2 })
.unwrap(),
29.9
);
assert_eq!(
999f64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
0.999
);
assert_eq!(
1000f64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
1.0
);
assert_eq!(
1999f64
.normalize_scale(ConvertDetails { from: 9, to: 6 })
.unwrap(),
1.999
);
}
}