terp_fee/
lib.rs

1use cosmwasm_std::{coin, coins, Addr, BankMsg, Coin, Decimal, Event, MessageInfo, Uint128};
2use cw_utils::{may_pay, PaymentError};
3use terp_sdk::{create_fund_community_pool_msg, Response, SubMsg, NATIVE_FEE_DENOM};
4use thiserror::Error;
5
6// governance parameters
7const FEE_BURN_PERCENT: u64 = 50;
8const FOUNDATION: &str = "terp1gnfqzr25fgds7zn7m6njd795aat5yaf6vk7hd9";
9
10/// Burn and distribute fees and return an error if the fee is not enough
11pub fn checked_fair_burn(
12    info: &MessageInfo,
13    fee: u128,
14    developer: Option<Addr>,
15    res: &mut Response,
16) -> Result<(), FeeError> {
17    // Use may_pay because fees could be 0. Add check to avoid transferring 0 funds
18    let payment = may_pay(info, NATIVE_FEE_DENOM)?;
19    if payment.u128() < fee {
20        return Err(FeeError::InsufficientFee(fee, payment.u128()));
21    };
22
23    if payment.u128() != 0u128 {
24        fair_burn(fee, developer, res);
25    }
26
27    Ok(())
28}
29
30/// IBC assets go to community pool and dev
31/// 7/29/23 temporary fix until we switch to using fairburn contract
32pub fn ibc_denom_fair_burn(
33    fee: Coin,
34    developer: Option<Addr>,
35    res: &mut Response,
36) -> Result<(), FeeError> {
37    let mut event = Event::new("ibc-fair-burn");
38
39    match &developer {
40        Some(developer) => {
41            // Calculate the fees. 50% to dev, 50% to foundation
42            let dev_fee = (fee.amount.mul_ceil(Decimal::percent(FEE_BURN_PERCENT))).u128();
43            let dev_coin = coin(dev_fee, fee.denom.to_string());
44            let foundation_coin = coin(fee.amount.u128() - dev_fee, fee.denom);
45
46            event = event.add_attribute("dev_addr", developer.to_string());
47            event = event.add_attribute("dev_coin", dev_coin.to_string());
48            event = event.add_attribute("foundation_coin", foundation_coin.to_string());
49
50            res.messages.push(SubMsg::new(BankMsg::Send {
51                to_address: developer.to_string(),
52                amount: vec![dev_coin],
53            }));
54            res.messages.push(SubMsg::new(BankMsg::Send {
55                to_address: FOUNDATION.to_string(),
56                amount: vec![foundation_coin],
57            }));
58        }
59        None => {
60            // No dev, send all to foundation.
61            event = event.add_attribute("foundation_coin", fee.to_string());
62            res.messages.push(SubMsg::new(BankMsg::Send {
63                to_address: FOUNDATION.to_string(),
64                amount: vec![fee],
65            }));
66        }
67    }
68
69    res.events.push(event);
70    Ok(())
71}
72
73/// Burn and distribute fees, assuming the right fee is passed in
74pub fn fair_burn(fee: u128, developer: Option<Addr>, res: &mut Response) {
75    let mut event = Event::new("fair-burn");
76
77    // calculate the fair burn fee
78    let burn_fee = (Uint128::from(fee) * Decimal::percent(FEE_BURN_PERCENT)).u128();
79    let burn_coin = coins(burn_fee, NATIVE_FEE_DENOM);
80    res.messages
81        .push(SubMsg::new(BankMsg::Burn { amount: burn_coin }));
82    event = event.add_attribute("burn_amount", Uint128::from(burn_fee).to_string());
83
84    // send remainder to developer or community pool
85    let remainder = fee - burn_fee;
86
87    if let Some(dev) = developer {
88        res.messages.push(SubMsg::new(BankMsg::Send {
89            to_address: dev.to_string(),
90            amount: coins(remainder, NATIVE_FEE_DENOM),
91        }));
92        event = event.add_attribute("dev", dev.to_string());
93        event = event.add_attribute("dev_amount", Uint128::from(remainder).to_string());
94    } else {
95        res.messages
96            .push(SubMsg::new(create_fund_community_pool_msg(coins(
97                remainder,
98                NATIVE_FEE_DENOM,
99            ))));
100        event = event.add_attribute("dist_amount", Uint128::from(remainder).to_string());
101    }
102
103    res.events.push(event);
104}
105
106#[derive(Error, Debug, PartialEq, Eq)]
107pub enum FeeError {
108    #[error("Insufficient fee: expected {0}, got {1}")]
109    InsufficientFee(u128, u128),
110
111    #[error("{0}")]
112    Payment(#[from] PaymentError),
113}
114
115#[cfg(test)]
116mod tests {
117    use cosmwasm_std::{coins, Addr, BankMsg};
118    use terp_sdk::{create_fund_community_pool_msg, Response, NATIVE_FEE_DENOM};
119
120    use crate::{fair_burn, SubMsg};
121
122    #[test]
123    fn check_fair_burn_no_dev_rewards() {
124        let mut res = Response::new();
125
126        fair_burn(9u128, None, &mut res);
127        let burn_msg = SubMsg::new(BankMsg::Burn {
128            amount: coins(4, "uthiol".to_string()),
129        });
130        let dist_msg = SubMsg::new(create_fund_community_pool_msg(coins(5, NATIVE_FEE_DENOM)));
131        assert_eq!(res.messages.len(), 2);
132        assert_eq!(res.messages[0], burn_msg);
133        assert_eq!(res.messages[1], dist_msg);
134    }
135
136    #[test]
137    fn check_fair_burn_with_dev_rewards() {
138        let mut res = Response::new();
139
140        fair_burn(9u128, Some(Addr::unchecked("jeret")), &mut res);
141        let bank_msg = SubMsg::new(BankMsg::Send {
142            to_address: "jeret".to_string(),
143            amount: coins(5, NATIVE_FEE_DENOM),
144        });
145        let burn_msg = SubMsg::new(BankMsg::Burn {
146            amount: coins(4, NATIVE_FEE_DENOM),
147        });
148        assert_eq!(res.messages.len(), 2);
149        assert_eq!(res.messages[0], burn_msg);
150        assert_eq!(res.messages[1], bank_msg);
151    }
152
153    #[test]
154    fn check_fair_burn_with_dev_rewards_different_amount() {
155        let mut res = Response::new();
156
157        fair_burn(1420u128, Some(Addr::unchecked("eret")), &mut res);
158        let bank_msg = SubMsg::new(BankMsg::Send {
159            to_address: "eret".to_string(),
160            amount: coins(710, NATIVE_FEE_DENOM),
161        });
162        let burn_msg = SubMsg::new(BankMsg::Burn {
163            amount: coins(710, NATIVE_FEE_DENOM),
164        });
165        assert_eq!(res.messages.len(), 2);
166        assert_eq!(res.messages[0], burn_msg);
167        assert_eq!(res.messages[1], bank_msg);
168    }
169}