defuse/contract/tokens/nep245/
resolver.rs

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