abstract_std/objects/
fee.rsuse cosmwasm_std::{Addr, Coin, CosmosMsg, Decimal, MessageInfo, Uint128};
use cw_asset::Asset;
use crate::{error::AbstractError, AbstractResult};
#[cosmwasm_schema::cw_serde]
pub struct UsageFee {
fee: Fee,
recipient: Addr,
}
impl UsageFee {
pub fn new(share: Decimal, recipient: Addr) -> AbstractResult<Self> {
let fee = Fee::new(share)?;
Ok(Self { fee, recipient })
}
pub fn set_share(&mut self, share: Decimal) -> AbstractResult<()> {
self.fee = Fee::new(share)?;
Ok(())
}
pub fn share(&self) -> Decimal {
self.fee.share()
}
pub fn compute(&self, amount: Uint128) -> Uint128 {
amount.mul_floor(self.share())
}
pub fn recipient(&self) -> Addr {
self.recipient.clone()
}
pub fn set_recipient(&mut self, recipient: Addr) {
self.recipient = recipient;
}
}
#[cosmwasm_schema::cw_serde]
#[derive(Copy)]
pub struct Fee {
share: Decimal,
}
impl Fee {
pub fn new(share: Decimal) -> AbstractResult<Self> {
if share >= Decimal::percent(100) {
return Err(AbstractError::Fee(
"fee share must be lesser than 100%".to_string(),
));
}
Ok(Fee { share })
}
pub fn compute(&self, amount: Uint128) -> Uint128 {
amount.mul_floor(self.share)
}
pub fn msg(&self, asset: Asset, recipient: Addr) -> AbstractResult<CosmosMsg> {
asset.transfer_msg(recipient).map_err(Into::into)
}
pub fn share(&self) -> Decimal {
self.share
}
}
#[cosmwasm_schema::cw_serde]
pub struct FixedFee {
fee: Coin,
}
impl FixedFee {
pub fn new(fee: &Coin) -> Self {
FixedFee { fee: fee.clone() }
}
pub fn quantity(mut self, qty: u128) -> Self {
self.fee.amount *= Uint128::from(qty);
self
}
pub fn assert_payment(self, msg_info: &MessageInfo) -> AbstractResult<Coin> {
if self.fee.amount.is_zero() {
return Ok(self.fee);
}
if msg_info.funds.len() != 1
|| msg_info.funds[0].denom != self.fee.denom
|| self.fee.amount != msg_info.funds[0].amount
{
return Err(AbstractError::Fee(format!(
"Invalid fee payment sent. Expected {}, sent {:?}",
self.fee, msg_info.funds
)));
}
Ok(self.fee)
}
pub fn charge(self, msg_info: &mut MessageInfo) -> AbstractResult<Coin> {
if self.fee.amount.is_zero() {
return Ok(self.fee);
}
let original_funds = msg_info.funds.clone();
let funds_to_use = msg_info
.funds
.iter_mut()
.find(|f| f.denom == self.fee.denom)
.ok_or(AbstractError::Fee(format!(
"Invalid fee payment sent. Expected {}, sent {:?}",
self.fee, original_funds
)))?;
if funds_to_use.amount < self.fee.amount {
return Err(AbstractError::Fee(format!(
"Invalid fee payment sent. Expected {}, sent {:?}",
self.fee, original_funds
)));
}
funds_to_use.amount -= self.fee.amount;
Ok(self.fee)
}
pub fn fee(&self) -> Coin {
self.fee.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod fee {
use super::*;
#[coverage_helper::test]
fn test_fee_manual_construction() {
let fee = Fee {
share: Decimal::percent(20u64),
};
let deposit = Uint128::from(1000000u64);
let deposit_fee = fee.compute(deposit);
assert_eq!(deposit_fee, Uint128::from(200000u64));
}
#[coverage_helper::test]
fn test_fee_new() {
let fee = Fee::new(Decimal::percent(20u64)).unwrap();
let deposit = Uint128::from(1000000u64);
let deposit_fee = fee.compute(deposit);
assert_eq!(deposit_fee, Uint128::from(200000u64));
}
#[coverage_helper::test]
fn test_fee_new_gte_100() {
let fee = Fee::new(Decimal::percent(100u64));
assert!(fee.is_err());
let fee = Fee::new(Decimal::percent(101u64));
assert!(fee.is_err());
}
#[coverage_helper::test]
fn test_fee_share() {
let expected_percent = 20u64;
let fee = Fee::new(Decimal::percent(expected_percent)).unwrap();
assert_eq!(fee.share(), Decimal::percent(expected_percent));
}
#[coverage_helper::test]
fn test_fee_msg() {
let fee = Fee::new(Decimal::percent(20u64)).unwrap();
let asset = Asset::native("uusd", Uint128::from(1000000u64));
let recipient = Addr::unchecked("recipient");
let msg = fee.msg(asset.clone(), recipient.clone()).unwrap();
assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
}
}
mod transfer_fee {
use cosmwasm_std::{coin, coins, testing::message_info};
use super::*;
#[coverage_helper::test]
fn test_transfer_fee_new() {
let fee = UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
let deposit = Uint128::from(1000000u64);
let deposit_fee = fee.compute(deposit);
assert_eq!(deposit_fee, Uint128::from(200000u64));
}
#[coverage_helper::test]
fn test_transfer_fee_share() {
let expected_percent = 20u64;
let fee = UsageFee::new(
Decimal::percent(expected_percent),
Addr::unchecked("recipient"),
)
.unwrap();
assert_eq!(fee.share(), Decimal::percent(expected_percent));
}
#[coverage_helper::test]
fn test_transfer_fee_msg() {
let fee = UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
let asset = Asset::native("uusd", Uint128::from(1000000u64));
let recipient = Addr::unchecked("recipient");
let msg = fee.fee.msg(asset.clone(), recipient.clone()).unwrap();
assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
}
#[coverage_helper::test]
fn test_transfer_fee_new_gte_100() {
let fee = UsageFee::new(Decimal::percent(100u64), Addr::unchecked("recipient"));
assert!(fee.is_err());
let fee = UsageFee::new(Decimal::percent(101u64), Addr::unchecked("recipient"));
assert!(fee.is_err());
}
#[coverage_helper::test]
fn test_transfer_fee_set_recipient() {
let mut fee =
UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
let new_recipient = Addr::unchecked("new_recipient");
fee.set_recipient(new_recipient.clone());
assert_eq!(fee.recipient(), Addr::unchecked(new_recipient));
}
#[coverage_helper::test]
fn test_transfer_fee_set_share() {
let mut fee =
UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
let new_share = Decimal::percent(10u64);
fee.set_share(new_share).unwrap();
assert_eq!(fee.share(), new_share);
}
#[coverage_helper::test]
fn test_loose_fee_validation() {
let fee = FixedFee::new(&coin(45, "ujunox"));
let mut info = message_info(&Addr::unchecked("anyone"), &coins(47, "ujunox"));
fee.charge(&mut info).unwrap();
assert_eq!(info.funds, coins(2, "ujunox"));
}
}
}