use crate::utils::ids::AccountIndex;
use crate::utils::json::Json;
use crate::utils::sign::goldilocks::Fp;
use crate::utils::sign::tx::{BadTx, Envelope, FEE_TICK, Tx, TxType, element, number};
use crate::utils::{BadUnits, Dec, exact_units};
pub const SHARE_TICK: u16 = 10_000;
pub const INITIAL_SHARE_PRICE: Dec = Dec::lit::<3>(1);
pub const MIN_INITIAL_TOTAL_SHARES: u64 = 1_000_000;
pub const MAX_INITIAL_TOTAL_SHARES: u64 = 1_000_000_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PoolStatus {
#[default]
Active,
Frozen,
Other(u8),
}
impl PoolStatus {
#[must_use]
pub const fn code(self) -> u8 {
match self {
PoolStatus::Active => 0,
PoolStatus::Frozen => 1,
PoolStatus::Other(code) => code,
}
}
#[must_use]
pub const fn from_code(code: u8) -> PoolStatus {
match code {
0 => PoolStatus::Active,
1 => PoolStatus::Frozen,
other => PoolStatus::Other(other),
}
}
}
impl<'de> serde::Deserialize<'de> for PoolStatus {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
u8::deserialize(deserializer).map(PoolStatus::from_code)
}
}
fn rate(value: Dec, places: u32, most: u64) -> Result<u64, BadUnits> {
match value.is_zero() {
true => Ok(0),
false => exact_units(value, places, most),
}
}
pub fn operator_fee_units(fee: Dec) -> Result<u32, BadUnits> {
let units = rate(fee, 6, u64::from(FEE_TICK))?;
Ok(u32::try_from(units).unwrap_or(FEE_TICK))
}
pub fn min_operator_share_units(share: Dec) -> Result<u16, BadUnits> {
let units = rate(share, 4, u64::from(SHARE_TICK))?;
Ok(u16::try_from(units).unwrap_or(SHARE_TICK))
}
fn check_fee(fee: u32) -> Result<(), BadTx> {
match fee > FEE_TICK {
true => Err(BadTx::new("OperatorFee", "an operator fee is at most 1,000,000, all of the profit")),
false => Ok(()),
}
}
fn check_share_rate(rate: u16) -> Result<(), BadTx> {
match rate > SHARE_TICK {
true => Err(BadTx::new("MinOperatorShareRate", "a minimum operator share is at most 10,000, all of the pool")),
false => Ok(()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreatePublicPool {
pub operator_fee: u32,
pub initial_total_shares: u64,
pub min_operator_share_rate: u16,
}
impl CreatePublicPool {
pub fn new(operator_fee: Dec, initial: Dec, min_operator_share: Dec) -> Result<CreatePublicPool, BadUnits> {
Ok(CreatePublicPool {
operator_fee: operator_fee_units(operator_fee)?,
initial_total_shares: exact_units(initial, 3, MAX_INITIAL_TOTAL_SHARES)?,
min_operator_share_rate: min_operator_share_units(min_operator_share)?,
})
}
}
impl Tx for CreatePublicPool {
fn tx_type(&self) -> TxType {
TxType::CreatePublicPool
}
fn check_signing(&self, _chain_id: u32, envelope: &Envelope) -> Result<(), BadTx> {
match envelope.account.is_master() {
true => Ok(()),
false => Err(BadTx::new("AccountIndex", "a public pool is created by a master account")),
}
}
fn hash_fields(&self) -> Vec<Fp> {
vec![element(self.operator_fee), element(self.initial_total_shares), element(self.min_operator_share_rate)]
}
fn info_fields(&self) -> Vec<(&'static str, Json)> {
vec![
("OperatorFee", number(self.operator_fee)),
("InitialTotalShares", number(self.initial_total_shares)),
("MinOperatorShareRate", number(self.min_operator_share_rate)),
]
}
fn check(&self) -> Result<(), BadTx> {
check_fee(self.operator_fee)?;
if self.initial_total_shares == 0 {
return Err(BadTx::new("InitialTotalShares", "a pool opens with at least one share"));
}
if self.initial_total_shares > MAX_INITIAL_TOTAL_SHARES {
return Err(BadTx::new("InitialTotalShares", "a pool opens with at most 10^12 shares"));
}
check_share_rate(self.min_operator_share_rate)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UpdatePublicPool {
pub pool: AccountIndex,
pub status: PoolStatus,
pub operator_fee: u32,
pub min_operator_share_rate: u16,
}
impl UpdatePublicPool {
pub fn new(
pool: AccountIndex,
status: PoolStatus,
operator_fee: Dec,
min_operator_share: Dec,
) -> Result<UpdatePublicPool, BadUnits> {
Ok(UpdatePublicPool {
pool,
status,
operator_fee: operator_fee_units(operator_fee)?,
min_operator_share_rate: min_operator_share_units(min_operator_share)?,
})
}
}
impl Tx for UpdatePublicPool {
fn tx_type(&self) -> TxType {
TxType::UpdatePublicPool
}
fn hash_fields(&self) -> Vec<Fp> {
vec![
element(self.pool.get()),
element(self.status.code()),
element(self.operator_fee),
element(self.min_operator_share_rate),
]
}
fn info_fields(&self) -> Vec<(&'static str, Json)> {
vec![
("PublicPoolIndex", number(self.pool.get())),
("Status", number(self.status.code())),
("OperatorFee", number(self.operator_fee)),
("MinOperatorShareRate", number(self.min_operator_share_rate)),
]
}
fn check(&self) -> Result<(), BadTx> {
if !matches!(self.status, PoolStatus::Active | PoolStatus::Frozen) {
return Err(BadTx::new("Status", "a pool is active (0) or frozen (1)"));
}
check_fee(self.operator_fee)?;
check_share_rate(self.min_operator_share_rate)
}
}
#[cfg(test)]
#[path = "tests/operator.rs"]
mod tests;