multiversx_chain_vm/builtin_functions/
builtin_func_container.rs

1use super::{
2    builtin_func_trait::BuiltinFunction,
3    esdt_nft::{
4        ESDTLocalBurn, ESDTLocalMint, ESDTNftAddQuantity, ESDTNftAddUri, ESDTNftBurn,
5        ESDTNftCreate, ESDTNftUpdateAttributes,
6    },
7    general::{ChangeOwner, ClaimDeveloperRewards, DeleteUsername, SetUsername, UpgradeContract},
8    transfer::{ESDTMultiTransfer, ESDTNftTransfer, ESDTTransfer},
9    BuiltinFunctionEsdtTransferInfo,
10};
11
12use crate::{
13    host::context::{BlockchainUpdate, TxCache, TxInput, TxResult},
14    host::runtime::{RuntimeInstanceCallLambda, RuntimeRef},
15    types::EsdtLocalRole,
16};
17
18use crate::chain_core::builtin_func_names::*;
19
20/// Container for builtin function logic.
21///
22/// Currently has no data, but could conceivably be configurable in the future.
23#[derive(Default)]
24pub struct BuiltinFunctionContainer;
25
26impl BuiltinFunctionContainer {
27    /// If the call points to a builtin function, it executes it, otherwise calls the `or_else` closure.
28    ///
29    /// It also checks that the appropriate roles are set, where applicable.
30    pub fn execute_builtin_function_or_else<F, Else>(
31        &self,
32        runtime: &RuntimeRef,
33        tx_input: TxInput,
34        tx_cache: TxCache,
35        f: F,
36        or_else: Else,
37    ) -> (TxResult, BlockchainUpdate)
38    where
39        F: RuntimeInstanceCallLambda,
40        Else: FnOnce(TxInput, TxCache, F) -> (TxResult, BlockchainUpdate),
41    {
42        BuiltinFunctionCall::new(runtime, tx_input, tx_cache).execute_or_else(f, or_else)
43    }
44
45    /// Provides data on the builtin functions that perform ESDT token transfers.
46    pub fn extract_token_transfers(&self, tx_input: &TxInput) -> BuiltinFunctionEsdtTransferInfo {
47        match tx_input.func_name.as_str() {
48            ESDT_MULTI_TRANSFER_FUNC_NAME => bf_extract_transfers(ESDTMultiTransfer, tx_input),
49            ESDT_NFT_TRANSFER_FUNC_NAME => bf_extract_transfers(ESDTNftTransfer, tx_input),
50            ESDT_TRANSFER_FUNC_NAME => bf_extract_transfers(ESDTTransfer, tx_input),
51            _ => BuiltinFunctionEsdtTransferInfo::empty(tx_input),
52        }
53    }
54}
55
56fn bf_extract_transfers<B>(builtin_func: B, tx_input: &TxInput) -> BuiltinFunctionEsdtTransferInfo
57where
58    B: BuiltinFunction,
59{
60    builtin_func.extract_esdt_transfers(tx_input)
61}
62
63/// Syntax helper for the big builtin function match in `execute_or_else`.
64/// Thanks to it we do not need to write out the arguments for each match arm.
65struct BuiltinFunctionCall<'a> {
66    runtime: &'a RuntimeRef,
67    tx_input: TxInput,
68    tx_cache: TxCache,
69}
70
71impl<'a> BuiltinFunctionCall<'a> {
72    pub fn new(runtime: &'a RuntimeRef, tx_input: TxInput, tx_cache: TxCache) -> Self {
73        BuiltinFunctionCall {
74            runtime,
75            tx_input,
76            tx_cache,
77        }
78    }
79
80    pub fn execute_or_else<F, Else>(self, f: F, or_else: Else) -> (TxResult, BlockchainUpdate)
81    where
82        F: RuntimeInstanceCallLambda,
83        Else: FnOnce(TxInput, TxCache, F) -> (TxResult, BlockchainUpdate),
84    {
85        match self.tx_input.func_name.as_str() {
86            ESDT_LOCAL_MINT_FUNC_NAME => {
87                self.check_role_and_execute(EsdtLocalRole::Mint, ESDTLocalMint, f)
88            }
89            ESDT_LOCAL_BURN_FUNC_NAME => {
90                self.check_role_and_execute(EsdtLocalRole::Burn, ESDTLocalBurn, f)
91            }
92            ESDT_NFT_CREATE_FUNC_NAME => {
93                self.check_role_and_execute(EsdtLocalRole::NftCreate, ESDTNftCreate, f)
94            }
95            ESDT_NFT_BURN_FUNC_NAME => {
96                self.check_role_and_execute(EsdtLocalRole::NftBurn, ESDTNftBurn, f)
97            }
98            ESDT_NFT_ADD_QUANTITY_FUNC_NAME => {
99                self.check_role_and_execute(EsdtLocalRole::NftAddQuantity, ESDTNftAddQuantity, f)
100            }
101            ESDT_NFT_ADD_URI_FUNC_NAME => {
102                self.check_role_and_execute(EsdtLocalRole::NftAddUri, ESDTNftAddUri, f)
103            }
104            ESDT_NFT_UPDATE_ATTRIBUTES_FUNC_NAME => self.check_role_and_execute(
105                EsdtLocalRole::NftUpdateAttributes,
106                ESDTNftUpdateAttributes,
107                f,
108            ),
109
110            ESDT_MULTI_TRANSFER_FUNC_NAME => self.execute_bf(ESDTMultiTransfer, f),
111            ESDT_NFT_TRANSFER_FUNC_NAME => self.execute_bf(ESDTNftTransfer, f),
112            ESDT_TRANSFER_FUNC_NAME => self.execute_bf(ESDTTransfer, f),
113            CHANGE_OWNER_BUILTIN_FUNC_NAME => self.execute_bf(ChangeOwner, f),
114            CLAIM_DEVELOPER_REWARDS_FUNC_NAME => self.execute_bf(ClaimDeveloperRewards, f),
115            SET_USERNAME_FUNC_NAME => self.execute_bf(SetUsername, f),
116            DELETE_USERNAME_FUNC_NAME => self.execute_bf(DeleteUsername, f),
117            UPGRADE_CONTRACT_FUNC_NAME => self.execute_bf(UpgradeContract, f),
118            MIGRATE_USERNAME_FUNC_NAME => {
119                panic!("builtin function {MIGRATE_USERNAME_FUNC_NAME} was dropped")
120            }
121            _ => or_else(self.tx_input, self.tx_cache, f),
122        }
123    }
124
125    fn execute_bf<B, F>(self, builtin_func: B, f: F) -> (TxResult, BlockchainUpdate)
126    where
127        B: BuiltinFunction,
128        F: RuntimeInstanceCallLambda,
129    {
130        builtin_func.execute(self.tx_input, self.tx_cache, self.runtime, f)
131    }
132
133    fn check_role_and_execute<B, F>(
134        self,
135        role: EsdtLocalRole,
136        builtin_func: B,
137        f: F,
138    ) -> (TxResult, BlockchainUpdate)
139    where
140        B: BuiltinFunction,
141        F: RuntimeInstanceCallLambda,
142    {
143        if check_allowed_to_execute(role, &self.tx_input, &self.tx_cache) {
144            self.execute_bf(builtin_func, f)
145        } else {
146            (
147                TxResult::from_vm_error("action is not allowed"),
148                BlockchainUpdate::empty(),
149            )
150        }
151    }
152}
153
154fn check_allowed_to_execute(role: EsdtLocalRole, tx_input: &TxInput, tx_cache: &TxCache) -> bool {
155    let token_identifier = tx_input.args[0].clone();
156    let available_roles = tx_cache.with_account_mut(&tx_input.to, |account| {
157        account.esdt.get_roles(&token_identifier)
158    });
159    available_roles
160        .iter()
161        .any(|available_role| available_role.as_slice() == role.name().as_bytes())
162}