multiversx_sc/contract_base/wrappers/
call_value_wrapper.rs

1use core::marker::PhantomData;
2
3use multiversx_chain_core::EGLD_000000_TOKEN_IDENTIFIER;
4
5use crate::{
6    api::{
7        const_handles, use_raw_handle, CallValueApi, CallValueApiImpl, ErrorApi, ErrorApiImpl,
8        ManagedBufferApiImpl, ManagedTypeApi, RawHandle, StaticVarApiFlags, StaticVarApiImpl,
9    },
10    err_msg,
11    types::{
12        BigUint, EgldDecimals, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment,
13        EgldOrMultiEsdtPayment, EsdtTokenPayment, ManagedDecimal, ManagedRef, ManagedType,
14        ManagedVec, ManagedVecItem, ManagedVecItemPayload, ManagedVecPayloadIterator,
15        ManagedVecRef, TokenIdentifier,
16    },
17};
18
19#[derive(Default)]
20pub struct CallValueWrapper<A>
21where
22    A: CallValueApi + ErrorApi + ManagedTypeApi,
23{
24    _phantom: PhantomData<A>,
25}
26
27impl<A> CallValueWrapper<A>
28where
29    A: CallValueApi + ErrorApi + ManagedTypeApi,
30{
31    pub fn new() -> Self {
32        CallValueWrapper {
33            _phantom: PhantomData,
34        }
35    }
36
37    /// Cached transfers from the VM.
38    fn all_esdt_transfers_unchecked(&self) -> A::ManagedBufferHandle {
39        let all_transfers_unchecked_handle: A::ManagedBufferHandle =
40            use_raw_handle(const_handles::CALL_VALUE_MULTI_ESDT);
41        if !A::static_var_api_impl()
42            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ESDT_UNCHECKED_INITIALIZED)
43        {
44            A::call_value_api_impl()
45                .load_all_esdt_transfers(all_transfers_unchecked_handle.clone());
46        }
47        all_transfers_unchecked_handle
48    }
49
50    /// Retrieves the EGLD call value from the VM.
51    ///
52    /// Will return 0 in case of an ESDT transfer, even though EGLD and ESDT transfers are now possible.
53    pub fn egld_direct_non_strict(&self) -> ManagedRef<'static, A, BigUint<A>> {
54        let call_value_handle: A::BigIntHandle = use_raw_handle(const_handles::CALL_VALUE_EGLD);
55        if !A::static_var_api_impl()
56            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_EGLD_DIRECT_INITIALIZED)
57        {
58            A::call_value_api_impl().load_egld_value(call_value_handle.clone());
59        }
60        unsafe { ManagedRef::wrap_handle(call_value_handle) }
61    }
62
63    /// Retrieves the EGLD call value and crashes if anything else was transferred.
64    ///
65    /// Accepts both EGLD sent directly, as well as EGLD sent alone in a multi-transfer.
66    ///
67    /// Does not accept a multi-transfer with 2 or more transfers, not even 2 or more EGLD transfers.
68    pub fn egld(&self) -> ManagedRef<'static, A, BigUint<A>> {
69        let all_transfers = self.all_transfers();
70        match all_transfers.len() {
71            0 => {
72                use crate::api::BigIntApiImpl;
73
74                let call_value_handle: A::BigIntHandle =
75                    use_raw_handle(const_handles::CALL_VALUE_EGLD);
76                A::managed_type_impl().bi_set_int64(call_value_handle.clone(), 0);
77                unsafe { ManagedRef::wrap_handle(call_value_handle) }
78            }
79            1 => {
80                let first = all_transfers.get(0);
81                if !first.token_identifier.is_egld() {
82                    A::error_api_impl().signal_error(err_msg::NON_PAYABLE_FUNC_ESDT.as_bytes());
83                }
84                unsafe { ManagedRef::wrap_handle(first.amount.get_handle()) }
85            }
86            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes()),
87        }
88    }
89
90    /// Retrieves the EGLD call value from the VM.
91    ///
92    /// Will return 0 in case of an ESDT transfer, even though EGLD and ESDT transfers are now possible.
93    ///
94    /// ## Important!
95    ///
96    /// Does not cover multi-transfer scenarios properly, but left for backwards compatibility.
97    ///
98    /// Please use `.egld()` instead!
99    ///
100    /// For raw handling, `.egld_direct_non_strict()` is also acceptable.
101    #[deprecated(
102        since = "0.55.0",
103        note = "Does not cover multi-transfer scenarios properly, but left for backwards compatibility. Please use .egld() instead!"
104    )]
105    pub fn egld_value(&self) -> ManagedRef<'static, A, BigUint<A>> {
106        self.egld_direct_non_strict()
107    }
108
109    /// Returns the EGLD call value from the VM as ManagedDecimal
110    pub fn egld_decimal(&self) -> ManagedDecimal<A, EgldDecimals> {
111        ManagedDecimal::<A, EgldDecimals>::const_decimals_from_raw(self.egld_value().clone())
112    }
113
114    /// Returns all ESDT transfers that accompany this SC call.
115    /// Will return 0 results if nothing was transferred, or just EGLD.
116    ///
117    /// Will crash for EGLD + ESDT multi transfers.
118    ///
119    /// Provided for backwards compatibility, if possible, use `all_transfers` instead.
120    pub fn all_esdt_transfers(&self) -> ManagedRef<'static, A, ManagedVec<A, EsdtTokenPayment<A>>> {
121        let multi_esdt_handle: A::ManagedBufferHandle = self.all_esdt_transfers_unchecked();
122        let checked = A::static_var_api_impl()
123            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ESDT_INITIALIZED);
124        if !checked && egld_000000_transfer_exists::<A>(multi_esdt_handle.clone()) {
125            A::error_api_impl().signal_error(err_msg::ESDT_UNEXPECTED_EGLD.as_bytes())
126        }
127
128        unsafe { ManagedRef::wrap_handle(multi_esdt_handle) }
129    }
130
131    /// Will return all transfers in the form of a list of EgldOrEsdtTokenPayment.
132    ///
133    /// Both EGLD and ESDT can be returned.
134    ///
135    /// In case of a single EGLD transfer, only one item will be returned,
136    /// the EGLD payment represented as an ESDT transfer (EGLD-000000).
137    pub fn all_transfers(
138        &self,
139    ) -> ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> {
140        let all_transfers_handle: A::ManagedBufferHandle =
141            use_raw_handle(const_handles::CALL_VALUE_ALL);
142        if !A::static_var_api_impl()
143            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED)
144        {
145            A::call_value_api_impl().load_all_transfers(all_transfers_handle.clone());
146        }
147        unsafe { ManagedRef::wrap_handle(all_transfers_handle) }
148    }
149
150    /// Verify and casts the received multi ESDT transfer in to an array.
151    ///
152    /// Can be used to extract all payments in one line like this:
153    ///
154    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_esdt();`.
155    ///
156    /// Rejects EGLD transfers. Switch to `multi_egld_or_esdt` to accept mixed transfers.
157    pub fn multi_esdt<const N: usize>(&self) -> [ManagedVecRef<'static, EsdtTokenPayment<A>>; N] {
158        let esdt_transfers = self.all_esdt_transfers();
159        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
160            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes())
161        });
162        unsafe { core::mem::transmute(array) }
163    }
164
165    /// Verify and casts the received multi ESDT transfer in to an array.
166    ///
167    /// Can be used to extract all payments in one line like this:
168    ///
169    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_egld_or_esdt();`.
170    pub fn multi_egld_or_esdt<const N: usize>(
171        &self,
172    ) -> [ManagedVecRef<'static, EgldOrEsdtTokenPayment<A>>; N] {
173        let esdt_transfers = self.all_transfers();
174        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
175            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
176        });
177        unsafe { core::mem::transmute(array) }
178    }
179
180    /// Expects precisely one ESDT token transfer, fungible or not.
181    ///
182    /// Will return the received ESDT payment.
183    ///
184    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
185    pub fn single_esdt(&self) -> ManagedVecRef<'static, EsdtTokenPayment<A>> {
186        let esdt_transfers = self.all_esdt_transfers();
187        if esdt_transfers.len() != 1 {
188            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes())
189        }
190        let value = esdt_transfers.get(0);
191        unsafe { core::mem::transmute(value) }
192    }
193
194    /// Expects precisely one fungible ESDT token transfer.
195    ///
196    /// Returns the token ID and the amount for fungible ESDT transfers.
197    ///
198    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
199    pub fn single_fungible_esdt(
200        &self,
201    ) -> (
202        ManagedRef<'static, A, TokenIdentifier<A>>,
203        ManagedRef<'static, A, BigUint<A>>,
204    ) {
205        let payment = self.single_esdt();
206        if payment.token_nonce != 0 {
207            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED_ERR_MSG.as_bytes());
208        }
209
210        unsafe {
211            (
212                ManagedRef::wrap_handle(payment.token_identifier.get_handle()),
213                ManagedRef::wrap_handle(payment.amount.get_handle()),
214            )
215        }
216    }
217
218    /// Accepts and returns either an EGLD payment, or a single ESDT token.
219    ///
220    /// Will halt execution if more than one ESDT transfer was received.
221    ///
222    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
223    pub fn egld_or_single_esdt(&self) -> EgldOrEsdtTokenPayment<A> {
224        let esdt_transfers_handle = self.all_esdt_transfers_unchecked();
225        let esdt_transfers: ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> =
226            unsafe { ManagedRef::wrap_handle(esdt_transfers_handle) };
227        match esdt_transfers.len() {
228            0 => EgldOrEsdtTokenPayment {
229                token_identifier: EgldOrEsdtTokenIdentifier::egld(),
230                token_nonce: 0,
231                amount: self.egld_direct_non_strict().clone(),
232            },
233            1 => esdt_transfers.get(0).clone(),
234            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes()),
235        }
236    }
237
238    /// Accepts and returns either an EGLD payment, or a single fungible ESDT token.
239    ///
240    /// Will halt execution if more than one ESDT transfer was received, or if the received ESDT is non- or semi-fungible.
241    ///
242    /// Works similar to `egld_or_single_esdt`,
243    /// but checks the nonce to be 0 and returns a tuple of just token identifier and amount, for convenience.
244    ///
245    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
246    pub fn egld_or_single_fungible_esdt(&self) -> (EgldOrEsdtTokenIdentifier<A>, BigUint<A>) {
247        let payment = self.egld_or_single_esdt();
248        if payment.token_nonce != 0 {
249            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED_ERR_MSG.as_bytes());
250        }
251
252        (payment.token_identifier, payment.amount)
253    }
254
255    /// Accepts any sort of patyment, which is either:
256    /// - EGLD (can be zero in case of no payment whatsoever);
257    /// - Multi-ESDT (one or more ESDT transfers).
258    pub fn any_payment(&self) -> EgldOrMultiEsdtPayment<A> {
259        let esdt_transfers = self.all_esdt_transfers();
260        if esdt_transfers.is_empty() {
261            EgldOrMultiEsdtPayment::Egld(self.egld_direct_non_strict().clone())
262        } else {
263            EgldOrMultiEsdtPayment::MultiEsdt(esdt_transfers.clone())
264        }
265    }
266}
267
268fn egld_000000_transfer_exists<A>(transfers_vec_handle: A::ManagedBufferHandle) -> bool
269where
270    A: CallValueApi + ErrorApi + ManagedTypeApi,
271{
272    A::managed_type_impl().mb_overwrite(
273        use_raw_handle(const_handles::MBUF_EGLD_000000),
274        EGLD_000000_TOKEN_IDENTIFIER.as_bytes(),
275    );
276    unsafe {
277        let mut iter: ManagedVecPayloadIterator<
278            A,
279            <EsdtTokenPayment<A> as ManagedVecItem>::PAYLOAD,
280        > = ManagedVecPayloadIterator::new(transfers_vec_handle);
281
282        iter.any(|payload| {
283            let token_identifier_handle = RawHandle::read_from_payload(payload.slice_unchecked(0));
284            A::managed_type_impl().mb_eq(
285                use_raw_handle(const_handles::MBUF_EGLD_000000),
286                use_raw_handle(token_identifier_handle),
287            )
288        })
289    }
290}