1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    sandbox::utils::{
        contains_module, explain_execution_effects, explain_execution_error, get_gas_status,
        is_bytecode_file, maybe_commit_effects, on_disk_state_view::OnDiskStateView,
    },
    NativeFunctionRecord,
};
use move_binary_format::file_format::{CompiledModule, CompiledScript};
use move_core_types::{
    account_address::AccountAddress,
    errmap::ErrorMapping,
    identifier::IdentStr,
    language_storage::TypeTag,
    transaction_argument::{convert_txn_args, TransactionArgument},
};
use move_lang::{
    self, compiled_unit::AnnotatedCompiledUnit, shared::AddressBytes, Compiler, Flags,
};
use move_vm_runtime::move_vm::MoveVM;

use anyhow::{anyhow, bail, Result};
use std::{collections::BTreeMap, fs, path::Path};

pub fn run(
    natives: impl IntoIterator<Item = NativeFunctionRecord>,
    error_descriptions: &ErrorMapping,
    state: &OnDiskStateView,
    script_file: &str,
    script_name_opt: &Option<String>,
    signers: &[String],
    txn_args: &[TransactionArgument],
    vm_type_args: Vec<TypeTag>,
    named_address_mapping: BTreeMap<String, AddressBytes>,
    gas_budget: Option<u64>,
    dry_run: bool,
    verbose: bool,
) -> Result<()> {
    fn compile_script(
        state: &OnDiskStateView,
        script_file: &str,
        named_address_mapping: BTreeMap<String, AddressBytes>,
        verbose: bool,
    ) -> Result<Option<CompiledScript>> {
        if verbose {
            println!("Compiling transaction script...")
        }
        let (_files, compiled_units) =
            Compiler::new(&[script_file.to_string()], &[state.interface_files_dir()?])
                .set_flags(Flags::empty().set_sources_shadow_deps(false))
                .set_named_address_values(named_address_mapping)
                .build_and_report()?;

        let mut script_opt = None;
        for c in compiled_units {
            match c {
                AnnotatedCompiledUnit::Script(annot_script) => {
                    if script_opt.is_some() {
                        bail!("Error: Found more than one script")
                    }
                    script_opt = Some(annot_script.named_script.script)
                }
                AnnotatedCompiledUnit::Module(annot_module) => {
                    if verbose {
                        println!(
                            "Warning: Found module '{}' in file specified for the script. This \
                             module will not be published.",
                            annot_module.module_ident()
                        )
                    }
                }
            }
        }

        Ok(script_opt)
    }

    let path = Path::new(script_file);
    if !path.exists() {
        bail!("Script file {:?} does not exist", path)
    };
    let bytecode = if is_bytecode_file(path) {
        assert!(
            state.is_module_path(path) || !contains_module(path),
            "Attempting to run module {:?} outside of the `storage/` directory.
move run` must be applied to a module inside `storage/`",
            path
        );
        // script bytecode; read directly from file
        fs::read(path)?
    } else {
        // script source file; compile first and then extract bytecode
        let script_opt = compile_script(state, script_file, named_address_mapping, verbose)?;
        match script_opt {
            Some(script) => {
                let mut script_bytes = vec![];
                script.serialize(&mut script_bytes)?;
                script_bytes
            }
            None => bail!("Unable to find script in file {:?}", script_file),
        }
    };

    let signer_addresses = signers
        .iter()
        .map(|s| AccountAddress::from_hex_literal(s))
        .collect::<Result<Vec<AccountAddress>, _>>()?;
    // TODO: parse Value's directly instead of going through the indirection of TransactionArgument?
    let vm_args: Vec<Vec<u8>> = convert_txn_args(txn_args);

    let vm = MoveVM::new(natives).unwrap();
    let mut gas_status = get_gas_status(gas_budget)?;
    let mut session = vm.new_session(state);

    let script_type_parameters = vec![];
    let script_parameters = vec![];
    let res = match script_name_opt {
        Some(script_name) => {
            // script fun. parse module, extract script ID to pass to VM
            let module = CompiledModule::deserialize(&bytecode)
                .map_err(|e| anyhow!("Error deserializing module: {:?}", e))?;
            session
                .execute_script_function(
                    &module.self_id(),
                    IdentStr::new(script_name)?,
                    vm_type_args.clone(),
                    vm_args,
                    signer_addresses.clone(),
                    &mut gas_status,
                )
                .map(|_| ())
        }
        None => session.execute_script(
            bytecode.to_vec(),
            vm_type_args.clone(),
            vm_args,
            signer_addresses.clone(),
            &mut gas_status,
        ),
    };

    if let Err(err) = res {
        explain_execution_error(
            error_descriptions,
            err,
            state,
            &script_type_parameters,
            &script_parameters,
            &vm_type_args,
            &signer_addresses,
            txn_args,
        )
    } else {
        let (changeset, events) = session.finish().map_err(|e| e.into_vm_status())?;
        if verbose {
            explain_execution_effects(&changeset, &events, state)?
        }
        maybe_commit_effects(!dry_run, changeset, events, state)
    }
}