multiversx_sc/storage/mappers/token/
fungible_token_mapper.rs1use multiversx_chain_core::types::EsdtLocalRole;
2
3use crate::{
4 abi::{TypeAbi, TypeAbiFrom},
5 api::ErrorApiImpl,
6 codec::{EncodeErrorHandler, TopEncodeMulti, TopEncodeMultiOutput},
7 storage::mappers::{
8 source::{CurrentStorage, StorageAddress},
9 StorageMapperFromAddress,
10 },
11 storage_clear, storage_get, storage_get_len, storage_set,
12 types::{
13 system_proxy::{ESDTSystemSCProxy, FungibleTokenProperties},
14 ESDTSystemSCAddress, ManagedRef, Tx,
15 },
16};
17
18use super::{
19 super::StorageMapper,
20 error::{
21 INVALID_PAYMENT_TOKEN_ERR_MSG, INVALID_TOKEN_ID_ERR_MSG, MUST_SET_TOKEN_ID_ERR_MSG,
22 PENDING_ERR_MSG, TOKEN_ID_ALREADY_SET_ERR_MSG,
23 },
24 TokenMapperState,
25};
26use crate::{
27 abi::TypeName,
28 api::{CallTypeApi, StorageMapperApi},
29 contract_base::{BlockchainWrapper, SendWrapper},
30 storage::StorageKey,
31 types::{
32 BigUint, CallbackClosure, EsdtTokenPayment, EsdtTokenType, ManagedAddress, ManagedBuffer,
33 ManagedType, ManagedVec, TokenIdentifier,
34 },
35};
36
37pub(crate) const DEFAULT_ISSUE_CALLBACK_NAME: &str = "default_issue_cb";
38pub(crate) const DEFAULT_ISSUE_WITH_INIT_SUPPLY_CALLBACK_NAME: &str =
39 "default_issue_init_supply_cb";
40
41pub struct FungibleTokenMapper<SA, A = CurrentStorage>
42where
43 SA: StorageMapperApi + CallTypeApi,
44 A: StorageAddress<SA>,
45{
46 key: StorageKey<SA>,
47 token_state: TokenMapperState<SA>,
48 address: A,
49}
50
51impl<SA> StorageMapper<SA> for FungibleTokenMapper<SA, CurrentStorage>
52where
53 SA: StorageMapperApi + CallTypeApi,
54{
55 fn new(base_key: StorageKey<SA>) -> Self {
56 Self {
57 token_state: storage_get(base_key.as_ref()),
58 key: base_key,
59 address: CurrentStorage,
60 }
61 }
62}
63
64impl<SA> StorageMapperFromAddress<SA> for FungibleTokenMapper<SA, ManagedAddress<SA>>
65where
66 SA: StorageMapperApi + CallTypeApi,
67{
68 fn new_from_address(address: ManagedAddress<SA>, base_key: StorageKey<SA>) -> Self {
69 Self {
70 token_state: storage_get(base_key.as_ref()),
71 key: base_key,
72 address,
73 }
74 }
75}
76
77impl<SA> FungibleTokenMapper<SA, CurrentStorage>
78where
79 SA: StorageMapperApi + CallTypeApi,
80{
81 pub fn issue(
100 &self,
101 issue_cost: BigUint<SA>,
102 token_display_name: ManagedBuffer<SA>,
103 token_ticker: ManagedBuffer<SA>,
104 initial_supply: BigUint<SA>,
105 num_decimals: usize,
106 opt_callback: Option<CallbackClosure<SA>>,
107 ) -> ! {
108 self.check_not_set();
109
110 let callback = match opt_callback {
111 Some(cb) => cb,
112 None => self.default_callback_closure_obj(&initial_supply),
113 };
114 let properties = FungibleTokenProperties {
115 num_decimals,
116 ..Default::default()
117 };
118
119 storage_set(self.get_storage_key(), &TokenMapperState::<SA>::Pending);
120 Tx::new_tx_from_sc()
121 .to(ESDTSystemSCAddress)
122 .typed(ESDTSystemSCProxy)
123 .issue_fungible(
124 issue_cost,
125 &token_display_name,
126 &token_ticker,
127 &initial_supply,
128 properties,
129 )
130 .callback(callback)
131 .async_call_and_exit()
132 }
133
134 pub fn issue_and_set_all_roles(
153 &self,
154 issue_cost: BigUint<SA>,
155 token_display_name: ManagedBuffer<SA>,
156 token_ticker: ManagedBuffer<SA>,
157 num_decimals: usize,
158 opt_callback: Option<CallbackClosure<SA>>,
159 ) -> ! {
160 self.check_not_set();
161
162 let callback = match opt_callback {
163 Some(cb) => cb,
164 None => self.default_callback_closure_obj(&BigUint::zero()),
165 };
166
167 storage_set(self.get_storage_key(), &TokenMapperState::<SA>::Pending);
168 Tx::new_tx_from_sc()
169 .to(ESDTSystemSCAddress)
170 .typed(ESDTSystemSCProxy)
171 .issue_and_set_all_roles(
172 issue_cost,
173 token_display_name,
174 token_ticker,
175 EsdtTokenType::Fungible,
176 num_decimals,
177 )
178 .callback(callback)
179 .async_call_and_exit();
180 }
181
182 pub fn clear(&mut self) {
183 let state: TokenMapperState<SA> = storage_get(self.key.as_ref());
184 if state.is_pending() {
185 storage_clear(self.key.as_ref());
186 }
187 }
188
189 pub fn mint(&self, amount: BigUint<SA>) -> EsdtTokenPayment<SA> {
190 let send_wrapper = SendWrapper::<SA>::new();
191 let token_id = self.get_token_id();
192
193 send_wrapper.esdt_local_mint(&token_id, 0, &amount);
194
195 EsdtTokenPayment::new(token_id, 0, amount)
196 }
197
198 pub fn mint_and_send(
199 &self,
200 to: &ManagedAddress<SA>,
201 amount: BigUint<SA>,
202 ) -> EsdtTokenPayment<SA> {
203 let payment = self.mint(amount);
204 self.send_payment(to, &payment);
205
206 payment
207 }
208
209 pub fn burn(&self, amount: &BigUint<SA>) {
210 let send_wrapper = SendWrapper::<SA>::new();
211 let token_id = self.get_token_id_ref();
212
213 send_wrapper.esdt_local_burn(token_id, 0, amount);
214 }
215
216 pub fn send_payment(&self, to: &ManagedAddress<SA>, payment: &EsdtTokenPayment<SA>) {
217 Tx::new_tx_from_sc()
218 .to(to)
219 .single_esdt(&payment.token_identifier, 0, &payment.amount)
220 .transfer();
221 }
222
223 pub fn set_if_empty(&mut self, token_id: TokenIdentifier<SA>) {
224 if self.is_empty() {
225 self.set_token_id(token_id);
226 }
227 }
228
229 pub fn set_local_roles(
230 &self,
231 roles: &[EsdtLocalRole],
232 opt_callback: Option<CallbackClosure<SA>>,
233 ) -> ! {
234 let own_sc_address = Self::get_sc_address();
235 self.set_local_roles_for_address(&own_sc_address, roles, opt_callback);
236 }
237
238 pub fn set_local_roles_for_address(
239 &self,
240 address: &ManagedAddress<SA>,
241 roles: &[EsdtLocalRole],
242 opt_callback: Option<CallbackClosure<SA>>,
243 ) -> ! {
244 self.require_issued_or_set();
245
246 let token_id = self.get_token_id_ref();
247 Tx::new_tx_from_sc()
248 .to(ESDTSystemSCAddress)
249 .typed(ESDTSystemSCProxy)
250 .set_special_roles(address, token_id, roles[..].iter().cloned())
251 .callback(opt_callback)
252 .async_call_and_exit()
253 }
254
255 pub fn set_token_id(&mut self, token_id: TokenIdentifier<SA>) {
256 self.store_token_id(&token_id);
257 self.token_state = TokenMapperState::Token(token_id);
258 }
259
260 pub(crate) fn store_token_id(&self, token_id: &TokenIdentifier<SA>) {
261 if self.get_token_state().is_set() {
262 SA::error_api_impl().signal_error(TOKEN_ID_ALREADY_SET_ERR_MSG);
263 }
264 if !token_id.is_valid_esdt_identifier() {
265 SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG);
266 }
267 storage_set(
268 self.get_storage_key(),
269 &TokenMapperState::Token(token_id.clone()),
270 );
271 }
272
273 pub fn get_balance(&self) -> BigUint<SA> {
274 let b_wrapper = BlockchainWrapper::new();
275 let own_sc_address = Self::get_sc_address();
276 let token_id = self.get_token_id_ref();
277
278 b_wrapper.get_esdt_balance(&own_sc_address, token_id, 0)
279 }
280
281 pub fn get_sc_address() -> ManagedAddress<SA> {
282 let b_wrapper = BlockchainWrapper::new();
283 b_wrapper.get_sc_address()
284 }
285}
286
287impl<SA, A> FungibleTokenMapper<SA, A>
288where
289 SA: StorageMapperApi + CallTypeApi,
290 A: StorageAddress<SA>,
291{
292 pub fn get_storage_key(&self) -> ManagedRef<'_, SA, StorageKey<SA>> {
293 self.key.as_ref()
294 }
295
296 pub fn get_token_state(&self) -> TokenMapperState<SA> {
297 self.token_state.clone()
298 }
299
300 pub fn get_token_id(&self) -> TokenIdentifier<SA> {
301 if let TokenMapperState::Token(token) = &self.token_state {
302 token.clone()
303 } else {
304 SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG)
305 }
306 }
307
308 pub fn get_token_id_ref(&self) -> &TokenIdentifier<SA> {
309 if let TokenMapperState::Token(token) = &self.token_state {
310 token
311 } else {
312 SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG);
313 }
314 }
315
316 pub fn is_empty(&self) -> bool {
317 storage_get_len(self.get_storage_key()) == 0
318 }
319
320 pub fn require_issued_or_set(&self) {
321 if self.is_empty() {
322 SA::error_api_impl().signal_error(MUST_SET_TOKEN_ID_ERR_MSG);
323 }
324 }
325
326 pub fn require_same_token(&self, expected_token_id: &TokenIdentifier<SA>) {
327 let actual_token_id = self.get_token_id_ref();
328 if actual_token_id != expected_token_id {
329 SA::error_api_impl().signal_error(INVALID_PAYMENT_TOKEN_ERR_MSG);
330 }
331 }
332
333 pub fn require_all_same_token(&self, payments: &ManagedVec<SA, EsdtTokenPayment<SA>>) {
334 let actual_token_id = self.get_token_id_ref();
335 for p in payments {
336 if actual_token_id != &p.token_identifier {
337 SA::error_api_impl().signal_error(INVALID_PAYMENT_TOKEN_ERR_MSG);
338 }
339 }
340 }
341
342 pub fn default_callback_closure_obj(
343 &self,
344 initial_supply: &BigUint<SA>,
345 ) -> CallbackClosure<SA> {
346 let initial_caller = BlockchainWrapper::<SA>::new().get_caller();
347 let cb_name = if initial_supply > &0 {
348 DEFAULT_ISSUE_WITH_INIT_SUPPLY_CALLBACK_NAME
349 } else {
350 DEFAULT_ISSUE_CALLBACK_NAME
351 };
352
353 let mut cb_closure = CallbackClosure::new(cb_name);
354 cb_closure.push_endpoint_arg(&initial_caller);
355 cb_closure.push_endpoint_arg(&self.key.buffer);
356
357 cb_closure
358 }
359
360 pub(crate) fn check_not_set(&self) {
361 let storage_value: TokenMapperState<SA> = storage_get(self.get_storage_key());
362 match storage_value {
363 TokenMapperState::NotSet => {}
364 TokenMapperState::Pending => {
365 SA::error_api_impl().signal_error(PENDING_ERR_MSG);
366 }
367 TokenMapperState::Token(_) => {
368 SA::error_api_impl().signal_error(TOKEN_ID_ALREADY_SET_ERR_MSG);
369 }
370 }
371 }
372}
373
374impl<SA> TopEncodeMulti for FungibleTokenMapper<SA>
375where
376 SA: StorageMapperApi + CallTypeApi,
377{
378 fn multi_encode_or_handle_err<O, H>(&self, output: &mut O, h: H) -> Result<(), H::HandledErr>
379 where
380 O: TopEncodeMultiOutput,
381 H: EncodeErrorHandler,
382 {
383 if self.is_empty() {
384 output.push_single_value(&ManagedBuffer::<SA>::new(), h)
385 } else {
386 output.push_single_value(&self.get_token_id(), h)
387 }
388 }
389}
390
391impl<SA> TypeAbiFrom<FungibleTokenMapper<SA>> for TokenIdentifier<SA> where
392 SA: StorageMapperApi + CallTypeApi
393{
394}
395
396impl<SA> TypeAbiFrom<Self> for FungibleTokenMapper<SA> where SA: StorageMapperApi + CallTypeApi {}
397
398impl<SA> TypeAbi for FungibleTokenMapper<SA>
399where
400 SA: StorageMapperApi + CallTypeApi,
401{
402 type Unmanaged = Self;
403
404 fn type_name() -> TypeName {
405 TokenIdentifier::<SA>::type_name()
406 }
407
408 fn type_name_rust() -> TypeName {
409 TokenIdentifier::<SA>::type_name_rust()
410 }
411
412 fn provide_type_descriptions<TDC: crate::abi::TypeDescriptionContainer>(accumulator: &mut TDC) {
413 TokenIdentifier::<SA>::provide_type_descriptions(accumulator);
414 }
415
416 fn is_variadic() -> bool {
417 false
418 }
419}