1pub struct FeeSplitArgs {
2 pub fee_amount: u64,
3 pub protocol_bps: u64,
4 pub lp_bps: u64,
5 pub base_total_bps: u64,
6 pub decay_premium_bps: u64,
7}
8
9#[must_use]
13pub fn split_fee_amount(args: FeeSplitArgs) -> Option<(u64, u64, u64, u64)> {
14 let fee_amount = args.fee_amount;
15 if fee_amount == 0 {
17 return Some((0, 0, 0, 0));
18 }
19 let effective_total_bps =
20 args.base_total_bps.checked_add(args.decay_premium_bps)?;
21 if effective_total_bps == 0 {
22 return None;
23 }
24
25 if args.base_total_bps == 0 {
26 return Some((fee_amount, 0, 0, fee_amount));
27 }
28
29 let lp_fee = u128::from(fee_amount)
30 .checked_mul(u128::from(args.lp_bps))?
31 .checked_div(u128::from(effective_total_bps))?;
32 let lp_fee = u64::try_from(lp_fee).ok()?;
33
34 let creator_bps = args
35 .base_total_bps
36 .checked_sub(args.protocol_bps)?
37 .checked_sub(args.lp_bps)?;
38 let creator_fee = u128::from(fee_amount)
39 .checked_mul(u128::from(creator_bps))?
40 .checked_div(u128::from(effective_total_bps))?;
41 let creator_fee = u64::try_from(creator_fee).ok()?;
42
43 let sniper_fee = u128::from(fee_amount)
44 .checked_mul(u128::from(args.decay_premium_bps))?
45 .checked_div(u128::from(effective_total_bps))?;
46 let sniper_fee = u64::try_from(sniper_fee).ok()?;
47
48 let protocol_fee =
49 fee_amount.checked_sub(lp_fee)?.checked_sub(creator_fee)?;
50
51 Some((protocol_fee, lp_fee, creator_fee, sniper_fee))
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 const STANDARD: FeeSplitArgs = FeeSplitArgs {
59 fee_amount: 1000,
60 protocol_bps: 200,
61 lp_bps: 200,
62 base_total_bps: 1000,
63 decay_premium_bps: 0,
64 };
65
66 #[test]
67 fn test_split_fee_amount_no_dust_lost() {
68 let (protocol_fee, lp_fee, creator_fee, sniper_fee) =
69 split_fee_amount(STANDARD).unwrap();
70
71 assert_eq!(protocol_fee, 200);
72 assert_eq!(lp_fee, 200);
73 assert_eq!(creator_fee, 600);
74 assert_eq!(sniper_fee, 0);
75 assert_eq!(protocol_fee + lp_fee + creator_fee, 1000);
76 }
77
78 #[test]
79 fn test_split_fee_amount_protocol_wins_rounding() {
80 let (protocol_fee, lp_fee, creator_fee, sniper_fee) =
81 split_fee_amount(FeeSplitArgs {
82 fee_amount: 1001,
83 ..STANDARD
84 })
85 .unwrap();
86
87 assert_eq!(lp_fee, 200);
88 assert_eq!(creator_fee, 600);
89 assert_eq!(protocol_fee, 201);
90 assert_eq!(sniper_fee, 0);
91 assert_eq!(protocol_fee + lp_fee + creator_fee, 1001);
92 }
93
94 #[test]
95 fn test_split_fee_with_decay_protocol_wins_rounding() {
96 let (protocol_fee, lp_fee, creator_fee, sniper_fee) =
97 split_fee_amount(FeeSplitArgs {
98 fee_amount: 1001,
99 decay_premium_bps: 500,
100 ..STANDARD
101 })
102 .unwrap();
103
104 assert_eq!(lp_fee, 133);
105 assert_eq!(creator_fee, 400);
106 assert_eq!(protocol_fee, 468);
107 assert_eq!(sniper_fee, 333);
108 assert_eq!(protocol_fee + lp_fee + creator_fee, 1001);
109 }
110
111 #[test]
112 fn test_split_fee_max_amount() {
113 let fee_amount = 1_000_000_000_000_000u64;
114 let result = split_fee_amount(FeeSplitArgs {
115 fee_amount,
116 protocol_bps: 100,
117 lp_bps: 100,
118 base_total_bps: 300,
119 decay_premium_bps: 0,
120 });
121 assert!(result.is_some());
122 let (protocol, lp, creator, _decay) = result.unwrap();
123 assert_eq!(protocol + lp + creator, fee_amount);
124 }
125
126 #[test]
127 fn test_split_fee_zero_amount() {
128 assert_eq!(
129 split_fee_amount(FeeSplitArgs {
130 fee_amount: 0,
131 protocol_bps: 100,
132 lp_bps: 100,
133 base_total_bps: 300,
134 decay_premium_bps: 0,
135 }),
136 Some((0, 0, 0, 0)),
137 );
138 }
139
140 #[test]
141 fn test_split_fee_invalid_bps_underflows() {
142 assert_eq!(
143 split_fee_amount(FeeSplitArgs {
144 protocol_bps: 600,
145 lp_bps: 500,
146 ..STANDARD
147 }),
148 None,
149 );
150 }
151
152 #[test]
153 fn test_split_fee_zero_total_bps() {
154 assert_eq!(
155 split_fee_amount(FeeSplitArgs {
156 fee_amount: 1000,
157 protocol_bps: 0,
158 lp_bps: 0,
159 base_total_bps: 0,
160 decay_premium_bps: 0,
161 }),
162 None,
163 );
164 }
165
166 #[test]
167 fn test_split_fee_zero_amount_and_zero_total_bps() {
168 assert_eq!(
169 split_fee_amount(FeeSplitArgs {
170 fee_amount: 0,
171 protocol_bps: 0,
172 lp_bps: 0,
173 base_total_bps: 0,
174 decay_premium_bps: 0,
175 }),
176 Some((0, 0, 0, 0)),
177 );
178 }
179}