multiversx_sc/storage/mappers/token/
fungible_token_mapper.rs

1use 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    /// Important: If you use custom callback, remember to save the token ID in the callback and clear the mapper in case of error! Clear is unusable outside this specific case.
82    ///
83    /// #[callback]
84    /// fn my_custom_callback(
85    ///     &self,
86    ///     #[call_result] result: ManagedAsyncCallResult<()>,
87    /// ) {
88    ///      match result {
89    ///     ManagedAsyncCallResult::Ok(token_id) => {
90    ///         self.fungible_token_mapper().set_token_id(token_id);
91    ///     },
92    ///     ManagedAsyncCallResult::Err(_) => {
93    ///         self.fungible_token_mapper().clear();
94    ///     },
95    /// }
96    ///
97    /// If you want to use default callbacks, import the default_issue_callbacks::DefaultIssueCallbacksModule from multiversx-sc-modules
98    /// and pass None for the opt_callback argument
99    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    /// Important: If you use custom callback, remember to save the token ID in the callback and clear the mapper in case of error! Clear is unusable outside this specific case.
135    ///
136    /// #[callback]
137    /// fn my_custom_callback(
138    ///     &self,
139    ///     #[call_result] result: ManagedAsyncCallResult<()>,
140    /// ) {
141    ///      match result {
142    ///     ManagedAsyncCallResult::Ok(token_id) => {
143    ///         self.fungible_token_mapper().set_token_id(token_id);
144    ///     },
145    ///     ManagedAsyncCallResult::Err(_) => {
146    ///         self.fungible_token_mapper().clear();
147    ///     },
148    /// }
149    ///
150    /// If you want to use default callbacks, import the default_issue_callbacks::DefaultIssueCallbacksModule from multiversx-sc-modules
151    /// and pass None for the opt_callback argument
152    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}