use crate::account::Key;
use crate::trade::builder::fee::{Builder, BuilderFee, OverCap};
use crate::trade::orders::PlaceOrder;
use crate::utils::{Address, Asset, AssetKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeeRule {
ShareOfCap(u64),
Flat(BuilderFee),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuilderSetting {
address: Address,
rule: FeeRule,
}
impl BuilderSetting {
#[must_use]
pub const fn share_of_cap(address: Address, percent: u64) -> Option<BuilderSetting> {
match percent <= 100 {
true => Some(BuilderSetting { address, rule: FeeRule::ShareOfCap(percent) }),
false => None,
}
}
#[must_use]
pub const fn flat(address: Address, fee: BuilderFee) -> BuilderSetting {
BuilderSetting { address, rule: FeeRule::Flat(fee) }
}
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
#[must_use]
pub const fn rule(&self) -> FeeRule {
self.rule
}
#[must_use]
pub const fn fee_on(&self, kind: AssetKind) -> BuilderFee {
match self.rule {
FeeRule::ShareOfCap(percent) => {
BuilderFee::from_tenths(kind.builder_cap().tenths().saturating_mul(percent) / 100)
}
FeeRule::Flat(fee) => fee,
}
}
pub fn on(&self, kind: AssetKind) -> Result<Builder, OverCap> {
let fee = self.fee_on(kind);
let cap = kind.builder_cap();
match fee > cap {
true => Err(OverCap { fee, cap, kind }),
false => Ok(Builder::new(self.address, fee)),
}
}
pub fn on_assets(&self, assets: impl IntoIterator<Item = Asset>) -> Result<Option<Builder>, OverCap> {
AssetKind::strictest(assets).map(|kind| self.on(kind)).transpose()
}
#[must_use]
pub fn most(&self) -> BuilderFee {
AssetKind::ALL
.into_iter()
.filter_map(|kind| self.on(kind).ok())
.map(|builder| builder.fee())
.max()
.unwrap_or_default()
}
pub fn approval(&self, order: &PlaceOrder, approved: &Approved, by: Key) -> Result<Approval, ApprovalRefused> {
let Some(builder) = order.builder().filter(|builder| builder.fee() > BuilderFee::ZERO) else {
return Ok(Approval::Goes);
};
if builder.address() != self.address {
return Err(ApprovalRefused::OtherBuilder { named: builder.address(), set: self.address });
}
let fee = builder.fee();
let approved = match approved {
Approved::Upto(approved) if *approved >= fee => return Ok(Approval::Goes),
Approved::Upto(approved) => *approved,
Approved::Unread => return Err(ApprovalRefused::NotRead),
Approved::Failed(why) => return Err(ApprovalRefused::Unreadable(why.clone())),
};
let rate = self.most().max(fee);
match by {
Key::Main => Ok(Approval::ApproveFirst { approved, rate }),
Key::Agent => Err(ApprovalRefused::AgentCannotApprove { approved, rate }),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum Approved {
#[default]
Unread,
Upto(BuilderFee),
Failed(String),
}
impl Approved {
#[must_use]
pub fn words(&self) -> String {
match self {
Approved::Unread => "not read yet".to_owned(),
Approved::Upto(fee) if *fee == BuilderFee::ZERO => "nothing yet".to_owned(),
Approved::Upto(fee) => format!("up to {fee}"),
Approved::Failed(why) => format!("would not read: {why}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Approval {
Goes,
ApproveFirst { approved: BuilderFee, rate: BuilderFee },
}
impl Approval {
#[must_use]
pub fn max_fee_rate(&self) -> Option<String> {
match self {
Approval::Goes => None,
Approval::ApproveFirst { rate, .. } => Some(rate.percent()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ApprovalRefused {
#[error("the builder fee this account has approved is not read yet; try again in a moment")]
NotRead,
#[error("the builder fee this account has approved would not read, so it is not guessed at: {0}")]
Unreadable(String),
#[error(
"the account's main key has to approve the builder fee up to {rate} once (approved now: {approved}); an agent key cannot sign approveBuilderFee"
)]
AgentCannotApprove { approved: BuilderFee, rate: BuilderFee },
#[error("the order names the builder {named}, and what the account approved was read for {set}")]
OtherBuilder { named: Address, set: Address },
}
#[cfg(test)]
#[path = "tests/setting.rs"]
mod tests;