1use cosmwasm_std::{
2 coin, coins, ensure, Addr, BankMsg, Coin, Decimal, Event, MessageInfo, Uint128,
3};
4use cw_utils::{may_pay, must_pay, PaymentError};
5use sg_std::{create_fund_fairburn_pool_msg, Response, SubMsg, NATIVE_DENOM};
6use thiserror::Error;
7
8const FEE_BURN_PERCENT: u64 = 50;
10const FOUNDATION: &str = "stars1xqz6xujjyz0r9uzn7srasle5uynmpa0zkjr5l8";
11const LAUNCHPAD_DAO_ADDRESS: &str =
12 "stars1huqk6ha02jgrm69lxh8xfgl6wch9wlg7s65ujxydwdr725cxvuus423tj0";
13const LIQUIDITY_DAO_ADDRESS: &str =
14 "stars12he2ldxl950wfypvelqwkac4mdul7clzgd8wdlnmjvll8z2cc47qsatvl2";
15
16pub fn checked_fair_burn(
18 info: &MessageInfo,
19 fee: u128,
20 developer: Option<Addr>,
21 res: &mut Response,
22) -> Result<(), FeeError> {
23 let payment = may_pay(info, NATIVE_DENOM)?;
25 if payment.u128() < fee {
26 return Err(FeeError::InsufficientFee(fee, payment.u128()));
27 };
28
29 if payment.u128() != 0u128 {
30 fair_burn(fee, developer, res);
31 }
32
33 Ok(())
34}
35
36pub fn ibc_denom_fair_burn(
39 fee: Coin,
40 developer: Option<Addr>,
41 res: &mut Response,
42) -> Result<(), FeeError> {
43 let mut event = Event::new("ibc-fair-burn");
44
45 match &developer {
46 Some(developer) => {
47 let dev_fee = (fee.amount.mul_ceil(Decimal::percent(FEE_BURN_PERCENT))).u128();
49 let dev_coin = coin(dev_fee, fee.denom.to_string());
50 let foundation_coin = coin(fee.amount.u128() - dev_fee, fee.denom);
51
52 event = event.add_attribute("dev_addr", developer.to_string());
53 event = event.add_attribute("dev_coin", dev_coin.to_string());
54 event = event.add_attribute("foundation_coin", foundation_coin.to_string());
55
56 res.messages.push(SubMsg::new(BankMsg::Send {
57 to_address: developer.to_string(),
58 amount: vec![dev_coin],
59 }));
60 res.messages.push(SubMsg::new(BankMsg::Send {
61 to_address: FOUNDATION.to_string(),
62 amount: vec![foundation_coin],
63 }));
64 }
65 None => {
66 event = event.add_attribute("foundation_coin", fee.to_string());
68 res.messages.push(SubMsg::new(BankMsg::Send {
69 to_address: FOUNDATION.to_string(),
70 amount: vec![fee],
71 }));
72 }
73 }
74
75 res.events.push(event);
76 Ok(())
77}
78
79pub fn distribute_mint_fees(
80 fee: Coin,
81 res: &mut Response,
82 is_featured: bool,
83 developer: Option<Addr>,
84) -> Result<(), FeeError> {
85 let liquidity_dao_ratio: Decimal = Decimal::from_ratio(1u128, 5u128);
86 let liquidity_dao_ratio_featured: Decimal = Decimal::from_ratio(1u128, 8u128);
87
88 let mut event = Event::new("mint-fee-distribution");
89
90 let liquidity_dao_percentage = if is_featured {
91 liquidity_dao_ratio_featured
92 } else {
93 liquidity_dao_ratio
94 };
95
96 match &developer {
97 Some(developer) => {
98 let dev_fee = fee
99 .amount
100 .mul_ceil(Decimal::percent(FEE_BURN_PERCENT))
101 .u128();
102 let dev_coin = coin(dev_fee, fee.denom.to_string());
103 let remaining_coin = coin(fee.amount.u128() - dev_fee, fee.denom.clone());
104
105 let liquidity_dao_fee =
106 (remaining_coin.amount.mul_ceil(liquidity_dao_percentage)).u128();
107 let liquidity_dao_coin = coin(liquidity_dao_fee, fee.denom.to_string());
108 let foundation_coin = coin(remaining_coin.amount.u128() - liquidity_dao_fee, fee.denom);
109
110 event = event.add_attribute("dev_addr", developer.to_string());
111 event = event.add_attribute("dev_coin", dev_coin.to_string());
112 event = event.add_attribute("liquidity_DAO_addr", LIQUIDITY_DAO_ADDRESS.to_string());
113 event = event.add_attribute("liquidity_DAO_coin", liquidity_dao_coin.to_string());
114 event = event.add_attribute("foundation_addr", FOUNDATION.to_string());
115 event = event.add_attribute("foundation_coin", foundation_coin.to_string());
116
117 res.messages.push(SubMsg::new(BankMsg::Send {
118 to_address: developer.to_string(),
119 amount: vec![dev_coin],
120 }));
121 res.messages.push(SubMsg::new(BankMsg::Send {
122 to_address: LIQUIDITY_DAO_ADDRESS.to_string(),
123 amount: vec![liquidity_dao_coin],
124 }));
125 res.messages.push(SubMsg::new(BankMsg::Send {
126 to_address: FOUNDATION.to_string(),
127 amount: vec![foundation_coin],
128 }));
129 }
130 None => {
131 let liquidity_dao_fee = fee.amount.mul_ceil(liquidity_dao_percentage).u128();
132 let liquidity_dao_coin = coin(liquidity_dao_fee, fee.denom.to_string());
133 let foundation_coin = coin(fee.amount.u128() - liquidity_dao_fee, fee.denom);
134
135 event = event.add_attribute("liquidity_DAO_addr", LIQUIDITY_DAO_ADDRESS.to_string());
136 event = event.add_attribute("liquidity_DAO_coin", liquidity_dao_coin.to_string());
137 event = event.add_attribute("foundation_addr", FOUNDATION.to_string());
138 event = event.add_attribute("foundation_coin", foundation_coin.to_string());
139
140 res.messages.push(SubMsg::new(BankMsg::Send {
141 to_address: LIQUIDITY_DAO_ADDRESS.to_string(),
142 amount: vec![liquidity_dao_coin],
143 }));
144 res.messages.push(SubMsg::new(BankMsg::Send {
145 to_address: FOUNDATION.to_string(),
146 amount: vec![foundation_coin],
147 }));
148 }
149 }
150
151 res.events.push(event);
152 Ok(())
153}
154
155pub fn fair_burn(fee: u128, developer: Option<Addr>, res: &mut Response) {
157 let mut event = Event::new("fair-burn");
158
159 let burn_fee = (Uint128::from(fee) * Decimal::percent(FEE_BURN_PERCENT)).u128();
161 let burn_coin = coins(burn_fee, NATIVE_DENOM);
162 res.messages
163 .push(SubMsg::new(BankMsg::Burn { amount: burn_coin }));
164 event = event.add_attribute("burn_amount", Uint128::from(burn_fee).to_string());
165
166 let remainder = fee - burn_fee;
168
169 if let Some(dev) = developer {
170 res.messages.push(SubMsg::new(BankMsg::Send {
171 to_address: dev.to_string(),
172 amount: coins(remainder, NATIVE_DENOM),
173 }));
174 event = event.add_attribute("dev", dev.to_string());
175 event = event.add_attribute("dev_amount", Uint128::from(remainder).to_string());
176 } else {
177 res.messages
178 .push(SubMsg::new(create_fund_fairburn_pool_msg(coins(
179 remainder,
180 NATIVE_DENOM,
181 ))));
182 event = event.add_attribute("dist_amount", Uint128::from(remainder).to_string());
183 }
184
185 res.events.push(event);
186}
187
188pub fn transfer_funds_to_launchpad_dao(
189 info: &MessageInfo,
190 fee: u128,
191 accepted_denom: &str,
192 res: &mut Response,
193) -> Result<(), FeeError> {
194 let payment = must_pay(info, accepted_denom)?;
195 ensure!(
196 payment.u128() >= fee,
197 FeeError::InsufficientFee(fee, payment.u128())
198 );
199
200 let msg = BankMsg::Send {
201 to_address: LAUNCHPAD_DAO_ADDRESS.to_string(),
202 amount: vec![coin(payment.u128(), accepted_denom)],
203 };
204 res.messages.push(SubMsg::new(msg));
205
206 Ok(())
207}
208
209#[derive(Error, Debug, PartialEq, Eq)]
210pub enum FeeError {
211 #[error("Insufficient fee: expected {0}, got {1}")]
212 InsufficientFee(u128, u128),
213
214 #[error("{0}")]
215 Payment(#[from] PaymentError),
216}
217
218#[cfg(test)]
219mod tests {
220 use cosmwasm_std::{coins, Addr, BankMsg};
221 use sg_std::{create_fund_fairburn_pool_msg, Response, NATIVE_DENOM};
222
223 use crate::{fair_burn, SubMsg};
224
225 #[test]
226 fn check_fair_burn_no_dev_rewards() {
227 let mut res = Response::new();
228
229 fair_burn(9u128, None, &mut res);
230 let burn_msg = SubMsg::new(BankMsg::Burn {
231 amount: coins(4, "ustars".to_string()),
232 });
233 let dist_msg = SubMsg::new(create_fund_fairburn_pool_msg(coins(5, NATIVE_DENOM)));
234 assert_eq!(res.messages.len(), 2);
235 assert_eq!(res.messages[0], burn_msg);
236 assert_eq!(res.messages[1], dist_msg);
237 }
238
239 #[test]
240 fn check_fair_burn_with_dev_rewards() {
241 let mut res = Response::new();
242
243 fair_burn(9u128, Some(Addr::unchecked("geordi")), &mut res);
244 let bank_msg = SubMsg::new(BankMsg::Send {
245 to_address: "geordi".to_string(),
246 amount: coins(5, NATIVE_DENOM),
247 });
248 let burn_msg = SubMsg::new(BankMsg::Burn {
249 amount: coins(4, NATIVE_DENOM),
250 });
251 assert_eq!(res.messages.len(), 2);
252 assert_eq!(res.messages[0], burn_msg);
253 assert_eq!(res.messages[1], bank_msg);
254 }
255
256 #[test]
257 fn check_fair_burn_with_dev_rewards_different_amount() {
258 let mut res = Response::new();
259
260 fair_burn(1420u128, Some(Addr::unchecked("geordi")), &mut res);
261 let bank_msg = SubMsg::new(BankMsg::Send {
262 to_address: "geordi".to_string(),
263 amount: coins(710, NATIVE_DENOM),
264 });
265 let burn_msg = SubMsg::new(BankMsg::Burn {
266 amount: coins(710, NATIVE_DENOM),
267 });
268 assert_eq!(res.messages.len(), 2);
269 assert_eq!(res.messages[0], burn_msg);
270 assert_eq!(res.messages[1], bank_msg);
271 }
272}