1use ark_ff::{BigInteger, BigInteger256, Zero};
35use mina_curves::pasta::Fp;
36use poseidon::hash::Inputs;
37
38use crate::{
39 proofs::{
40 field::{field, Boolean, FieldWitness},
41 numbers::currency::{CheckedFee, CheckedSigned},
42 witness::Witness,
43 },
44 AppendToInputs, ToInputs, TokenId,
45};
46
47use super::{
48 currency::{Fee, Magnitude, Sgn, Signed},
49 scan_state::transaction_snark::OneOrTwo,
50};
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct FeeExcess {
54 pub fee_token_l: TokenId,
55 pub fee_excess_l: Signed<Fee>,
56 pub fee_token_r: TokenId,
57 pub fee_excess_r: Signed<Fee>,
58}
59
60#[derive(Debug, Clone)]
61pub struct CheckedFeeExcess<F: FieldWitness> {
62 pub fee_token_l: TokenId,
63 pub fee_excess_l: CheckedSigned<F, CheckedFee<F>>,
64 pub fee_token_r: TokenId,
65 pub fee_excess_r: CheckedSigned<F, CheckedFee<F>>,
66}
67
68impl ToInputs for FeeExcess {
69 fn to_inputs(&self, inputs: &mut Inputs) {
71 let Self {
72 fee_token_l,
73 fee_excess_l,
74 fee_token_r,
75 fee_excess_r,
76 } = self;
77
78 inputs.append(fee_token_l);
79 inputs.append(fee_excess_l);
80 inputs.append(fee_token_r);
81 inputs.append(fee_excess_r);
82 }
83}
84
85impl FeeExcess {
86 pub fn empty() -> Self {
87 Self {
88 fee_token_l: TokenId::default(),
89 fee_excess_l: Signed::<Fee>::zero(),
90 fee_token_r: TokenId::default(),
91 fee_excess_r: Signed::<Fee>::zero(),
92 }
93 }
94
95 pub fn is_zero(&self) -> bool {
96 let Self {
97 fee_token_l,
98 fee_excess_l,
99 fee_token_r,
100 fee_excess_r,
101 } = self;
102
103 fee_token_l.is_default()
104 && fee_token_r.is_default()
105 && fee_excess_l.is_zero()
106 && fee_excess_r.is_zero()
107 }
108
109 pub fn of_one_or_two(excesses: OneOrTwo<(TokenId, Signed<Fee>)>) -> Result<Self, String> {
111 match excesses {
112 OneOrTwo::One((fee_token_l, fee_excess_l)) => Self {
113 fee_token_l,
114 fee_excess_l,
115 fee_token_r: TokenId::default(),
116 fee_excess_r: Signed::<Fee>::zero(),
117 },
118 OneOrTwo::Two(((fee_token_l, fee_excess_l), (fee_token_r, fee_excess_r))) => Self {
119 fee_token_l,
120 fee_excess_l,
121 fee_token_r,
122 fee_excess_r,
123 },
124 }
125 .rebalance()
126 }
127
128 pub fn of_single((fee_token_l, fee_excess_l): (TokenId, Signed<Fee>)) -> Self {
130 Self {
131 fee_token_l,
132 fee_excess_l,
133 fee_token_r: TokenId::default(),
134 fee_excess_r: Signed::<Fee>::zero(),
135 }
136 }
137
138 fn rebalance(&self) -> Result<Self, String> {
145 let Self {
146 fee_token_l,
147 fee_excess_l,
148 fee_token_r,
149 fee_excess_r,
150 } = self;
151
152 let fee_token_l = if fee_excess_l.magnitude.is_zero() {
154 fee_token_r
155 } else {
156 fee_token_l
157 };
158
159 let (fee_excess_l, fee_excess_r) = if fee_token_l == fee_token_r {
161 match fee_excess_l.add(fee_excess_r) {
162 Some(fee_excess_l) => (fee_excess_l, Signed::<Fee>::zero()),
163 None => return Err("Error adding fees: overflow".to_string()),
164 }
165 } else {
166 (*fee_excess_l, *fee_excess_r)
167 };
168
169 let fee_token_l = if fee_excess_l.magnitude.is_zero() {
174 TokenId::default()
175 } else {
176 fee_token_l.clone()
177 };
178 let fee_token_r = if fee_excess_r.magnitude.is_zero() {
179 TokenId::default()
180 } else {
181 fee_token_r.clone()
182 };
183
184 Ok(Self {
185 fee_token_l,
186 fee_excess_l,
187 fee_token_r,
188 fee_excess_r,
189 })
190 }
191
192 fn rebalance_checked(
193 fee_token_l: TokenId,
194 fee_excess_l: Fp,
195 fee_token_r: TokenId,
196 fee_excess_r: Fp,
197 w: &mut Witness<Fp>,
198 ) -> (TokenId, Fp, TokenId, Fp) {
199 let fee_token_l = {
200 let excess_is_zero = field::equal(Fp::zero(), fee_excess_l, w);
201 w.exists_no_check(match excess_is_zero {
202 Boolean::True => &fee_token_r,
203 Boolean::False => &fee_token_l,
204 })
205 };
206
207 let (fee_excess_l, fee_excess_r) = {
208 let tokens_equal = field::equal(fee_token_l.0, fee_token_r.0, w);
209 let amount_to_move = w.exists_no_check(match tokens_equal {
210 Boolean::True => fee_excess_r,
211 Boolean::False => Fp::zero(),
212 });
213
214 (fee_excess_l + amount_to_move, fee_excess_r + amount_to_move)
215 };
216
217 let fee_token_l = {
218 let excess_is_zero = field::equal(Fp::zero(), fee_excess_l, w);
219 w.exists_no_check(match excess_is_zero {
220 Boolean::True => TokenId::default(),
221 Boolean::False => fee_token_l.clone(),
222 })
223 };
224
225 let fee_token_r = {
226 let excess_is_zero = field::equal(Fp::zero(), fee_excess_r, w);
227 w.exists_no_check(match excess_is_zero {
228 Boolean::True => TokenId::default(),
229 Boolean::False => fee_token_r.clone(),
230 })
231 };
232
233 (fee_token_l, fee_excess_l, fee_token_r, fee_excess_r)
234 }
235
236 pub fn combine(
240 Self {
241 fee_token_l: fee_token1_l,
242 fee_excess_l: fee_excess1_l,
243 fee_token_r: fee_token1_r,
244 fee_excess_r: fee_excess1_r,
245 }: &Self,
246 Self {
247 fee_token_l: fee_token2_l,
248 fee_excess_l: fee_excess2_l,
249 fee_token_r: fee_token2_r,
250 fee_excess_r: fee_excess2_r,
251 }: &Self,
252 ) -> Result<Self, String> {
253 let ((fee_token1_l, fee_excess1_l), (fee_token2_l, fee_excess2_l)) = eliminate_fee_excess(
256 (fee_token1_l, fee_excess1_l),
257 (fee_token1_r, fee_excess1_r),
258 (fee_token2_l, fee_excess2_l),
259 )?;
260
261 let ((fee_token1_l, fee_excess1_l), (fee_token2_r, fee_excess2_r)) = eliminate_fee_excess(
264 (fee_token1_l, &fee_excess1_l),
265 (fee_token2_l, &fee_excess2_l),
266 (fee_token2_r, fee_excess2_r),
267 )?;
268
269 Self {
270 fee_token_l: fee_token1_l.clone(),
271 fee_excess_l: fee_excess1_l,
272 fee_token_r: fee_token2_r.clone(),
273 fee_excess_r: fee_excess2_r,
274 }
275 .rebalance()
276 }
277
278 pub fn combine_checked(
279 Self {
280 fee_token_l: fee_token1_l,
281 fee_excess_l: fee_excess1_l,
282 fee_token_r: fee_token1_r,
283 fee_excess_r: fee_excess1_r,
284 }: &Self,
285 Self {
286 fee_token_l: fee_token2_l,
287 fee_excess_l: fee_excess2_l,
288 fee_token_r: fee_token2_r,
289 fee_excess_r: fee_excess2_r,
290 }: &Self,
291 w: &mut Witness<Fp>,
292 ) -> (TokenId, Signed<Fee>, TokenId, Signed<Fee>) {
293 let fee_excess1_l = fee_excess1_l.to_checked::<Fp>().value(w);
295 let fee_excess1_r = fee_excess1_r.to_checked::<Fp>().value(w);
296 let fee_excess2_l = fee_excess2_l.to_checked::<Fp>().value(w);
297 let fee_excess2_r = fee_excess2_r.to_checked::<Fp>().value(w);
298
299 let ((fee_token1_l, fee_excess1_l), (fee_token2_l, fee_excess2_l)) =
300 eliminate_fee_excess_checked(
301 (fee_token1_l, fee_excess1_l),
302 (fee_token1_r, fee_excess1_r),
303 (fee_token2_l, fee_excess2_l),
304 w,
305 );
306
307 let ((fee_token1_l, fee_excess1_l), (fee_token2_r, fee_excess2_r)) =
308 eliminate_fee_excess_checked(
309 (&fee_token1_l, fee_excess1_l),
310 (&fee_token2_l, fee_excess2_l),
311 (fee_token2_r, fee_excess2_r),
312 w,
313 );
314
315 let (fee_token_l, fee_excess_l, fee_token_r, fee_excess_r) =
316 Self::rebalance_checked(fee_token1_l, fee_excess1_l, fee_token2_r, fee_excess2_r, w);
317
318 let convert_to_currency = |excess: Fp| {
319 let bigint: BigInteger256 = excess.into();
320 let is_neg = bigint.get_bit(255 - 1);
321 let sgn = if is_neg { Sgn::Neg } else { Sgn::Pos };
322 let magnitude = Fee::from_u64(bigint.to_64x4()[0]);
323 Signed::create(magnitude, sgn)
324 };
325
326 let fee_excess_l = w.exists(convert_to_currency(fee_excess_l));
327 fee_excess_l.to_checked::<Fp>().value(w); let fee_excess_r = w.exists(convert_to_currency(fee_excess_r));
330 fee_excess_r.to_checked::<Fp>().value(w); (fee_token_l, fee_excess_l, fee_token_r, fee_excess_r)
333 }
334}
335
336fn eliminate_fee_excess<'a>(
341 (fee_token_l, fee_excess_l): (&'a TokenId, &'a Signed<Fee>),
342 (fee_token_m, fee_excess_m): (&'a TokenId, &'a Signed<Fee>),
343 (fee_token_r, fee_excess_r): (&'a TokenId, &'a Signed<Fee>),
344) -> Result<((&'a TokenId, Signed<Fee>), (&'a TokenId, Signed<Fee>)), String> {
345 let add_err = |x: &Signed<Fee>, y: &Signed<Fee>| -> Result<Signed<Fee>, String> {
346 x.add(y)
347 .ok_or_else(|| "Error adding fees: Overflow".to_string())
348 };
349
350 if fee_token_l == fee_token_m || fee_excess_l.magnitude.is_zero() {
351 let fee_excess_l = add_err(fee_excess_l, fee_excess_m)?;
352 Ok(((fee_token_m, fee_excess_l), (fee_token_r, *fee_excess_r)))
353 } else if fee_token_r == fee_token_m || fee_excess_r.magnitude.is_zero() {
354 let fee_excess_r = add_err(fee_excess_r, fee_excess_m)?;
355 Ok(((fee_token_l, *fee_excess_l), (fee_token_m, fee_excess_r)))
356 } else if fee_excess_m.magnitude.is_zero() {
357 Ok(((fee_token_l, *fee_excess_l), (fee_token_r, *fee_excess_r)))
358 } else {
359 Err(format!(
360 "Error eliminating fee excess: Excess for token {:?} \
361 {:?} was nonzero",
362 fee_token_m, fee_excess_m
363 ))
364 }
365}
366
367pub fn assert_equal_checked(_t1: &FeeExcess, t2: &FeeExcess, w: &mut Witness<Fp>) {
368 t2.fee_excess_l.to_checked::<Fp>().value(w);
369 t2.fee_excess_r.to_checked::<Fp>().value(w);
370}
371
372fn eliminate_fee_excess_checked<'a>(
373 (fee_token_l, fee_excess_l): (&'a TokenId, Fp),
374 (fee_token_m, fee_excess_m): (&'a TokenId, Fp),
375 (fee_token_r, fee_excess_r): (&'a TokenId, Fp),
376 w: &mut Witness<Fp>,
377) -> ((TokenId, Fp), (TokenId, Fp)) {
378 let mut combine = |fee_token: &TokenId, fee_excess: Fp, fee_excess_m: Fp| {
379 let fee_token_equal = field::equal(fee_token.0, fee_token_m.0, w);
380 let fee_excess_zero = field::equal(Fp::zero(), fee_excess, w);
381
382 let may_move = fee_token_equal.or(&fee_excess_zero, w);
383
384 let fee_token = w.exists_no_check(match fee_excess_zero {
385 Boolean::True => fee_token_m,
386 Boolean::False => fee_token,
387 });
388
389 let fee_excess_to_move = w.exists_no_check(match may_move {
390 Boolean::True => fee_excess_m,
391 Boolean::False => Fp::zero(),
392 });
393
394 (
395 (fee_token.clone(), fee_excess + fee_excess_to_move),
396 fee_excess_m - fee_excess_to_move,
397 )
398 };
399
400 let ((fee_token_l, fee_excess_l), fee_excess_m) =
401 combine(fee_token_l, fee_excess_l, fee_excess_m);
402 let ((fee_token_r, fee_excess_r), _fee_excess_m) =
403 combine(fee_token_r, fee_excess_r, fee_excess_m);
404
405 ((fee_token_l, fee_excess_l), (fee_token_r, fee_excess_r))
406}