use serde::{Deserialize, Deserializer};
use crate::client::http::{ApiError, Call, Transport, read};
use crate::utils::ids::whole;
use crate::utils::{AccountIndex, BadUnits, Dec, Figure, LIT_DECIMALS, Round, exact_units};
pub const MOST_LEASE_UNITS: u64 = i64::MAX.unsigned_abs();
pub const MOST_LEASES: u8 = 100;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct LeaseOption {
pub duration_days: u32,
pub annual_rate: Figure,
}
impl LeaseOption {
#[must_use]
pub fn fee(&self, lease_units: u64) -> Option<u64> {
lease_fee(lease_units, &self.annual_rate, self.duration_days)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct LeaseOptions {
pub options: Vec<LeaseOption>,
#[serde(rename = "lit_incentives_account_index")]
pub fee_account: AccountIndex,
}
impl LeaseOptions {
#[must_use]
pub fn lasting(&self, days: u32) -> Option<&LeaseOption> {
self.options.iter().find(|option| option.duration_days == days)
}
}
#[must_use]
pub const fn lease_options_call() -> Call {
Call::get("leaseOptions")
}
pub async fn lease_options(transport: &impl Transport) -> Result<LeaseOptions, ApiError> {
read(transport, lease_options_call()).await
}
#[must_use]
pub fn lease_fee(lease_units: u64, annual_rate: &Figure, days: u32) -> Option<u64> {
let hundredths = annual_rate.to_dec()?.units_at(2, Round::Down)?;
let hundredths = u128::try_from(hundredths).ok()?;
let product = u128::from(lease_units).checked_mul(hundredths)?.checked_mul(u128::from(days))?;
u64::try_from(product / (360 * 10_000)).ok()
}
pub fn lease_units(lit: Dec) -> Result<u64, BadUnits> {
exact_units(lit, LIT_DECIMALS, MOST_LEASE_UNITS)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LeaseStatus {
WaitingFee,
Leased,
Expired,
Canceled,
Other(String),
}
impl LeaseStatus {
#[must_use]
pub fn from_word(word: &str) -> LeaseStatus {
match word {
"waiting_fee" => LeaseStatus::WaitingFee,
"leased" => LeaseStatus::Leased,
"expired" => LeaseStatus::Expired,
"canceled" => LeaseStatus::Canceled,
other => LeaseStatus::Other(other.to_owned()),
}
}
}
impl<'de> Deserialize<'de> for LeaseStatus {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer).map(|word| LeaseStatus::from_word(&word))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Lease {
#[serde(deserialize_with = "whole")]
pub id: u64,
pub master_account_index: AccountIndex,
#[serde(deserialize_with = "whole")]
pub lease_amount: u64,
#[serde(deserialize_with = "whole")]
pub fee_amount: u64,
#[serde(deserialize_with = "whole")]
pub start: u64,
#[serde(deserialize_with = "whole")]
pub end: u64,
pub status: LeaseStatus,
#[serde(default)]
pub error: String,
}
impl Lease {
#[must_use]
pub fn is_active(&self, now_ms: u64) -> bool {
self.status == LeaseStatus::Leased && (self.start..self.end).contains(&now_ms)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Leases {
#[serde(default)]
pub leases: Vec<Lease>,
#[serde(default)]
pub next_cursor: Option<String>,
}
#[must_use]
pub fn leases_call(account: AccountIndex, cursor: Option<&str>, limit: Option<u8>) -> Call {
Call::get("leases")
.field("account_index", account)
.maybe("cursor", cursor)
.maybe("limit", limit.map(|limit| limit.clamp(1, MOST_LEASES)))
.authed()
}
pub async fn leases(
transport: &impl Transport,
account: AccountIndex,
cursor: Option<&str>,
limit: Option<u8>,
) -> Result<Leases, ApiError> {
read(transport, leases_call(account, cursor, limit)).await
}
#[must_use]
pub fn lit_lease_call(tx_info: String, lease_units: u64, duration_days: u32) -> Call {
Call::post("litLease")
.field("tx_info", tx_info)
.field("lease_amount", lease_units)
.field("duration_days", duration_days)
}
#[cfg(test)]
#[path = "tests/credits.rs"]
mod tests;