defuse/contract/tokens/nep245/
resolver.rs

1use std::borrow::Cow;
2
3use defuse_near_utils::{
4    Lock, REFUND_MEMO, UnwrapOrPanic, UnwrapOrPanicError, promise_result_checked_json_with_len,
5};
6use defuse_nep245::{
7    ClearedApproval, MtEvent, MtTransferEvent, TokenId, resolver::MultiTokenResolver,
8};
9use near_sdk::{AccountId, json_types::U128, near, require};
10
11use crate::contract::{Contract, ContractExt};
12
13#[near]
14impl MultiTokenResolver for Contract {
15    #[private]
16    fn mt_resolve_transfer(
17        &mut self,
18        previous_owner_ids: Vec<AccountId>,
19        receiver_id: AccountId,
20        token_ids: Vec<TokenId>,
21        #[allow(unused_mut)] mut amounts: Vec<U128>,
22        approvals: Option<Vec<Option<Vec<ClearedApproval>>>>,
23    ) -> Vec<U128> {
24        require!(approvals.is_none(), "approvals are not supported");
25        require!(
26            !token_ids.is_empty()
27                && previous_owner_ids.len() == token_ids.len()
28                && amounts.len() == token_ids.len(),
29            "invalid args"
30        );
31
32        let mut refunds = promise_result_checked_json_with_len::<Vec<U128>>(0, amounts.len())
33            .ok()
34            .and_then(Result::ok)
35            .filter(|refund| refund.len() == amounts.len())
36            .unwrap_or_else(|| amounts.clone());
37
38        let sender_id = previous_owner_ids.first().cloned().unwrap_or_panic();
39
40        for ((token_id, previous_owner_id), (amount, refund)) in token_ids
41            .iter()
42            .map(|token_id| token_id.parse().unwrap_or_panic_display())
43            .zip(previous_owner_ids)
44            .zip(amounts.iter_mut().zip(&mut refunds))
45        {
46            require!(
47                sender_id == previous_owner_id,
48                "approvals are not supported"
49            );
50
51            refund.0 = refund.0.min(amount.0);
52            let Some(receiver) = self
53                .accounts
54                .get_mut(&receiver_id)
55                // NOTE: refunds from locked accounts are allowed to prevent
56                // senders from loss of funds.
57                //
58                // Receiver's account might have been locked between
59                // `mt_transfer_call()` and `mt_resolve_transfer()`, so that
60                // outgoing transfers are no longer allowed for this account.
61                // But here we distinguish between regular transfers and
62                // refunds, despite it would lead to `mt_transfer` event
63                // emitted with `old_owner_id` being the locked account.
64                //
65                // Locked receivers still won't be able to transfer funds in
66                // `<receiver_id>::on_mt_transfer()` implementation.
67                .map(Lock::as_inner_unchecked_mut)
68            else {
69                // receiver doesn't have an account, so nowhere to refund from
70                return amounts;
71            };
72            let receiver_balance = receiver.token_balances.amount_for(&token_id);
73            // refund maximum what we can
74            refund.0 = refund.0.min(receiver_balance);
75            if refund.0 == 0 {
76                // noting to refund
77                continue;
78            }
79
80            // withdraw refund
81            receiver
82                .token_balances
83                .sub(token_id.clone(), refund.0)
84                .unwrap_or_panic();
85            // deposit refund
86            self.accounts
87                .get_or_create(previous_owner_id)
88                // refunds are allowed for locked accounts
89                .as_inner_unchecked_mut()
90                .token_balances
91                .add(token_id, refund.0)
92                .unwrap_or_panic();
93
94            // update as used amount in-place
95            amount.0 -= refund.0;
96        }
97
98        let (refunded_token_ids, refunded_amounts): (Vec<_>, Vec<_>) = token_ids
99            .into_iter()
100            .zip(refunds)
101            .filter(|(_token_id, refund)| refund.0 > 0)
102            .unzip();
103
104        if !refunded_amounts.is_empty() {
105            MtEvent::MtTransfer(Cow::Borrowed(
106                [MtTransferEvent {
107                    authorized_id: None,
108                    old_owner_id: Cow::Borrowed(&receiver_id),
109                    new_owner_id: Cow::Borrowed(&sender_id),
110                    token_ids: refunded_token_ids.into(),
111                    amounts: refunded_amounts.into(),
112                    memo: Some(REFUND_MEMO.into()),
113                }]
114                .as_slice(),
115            ))
116            // NOTE: No need for `check_refund()` here since this IS the refund.
117            // The refund memo size was already accounted for in the original transfer.
118            .emit();
119        }
120
121        amounts
122    }
123}