defuse/contract/tokens/nep245/
core.rs1use crate::contract::{Contract, ContractExt};
2use defuse_core::{
3 DefuseError, Result, engine::StateView, intents::tokens::NotifyOnTransfer, token_id::TokenId,
4};
5use defuse_near_utils::{UnwrapOrPanic, UnwrapOrPanicError};
6use defuse_nep245::{MtEvent, MtTransferEvent, MultiTokenCore, receiver::ext_mt_receiver};
7use near_plugins::{Pausable, pause};
8use near_sdk::{
9 AccountId, AccountIdRef, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env,
10 json_types::U128, near, require,
11};
12use std::borrow::Cow;
13
14#[near]
15impl MultiTokenCore for Contract {
16 #[payable]
17 fn mt_transfer(
18 &mut self,
19 receiver_id: AccountId,
20 token_id: defuse_nep245::TokenId,
21 amount: U128,
22 approval: Option<(AccountId, u64)>,
23 memo: Option<String>,
24 ) {
25 self.mt_batch_transfer(
26 receiver_id,
27 [token_id].into(),
28 [amount].into(),
29 approval.map(|a| vec![Some(a)]),
30 memo,
31 );
32 }
33
34 #[pause(name = "mt_transfer")]
35 #[payable]
36 fn mt_batch_transfer(
37 &mut self,
38 receiver_id: AccountId,
39 token_ids: Vec<defuse_nep245::TokenId>,
40 amounts: Vec<U128>,
41 approvals: Option<Vec<Option<(AccountId, u64)>>>,
42 memo: Option<String>,
43 ) {
44 assert_one_yocto();
45 require!(approvals.is_none(), "approvals are not supported");
46
47 self.internal_mt_batch_transfer(
48 &self.ensure_auth_predecessor_id(),
49 &receiver_id,
50 &token_ids,
51 &amounts,
52 memo.as_deref(),
53 false,
54 )
55 .unwrap_or_panic()
56 }
57
58 #[pause(name = "mt_transfer")]
59 #[payable]
60 fn mt_transfer_call(
61 &mut self,
62 receiver_id: AccountId,
63 token_id: defuse_nep245::TokenId,
64 amount: U128,
65 approval: Option<(AccountId, u64)>,
66 memo: Option<String>,
67 msg: String,
68 ) -> PromiseOrValue<Vec<U128>> {
69 self.mt_batch_transfer_call(
70 receiver_id,
71 [token_id].into(),
72 [amount].into(),
73 approval.map(|a| vec![Some(a)]),
74 memo,
75 msg,
76 )
77 }
78
79 #[pause(name = "mt_transfer")]
80 #[payable]
81 fn mt_batch_transfer_call(
82 &mut self,
83 receiver_id: AccountId,
84 token_ids: Vec<defuse_nep245::TokenId>,
85 amounts: Vec<U128>,
86 approvals: Option<Vec<Option<(AccountId, u64)>>>,
87 memo: Option<String>,
88 msg: String,
89 ) -> PromiseOrValue<Vec<U128>> {
90 assert_one_yocto();
91 require!(approvals.is_none(), "approvals are not supported");
92
93 self.internal_mt_batch_transfer_call(
94 self.ensure_auth_predecessor_id(),
95 receiver_id,
96 token_ids,
97 amounts,
98 memo.as_deref(),
99 msg,
100 false,
101 )
102 .unwrap_or_panic()
103 }
104
105 fn mt_token(
106 &self,
107 token_ids: Vec<defuse_nep245::TokenId>,
108 ) -> Vec<Option<defuse_nep245::Token>> {
109 token_ids
110 .into_iter()
111 .map(|token_id| {
112 self.total_supplies
113 .contains_key(&token_id.parse().ok()?)
114 .then_some(defuse_nep245::Token {
115 token_id,
116 owner_id: None,
117 })
118 })
119 .collect()
120 }
121
122 fn mt_balance_of(&self, account_id: AccountId, token_id: defuse_nep245::TokenId) -> U128 {
123 U128(self.internal_mt_balance_of(&account_id, &token_id))
124 }
125
126 fn mt_batch_balance_of(
127 &self,
128 account_id: AccountId,
129 token_ids: Vec<defuse_nep245::TokenId>,
130 ) -> Vec<U128> {
131 token_ids
132 .into_iter()
133 .map(|token_id| self.internal_mt_balance_of(&account_id, &token_id))
134 .map(U128)
135 .collect()
136 }
137
138 fn mt_supply(&self, token_id: defuse_nep245::TokenId) -> Option<U128> {
139 Some(U128(
140 self.total_supplies.amount_for(&token_id.parse().ok()?),
141 ))
142 }
143
144 fn mt_batch_supply(&self, token_ids: Vec<defuse_nep245::TokenId>) -> Vec<Option<U128>> {
145 token_ids
146 .into_iter()
147 .map(|token_id| self.mt_supply(token_id))
148 .collect()
149 }
150}
151
152impl Contract {
153 pub(crate) fn internal_mt_balance_of(
154 &self,
155 account_id: &AccountIdRef,
156 token_id: &defuse_nep245::TokenId,
157 ) -> u128 {
158 let Ok(token_id) = token_id.parse() else {
159 return 0;
160 };
161 self.balance_of(account_id, &token_id)
162 }
163
164 pub(crate) fn internal_mt_batch_transfer(
165 &mut self,
166 sender_id: &AccountIdRef,
167 receiver_id: &AccountIdRef,
168 token_ids: &[defuse_nep245::TokenId],
169 amounts: &[U128],
170 memo: Option<&str>,
171 force: bool,
172 ) -> Result<()> {
173 if sender_id == receiver_id || token_ids.len() != amounts.len() || amounts.is_empty() {
174 return Err(DefuseError::InvalidIntent);
175 }
176
177 for (token_id, amount) in token_ids.iter().zip(amounts.iter().map(|a| a.0)) {
178 if amount == 0 {
179 return Err(DefuseError::InvalidIntent);
180 }
181 let token_id: TokenId = token_id.parse()?;
182
183 self.accounts
184 .get_mut(sender_id)
185 .ok_or_else(|| DefuseError::AccountNotFound(sender_id.to_owned()))?
186 .get_mut_maybe_forced(force)
187 .ok_or_else(|| DefuseError::AccountLocked(sender_id.to_owned()))?
188 .token_balances
189 .sub(token_id.clone(), amount)
190 .ok_or(DefuseError::BalanceOverflow)?;
191 self.accounts
192 .get_or_create(receiver_id.to_owned())
193 .as_inner_unchecked_mut()
195 .token_balances
196 .add(token_id, amount)
197 .ok_or(DefuseError::BalanceOverflow)?;
198 }
199
200 MtEvent::MtTransfer(
201 [MtTransferEvent {
202 authorized_id: None,
203 old_owner_id: sender_id.into(),
204 new_owner_id: Cow::Borrowed(receiver_id),
205 token_ids: token_ids.into(),
206 amounts: amounts.into(),
207 memo: memo.map(Into::into),
208 }]
209 .as_slice()
210 .into(),
211 )
212 .check_refund()?
213 .emit();
214
215 Ok(())
216 }
217
218 #[allow(clippy::too_many_arguments)]
219 pub(crate) fn internal_mt_batch_transfer_call(
220 &mut self,
221 sender_id: AccountId,
222 receiver_id: AccountId,
223 token_ids: Vec<defuse_nep245::TokenId>,
224 amounts: Vec<U128>,
225 memo: Option<&str>,
226 msg: String,
227 force: bool,
228 ) -> Result<PromiseOrValue<Vec<U128>>> {
229 self.internal_mt_batch_transfer(
230 &sender_id,
231 &receiver_id,
232 &token_ids,
233 &amounts,
234 memo,
235 force,
236 )?;
237
238 Ok(Self::notify_and_resolve_transfer(
239 sender_id,
240 receiver_id,
241 token_ids,
242 amounts,
243 NotifyOnTransfer::new(msg),
244 ))
245 }
246
247 pub(crate) fn notify_and_resolve_transfer(
248 sender_id: AccountId,
249 receiver_id: AccountId,
250 token_ids: Vec<defuse_nep245::TokenId>,
251 amounts: Vec<U128>,
252 notify: NotifyOnTransfer,
253 ) -> PromiseOrValue<Vec<U128>> {
254 let previous_owner_ids = vec![sender_id.clone(); token_ids.len()];
255
256 Self::notify_on_transfer(
257 sender_id,
258 previous_owner_ids.clone(),
259 receiver_id.clone(),
260 token_ids.clone(),
261 amounts.clone(),
262 notify,
263 )
264 .then(
265 Self::ext(env::current_account_id())
266 .with_static_gas(Self::mt_resolve_gas(token_ids.len()))
267 .with_unused_gas_weight(0)
269 .mt_resolve_transfer(previous_owner_ids, receiver_id, token_ids, amounts, None),
270 )
271 .into()
272 }
273
274 pub(crate) fn notify_on_transfer(
275 sender_id: AccountId,
276 previous_owner_ids: Vec<AccountId>,
277 receiver_id: AccountId,
278 token_ids: Vec<defuse_nep245::TokenId>,
279 amounts: Vec<U128>,
280 notify: NotifyOnTransfer,
281 ) -> Promise {
282 let mut p = Promise::new(receiver_id);
283
284 if let Some(state_init) = notify.state_init {
285 p = p.state_init(
291 state_init,
292 NearToken::ZERO,
294 );
295 }
296
297 ext_mt_receiver::ext_on(p)
298 .with_static_gas(notify.min_gas.unwrap_or_default())
299 .with_unused_gas_weight(1)
301 .mt_on_transfer(
302 sender_id,
303 previous_owner_ids,
304 token_ids,
305 amounts,
306 notify.msg,
307 )
308 }
309
310 #[must_use]
311 fn mt_resolve_gas(token_count: usize) -> Gas {
312 const MT_RESOLVE_TRANSFER_PER_TOKEN_GAS: Gas = Gas::from_tgas(2);
315 const MT_RESOLVE_TRANSFER_BASE_GAS: Gas = Gas::from_tgas(8);
316 let token_count: u64 = token_count.try_into().unwrap_or_panic_display();
317
318 MT_RESOLVE_TRANSFER_BASE_GAS
319 .checked_add(
320 MT_RESOLVE_TRANSFER_PER_TOKEN_GAS
321 .checked_mul(token_count)
322 .unwrap_or_panic(),
323 )
324 .unwrap_or_panic()
325 }
326}