Skip to main content

multiversx_sc/types/interaction/tx_data/
function_call.rs

1use multiversx_sc_codec::{
2    DecodeErrorHandler, EncodeErrorHandler, TopDecodeMulti, TopDecodeMultiInput, TopEncodeMulti,
3    TopEncodeMultiOutput,
4};
5
6use crate::{
7    abi::{TypeAbi, TypeAbiFrom, TypeName},
8    api::{
9        ESDT_MULTI_TRANSFER_FUNC_NAME, ESDT_NFT_TRANSFER_FUNC_NAME, ESDT_TRANSFER_FUNC_NAME,
10        ManagedTypeApi,
11    },
12    types::{
13        EsdtTokenPaymentRefs, ManagedAddress, ManagedArgBuffer, ManagedBuffer,
14        MultiEgldOrEsdtPayment, MultiValueEncoded,
15    },
16};
17
18/// Encodes a function call on the blockchain, composed of a function name and its encoded arguments.
19///
20/// Can be used as a multi-argument, to embed a call within a call.
21#[derive(Clone)]
22pub struct FunctionCall<Api>
23where
24    Api: ManagedTypeApi,
25{
26    pub function_name: ManagedBuffer<Api>,
27    pub arg_buffer: ManagedArgBuffer<Api>,
28}
29
30impl<Api> FunctionCall<Api>
31where
32    Api: ManagedTypeApi,
33{
34    /// Initializes a new function call with a function call name.
35    ///
36    /// The arguments will need to be added afterwards.
37    pub fn new<N: Into<ManagedBuffer<Api>>>(function_name: N) -> Self {
38        FunctionCall {
39            function_name: function_name.into(),
40            arg_buffer: ManagedArgBuffer::new(),
41        }
42    }
43
44    /// Initializes a new empty function call, this means no function name and no arguments.
45    pub fn empty() -> Self {
46        FunctionCall::new(ManagedBuffer::new())
47    }
48
49    /// Empty function calls have empty function names.
50    ///
51    /// There should be no function call with empty function call but with arguments.
52    pub fn is_empty(&self) -> bool {
53        self.function_name.is_empty()
54    }
55
56    /// Adds an argument of any serializable type.
57    ///
58    /// Multi-values are accepted. No type checking performed.
59    pub fn argument<T: TopEncodeMulti>(mut self, arg: &T) -> Self {
60        self.arg_buffer.push_multi_arg(arg);
61        self
62    }
63
64    pub fn arguments_raw(mut self, raw: ManagedArgBuffer<Api>) -> Self {
65        self.arg_buffer = raw;
66        self
67    }
68}
69
70impl<Api> From<()> for FunctionCall<Api>
71where
72    Api: ManagedTypeApi,
73{
74    fn from(_: ()) -> Self {
75        FunctionCall::empty()
76    }
77}
78
79#[cfg(feature = "contract-call-legacy")]
80impl<Api, R> From<crate::types::ContractCallNoPayment<Api, R>> for FunctionCall<Api>
81where
82    Api: crate::api::CallTypeApi,
83{
84    fn from(ccnp: crate::types::ContractCallNoPayment<Api, R>) -> Self {
85        ccnp.function_call
86    }
87}
88
89impl<Api> TopEncodeMulti for FunctionCall<Api>
90where
91    Api: ManagedTypeApi,
92{
93    fn multi_encode_or_handle_err<O, H>(&self, output: &mut O, h: H) -> Result<(), H::HandledErr>
94    where
95        O: TopEncodeMultiOutput,
96        H: EncodeErrorHandler,
97    {
98        if self.function_name.is_empty() {
99            return Ok(());
100        }
101        output.push_single_value(&self.function_name, h)?;
102        for arg in self.arg_buffer.raw_arg_iter() {
103            output.push_single_value(&*arg, h)?;
104        }
105
106        Ok(())
107    }
108}
109
110impl<Api> TopDecodeMulti for FunctionCall<Api>
111where
112    Api: ManagedTypeApi,
113{
114    fn multi_decode_or_handle_err<I, H>(input: &mut I, h: H) -> Result<Self, H::HandledErr>
115    where
116        I: TopDecodeMultiInput,
117        H: DecodeErrorHandler,
118    {
119        if !input.has_next() {
120            return Ok(FunctionCall::empty());
121        }
122
123        let function_name = ManagedBuffer::<Api>::multi_decode_or_handle_err(input, h)?;
124        let args =
125            MultiValueEncoded::<Api, ManagedBuffer<Api>>::multi_decode_or_handle_err(input, h)?;
126        Ok(FunctionCall {
127            function_name,
128            arg_buffer: args.to_arg_buffer(),
129        })
130    }
131}
132
133impl<Api> TypeAbiFrom<Self> for FunctionCall<Api> where Api: ManagedTypeApi {}
134
135impl<Api> TypeAbi for FunctionCall<Api>
136where
137    Api: ManagedTypeApi,
138{
139    type Unmanaged = Self;
140
141    fn type_name() -> TypeName {
142        crate::abi::type_name_variadic::<ManagedBuffer<Api>>()
143    }
144
145    fn type_name_rust() -> TypeName {
146        "FunctionCall<$API>".into()
147    }
148
149    fn is_variadic() -> bool {
150        true
151    }
152}
153
154impl<Api> FunctionCall<Api>
155where
156    Api: ManagedTypeApi,
157{
158    /// Constructs `ESDTTransfer` builtin function call.
159    pub(crate) fn convert_to_single_transfer_fungible_call(
160        self,
161        payment: EsdtTokenPaymentRefs<'_, Api>,
162    ) -> FunctionCall<Api> {
163        // EGLD not supported
164        // but serializing token identifier buffer for efficiency, no need to convert to "EGLD" from "EGLD-000000"
165        FunctionCall::new(ESDT_TRANSFER_FUNC_NAME)
166            .argument(&payment.token_identifier.as_managed_buffer())
167            .argument(&payment.amount)
168            .argument(&self)
169    }
170
171    /// Constructs `ESDTNFTTransfer` builtin function call.
172    ///
173    /// `ESDTNFTTransfer` takes 4 arguments:
174    /// arg0 - token identifier
175    /// arg1 - nonce
176    /// arg2 - quantity to transfer
177    /// arg3 - destination address
178    pub(crate) fn convert_to_single_transfer_nft_call(
179        self,
180        to: &ManagedAddress<Api>,
181        payment: EsdtTokenPaymentRefs<'_, Api>,
182    ) -> FunctionCall<Api> {
183        // EGLD not supported
184        // but serializing token identifier buffer for efficiency, no need to convert to "EGLD" from "EGLD-000000"
185        FunctionCall::new(ESDT_NFT_TRANSFER_FUNC_NAME)
186            .argument(&payment.token_identifier.as_managed_buffer())
187            .argument(&payment.token_nonce)
188            .argument(&payment.amount)
189            .argument(to)
190            .argument(&self)
191    }
192
193    /// Constructs `MultiESDTNFTTransfer` builtin function call.
194    pub(crate) fn convert_to_multi_transfer_esdt_call(
195        self,
196        to: &ManagedAddress<Api>,
197        payments: &MultiEgldOrEsdtPayment<Api>,
198    ) -> FunctionCall<Api> {
199        let mut result = FunctionCall::new(ESDT_MULTI_TRANSFER_FUNC_NAME)
200            .argument(&to)
201            .argument(&payments.len());
202
203        for payment in payments {
204            // serializing token identifier buffer to get EGLD-00000 instead of EGLD
205            result = result
206                .argument(&payment.token_identifier.token_id)
207                .argument(&payment.token_nonce)
208                .argument(&payment.amount);
209        }
210
211        result.argument(&self)
212    }
213}