abstract_os/objects/
fee.rs

1use crate::{error::AbstractOsError, AbstractResult};
2use cosmwasm_std::{Addr, Api, CosmosMsg, Decimal, Uint128};
3use cw_asset::Asset;
4
5/// A wrapper around Fee to help handle fee logic.
6/// Use this with `Chargeable` trait in the SDK to charge fees on asset structs.
7#[cosmwasm_schema::cw_serde]
8pub struct UsageFee {
9    fee: Fee,
10    recipient: Addr,
11}
12
13impl UsageFee {
14    pub fn new(
15        api: &dyn Api,
16        share: Decimal,
17        recipient: impl Into<String>,
18    ) -> AbstractResult<Self> {
19        let recipient = api.addr_validate(&recipient.into())?;
20        let fee = Fee::new(share)?;
21        Ok(UsageFee { fee, recipient })
22    }
23    pub fn set_share(&mut self, share: Decimal) -> AbstractResult<()> {
24        self.fee = Fee::new(share)?;
25        Ok(())
26    }
27    pub fn share(&self) -> Decimal {
28        self.fee.share()
29    }
30    pub fn compute(&self, amount: Uint128) -> Uint128 {
31        amount * self.share()
32    }
33    pub fn recipient(&self) -> Addr {
34        self.recipient.clone()
35    }
36    pub fn set_recipient(
37        &mut self,
38        api: &dyn Api,
39        recipient: impl Into<String>,
40    ) -> AbstractResult<()> {
41        self.recipient = api.addr_validate(&recipient.into())?;
42        Ok(())
43    }
44}
45
46/// A wrapper around Decimal to help handle fractional fees.
47#[cosmwasm_schema::cw_serde]
48pub struct Fee {
49    /// fraction of asset to take as fee.
50    share: Decimal,
51}
52
53impl Fee {
54    pub fn new(share: Decimal) -> AbstractResult<Self> {
55        if share >= Decimal::percent(100) {
56            return Err(AbstractOsError::Fee(
57                "fee share must be lesser than 100%".to_string(),
58            ));
59        }
60        Ok(Fee { share })
61    }
62    pub fn compute(&self, amount: Uint128) -> Uint128 {
63        amount * self.share
64    }
65
66    pub fn msg(&self, asset: Asset, recipient: Addr) -> AbstractResult<CosmosMsg> {
67        asset.transfer_msg(recipient).map_err(Into::into)
68    }
69    pub fn share(&self) -> Decimal {
70        self.share
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    mod fee {
79        use super::*;
80
81        #[test]
82        fn test_fee_manual_construction() {
83            let fee = Fee {
84                share: Decimal::percent(20u64),
85            };
86            let deposit = Uint128::from(1000000u64);
87            let deposit_fee = fee.compute(deposit);
88            assert_eq!(deposit_fee, Uint128::from(200000u64));
89        }
90
91        #[test]
92        fn test_fee_new() {
93            let fee = Fee::new(Decimal::percent(20u64)).unwrap();
94            let deposit = Uint128::from(1000000u64);
95            let deposit_fee = fee.compute(deposit);
96            assert_eq!(deposit_fee, Uint128::from(200000u64));
97        }
98
99        #[test]
100        fn test_fee_new_gte_100() {
101            let fee = Fee::new(Decimal::percent(100u64));
102            assert!(fee.is_err());
103            let fee = Fee::new(Decimal::percent(101u64));
104            assert!(fee.is_err());
105        }
106
107        #[test]
108        fn test_fee_share() {
109            let expected_percent = 20u64;
110            let fee = Fee::new(Decimal::percent(expected_percent)).unwrap();
111            assert_eq!(fee.share(), Decimal::percent(expected_percent));
112        }
113
114        #[test]
115        fn test_fee_msg() {
116            let fee = Fee::new(Decimal::percent(20u64)).unwrap();
117            let asset = Asset::native("uusd", Uint128::from(1000000u64));
118
119            let recipient = Addr::unchecked("recipient");
120            let msg = fee.msg(asset.clone(), recipient.clone()).unwrap();
121            assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
122        }
123    }
124    mod transfer_fee {
125        use cosmwasm_std::testing::MockApi;
126
127        use super::*;
128
129        #[test]
130        fn test_transfer_fee_new() {
131            let api = MockApi::default();
132            let fee = UsageFee::new(&api, Decimal::percent(20u64), "recipient").unwrap();
133            let deposit = Uint128::from(1000000u64);
134            let deposit_fee = fee.compute(deposit);
135            assert_eq!(deposit_fee, Uint128::from(200000u64));
136        }
137
138        #[test]
139        fn test_transfer_fee_share() {
140            let api = MockApi::default();
141            let expected_percent = 20u64;
142            let fee = UsageFee::new(&api, Decimal::percent(expected_percent), "recipient").unwrap();
143            assert_eq!(fee.share(), Decimal::percent(expected_percent));
144        }
145
146        #[test]
147        fn test_transfer_fee_msg() {
148            let api = MockApi::default();
149            let fee = UsageFee::new(&api, Decimal::percent(20u64), "recipient").unwrap();
150            let asset = Asset::native("uusd", Uint128::from(1000000u64));
151
152            let recipient = Addr::unchecked("recipient");
153            let msg = fee.fee.msg(asset.clone(), recipient.clone()).unwrap();
154            assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
155        }
156
157        #[test]
158        fn test_transfer_fee_new_gte_100() {
159            let api = MockApi::default();
160            let fee = UsageFee::new(&api, Decimal::percent(100u64), "recipient");
161            assert!(fee.is_err());
162            let fee = UsageFee::new(&api, Decimal::percent(101u64), "recipient");
163            assert!(fee.is_err());
164        }
165
166        #[test]
167        fn test_transfer_fee_set_recipient() {
168            let api = MockApi::default();
169            let mut fee = UsageFee::new(&api, Decimal::percent(20u64), "recipient").unwrap();
170            let new_recipient = "new_recipient";
171            fee.set_recipient(&api, new_recipient).unwrap();
172            assert_eq!(fee.recipient(), Addr::unchecked(new_recipient));
173        }
174        #[test]
175        fn test_transfer_fee_set_share() {
176            let api = MockApi::default();
177            let mut fee = UsageFee::new(&api, Decimal::percent(20u64), "recipient").unwrap();
178            let new_share = Decimal::percent(10u64);
179            fee.set_share(new_share).unwrap();
180            assert_eq!(fee.share(), new_share);
181        }
182    }
183}