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