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