sui_adapter_latest/static_programmable_transactions/linkage/
analysis.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    data_store::PackageStore,
6    execution_mode::ExecutionMode,
7    execution_value::ExecutionState,
8    static_programmable_transactions::{
9        linkage::{
10            config::{LinkageConfig, ResolutionConfig},
11            resolution::{ResolutionTable, VersionConstraint, add_and_unify, get_package},
12            resolved_linkage::{ExecutableLinkage, ResolvedLinkage},
13        },
14        loading::ast::Type,
15    },
16};
17use move_binary_format::file_format::Visibility;
18use move_core_types::identifier::IdentStr;
19use move_vm_runtime::validation::verification::ast::Package as VerifiedPackage;
20use sui_protocol_config::ProtocolConfig;
21use sui_types::{
22    base_types::ObjectID, error::ExecutionError, execution_status::ExecutionErrorKind,
23    transaction::ProgrammableTransaction,
24};
25
26#[derive(Debug)]
27pub struct LinkageAnalyzer {
28    internal: ResolutionConfig,
29}
30
31impl LinkageAnalyzer {
32    pub fn new<Mode: ExecutionMode>(
33        protocol_config: &ProtocolConfig,
34    ) -> Result<Self, ExecutionError> {
35        let always_include_system_packages = !Mode::packages_are_predefined();
36        let linkage_config = LinkageConfig::new(
37            protocol_config
38                .include_special_package_amendments_as_option()
39                .clone(),
40            always_include_system_packages,
41        );
42        let binary_config = protocol_config.binary_config(None);
43        Ok(Self {
44            internal: ResolutionConfig::new(linkage_config, binary_config),
45        })
46    }
47
48    pub fn compute_call_linkage(
49        &self,
50        package: &ObjectID,
51        module_name: &IdentStr,
52        function_name: &IdentStr,
53        type_args: &[Type],
54        store: &dyn PackageStore,
55    ) -> Result<ExecutableLinkage, ExecutionError> {
56        Ok(ExecutableLinkage::new(
57            ResolvedLinkage::from_resolution_table(self.compute_call_linkage_(
58                package,
59                module_name,
60                function_name,
61                type_args,
62                store,
63            )?),
64        ))
65    }
66
67    pub fn compute_publication_linkage(
68        &self,
69        deps: &[ObjectID],
70        store: &dyn PackageStore,
71    ) -> Result<ResolvedLinkage, ExecutionError> {
72        Ok(ResolvedLinkage::from_resolution_table(
73            self.compute_publication_linkage_(deps, store)?,
74        ))
75    }
76
77    pub fn config(&self) -> &ResolutionConfig {
78        &self.internal
79    }
80
81    pub fn compute_input_type_resolution_linkage(
82        &self,
83        tx: &ProgrammableTransaction,
84        package_store: &dyn PackageStore,
85        object_store: &dyn ExecutionState,
86    ) -> Result<ExecutableLinkage, ExecutionError> {
87        input_type_resolution_analysis::compute_resolution_linkage(
88            self,
89            tx,
90            package_store,
91            object_store,
92        )
93    }
94
95    fn compute_call_linkage_(
96        &self,
97        package: &ObjectID,
98        module_name: &IdentStr,
99        function_name: &IdentStr,
100        type_args: &[Type],
101        store: &dyn PackageStore,
102    ) -> Result<ResolutionTable, ExecutionError> {
103        let mut resolution_table = self.internal.resolution_table_with_native_packages(store)?;
104
105        fn add_package(
106            object_id: &ObjectID,
107            store: &dyn PackageStore,
108            resolution_table: &mut ResolutionTable,
109            self_resolution_fn: fn(&VerifiedPackage) -> Option<VersionConstraint>,
110            dep_resolution_fn: fn(&VerifiedPackage) -> Option<VersionConstraint>,
111        ) -> Result<(), ExecutionError> {
112            let pkg = get_package(object_id, store)?;
113            let transitive_deps = resolution_table
114                .config
115                .linkage_table(&pkg)
116                .into_values()
117                .map(ObjectID::from);
118            for object_id in transitive_deps {
119                add_and_unify(&object_id, store, resolution_table, dep_resolution_fn)?;
120            }
121            add_and_unify(object_id, store, resolution_table, self_resolution_fn)?;
122            Ok(())
123        }
124
125        let pkg = get_package(package, store)?;
126        let fn_not_found_err = || {
127            ExecutionError::new_with_source(
128                ExecutionErrorKind::FunctionNotFound,
129                format!(
130                    "Could not resolve function '{}' in module '{}::{}'",
131                    function_name, package, module_name
132                ),
133            )
134        };
135        let fdef = pkg
136            .modules()
137            .iter()
138            .find(|m| m.0.name() == module_name)
139            .ok_or_else(fn_not_found_err)?
140            .1
141            .compiled_module()
142            .find_function_def_by_name(function_name.as_str())
143            .ok_or_else(fn_not_found_err)?;
144
145        let dep_resolution_fn = match fdef.1.visibility {
146            Visibility::Public => VersionConstraint::at_least,
147            Visibility::Private | Visibility::Friend => VersionConstraint::exact,
148        };
149
150        add_package(
151            package,
152            store,
153            &mut resolution_table,
154            VersionConstraint::exact,
155            dep_resolution_fn,
156        )?;
157
158        for type_defining_id in type_args.iter().flat_map(|ty| ty.all_addresses()) {
159            // Type arguments are "at least" constraints
160            add_package(
161                &ObjectID::from(type_defining_id),
162                store,
163                &mut resolution_table,
164                VersionConstraint::at_least,
165                VersionConstraint::at_least,
166            )?;
167        }
168
169        Ok(resolution_table)
170    }
171
172    /// Compute the linkage for a publish or upgrade command. This is a special case because
173    fn compute_publication_linkage_(
174        &self,
175        deps: &[ObjectID],
176        store: &dyn PackageStore,
177    ) -> Result<ResolutionTable, ExecutionError> {
178        let mut resolution_table = self.internal.resolution_table_with_native_packages(store)?;
179        for id in deps {
180            add_and_unify(id, store, &mut resolution_table, VersionConstraint::exact)?;
181        }
182        Ok(resolution_table)
183    }
184}
185
186mod input_type_resolution_analysis {
187    use crate::{
188        data_store::PackageStore,
189        execution_value::ExecutionState,
190        static_programmable_transactions::linkage::{
191            analysis::LinkageAnalyzer,
192            resolution::ResolutionTable,
193            resolved_linkage::{ExecutableLinkage, ResolvedLinkage},
194        },
195    };
196    use move_core_types::language_storage::StructTag;
197    use sui_types::{
198        base_types::ObjectID,
199        error::ExecutionError,
200        execution_status::ExecutionErrorKind,
201        transaction::{
202            CallArg, Command, FundsWithdrawalArg, ObjectArg, ProgrammableMoveCall,
203            ProgrammableTransaction, WithdrawalTypeArg,
204        },
205        type_input::TypeInput,
206    };
207
208    pub(super) fn compute_resolution_linkage(
209        analyzer: &LinkageAnalyzer,
210        tx: &ProgrammableTransaction,
211        package_store: &dyn PackageStore,
212        object_store: &dyn ExecutionState,
213    ) -> Result<ExecutableLinkage, ExecutionError> {
214        let ProgrammableTransaction { inputs, commands } = tx;
215
216        let mut resolution_table = analyzer
217            .internal
218            .resolution_table_with_native_packages(package_store)?;
219        for arg in inputs.iter() {
220            input(&mut resolution_table, arg, package_store, object_store)?;
221        }
222
223        for cmd in commands.iter() {
224            command(&mut resolution_table, cmd, package_store)?;
225        }
226
227        Ok(ExecutableLinkage::new(
228            ResolvedLinkage::from_resolution_table(resolution_table),
229        ))
230    }
231
232    fn input(
233        resolution_table: &mut ResolutionTable,
234        arg: &CallArg,
235        package_store: &dyn PackageStore,
236        object_store: &dyn ExecutionState,
237    ) -> Result<(), ExecutionError> {
238        match arg {
239            CallArg::Pure(_) | CallArg::Object(ObjectArg::Receiving(_)) => (),
240            CallArg::Object(
241                ObjectArg::ImmOrOwnedObject((id, _, _)) | ObjectArg::SharedObject { id, .. },
242            ) => {
243                let Some(obj) = object_store.read_object(id) else {
244                    invariant_violation!("Object {:?} not found in object store", id);
245                };
246                let Some(ty) = obj.type_() else {
247                    invariant_violation!("Object {:?} has does not have a Move type", id);
248                };
249
250                // invariant: the addresses in the type are defining addresses for the types since
251                // these are the types of the objects as stored on-chain.
252                let tag: StructTag = ty.clone().into();
253                let ids = tag.all_addresses().into_iter().map(ObjectID::from);
254                resolution_table.add_type_linkages_to_table(ids, package_store)?;
255            }
256            CallArg::FundsWithdrawal(f) => {
257                let FundsWithdrawalArg { type_arg, .. } = f;
258                match type_arg {
259                    WithdrawalTypeArg::Balance(tag) => {
260                        let ids = tag.all_addresses().into_iter().map(ObjectID::from);
261                        resolution_table.add_type_linkages_to_table(ids, package_store)?;
262                    }
263                }
264            }
265        }
266
267        Ok(())
268    }
269
270    fn command(
271        resolution_table: &mut ResolutionTable,
272        command: &Command,
273        package_store: &dyn PackageStore,
274    ) -> Result<(), ExecutionError> {
275        let mut add_ty_input = |ty: &TypeInput| {
276            let tag = ty.to_type_tag().map_err(|e| {
277                ExecutionError::new_with_source(
278                    ExecutionErrorKind::InvalidLinkage,
279                    format!("Invalid type tag in move call argument: {:?}", e),
280                )
281            })?;
282            let ids = tag.all_addresses().into_iter().map(ObjectID::from);
283            resolution_table.add_type_linkages_to_table(ids, package_store)
284        };
285        match command {
286            Command::MoveCall(pmc) => {
287                let ProgrammableMoveCall {
288                    package,
289                    type_arguments,
290                    ..
291                } = &**pmc;
292                type_arguments.iter().try_for_each(add_ty_input)?;
293                resolution_table.add_type_linkages_to_table([*package], package_store)?;
294            }
295            Command::MakeMoveVec(Some(ty), _) => {
296                add_ty_input(ty)?;
297            }
298            Command::MakeMoveVec(None, _)
299            | Command::TransferObjects(_, _)
300            | Command::SplitCoins(_, _)
301            | Command::MergeCoins(_, _) => (),
302            Command::Publish(_, object_ids) => {
303                resolution_table.add_type_linkages_to_table(object_ids, package_store)?;
304            }
305            Command::Upgrade(_, object_ids, object_id, _) => {
306                resolution_table.add_type_linkages_to_table([*object_id], package_store)?;
307                resolution_table.add_type_linkages_to_table(object_ids, package_store)?;
308            }
309        }
310
311        Ok(())
312    }
313}