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, promise_result_checked_json_with_len};
10use defuse_nep245::{MtBurnEvent, MtEvent, MtMintEvent};
11use itertools::{Either, Itertools};
12use near_sdk::{AccountId, AccountIdRef, Gas, env, json_types::U128};
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 = promise_result_checked_json_with_len::<Vec<U128>>(0, tokens_count)
161 .ok()
162 .and_then(Result::ok)
163 .filter(|refunds| refunds.len() == tokens_count);
164
165 let mut burn_event = MtBurnEvent {
166 owner_id: Cow::Borrowed(receiver_id),
167 authorized_id: None,
168 token_ids: Vec::with_capacity(tokens_count).into(),
169 amounts: Vec::with_capacity(tokens_count).into(),
170 memo: Some(REFUND_MEMO.into()),
171 };
172
173 let Some(receiver) = self
174 .storage
175 .accounts
176 .get_mut(receiver_id)
177 .map(Lock::as_inner_unchecked_mut)
178 else {
179 tokens_iter.for_each(|(_, amount)| *amount = 0);
180 return;
181 };
182
183 for ((token_id, deposited), requested_refund) in
184 tokens_iter.zip_eq(requested_refunds.map_or_else(
185 || Either::Right(std::iter::repeat_n(None, tokens_count)),
186 |v| Either::Left(v.into_iter().map(|elem| Some(elem.0))),
187 ))
188 {
189 let requested_refund = requested_refund.unwrap_or(*deposited);
190 let balance_left = receiver.token_balances.amount_for(&token_id);
191 let refund_amount = requested_refund.min(*deposited).min(balance_left);
193 *deposited = refund_amount;
194 if refund_amount == 0 {
195 continue;
196 }
197
198 burn_event.token_ids.to_mut().push(token_id.to_string());
199 burn_event.amounts.to_mut().push(U128(refund_amount));
200
201 receiver
202 .token_balances
203 .sub(token_id.clone(), refund_amount)
204 .ok_or(DefuseError::BalanceOverflow)
205 .unwrap_or_panic();
206
207 self.storage
208 .state
209 .total_supplies
210 .sub(token_id, refund_amount)
211 .ok_or(DefuseError::BalanceOverflow)
212 .unwrap_or_panic();
213 }
214
215 if !burn_event.amounts.is_empty() {
216 MtEvent::MtBurn([burn_event].as_slice().into()).emit();
219 }
220 }
221}