defuse/contract/tokens/
mod.rs1#[cfg(feature = "imt")]
2mod imt;
3mod nep141;
4mod nep171;
5mod nep245;
6
7use super::Contract;
8use defuse_core::{DefuseError, Result, token_id::TokenId};
9use defuse_near_utils::{Lock, REFUND_MEMO, UnwrapOrPanic, UnwrapOrPanicError};
10use defuse_nep245::{MtBurnEvent, MtEvent, MtMintEvent};
11use itertools::{Either, Itertools};
12use near_sdk::{AccountId, AccountIdRef, Gas, env, json_types::U128, serde_json};
13use std::borrow::Cow;
14
15pub const STORAGE_DEPOSIT_GAS: Gas = Gas::from_tgas(10);
16
17impl Contract {
18 pub(crate) fn deposit(
19 &mut self,
20 owner_id: AccountId,
21 tokens: impl IntoIterator<Item = (TokenId, u128)>,
22 memo: Option<&str>,
23 ) -> Result<()> {
24 let owner = self
25 .storage
26 .accounts
27 .get_or_create(owner_id.clone())
28 .as_inner_unchecked_mut();
30
31 let mut mint_event = MtMintEvent {
32 owner_id: owner_id.into(),
33 token_ids: Vec::new().into(),
34 amounts: Vec::new().into(),
35 memo: memo.map(Into::into),
36 };
37
38 for (token_id, amount) in tokens {
39 if amount == 0 {
40 return Err(DefuseError::InvalidIntent);
41 }
42
43 mint_event.token_ids.to_mut().push(token_id.to_string());
44 mint_event.amounts.to_mut().push(U128(amount));
45
46 let total_supply = self
47 .storage
48 .state
49 .total_supplies
50 .add(token_id.clone(), amount)
51 .ok_or(DefuseError::BalanceOverflow)?;
52 match token_id {
53 TokenId::Nep171(ref tid) => {
54 if total_supply > 1 {
55 return Err(DefuseError::NftAlreadyDeposited(tid.clone()));
56 }
57 }
58 TokenId::Nep141(_) | TokenId::Nep245(_) => {}
59 #[cfg(feature = "imt")]
60 TokenId::Imt(_) => {}
61 }
62
63 owner
64 .token_balances
65 .add(token_id, amount)
66 .ok_or(DefuseError::BalanceOverflow)?;
67 }
68
69 if !mint_event.amounts.is_empty() {
70 MtEvent::MtMint([mint_event].as_slice().into())
71 .check_refund()?
72 .emit();
73 }
74
75 Ok(())
76 }
77
78 pub(crate) fn withdraw(
79 &mut self,
80 owner_id: &AccountIdRef,
81 token_amounts: impl IntoIterator<Item = (TokenId, u128)>,
82 memo: Option<impl Into<String>>,
83 force: bool,
84 ) -> Result<()> {
85 let owner = self
86 .storage
87 .accounts
88 .get_mut(owner_id)
89 .ok_or_else(|| DefuseError::AccountNotFound(owner_id.to_owned()))?
90 .get_mut_maybe_forced(force)
91 .ok_or_else(|| DefuseError::AccountLocked(owner_id.to_owned()))?;
92
93 let mut burn_event = MtBurnEvent {
94 owner_id: Cow::Owned(owner_id.to_owned()),
95 authorized_id: None,
96 token_ids: Vec::new().into(),
97 amounts: Vec::new().into(),
98 memo: memo.map(Into::into).map(Into::into),
99 };
100
101 for (token_id, amount) in token_amounts {
102 if amount == 0 {
103 return Err(DefuseError::InvalidIntent);
104 }
105
106 burn_event.token_ids.to_mut().push(token_id.to_string());
107 burn_event.amounts.to_mut().push(U128(amount));
108
109 owner
110 .token_balances
111 .sub(token_id.clone(), amount)
112 .ok_or(DefuseError::BalanceOverflow)?;
113
114 self.storage
115 .state
116 .total_supplies
117 .sub(token_id, amount)
118 .ok_or(DefuseError::BalanceOverflow)?;
119 }
120
121 if !burn_event.amounts.is_empty() {
126 self.runtime.postponed_burns.mt_burn(burn_event);
127 }
128
129 Ok(())
130 }
131}
132
133impl Contract {
134 #[must_use]
135 pub(crate) fn mt_resolve_deposit_gas(token_count: usize) -> Gas {
136 const MT_RESOLVE_DEPOSIT_PER_TOKEN_GAS: Gas = Gas::from_tgas(2);
137 const MT_RESOLVE_DEPOSIT_BASE_GAS: Gas = Gas::from_tgas(4);
138
139 let token_count: u64 = token_count
140 .try_into()
141 .unwrap_or_else(|_| env::panic_str(&format!("token_count overflow: {token_count}")));
142
143 MT_RESOLVE_DEPOSIT_BASE_GAS
144 .checked_add(
145 MT_RESOLVE_DEPOSIT_PER_TOKEN_GAS
146 .checked_mul(token_count)
147 .unwrap_or_else(|| env::panic_str("gas calculation overflow")),
148 )
149 .unwrap_or_else(|| env::panic_str("gas calculation overflow"))
150 }
151
152 pub fn resolve_deposit_internal<'a, I>(&mut self, receiver_id: &AccountIdRef, tokens: I)
153 where
154 I: IntoIterator<Item = (TokenId, &'a mut u128)>,
155 I::IntoIter: ExactSizeIterator,
156 {
157 let tokens_iter = tokens.into_iter();
158 let tokens_count = tokens_iter.len();
159
160 let requested_refunds =
161 env::promise_result_checked(0, Self::mt_on_transfer_max_result_len(tokens_count))
162 .ok()
163 .and_then(|value| serde_json::from_slice::<Vec<U128>>(&value).ok())
164 .filter(|refunds| refunds.len() == tokens_count);
165
166 let mut burn_event = MtBurnEvent {
167 owner_id: Cow::Borrowed(receiver_id),
168 authorized_id: None,
169 token_ids: Vec::with_capacity(tokens_count).into(),
170 amounts: Vec::with_capacity(tokens_count).into(),
171 memo: Some(REFUND_MEMO.into()),
172 };
173
174 let Some(receiver) = self
175 .storage
176 .accounts
177 .get_mut(receiver_id)
178 .map(Lock::as_inner_unchecked_mut)
179 else {
180 tokens_iter.for_each(|(_, amount)| *amount = 0);
181 return;
182 };
183
184 for ((token_id, deposited), requested_refund) in
185 tokens_iter.zip_eq(requested_refunds.map_or_else(
186 || Either::Right(std::iter::repeat_n(None, tokens_count)),
187 |v| Either::Left(v.into_iter().map(|elem| Some(elem.0))),
188 ))
189 {
190 let requested_refund = requested_refund.unwrap_or(*deposited);
192 let balance_left = receiver.token_balances.amount_for(&token_id);
193 let refund_amount = balance_left.min(requested_refund);
194 *deposited = refund_amount;
195 if refund_amount == 0 {
196 continue;
197 }
198
199 burn_event.token_ids.to_mut().push(token_id.to_string());
200 burn_event.amounts.to_mut().push(U128(refund_amount));
201
202 receiver
203 .token_balances
204 .sub(token_id.clone(), refund_amount)
205 .ok_or(DefuseError::BalanceOverflow)
206 .unwrap_or_panic();
207
208 self.storage
209 .state
210 .total_supplies
211 .sub(token_id, refund_amount)
212 .ok_or(DefuseError::BalanceOverflow)
213 .unwrap_or_panic();
214 }
215
216 if !burn_event.amounts.is_empty() {
217 MtEvent::MtBurn([burn_event].as_slice().into())
218 .check_refund()
219 .unwrap_or_panic_display()
220 .emit();
221 }
222 }
223
224 const fn mt_on_transfer_max_result_len(amounts_count: usize) -> usize {
225 const MAX_LEN_PER_AMOUNT: usize =
227 " \"+340282366920938463463374607431768211455\",\n".len(); amounts_count
230 .saturating_mul(MAX_LEN_PER_AMOUNT)
231 .saturating_add("[\n]".len())
232 }
233}