Skip to main content

multiversx_sc/contract_base/wrappers/
call_value_wrapper.rs

1use core::marker::PhantomData;
2
3use crate::{
4    api::{
5        CallValueApi, CallValueApiImpl, ErrorApi, ErrorApiImpl, ManagedBufferApiImpl,
6        ManagedTypeApi, RawHandle, StaticVarApiFlags, StaticVarApiImpl, const_handles,
7        use_raw_handle,
8    },
9    contract_base::BlockchainWrapper,
10    err_msg,
11    types::{
12        BigUint, EgldDecimals, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment,
13        EgldOrMultiEsdtPayment, EsdtTokenIdentifier, EsdtTokenPayment, ManagedDecimal, ManagedRef,
14        ManagedType, ManagedVec, ManagedVecItem, ManagedVecItemPayload, ManagedVecPayloadIterator,
15        Payment, PaymentVec, Ref,
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();
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_native() {
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    fn all_transfers_handle(&self) -> A::ManagedBufferHandle {
132        let all_transfers_handle: A::ManagedBufferHandle =
133            use_raw_handle(const_handles::CALL_VALUE_ALL);
134        if !A::static_var_api_impl()
135            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED)
136        {
137            A::call_value_api_impl().load_all_transfers(all_transfers_handle.clone());
138        }
139        all_transfers_handle
140    }
141
142    /// Will return all transfers in the form of a list of EgldOrEsdtTokenPayment.
143    ///
144    /// Both EGLD and ESDT can be returned.
145    ///
146    /// In case of a single EGLD transfer, only one item will be returned,
147    /// the EGLD payment represented as an ESDT transfer (EGLD-000000).
148    pub fn all_transfers(
149        &self,
150    ) -> ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> {
151        let all_transfers_handle = self.all_transfers_handle();
152        unsafe { ManagedRef::wrap_handle(all_transfers_handle) }
153    }
154
155    /// Will return all transfers in the form of a list of Payment.
156    ///
157    /// It handles all tokens uniformly, including the native token (EGLD or lightspeed chain native tokens).
158    ///
159    /// In case of a single EGLD transfer, only one item will be returned,
160    /// the EGLD payment represented as an ESDT transfer (EGLD-000000).
161    pub fn all(&self) -> ManagedRef<'static, A, PaymentVec<A>> {
162        let all_transfers_handle = self.all_transfers_handle();
163        unsafe { ManagedRef::wrap_handle(all_transfers_handle) }
164    }
165
166    /// Accepts a single payment.
167    ///
168    /// Will halt execution if zero or more than one payment was received.
169    pub fn single(&self) -> Ref<'static, Payment<A>> {
170        let esdt_transfers = self.all();
171        if esdt_transfers.len() != 1 {
172            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
173        }
174        let value = esdt_transfers.get(0);
175        unsafe {
176            // transmute only used because the compiler doesn't seem to be able to unify the 'static lifetime properly
177            core::mem::transmute::<Ref<'_, Payment<A>>, Ref<'static, Payment<A>>>(value)
178        }
179    }
180
181    /// Accepts either a single payment, or no payment at all.
182    ///
183    /// Will halt execution if zero or more than one payment was received.
184    pub fn single_optional(&self) -> Option<Ref<'static, Payment<A>>> {
185        let esdt_transfers: ManagedRef<'static, A, ManagedVec<A, Payment<A>>> = self.all();
186        match esdt_transfers.len() {
187            0 => None,
188            1 => {
189                let value = esdt_transfers.get(0);
190                // transmute only used because the compiler doesn't seem to be able to unify the 'static lifetime properly
191                let lifetime_fix = unsafe {
192                    core::mem::transmute::<Ref<'_, Payment<A>>, Ref<'static, Payment<A>>>(value)
193                };
194                Some(lifetime_fix)
195            }
196            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes()),
197        }
198    }
199
200    /// Verify and casts the received multi ESDT transfer in to an array.
201    ///
202    /// Can be used to extract all payments in one line like this:
203    ///
204    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_egld_or_esdt();`.
205    pub fn array<const N: usize>(&self) -> [Ref<'static, Payment<A>>; N] {
206        let list = self.all();
207        let array = list.to_array_of_refs::<N>().unwrap_or_else(|| {
208            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
209        });
210        unsafe { core::mem::transmute(array) }
211    }
212
213    /// Verify and casts the received multi ESDT transfer in to an array.
214    ///
215    /// Can be used to extract all payments in one line like this:
216    ///
217    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_esdt();`.
218    ///
219    /// Rejects EGLD transfers. Switch to `multi_egld_or_esdt` to accept mixed transfers.
220    pub fn multi_esdt<const N: usize>(&self) -> [Ref<'static, EsdtTokenPayment<A>>; N] {
221        let esdt_transfers = self.all_esdt_transfers();
222        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
223            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
224        });
225        unsafe { core::mem::transmute(array) }
226    }
227
228    /// Verify and casts the received multi ESDT transfer in to an array.
229    ///
230    /// Can be used to extract all payments in one line like this:
231    ///
232    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_egld_or_esdt();`.
233    pub fn multi_egld_or_esdt<const N: usize>(
234        &self,
235    ) -> [Ref<'static, EgldOrEsdtTokenPayment<A>>; N] {
236        let esdt_transfers = self.all_transfers();
237        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
238            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
239        });
240        unsafe { core::mem::transmute(array) }
241    }
242
243    /// Expects precisely one ESDT token transfer, fungible or not.
244    ///
245    /// Will return the received ESDT payment.
246    ///
247    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
248    pub fn single_esdt(&self) -> Ref<'static, EsdtTokenPayment<A>> {
249        let esdt_transfers = self.all_esdt_transfers();
250        if esdt_transfers.len() != 1 {
251            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
252        }
253        let value = esdt_transfers.get(0);
254        unsafe { core::mem::transmute(value) }
255    }
256
257    /// Expects precisely one fungible ESDT token transfer.
258    ///
259    /// Returns the token ID and the amount for fungible ESDT transfers.
260    ///
261    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
262    pub fn single_fungible_esdt(
263        &self,
264    ) -> (
265        ManagedRef<'static, A, EsdtTokenIdentifier<A>>,
266        ManagedRef<'static, A, BigUint<A>>,
267    ) {
268        let payment = self.single_esdt();
269        if payment.token_nonce != 0 {
270            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED.as_bytes());
271        }
272
273        unsafe {
274            (
275                ManagedRef::wrap_handle(payment.token_identifier.get_handle()),
276                ManagedRef::wrap_handle(payment.amount.get_handle()),
277            )
278        }
279    }
280
281    /// Accepts and returns either an EGLD payment, or a single ESDT token.
282    ///
283    /// Will halt execution if more than one ESDT transfer was received.
284    ///
285    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
286    pub fn egld_or_single_esdt(&self) -> EgldOrEsdtTokenPayment<A> {
287        let esdt_transfers_handle = self.all_esdt_transfers_unchecked();
288        let esdt_transfers: ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> =
289            unsafe { ManagedRef::wrap_handle(esdt_transfers_handle) };
290        match esdt_transfers.len() {
291            0 => EgldOrEsdtTokenPayment {
292                token_identifier: EgldOrEsdtTokenIdentifier::egld(),
293                token_nonce: 0,
294                amount: self.egld_direct_non_strict().clone(),
295            },
296            1 => esdt_transfers.get(0).clone(),
297            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes()),
298        }
299    }
300
301    /// Accepts and returns either an EGLD payment, or a single fungible ESDT token.
302    ///
303    /// Will halt execution if more than one ESDT transfer was received, or if the received ESDT is non- or semi-fungible.
304    ///
305    /// Works similar to `egld_or_single_esdt`,
306    /// but checks the nonce to be 0 and returns a tuple of just token identifier and amount, for convenience.
307    ///
308    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
309    pub fn egld_or_single_fungible_esdt(&self) -> (EgldOrEsdtTokenIdentifier<A>, BigUint<A>) {
310        let payment = self.egld_or_single_esdt();
311        if payment.token_nonce != 0 {
312            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED.as_bytes());
313        }
314
315        (payment.token_identifier, payment.amount)
316    }
317
318    /// Accepts any sort of payment, which is either:
319    /// - EGLD (can be zero in case of no payment whatsoever);
320    /// - Multi-ESDT (one or more ESDT transfers).
321    #[deprecated(
322        note = "It comes from a time when only 1 EGLD payment, or ESDT multi-transfer was possible. This is no longer the case. Use `any` instead.",
323        since = "0.64.0"
324    )]
325    pub fn any_payment(&self) -> EgldOrMultiEsdtPayment<A> {
326        let esdt_transfers = self.all_esdt_transfers();
327        if esdt_transfers.is_empty() {
328            EgldOrMultiEsdtPayment::Egld(self.egld_direct_non_strict().clone())
329        } else {
330            EgldOrMultiEsdtPayment::MultiEsdt(esdt_transfers.clone())
331        }
332    }
333}
334
335fn egld_000000_transfer_exists<A>(transfers_vec_handle: A::ManagedBufferHandle) -> bool
336where
337    A: CallValueApi + ErrorApi + ManagedTypeApi,
338{
339    let native_token_handle = BlockchainWrapper::<A>::new().get_native_token_handle();
340    unsafe {
341        let mut iter: ManagedVecPayloadIterator<
342            A,
343            <EsdtTokenPayment<A> as ManagedVecItem>::PAYLOAD,
344        > = ManagedVecPayloadIterator::new(transfers_vec_handle);
345
346        iter.any(|payload| {
347            let token_identifier_handle = RawHandle::read_from_payload(payload.slice_unchecked(0));
348            A::managed_type_impl().mb_eq(
349                native_token_handle.clone(),
350                use_raw_handle(token_identifier_handle),
351            )
352        })
353    }
354}