abstract_std/objects/
fee.rs1use cosmwasm_std::{Addr, Coin, CosmosMsg, Decimal, MessageInfo, Uint128};
2use cw_asset::Asset;
3
4use crate::{error::AbstractError, AbstractResult};
5
6#[cosmwasm_schema::cw_serde]
9pub struct UsageFee {
10 fee: Fee,
11 recipient: Addr,
12}
13
14impl UsageFee {
15 pub fn new(share: Decimal, recipient: Addr) -> AbstractResult<Self> {
16 let fee = Fee::new(share)?;
17 Ok(Self { fee, recipient })
18 }
19
20 pub fn set_share(&mut self, share: Decimal) -> AbstractResult<()> {
21 self.fee = Fee::new(share)?;
22 Ok(())
23 }
24
25 pub fn share(&self) -> Decimal {
26 self.fee.share()
27 }
28
29 pub fn compute(&self, amount: Uint128) -> Uint128 {
30 amount.mul_floor(self.share())
31 }
32
33 pub fn recipient(&self) -> Addr {
34 self.recipient.clone()
35 }
36
37 pub fn set_recipient(&mut self, recipient: Addr) {
38 self.recipient = recipient;
39 }
40}
41
42#[cosmwasm_schema::cw_serde]
44#[derive(Copy)]
45pub struct Fee {
46 share: Decimal,
48}
49
50impl Fee {
51 pub fn new(share: Decimal) -> AbstractResult<Self> {
52 if share >= Decimal::percent(100) {
53 return Err(AbstractError::Fee(
54 "fee share must be lesser than 100%".to_string(),
55 ));
56 }
57 Ok(Fee { share })
58 }
59
60 pub fn compute(&self, amount: Uint128) -> Uint128 {
61 amount.mul_floor(self.share)
62 }
63
64 pub fn msg(&self, asset: Asset, recipient: Addr) -> AbstractResult<CosmosMsg> {
65 asset.transfer_msg(recipient).map_err(Into::into)
66 }
67
68 pub fn share(&self) -> Decimal {
69 self.share
70 }
71}
72
73#[cosmwasm_schema::cw_serde]
75pub struct FixedFee {
76 fee: Coin,
78}
79
80impl FixedFee {
81 pub fn new(fee: &Coin) -> Self {
83 FixedFee { fee: fee.clone() }
84 }
85 pub fn quantity(mut self, qty: u128) -> Self {
88 self.fee.amount *= Uint128::from(qty);
89 self
90 }
91
92 pub fn assert_payment(self, msg_info: &MessageInfo) -> AbstractResult<Coin> {
95 if self.fee.amount.is_zero() {
96 return Ok(self.fee);
97 }
98 if msg_info.funds.len() != 1
99 || msg_info.funds[0].denom != self.fee.denom
100 || self.fee.amount != msg_info.funds[0].amount
101 {
102 return Err(AbstractError::Fee(format!(
103 "Invalid fee payment sent. Expected {}, sent {:?}",
104 self.fee, msg_info.funds
105 )));
106 }
107 Ok(self.fee)
108 }
109
110 pub fn charge(self, msg_info: &mut MessageInfo) -> AbstractResult<Coin> {
115 if self.fee.amount.is_zero() {
116 return Ok(self.fee);
117 }
118 let original_funds = msg_info.funds.clone();
119
120 let funds_to_use = msg_info
122 .funds
123 .iter_mut()
124 .find(|f| f.denom == self.fee.denom)
125 .ok_or(AbstractError::Fee(format!(
126 "Invalid fee payment sent. Expected {}, sent {:?}",
127 self.fee, original_funds
128 )))?;
129
130 if funds_to_use.amount < self.fee.amount {
131 return Err(AbstractError::Fee(format!(
132 "Invalid fee payment sent. Expected {}, sent {:?}",
133 self.fee, original_funds
134 )));
135 }
136
137 funds_to_use.amount -= self.fee.amount;
138 Ok(self.fee)
139 }
140 pub fn fee(&self) -> Coin {
141 self.fee.clone()
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 mod fee {
150 use super::*;
151
152 #[coverage_helper::test]
153 fn test_fee_manual_construction() {
154 let fee = Fee {
155 share: Decimal::percent(20u64),
156 };
157 let deposit = Uint128::from(1000000u64);
158 let deposit_fee = fee.compute(deposit);
159 assert_eq!(deposit_fee, Uint128::from(200000u64));
160 }
161
162 #[coverage_helper::test]
163 fn test_fee_new() {
164 let fee = Fee::new(Decimal::percent(20u64)).unwrap();
165 let deposit = Uint128::from(1000000u64);
166 let deposit_fee = fee.compute(deposit);
167 assert_eq!(deposit_fee, Uint128::from(200000u64));
168 }
169
170 #[coverage_helper::test]
171 fn test_fee_new_gte_100() {
172 let fee = Fee::new(Decimal::percent(100u64));
173 assert!(fee.is_err());
174 let fee = Fee::new(Decimal::percent(101u64));
175 assert!(fee.is_err());
176 }
177
178 #[coverage_helper::test]
179 fn test_fee_share() {
180 let expected_percent = 20u64;
181 let fee = Fee::new(Decimal::percent(expected_percent)).unwrap();
182 assert_eq!(fee.share(), Decimal::percent(expected_percent));
183 }
184
185 #[coverage_helper::test]
186 fn test_fee_msg() {
187 let fee = Fee::new(Decimal::percent(20u64)).unwrap();
188 let asset = Asset::native("uusd", Uint128::from(1000000u64));
189
190 let recipient = Addr::unchecked("recipient");
191 let msg = fee.msg(asset.clone(), recipient.clone()).unwrap();
192 assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
193 }
194 }
195 mod transfer_fee {
196 use cosmwasm_std::{coin, coins, testing::message_info};
197
198 use super::*;
199
200 #[coverage_helper::test]
201 fn test_transfer_fee_new() {
202 let fee = UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
203 let deposit = Uint128::from(1000000u64);
204 let deposit_fee = fee.compute(deposit);
205 assert_eq!(deposit_fee, Uint128::from(200000u64));
206 }
207
208 #[coverage_helper::test]
209 fn test_transfer_fee_share() {
210 let expected_percent = 20u64;
211 let fee = UsageFee::new(
212 Decimal::percent(expected_percent),
213 Addr::unchecked("recipient"),
214 )
215 .unwrap();
216 assert_eq!(fee.share(), Decimal::percent(expected_percent));
217 }
218
219 #[coverage_helper::test]
220 fn test_transfer_fee_msg() {
221 let fee = UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
222 let asset = Asset::native("uusd", Uint128::from(1000000u64));
223
224 let recipient = Addr::unchecked("recipient");
225 let msg = fee.fee.msg(asset.clone(), recipient.clone()).unwrap();
226 assert_eq!(msg, asset.transfer_msg(recipient).unwrap(),);
227 }
228
229 #[coverage_helper::test]
230 fn test_transfer_fee_new_gte_100() {
231 let fee = UsageFee::new(Decimal::percent(100u64), Addr::unchecked("recipient"));
232 assert!(fee.is_err());
233 let fee = UsageFee::new(Decimal::percent(101u64), Addr::unchecked("recipient"));
234 assert!(fee.is_err());
235 }
236
237 #[coverage_helper::test]
238 fn test_transfer_fee_set_recipient() {
239 let mut fee =
240 UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
241 let new_recipient = Addr::unchecked("new_recipient");
242 fee.set_recipient(new_recipient.clone());
243 assert_eq!(fee.recipient(), Addr::unchecked(new_recipient));
244 }
245 #[coverage_helper::test]
246 fn test_transfer_fee_set_share() {
247 let mut fee =
248 UsageFee::new(Decimal::percent(20u64), Addr::unchecked("recipient")).unwrap();
249 let new_share = Decimal::percent(10u64);
250 fee.set_share(new_share).unwrap();
251 assert_eq!(fee.share(), new_share);
252 }
253 #[coverage_helper::test]
254 fn test_loose_fee_validation() {
255 let fee = FixedFee::new(&coin(45, "ujunox"));
256 let mut info = message_info(&Addr::unchecked("anyone"), &coins(47, "ujunox"));
257 fee.charge(&mut info).unwrap();
258 assert_eq!(info.funds, coins(2, "ujunox"));
259 }
260 }
261}