use crate::client::Network;
use crate::utils::AccountIndex;
use crate::utils::json::Json;
use crate::utils::sign::goldilocks::Fp;
use crate::utils::sign::tx::{BadTx, Tx, TxType, element, number};
#[cfg(feature = "sign")]
use {
crate::account::keys::ApiKey,
crate::client::{Client, Http, SendError, Sent},
crate::earn::pools::reads::{PoolFilter, public_pool, shares_in},
crate::utils::Dec,
crate::utils::sign::SignTxError,
};
pub const MOST_SHARES: u64 = (1 << 60) - 1;
#[must_use]
pub const fn llp_cooldown_ms(network: Network) -> u64 {
match network {
Network::Lighter => 300_000,
Network::Robinhood => 0,
}
}
pub(crate) fn check_pool_shares(pool_field: &'static str, pool: AccountIndex, shares: u64) -> Result<(), BadTx> {
if pool.is_master() {
return Err(BadTx::new(pool_field, "a pool's index is 2^47 or above, among the sub-accounts"));
}
if shares == 0 || shares > MOST_SHARES {
return Err(BadTx::new("ShareAmount", "a share amount is 1 to 2^60 - 1"));
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MintShares {
pub pool: AccountIndex,
pub shares: u64,
}
impl MintShares {
#[must_use]
pub const fn new(pool: AccountIndex, shares: u64) -> MintShares {
MintShares { pool, shares }
}
}
impl Tx for MintShares {
fn tx_type(&self) -> TxType {
TxType::MintShares
}
fn hash_fields(&self) -> Vec<Fp> {
vec![element(self.pool.get()), element(self.shares)]
}
fn info_fields(&self) -> Vec<(&'static str, Json)> {
vec![("PublicPoolIndex", number(self.pool.get())), ("ShareAmount", number(self.shares))]
}
fn check(&self) -> Result<(), BadTx> {
check_pool_shares("PublicPoolIndex", self.pool, self.shares)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BurnShares {
pub pool: AccountIndex,
pub shares: u64,
}
impl BurnShares {
#[must_use]
pub const fn new(pool: AccountIndex, shares: u64) -> BurnShares {
BurnShares { pool, shares }
}
}
impl Tx for BurnShares {
fn tx_type(&self) -> TxType {
TxType::BurnShares
}
fn hash_fields(&self) -> Vec<Fp> {
vec![element(self.pool.get()), element(self.shares)]
}
fn info_fields(&self) -> Vec<(&'static str, Json)> {
vec![("PublicPoolIndex", number(self.pool.get())), ("ShareAmount", number(self.shares))]
}
fn check(&self) -> Result<(), BadTx> {
check_pool_shares("PublicPoolIndex", self.pool, self.shares)
}
}
#[cfg(feature = "sign")]
fn refused(field: &'static str, why: impl Into<String>) -> SendError {
SendError::Sign(SignTxError::Bad(BadTx::new(field, why)))
}
#[cfg(feature = "sign")]
pub async fn deposit<H: Http>(
client: &Client<H, ApiKey>,
pool: AccountIndex,
collateral: Dec,
) -> Result<Sent, SendError> {
let Some(listed) = public_pool(client, pool, PoolFilter::Any).await? else {
return Err(refused("PublicPoolIndex", format!("{pool} is no public pool")));
};
if !listed.takes_deposits() {
return Err(refused("PublicPoolIndex", format!("pool {pool} is frozen: it only pays out")));
}
match listed.shares_for(collateral) {
Some(shares) if shares > 0 => client.send(&MintShares::new(pool, shares)).await,
_ => Err(refused("ShareAmount", format!("{collateral} buys no whole share of pool {pool}"))),
}
}
#[cfg(feature = "sign")]
pub async fn withdraw<H: Http>(client: &Client<H, ApiKey>, pool: AccountIndex, shares: u64) -> Result<Sent, SendError> {
client.send(&BurnShares::new(pool, shares)).await
}
#[cfg(feature = "sign")]
pub async fn withdraw_all<H: Http>(client: &Client<H, ApiKey>, pool: AccountIndex) -> Result<Sent, SendError> {
match shares_in(client, client.key().account(), pool).await? {
0 => Err(refused("ShareAmount", format!("the account holds no shares of pool {pool}"))),
shares => withdraw(client, pool, shares).await,
}
}
#[cfg(test)]
#[path = "tests/shares.rs"]
mod tests;