1use crate::{
2 DefuseError, Nonce, NoncePrefix, Result, Salt,
3 amounts::Amounts,
4 fees::Pips,
5 intents::{
6 auth::AuthCall,
7 token_diff::TokenDeltas,
8 tokens::{
9 FtWithdraw, MtWithdraw, NativeWithdraw, NftWithdraw, NotifyOnTransfer, StorageDeposit,
10 },
11 },
12 public_key::PublicKey,
13 token_id::TokenId,
14};
15use defuse_map_utils::cleanup::DefaultMap;
16use defuse_nep245::{MtEvent, MtTransferEvent};
17use near_sdk::{AccountId, AccountIdRef, json_types::U128};
18use serde::{Deserialize, Serialize};
19use serde_with::{DisplayFromStr, serde_as};
20use std::{
21 borrow::Cow,
22 cmp::Reverse,
23 collections::{BTreeMap, HashMap},
24 iter,
25};
26
27use super::{State, StateView};
28
29pub struct Deltas<S> {
30 state: S,
31 deltas: TransferMatcher,
32}
33
34impl<S> Deltas<S> {
35 #[inline]
36 pub fn new(state: S) -> Self {
37 Self {
38 state,
39 deltas: TransferMatcher::new(),
40 }
41 }
42
43 #[inline]
44 pub fn finalize(self) -> Result<Transfers, InvariantViolated> {
45 self.deltas.finalize()
46 }
47}
48
49impl<S> StateView for Deltas<S>
50where
51 S: StateView,
52{
53 #[inline]
54 fn verifying_contract(&self) -> Cow<'_, AccountIdRef> {
55 self.state.verifying_contract()
56 }
57
58 #[inline]
59 fn wnear_id(&self) -> Cow<'_, AccountIdRef> {
60 self.state.wnear_id()
61 }
62
63 #[inline]
64 fn fee(&self) -> Pips {
65 self.state.fee()
66 }
67
68 #[inline]
69 fn fee_collector(&self) -> Cow<'_, AccountIdRef> {
70 self.state.fee_collector()
71 }
72
73 #[inline]
74 fn has_public_key(&self, account_id: &AccountIdRef, public_key: &PublicKey) -> bool {
75 self.state.has_public_key(account_id, public_key)
76 }
77
78 #[inline]
79 fn iter_public_keys(&self, account_id: &AccountIdRef) -> impl Iterator<Item = PublicKey> + '_ {
80 self.state.iter_public_keys(account_id)
81 }
82
83 #[inline]
84 fn is_nonce_used(&self, account_id: &AccountIdRef, nonce: Nonce) -> bool {
85 self.state.is_nonce_used(account_id, nonce)
86 }
87
88 #[inline]
89 fn balance_of(&self, account_id: &AccountIdRef, token_id: &TokenId) -> u128 {
90 self.state.balance_of(account_id, token_id)
91 }
92
93 #[inline]
94 fn is_account_locked(&self, account_id: &AccountIdRef) -> bool {
95 self.state.is_account_locked(account_id)
96 }
97
98 #[inline]
99 fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountIdRef) -> bool {
100 self.state.is_auth_by_predecessor_id_enabled(account_id)
101 }
102
103 #[inline]
104 fn is_valid_salt(&self, salt: Salt) -> bool {
105 self.state.is_valid_salt(salt)
106 }
107}
108
109impl<S> State for Deltas<S>
110where
111 S: State,
112{
113 #[inline]
114 fn add_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> {
115 self.state.add_public_key(account_id, public_key)
116 }
117
118 #[inline]
119 fn remove_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> {
120 self.state.remove_public_key(account_id, public_key)
121 }
122
123 #[inline]
124 fn commit_nonce(&mut self, account_id: AccountId, nonce: Nonce) -> Result<()> {
125 self.state.commit_nonce(account_id, nonce)
126 }
127
128 #[inline]
129 fn cleanup_nonce_by_prefix(
130 &mut self,
131 account_id: &AccountIdRef,
132 prefix: NoncePrefix,
133 ) -> Result<bool> {
134 self.state.cleanup_nonce_by_prefix(account_id, prefix)
135 }
136
137 fn internal_add_balance(
138 &mut self,
139 owner_id: AccountId,
140 tokens: impl IntoIterator<Item = (TokenId, u128)>,
141 ) -> Result<()> {
142 for (token_id, amount) in tokens {
143 self.state
144 .internal_add_balance(owner_id.clone(), [(token_id.clone(), amount)])?;
145 if !self.deltas.deposit(owner_id.clone(), token_id, amount) {
146 return Err(DefuseError::BalanceOverflow);
147 }
148 }
149 Ok(())
150 }
151
152 fn internal_sub_balance(
153 &mut self,
154 owner_id: &AccountIdRef,
155 tokens: impl IntoIterator<Item = (TokenId, u128)>,
156 ) -> Result<()> {
157 for (token_id, amount) in tokens {
158 self.state
159 .internal_sub_balance(owner_id, [(token_id.clone(), amount)])?;
160 if !self.deltas.withdraw(owner_id.to_owned(), token_id, amount) {
161 return Err(DefuseError::BalanceOverflow);
162 }
163 }
164 Ok(())
165 }
166
167 #[inline]
168 fn ft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: FtWithdraw) -> Result<()> {
169 self.state.ft_withdraw(owner_id, withdraw)
170 }
171
172 #[inline]
173 fn nft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NftWithdraw) -> Result<()> {
174 self.state.nft_withdraw(owner_id, withdraw)
175 }
176
177 #[inline]
178 fn mt_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: MtWithdraw) -> Result<()> {
179 self.state.mt_withdraw(owner_id, withdraw)
180 }
181
182 #[inline]
183 fn native_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NativeWithdraw) -> Result<()> {
184 self.state.native_withdraw(owner_id, withdraw)
185 }
186
187 #[inline]
188 fn notify_on_transfer(
189 &self,
190 sender_id: &AccountIdRef,
191 receiver_id: AccountId,
192 tokens: Amounts,
193 notification: NotifyOnTransfer,
194 ) {
195 self.state
196 .notify_on_transfer(sender_id, receiver_id, tokens, notification);
197 }
198
199 #[inline]
200 fn storage_deposit(
201 &mut self,
202 owner_id: &AccountIdRef,
203 storage_deposit: StorageDeposit,
204 ) -> Result<()> {
205 self.state.storage_deposit(owner_id, storage_deposit)
206 }
207
208 #[inline]
209 fn set_auth_by_predecessor_id(&mut self, account_id: AccountId, enable: bool) -> Result<bool> {
210 self.state.set_auth_by_predecessor_id(account_id, enable)
211 }
212
213 #[inline]
214 fn auth_call(&mut self, signer_id: &AccountIdRef, auth_call: AuthCall) -> Result<()> {
215 self.state.auth_call(signer_id, auth_call)
216 }
217
218 #[inline]
219 fn mint(&mut self, owner_id: AccountId, tokens: Amounts, memo: Option<String>) -> Result<()> {
220 self.state.mint(owner_id, tokens, memo)
221 }
222
223 #[inline]
224 fn burn(
225 &mut self,
226 owner_id: &AccountIdRef,
227 tokens: Amounts,
228 memo: Option<String>,
229 ) -> Result<()> {
230 self.state.burn(owner_id, tokens, memo)
231 }
232}
233
234#[derive(Debug, Default)]
243pub struct TransferMatcher(HashMap<TokenId, TokenTransferMatcher>);
244
245impl TransferMatcher {
246 #[inline]
247 pub fn new() -> Self {
248 Self(HashMap::new())
249 }
250
251 #[inline]
252 pub fn deposit(&mut self, owner_id: AccountId, token_id: TokenId, amount: u128) -> bool {
253 self.0.entry_or_default(token_id).deposit(owner_id, amount)
254 }
255
256 #[inline]
257 pub fn withdraw(&mut self, owner_id: AccountId, token_id: TokenId, amount: u128) -> bool {
258 self.0.entry_or_default(token_id).withdraw(owner_id, amount)
259 }
260
261 #[inline]
262 pub fn add_delta(&mut self, owner_id: AccountId, token_id: TokenId, delta: i128) -> bool {
263 self.0.entry_or_default(token_id).add_delta(owner_id, delta)
264 }
265
266 pub fn finalize(self) -> Result<Transfers, InvariantViolated> {
269 let mut transfers = Transfers::default();
270 let mut deltas = TokenDeltas::default();
271 for (token_id, transfer_matcher) in self.0 {
272 if let Err(unmatched) = transfer_matcher.finalize_into(&token_id, &mut transfers)
273 && (unmatched == 0 || deltas.apply_delta(token_id, unmatched).is_none())
274 {
275 return Err(InvariantViolated::Overflow);
276 }
277 }
278 if !deltas.is_empty() {
279 return Err(InvariantViolated::UnmatchedDeltas {
280 unmatched_deltas: deltas,
281 });
282 }
283 Ok(transfers)
284 }
285}
286
287type AccountAmounts = Amounts<HashMap<AccountId, u128>>;
288
289#[derive(Debug, Default, PartialEq, Eq)]
291pub struct TokenTransferMatcher {
292 deposits: AccountAmounts,
293 withdrawals: AccountAmounts,
294}
295
296impl TokenTransferMatcher {
297 #[inline]
298 pub fn deposit(&mut self, owner_id: AccountId, amount: u128) -> bool {
299 Self::sub_add(&mut self.withdrawals, &mut self.deposits, owner_id, amount)
300 }
301
302 #[inline]
303 pub fn withdraw(&mut self, owner_id: AccountId, amount: u128) -> bool {
304 Self::sub_add(&mut self.deposits, &mut self.withdrawals, owner_id, amount)
305 }
306
307 #[inline]
308 pub fn add_delta(&mut self, owner_id: AccountId, delta: i128) -> bool {
309 let amount = delta.unsigned_abs();
310 if delta.is_negative() {
311 self.withdraw(owner_id, amount)
312 } else {
313 self.deposit(owner_id, amount)
314 }
315 }
316
317 fn sub_add(
318 sub: &mut AccountAmounts,
319 add: &mut AccountAmounts,
320 owner_id: AccountId,
321 mut amount: u128,
322 ) -> bool {
323 let s = sub.amount_for(&owner_id);
324 if s > 0 {
325 let a = s.min(amount);
326 sub.sub(owner_id.clone(), a)
327 .unwrap_or_else(|| unreachable!());
328 amount = amount.saturating_sub(a);
329 if amount == 0 {
330 return true;
331 }
332 }
333 add.add(owner_id, amount).is_some()
334 }
335
336 pub fn finalize_into(self, token_id: &TokenId, transfers: &mut Transfers) -> Result<(), i128> {
339 let [mut deposits, mut withdrawals] = [self.deposits, self.withdrawals].map(|amounts| {
341 let mut amounts: Vec<_> = amounts.into_iter().collect();
342 amounts.sort_unstable_by_key(|(_, amount)| Reverse(*amount));
343 amounts.into_iter()
344 });
345
346 let (mut deposit, mut withdraw) = (deposits.next(), withdrawals.next());
348
349 while let Some(((sender, send), (receiver, receive))) =
351 withdraw.as_mut().zip(deposit.as_mut())
352 {
353 let transfer = (*send).min(*receive);
355 transfers
356 .transfer(sender.clone(), receiver.clone(), token_id.clone(), transfer)
357 .unwrap_or_else(|| unreachable!());
360
361 *send = send.saturating_sub(transfer);
363 *receive = receive.saturating_sub(transfer);
364
365 if *send == 0 {
366 withdraw = withdrawals.next();
368 }
369 if *receive == 0 {
370 deposit = deposits.next();
372 }
373 }
374
375 if let Some((_, send)) = withdraw {
377 return Err(withdrawals
378 .try_fold(send, |total, (_, s)| total.checked_add(s))
379 .and_then(|total| i128::try_from(total).ok())
380 .and_then(i128::checked_neg)
381 .unwrap_or_default());
382 }
383 if let Some((_, receive)) = deposit {
385 return Err(deposits
386 .try_fold(receive, |total, (_, r)| total.checked_add(r))
387 .and_then(|total| i128::try_from(total).ok())
388 .unwrap_or_default());
389 }
390
391 Ok(())
392 }
393}
394
395#[must_use]
397#[derive(Debug, Default, PartialEq, Eq)]
398pub struct Transfers(
399 HashMap<AccountId, HashMap<AccountId, Amounts<HashMap<TokenId, u128>>>>,
401);
402
403impl Transfers {
404 #[must_use]
405 pub fn transfer(
406 &mut self,
407 sender_id: AccountId,
408 receiver_id: AccountId,
409 token_id: TokenId,
410 amount: u128,
411 ) -> Option<u128> {
412 let mut sender = self.0.entry_or_default(sender_id);
413 let mut receiver = sender.entry_or_default(receiver_id);
414 receiver.add(token_id, amount)
415 }
416
417 pub fn with_transfer(
418 mut self,
419 sender_id: AccountId,
420 receiver_id: AccountId,
421 token_id: TokenId,
422 amount: u128,
423 ) -> Option<Self> {
424 self.transfer(sender_id, receiver_id, token_id, amount)?;
425 Some(self)
426 }
427
428 pub fn as_mt_event(&self) -> Option<MtEvent<'_>> {
429 if self.0.is_empty() {
430 return None;
431 }
432 Some(MtEvent::MtTransfer(
433 self.0
434 .iter()
435 .flat_map(|(sender_id, transfers)| iter::repeat(sender_id).zip(transfers))
436 .map(|(sender_id, (receiver_id, transfers))| {
437 let (token_ids, amounts) = transfers
438 .iter()
439 .map(|(token_id, amount)| (token_id.to_string(), U128(*amount)))
440 .unzip();
441 MtTransferEvent {
442 authorized_id: None,
443 old_owner_id: Cow::Borrowed(sender_id),
444 new_owner_id: Cow::Borrowed(receiver_id),
445 token_ids: Cow::Owned(token_ids),
446 amounts: Cow::Owned(amounts),
447 memo: None,
448 }
449 })
450 .collect::<Vec<_>>()
451 .into(),
452 ))
453 }
454}
455
456#[serde_as]
457#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459#[serde(tag = "error", rename_all = "snake_case")]
460pub enum InvariantViolated {
461 UnmatchedDeltas {
462 #[serde_as(as = "Amounts<BTreeMap<_, DisplayFromStr>>")]
463 unmatched_deltas: TokenDeltas,
464 },
465 Overflow,
466}
467
468impl InvariantViolated {
469 #[inline]
470 pub const fn as_unmatched_deltas(&self) -> Option<&TokenDeltas> {
471 match self {
472 Self::UnmatchedDeltas {
473 unmatched_deltas: deltas,
474 } => Some(deltas),
475 Self::Overflow => None,
476 }
477 }
478
479 #[inline]
480 pub fn into_unmatched_deltas(self) -> Option<TokenDeltas> {
481 match self {
482 Self::UnmatchedDeltas {
483 unmatched_deltas: deltas,
484 } => Some(deltas),
485 Self::Overflow => None,
486 }
487 }
488}
489
490#[cfg(test)]
491#[allow(clippy::many_single_char_names)]
492mod tests {
493 use crate::token_id::nep141::Nep141TokenId;
494
495 use super::*;
496
497 #[test]
498 fn test_transfers() {
499 let mut transfers = TransferMatcher::default();
500 let [a, b, c, d, e, f, g]: [AccountId; 7] =
501 ["a", "b", "c", "d", "e", "f", "g"].map(|s| format!("{s}.near").parse().unwrap());
502 let [ft1, ft2] = ["ft1", "ft2"].map(|a| {
503 TokenId::from(Nep141TokenId::new(
504 format!("{a}.near").parse::<AccountId>().unwrap(),
505 ))
506 });
507
508 let deltas: HashMap<AccountId, TokenDeltas> = [
509 (&a, [(&ft1, -5), (&ft2, 1)].as_slice()),
510 (&b, [(&ft1, 4), (&ft2, -1)].as_slice()),
511 (&c, [(&ft1, 3)].as_slice()),
512 (&d, [(&ft1, -10)].as_slice()),
513 (&e, [(&ft1, -1)].as_slice()),
514 (&f, [(&ft1, 10)].as_slice()),
515 (&g, [(&ft1, -1)].as_slice()),
516 ]
517 .into_iter()
518 .map(|(owner_id, deltas)| {
519 (
520 owner_id.clone(),
521 TokenDeltas::default()
522 .with_apply_deltas(
523 deltas
524 .iter()
525 .map(|(token_id, delta)| ((*token_id).clone(), *delta)),
526 )
527 .unwrap(),
528 )
529 })
530 .collect();
531
532 for (owner, (token_id, delta)) in deltas
533 .iter()
534 .flat_map(|(owner_id, deltas)| iter::repeat(owner_id).zip(deltas))
535 {
536 assert!(transfers.add_delta(owner.clone(), token_id.clone(), *delta));
537 }
538
539 let transfers = transfers.finalize().unwrap();
540 let mut new_deltas: HashMap<AccountId, TokenDeltas> = HashMap::new();
541
542 for (sender_id, transfers) in transfers.0 {
543 for (receiver_id, amounts) in transfers {
544 for (token_id, amount) in amounts {
545 new_deltas
546 .entry_or_default(sender_id.clone())
547 .sub(token_id.clone(), amount)
548 .unwrap();
549
550 new_deltas
551 .entry_or_default(receiver_id.clone())
552 .add(token_id, amount)
553 .unwrap();
554 }
555 }
556 }
557
558 assert_eq!(new_deltas, deltas);
559 }
560
561 #[test]
562 fn test_unmatched() {
563 let mut deltas = TransferMatcher::default();
564 let [a, b, _c, d, e, f, g]: [AccountId; 7] =
565 ["a", "b", "c", "d", "e", "f", "g"].map(|s| format!("{s}.near").parse().unwrap());
566 let [ft1, ft2] = ["ft1", "ft2"].map(|a| {
567 TokenId::from(Nep141TokenId::new(
568 format!("{a}.near").parse::<AccountId>().unwrap(),
569 ))
570 });
571
572 for (owner, token_id, delta) in [
573 (&a, &ft1, -5),
574 (&b, &ft1, 4),
575 (&d, &ft1, -10),
576 (&e, &ft1, -1),
577 (&f, &ft1, 10),
578 (&g, &ft1, -1),
579 (&a, &ft2, -1),
580 ] {
581 assert!(deltas.add_delta(owner.clone(), token_id.clone(), delta));
582 }
583
584 assert_eq!(
585 deltas.finalize().unwrap_err(),
586 InvariantViolated::UnmatchedDeltas {
587 unmatched_deltas: TokenDeltas::default()
588 .with_apply_delta(ft1, -3)
589 .unwrap()
590 .with_apply_delta(ft2, -1)
591 .unwrap()
592 }
593 );
594 }
595}