use andromeda_std::{
amp::recipient::Recipient, andr_exec, andr_instantiate, andr_query, error::ContractError,
};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{ensure, Coin, Decimal, Fraction, QuerierWrapper};
#[andr_instantiate]
#[cw_serde]
pub struct InstantiateMsg {
pub rates: Vec<RateInfo>,
}
#[andr_exec]
#[cw_serde]
pub enum ExecuteMsg {
UpdateRates { rates: Vec<RateInfo> },
}
#[andr_query]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(PaymentsResponse)]
Payments {},
}
#[cw_serde]
pub struct PaymentsResponse {
pub payments: Vec<RateInfo>,
}
#[cw_serde]
pub struct RateInfo {
pub rate: Rate,
pub is_additive: bool,
pub description: Option<String>,
pub recipients: Vec<Recipient>,
}
#[cw_serde]
pub enum Rate {
Flat(Coin),
Percent(PercentRate),
}
#[cw_serde] pub struct PercentRate {
pub percent: Decimal,
}
impl From<Decimal> for Rate {
fn from(decimal: Decimal) -> Self {
Rate::Percent(PercentRate { percent: decimal })
}
}
impl Rate {
pub fn is_non_zero(&self) -> Result<bool, ContractError> {
match self {
Rate::Flat(coin) => Ok(!coin.amount.is_zero()),
Rate::Percent(PercentRate { percent }) => Ok(!percent.is_zero()),
}
}
pub fn validate(&self, querier: &QuerierWrapper) -> Result<Rate, ContractError> {
let rate = self.clone().get_rate(querier)?;
ensure!(rate.is_non_zero()?, ContractError::InvalidRate {});
if let Rate::Percent(PercentRate { percent }) = rate {
ensure!(percent <= Decimal::one(), ContractError::InvalidRate {});
}
Ok(rate)
}
fn get_rate(self, _querier: &QuerierWrapper) -> Result<Rate, ContractError> {
match self {
Rate::Flat(_) => Ok(self),
Rate::Percent(_) => Ok(self),
}
}
}
pub struct PaymentAttribute {
pub amount: Coin,
pub receiver: String,
}
impl ToString for PaymentAttribute {
fn to_string(&self) -> String {
format!("{}<{}", self.receiver, self.amount)
}
}
pub fn calculate_fee(fee_rate: Rate, payment: &Coin) -> Result<Coin, ContractError> {
match fee_rate {
Rate::Flat(rate) => Ok(Coin::new(rate.amount.u128(), rate.denom)),
Rate::Percent(PercentRate { percent }) => {
ensure!(
percent <= Decimal::one() && !percent.is_zero(),
ContractError::InvalidRate {}
);
let mut fee_amount = payment.amount * percent;
let reversed_fee = fee_amount * percent.inv().unwrap();
if payment.amount > reversed_fee {
fee_amount = fee_amount.checked_add(1u128.into())?;
}
Ok(Coin::new(fee_amount.u128(), payment.denom.clone()))
} }
}
#[cfg(test)]
mod tests {
use cosmwasm_std::{coin, Uint128};
use super::*;
#[test]
fn test_calculate_fee() {
let payment = coin(101, "uluna");
let expected = Ok(coin(5, "uluna"));
let fee = Rate::from(Decimal::percent(4));
let received = calculate_fee(fee, &payment);
assert_eq!(expected, received);
assert_eq!(expected, received);
let payment = coin(125, "uluna");
let fee = Rate::Flat(Coin {
amount: Uint128::from(5_u128),
denom: "uluna".to_string(),
});
let received = calculate_fee(fee, &payment);
assert_eq!(expected, received);
}
}