Skip to main content

sui_adapter_latest/static_programmable_transactions/typing/verify/
move_functions.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::execution_mode::ExecutionMode;
5use crate::sp;
6use crate::static_programmable_transactions::{env::Env, loading::ast::Type, typing::ast as T};
7use move_binary_format::file_format::Visibility;
8use move_core_types::identifier::IdentStr;
9use move_core_types::language_storage::ModuleId;
10use sui_types::base_types::TxContextKind;
11use sui_types::error::ExecutionErrorTrait;
12use sui_types::execution_status::{CommandArgumentError, ExecutionErrorKind};
13use sui_verifier::private_generics_verifier_v2;
14
15/// Checks the following
16/// - valid visibility for move function calls
17///   - Can be disabled under certain execution modes
18/// - private generics rules for move function calls
19/// - no references returned from move calls
20///    - Can be disabled under certain execution modes
21///    - Can be disabled via a feature flag
22/// - valid `TxContext` usage in the signature
23///    - Gated by a feature flag
24pub fn verify<Mode: ExecutionMode>(
25    env: &Env<Mode>,
26    txn: &T::Transaction,
27) -> Result<(), Mode::Error> {
28    for c in &txn.commands {
29        command::<Mode>(env, c).map_err(|e| e.with_command_index(c.idx as usize))?;
30    }
31    Ok(())
32}
33
34fn command<Mode: ExecutionMode>(
35    env: &Env<Mode>,
36    sp!(_, c): &T::Command,
37) -> Result<(), Mode::Error> {
38    let T::Command_ {
39        command,
40        result_type: _,
41        drop_values: _,
42        incurs_post_execution_checks: _,
43    } = c;
44    match command {
45        T::Command__::MoveCall(call) => move_call::<Mode>(env, call)?,
46        T::Command__::TransferObjects(_, _)
47        | T::Command__::SplitCoins(_, _, _)
48        | T::Command__::MergeCoins(_, _, _)
49        | T::Command__::MakeMoveVec(_, _)
50        | T::Command__::Publish(_, _, _)
51        | T::Command__::Upgrade(_, _, _, _, _) => (),
52    }
53    Ok(())
54}
55
56/// Checks a move call for
57/// - valid signature (no references in return type)
58/// - valid `TxContext` usage in the signature
59/// - valid visibility
60/// - private generics rules
61fn move_call<Mode: ExecutionMode>(env: &Env<Mode>, call: &T::MoveCall) -> Result<(), Mode::Error> {
62    let T::MoveCall {
63        function,
64        arguments: _,
65    } = call;
66    check_signature::<Mode>(env, function)?;
67    check_tx_context::<Mode>(env, function)?;
68    check_private_generics_v2(&function.original_mid, function.name.as_ident_str())?;
69    check_visibility::<Mode>(env, function)?;
70    Ok(())
71}
72
73fn check_signature<Mode: ExecutionMode>(
74    env: &Env<Mode>,
75    function: &T::LoadedFunction,
76) -> Result<(), Mode::Error> {
77    fn check_return_type<Mode: ExecutionMode, E: ExecutionErrorTrait>(
78        idx: usize,
79        return_type: &T::Type,
80    ) -> Result<(), E> {
81        if let Type::Reference(_, _) = return_type
82            && !Mode::allow_arbitrary_values()
83        {
84            return Err(E::from_kind(
85                ExecutionErrorKind::InvalidPublicFunctionReturnType {
86                    idx: checked_as!(idx, u16)?,
87                },
88            ));
89        }
90        Ok(())
91    }
92
93    if env.protocol_config.allow_references_in_ptbs() {
94        return Ok(());
95    }
96
97    for (idx, ty) in function.signature.return_.iter().enumerate() {
98        check_return_type::<Mode, Mode::Error>(idx, ty)?;
99    }
100    Ok(())
101}
102
103/// Checks `TxContext` usage in the function's signature:
104/// - In the parameters, `TxContext` can appear at most once as `&mut TxContext`, or any number of
105///   times as `&TxContext`. It can never be taken by value.
106/// - It can never appear in return position, meaning it can never become a result of a command.
107///
108/// These rules apply to the instantiated signature, so they cover generic parameters and return
109/// types instantiated with `TxContext`. Unlike the reference rules in `check_signature`, they are
110/// enforced under all execution modes.
111fn check_tx_context<Mode: ExecutionMode>(
112    env: &Env<Mode>,
113    function: &T::LoadedFunction,
114) -> Result<(), Mode::Error> {
115    if !env.protocol_config.ptb_tx_context_restrictions() {
116        return Ok(());
117    }
118    check_no_tx_context_by_value::<Mode::Error>(&function.signature.parameters)?;
119    check_tx_context_refs::<Mode::Error>(&function.signature.parameters)?;
120    check_no_tx_context_return::<Mode::Error>(&function.signature.return_)?;
121    Ok(())
122}
123
124/// `TxContext` can never be taken by value
125fn check_no_tx_context_by_value<E: ExecutionErrorTrait>(parameters: &[Type]) -> Result<(), E> {
126    let Some(idx) = parameters
127        .iter()
128        .position(|param| param.is_tx_context_by_value())
129    else {
130        return Ok(());
131    };
132    Err(E::new_with_source(
133        ExecutionErrorKind::command_argument_error(
134            CommandArgumentError::InvalidTxContext,
135            checked_as!(idx, u16)?,
136        ),
137        "TxContext cannot be taken by value",
138    ))
139}
140
141/// If `&mut TxContext` appears, it must be the only `TxContext` parameter: no other `TxContext`
142/// reference, mutable or immutable, may appear alongside it
143fn check_tx_context_refs<E: ExecutionErrorTrait>(parameters: &[Type]) -> Result<(), E> {
144    let mut mut_idxs = parameters
145        .iter()
146        .enumerate()
147        .filter(|(_, param)| param.is_tx_context() == TxContextKind::Mutable)
148        .map(|(idx, _)| idx);
149    let Some(first_mut_idx) = mut_idxs.next() else {
150        return Ok(());
151    };
152    if let Some(second_mut_idx) = mut_idxs.next() {
153        return Err(E::new_with_source(
154            ExecutionErrorKind::command_argument_error(
155                CommandArgumentError::InvalidTxContext,
156                checked_as!(second_mut_idx, u16)?,
157            ),
158            "TxContext can be taken by mutable reference at most once",
159        ));
160    }
161    if parameters
162        .iter()
163        .any(|param| param.is_tx_context() == TxContextKind::Immutable)
164    {
165        return Err(E::new_with_source(
166            ExecutionErrorKind::command_argument_error(
167                CommandArgumentError::InvalidTxContext,
168                checked_as!(first_mut_idx, u16)?,
169            ),
170            "&mut TxContext cannot be used alongside other TxContext parameters",
171        ));
172    }
173    Ok(())
174}
175
176/// `TxContext` can never appear in return position, by value or by reference
177fn check_no_tx_context_return<E: ExecutionErrorTrait>(return_: &[Type]) -> Result<(), E> {
178    let Some(idx) = return_.iter().position(|return_ty| {
179        return_ty.is_tx_context() != TxContextKind::None || return_ty.is_tx_context_by_value()
180    }) else {
181        return Ok(());
182    };
183    Err(E::new_with_source(
184        ExecutionErrorKind::command_argument_error(
185            CommandArgumentError::InvalidTxContext,
186            checked_as!(idx, u16)?,
187        ),
188        "TxContext cannot be returned from a Move call",
189    ))
190}
191
192fn check_visibility<Mode: ExecutionMode>(
193    _env: &Env<Mode>,
194    function: &T::LoadedFunction,
195) -> Result<(), Mode::Error> {
196    let visibility = function.visibility;
197    let is_entry = function.is_entry;
198    match (visibility, is_entry) {
199        // can call entry
200        (Visibility::Private | Visibility::Friend, true) => (),
201        // can call public entry
202        (Visibility::Public, true) => (),
203        // can call public
204        (Visibility::Public, false) => (),
205        // cannot call private or friend if not entry
206        (Visibility::Private | Visibility::Friend, false) => {
207            if !Mode::allow_arbitrary_function_calls() {
208                return Err(Mode::Error::new_with_source(
209                    ExecutionErrorKind::NonEntryFunctionInvoked,
210                    "Can only call `entry` or `public` functions",
211                ));
212            }
213        }
214    };
215    Ok(())
216}
217
218fn check_private_generics_v2<E: ExecutionErrorTrait>(
219    callee_package: &ModuleId,
220    callee_function: &IdentStr,
221) -> Result<(), E> {
222    let callee_address = *callee_package.address();
223    let callee_module = callee_package.name();
224    let callee = (callee_address, callee_module, callee_function);
225    let Some((_f, internal_type_parameters)) = private_generics_verifier_v2::FUNCTIONS_TO_CHECK
226        .iter()
227        .find(|(f, _)| &callee == f)
228    else {
229        return Ok(());
230    };
231    // If we find an internal type parameter, the call is automatically invalid--since we
232    // are not in a module and cannot define any types to satisfy the internal constraint.
233    let Some((internal_idx, _)) = internal_type_parameters
234        .iter()
235        .enumerate()
236        .find(|(_, is_internal)| **is_internal)
237    else {
238        // No `internal` type parameters, so it is ok to call
239        return Ok(());
240    };
241    let callee_package_name = private_generics_verifier_v2::callee_package_name(&callee_address);
242    let help =
243        private_generics_verifier_v2::help_message(&callee_address, callee_module, callee_function);
244    let msg = format!(
245        "Cannot directly call function '{}::{}::{}' since type parameter #{} can \
246                 only be instantiated with types defined within the caller's module.{}",
247        callee_package_name, callee_module, callee_function, internal_idx, help,
248    );
249    Err(E::new_with_source(
250        ExecutionErrorKind::NonEntryFunctionInvoked,
251        msg,
252    ))
253}