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, promise_result_checked_json_with_len};
10use defuse_nep245::{MtBurnEvent, MtEvent, MtMintEvent};
11use itertools::{Either, Itertools};
12use near_sdk::{AccountId, AccountIdRef, FunctionError, 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(_) | TokenId::Imt(_) => {}
59 }
60
61 owner
62 .token_balances
63 .add(token_id, amount)
64 .ok_or(DefuseError::BalanceOverflow)?;
65 }
66
67 if !mint_event.amounts.is_empty() {
68 MtEvent::MtMint([mint_event].as_slice().into())
69 .check_refund()?
70 .emit();
71 }
72
73 Ok(())
74 }
75
76 pub(crate) fn withdraw(
77 &mut self,
78 owner_id: &AccountIdRef,
79 token_amounts: impl IntoIterator<Item = (TokenId, u128)>,
80 memo: Option<impl Into<String>>,
81 force: bool,
82 ) -> Result<()> {
83 let owner = self
84 .storage
85 .accounts
86 .get_mut(owner_id)
87 .ok_or_else(|| DefuseError::AccountNotFound(owner_id.to_owned()))?
88 .get_mut_maybe_forced(force)
89 .ok_or_else(|| DefuseError::AccountLocked(owner_id.to_owned()))?;
90
91 let mut burn_event = MtBurnEvent {
92 owner_id: Cow::Owned(owner_id.to_owned()),
93 authorized_id: None,
94 token_ids: Vec::new().into(),
95 amounts: Vec::new().into(),
96 memo: memo.map(Into::into).map(Into::into),
97 };
98
99 for (token_id, amount) in token_amounts {
100 if amount == 0 {
101 return Err(DefuseError::InvalidIntent);
102 }
103
104 burn_event.token_ids.to_mut().push(token_id.to_string());
105 burn_event.amounts.to_mut().push(U128(amount));
106
107 owner
108 .token_balances
109 .sub(token_id.clone(), amount)
110 .ok_or(DefuseError::BalanceOverflow)?;
111
112 self.storage
113 .state
114 .total_supplies
115 .sub(token_id, amount)
116 .ok_or(DefuseError::BalanceOverflow)?;
117 }
118
119 if !burn_event.amounts.is_empty() {
124 self.runtime.postponed_burns.mt_burn(burn_event);
125 }
126
127 Ok(())
128 }
129}
130
131impl Contract {
132 #[must_use]
133 pub(crate) fn mt_resolve_deposit_gas(token_count: usize) -> Gas {
134 const MT_RESOLVE_DEPOSIT_PER_TOKEN_GAS: Gas = Gas::from_tgas(2);
135 const MT_RESOLVE_DEPOSIT_BASE_GAS: Gas = Gas::from_tgas(4);
136
137 let token_count: u64 = token_count
138 .try_into()
139 .unwrap_or_else(|_| env::panic_str(&format!("token_count overflow: {token_count}")));
140
141 MT_RESOLVE_DEPOSIT_BASE_GAS
142 .checked_add(
143 MT_RESOLVE_DEPOSIT_PER_TOKEN_GAS
144 .checked_mul(token_count)
145 .ok_or(DefuseError::GasOverflow)
146 .unwrap_or_else(|err| err.panic()),
147 )
148 .ok_or(DefuseError::GasOverflow)
149 .unwrap_or_else(|err| err.panic())
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_else(|err| err.panic());
206
207 self.storage
208 .state
209 .total_supplies
210 .sub(token_id, refund_amount)
211 .ok_or(DefuseError::BalanceOverflow)
212 .unwrap_or_else(|err| err.panic());
213 }
214
215 if !burn_event.amounts.is_empty() {
216 MtEvent::MtBurn([burn_event].as_slice().into()).emit();
219 }
220 }
221}