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        big_num_cmp::bi_gt_zero, BigUint, EgldDecimals, EgldOrEsdtTokenIdentifier,
13        EgldOrEsdtTokenPayment, EgldOrMultiEsdtPayment, EsdtTokenPayment, ManagedDecimal,
14        ManagedRef, ManagedType, ManagedVec, ManagedVecItem, ManagedVecItemPayload,
15        ManagedVecPayloadIterator, 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 posible.
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 esdt_transfers_handle = self.all_esdt_transfers_unchecked();
70        let esdt_transfers: ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> =
71            unsafe { ManagedRef::wrap_handle(esdt_transfers_handle) };
72        match esdt_transfers.len() {
73            0 => self.egld_direct_non_strict(),
74            1 => {
75                let first = esdt_transfers.get(0);
76                if !first.token_identifier.is_egld() {
77                    A::error_api_impl().signal_error(err_msg::NON_PAYABLE_FUNC_ESDT.as_bytes());
78                }
79                unsafe { ManagedRef::wrap_handle(first.amount.get_handle()) }
80            },
81            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes()),
82        }
83    }
84
85    /// Retrieves the EGLD call value from the VM.
86    ///
87    /// Will return 0 in case of an ESDT transfer, even though EGLD and ESDT transfers are now posible.
88    ///
89    /// ## Important!
90    ///
91    /// Does not cover multi-transfer scenarios properly, but left for backwards compatibility.
92    ///
93    /// Please use `.egld()` instead!
94    ///
95    /// For raw handling, `.egld_direct_non_strict()` is also acceptable.
96    #[deprecated(
97        since = "0.55.0",
98        note = "Does not cover multi-transfer scenarios properly, but left for backwards compatibility. Please use .egld() instead!"
99    )]
100    pub fn egld_value(&self) -> ManagedRef<'static, A, BigUint<A>> {
101        self.egld_direct_non_strict()
102    }
103
104    /// Returns the EGLD call value from the VM as ManagedDecimal
105    pub fn egld_decimal(&self) -> ManagedDecimal<A, EgldDecimals> {
106        ManagedDecimal::<A, EgldDecimals>::const_decimals_from_raw(self.egld_value().clone())
107    }
108
109    /// Returns all ESDT transfers that accompany this SC call.
110    /// Will return 0 results if nothing was transfered, or just EGLD.
111    ///
112    /// Will crash for EGLD + ESDT multi transfers.
113    pub fn all_esdt_transfers(&self) -> ManagedRef<'static, A, ManagedVec<A, EsdtTokenPayment<A>>> {
114        let multi_esdt_handle: A::ManagedBufferHandle = self.all_esdt_transfers_unchecked();
115        let checked = A::static_var_api_impl()
116            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ESDT_INITIALIZED);
117        if !checked && egld_000000_transfer_exists::<A>(multi_esdt_handle.clone()) {
118            A::error_api_impl().signal_error(err_msg::ESDT_UNEXPECTED_EGLD.as_bytes())
119        }
120
121        unsafe { ManagedRef::wrap_handle(multi_esdt_handle) }
122    }
123
124    /// Will return all transfers in the form of a list of EgldOrEsdtTokenPayment.
125    ///
126    /// Both EGLD and ESDT can be returned.
127    ///
128    /// In case of a single EGLD transfer, only one item will be returned,
129    /// the EGLD payment represented as an ESDT transfer (EGLD-000000).
130    pub fn all_transfers(
131        &self,
132    ) -> ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> {
133        let all_transfers_handle: A::ManagedBufferHandle =
134            use_raw_handle(const_handles::CALL_VALUE_ALL);
135        if !A::static_var_api_impl()
136            .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED)
137        {
138            let egld_single = self.egld_direct_non_strict();
139            if bi_gt_zero::<A>(egld_single.get_handle()) {
140                A::managed_type_impl().mb_overwrite(
141                    use_raw_handle(const_handles::MBUF_EGLD_000000),
142                    EGLD_000000_TOKEN_IDENTIFIER.as_bytes(),
143                );
144                A::managed_type_impl().mb_overwrite(
145                    all_transfers_handle.clone(),
146                    &const_handles::EGLD_PAYMENT_PAYLOAD[..],
147                );
148            } else {
149                // clone all_esdt_transfers_unchecked -> all_transfers
150                let all_transfers_unchecked_handle = self.all_esdt_transfers_unchecked();
151                A::managed_type_impl().mb_overwrite(all_transfers_handle.clone(), &[]);
152                A::managed_type_impl()
153                    .mb_append(all_transfers_handle.clone(), all_transfers_unchecked_handle);
154            }
155        }
156        unsafe { ManagedRef::wrap_handle(all_transfers_handle) }
157    }
158
159    /// Verify and casts the received multi ESDT transfer in to an array.
160    ///
161    /// Can be used to extract all payments in one line like this:
162    ///
163    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_esdt();`.
164    ///
165    /// Rejects EGLD transfers. Switch to `multi_egld_or_esdt` to accept mixed transfers.
166    pub fn multi_esdt<const N: usize>(&self) -> [ManagedVecRef<'static, EsdtTokenPayment<A>>; N] {
167        let esdt_transfers = self.all_esdt_transfers();
168        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
169            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes())
170        });
171        unsafe { core::mem::transmute(array) }
172    }
173
174    /// Verify and casts the received multi ESDT transfer in to an array.
175    ///
176    /// Can be used to extract all payments in one line like this:
177    ///
178    /// `let [payment_a, payment_b, payment_c] = self.call_value().multi_egld_or_esdt();`.
179    pub fn multi_egld_or_esdt<const N: usize>(
180        &self,
181    ) -> [ManagedVecRef<'static, EgldOrEsdtTokenPayment<A>>; N] {
182        let esdt_transfers = self.all_transfers();
183        let array = esdt_transfers.to_array_of_refs::<N>().unwrap_or_else(|| {
184            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_TRANSFERS.as_bytes())
185        });
186        unsafe { core::mem::transmute(array) }
187    }
188
189    /// Expects precisely one ESDT token transfer, fungible or not.
190    ///
191    /// Will return the received ESDT payment.
192    ///
193    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
194    pub fn single_esdt(&self) -> ManagedVecRef<'static, EsdtTokenPayment<A>> {
195        let esdt_transfers = self.all_esdt_transfers();
196        if esdt_transfers.len() != 1 {
197            A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes())
198        }
199        let value = esdt_transfers.get(0);
200        unsafe { core::mem::transmute(value) }
201    }
202
203    /// Expects precisely one fungible ESDT token transfer.
204    ///
205    /// Returns the token ID and the amount for fungible ESDT transfers.
206    ///
207    /// The amount cannot be 0, since that would not qualify as an ESDT transfer.
208    pub fn single_fungible_esdt(
209        &self,
210    ) -> (
211        ManagedRef<'static, A, TokenIdentifier<A>>,
212        ManagedRef<'static, A, BigUint<A>>,
213    ) {
214        let payment = self.single_esdt();
215        if payment.token_nonce != 0 {
216            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED_ERR_MSG.as_bytes());
217        }
218
219        unsafe {
220            (
221                ManagedRef::wrap_handle(payment.token_identifier.get_handle()),
222                ManagedRef::wrap_handle(payment.amount.get_handle()),
223            )
224        }
225    }
226
227    /// Accepts and returns either an EGLD payment, or a single ESDT token.
228    ///
229    /// Will halt execution if more than one ESDT transfer was received.
230    ///
231    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
232    pub fn egld_or_single_esdt(&self) -> EgldOrEsdtTokenPayment<A> {
233        let esdt_transfers_handle = self.all_esdt_transfers_unchecked();
234        let esdt_transfers: ManagedRef<'static, A, ManagedVec<A, EgldOrEsdtTokenPayment<A>>> =
235            unsafe { ManagedRef::wrap_handle(esdt_transfers_handle) };
236        match esdt_transfers.len() {
237            0 => EgldOrEsdtTokenPayment {
238                token_identifier: EgldOrEsdtTokenIdentifier::egld(),
239                token_nonce: 0,
240                amount: self.egld_direct_non_strict().clone(),
241            },
242            1 => esdt_transfers.get(0).clone(),
243            _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes()),
244        }
245    }
246
247    /// Accepts and returns either an EGLD payment, or a single fungible ESDT token.
248    ///
249    /// Will halt execution if more than one ESDT transfer was received, or if the received ESDT is non- or semi-fungible.
250    ///
251    /// Works similar to `egld_or_single_esdt`,
252    /// but checks the nonce to be 0 and returns a tuple of just token identifier and amount, for convenience.
253    ///
254    /// In case no transfer of value happen, it will return a payment of 0 EGLD.
255    pub fn egld_or_single_fungible_esdt(&self) -> (EgldOrEsdtTokenIdentifier<A>, BigUint<A>) {
256        let payment = self.egld_or_single_esdt();
257        if payment.token_nonce != 0 {
258            A::error_api_impl().signal_error(err_msg::FUNGIBLE_TOKEN_EXPECTED_ERR_MSG.as_bytes());
259        }
260
261        (payment.token_identifier, payment.amount)
262    }
263
264    /// Accepts any sort of patyment, which is either:
265    /// - EGLD (can be zero in case of no payment whatsoever);
266    /// - Multi-ESDT (one or more ESDT transfers).
267    pub fn any_payment(&self) -> EgldOrMultiEsdtPayment<A> {
268        let esdt_transfers = self.all_esdt_transfers();
269        if esdt_transfers.is_empty() {
270            EgldOrMultiEsdtPayment::Egld(self.egld_direct_non_strict().clone())
271        } else {
272            EgldOrMultiEsdtPayment::MultiEsdt(esdt_transfers.clone())
273        }
274    }
275}
276
277fn egld_000000_transfer_exists<A>(transfers_vec_handle: A::ManagedBufferHandle) -> bool
278where
279    A: CallValueApi + ErrorApi + ManagedTypeApi,
280{
281    A::managed_type_impl().mb_overwrite(
282        use_raw_handle(const_handles::MBUF_EGLD_000000),
283        EGLD_000000_TOKEN_IDENTIFIER.as_bytes(),
284    );
285    unsafe {
286        let mut iter: ManagedVecPayloadIterator<
287            A,
288            <EsdtTokenPayment<A> as ManagedVecItem>::PAYLOAD,
289        > = ManagedVecPayloadIterator::new(transfers_vec_handle);
290
291        iter.any(|payload| {
292            let token_identifier_handle = RawHandle::read_from_payload(payload.slice_unchecked(0));
293            A::managed_type_impl().mb_eq(
294                use_raw_handle(const_handles::MBUF_EGLD_000000),
295                use_raw_handle(token_identifier_handle),
296            )
297        })
298    }
299}