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