diff --git a/.cargo/config.toml b/.cargo/config.toml index 3a7bfeb7..b341c695 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ [env] RUST_BIGDECIMAL_FMT_EXPONENTIAL_UPPER_THRESHOLD="100" -RUST_BIGDECIMAL_FMT_EXPONENTIAL_LOWER_THRESHOLD="100" +RUST_BIGDECIMAL_FMT_EXPONENTIAL_LOWER_THRESHOLD="100" \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f8470eb0..f45d2636 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: - name: Install latest rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.77.1 + toolchain: 1.80.1 override: true components: rustfmt, clippy - uses: actions/setup-go@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 747db6f8..86f9df8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: - name: Install latest rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.77.1 + toolchain: 1.80.1 - name: Build Rust run: make build-rust - uses: actions/setup-go@v4 diff --git a/Cargo.toml b/Cargo.toml index 4a1d6cd1..11b95e5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ license = "BUSL-1.1" license-file = "LICENSE" homepage = "https://initia.xyz" repository = "https://github.com/initia-labs/movevm" -rust-version = "1.77.1" +rust-version = "1.80.1" [workspace.dependencies] # Internal crate dependencies. @@ -65,9 +65,11 @@ initia-move-resource-viewer = { path = "crates/resource-viewer" } # External crate dependencies. # Please do not add any test features here: they should be declared by the individual crate. -anyhow = "1.0.41" -bcs = "0.1.5" +ambassador = "0.4.1" +anyhow = "1.0.71" +bcs = { git = "https://github.com/aptos-labs/bcs.git", rev = "d31fab9d81748e2594be5cd5cdf845786a30562d" } better_any = "0.1.1" +claims = "0.7" clru = "^0.6.2" cbindgen = "0.26.0" clap = { version = "4.3.9", features = ["derive", "env", "suggestions"] } @@ -76,7 +78,7 @@ dialoguer = "0.10.2" ed25519-consensus = { version = "2.0.1", features = ["serde"] } errno = "0.3.0" hex = { version = "0.4.3", default-features = false } -itertools = "0.10.3" +itertools = "0.13" libsecp256k1 = { version = "0.7.1" } log = { version = "0.4.17", features = [ "max_level_debug", @@ -95,16 +97,17 @@ serde-generate = { git = "https://github.com/aptos-labs/serde-reflection", rev = serde-reflection = { git = "https://github.com/aptos-labs/serde-reflection", rev = "73b6bbf748334b71ff6d7d09d06a29e3062ca075" } sha2 = "0.10.8" sha3 = "0.10.6" -smallvec = "1.6.1" +smallvec = "1.8.0" smallbitvec = "2.5.1" -tempfile = "3.2.0" +tempfile = "3.3.0" serial_test = "1.0.0" -thiserror = "1.0.34" +thiserror = "1.0.37" tsu = "1.0.1" bytes = "1.4.0" parking_lot = "0.12.1" base64 = "0.21.7" bigdecimal = ">=0.4.5" +primitive-types = { version = "0.10" } bech32 = "0.11" triomphe = "0.1.9" ripemd = "0.1.1" @@ -113,51 +116,51 @@ tiny-keccak = { version = "2.0.2", features = ["keccak", "sha3"] } # Note: the BEGIN and END comments below are required for external tooling. Do not remove. # BEGIN MOVE DEPENDENCIES -move-binary-format = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-bytecode-verifier = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-bytecode-utils = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-cli = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-command-line-common = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-compiler = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-coverage = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-core-types = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-docgen = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-model = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-package = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-prover = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-prover-boogie-backend = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-prover-bytecode-pipeline = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-resource-viewer = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-stackless-bytecode = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-stdlib = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-symbol-pool = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-unit-test = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-vm-runtime = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-vm-test-utils = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } -move-vm-types = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "85966af7ddcf971f3f04f4f409c3e67c9d9b11cb" } +move-binary-format = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-bytecode-verifier = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-bytecode-utils = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-cli = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-command-line-common = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-compiler = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-coverage = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-core-types = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-docgen = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-model = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-package = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-prover = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-prover-boogie-backend = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-prover-bytecode-pipeline = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-resource-viewer = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-stackless-bytecode = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-stdlib = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-symbol-pool = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-unit-test = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-vm-runtime = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-vm-test-utils = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } +move-vm-types = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "751c9a34969e54dd18452d41f3c996fc2e971eb7" } # END MOVE DEPENDENCIES # FOR LOCAL DEVELOPMENNT -# move-binary-format = { path = "../aptos-core/third_party/move/move-binary-format" } -# move-bytecode-verifier = { path = "../aptos-core/third_party/move/move-bytecode-verifier" } -# move-bytecode-utils = { path = "../aptos-core/third_party/move/tools/move-bytecode-utils" } -# move-cli = { path = "../aptos-core/third_party/move/tools/move-cli" } -# move-command-line-common = { path = "../aptos-core/third_party/move/move-command-line-common" } -# move-compiler = { path = "../aptos-core/third_party/move/move-compiler" } -# move-coverage ={ path = "../aptos-core/third_party/move/tools/move-coverage" } -# move-core-types = { path = "../aptos-core/third_party/move/move-core/types" } -# move-docgen = { path = "../aptos-core/third_party/move/move-prover/move-docgen" } -# move-model = { path = "../aptos-core/third_party/move/move-model" } -# move-package = { path = "../aptos-core/third_party/move/tools/move-package" } -# move-prover = { path = "../aptos-core/third_party/move/move-prover" } -# move-prover-boogie-backend = { path = "../aptos-core/third_party/move/move-prover/boogie-backend" } -# move-prover-bytecode-pipeline = { path = "../aptos-core/third_party/move/move-prover/bytecode-pipeline" } -# move-resource-viewer = { path = "../aptos-core/third_party/move/tools/move-resource-viewer" } -# move-stackless-bytecode = { path = "../aptos-core/third_party/move/move-model/bytecode" } -# move-stdlib = { path = "../aptos-core/third_party/move/move-stdlib" } -# move-symbol-pool = { path = "../aptos-core/third_party/move/move-symbol-pool" } -# move-unit-test = { path = "../aptos-core/third_party/move/tools/move-unit-test" } -# move-vm-runtime = { path = "../aptos-core/third_party/move/move-vm/runtime" } -# move-vm-test-utils = { path = "../aptos-core/third_party/move/move-vm/test-utils" } -# move-vm-types = { path = "../aptos-core/third_party/move/move-vm/types" } +# move-binary-format = { path = "../move/move-binary-format" } +# move-bytecode-verifier = { path = "../move/move-bytecode-verifier" } +# move-bytecode-utils = { path = "../move/tools/move-bytecode-utils" } +# move-cli = { path = "../move/tools/move-cli" } +# move-command-line-common = { path = "../move/move-command-line-common" } +# move-compiler = { path = "../move/move-compiler" } +# move-coverage ={ path = "../move/tools/move-coverage" } +# move-core-types = { path = "../move/move-core/types" } +# move-docgen = { path = "../move/move-prover/move-docgen" } +# move-model = { path = "../move/move-model" } +# move-package = { path = "../move/tools/move-package" } +# move-prover = { path = "../move/move-prover" } +# move-prover-boogie-backend = { path = "../move/move-prover/boogie-backend" } +# move-prover-bytecode-pipeline = { path = "../move/move-prover/bytecode-pipeline" } +# move-resource-viewer = { path = "../move/tools/move-resource-viewer" } +# move-stackless-bytecode = { path = "../move/move-model/bytecode" } +# move-stdlib = { path = "../move/move-stdlib" } +# move-symbol-pool = { path = "../move/move-symbol-pool" } +# move-unit-test = { path = "../move/tools/move-unit-test" } +# move-vm-runtime = { path = "../move/move-vm/runtime" } +# move-vm-test-utils = { path = "../move/move-vm/test-utils" } +# move-vm-types = { path = "../move/move-vm/types" } diff --git a/Makefile b/Makefile index adc8200c..a43891bb 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ test-safety: # Use package list mode to include all subdirectores. The -count=1 turns off caching. GODEBUG=cgocheck=2 go test -race -v -count=1 -parallel=1 ./... -test-rust: test-compiler test-lib test-e2e test-movevm test-json +test-rust: test-compiler test-lib test-e2e test-movevm test-json test-storage test-compiler: cargo test -p initia-move-compiler @@ -67,6 +67,9 @@ test-json: test-lib: cargo test -p initia-move-vm +test-storage: + cargo test -p initia-move-storage + test-e2e: cargo test -p e2e-move-tests --features testing diff --git a/README.md b/README.md index bf90bf4d..ad4a437f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Initia MoveVM + The Initia MoveVM is forked from [wasmvm](https://github.com/CosmWasm/wasmvm) with some modifications to support ([Aptos](https://github.com/aptos-labs/aptos-core)) MoveVM. This mechanism allows the users to compile, initialize, and execute Move Smart Contracts from Go applications, in particular from [x/move](https://github.com/initia-labs/initia/tree/main/x/move). @@ -8,7 +9,7 @@ This repo contains both Rust and Go codes. The rust code is compiled into a dll/ ## Supported Platform -Requires **Rust 1.77+ and Go 1.22+.** +Requires **Rust 1.80+ and Go 1.22+.** The Rust implementation of the VM is compiled to a library called libmovevm. This is then linked to the Go code when the final binary is built. For that reason not all systems supported by Go are supported by this project. @@ -44,4 +45,4 @@ There are two parts to this code - go and rust. The first step is to ensure that If this is present, then `make test` will run the Go test suite and you can import this code freely. If it is not present you will have to build it for your system, and ideally add it to this repo with a PR (on your fork). We will set up a proper CI system for building these binaries, but we are not there yet. -To build the rust side, try make `build-rust` and wait for it to compile. This depends on `cargo` being installed with rustc version 1.77+. Generally, you can just use rustup to install all this with no problems. +To build the rust side, try make `build-rust` and wait for it to compile. This depends on `cargo` being installed with rustc version 1.80+. Generally, you can just use rustup to install all this with no problems. diff --git a/builders/Dockerfile.alpine b/builders/Dockerfile.alpine index 0a6e2a99..2f8a2f0a 100644 --- a/builders/Dockerfile.alpine +++ b/builders/Dockerfile.alpine @@ -16,7 +16,7 @@ RUN set -eux \ RUN wget "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-musl/rustup-init" \ && chmod +x rustup-init \ - && ./rustup-init -y --no-modify-path --profile minimal --default-toolchain 1.77.1 \ + && ./rustup-init -y --no-modify-path --profile minimal --default-toolchain 1.80.1 \ && rm rustup-init \ && chmod -R a+w $RUSTUP_HOME $CARGO_HOME diff --git a/builders/Dockerfile.centos7 b/builders/Dockerfile.centos7 index 99368d07..5b2bb0d7 100644 --- a/builders/Dockerfile.centos7 +++ b/builders/Dockerfile.centos7 @@ -27,7 +27,7 @@ ENV RUSTUP_HOME=/usr/local/rustup \ RUN wget "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init" \ && chmod +x rustup-init \ - && ./rustup-init -y --no-modify-path --profile minimal --default-toolchain 1.77.1 \ + && ./rustup-init -y --no-modify-path --profile minimal --default-toolchain 1.80.1 \ && rm rustup-init \ && chmod -R a+w $RUSTUP_HOME $CARGO_HOME \ && rustup --version \ diff --git a/builders/Dockerfile.cross b/builders/Dockerfile.cross index d6fd5a8d..ca323eee 100644 --- a/builders/Dockerfile.cross +++ b/builders/Dockerfile.cross @@ -1,4 +1,4 @@ -FROM rust:1.77.1-bullseye +FROM rust:1.80.1-bullseye # Install build dependencies RUN apt-get update \ diff --git a/crates/compiler/src/unit_test_factory.rs b/crates/compiler/src/unit_test_factory.rs index 172c64aa..c3380ca7 100644 --- a/crates/compiler/src/unit_test_factory.rs +++ b/crates/compiler/src/unit_test_factory.rs @@ -27,11 +27,12 @@ impl InitiaUnitTestFactory { let table_change_set = table_context .into_change_set() .map_err(|e| e.finish(Location::Undefined))?; - let write_set = WriteSet::new(changes.clone(), table_change_set).map_err(|e| { - PartialVMError::new(StatusCode::FAILED_TO_SERIALIZE_WRITE_SET_CHANGES) - .with_message(e.to_string()) - .finish(Location::Undefined) - })?; + let write_set = + WriteSet::new_with_change_set(changes.clone(), table_change_set).map_err(|e| { + PartialVMError::new(StatusCode::FAILED_TO_SERIALIZE_WRITE_SET_CHANGES) + .with_message(e.to_string()) + .finish(Location::Undefined) + })?; gas_meter.charge_write_set_gas(&write_set)?; diff --git a/crates/e2e-move-tests/Cargo.toml b/crates/e2e-move-tests/Cargo.toml index c900b90a..d6b5e665 100644 --- a/crates/e2e-move-tests/Cargo.toml +++ b/crates/e2e-move-tests/Cargo.toml @@ -45,5 +45,6 @@ default = [] testing = [ "initia-move-gas/testing", "initia-move-natives/testing", + "initia-move-storage/testing", "move-vm-runtime/testing", ] diff --git a/crates/e2e-move-tests/src/harness.rs b/crates/e2e-move-tests/src/harness.rs index bd2b00b0..b5d6b2f6 100644 --- a/crates/e2e-move-tests/src/harness.rs +++ b/crates/e2e-move-tests/src/harness.rs @@ -3,6 +3,7 @@ use bytes::Bytes; use initia_move_compiler::built_package::BuiltPackage; +use initia_move_natives::code::UpgradePolicy; use initia_move_types::env::Env; use initia_move_types::view_function::{ViewFunction, ViewOutput}; use move_core_types::account_address::AccountAddress; @@ -13,7 +14,7 @@ use move_package::BuildConfig; use crate::test_utils::mock_chain::{MockAPI, MockChain, MockState, MockTableState}; use crate::test_utils::parser::MemberId; use initia_move_gas::Gas; -use initia_move_storage::{state_view::StateView, state_view_impl::StateViewImpl}; +use initia_move_storage::state_view::StateView; use initia_move_types::access_path::AccessPath; use initia_move_types::message::{Message, MessageOutput}; use initia_move_types::module::ModuleBundle; @@ -69,7 +70,6 @@ impl MoveHarness { pub fn initialize(&mut self) { let state = self.chain.create_state(); - let resolver = StateViewImpl::new(&state); let mut table_resolver = MockTableState::new(&state); let env = Env::new( @@ -85,7 +85,7 @@ impl MoveHarness { .initialize( &self.api, &env, - &resolver, + &state, &mut table_resolver, self.load_precompiled_stdlib() .expect("Failed to load precompiles"), @@ -109,9 +109,10 @@ impl MoveHarness { &mut self, acc: &AccountAddress, path: &str, + upgrade_policy: UpgradePolicy, ) -> Result { - let (module_ids, code) = self.compile_package(path); - let msg = self.create_publish_message(*acc, module_ids, code); + let code = self.compile_package(path); + let msg = self.create_publish_message(*acc, code, upgrade_policy); self.run_message(msg) } @@ -158,7 +159,6 @@ impl MoveHarness { view_fn: ViewFunction, state: &MockState, ) -> Result { - let resolver = StateViewImpl::new(state); let mut table_resolver = MockTableState::new(state); let gas_limit = Gas::new(100_000_000u64); @@ -176,7 +176,7 @@ impl MoveHarness { &mut gas_meter, &self.api, &env, - &resolver, + state, &mut table_resolver, &view_fn, ) @@ -188,7 +188,7 @@ impl MoveHarness { vals } - pub fn compile_package(&mut self, path: &str) -> (Vec, Vec>) { + pub fn compile_package(&mut self, path: &str) -> Vec> { let package_path = path_in_crate(path); let package = BuiltPackage::build( package_path.clone(), @@ -202,29 +202,22 @@ impl MoveHarness { ) .expect("compile failed"); - ( - package - .modules() - .map(|v| v.self_id().short_str_lossless()) - .collect(), - package.extract_code(), - ) + package.extract_code() } pub fn create_publish_message( &mut self, sender: AccountAddress, - module_ids: Vec, modules: Vec>, + upgrade_policy: UpgradePolicy, ) -> Message { let ef = MoveHarness::create_entry_function_with_json( - str::parse("0x1::code::publish").unwrap(), + str::parse("0x1::code::publish_v2").unwrap(), vec![], vec![ - serde_json::to_string(&module_ids).unwrap(), serde_json::to_string(&modules.iter().map(hex::encode).collect::>()) .unwrap(), - serde_json::to_string(&(1_u8)).unwrap(), // compatible upgrade policy + serde_json::to_string(&upgrade_policy.to_u8()).unwrap(), // compatible upgrade policy ], ); Message::execute(vec![sender], ef) @@ -321,8 +314,6 @@ impl MoveHarness { ); let state = self.chain.create_state(); - - let resolver = StateViewImpl::new(&state); let mut table_resolver = MockTableState::new(&state); let gas_limit: initia_move_gas::GasQuantity = @@ -332,7 +323,7 @@ impl MoveHarness { &mut gas_meter, &self.api, &env, - &resolver, + &state, &mut table_resolver, message, ) @@ -351,7 +342,6 @@ impl MoveHarness { Self::generate_random_hash().try_into().unwrap(), ); - let resolver = StateViewImpl::new(state); let mut table_resolver = MockTableState::new(state); let gas_limit = Gas::new(100_000_000u64); @@ -360,7 +350,7 @@ impl MoveHarness { &mut gas_meter, &self.api, &env, - &resolver, + state, &mut table_resolver, message, ) diff --git a/crates/e2e-move-tests/src/tests/args.rs b/crates/e2e-move-tests/src/tests/args.rs index 4ed1def4..3a6eab77 100644 --- a/crates/e2e-move-tests/src/tests/args.rs +++ b/crates/e2e-move-tests/src/tests/args.rs @@ -1,6 +1,7 @@ use crate::MoveHarness; use bigdecimal::num_bigint::BigUint; use bigdecimal::FromPrimitive; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::identifier::Identifier; use move_core_types::language_storage::{StructTag, TypeTag}; @@ -26,7 +27,9 @@ fn success_generic(ty_args: Vec, tests: Vec) { h.initialize(); // publish package - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); // Check in initial state, resource does not exist. @@ -74,7 +77,9 @@ fn fail_generic(ty_args: Vec, tests: Vec<(&str, Vec>, StatusCod h.initialize(); // publish package - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); // Check in initial state, resource does not exist. @@ -749,7 +754,9 @@ fn json_object_args() { h.initialize(); // publish package - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); // execute create_object @@ -794,7 +801,9 @@ fn biguint_bigdecimal() { h.initialize(); // publish package - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); // execute create_object diff --git a/crates/e2e-move-tests/src/tests/cache.rs b/crates/e2e-move-tests/src/tests/cache.rs index cfeaf23f..7c328b40 100644 --- a/crates/e2e-move-tests/src/tests/cache.rs +++ b/crates/e2e-move-tests/src/tests/cache.rs @@ -1,4 +1,5 @@ use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; // revive this at loader v2 @@ -31,11 +32,7 @@ fn test_redeploy_should_update_module_cache() { h.initialize(); let code = hex::decode("a11ceb0b060000000d01000a020a12031c28044404054822076aa001088a022010aa02420aec02140b8003020c8203670de903020eeb030200000001000200030004000509010001000600000007060003110700000800010100000900020100000a03040100000b04010002120700000413040801000114050401060505060901050103010b00010900020c03000109000205070b0001090001060c010803010802094261736963436f696e056576656e74067369676e657206737472696e6709747970655f696e666f04436f696e06496e69746961094d696e744576656e7403676574086765745f636f696e046d696e74066e756d6265720576616c756504746573740b64756d6d795f6669656c6406616d6f756e7409636f696e5f7479706506537472696e670a616464726573735f6f6609747970655f6e616d6504656d6974000000000000000000000000000000000000000000000000000000000000000113696e697469613a3a6d657461646174615f76302d0001094d696e744576656e740104000303676574010100066e756d626572010100086765745f636f696e0101000002020c030d010102010e010202020f031008030005000100010004050b003d0037001402010100010004040b003d0014020201040100061d0e0011040c020a023b0020040d0e000a010839003f0005180b023c000c030a033700140a01160b033600150b0138001202380102030100000402064101000000000000020000000500").expect("ms"); - let msg = h.create_publish_message( - AccountAddress::ONE, - vec!["0x1::BasicCoin".to_string()], - vec![code], - ); + let msg = h.create_publish_message(AccountAddress::ONE, vec![code], UpgradePolicy::Compatible); let output = h.run_message(msg).expect("should success"); // commit to store for republish check, @@ -53,7 +50,7 @@ fn test_redeploy_should_update_module_cache() { // upgrade module let output = h - .publish_package(&AccountAddress::ONE, path) + .publish_package(&AccountAddress::ONE, path, UpgradePolicy::Compatible) .expect("should success"); // have to use new module cache, diff --git a/crates/e2e-move-tests/src/tests/code.rs b/crates/e2e-move-tests/src/tests/code.rs new file mode 100644 index 00000000..dd06c51d --- /dev/null +++ b/crates/e2e-move-tests/src/tests/code.rs @@ -0,0 +1,118 @@ +use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; +use move_core_types::account_address::AccountAddress; + +fn setup_harness() -> (MoveHarness, AccountAddress) { + let mut h = MoveHarness::new(); + let acc = AccountAddress::from_hex_literal("0x9999").unwrap(); + h.initialize(); + (h, acc) +} + +fn publish_and_commit( + h: &mut MoveHarness, + acc: &AccountAddress, + path: &str, + policy: UpgradePolicy, +) { + let output = h + .publish_package(acc, path, policy) + .expect("should succeed"); + h.commit(output, true); +} + +fn view_string(h: &mut MoveHarness, path: &str) -> String { + let view_function = h.create_view_function(str::parse(path).unwrap(), vec![], vec![]); + + h.run_view_function(view_function).expect("should succeed") +} + +#[test] +fn test_simple_publish_compatible() { + let (mut h, acc) = setup_harness(); + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer", + UpgradePolicy::Compatible, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer::view_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); + + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer2", + UpgradePolicy::Compatible, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer2::view_my_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); +} + +#[test] +fn test_simple_publish_immutable() { + let (mut h, acc) = setup_harness(); + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer", + UpgradePolicy::Immutable, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer::view_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); + + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer2", + UpgradePolicy::Immutable, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer2::view_my_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); +} + +#[test] +fn test_publish_immutable_referring_compatible() { + let (mut h, acc) = setup_harness(); + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer", + UpgradePolicy::Compatible, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer::view_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); + + let path = "src/tests/string_viewer.data/viewer2"; + h.publish_package(&acc, path, UpgradePolicy::Immutable) + .expect_err("expected an error during package publishing"); +} + +#[test] +fn test_publish_compatible_referring_immutable() { + let (mut h, acc) = setup_harness(); + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer", + UpgradePolicy::Immutable, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer::view_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); + + publish_and_commit( + &mut h, + &acc, + "src/tests/string_viewer.data/viewer2", + UpgradePolicy::Compatible, + ); + + let view_output = view_string(&mut h, "0x9999::string_viewer2::view_my_string"); + assert_eq!("\"Hello, World!\"".to_string(), view_output); +} diff --git a/crates/e2e-move-tests/src/tests/cosmos.rs b/crates/e2e-move-tests/src/tests/cosmos.rs index f0c1d2fb..9877ddc1 100644 --- a/crates/e2e-move-tests/src/tests/cosmos.rs +++ b/crates/e2e-move-tests/src/tests/cosmos.rs @@ -2,6 +2,7 @@ use core::str; use crate::tests::common::ExpectedOutput; use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use initia_move_types::cosmos::{CosmosCallback, CosmosMessage}; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::TypeTag; @@ -63,7 +64,9 @@ fn run_tests(tests: Vec) { h.initialize(); // publish package - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); for (sender, entry, ty_args, args, exp_output) in tests { diff --git a/crates/e2e-move-tests/src/tests/infinite_loop.rs b/crates/e2e-move-tests/src/tests/infinite_loop.rs index 6dd55c52..b703a065 100644 --- a/crates/e2e-move-tests/src/tests/infinite_loop.rs +++ b/crates/e2e-move-tests/src/tests/infinite_loop.rs @@ -1,4 +1,5 @@ use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::vm_status::StatusCode; @@ -12,7 +13,9 @@ fn empty_while_loop() { h.initialize(); - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); let err = h diff --git a/crates/e2e-move-tests/src/tests/max_loop_depth.rs b/crates/e2e-move-tests/src/tests/max_loop_depth.rs index da52470b..629093f5 100644 --- a/crates/e2e-move-tests/src/tests/max_loop_depth.rs +++ b/crates/e2e-move-tests/src/tests/max_loop_depth.rs @@ -1,4 +1,5 @@ use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::{account_address::AccountAddress, vm_status::StatusCode}; #[test] @@ -8,7 +9,9 @@ fn module_loop_depth_at_limit() { let mut h = MoveHarness::new(); h.initialize(); - let _ = h.publish_package(&acc, path).expect("should success"); + let _ = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); } #[test] @@ -17,6 +20,8 @@ fn module_loop_depth_just_above_limit() { let path = "src/tests/max_loop_depth.data/pack-bad"; let mut h = MoveHarness::new(); h.initialize(); - let status = h.publish_package(&acc, path).expect_err("should error"); + let status = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect_err("should error"); assert!(status.status_code() == StatusCode::LOOP_MAX_DEPTH_REACHED); } diff --git a/crates/e2e-move-tests/src/tests/memory_quota.rs b/crates/e2e-move-tests/src/tests/memory_quota.rs index b4303f9f..b1ac3d95 100644 --- a/crates/e2e-move-tests/src/tests/memory_quota.rs +++ b/crates/e2e-move-tests/src/tests/memory_quota.rs @@ -1,4 +1,5 @@ use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::vm_status::StatusCode; @@ -9,7 +10,9 @@ fn clone_large_vectors() { let mut h = MoveHarness::new(); h.initialize(); - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); let _ = h @@ -41,7 +44,9 @@ fn add_vec_to_table() { // Load the code h.initialize(); - let output = h.publish_package(&acc, path).expect("should success"); + let output = h + .publish_package(&acc, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); let status = h diff --git a/crates/e2e-move-tests/src/tests/mod.rs b/crates/e2e-move-tests/src/tests/mod.rs index 00e155b9..76711ba2 100644 --- a/crates/e2e-move-tests/src/tests/mod.rs +++ b/crates/e2e-move-tests/src/tests/mod.rs @@ -1,5 +1,6 @@ mod args; mod cache; +mod code; mod common; mod cosmos; mod infinite_loop; diff --git a/crates/e2e-move-tests/src/tests/output.rs b/crates/e2e-move-tests/src/tests/output.rs index 50b364ce..d3605227 100644 --- a/crates/e2e-move-tests/src/tests/output.rs +++ b/crates/e2e-move-tests/src/tests/output.rs @@ -1,5 +1,6 @@ use crate::tests::common::ExpectedOutput; use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::TypeTag; use move_core_types::vm_status::VMStatus; @@ -22,7 +23,7 @@ fn run_tests(tests: Vec) { // publish std coin let output = h - .publish_package(&minter_addr, path) + .publish_package(&minter_addr, path, UpgradePolicy::Compatible) .expect("should success"); h.commit(output, true); diff --git a/crates/e2e-move-tests/src/tests/std_coin.rs b/crates/e2e-move-tests/src/tests/std_coin.rs index 11ca7c42..b4921170 100644 --- a/crates/e2e-move-tests/src/tests/std_coin.rs +++ b/crates/e2e-move-tests/src/tests/std_coin.rs @@ -1,5 +1,6 @@ use crate::tests::common::ExpectedOutput; use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::TypeTag; use move_core_types::vm_status::VMStatus; @@ -32,7 +33,7 @@ fn run_tests(tests: Vec) { // publish std coin let output = h - .publish_package(&minter_addr, path) + .publish_package(&minter_addr, path, UpgradePolicy::Compatible) .expect("should success"); h.commit(output, true); diff --git a/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/Move.toml b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/Move.toml new file mode 100644 index 00000000..3ff69c9f --- /dev/null +++ b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "StringViewer" +version = "0.0.0" + +[dependencies] +InitiaStdlib = { local = "../../../../../../precompile/modules/initia_stdlib" } + +[addresses] +std = "0x1" +publisher = "0x9999" diff --git a/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/sources/StringViewer.move b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/sources/StringViewer.move new file mode 100644 index 00000000..150177ad --- /dev/null +++ b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer/sources/StringViewer.move @@ -0,0 +1,8 @@ +module publisher::string_viewer { + use std::string; + + #[view] + public fun view_string(): string::String { + string::utf8(b"Hello, World!") + } +} diff --git a/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/Move.toml b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/Move.toml new file mode 100644 index 00000000..0a73ab8b --- /dev/null +++ b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/Move.toml @@ -0,0 +1,11 @@ +[package] +name = "StringViewer2" +version = "0.0.0" + +[dependencies] +InitiaStdlib = { local = "../../../../../../precompile/modules/initia_stdlib" } +StringViewer = { local = "../viewer" } + +[addresses] +std = "0x1" +publisher = "0x9999" diff --git a/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/sources/StringViewer2.move b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/sources/StringViewer2.move new file mode 100644 index 00000000..623c076e --- /dev/null +++ b/crates/e2e-move-tests/src/tests/string_viewer.data/viewer2/sources/StringViewer2.move @@ -0,0 +1,9 @@ +module publisher::string_viewer2 { + use std::string; + use publisher::string_viewer; + + #[view] + public fun view_my_string(): string::String { + string_viewer::view_string() + } +} diff --git a/crates/e2e-move-tests/src/tests/table.rs b/crates/e2e-move-tests/src/tests/table.rs index 9452e448..e16bdef5 100644 --- a/crates/e2e-move-tests/src/tests/table.rs +++ b/crates/e2e-move-tests/src/tests/table.rs @@ -1,6 +1,7 @@ use crate::test_utils::generate_account; use crate::tests::common::{ExpectedOutput, ExpectedOutputItem}; use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::TypeTag; use move_core_types::vm_status::VMStatus; @@ -22,7 +23,9 @@ fn run_tests(tests: Vec) { h.initialize(); // publish table data - let output = h.publish_package(&test_addr, path).expect("should success"); + let output = h + .publish_package(&test_addr, path, UpgradePolicy::Compatible) + .expect("should success"); h.commit(output, true); for (senders, entry, ty_args, args, exp_output) in tests { diff --git a/crates/e2e-move-tests/src/tests/view_output.rs b/crates/e2e-move-tests/src/tests/view_output.rs index 56487865..382d94b6 100644 --- a/crates/e2e-move-tests/src/tests/view_output.rs +++ b/crates/e2e-move-tests/src/tests/view_output.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use crate::MoveHarness; +use initia_move_natives::code::UpgradePolicy; use initia_move_types::json_event::JsonEvents; use initia_move_types::view_function::ViewFunction; use move_core_types::identifier::Identifier; @@ -18,7 +19,7 @@ fn test_view_output() { // publish std coin let output = h - .publish_package(&deployer_addr, path) + .publish_package(&deployer_addr, path, UpgradePolicy::Compatible) .expect("should success"); h.commit(output, true); diff --git a/crates/json/Cargo.toml b/crates/json/Cargo.toml index 91476571..0e9b78e3 100644 --- a/crates/json/Cargo.toml +++ b/crates/json/Cargo.toml @@ -14,11 +14,13 @@ default = [] [dependencies] initia-move-types = { workspace = true } +initia-move-storage = { workspace = true } ## Move Dependencies move-core-types = { workspace = true } move-vm-types = { workspace = true } move-binary-format = { workspace = true } +move-vm-runtime = { workspace = true } bigdecimal = { workspace = true } serde_json = { workspace = true } @@ -31,5 +33,3 @@ smallbitvec = { workspace = true } triomphe = { workspace = true } bytes = { workspace = true } anyhow = { workspace = true } - -initia-move-storage = { workspace = true } diff --git a/crates/json/src/json_to_move.rs b/crates/json/src/json_to_move.rs index dcb3800b..c4609b17 100644 --- a/crates/json/src/json_to_move.rs +++ b/crates/json/src/json_to_move.rs @@ -6,6 +6,7 @@ use bigdecimal::{ BigDecimal, Signed, }; use bytes::Bytes; +use initia_move_storage::{initia_storage::InitiaStorage, state_view::StateView}; use move_binary_format::errors::{PartialVMResult, VMResult}; use move_core_types::{ account_address::AccountAddress, @@ -15,6 +16,7 @@ use move_core_types::{ u256::U256, value::{MoveTypeLayout, MoveValue}, }; +use move_vm_runtime::ModuleStorage; use move_vm_types::{ loaded_data::runtime_types::{ StructNameIndex, StructType, @@ -22,21 +24,25 @@ use move_vm_types::{ }, resolver::ResourceResolver, }; - use serde_json::Value as JSONValue; use crate::errors::{deserialization_error, deserialization_error_with_msg}; pub trait StructResolver { - fn get_struct_type(&self, index: StructNameIndex) -> Option>; - fn get_type_tag(&self, ty: &Type) -> VMResult; + fn get_struct_type( + &self, + index: StructNameIndex, + module_storage: &impl ModuleStorage, + ) -> Option>; + fn type_to_type_tag(&self, ty: &Type, module_storage: &impl ModuleStorage) + -> VMResult; } // deserialize json argument to JSONValue and convert to MoveValue, // and then do bcs serialization. -pub fn deserialize_json_args( - struct_resolver: &S, - resource_resolver: &R, +pub fn deserialize_json_args( + code_storage: &InitiaStorage, + struct_resolver: &impl StructResolver, ty: &Type, arg: &[u8], ) -> VMResult> { @@ -52,14 +58,14 @@ pub fn deserialize_json_args( serde_json::from_slice(arg).map_err(deserialization_error_with_msg)?; let move_val = - convert_json_value_to_move_value(struct_resolver, resource_resolver, ty, json_val, 1)?; + convert_json_value_to_move_value(code_storage, struct_resolver, ty, json_val, 1)?; bcs::to_bytes(&move_val).map_err(deserialization_error_with_msg) } // convert JSONValue to MoveValue. -fn convert_json_value_to_move_value( - struct_resolver: &S, - resource_resolver: &R, +fn convert_json_value_to_move_value( + code_storage: &InitiaStorage, + struct_resolver: &impl StructResolver, ty: &Type, json_val: JSONValue, depth: usize, @@ -122,8 +128,8 @@ fn convert_json_value_to_move_value( let mut vec = Vec::new(); for json_val in json_vals { vec.push(convert_json_value_to_move_value( + code_storage, struct_resolver, - resource_resolver, ty, json_val, depth + 1, @@ -133,7 +139,7 @@ fn convert_json_value_to_move_value( } Struct { idx, .. } => { let st = struct_resolver - .get_struct_type(*idx) + .get_struct_type(*idx, code_storage) .ok_or_else(deserialization_error)?; let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); match full_name.as_str() { @@ -216,7 +222,7 @@ fn convert_json_value_to_move_value( } let st = struct_resolver - .get_struct_type(*idx) + .get_struct_type(*idx, code_storage) .ok_or_else(deserialization_error)?; let ty = ty_args.first().ok_or_else(deserialization_error)?; let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); @@ -227,8 +233,8 @@ fn convert_json_value_to_move_value( } return Ok(MoveValue::Vector(vec![convert_json_value_to_move_value( + code_storage, struct_resolver, - resource_resolver, ty, json_val, depth + 1, @@ -243,7 +249,7 @@ fn convert_json_value_to_move_value( // verify a object // 1) address is holding object core resource // 2) object is holding inner type resource - verify_object(struct_resolver, resource_resolver, addr, ty)?; + verify_object(code_storage, struct_resolver, addr, ty)?; MoveValue::Address(addr) } @@ -259,12 +265,13 @@ fn convert_json_value_to_move_value( } // verify object address is holding object core and inner type resources. -fn verify_object( - struct_resolver: &S, - resource_resolver: &R, +fn verify_object( + code_storage: &InitiaStorage, + struct_resolver: &impl StructResolver, addr: AccountAddress, inner_type: &Type, ) -> VMResult<()> { + let resource_resolver = code_storage.state_view_impl(); // verify a object hold object core if resource_resolver .get_resource_bytes_with_metadata_and_layout( @@ -287,8 +294,9 @@ fn verify_object( // verify a object hold inner type let inner_type_tag = struct_resolver - .get_type_tag(inner_type) + .type_to_type_tag(inner_type, code_storage) .map_err(deserialization_error_with_msg)?; + let inner_type_st = if let TypeTag::Struct(inner_type_st) = inner_type_tag { inner_type_st } else { @@ -331,7 +339,9 @@ mod json_arg_testing { use bigdecimal::FromPrimitive; use bytes::Bytes; - use initia_move_storage::{state_view::StateView, state_view_impl::StateViewImpl}; + use initia_move_storage::{ + module_cache::InitiaModuleCache, script_cache::InitiaScriptCache, state_view::StateView, + }; use initia_move_types::access_path::{AccessPath, DataPath}; use move_binary_format::{ errors::{Location, PartialVMError}, @@ -343,11 +353,14 @@ mod json_arg_testing { language_storage::{ModuleId, StructTag}, vm_status::StatusCode, }; + use move_vm_runtime::RuntimeEnvironment; use move_vm_types::loaded_data::runtime_types::AbilityInfo; use smallbitvec::SmallBitVec; use super::*; + const TEST_CACHE_CAPACITY: usize = 100; + struct MockState { pub map: BTreeMap, Vec>, pub structs: BTreeMap>, @@ -363,24 +376,35 @@ mod json_arg_testing { } impl StructResolver for MockState { - fn get_struct_type(&self, index: StructNameIndex) -> Option> { + fn get_struct_type( + &self, + index: StructNameIndex, + _module_storage: &impl ModuleStorage, + ) -> Option> { self.structs.get(&index).cloned() } - fn get_type_tag(&self, ty: &Type) -> VMResult { + fn type_to_type_tag( + &self, + ty: &Type, + module_storage: &impl ModuleStorage, + ) -> VMResult { match ty { Struct { idx, .. } => { - let st = self.structs.get(idx).ok_or_else(|| { - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .finish(Location::Undefined) - })?; + let struct_ty = + self.get_struct_type(*idx, module_storage).ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .finish(Location::Undefined) + })?; Ok(TypeTag::Struct(Box::new(StructTag { - address: st.module.address, - module: st.module.name.clone(), - name: st.name.clone(), + address: struct_ty.module.address, + module: struct_ty.module.name.clone(), + name: struct_ty.name.clone(), type_args: vec![], }))) } - _ => unimplemented!(), + _ => { + Err(PartialVMError::new(StatusCode::TYPE_MISMATCH).finish(Location::Undefined)) + } } } } @@ -395,138 +419,202 @@ mod json_arg_testing { #[test] fn test_deserialize_json_args_u8() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U8; let arg = b"123"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&123u8).unwrap()); // invalid negative let arg = b"-123"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"123.4567"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_u16() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U16; let arg = b"123"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&123u16).unwrap()); // invalid negative let arg = b"-123"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"123.4567"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_u32() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U32; let arg = b"123"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&123u32).unwrap()); // invalid negative let arg = b"-123"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"123.4567"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_u64() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U64; let arg = b"\"123\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&123u64).unwrap()); // invalid negative let arg = b"\"-123\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"\"123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_u128() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U128; let arg = b"\"123\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&123u128).unwrap()); // invalid negative let arg = b"\"-123\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"\"123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_u256() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::U256; let arg = b"\"123\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&U256::from(123u128)).unwrap()); // invalid negative let arg = b"\"-123\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); // invalid decimal let arg = b"\"123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] fn test_deserialize_json_args_bool() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::Bool; let arg = b"true"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&true).unwrap()); } #[test] fn test_deserialize_json_args_address() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::Address; let arg = b"\"0x1\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, bcs::to_bytes(&"0x1".parse::().unwrap()).unwrap() @@ -536,27 +624,43 @@ mod json_arg_testing { #[test] fn test_deserialize_json_args_vec_u8() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::Vector(triomphe::Arc::new(Type::U8)); let arg = b"[0, 1, 2, 3]"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&vec![0u8, 1u8, 2u8, 3u8]).unwrap()); // hex string to vector let arg = b"\"00010203\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes(&vec![0u8, 1u8, 2u8, 3u8]).unwrap()); } #[test] fn test_deserialize_json_args_vec_address() { let mock_state = mock_state(); - let state_view = StateViewImpl::new(&mock_state); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); let ty = Type::Vector(triomphe::Arc::new(Type::Address)); let arg = b"[\"0x1\", \"0x2\"]"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, bcs::to_bytes(&vec![ @@ -568,7 +672,7 @@ mod json_arg_testing { // invalid inner addresss let arg = b"[\"0xgg\"]"; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } pub fn for_test(module_name: &str, name: &str) -> StructType { @@ -588,24 +692,34 @@ mod json_arg_testing { #[test] fn test_deserialize_json_args_string() { let mut mock_state = mock_state(); + mock_state .structs .insert(StructNameIndex(0), Arc::new(for_test("string", "String"))); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::Struct { idx: StructNameIndex(0), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"hello\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!(result, bcs::to_bytes("hello").unwrap()); } #[test] fn test_deserialize_json_args_object() { let mut mock_state = mock_state(); + mock_state .structs .insert(StructNameIndex(0), Arc::new(for_test("object", "Object"))); @@ -663,14 +777,23 @@ mod json_arg_testing { let hex_addr = format!("\"{}\"", obj_addr.to_hex_literal()); let arg = hex_addr.as_bytes(); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + // valid object address - let state_view = StateViewImpl::new(&mock_state); - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg); + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg); assert_eq!(result.unwrap(), bcs::to_bytes(&obj_addr).unwrap()); // invalid object address let wrong_object_addr_arg = b"\"0x1\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, wrong_object_addr_arg) + _ = deserialize_json_args(&code_storage, &mock_state, &ty, wrong_object_addr_arg) .unwrap_err(); // invalid inner type @@ -682,11 +805,11 @@ mod json_arg_testing { ability: AbilityInfo::struct_(AbilitySet::singleton(Ability::Key)), }]), }; - _ = deserialize_json_args(&mock_state, &state_view, &wrong_inner_ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &wrong_inner_ty, arg).unwrap_err(); // invalid address let arg = b"\"0xgg\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] @@ -696,15 +819,24 @@ mod json_arg_testing { .structs .insert(StructNameIndex(0), Arc::new(for_test("option", "Option"))); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::StructInstantiation { idx: StructNameIndex(0), ty_args: triomphe::Arc::new(vec![Type::Address]), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"0x1\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, bcs::to_bytes(&vec!["0x1".parse::().unwrap()]).unwrap() @@ -712,7 +844,7 @@ mod json_arg_testing { // invalid inner value let arg = b"\"0xgg\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] @@ -722,15 +854,24 @@ mod json_arg_testing { .structs .insert(StructNameIndex(0), Arc::new(for_test("option", "Option"))); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::StructInstantiation { idx: StructNameIndex(0), ty_args: triomphe::Arc::new(vec![Type::Address]), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"null"; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, bcs::to_bytes::>(&vec![]).unwrap() @@ -745,14 +886,22 @@ mod json_arg_testing { Arc::new(for_test("fixed_point32", "FixedPoint32")), ); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::Struct { idx: StructNameIndex(0), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"123.4567\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, @@ -761,7 +910,7 @@ mod json_arg_testing { // invalid negative let arg = b"\"-123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] @@ -772,14 +921,23 @@ mod json_arg_testing { Arc::new(for_test("fixed_point64", "FixedPoint64")), ); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::Struct { idx: StructNameIndex(0), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"123.4567\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, @@ -788,7 +946,7 @@ mod json_arg_testing { // invalid negative let arg = b"\"-123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] @@ -798,14 +956,22 @@ mod json_arg_testing { .structs .insert(StructNameIndex(0), Arc::new(for_test("biguint", "BigUint"))); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::Struct { idx: StructNameIndex(0), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"1234567\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, @@ -814,7 +980,7 @@ mod json_arg_testing { // invalid negative let arg = b"\"-1234567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } #[test] @@ -825,14 +991,22 @@ mod json_arg_testing { Arc::new(for_test("bigdecimal", "BigDecimal")), ); - let state_view = StateViewImpl::new(&mock_state); - let ty = Type::Struct { idx: StructNameIndex(0), ability: AbilityInfo::struct_(AbilitySet::ALL), }; let arg = b"\"123.4567\""; - let result = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap(); + let runtime_environment = RuntimeEnvironment::new(vec![]); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let code_storage = InitiaStorage::new( + &mock_state, + &runtime_environment, + script_cache, + module_cache, + ); + + let result = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap(); assert_eq!( result, @@ -846,6 +1020,6 @@ mod json_arg_testing { // invalid negative let arg = b"\"-123.4567\""; - _ = deserialize_json_args(&mock_state, &state_view, &ty, arg).unwrap_err(); + _ = deserialize_json_args(&code_storage, &mock_state, &ty, arg).unwrap_err(); } } diff --git a/crates/natives/src/code.rs b/crates/natives/src/code.rs index 331a0aab..8c764ccc 100644 --- a/crates/natives/src/code.rs +++ b/crates/natives/src/code.rs @@ -1,5 +1,4 @@ use crate::{ - helpers::get_string, interface::{ RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, SafeNativeResult, }, @@ -9,20 +8,37 @@ use better_any::{Tid, TidAble}; use initia_move_types::module::ModuleBundle; use move_core_types::{account_address::AccountAddress, gas_algebra::NumBytes}; use move_vm_runtime::native_functions::NativeFunction; -use move_vm_types::{ - loaded_data::runtime_types::Type, - values::{Struct, Value}, -}; +use move_vm_types::{loaded_data::runtime_types::Type, values::Value}; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; use std::collections::VecDeque; -/// Whether a compatibility check should be performed for upgrades. The check only passes if -/// a new module has (a) the same public functions (b) for existing resources, no layout change. -const _UPGRADE_POLICY_COMPATIBLE: u8 = 1; +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub enum UpgradePolicy { + /// Whether a compatibility check should be performed for upgrades. The check only passes if + /// a new module has (a) the same public functions (b) for existing resources, no layout change. + Compatible = 1, + /// Whether the modules in the package are immutable and cannot be upgraded. + Immutable = 2, +} + +impl TryFrom for UpgradePolicy { + type Error = (); -/// Whether the modules in the package are immutable and cannot be upgraded. -const _UPGRADE_POLICY_IMMUTABLE: u8 = 2; + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(UpgradePolicy::Compatible), + 2 => Ok(UpgradePolicy::Immutable), + _ => Err(()), + } + } +} + +impl UpgradePolicy { + pub fn to_u8(self) -> u8 { + self as u8 + } +} /// A wrapper around the representation of a Move Option, which is a vector with 0 or 1 element. /// TODO: move this elsewhere for reuse? @@ -63,7 +79,7 @@ const ECATEGORY_INVALID_ARGUMENT: u64 = 0x1; // native errors always start from 100 const EALREADY_REQUESTED: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 100; -const EUNABLE_TO_PARSE_STRING: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 101; +const EINVALID_UPGRADE_POLICY: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 101; /// The native code context. #[derive(Tid, Default)] @@ -76,9 +92,9 @@ pub struct NativeCodeContext { /// Represents a request for code publishing made from a native call and to be processed /// by the Initia VM. pub struct PublishRequest { - pub destination: AccountAddress, + pub publisher: AccountAddress, pub module_bundle: ModuleBundle, - pub expected_modules: Option>, + pub upgrade_policy: UpgradePolicy, } /*************************************************************************************************** @@ -112,6 +128,12 @@ fn native_request_publish( context.charge(gas_params.code_request_publish_base_cost)?; + let upgrade_policy = UpgradePolicy::try_from(safely_pop_arg!(arguments, u8)).map_err(|_| { + SafeNativeError::Abort { + abort_code: EINVALID_UPGRADE_POLICY, + } + })?; + let mut code: Vec> = vec![]; for module_code in safely_pop_vec_arg!(arguments, Vec) { context.charge( @@ -120,21 +142,7 @@ fn native_request_publish( code.push(module_code); } - let mut expected_modules: Vec = vec![]; - for name in safely_pop_vec_arg!(arguments, Struct) { - let str_bytes = get_string(name)?; - - context.charge( - gas_params.code_request_publish_per_byte * NumBytes::new(str_bytes.len() as u64), - )?; - expected_modules.push(String::from_utf8(str_bytes).map_err(|_| { - SafeNativeError::Abort { - abort_code: EUNABLE_TO_PARSE_STRING, - } - })?); - } - - let destination = safely_pop_arg!(arguments, AccountAddress); + let publisher = safely_pop_arg!(arguments, AccountAddress); let code_context = context.extensions_mut().get_mut::(); if code_context.requested_module_bundle.is_some() { @@ -144,9 +152,9 @@ fn native_request_publish( } code_context.requested_module_bundle = Some(PublishRequest { - destination, + publisher, module_bundle: ModuleBundle::new(code), - expected_modules: Some(expected_modules), + upgrade_policy, }); Ok(smallvec![]) diff --git a/crates/natives/src/native_functions.rs b/crates/natives/src/native_functions.rs deleted file mode 100644 index 9d00394a..00000000 --- a/crates/natives/src/native_functions.rs +++ /dev/null @@ -1,163 +0,0 @@ -use move_binary_format::errors::{ - ExecutionState, Location, PartialVMError, PartialVMResult, VMResult, -}; -use move_core_types::{ - account_address::AccountAddress, - gas_algebra::{InternalGas, NumBytes}, - identifier::Identifier, - language_storage::TypeTag, - value::MoveTypeLayout, - vm_status::StatusCode, -}; -use move_vm_types::{ - loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value, -}; -use std::{ - collections::{HashMap, VecDeque}, - fmt::Write, - sync::Arc, -}; - -pub type UnboxedNativeFunction = dyn Fn(&mut NativeContext, Vec, VecDeque) -> PartialVMResult - + Send - + Sync - + 'static; - -pub type NativeFunction = Arc; - -pub type NativeFunctionTable = Vec<(AccountAddress, Identifier, Identifier, NativeFunction)>; - -pub fn make_table( - addr: AccountAddress, - elems: &[(&str, &str, NativeFunction)], -) -> NativeFunctionTable { - make_table_from_iter(addr, elems.iter().cloned()) -} - -pub fn make_table_from_iter>>( - addr: AccountAddress, - elems: impl IntoIterator, -) -> NativeFunctionTable { - elems - .into_iter() - .map(|(module_name, func_name, func)| { - ( - addr, - Identifier::new(module_name).unwrap(), - Identifier::new(func_name).unwrap(), - func, - ) - }) - .collect() -} - -pub(crate) struct NativeFunctions( - HashMap>>, -); - -impl NativeFunctions { - pub fn resolve( - &self, - addr: &AccountAddress, - module_name: &str, - func_name: &str, - ) -> Option { - self.0.get(addr)?.get(module_name)?.get(func_name).cloned() - } - - pub fn new(natives: I) -> PartialVMResult - where - I: IntoIterator, - { - let mut map = HashMap::new(); - for (addr, module_name, func_name, func) in natives.into_iter() { - let modules = map.entry(addr).or_insert_with(HashMap::new); - let funcs = modules - .entry(module_name.into_string()) - .or_insert_with(HashMap::new); - - if funcs.insert(func_name.into_string(), func).is_some() { - return Err(PartialVMError::new(StatusCode::DUPLICATE_NATIVE_FUNCTION)); - } - } - Ok(Self(map)) - } -} - -pub struct NativeContext<'a, 'b, 'c> { - interpreter: &'a mut Interpreter, - data_store: &'a mut TransactionDataCache<'c>, - resolver: &'a Resolver<'a>, - extensions: &'a mut NativeContextExtensions<'b>, - gas_balance: InternalGas, -} - -impl<'a, 'b, 'c> NativeContext<'a, 'b, 'c> { - pub(crate) fn new( - interpreter: &'a mut Interpreter, - data_store: &'a mut TransactionDataCache<'c>, - resolver: &'a Resolver<'a>, - extensions: &'a mut NativeContextExtensions<'b>, - gas_balance: InternalGas, - ) -> Self { - Self { - interpreter, - data_store, - resolver, - extensions, - gas_balance, - } - } -} - -impl<'a, 'b, 'c> NativeContext<'a, 'b, 'c> { - pub fn print_stack_trace(&self, buf: &mut B) -> PartialVMResult<()> { - self.interpreter - .debug_print_stack_trace(buf, self.resolver.loader()) - } - - pub fn exists_at( - &mut self, - address: AccountAddress, - type_: &Type, - ) -> VMResult<(bool, Option)> { - let (value, num_bytes) = self - .data_store - .load_resource(self.resolver.loader(), address, type_) - .map_err(|err| err.finish(Location::Undefined))?; - let exists = value - .exists() - .map_err(|err| err.finish(Location::Undefined))?; - Ok((exists, num_bytes)) - } - - pub fn type_to_type_tag(&self, ty: &Type) -> PartialVMResult { - self.resolver.loader().type_to_type_tag(ty) - } - - pub fn type_to_type_layout(&self, ty: &Type) -> PartialVMResult { - self.resolver.type_to_type_layout(ty) - } - - pub fn type_to_fully_annotated_layout(&self, ty: &Type) -> PartialVMResult { - self.resolver.type_to_fully_annotated_layout(ty) - } - - pub fn extensions(&self) -> &NativeContextExtensions<'b> { - self.extensions - } - - pub fn extensions_mut(&mut self) -> &mut NativeContextExtensions<'b> { - self.extensions - } - - /// Get count stack frames, including the one of the called native function. This - /// allows a native function to reflect about its caller. - pub fn stack_frames(&self, count: usize) -> ExecutionState { - self.interpreter.get_stack_frames(count) - } - - pub fn gas_balance(&self) -> InternalGas { - self.gas_balance - } -} diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index aa18858d..9408372f 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -14,9 +14,17 @@ move-vm-runtime = { workspace = true } move-vm-types = { workspace = true } move-bytecode-utils = { workspace = true } +ambassador = { workspace = true } anyhow = { workspace = true } thiserror = { workspace = true } bytes = { workspace = true } clru = { workspace = true } parking_lot = { workspace = true } sha3 = { workspace = true } +claims = { workspace = true } + +[features] +default = [] +testing = [ + "move-vm-runtime/testing", +] diff --git a/crates/storage/src/code_scale.rs b/crates/storage/src/code_scale.rs new file mode 100644 index 00000000..382ac6ea --- /dev/null +++ b/crates/storage/src/code_scale.rs @@ -0,0 +1,34 @@ +use clru::WeightScale; +use std::sync::Arc; + +use move_binary_format::file_format::CompiledScript; +use move_binary_format::CompiledModule; +use move_vm_runtime::Module; +use move_vm_runtime::Script; +use move_vm_types::code::{Code, ModuleCode}; + +use crate::module_cache::BytesWithHash; +use crate::module_cache::NoVersion; +use crate::state_view::Checksum; + +pub struct CodeScale; + +impl WeightScale> for CodeScale { + fn weight(&self, _key: &Checksum, _value: &Code) -> usize { + 1 + } +} + +pub struct ModuleCodeScale; + +impl WeightScale>> + for ModuleCodeScale +{ + fn weight( + &self, + _key: &Checksum, + _value: &Arc>, + ) -> usize { + 1 + } +} diff --git a/crates/storage/src/code_storage.rs b/crates/storage/src/code_storage.rs new file mode 100644 index 00000000..0dfe76d1 --- /dev/null +++ b/crates/storage/src/code_storage.rs @@ -0,0 +1,273 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use ambassador::Delegate; +use bytes::Bytes; +use move_binary_format::{errors::VMResult, file_format::CompiledScript, CompiledModule}; +use move_core_types::{account_address::AccountAddress, identifier::IdentStr, metadata::Metadata}; +use move_vm_runtime::{ + ambassador_impl_ModuleStorage, ambassador_impl_WithRuntimeEnvironment, + logging::expect_no_verification_errors, CodeStorage, Module, ModuleStorage, RuntimeEnvironment, + Script, WithRuntimeEnvironment, +}; +use move_vm_types::{ + code::{Code, ModuleBytesStorage}, + module_linker_error, sha3_256, +}; +use std::sync::Arc; + +use crate::{ + module_cache::InitiaModuleCache, + module_storage::{AsInitiaModuleStorage, InitiaModuleStorage}, + script_cache::InitiaScriptCache, + state_view::ChecksumStorage, +}; + +/// Code storage that stores both modules and scripts (not thread-safe). +#[allow(clippy::duplicated_attributes)] +#[derive(Delegate)] +#[delegate(WithRuntimeEnvironment, target = "module_storage")] +#[delegate(ModuleStorage, target = "module_storage")] +pub struct InitiaCodeStorage { + script_cache: Arc, + module_storage: M, +} + +pub trait AsInitiaCodeStorage<'a, S> { + fn as_initia_code_storage( + &'a self, + env: &'a RuntimeEnvironment, + script_cache: Arc, + module_cache: Arc, + ) -> InitiaCodeStorage>; + + fn into_initia_code_storage( + self, + env: &'a RuntimeEnvironment, + script_cache: Arc, + module_cache: Arc, + ) -> InitiaCodeStorage>; +} + +impl<'a, S: ModuleBytesStorage + ChecksumStorage> AsInitiaCodeStorage<'a, S> for S { + fn as_initia_code_storage( + &'a self, + env: &'a RuntimeEnvironment, + script_cache: Arc, + module_cache: Arc, + ) -> InitiaCodeStorage> { + InitiaCodeStorage::new( + script_cache, + self.as_initia_module_storage(env, module_cache), + ) + } + + fn into_initia_code_storage( + self, + env: &'a RuntimeEnvironment, + script_cache: Arc, + module_cache: Arc, + ) -> InitiaCodeStorage> { + InitiaCodeStorage::new( + script_cache, + self.into_initia_module_storage(env, module_cache), + ) + } +} + +impl InitiaCodeStorage { + /// Creates a new storage with no scripts. There are no constraints on which modules exist in + /// module storage. + fn new(script_cache: Arc, module_storage: M) -> Self { + Self { + script_cache, + module_storage, + } + } + + /// Returns the underlying module storage used by this code storage. + pub fn module_storage(&self) -> &M { + &self.module_storage + } +} + +impl CodeStorage for InitiaCodeStorage { + fn deserialize_and_cache_script( + &self, + serialized_script: &[u8], + ) -> VMResult> { + let hash = sha3_256(serialized_script); + Ok(match self.script_cache.get_script(&hash) { + Some(script) => script.deserialized().clone(), + None => { + let deserialized_script = self + .runtime_environment() + .deserialize_into_script(serialized_script)?; + self.script_cache + .insert_deserialized_script(hash, deserialized_script)? + } + }) + } + + fn verify_and_cache_script(&self, serialized_script: &[u8]) -> VMResult> { + use Code::*; + + let hash = sha3_256(serialized_script); + let deserialized_script = match self.script_cache.get_script(&hash) { + Some(Verified(script)) => return Ok(script), + Some(Deserialized(deserialized_script)) => deserialized_script, + None => self + .runtime_environment() + .deserialize_into_script(serialized_script) + .map(Arc::new)?, + }; + + // Locally verify the script. + let locally_verified_script = self + .runtime_environment() + .build_locally_verified_script(deserialized_script)?; + + // Verify the script is correct w.r.t. its dependencies. + let immediate_dependencies = locally_verified_script + .immediate_dependencies_iter() + .map(|(addr, name)| { + // Since module is stored on-chain, we should not see any verification errors here. + self.fetch_verified_module(addr, name) + .map_err(expect_no_verification_errors)? + .ok_or_else(|| module_linker_error!(addr, name)) + }) + .collect::>>()?; + let verified_script = self + .runtime_environment() + .build_verified_script(locally_verified_script, &immediate_dependencies)?; + + self.script_cache + .insert_verified_script(hash, verified_script) + } +} + +#[cfg(test)] +use crate::state_view::Checksum; +impl InitiaCodeStorage { + /// Test-only method that checks the state of the script cache. + #[cfg(test)] + pub(crate) fn assert_cached_state<'b>( + &self, + deserialized: Vec<&'b Checksum>, + verified: Vec<&'b Checksum>, + ) { + assert_eq!( + self.script_cache.num_scripts(), + deserialized.len() + verified.len() + ); + for hash in deserialized { + let script = claims::assert_some!(self.script_cache.get_script(hash)); + assert!(!script.is_verified()) + } + for hash in verified { + let script = claims::assert_some!(self.script_cache.get_script(hash)); + assert!(script.is_verified()) + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use claims::assert_ok; + use move_binary_format::{ + file_format::empty_script_with_dependencies, file_format_common::VERSION_DEFAULT, + }; + use move_core_types::{ + account_address::AccountAddress, identifier::Identifier, language_storage::ModuleId, + }; + use move_vm_runtime::{CodeStorage, RuntimeEnvironment}; + use move_vm_types::sha3_256; + + use crate::{ + code_storage::AsInitiaCodeStorage, + memory_module_storage::InMemoryStorage, + module_cache::InitiaModuleCache, + module_storage::test::{add_module_bytes, TEST_CACHE_CAPACITY}, + script_cache::InitiaScriptCache, + }; + + pub fn make_script<'a>(dependencies: impl IntoIterator) -> Vec { + let mut script = empty_script_with_dependencies(dependencies); + script.version = VERSION_DEFAULT; + + let mut serialized_script = vec![]; + assert_ok!(script.serialize(&mut serialized_script)); + serialized_script + } + + #[test] + fn test_deserialized_script_caching() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + + add_module_bytes(&mut module_bytes_storage, "a", vec!["b", "c"], vec![]); + add_module_bytes(&mut module_bytes_storage, "b", vec![], vec![]); + add_module_bytes(&mut module_bytes_storage, "c", vec![], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let code_storage = module_bytes_storage.into_initia_code_storage( + &runtime_environment, + script_cache, + module_cache, + ); + + let serialized_script = make_script(vec!["a"]); + let hash_1 = sha3_256(&serialized_script); + assert_ok!(code_storage.deserialize_and_cache_script(&serialized_script)); + + let serialized_script = make_script(vec!["b"]); + let hash_2 = sha3_256(&serialized_script); + assert_ok!(code_storage.deserialize_and_cache_script(&serialized_script)); + + code_storage.assert_cached_state(vec![&hash_1, &hash_2], vec![]); + } + + #[test] + fn test_verified_script_caching() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let script_cache = InitiaScriptCache::new(TEST_CACHE_CAPACITY); + + let a_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + let b_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("b").unwrap()); + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec!["b", "c"], vec![]); + let checksum_b = add_module_bytes(&mut module_bytes_storage, "b", vec![], vec![]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec![], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let code_storage = module_bytes_storage.into_initia_code_storage( + &runtime_environment, + script_cache, + module_cache, + ); + + let serialized_script = make_script(vec!["a"]); + let hash = sha3_256(&serialized_script); + assert_ok!(code_storage.deserialize_and_cache_script(&serialized_script)); + + // Nothing gets loaded into module cache. + code_storage + .module_storage() + .assert_cached_state(vec![], vec![], vec![], vec![]); + code_storage.assert_cached_state(vec![&hash], vec![]); + + assert_ok!(code_storage.verify_and_cache_script(&serialized_script)); + + // Script is verified, so its dependencies are loaded into cache. + code_storage.module_storage().assert_cached_state( + vec![], + vec![], + vec![&a_id, &b_id, &c_id], + vec![&checksum_a, &checksum_b, &checksum_c], + ); + code_storage.assert_cached_state(vec![], vec![&hash]); + } +} diff --git a/crates/storage/src/initia_storage.rs b/crates/storage/src/initia_storage.rs new file mode 100644 index 00000000..f6d788eb --- /dev/null +++ b/crates/storage/src/initia_storage.rs @@ -0,0 +1,47 @@ +use crate::{ + code_storage::{AsInitiaCodeStorage, InitiaCodeStorage}, + module_cache::InitiaModuleCache, + module_storage::InitiaModuleStorage, + script_cache::InitiaScriptCache, + state_view::StateView, + state_view_impl::StateViewImpl, +}; +use ambassador::Delegate; +use bytes::Bytes; +use move_binary_format::{errors::VMResult, file_format::CompiledScript, CompiledModule}; +use move_core_types::{account_address::AccountAddress, identifier::IdentStr, metadata::Metadata}; +use move_vm_runtime::{ + ambassador_impl_CodeStorage, ambassador_impl_ModuleStorage, + ambassador_impl_WithRuntimeEnvironment, CodeStorage, Module, ModuleStorage, RuntimeEnvironment, + Script, WithRuntimeEnvironment, +}; +use std::sync::Arc; + +#[derive(Delegate)] +#[delegate(WithRuntimeEnvironment)] +#[delegate(ModuleStorage)] +#[delegate(CodeStorage)] +pub struct InitiaStorage<'s, S> { + storage: InitiaCodeStorage>>, +} + +impl<'s, S: StateView> InitiaStorage<'s, S> { + pub fn new( + state_view: &'s S, + runtime_environment: &'s RuntimeEnvironment, + script_cache: Arc, + module_cache: Arc, + ) -> Self { + let state_view_impl = StateViewImpl::new(state_view); + let storage = state_view_impl.into_initia_code_storage( + runtime_environment, + script_cache, + module_cache, + ); + Self { storage } + } + + pub fn state_view_impl(&self) -> &StateViewImpl<'s, S> { + self.storage.module_storage().byte_storage() + } +} diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 9ce956c4..f02a8b72 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -1,3 +1,12 @@ pub mod state_view; pub mod state_view_impl; pub mod table_resolver; + +pub mod code_storage; +pub mod initia_storage; +pub mod memory_module_storage; +pub mod module_cache; +pub mod module_storage; +pub mod script_cache; + +pub mod code_scale; diff --git a/crates/storage/src/memory_module_storage.rs b/crates/storage/src/memory_module_storage.rs new file mode 100644 index 00000000..4fefb43e --- /dev/null +++ b/crates/storage/src/memory_module_storage.rs @@ -0,0 +1,325 @@ +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use bytes::Bytes; +use move_binary_format::{ + deserializer::DeserializerConfig, + errors::{PartialVMError, PartialVMResult, VMResult}, + file_format_common::{IDENTIFIER_SIZE_MAX, VERSION_MAX}, + CompiledModule, +}; +use move_bytecode_utils::compiled_module_viewer::CompiledModuleView; +use move_core_types::{ + account_address::AccountAddress, + effects::{AccountChangeSet, ChangeSet, Op}, + identifier::{IdentStr, Identifier}, + language_storage::{ModuleId, StructTag}, + metadata::Metadata, + value::MoveTypeLayout, + vm_status::StatusCode, +}; +use move_vm_types::{ + code::ModuleBytesStorage, + resolver::{resource_size, ModuleResolver, ResourceResolver}, + sha3_256, +}; +use std::{ + collections::{btree_map, BTreeMap}, + fmt::Debug, +}; + +use crate::state_view::{Checksum, ChecksumStorage}; + +/// A dummy storage containing no modules or resources. +#[derive(Debug, Clone)] +pub struct BlankStorage; + +impl Default for BlankStorage { + fn default() -> Self { + Self::new() + } +} + +impl BlankStorage { + pub fn new() -> Self { + Self + } +} + +impl ModuleBytesStorage for BlankStorage { + fn fetch_module_bytes( + &self, + _address: &AccountAddress, + _module_name: &IdentStr, + ) -> VMResult> { + Ok(None) + } +} + +impl ChecksumStorage for BlankStorage { + fn fetch_checksum( + &self, + _address: &AccountAddress, + _module_name: &IdentStr, + ) -> VMResult> { + Ok(None) + } +} + +impl ModuleResolver for BlankStorage { + fn get_module_metadata(&self, _module_id: &ModuleId) -> Vec { + vec![] + } + + fn get_module(&self, _module_id: &ModuleId) -> PartialVMResult> { + Ok(None) + } +} + +impl ResourceResolver for BlankStorage { + fn get_resource_bytes_with_metadata_and_layout( + &self, + _address: &AccountAddress, + _tag: &StructTag, + _metadata: &[Metadata], + _maybe_layout: Option<&MoveTypeLayout>, + ) -> PartialVMResult<(Option, usize)> { + Ok((None, 0)) + } +} + +/// Simple in-memory storage for modules and resources under an account. +#[derive(Debug, Clone)] +struct InMemoryAccountStorage { + resources: BTreeMap, + modules: BTreeMap, + checksums: BTreeMap, +} + +/// Simple in-memory storage that can be used as a Move VM storage backend for testing purposes. +#[derive(Debug, Clone)] +pub struct InMemoryStorage { + accounts: BTreeMap, +} + +impl CompiledModuleView for InMemoryStorage { + type Item = CompiledModule; + + fn view_compiled_module(&self, id: &ModuleId) -> anyhow::Result> { + Ok(match self.get_module(id)? { + Some(bytes) => { + let config = DeserializerConfig::new(VERSION_MAX, IDENTIFIER_SIZE_MAX); + Some(CompiledModule::deserialize_with_config(&bytes, &config)?) + } + None => None, + }) + } +} + +fn apply_changes( + map: &mut BTreeMap, + changes: impl IntoIterator)>, +) -> PartialVMResult<()> +where + K: Ord + Debug, +{ + use btree_map::Entry::*; + use Op::*; + + for (k, op) in changes.into_iter() { + match (map.entry(k), op) { + (Occupied(entry), New(_)) => { + return Err( + PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!( + "Failed to apply changes -- key {:?} already exists", + entry.key() + )), + ) + } + (Occupied(entry), Delete) => { + entry.remove(); + } + (Occupied(entry), Modify(val)) => { + *entry.into_mut() = val; + } + (Vacant(entry), New(val)) => { + entry.insert(val); + } + (Vacant(entry), Delete | Modify(_)) => { + return Err( + PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!( + "Failed to apply changes -- key {:?} does not exist", + entry.key() + )), + ) + } + } + } + Ok(()) +} + +fn get_or_insert(map: &mut BTreeMap, key: K, make_val: F) -> &mut V +where + K: Ord, + F: FnOnce() -> V, +{ + use btree_map::Entry::*; + + match map.entry(key) { + Occupied(entry) => entry.into_mut(), + Vacant(entry) => entry.insert(make_val()), + } +} + +impl InMemoryAccountStorage { + fn apply(&mut self, account_changeset: AccountChangeSet) -> PartialVMResult<()> { + let (modules, resources) = account_changeset.into_inner(); + apply_changes(&mut self.modules, modules.clone())?; + apply_changes( + &mut self.checksums, + Self::modules_to_checksum_changes(modules), + )?; + apply_changes(&mut self.resources, resources)?; + Ok(()) + } + + fn modules_to_checksum_changes( + modules: BTreeMap>, + ) -> BTreeMap> { + modules + .into_iter() + .map(|(k, v)| { + ( + k, + match v { + Op::New(bytes) => Op::New(sha3_256(&bytes)), + Op::Modify(bytes) => Op::Modify(sha3_256(&bytes)), + Op::Delete => Op::Delete, + }, + ) + }) + .collect() + } + + fn new() -> Self { + Self { + modules: BTreeMap::new(), + checksums: BTreeMap::new(), + resources: BTreeMap::new(), + } + } +} + +impl Default for InMemoryStorage { + fn default() -> Self { + Self::new() + } +} + +impl InMemoryStorage { + pub fn apply_extended(&mut self, changeset: ChangeSet) -> PartialVMResult<()> { + for (addr, account_changeset) in changeset.into_inner() { + self.accounts + .entry(addr) + .or_insert_with(InMemoryAccountStorage::new) + .apply(account_changeset)?; + } + + Ok(()) + } + + pub fn apply(&mut self, changeset: ChangeSet) -> PartialVMResult<()> { + self.apply_extended(changeset) + } + + pub fn new() -> Self { + Self { + accounts: BTreeMap::new(), + } + } + + /// Adds serialized module bytes to this storage. + pub fn add_module_bytes( + &mut self, + address: &AccountAddress, + module_name: &IdentStr, + bytes: Bytes, + ) -> Checksum { + let checksum = sha3_256(&bytes); + + let account = get_or_insert(&mut self.accounts, *address, || { + InMemoryAccountStorage::new() + }); + account.modules.insert(module_name.to_owned(), bytes); + account.checksums.insert(module_name.to_owned(), checksum); + checksum + } + + pub fn publish_or_overwrite_resource( + &mut self, + addr: AccountAddress, + struct_tag: StructTag, + blob: Vec, + ) { + let account = get_or_insert(&mut self.accounts, addr, InMemoryAccountStorage::new); + account.resources.insert(struct_tag, blob.into()); + } +} + +impl ModuleBytesStorage for InMemoryStorage { + fn fetch_module_bytes( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + Ok(self + .accounts + .get(address) + .and_then(|account_storage| account_storage.modules.get(module_name).cloned())) + } +} + +impl ChecksumStorage for InMemoryStorage { + fn fetch_checksum( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + Ok(self + .accounts + .get(address) + .and_then(|account_storage| account_storage.checksums.get(module_name)) + .cloned()) + } +} + +impl ModuleResolver for InMemoryStorage { + fn get_module_metadata(&self, _module_id: &ModuleId) -> Vec { + vec![] + } + + fn get_module(&self, module_id: &ModuleId) -> PartialVMResult> { + Ok(self + .accounts + .get(module_id.address()) + .and_then(|account_storage| account_storage.modules.get(module_id.name()).cloned())) + } +} + +impl ResourceResolver for InMemoryStorage { + fn get_resource_bytes_with_metadata_and_layout( + &self, + address: &AccountAddress, + tag: &StructTag, + _metadata: &[Metadata], + _maybe_layout: Option<&MoveTypeLayout>, + ) -> PartialVMResult<(Option, usize)> { + if let Some(account_storage) = self.accounts.get(address) { + let buf = account_storage.resources.get(tag).cloned(); + let buf_size = resource_size(&buf); + return Ok((buf, buf_size)); + } + Ok((None, 0)) + } +} diff --git a/crates/storage/src/module_cache.rs b/crates/storage/src/module_cache.rs new file mode 100644 index 00000000..1a22fce4 --- /dev/null +++ b/crates/storage/src/module_cache.rs @@ -0,0 +1,188 @@ +use std::{hash::RandomState, num::NonZeroUsize, sync::Arc}; + +use bytes::Bytes; +use clru::{CLruCache, CLruCacheConfig}; +use move_binary_format::{ + errors::{Location, PartialVMError, VMError, VMResult}, + CompiledModule, +}; +use move_core_types::{language_storage::ModuleId, vm_status::StatusCode}; +use move_vm_runtime::Module; +use move_vm_types::code::{ModuleCode, ModuleCodeBuilder, WithBytes, WithHash}; +use parking_lot::Mutex; + +use crate::{code_scale::ModuleCodeScale, state_view::Checksum}; + +fn handle_cache_error(module_id: ModuleId) -> VMError { + PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED) + .with_message("Module storage cache eviction error".to_string()) + .finish(Location::Module(module_id)) +} + +/// Extension for modules stored in [InitialModuleCache] to also capture information about bytes +/// and hash. +#[derive(PartialEq, Eq, Debug)] +pub struct BytesWithHash { + /// Bytes of the module. + bytes: Bytes, + /// Hash of the module. + hash: [u8; 32], +} + +impl BytesWithHash { + /// Returns new extension containing bytes and hash. + pub fn new(bytes: Bytes, hash: [u8; 32]) -> Self { + Self { bytes, hash } + } +} + +impl WithBytes for BytesWithHash { + fn bytes(&self) -> &Bytes { + &self.bytes + } +} + +impl WithHash for BytesWithHash { + fn hash(&self) -> &[u8; 32] { + &self.hash + } +} + +/// Placeholder for module versioning since we do not allow mutations in [InitiaModuleCache]. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +pub struct NoVersion; + +pub struct InitiaModuleCache { + #[allow(clippy::type_complexity)] + pub(crate) module_cache: Mutex< + CLruCache< + Checksum, + Arc>, + RandomState, + ModuleCodeScale, + >, + >, +} + +impl InitiaModuleCache { + pub fn new(cache_capacity: usize) -> Arc { + let capacity = NonZeroUsize::new(cache_capacity).unwrap(); + Arc::new(InitiaModuleCache { + module_cache: Mutex::new(CLruCache::with_config( + CLruCacheConfig::new(capacity).with_scale(ModuleCodeScale), + )), + }) + } +} + +// modified ModuleCache trait implementation +impl InitiaModuleCache { + #[allow(unused)] + pub(crate) fn insert_deserialized_module( + &self, + key: Checksum, + deserialized_code: CompiledModule, + extension: Arc, + version: NoVersion, + ) -> VMResult<()> { + let mut module_cache = self.module_cache.lock(); + + match module_cache.get(&key) { + // we don't use version of the module, so we don't need to check it + Some(_) => Ok(()), + None => { + let module_id = deserialized_code.self_id(); + let module = Arc::new(ModuleCode::from_deserialized( + deserialized_code, + extension, + version, + )); + module_cache + .put_with_weight(key, module) + .map_err(|_| handle_cache_error(module_id))?; + Ok(()) + } + } + } + + pub(crate) fn insert_verified_module( + &self, + key: Checksum, + verified_code: Module, + extension: Arc, + version: NoVersion, + ) -> VMResult>> { + let mut module_cache = self.module_cache.lock(); + + match module_cache.get(&key) { + Some(code) => { + if code.code().is_verified() { + Ok(code.clone()) + } else { + let module_id = verified_code.self_id(); + let module = + Arc::new(ModuleCode::from_verified(verified_code, extension, version)); + module_cache + .put_with_weight(key, module.clone()) + .map_err(|_| handle_cache_error(module_id))?; + Ok(module) + } + } + None => { + let module_id = verified_code.self_id(); + let module = Arc::new(ModuleCode::from_verified(verified_code, extension, version)); + module_cache + .put_with_weight(key, module.clone()) + .map_err(|_| handle_cache_error(module_id))?; + Ok(module) + } + } + } + + #[allow(clippy::type_complexity)] + pub(crate) fn get_module_or_build_with( + &self, + id: &ModuleId, + checksum: &Checksum, + builder: &dyn ModuleCodeBuilder< + Key = ModuleId, + Deserialized = CompiledModule, + Verified = Module, + Extension = BytesWithHash, + Version = NoVersion, + >, + ) -> VMResult>>> { + let mut module_cache = self.module_cache.lock(); + Ok(match module_cache.get(checksum) { + Some(code) => Some(code.clone()), + None => match builder.build(id)? { + Some(code) => { + if code.extension().hash() != checksum { + return Err(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + ) + .with_message("Checksum mismatch".to_string()) + .finish(Location::Module(id.clone()))); + } + + let code = Arc::new(code); + module_cache + .put_with_weight(*checksum, code.clone()) + .map_err(|_| { + PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED) + .with_message("Module storage cache eviction error".to_string()) + .finish(Location::Module(id.clone())) + })?; + Some(code) + } + None => None, + }, + }) + } + + #[allow(unused)] + pub(crate) fn num_modules(&self) -> usize { + let module_cache = self.module_cache.lock(); + module_cache.len() + } +} diff --git a/crates/storage/src/module_storage.rs b/crates/storage/src/module_storage.rs new file mode 100644 index 00000000..d8d4981d --- /dev/null +++ b/crates/storage/src/module_storage.rs @@ -0,0 +1,705 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + module_cache::{BytesWithHash, InitiaModuleCache, NoVersion}, + state_view::{Checksum, ChecksumStorage}, +}; +use bytes::Bytes; + +use move_binary_format::{errors::VMResult, CompiledModule}; +use move_core_types::{ + account_address::AccountAddress, identifier::IdentStr, language_storage::ModuleId, + metadata::Metadata, +}; +use move_vm_runtime::{Module, ModuleStorage, RuntimeEnvironment, WithRuntimeEnvironment}; +use move_vm_types::{ + code::{ModuleBytesStorage, ModuleCode, ModuleCodeBuilder, WithBytes, WithHash}, + module_cyclic_dependency_error, module_linker_error, +}; +use std::{borrow::Borrow, collections::HashSet, ops::Deref, sync::Arc}; + +/// Implementation of (not thread-safe) module storage used for Move unit tests, and externally. +pub struct InitiaModuleStorage<'a, S> { + /// Environment where this module storage is defined in. + runtime_environment: &'a RuntimeEnvironment, + /// Storage with deserialized modules, i.e., module cache. + module_cache: Arc, + /// Immutable baseline storage from which one can fetch raw module bytes and checksums. + base_storage: BorrowedOrOwned<'a, S>, +} + +pub trait AsInitiaModuleStorage<'a, S> { + fn as_initia_module_storage( + &'a self, + env: &'a RuntimeEnvironment, + module_cache: Arc, + ) -> InitiaModuleStorage<'a, S>; + + fn into_initia_module_storage( + self, + env: &'a RuntimeEnvironment, + module_cache: Arc, + ) -> InitiaModuleStorage<'a, S>; +} + +impl<'a, S: ModuleBytesStorage + ChecksumStorage> AsInitiaModuleStorage<'a, S> for S { + fn as_initia_module_storage( + &'a self, + env: &'a RuntimeEnvironment, + module_cache: Arc, + ) -> InitiaModuleStorage<'a, S> { + InitiaModuleStorage::from_borrowed(env, self, module_cache) + } + + fn into_initia_module_storage( + self, + env: &'a RuntimeEnvironment, + module_cache: Arc, + ) -> InitiaModuleStorage<'a, S> { + InitiaModuleStorage::from_owned(env, self, module_cache) + } +} + +impl<'s, S: ModuleBytesStorage + ChecksumStorage> WithRuntimeEnvironment + for InitiaModuleStorage<'s, S> +{ + fn runtime_environment(&self) -> &RuntimeEnvironment { + self.runtime_environment + } +} + +impl<'s, S: ModuleBytesStorage + ChecksumStorage> InitiaModuleStorage<'s, S> { + /// Private constructor from borrowed byte storage. Creates empty module storage cache. + fn from_borrowed( + runtime_environment: &'s RuntimeEnvironment, + storage: &'s S, + module_cache: Arc, + ) -> Self { + Self { + runtime_environment, + module_cache, + base_storage: BorrowedOrOwned::Borrowed(storage), + } + } + + /// Private constructor that captures provided byte storage by value. Creates empty module + /// storage cache. + fn from_owned( + runtime_environment: &'s RuntimeEnvironment, + storage: S, + module_cache: Arc, + ) -> Self { + Self { + runtime_environment, + module_cache, + base_storage: BorrowedOrOwned::Owned(storage), + } + } + + /// The reference to the baseline byte storage used by this module storage. + pub fn byte_storage(&self) -> &S { + &self.base_storage + } + + /// Test-only method that checks the state of the module cache. + #[cfg(test)] + pub(crate) fn assert_cached_state( + &self, + deserialized: Vec<&'s ModuleId>, + deserialized_checksum: Vec<&'s Checksum>, + verified: Vec<&'s ModuleId>, + verified_checksum: Vec<&'s Checksum>, + ) { + assert_eq!( + self.module_cache.num_modules(), + deserialized.len() + verified.len() + ); + assert_eq!(deserialized.len(), deserialized_checksum.len()); + for (id, checksum) in deserialized.into_iter().zip(deserialized_checksum) { + let result = self + .module_cache + .get_module_or_build_with(id, checksum, self); + let module = claims::assert_some!(claims::assert_ok!(result)); + assert!(!module.code().is_verified()) + } + assert_eq!(verified.len(), verified_checksum.len()); + for (id, checksum) in verified.into_iter().zip(verified_checksum) { + let result = self + .module_cache + .get_module_or_build_with(id, checksum, self); + let module = claims::assert_some!(claims::assert_ok!(result)); + assert!(module.code().is_verified()) + } + } +} + +impl<'s, S: ModuleBytesStorage + ChecksumStorage> ModuleCodeBuilder for InitiaModuleStorage<'s, S> { + type Deserialized = CompiledModule; + type Extension = BytesWithHash; + type Key = ModuleId; + type Verified = Module; + type Version = NoVersion; + + fn build( + &self, + key: &Self::Key, + ) -> VMResult< + Option>, + > { + let bytes = match self + .base_storage + .fetch_module_bytes(key.address(), key.name())? + { + Some(bytes) => bytes, + None => return Ok(None), + }; + + let checksum = match self + .base_storage + .fetch_checksum(key.address(), key.name())? + { + Some(checksum) => checksum, + None => return Ok(None), + }; + + let (compiled_module, _, hash) = self + .runtime_environment() + .deserialize_into_compiled_module(&bytes)?; + + if checksum != hash { + return Err(module_linker_error!(key.address(), key.name())); + } + + let extension = Arc::new(BytesWithHash::new(bytes, hash)); + let module = ModuleCode::from_deserialized(compiled_module, extension, NoVersion); + Ok(Some(module)) + } +} + +impl<'a, S: ModuleBytesStorage + ChecksumStorage> ModuleStorage for InitiaModuleStorage<'a, S> { + fn check_module_exists( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(false), + }; + Ok(self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + .is_some()) + } + + fn fetch_module_bytes( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(None), + }; + Ok(self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + .map(|module| module.extension().bytes().clone())) + } + + fn fetch_module_size_in_bytes( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(None), + }; + Ok(self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + .map(|module| module.extension().bytes().len())) + } + + fn fetch_module_metadata( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult>> { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(None), + }; + Ok(self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + .map(|module| module.code().deserialized().metadata.clone())) + } + + fn fetch_deserialized_module( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult>> { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(None), + }; + Ok(self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + .map(|module| module.code().deserialized().clone())) + } + + fn fetch_verified_module( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult>> { + let id = ModuleId::new(*address, module_name.to_owned()); + let checksum = match self.base_storage.fetch_checksum(address, module_name)? { + Some(checksum) => checksum, + None => return Ok(None), + }; + + // Look up the verified module in cache, if it is not there, or if the module is not yet + // verified, we need to load & verify its transitive dependencies. + let module = match self + .module_cache + .get_module_or_build_with(&id, &checksum, self)? + { + Some(module) => module, + None => return Ok(None), + }; + + if module.code().is_verified() { + return Ok(Some(module.code().verified().clone())); + } + + let mut visited = HashSet::new(); + visited.insert(id.clone()); + Ok(Some(visit_dependencies_and_verify( + id, + checksum, + module, + &mut visited, + self, + )?)) + } +} + +/// Visits the dependencies of the given module. If dependencies form a cycle (which should not be +/// the case as we check this when modules are added to the module cache), an error is returned. +/// +/// Note: +/// This implementation **does not** load transitive friends. While it is possible to view +/// friends as `used-by` relation, it cannot be checked fully. For example, consider the case +/// when we have four modules A, B, C, D and let `X --> Y` be a dependency relation (Y is a +/// dependency of X) and `X ==> Y ` a friend relation (X declares Y a friend). Then consider the +/// case `A --> B <== C --> D <== A`. Here, if we opt for `used-by` semantics, there is a cycle. +/// But it cannot be checked, since, A only sees B and D, and C sees B and D, but both B and D do +/// not see any dependencies or friends. Hence, A cannot discover C and vice-versa, making +/// detection of such corner cases only possible if **all existing modules are checked**, which +/// is clearly infeasible. +fn visit_dependencies_and_verify( + module_id: ModuleId, + checksum: Checksum, + module: Arc>, + visited: &mut HashSet, + module_cache_with_context: &InitiaModuleStorage<'_, S>, +) -> VMResult> { + let runtime_environment = module_cache_with_context.runtime_environment; + + // Step 1: Local verification. + runtime_environment.paranoid_check_module_address_and_name( + module.code().deserialized(), + module_id.address(), + module_id.name(), + )?; + let locally_verified_code = runtime_environment.build_locally_verified_module( + module.code().deserialized().clone(), + module.extension().size_in_bytes(), + module.extension().hash(), + )?; + + // Step 2: Traverse and collect all verified immediate dependencies so that we can verify + // non-local properties of the module. + let mut verified_dependencies = vec![]; + for (addr, name) in locally_verified_code.immediate_dependencies_iter() { + let dependency_id = ModuleId::new(*addr, name.to_owned()); + let dependency_checksum = module_cache_with_context + .base_storage + .fetch_checksum(addr, name)? + .ok_or_else(|| module_linker_error!(dependency_id.address(), dependency_id.name()))?; + + let dependency = module_cache_with_context + .module_cache + .get_module_or_build_with( + &dependency_id, + &dependency_checksum, + module_cache_with_context, + )? + .ok_or_else(|| module_linker_error!(addr, name))?; + + // Dependency is already verified! + if dependency.code().is_verified() { + verified_dependencies.push(dependency.code().verified().clone()); + continue; + } + + if visited.insert(dependency_id.clone()) { + // Dependency is not verified, and we have not visited it yet. + let verified_dependency = visit_dependencies_and_verify( + dependency_id.clone(), + dependency_checksum, + dependency, + visited, + module_cache_with_context, + )?; + verified_dependencies.push(verified_dependency); + } else { + // We must have found a cycle otherwise. + return Err(module_cyclic_dependency_error!( + dependency_id.address(), + dependency_id.name() + )); + } + } + + let verified_code = + runtime_environment.build_verified_module(locally_verified_code, &verified_dependencies)?; + let module = module_cache_with_context + .module_cache + .insert_verified_module( + checksum, + verified_code, + module.extension().clone(), + module.version(), + )?; + Ok(module.code().verified().clone()) +} + +/// Represents owned or borrowed types, similar to [std::borrow::Cow] but without enforcing +/// [ToOwned] trait bound on types it stores. We use it to be able to construct different storages +/// that capture or borrow underlying byte storage. +enum BorrowedOrOwned<'a, T> { + Borrowed(&'a T), + Owned(T), +} + +impl<'a, T> Deref for BorrowedOrOwned<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match *self { + Self::Borrowed(x) => x, + Self::Owned(ref x) => x.borrow(), + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use bytes::Bytes; + use claims::{assert_err, assert_none, assert_ok, assert_some}; + use move_binary_format::{ + file_format::empty_module_with_dependencies_and_friends, + file_format_common::VERSION_DEFAULT, CompiledModule, + }; + use move_core_types::{ + account_address::AccountAddress, ident_str, identifier::Identifier, + language_storage::ModuleId, vm_status::StatusCode, + }; + use move_vm_runtime::{ModuleStorage, RuntimeEnvironment}; + + use crate::{ + memory_module_storage::InMemoryStorage, module_cache::InitiaModuleCache, + module_storage::AsInitiaModuleStorage, state_view::Checksum, + }; + + pub const TEST_CACHE_CAPACITY: usize = 100; + + fn make_module<'a>( + module_name: &'a str, + dependencies: impl IntoIterator, + friends: impl IntoIterator, + ) -> (CompiledModule, Bytes) { + let mut module = + empty_module_with_dependencies_and_friends(module_name, dependencies, friends); + module.version = VERSION_DEFAULT; + + let mut module_bytes = vec![]; + assert_ok!(module.serialize(&mut module_bytes)); + + (module, module_bytes.into()) + } + + pub(crate) fn add_module_bytes<'a>( + module_bytes_storage: &mut InMemoryStorage, + module_name: &'a str, + dependencies: impl IntoIterator, + friends: impl IntoIterator, + ) -> Checksum { + let (module, bytes) = make_module(module_name, dependencies, friends); + module_bytes_storage.add_module_bytes(module.self_addr(), module.self_name(), bytes) + } + + #[test] + fn test_module_does_not_exist() { + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let module_storage = + InMemoryStorage::new().into_initia_module_storage(&runtime_environment, module_cache); + + let result = module_storage.check_module_exists(&AccountAddress::ZERO, ident_str!("a")); + assert!(!assert_ok!(result)); + + let result = + module_storage.fetch_module_size_in_bytes(&AccountAddress::ZERO, ident_str!("a")); + assert_none!(assert_ok!(result)); + + let result = module_storage.fetch_module_metadata(&AccountAddress::ZERO, ident_str!("a")); + assert_none!(assert_ok!(result)); + + let result = + module_storage.fetch_deserialized_module(&AccountAddress::ZERO, ident_str!("a")); + assert_none!(assert_ok!(result)); + + let result = module_storage.fetch_verified_module(&AccountAddress::ZERO, ident_str!("a")); + assert_none!(assert_ok!(result)); + } + + #[test] + fn test_module_exists() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec![], vec![]); + let id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + assert!(assert_ok!( + module_storage.check_module_exists(id.address(), id.name()) + )); + module_storage.assert_cached_state(vec![&id], vec![&checksum_a], vec![], vec![]); + } + + #[test] + fn test_deserialized_caching() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let a_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec!["b", "c"], vec![]); + add_module_bytes(&mut module_bytes_storage, "b", vec![], vec![]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec!["d", "e"], vec![]); + add_module_bytes(&mut module_bytes_storage, "d", vec![], vec![]); + add_module_bytes(&mut module_bytes_storage, "e", vec![], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + let result = module_storage.fetch_module_metadata(a_id.address(), a_id.name()); + let expected = make_module("a", vec!["b", "c"], vec![]).0.metadata; + assert_eq!(assert_some!(assert_ok!(result)), expected); + module_storage.assert_cached_state(vec![&a_id], vec![&checksum_a], vec![], vec![]); + + let result = module_storage.fetch_deserialized_module(c_id.address(), c_id.name()); + let expected = make_module("c", vec!["d", "e"], vec![]).0; + assert_eq!(assert_some!(assert_ok!(result)).as_ref(), &expected); + module_storage.assert_cached_state( + vec![&a_id, &c_id], + vec![&checksum_a, &checksum_c], + vec![], + vec![], + ); + } + + #[test] + fn test_dependency_tree_traversal() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let a_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + let b_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("b").unwrap()); + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + let d_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("d").unwrap()); + let e_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("e").unwrap()); + + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec!["b", "c"], vec![]); + let checksum_b = add_module_bytes(&mut module_bytes_storage, "b", vec![], vec![]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec!["d", "e"], vec![]); + let checksum_d = add_module_bytes(&mut module_bytes_storage, "d", vec![], vec![]); + let checksum_e = add_module_bytes(&mut module_bytes_storage, "e", vec![], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + assert_ok!(module_storage.fetch_verified_module(c_id.address(), c_id.name())); + module_storage.assert_cached_state( + vec![], + vec![], + vec![&c_id, &d_id, &e_id], + vec![&checksum_c, &checksum_d, &checksum_e], + ); + + assert_ok!(module_storage.fetch_verified_module(a_id.address(), a_id.name())); + module_storage.assert_cached_state( + vec![], + vec![], + vec![&a_id, &b_id, &c_id, &d_id, &e_id], + vec![ + &checksum_a, + &checksum_b, + &checksum_c, + &checksum_d, + &checksum_e, + ], + ); + + assert_ok!(module_storage.fetch_verified_module(a_id.address(), a_id.name())); + } + + #[test] + fn test_dependency_dag_traversal() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let a_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + let b_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("b").unwrap()); + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + let d_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("d").unwrap()); + let e_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("e").unwrap()); + let f_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("f").unwrap()); + let g_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("g").unwrap()); + + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec!["b", "c"], vec![]); + let checksum_b = add_module_bytes(&mut module_bytes_storage, "b", vec!["d"], vec![]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec!["d"], vec![]); + let checksum_d = add_module_bytes(&mut module_bytes_storage, "d", vec!["e", "f"], vec![]); + let checksum_e = add_module_bytes(&mut module_bytes_storage, "e", vec!["g"], vec![]); + let checksum_f = add_module_bytes(&mut module_bytes_storage, "f", vec!["g"], vec![]); + let checksum_g = add_module_bytes(&mut module_bytes_storage, "g", vec![], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + assert_ok!(module_storage.fetch_deserialized_module(a_id.address(), a_id.name())); + assert_ok!(module_storage.fetch_deserialized_module(c_id.address(), c_id.name())); + module_storage.assert_cached_state( + vec![&a_id, &c_id], + vec![&checksum_a, &checksum_c], + vec![], + vec![], + ); + + assert_ok!(module_storage.fetch_verified_module(d_id.address(), d_id.name())); + module_storage.assert_cached_state( + vec![&a_id, &c_id], + vec![&checksum_a, &checksum_c], + vec![&d_id, &e_id, &f_id, &g_id], + vec![&checksum_d, &checksum_e, &checksum_f, &checksum_g], + ); + + assert_ok!(module_storage.fetch_verified_module(a_id.address(), a_id.name())); + module_storage.assert_cached_state( + vec![], + vec![], + vec![&a_id, &b_id, &c_id, &d_id, &e_id, &f_id, &g_id], + vec![ + &checksum_a, + &checksum_b, + &checksum_c, + &checksum_d, + &checksum_e, + &checksum_f, + &checksum_g, + ], + ); + } + + #[test] + fn test_cyclic_dependencies_traversal_fails() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + + add_module_bytes(&mut module_bytes_storage, "a", vec!["b"], vec![]); + add_module_bytes(&mut module_bytes_storage, "b", vec!["c"], vec![]); + add_module_bytes(&mut module_bytes_storage, "c", vec!["a"], vec![]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + let result = module_storage.fetch_verified_module(c_id.address(), c_id.name()); + assert_eq!( + assert_err!(result).major_status(), + StatusCode::CYCLIC_MODULE_DEPENDENCY + ); + } + + #[test] + fn test_cyclic_friends_are_allowed() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + + add_module_bytes(&mut module_bytes_storage, "a", vec![], vec!["b"]); + add_module_bytes(&mut module_bytes_storage, "b", vec![], vec!["c"]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec![], vec!["a"]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + let result = module_storage.fetch_verified_module(c_id.address(), c_id.name()); + assert_ok!(result); + + // Since `c` has no dependencies, only it gets deserialized and verified. + module_storage.assert_cached_state(vec![], vec![], vec![&c_id], vec![&checksum_c]); + } + + #[test] + fn test_transitive_friends_are_allowed_to_be_transitive_dependencies() { + let mut module_bytes_storage = InMemoryStorage::new(); + let module_cache = InitiaModuleCache::new(TEST_CACHE_CAPACITY); + + let a_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("a").unwrap()); + let b_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("b").unwrap()); + let c_id = ModuleId::new(AccountAddress::ZERO, Identifier::new("c").unwrap()); + + let checksum_a = add_module_bytes(&mut module_bytes_storage, "a", vec!["b"], vec!["d"]); + let checksum_b = add_module_bytes(&mut module_bytes_storage, "b", vec!["c"], vec![]); + let checksum_c = add_module_bytes(&mut module_bytes_storage, "c", vec![], vec![]); + add_module_bytes(&mut module_bytes_storage, "d", vec![], vec!["c"]); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = + module_bytes_storage.into_initia_module_storage(&runtime_environment, module_cache); + + assert_ok!(module_storage.fetch_verified_module(a_id.address(), a_id.name())); + module_storage.assert_cached_state( + vec![], + vec![], + vec![&a_id, &b_id, &c_id], + vec![&checksum_a, &checksum_b, &checksum_c], + ); + } +} diff --git a/crates/storage/src/script_cache.rs b/crates/storage/src/script_cache.rs new file mode 100644 index 00000000..ec4a2587 --- /dev/null +++ b/crates/storage/src/script_cache.rs @@ -0,0 +1,104 @@ +use std::{hash::RandomState, num::NonZeroUsize, sync::Arc}; + +use clru::{CLruCache, CLruCacheConfig}; +use move_binary_format::{ + errors::{Location, PartialVMError, VMResult}, + file_format::CompiledScript, +}; +use move_core_types::vm_status::StatusCode; +use move_vm_runtime::Script; +use move_vm_types::code::Code; +use parking_lot::Mutex; + +use crate::{code_scale::CodeScale, state_view::Checksum}; + +pub struct InitiaScriptCache { + pub(crate) script_cache: + Mutex, RandomState, CodeScale>>, +} + +impl InitiaScriptCache { + pub fn new(cache_capacity: usize) -> Arc { + Arc::new(InitiaScriptCache { + script_cache: Mutex::new(CLruCache::with_config( + CLruCacheConfig::new(NonZeroUsize::new(cache_capacity).unwrap()) + .with_scale(CodeScale), + )), + }) + } +} + +// modified ScriptCache trait implementation +// we need error handling for the script cache +impl InitiaScriptCache { + pub(crate) fn insert_deserialized_script( + &self, + key: Checksum, + deserialized_script: CompiledScript, + ) -> VMResult> { + let mut script_cache = self.script_cache.lock(); + match script_cache.get(&key) { + Some(code) => Ok(code.deserialized().clone()), + None => { + let new_script = Code::from_deserialized(deserialized_script); + let deserialized_script = new_script.deserialized().clone(); + + // error occurs when the new script has a weight greater than the cache capacity + script_cache.put_with_weight(key, new_script).map_err(|_| { + PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED) + .with_message("Script storage cache eviction error".to_string()) + .finish(Location::Script) + })?; + + Ok(deserialized_script) + } + } + } + + pub(crate) fn insert_verified_script( + &self, + key: Checksum, + verified_script: Script, + ) -> VMResult> { + let mut script_cache = self.script_cache.lock(); + + let (new_script, verified_script) = match script_cache.get(&key) { + Some(code) => { + if !code.is_verified() { + let new_script = Code::from_verified(verified_script); + let verified_script = new_script.verified().clone(); + (Some(new_script), verified_script) + } else { + (None, code.verified().clone()) + } + } + None => { + let new_script = Code::from_verified(verified_script); + let verified_script = new_script.verified().clone(); + (Some(new_script), verified_script) + } + }; + + if new_script.is_some() { + script_cache + .put_with_weight(key, new_script.unwrap()) + .map_err(|_| { + PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED) + .with_message("Script storage cache eviction error".to_string()) + .finish(Location::Script) + })?; + } + Ok(verified_script) + } + + pub(crate) fn get_script(&self, key: &Checksum) -> Option> { + let mut script_cache = self.script_cache.lock(); + script_cache.get(key).cloned() + } + + #[allow(unused)] + pub(crate) fn num_scripts(&self) -> usize { + let script_cache = self.script_cache.lock(); + script_cache.len() + } +} diff --git a/crates/storage/src/state_view.rs b/crates/storage/src/state_view.rs index c70a4bd6..23de3939 100644 --- a/crates/storage/src/state_view.rs +++ b/crates/storage/src/state_view.rs @@ -8,6 +8,10 @@ use anyhow::Result; use bytes::Bytes; use initia_move_types::access_path::AccessPath; +use move_binary_format::errors::VMResult; +use move_core_types::{account_address::AccountAddress, identifier::IdentStr}; + +pub type Checksum = [u8; 32]; /// `StateView` is a trait that defines a read-only snapshot of the global state. It is passed to /// the VM for transaction execution, during which the VM is guaranteed to read anything at the @@ -16,3 +20,11 @@ pub trait StateView { /// Gets the state for a single access path. fn get(&self, access_path: &AccessPath) -> Result>; } + +pub trait ChecksumStorage { + fn fetch_checksum( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult>; +} diff --git a/crates/storage/src/state_view_impl.rs b/crates/storage/src/state_view_impl.rs index 9d88e95c..3aac756f 100644 --- a/crates/storage/src/state_view_impl.rs +++ b/crates/storage/src/state_view_impl.rs @@ -1,29 +1,31 @@ #![forbid(unsafe_code)] -use super::state_view::StateView; +use crate::state_view::{Checksum, ChecksumStorage, StateView}; use bytes::Bytes; use move_binary_format::deserializer::DeserializerConfig; -use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_binary_format::errors::{Location, PartialVMError, PartialVMResult, VMResult}; use move_binary_format::CompiledModule; use move_bytecode_utils::compiled_module_viewer::CompiledModuleView; use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::IdentStr; use move_core_types::language_storage::ModuleId; use move_core_types::language_storage::StructTag; use move_core_types::metadata::Metadata; use move_core_types::value::MoveTypeLayout; use move_core_types::vm_status::StatusCode; +use move_vm_types::code::ModuleBytesStorage; use move_vm_types::resolver::{resource_size, ModuleResolver, ResourceResolver}; use initia_move_types::access_path::AccessPath; -pub struct StateViewImpl<'block, S> { - state_view: &'block S, +pub struct StateViewImpl<'s, S> { + state_view: &'s S, deserialize_config: DeserializerConfig, } -impl<'block, S: StateView> StateViewImpl<'block, S> { - pub fn new(state_view: &'block S) -> Self { +impl<'s, S: StateView> StateViewImpl<'s, S> { + pub fn new(state_view: &'s S) -> Self { Self { state_view, deserialize_config: DeserializerConfig::default(), @@ -31,7 +33,7 @@ impl<'block, S: StateView> StateViewImpl<'block, S> { } pub fn new_with_deserialize_config( - state_view: &'block S, + state_view: &'s S, deserialize_config: DeserializerConfig, ) -> Self { Self { @@ -41,7 +43,7 @@ impl<'block, S: StateView> StateViewImpl<'block, S> { } } -impl<'block, S: StateView> StateViewImpl<'block, S> { +impl<'s, S: StateView> StateViewImpl<'s, S> { pub(crate) fn get(&self, access_path: &AccessPath) -> PartialVMResult> { self.state_view.get(access_path).map_err(|err| { PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(err.to_string()) @@ -49,7 +51,58 @@ impl<'block, S: StateView> StateViewImpl<'block, S> { } } -impl<'block, S: StateView> ModuleResolver for StateViewImpl<'block, S> { +impl<'s, S: StateView> ChecksumStorage for StateViewImpl<'s, S> { + fn fetch_checksum( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + let ap = AccessPath::checksum_access_path(*address, module_name.to_owned()); + match self.get(&ap).map_err(|e| { + e.finish(Location::Module(ModuleId::new( + *address, + module_name.to_owned(), + ))) + })? { + Some(b) => { + if b.len() != 32 { + return Err(PartialVMError::new(StatusCode::STORAGE_ERROR) + .with_message(format!("Checksum has an invalid length: {}", b.len())) + .finish(Location::Module(ModuleId::new( + *address, + module_name.to_owned(), + )))); + } + let mut checksum: Checksum = [0u8; 32]; + checksum.copy_from_slice(&b); + Ok(Some(checksum)) + } + None => Ok(None), + } + } +} + +impl<'s, S: StateView> ModuleBytesStorage for StateViewImpl<'s, S> { + fn fetch_module_bytes( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + let module_id = ModuleId::new(*address, module_name.to_owned()); + let module_bytes = match self.get_module(&module_id).map_err(|e| { + e.finish(Location::Module(ModuleId::new( + *address, + module_name.to_owned(), + ))) + })? { + Some(bytes) => bytes, + _ => return Ok(None), + }; + Ok(Some(module_bytes)) + } +} + +impl<'s, S: StateView> ModuleResolver for StateViewImpl<'s, S> { fn get_module_metadata(&self, module_id: &ModuleId) -> Vec { let module_bytes = match self.get_module(module_id) { Ok(Some(bytes)) => bytes, @@ -66,13 +119,12 @@ impl<'block, S: StateView> ModuleResolver for StateViewImpl<'block, S> { } fn get_module(&self, module_id: &ModuleId) -> PartialVMResult> { - let ap = AccessPath::from(module_id); - + let ap = AccessPath::code_access_path(module_id.address, module_id.name.to_owned()); self.get(&ap) } } -impl<'block, S: StateView> ResourceResolver for StateViewImpl<'block, S> { +impl<'s, S: StateView> ResourceResolver for StateViewImpl<'s, S> { fn get_resource_bytes_with_metadata_and_layout( &self, address: &AccountAddress, @@ -87,7 +139,7 @@ impl<'block, S: StateView> ResourceResolver for StateViewImpl<'block, S> { } } -impl<'block, S: StateView> CompiledModuleView for StateViewImpl<'block, S> { +impl<'s, S: StateView> CompiledModuleView for StateViewImpl<'s, S> { type Item = CompiledModule; fn view_compiled_module(&self, id: &ModuleId) -> anyhow::Result> { diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index a9e512bf..7a3f8bb6 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -16,6 +16,7 @@ move-model = { workspace = true } move-coverage = { workspace = true } anyhow = { workspace = true } +bytes = {workspace = true} bcs = { workspace = true } serde = { workspace = true } serde_bytes = { workspace = true } diff --git a/crates/types/src/access_path.rs b/crates/types/src/access_path.rs index 2321525b..4579b640 100644 --- a/crates/types/src/access_path.rs +++ b/crates/types/src/access_path.rs @@ -67,6 +67,10 @@ impl AccessPath { AccessPath::new(address, Self::code_data_path(module_name)) } + pub fn checksum_access_path(address: AccountAddress, module_name: Identifier) -> AccessPath { + AccessPath::new(address, Self::checksum_data_path(module_name)) + } + pub fn table_item_access_path( handle /* Table Address */: AccountAddress, key: Vec, @@ -87,6 +91,10 @@ impl AccessPath { DataPath::Code(module_name) } + pub fn checksum_data_path(module_name: ModuleName) -> DataPath { + DataPath::Checksum(module_name) + } + pub fn table_item_data_path(key: Vec) -> DataPath { DataPath::TableItem(key) } @@ -133,12 +141,6 @@ impl fmt::Display for AccessPath { } } -impl From<&ModuleId> for AccessPath { - fn from(id: &ModuleId) -> AccessPath { - AccessPath::code_access_path(*id.address(), id.name().to_owned()) - } -} - impl From<&ResourceKey> for AccessPath { fn from(key: &ResourceKey) -> AccessPath { AccessPath::resource_access_path(key.address(), key.type_().clone()) @@ -148,6 +150,7 @@ impl From<&ResourceKey> for AccessPath { #[repr(u8)] pub enum DataType { Code, + Checksum, Resource, TableItem, TableInfo, @@ -159,6 +162,11 @@ impl DataType { pub fn is_code(self) -> bool { matches!(self, DataType::Code) } + + pub fn is_checksum(self) -> bool { + matches!(self, DataType::Checksum) + } + pub fn is_resource(self) -> bool { matches!(self, DataType::Resource) } @@ -177,6 +185,7 @@ impl DataType { pub fn from_index(idx: u8) -> Result { match idx { 0 => Ok(DataType::Code), + 1 => Ok(DataType::Checksum), 2 => Ok(DataType::Resource), 3 => Ok(DataType::TableItem), 4 => Ok(DataType::TableInfo), @@ -190,6 +199,7 @@ pub type ModuleName = Identifier; #[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Serialize, Deserialize)] pub enum DataPath { Code(ModuleName), + Checksum(ModuleName), Resource(StructTag), TableItem(Vec), TableInfo, @@ -200,6 +210,10 @@ impl DataPath { matches!(self, DataPath::Code(_)) } + pub fn is_checksum(&self) -> bool { + matches!(self, DataPath::Checksum(_)) + } + pub fn is_resource(&self) -> bool { matches!(self, DataPath::Resource(_)) } @@ -222,6 +236,7 @@ impl DataPath { pub fn data_type(&self) -> DataType { match self { DataPath::Code(_) => DataType::Code, + DataPath::Checksum(_) => DataType::Checksum, DataPath::Resource(_) => DataType::Resource, DataPath::TableItem(_) => DataType::TableItem, DataPath::TableInfo => DataType::TableInfo, @@ -235,6 +250,7 @@ impl DataPath { let prefix = self.data_type().storage_index(); let raw_key = match self { DataPath::Code(module_name) => bcs::to_bytes(module_name)?, + DataPath::Checksum(module_name) => bcs::to_bytes(module_name)?, DataPath::Resource(struct_tag) => bcs::to_bytes(struct_tag)?, DataPath::TableItem(key) => key.to_vec(), DataPath::TableInfo => vec![], @@ -255,6 +271,7 @@ impl DataPath { let data_type = DataType::from_index(data_type).map_err(|e| anyhow!(e))?; match data_type { DataType::Code => Ok(DataPath::Code(bcs::from_bytes(&val[1..])?)), + DataType::Checksum => Ok(DataPath::Checksum(bcs::from_bytes(&val[1..])?)), DataType::Resource => Ok(DataPath::Resource(bcs::from_bytes(&val[1..])?)), DataType::TableItem => Ok(DataPath::TableItem(val[1..].to_vec())), DataType::TableInfo => Ok(DataPath::TableInfo), @@ -273,6 +290,9 @@ impl fmt::Display for DataPath { DataPath::Code(module_name) => { write!(f, "{}/{}", storage_index, module_name) } + DataPath::Checksum(module_name) => { + write!(f, "{}/{}", storage_index, module_name) + } DataPath::Resource(struct_tag) => { write!(f, "{}/{}", storage_index, struct_tag) } @@ -300,6 +320,7 @@ impl FromStr for AccessPath { let data_path = match data_type { DataType::Code => AccessPath::code_data_path(Identifier::new(parts[2])?), + DataType::Checksum => AccessPath::checksum_data_path(Identifier::new(parts[2])?), DataType::Resource => AccessPath::resource_data_path(parse_struct_tag(parts[2])?), DataType::TableItem => AccessPath::table_item_data_path(decode_hex(parts[2])?), DataType::TableInfo => AccessPath::table_info_data_path(), diff --git a/crates/types/src/metadata.rs b/crates/types/src/metadata.rs index e3c3a558..2eb3daae 100644 --- a/crates/types/src/metadata.rs +++ b/crates/types/src/metadata.rs @@ -8,7 +8,10 @@ use serde::{Deserialize, Serialize}; pub const ERROR_PREFIX: &str = "E"; pub const VIEW_FUN_ATTRIBUTE: &str = "view"; pub const EVENT_STRUCT_ATTRIBUTE: &str = "event"; +pub const CODE_MODULE_NAME: &str = "code"; pub const INIT_MODULE_FUNCTION_NAME: &str = "init_module"; +pub const INIT_GENESIS_FUNCTION_NAME: &str = "init_genesis"; +pub const VERIFY_PUBLISH_REQUEST: &str = "verify_publish_request"; pub const METADATA_V0_MIN_FILE_FORMAT_VERSION: u32 = 6; /// The keys used to identify the metadata in the metadata section of the module bytecode. diff --git a/crates/types/src/module.rs b/crates/types/src/module.rs index c55a6f2a..e2b02201 100644 --- a/crates/types/src/module.rs +++ b/crates/types/src/module.rs @@ -6,6 +6,7 @@ use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_binary_format::CompiledModule; use move_core_types::language_storage::ModuleId; +use bytes::Bytes; use move_core_types::vm_status::StatusCode; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; @@ -65,6 +66,13 @@ impl ModuleBundle { self.codes.into_iter().map(Module::into_inner).collect() } + pub fn into_bytes(self) -> Vec { + self.codes + .into_iter() + .map(|m| m.into_inner().into()) + .collect() + } + pub fn res(&mut self) -> Vec> { self.codes.iter().map(|m| m.code.clone()).collect() } diff --git a/crates/types/src/vm_config.rs b/crates/types/src/vm_config.rs index 42b29098..7108aafc 100644 --- a/crates/types/src/vm_config.rs +++ b/crates/types/src/vm_config.rs @@ -4,4 +4,16 @@ use serde::{Deserialize, Serialize}; #[allow(clippy::upper_case_acronyms)] pub struct InitiaVMConfig { pub allow_unstable: bool, + pub script_cache_capacity: usize, + pub module_cache_capacity: usize, +} + +impl Default for InitiaVMConfig { + fn default() -> Self { + Self { + allow_unstable: true, + script_cache_capacity: 100, + module_cache_capacity: 500, + } + } } diff --git a/crates/types/src/write_set.rs b/crates/types/src/write_set.rs index a4b883ad..608783f1 100644 --- a/crates/types/src/write_set.rs +++ b/crates/types/src/write_set.rs @@ -1,8 +1,6 @@ use crate::{access_path::AccessPath, table::TableChangeSet}; -use move_core_types::{ - effects::{ChangeSet, Op}, - language_storage::ModuleId, -}; +use anyhow::ensure; +use move_core_types::effects::{ChangeSet, Op}; use std::collections::{btree_map, BTreeMap}; pub type WriteOp = Op>; @@ -11,7 +9,14 @@ pub type WriteOp = Op>; pub struct WriteSet(BTreeMap); impl WriteSet { - pub fn new(change_set: ChangeSet, table_change_set: TableChangeSet) -> anyhow::Result { + pub fn new_with_write_set(write_set: BTreeMap) -> Self { + Self(write_set) + } + + pub fn new_with_change_set( + change_set: ChangeSet, + table_change_set: TableChangeSet, + ) -> anyhow::Result { let mut write_set: BTreeMap = BTreeMap::new(); for (addr, account_changeset) in change_set.into_inner() { let (modules, resources) = account_changeset.into_inner(); @@ -20,12 +25,11 @@ impl WriteSet { write_set.insert(ap, blob_opt.map(|v| v.into())); } - for (name, blob_opt) in modules.into_iter() { - // write module bytes changes - let module_id = ModuleId::new(addr, name); - let ap = AccessPath::from(&module_id); - write_set.insert(ap, blob_opt.map(|v| v.into())); - } + // unused in loader v2 + ensure!( + modules.is_empty(), + "Modules should not be present in the account change set in Loader v2" + ); } for (handle, changes) in table_change_set.changes.into_iter() { @@ -49,6 +53,12 @@ impl WriteSet { } } +impl Extend<(AccessPath, WriteOp)> for WriteSet { + fn extend>(&mut self, iter: I) { + self.0.extend(iter) + } +} + impl ::std::iter::FromIterator<(AccessPath, WriteOp)> for WriteSet { fn from_iter>(iter: I) -> Self { let mut ws = WriteSet::default(); diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 89b226f3..3f03b009 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -38,7 +38,8 @@ thiserror = { workspace = true } bcs = { workspace = true } tempfile = { workspace = true } bigdecimal = { workspace = true } +bytes = { workspace = true } [dev-dependencies] triomphe = { workspace = true } -bytes = { workspace = true } + diff --git a/crates/vm/src/initia_vm.rs b/crates/vm/src/initia_vm.rs index 7bfcc28a..b738200d 100644 --- a/crates/vm/src/initia_vm.rs +++ b/crates/vm/src/initia_vm.rs @@ -1,16 +1,10 @@ use move_binary_format::{ - access::ModuleAccess, - compatibility::Compatibility, deserializer::DeserializerConfig, errors::{Location, PartialVMError, VMResult}, file_format::CompiledScript, - CompiledModule, }; use move_core_types::{ account_address::AccountAddress, - ident_str, - identifier::Identifier, - language_storage::ModuleId, value::{MoveTypeLayout, MoveValue}, vm_status::{StatusCode, VMStatus}, }; @@ -20,20 +14,21 @@ use move_vm_runtime::{ move_vm::MoveVM, native_extensions::NativeContextExtensions, session::SerializedReturnValues, + RuntimeEnvironment, }; -use move_vm_types::{gas::GasMeter, resolver::MoveResolver}; +use move_vm_types::resolver::MoveResolver; -use std::{collections::BTreeSet, sync::Arc}; +use std::sync::Arc; +use initia_move_gas::MiscGasParameters; use initia_move_gas::{ Gas, InitiaGasMeter, InitiaGasParameters, InitialGasSchedule, NativeGasParameters, }; -use initia_move_gas::{MiscGasParameters, NumBytes}; use initia_move_json::serialize_move_value_to_json_value; use initia_move_natives::{ account::{AccountAPI, NativeAccountContext}, all_natives, - code::{NativeCodeContext, PublishRequest}, + code::{NativeCodeContext, PublishRequest, UpgradePolicy}, cosmos::NativeCosmosContext, event::NativeEventContext, oracle::{NativeOracleContext, OracleAPI}, @@ -44,7 +39,10 @@ use initia_move_natives::{ use initia_move_natives::{ block::NativeBlockContext, staking::StakingAPI, table::NativeTableContext, }; -use initia_move_storage::table_resolver::TableResolver; +use initia_move_storage::{ + initia_storage::InitiaStorage, module_cache::InitiaModuleCache, + script_cache::InitiaScriptCache, state_view::StateView, table_resolver::TableResolver, +}; use initia_move_types::{ account::Accounts, cosmos::CosmosMessages, @@ -52,7 +50,6 @@ use initia_move_types::{ gas_usage::GasUsageSet, json_event::JsonEvents, message::{Message, MessageOutput, MessagePayload}, - metadata::INIT_MODULE_FUNCTION_NAME, module::ModuleBundle, staking_change_set::StakingChangeSet, view_function::{ViewFunction, ViewOutput}, @@ -63,28 +60,26 @@ use initia_move_types::{ use crate::{ session::{SessionExt, SessionOutput}, verifier::{ - config::verifier_config, errors::metadata_validation_error, - event_validation::verify_no_event_emission_in_script, metadata::get_vm_metadata, - module_init::verify_module_init_function, module_metadata::validate_publish_request, - script::reject_unstable_bytecode_for_script, + config::verifier_config, event_validation::verify_no_event_emission_in_script, + metadata::get_vm_metadata, script::reject_unstable_bytecode_for_script, transaction_arg_validation::validate_combine_signer_and_txn_args, - view_function::validate_view_function, + view_function::validate_view_function_and_construct, }, }; -#[derive(Clone)] #[allow(clippy::upper_case_acronyms)] pub struct InitiaVM { move_vm: Arc, gas_params: InitiaGasParameters, initia_vm_config: InitiaVMConfig, + runtime_environment: Arc, + script_cache: Arc, + module_cache: Arc, } impl Default for InitiaVM { fn default() -> Self { - Self::new(InitiaVMConfig { - allow_unstable: true, - }) + Self::new(InitiaVMConfig::default()) } } @@ -94,14 +89,24 @@ impl InitiaVM { let misc_params = MiscGasParameters::initial(); let vm_config = VMConfig { verifier_config: verifier_config(), + use_loader_v2: true, ..Default::default() }; - let move_vm = MoveVM::new_with_config(all_natives(gas_params, misc_params), vm_config); + let runtime_environment = Arc::new(RuntimeEnvironment::new_with_config( + all_natives(gas_params, misc_params), + vm_config, + )); + let move_vm = MoveVM::new_with_runtime_environment(&runtime_environment); + let script_cache = InitiaScriptCache::new(initia_vm_config.script_cache_capacity); + let module_cache = InitiaModuleCache::new(initia_vm_config.module_cache_capacity); Self { move_vm: Arc::new(move_vm), gas_params: InitiaGasParameters::initial(), initia_vm_config, + runtime_environment, + script_cache, + module_cache, } } @@ -110,15 +115,20 @@ impl InitiaVM { } #[inline(always)] - fn allow_unstable(&self) -> bool { + pub(crate) fn allow_unstable(&self) -> bool { self.initia_vm_config.allow_unstable } #[inline(always)] - pub fn deserialize_config(&self) -> &DeserializerConfig { + pub fn deserializer_config(&self) -> &DeserializerConfig { &self.move_vm.vm_config().deserializer_config } + #[inline(always)] + pub fn runtime_environment(&self) -> Arc { + self.runtime_environment.clone() + } + fn create_session< 'r, A: AccountAPI + StakingAPI + QueryAPI + OracleAPI, @@ -163,79 +173,57 @@ impl InitiaVM { #[allow(clippy::too_many_arguments)] pub fn initialize< - M: MoveResolver, + S: StateView, T: TableResolver, A: AccountAPI + StakingAPI + QueryAPI + OracleAPI, >( &mut self, api: &A, env: &Env, - state_view_impl: &M, - table_view_impl: &mut T, + storage: &S, + table_resolver: &mut T, module_bundle: ModuleBundle, allowed_publishers: Vec, ) -> Result { + let runtime_environment = self.runtime_environment(); + let code_storage = InitiaStorage::new( + storage, + &runtime_environment, + self.script_cache.clone(), + self.module_cache.clone(), + ); + let move_resolver = code_storage.state_view_impl(); + let gas_limit = Gas::new(u64::MAX); let gas_params = self.gas_params.clone(); let mut gas_meter = InitiaGasMeter::new(gas_params, gas_limit); - let mut session = self.create_session(api, env, state_view_impl, table_view_impl); + let session = self.create_session(api, env, move_resolver, table_resolver); let traversal_storage = TraversalStorage::new(); let mut traversal_context = TraversalContext::new(&traversal_storage); - let publish_request = PublishRequest { - destination: AccountAddress::ONE, - expected_modules: None, - module_bundle, - }; - - let published_module_ids = self.resolve_pending_code_publish( - state_view_impl, - &mut session, + let session_output = session.finish_with_module_publish( + self.deserializer_config(), + self.allow_unstable(), + &code_storage, &mut gas_meter, - publish_request, + PublishRequest { + publisher: AccountAddress::ONE, + module_bundle, + upgrade_policy: UpgradePolicy::Compatible, + }, &mut traversal_context, + Some(allowed_publishers), )?; - // execute code::init_genesis to properly store module metadata. - { - const CODE_MODULE_NAME: &str = "code"; - const INIT_GENESIS_FUNCTION_NAME: &str = "init_genesis"; - let args: Vec> = vec![ - MoveValue::Signer(AccountAddress::ONE) - .simple_serialize() - .unwrap(), - bcs::to_bytes(&published_module_ids).unwrap(), - bcs::to_bytes(&allowed_publishers).unwrap(), - ]; - - let function = session.load_function( - &ModuleId::new( - AccountAddress::ONE, - Identifier::new(CODE_MODULE_NAME).unwrap(), - ), - &Identifier::new(INIT_GENESIS_FUNCTION_NAME).unwrap(), - &[], - )?; - - // ignore the output - session.execute_entry_function( - function, - args, - &mut gas_meter, - &mut traversal_context, - )?; - } - // session cleanup - let session_output = session.finish()?; let output: MessageOutput = self.success_message_cleanup(session_output, &mut gas_meter)?; Ok(output) } pub fn execute_message< - M: MoveResolver, + S: StateView, T: TableResolver, A: AccountAPI + StakingAPI + QueryAPI + OracleAPI, >( @@ -243,22 +231,31 @@ impl InitiaVM { gas_meter: &mut InitiaGasMeter, api: &A, env: &Env, - state_view_impl: &M, - table_view_impl: &mut T, + storage: &S, + table_resolver: &mut T, msg: Message, ) -> Result { + let runtime_environment = self.runtime_environment(); + let senders = msg.senders().to_vec(); let traversal_storage = TraversalStorage::new(); let mut traversal_context = TraversalContext::new(&traversal_storage); + let code_storage = InitiaStorage::new( + storage, + &runtime_environment, + self.script_cache.clone(), + self.module_cache.clone(), + ); + // Charge for msg byte size gas_meter.charge_intrinsic_gas_for_transaction((msg.size() as u64).into())?; let res = self.execute_script_or_entry_function( api, env, - state_view_impl, - table_view_impl, + &code_storage, + table_resolver, senders, msg.payload(), gas_meter, @@ -269,7 +266,7 @@ impl InitiaVM { } pub fn execute_view_function< - M: MoveResolver, + S: StateView, T: TableResolver, A: AccountAPI + StakingAPI + QueryAPI + OracleAPI, >( @@ -277,23 +274,33 @@ impl InitiaVM { gas_meter: &mut InitiaGasMeter, api: &A, env: &Env, - state_view_impl: &M, - table_view_impl: &mut T, + storage: &S, + table_resolver: &mut T, view_fn: &ViewFunction, ) -> Result { - // flush loader cache if invalidated - self.move_vm.flush_loader_cache_if_invalidated(); - - let mut session = self.create_session(api, env, state_view_impl, table_view_impl); + let runtime_environment = self.runtime_environment(); + let code_storage = InitiaStorage::new( + storage, + &runtime_environment, + self.script_cache.clone(), + self.module_cache.clone(), + ); + let move_resolver = code_storage.state_view_impl(); + let mut session = self.create_session(api, env, move_resolver, table_resolver); let traversal_storage = TraversalStorage::new(); let mut traversal_context = TraversalContext::new(&traversal_storage); - let function = - session.load_function(view_fn.module(), view_fn.function(), view_fn.ty_args())?; - let metadata = get_vm_metadata(&session, view_fn.module()); - let args = validate_view_function( + let function = session.load_function( + &code_storage, + view_fn.module(), + view_fn.function(), + view_fn.ty_args(), + )?; + let metadata = get_vm_metadata(&code_storage, view_fn.module()); + + let args = validate_view_function_and_construct( &mut session, - state_view_impl, + &code_storage, view_fn.args().to_vec(), view_fn.function(), &function, @@ -311,6 +318,7 @@ impl InitiaVM { args, gas_meter, &mut traversal_context, + &code_storage, )?; // load fully annotated type layouts for return value serialization @@ -319,12 +327,13 @@ impl InitiaVM { .return_tys() .iter() .map(|ty| { - let ty_tag = session.get_type_tag(ty)?; - session.get_fully_annotated_type_layout(&ty_tag) + session + .inner + .get_fully_annotated_type_layout_from_ty(ty, &code_storage) }) .collect::>>()?; - let session_output = session.finish()?; + let session_output = session.finish(&code_storage)?; let (events, _, _, _, _) = session_output; let json_events = JsonEvents::new(events.into_iter().map(|e| e.into_inner()).collect()); let ret = serialize_response_to_json(&ret_ty_layouts, res)? @@ -335,28 +344,27 @@ impl InitiaVM { #[allow(clippy::too_many_arguments)] fn execute_script_or_entry_function< - M: MoveResolver, + S: StateView, T: TableResolver, A: AccountAPI + StakingAPI + QueryAPI + OracleAPI, >( &self, api: &A, env: &Env, - state_view_impl: &M, - table_view_impl: &mut T, + code_storage: &InitiaStorage, + table_resolver: &mut T, senders: Vec, payload: &MessagePayload, gas_meter: &mut InitiaGasMeter, traversal_context: &mut TraversalContext, ) -> Result { - // flush loader cache if invalidated - self.move_vm.flush_loader_cache_if_invalidated(); - - let mut session = self.create_session(api, env, state_view_impl, table_view_impl); + let move_resolver = code_storage.state_view_impl(); + let mut session = self.create_session(api, env, move_resolver, table_resolver); match payload { MessagePayload::Script(script) => { session.check_script_dependencies_and_check_gas( + code_storage, gas_meter, traversal_context, script.code(), @@ -364,11 +372,12 @@ impl InitiaVM { // we only use the ok path, let move vm handle the wrong path. // let Ok(s) = CompiledScript::deserialize(script.code()); - let function = session.load_script(script.code(), script.ty_args())?; + let function = + session.load_script(code_storage, script.code(), script.ty_args())?; let compiled_script = match CompiledScript::deserialize_with_config( script.code(), - self.deserialize_config(), + self.deserializer_config(), ) { Ok(script) => script, Err(err) => { @@ -389,7 +398,7 @@ impl InitiaVM { let args = validate_combine_signer_and_txn_args( &mut session, - state_view_impl, + code_storage, senders, script.args().to_vec(), &function, @@ -402,6 +411,7 @@ impl InitiaVM { args, gas_meter, traversal_context, + code_storage, ) } MessagePayload::Execute(entry_fn) => { @@ -409,12 +419,14 @@ impl InitiaVM { .referenced_module_ids .alloc(entry_fn.module().clone()); session.check_dependencies_and_charge_gas( + code_storage, gas_meter, traversal_context, [(module_id.address(), module_id.name())], )?; let function = session.load_function( + code_storage, entry_fn.module(), entry_fn.function(), entry_fn.ty_args(), @@ -433,9 +445,11 @@ impl InitiaVM { ); } + // need check function.is_friend_or_private() ?? + let args = validate_combine_signer_and_txn_args( &mut session, - state_view_impl, + code_storage, senders, entry_fn.args().to_vec(), &function, @@ -445,24 +459,29 @@ impl InitiaVM { // first execution does not execute `charge_call`, so need to record call here gas_meter.record_call(entry_fn.module()); - session.execute_entry_function(function, args, gas_meter, traversal_context) + session.execute_entry_function( + function, + args, + gas_meter, + traversal_context, + code_storage, + ) } }?; - if let Some(publish_request) = session.extract_publish_request() { - // mark loader cache as invalid until loader v2 is implemented - self.move_vm.mark_loader_cache_as_invalid(); - - self.resolve_pending_code_publish( - state_view_impl, - &mut session, + let session_output = if let Some(publish_request) = session.extract_publish_request() { + session.finish_with_module_publish( + self.deserializer_config(), + self.allow_unstable(), + code_storage, gas_meter, publish_request, traversal_context, - )?; - } - - let session_output = session.finish()?; + None, + )? + } else { + session.finish(code_storage)? + }; // Charge for gas cost for write set ops gas_meter.charge_write_set_gas(&session_output.1)?; @@ -471,194 +490,6 @@ impl InitiaVM { Ok(output) } - /// Resolve a pending code publish request registered via the NativeCodeContext. - fn resolve_pending_code_publish( - &self, - resolver: &M, - session: &mut SessionExt, - gas_meter: &mut InitiaGasMeter, - publish_request: PublishRequest, - traversal_context: &mut TraversalContext, - ) -> VMResult> { - let PublishRequest { - destination, - module_bundle, - expected_modules, - } = publish_request; - - let modules = self.deserialize_module_bundle(&module_bundle)?; - let modules: &Vec = - traversal_context.referenced_module_bundles.alloc(modules); - - // Note: Feature gating is needed here because the traversal of the dependencies could - // result in shallow-loading of the modules and therefore subtle changes in - // the error semantics. - { - // Charge old versions of existing modules, in case of upgrades. - for module in modules.iter() { - let id = module.self_id(); - let addr = module.self_addr(); - let name = module.self_name(); - - // TODO: Allow the check of special addresses to be customized. - if addr.is_special() || traversal_context.visited.insert((addr, name), ()).is_some() - { - continue; - } - - let size_if_module_exists = resolver - .get_module(&id) - .map_err(|e| e.finish(Location::Undefined))? - .map(|m| m.len() as u64); - - if let Some(size) = size_if_module_exists { - gas_meter - .charge_dependency(false, addr, name, NumBytes::new(size)) - .map_err(|err| { - err.finish(Location::Module(ModuleId::new(*addr, name.to_owned()))) - })?; - } - } - - // Charge all modules in the bundle that is about to be published. - for (module, blob) in modules.iter().zip(module_bundle.iter()) { - let module_id = &module.self_id(); - gas_meter - .charge_dependency( - true, - module_id.address(), - module_id.name(), - NumBytes::new(blob.code().len() as u64), - ) - .map_err(|err| err.finish(Location::Undefined))?; - } - - // Charge all dependencies. - // - // Must exclude the ones that are in the current bundle because they have not - // been published yet. - let module_ids_in_bundle = modules - .iter() - .map(|module| (module.self_addr(), module.self_name())) - .collect::>(); - - session.check_dependencies_and_charge_gas( - gas_meter, - traversal_context, - modules - .iter() - .flat_map(|module| { - module - .immediate_dependencies_iter() - .chain(module.immediate_friends_iter()) - }) - .filter(|addr_and_name| !module_ids_in_bundle.contains(addr_and_name)), - )?; - } - - // validate modules are properly compiled with metadata - validate_publish_request(session, modules, &module_bundle, self.allow_unstable())?; - - if let Some(expected_modules) = expected_modules { - for (m, expected_id) in modules.iter().zip(expected_modules.iter()) { - if m.self_id().short_str_lossless().as_str() != expected_id { - return Err(metadata_validation_error(&format!( - "unexpected module: '{}', expected: '{}'", - m.self_id().name(), - expected_id - ))); - } - } - } - - // Check what modules exist before publishing. - let mut exists = BTreeSet::new(); - for m in modules.iter() { - let id = m.self_id(); - - if session.exists_module(&id)? { - exists.insert(id); - } - } - - // sort the modules by dependencies - let (sorted_module_bundle, published_module_ids, sorted_compiled_modules) = module_bundle - .sorted_code_and_modules(modules) - .map_err(|e| e.finish(Location::Undefined))?; - - // publish and cache the modules on loader cache. - session.publish_module_bundle_with_compat_config( - sorted_module_bundle.into_inner(), - destination, - gas_meter, - // treat friends as private - Compatibility::new(true, false), - )?; - - // call init function of the each module - self.execute_module_initialization( - session, - gas_meter, - sorted_compiled_modules, - exists, - &[destination], - traversal_context, - )?; - - Ok(published_module_ids) - } - - /// Execute all module initializers. - fn execute_module_initialization( - &self, - session: &mut SessionExt, - gas_meter: &mut InitiaGasMeter, - modules: Vec<&CompiledModule>, - exists: BTreeSet, - senders: &[AccountAddress], - traversal_context: &mut TraversalContext, - ) -> VMResult<()> { - let init_func_name = ident_str!(INIT_MODULE_FUNCTION_NAME); - for module in modules { - if exists.contains(&module.self_id()) { - // Call initializer only on first publish. - continue; - } - - let init_function = session.load_function(&module.self_id(), init_func_name, &[]); - // it is ok to not have init_module function - // init_module function should be (1) private and (2) has no return value - // Note that for historic reasons, verification here is treated - // as StatusCode::CONSTRAINT_NOT_SATISFIED, there this cannot be unified - // with the general verify_module above. - if init_function.is_ok() { - if verify_module_init_function(module).is_ok() { - let args: Vec> = senders - .iter() - .map(|s| MoveValue::Signer(*s).simple_serialize().unwrap()) - .collect(); - - // first execution does not execute `charge_call`, so need to record call here - gas_meter.record_call(&module.self_id()); - - session.execute_function_bypass_visibility( - &module.self_id(), - init_func_name, - vec![], - args, - gas_meter, - traversal_context, - )?; - } else { - return Err(PartialVMError::new(StatusCode::CONSTRAINT_NOT_SATISFIED) - .finish(Location::Module(module.self_id()))); - } - } - } - - Ok(()) - } - fn success_message_cleanup( &self, session_output: SessionOutput, @@ -677,26 +508,6 @@ impl InitiaVM { gas_usage_set, )) } - - /// Deserialize a module bundle. - fn deserialize_module_bundle( - &self, - module_bundle: &ModuleBundle, - ) -> VMResult> { - let mut result = vec![]; - for module_blob in module_bundle.iter() { - match CompiledModule::deserialize_with_config( - module_blob.code(), - self.deserialize_config(), - ) { - Ok(module) => { - result.push(module); - } - Err(err) => return Err(err.finish(Location::Undefined)), - } - } - Ok(result) - } } fn serialize_response_to_json( diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index e1dd646d..b6060da1 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -1,6 +1,6 @@ -mod session; - pub use crate::initia_vm::InitiaVM; mod initia_vm; +mod publish; +mod session; mod verifier; diff --git a/crates/vm/src/publish.rs b/crates/vm/src/publish.rs new file mode 100644 index 00000000..08ffbc5a --- /dev/null +++ b/crates/vm/src/publish.rs @@ -0,0 +1,345 @@ +use std::collections::BTreeSet; + +use initia_move_gas::{InitiaGasMeter, NumBytes}; +use initia_move_natives::code::{PublishRequest, UpgradePolicy}; +use initia_move_storage::{initia_storage::InitiaStorage, state_view::StateView}; +use initia_move_types::{ + metadata::{ + CODE_MODULE_NAME, INIT_GENESIS_FUNCTION_NAME, INIT_MODULE_FUNCTION_NAME, + VERIFY_PUBLISH_REQUEST, + }, + module::ModuleBundle, +}; + +use move_binary_format::{ + access::ModuleAccess, + compatibility::Compatibility, + deserializer::DeserializerConfig, + errors::{Location, VMResult}, + CompiledModule, +}; +use move_core_types::{ + account_address::AccountAddress, ident_str, identifier::Identifier, language_storage::ModuleId, + value::MoveValue, +}; +use move_vm_runtime::{module_traversal::TraversalContext, ModuleStorage, StagingModuleStorage}; +use move_vm_types::gas::GasMeter; + +use crate::verifier::module_init::verify_module_init_function; +use crate::{ + session::{SessionExt, SessionOutput}, + verifier::module_metadata::validate_publish_request, +}; + +impl<'r, 'l> SessionExt<'r, 'l> { + #[allow(clippy::too_many_arguments)] + pub(crate) fn finish_with_module_publish( + mut self, + deserializer_config: &DeserializerConfig, + allow_unstable: bool, + code_storage: &InitiaStorage, + gas_meter: &mut InitiaGasMeter, + publish_request: PublishRequest, + traversal_context: &mut TraversalContext, + init_genesis_opts: Option>, + ) -> VMResult> { + let PublishRequest { + publisher, + module_bundle, + upgrade_policy, + } = publish_request; + + let modules = deserialize_module_bundle(&module_bundle, deserializer_config)?; + let modules: &Vec = + traversal_context.referenced_module_bundles.alloc(modules); + + self.check_publish_request( + code_storage, + gas_meter, + publisher, + &module_bundle, + modules, + upgrade_policy, + traversal_context, + allow_unstable, + init_genesis_opts.is_some(), + )?; + + let staging_module_storage = StagingModuleStorage::create_with_compat_config( + &publisher, + Compatibility::new(true, false), + code_storage, + module_bundle.into_bytes(), + )?; + + self.initialize_module( + code_storage, + gas_meter, + traversal_context, + &staging_module_storage, + publisher, + modules, + )?; + + if let Some(allowed_publishers) = init_genesis_opts { + self.initialize_genesis( + gas_meter, + traversal_context, + &staging_module_storage, + modules, + allowed_publishers, + )?; + } + + let mut output = self.finish(&staging_module_storage)?; + let module_write_set = Self::convert_modules_into_write_set( + code_storage, + staging_module_storage + .release_verified_module_bundle() + .into_iter(), + ) + .map_err(|e| e.finish(Location::Undefined))?; + output.1.extend(module_write_set); + Ok(output) + } + + pub(crate) fn initialize_module( + &mut self, + code_storage: &InitiaStorage, + gas_meter: &mut InitiaGasMeter, + traversal_context: &mut TraversalContext, + staging_module_storage: &StagingModuleStorage, + destination: AccountAddress, + modules: &[CompiledModule], + ) -> VMResult<()> { + let init_func_name = ident_str!(INIT_MODULE_FUNCTION_NAME); + for module in modules { + // Check if module existed previously. If not, we do not run initialization. + if code_storage.check_module_exists(module.self_addr(), module.self_name())? { + continue; + } + + let module_id = module.self_id(); + let init_function_exists = self + .inner + .load_function(staging_module_storage, &module_id, init_func_name, &[]) + .is_ok(); + + if init_function_exists { + // We need to check that init_module function we found is well-formed. + verify_module_init_function(module).map_err(|e| e.finish(Location::Undefined))?; + + self.inner.execute_function_bypass_visibility( + &module_id, + init_func_name, + vec![], + vec![MoveValue::Signer(destination).simple_serialize().unwrap()], + gas_meter, + traversal_context, + staging_module_storage, + )?; + } + } + Ok(()) + } + + /// Special function to initialize the genesis block. This function is only called once per + /// blockchain genesis. It is used to initialize the blockchain by setting genesis modules with + /// allowed publishers. + pub(crate) fn initialize_genesis( + &mut self, + gas_meter: &mut InitiaGasMeter, + traversal_context: &mut TraversalContext, + staging_module_storage: &StagingModuleStorage, + modules: &[CompiledModule], + allowed_publishers: Vec, + ) -> VMResult<()> { + let published_module_ids: Vec = modules + .iter() + .map(|m| m.self_id().short_str_lossless()) + .collect(); + + // ignore the output + self.inner.execute_function_bypass_visibility( + &ModuleId::new( + AccountAddress::ONE, + Identifier::new(CODE_MODULE_NAME).unwrap(), + ), + ident_str!(INIT_GENESIS_FUNCTION_NAME), + vec![], + vec![ + MoveValue::Signer(AccountAddress::ONE) + .simple_serialize() + .unwrap(), + bcs::to_bytes(&published_module_ids).unwrap(), + bcs::to_bytes(&allowed_publishers).unwrap(), + ], + gas_meter, + traversal_context, + staging_module_storage, + )?; + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn check_publish_request<'a, S: StateView>( + &mut self, + code_storage: &InitiaStorage, + gas_meter: &mut InitiaGasMeter, + publisher: AccountAddress, + module_bundle: &ModuleBundle, + modules: &'a [CompiledModule], + upgrade_policy: UpgradePolicy, + traversal_context: &mut TraversalContext<'a>, + allow_unsafe: bool, + skip_dependencies_upgrade_policy_verification: bool, + ) -> VMResult<()> { + // Note: Feature gating is needed here because the traversal of the dependencies could + // result in shallow-loading of the modules and therefore subtle changes in + // the error semantics. + { + // Charge old versions of existing modules, in case of upgrades. + for module in modules.iter() { + let addr = module.self_addr(); + let name = module.self_name(); + + // TODO: Allow the check of special addresses to be customized. + if addr.is_special() || traversal_context.visited.insert((addr, name), ()).is_some() + { + continue; + } + + if let Some(size) = code_storage + .fetch_module_size_in_bytes(addr, name)? + .map(|v| v as u64) + { + gas_meter + .charge_dependency(false, addr, name, NumBytes::new(size)) + .map_err(|err| { + err.finish(Location::Module(ModuleId::new(*addr, name.to_owned()))) + })?; + } + } + + // Charge all modules in the bundle that is about to be published. + for (module, blob) in modules.iter().zip(module_bundle.iter()) { + let module_id = &module.self_id(); + gas_meter + .charge_dependency( + true, + module_id.address(), + module_id.name(), + NumBytes::new(blob.code().len() as u64), + ) + .map_err(|err| err.finish(Location::Undefined))?; + } + + // Charge all dependencies. + // + // Must exclude the ones that are in the current bundle because they have not + // been published yet. + let module_ids_in_bundle = modules + .iter() + .map(|module| (module.self_addr(), module.self_name())) + .collect::>(); + + self.check_dependencies_and_charge_gas( + code_storage, + gas_meter, + traversal_context, + modules + .iter() + .flat_map(|module| { + module + .immediate_dependencies_iter() + .chain(module.immediate_friends_iter()) + }) + .filter(|addr_and_name| !module_ids_in_bundle.contains(addr_and_name)), + )?; + } + + // validate modules are properly compiled with metadata + validate_publish_request(code_storage, modules, module_bundle, allow_unsafe)?; + + // validate dependencies upgrade policy + if !skip_dependencies_upgrade_policy_verification { + self.verify_dependencies_upgrade_policy( + gas_meter, + code_storage, + traversal_context, + publisher, + modules, + upgrade_policy, + )?; + } + + Ok(()) + } + + fn verify_dependencies_upgrade_policy( + &mut self, + gas_meter: &mut InitiaGasMeter, + module_storage: &impl ModuleStorage, + traversal_context: &mut TraversalContext<'_>, + publisher: AccountAddress, + modules: &[CompiledModule], + upgrade_policy: UpgradePolicy, + ) -> VMResult<()> { + let mut module_ids = vec![]; + let mut dependency_addresses = vec![]; + let mut dependency_module_ids = vec![]; + for module in modules { + let mut deps_addresses = vec![]; + let mut deps_module_ids = vec![]; + for dep in module.immediate_dependencies() { + // allow a special addresses to be used as dependencies, + // even they are having a weaker upgrade policy. + if AccountAddress::is_special(dep.address()) { + continue; + } + + deps_addresses.push(dep.address().to_owned()); + deps_module_ids.push(dep.short_str_lossless()); + } + + module_ids.push(module.self_id().short_str_lossless()); + dependency_addresses.push(deps_addresses); + dependency_module_ids.push(deps_module_ids); + } + + let _ = self.inner.execute_function_bypass_visibility( + &ModuleId::new(AccountAddress::ONE, ident_str!(CODE_MODULE_NAME).into()), + ident_str!(VERIFY_PUBLISH_REQUEST), + vec![], + vec![ + MoveValue::Signer(publisher).simple_serialize().unwrap(), + bcs::to_bytes(&module_ids).unwrap(), + bcs::to_bytes(&dependency_addresses).unwrap(), + bcs::to_bytes(&dependency_module_ids).unwrap(), + bcs::to_bytes(&upgrade_policy.to_u8()).unwrap(), + ], + gas_meter, + traversal_context, + module_storage, + )?; + + Ok(()) + } +} + +fn deserialize_module_bundle( + module_bundle: &ModuleBundle, + deserializer_config: &DeserializerConfig, +) -> VMResult> { + let mut result = vec![]; + for module_blob in module_bundle.iter() { + match CompiledModule::deserialize_with_config(module_blob.code(), deserializer_config) { + Ok(module) => { + result.push(module); + } + Err(err) => return Err(err.finish(Location::Undefined)), + } + } + + Ok(result) +} diff --git a/crates/vm/src/session.rs b/crates/vm/src/session.rs index 2587bb67..9ddf25ba 100644 --- a/crates/vm/src/session.rs +++ b/crates/vm/src/session.rs @@ -1,8 +1,10 @@ use std::{ + collections::BTreeMap, ops::{Deref, DerefMut}, sync::Arc, }; +use bytes::Bytes; use initia_move_json::StructResolver; use initia_move_natives::{ account::NativeAccountContext, @@ -13,14 +15,25 @@ use initia_move_natives::{ table::NativeTableContext, }; use initia_move_types::{ - account::Accounts, cosmos::CosmosMessages, event::ContractEvent, - staking_change_set::StakingChangeSet, write_set::WriteSet, + access_path::AccessPath, + account::Accounts, + cosmos::CosmosMessages, + event::ContractEvent, + staking_change_set::StakingChangeSet, + write_set::{WriteOp, WriteSet}, }; -use move_binary_format::errors::{Location, PartialVMError, VMResult}; -use move_core_types::{language_storage::TypeTag, vm_status::StatusCode}; -use move_vm_runtime::session::Session; -use move_vm_types::loaded_data::runtime_types::{StructNameIndex, StructType, Type}; +use move_binary_format::errors::{Location, PartialVMError, PartialVMResult, VMResult}; +use move_core_types::{ + effects::Op, + language_storage::{ModuleId, TypeTag}, + vm_status::StatusCode, +}; +use move_vm_runtime::{session::Session, ModuleStorage}; +use move_vm_types::{ + loaded_data::runtime_types::{StructNameIndex, StructType, Type}, + sha3_256, +}; pub type SessionOutput<'r> = ( Vec, @@ -31,7 +44,7 @@ pub type SessionOutput<'r> = ( ); pub struct SessionExt<'r, 'l> { - inner: Session<'r, 'l>, + pub(crate) inner: Session<'r, 'l>, } impl<'r, 'l> SessionExt<'r, 'l> { @@ -39,8 +52,10 @@ impl<'r, 'l> SessionExt<'r, 'l> { Self { inner } } - pub fn finish(self) -> VMResult> { - let (change_set, mut extensions) = self.inner.finish_with_extensions()?; + pub fn finish(self, module_storage: &impl ModuleStorage) -> VMResult { + let Self { inner } = self; + + let (change_set, mut extensions) = inner.finish_with_extensions(module_storage)?; let event_context: NativeEventContext = extensions.remove::(); let events = event_context.into_events(); @@ -59,11 +74,12 @@ impl<'r, 'l> SessionExt<'r, 'l> { let new_accounts = account_context.into_accounts(); // build output change set from the changes - let write_set = WriteSet::new(change_set, table_change_set).map_err(|e| { - PartialVMError::new(StatusCode::FAILED_TO_SERIALIZE_WRITE_SET_CHANGES) - .with_message(e.to_string()) - .finish(Location::Undefined) - })?; + let write_set = + WriteSet::new_with_change_set(change_set, table_change_set).map_err(|e| { + PartialVMError::new(StatusCode::FAILED_TO_SERIALIZE_WRITE_SET_CHANGES) + .with_message(e.to_string()) + .finish(Location::Undefined) + })?; Ok(( events, @@ -78,15 +94,54 @@ impl<'r, 'l> SessionExt<'r, 'l> { let ctx = self.get_native_extensions().get_mut::(); ctx.requested_module_bundle.take() } + + /// Converts module bytes and their compiled representation extracted from publish request into + /// write ops. Only used by V2 loader implementation. + pub fn convert_modules_into_write_set( + module_storage: &impl ModuleStorage, + staged_modules: impl Iterator, + ) -> PartialVMResult { + let mut module_write_set: BTreeMap = BTreeMap::new(); + for (module_id, bytes) in staged_modules { + let module_exists = module_storage + .check_module_exists(&module_id.address, &module_id.name) + .map_err(|e| e.to_partial())?; + let op = if module_exists { + Op::Modify(bytes) + } else { + Op::New(bytes) + }; + let ap = AccessPath::code_access_path(module_id.address, module_id.name.to_owned()); + module_write_set.insert(ap, op.clone().map(|v| v.into())); + + let ap = AccessPath::checksum_access_path(module_id.address, module_id.name.to_owned()); + module_write_set.insert( + ap, + op.map(|v| { + let checksum = sha3_256(&v); + checksum.into() + }), + ); + } + Ok(WriteSet::new_with_write_set(module_write_set)) + } } impl StructResolver for SessionExt<'_, '_> { - fn get_struct_type(&self, index: StructNameIndex) -> Option> { - self.inner.get_struct_type(index) + fn get_struct_type( + &self, + index: StructNameIndex, + module_storage: &impl ModuleStorage, + ) -> Option> { + self.inner.fetch_struct_ty_by_idx(index, module_storage) } - fn get_type_tag(&self, ty: &Type) -> VMResult { - self.inner.get_type_tag(ty) + fn type_to_type_tag( + &self, + ty: &Type, + module_storage: &impl ModuleStorage, + ) -> VMResult { + self.inner.get_type_tag(ty, module_storage) } } diff --git a/crates/vm/src/verifier/config.rs b/crates/vm/src/verifier/config.rs index 17f91ac7..11df20c6 100644 --- a/crates/vm/src/verifier/config.rs +++ b/crates/vm/src/verifier/config.rs @@ -10,7 +10,6 @@ pub fn verifier_config() -> VerifierConfig { max_value_stack_size: 1024, max_type_nodes: Some(256), max_push_size: Some(10000), - max_dependency_depth: Some(100), max_struct_definitions: Some(200), max_fields_in_struct: Some(30), max_struct_variants: Some(90), diff --git a/crates/vm/src/verifier/event_validation.rs b/crates/vm/src/verifier/event_validation.rs index c9aeb466..bd7dfbca 100644 --- a/crates/vm/src/verifier/event_validation.rs +++ b/crates/vm/src/verifier/event_validation.rs @@ -14,7 +14,7 @@ use move_binary_format::{ use move_core_types::{ account_address::AccountAddress, language_storage::ModuleId, vm_status::StatusCode, }; -use move_vm_runtime::session::Session; +use move_vm_runtime::ModuleStorage; use std::collections::HashSet; use super::metadata::get_metadata_from_compiled_module; @@ -36,7 +36,7 @@ fn metadata_validation_error(msg: &str) -> VMError { /// * Extract the event metadata /// * Verify all changes are compatible upgrades (existing event attributes cannot be removed) pub(crate) fn validate_module_events( - session: &Session, + module_storage: &impl ModuleStorage, modules: &[CompiledModule], ) -> VMResult<()> { for module in modules { @@ -51,7 +51,7 @@ pub(crate) fn validate_module_events( validate_emit_calls(&new_event_structs, module)?; let original_event_structs = - extract_event_metadata_from_module(session, &module.self_id())?; + extract_event_metadata_from_module(module_storage, &module.self_id())?; for member in original_event_structs { // Fail if we see a removal of an event attribute. @@ -119,18 +119,14 @@ pub(crate) fn validate_emit_calls( /// Given a module id extract all event metadata pub(crate) fn extract_event_metadata_from_module( - session: &Session, + module_storage: &impl ModuleStorage, module_id: &ModuleId, ) -> VMResult> { - let metadata = session.load_module(module_id).map(|module| { - CompiledModule::deserialize_with_config( - &module, - &session.get_vm_config().deserializer_config, - ) - .map(|module| get_metadata_from_compiled_module(&module)) - }); + let metadata = module_storage + .fetch_deserialized_module(module_id.address(), module_id.name())? + .map(|module| get_metadata_from_compiled_module(module.as_ref())); - if let Ok(Ok(Some(metadata))) = metadata { + if let Some(Some(metadata)) = metadata { extract_event_metadata(&metadata) } else { Ok(HashSet::new()) diff --git a/crates/vm/src/verifier/metadata.rs b/crates/vm/src/verifier/metadata.rs index 41cba72e..99f8017f 100644 --- a/crates/vm/src/verifier/metadata.rs +++ b/crates/vm/src/verifier/metadata.rs @@ -1,9 +1,9 @@ /// Extract metadata from the VM, upgrading V0 to V1 representation as needed -use crate::session::SessionExt; use initia_move_types::metadata::{RuntimeModuleMetadataV0, INITIA_METADATA_KEY_V0}; use move_binary_format::{file_format::CompiledScript, CompiledModule}; use move_core_types::{language_storage::ModuleId, metadata::Metadata}; use move_model::metadata::{CompilationMetadata, COMPILATION_METADATA_KEY}; +use move_vm_runtime::ModuleStorage; /// Extract metadata from the VM, upgrading V0 to V1 representation as needed pub fn get_metadata(md: &[Metadata]) -> Option { @@ -15,12 +15,13 @@ pub fn get_metadata(md: &[Metadata]) -> Option { } pub(crate) fn get_vm_metadata( - session: &SessionExt, + module_storage: &impl ModuleStorage, module_id: &ModuleId, ) -> Option { - session - .get_move_vm() - .with_module_metadata(module_id, get_metadata) + let metadata = module_storage + .fetch_module_metadata(module_id.address(), module_id.name()) + .ok()??; + get_metadata(&metadata) } /// Extract metadata from a compiled module, upgrading V0 to V1 representation as needed. diff --git a/crates/vm/src/verifier/module_metadata.rs b/crates/vm/src/verifier/module_metadata.rs index 4b65bacf..14a7ba73 100644 --- a/crates/vm/src/verifier/module_metadata.rs +++ b/crates/vm/src/verifier/module_metadata.rs @@ -17,7 +17,7 @@ use move_core_types::{ vm_status::StatusCode, }; use move_model::metadata::{CompilationMetadata, COMPILATION_METADATA_KEY}; -use move_vm_runtime::session::Session; +use move_vm_runtime::ModuleStorage; use super::{ errors::{ @@ -30,7 +30,7 @@ use super::{ }; pub(crate) fn validate_publish_request( - session: &Session, + module_storage: &impl ModuleStorage, modules: &[CompiledModule], module_bundle: &ModuleBundle, allow_unstable: bool, @@ -47,7 +47,7 @@ pub(crate) fn validate_publish_request( validate_module_metadata(module).map_err(|e| metadata_validation_error(&e.to_string()))?; } - validate_module_events(session, modules) + validate_module_events(module_storage, modules) .map_err(|e| metadata_validation_error(&e.to_string()))?; Ok(()) diff --git a/crates/vm/src/verifier/transaction_arg_validation.rs b/crates/vm/src/verifier/transaction_arg_validation.rs index e5f3e2bb..b3bea33b 100644 --- a/crates/vm/src/verifier/transaction_arg_validation.rs +++ b/crates/vm/src/verifier/transaction_arg_validation.rs @@ -1,3 +1,5 @@ +use initia_move_storage::initia_storage::InitiaStorage; +use initia_move_storage::state_view::StateView; use move_binary_format::errors::{Location, PartialVMError}; use move_binary_format::file_format::FunctionDefinitionIndex; use move_binary_format::file_format_common::read_uleb128_as_u64; @@ -8,11 +10,10 @@ use move_core_types::vm_status::VMStatus; use move_core_types::{account_address::AccountAddress, value::MoveValue, vm_status::StatusCode}; use move_vm_runtime::module_traversal::{TraversalContext, TraversalStorage}; use move_vm_runtime::session::Session; -use move_vm_runtime::LoadedFunction; +use move_vm_runtime::{LoadedFunction, ModuleStorage}; use move_vm_types::gas::{GasMeter, UnmeteredGasMeter}; use move_vm_types::loaded_data::runtime_types::Type; -use move_vm_types::resolver::MoveResolver; use once_cell::sync::Lazy; use std::io::Read; use std::{collections::BTreeMap, io::Cursor}; @@ -112,9 +113,9 @@ pub(crate) static ALLOWED_STRUCTS: ConstructorMap = Lazy::new(|| { /// 3. check arg types are allowed after signers /// /// after validation, add senders and non-signer arguments to generate the final args -pub fn validate_combine_signer_and_txn_args( +pub fn validate_combine_signer_and_txn_args( session: &mut SessionExt, - state_view: &M, + code_storage: &InitiaStorage, senders: Vec, args: Vec>, func: &LoadedFunction, @@ -148,7 +149,7 @@ pub fn validate_combine_signer_and_txn_args( for ty in func.param_tys()[signer_param_cnt..].iter() { let subst_res = ty_builder.create_ty_with_subst(ty, func.ty_args()); let ty = subst_res.map_err(|e| e.finish(Location::Undefined).into_vm_status())?; - let valid = is_valid_txn_arg(session, &ty, allowed_structs); + let valid = is_valid_txn_arg(session, code_storage, &ty, allowed_structs); if !valid { return Err(VMStatus::error( StatusCode::INVALID_MAIN_FUNCTION_SIGNATURE, @@ -180,7 +181,7 @@ pub fn validate_combine_signer_and_txn_args( // FAILED_TO_DESERIALIZE_ARGUMENT error. let args = construct_args( session, - state_view, + code_storage, &func.param_tys()[signer_param_cnt..], args, func.ty_args(), @@ -205,20 +206,21 @@ pub fn validate_combine_signer_and_txn_args( // Return whether the argument is valid/allowed and whether it needs construction. pub(crate) fn is_valid_txn_arg( session: &Session, - typ: &Type, + module_storage: &impl ModuleStorage, + ty: &Type, allowed_structs: &ConstructorMap, ) -> bool { use move_vm_types::loaded_data::runtime_types::Type::*; - match typ { + match ty { Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address => true, - Vector(inner) => is_valid_txn_arg(session, inner, allowed_structs), - Struct { idx, .. } | StructInstantiation { idx, .. } => { - session.get_struct_type(*idx).is_some_and(|st| { + Vector(inner) => is_valid_txn_arg(session, module_storage, inner, allowed_structs), + Struct { idx, .. } | StructInstantiation { idx, .. } => session + .fetch_struct_ty_by_idx(*idx, module_storage) + .is_some_and(|st| { let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); allowed_structs.contains_key(&full_name) - }) - } + }), Signer | Reference(_) | MutableReference(_) | TyParam(_) => false, } } @@ -227,9 +229,9 @@ pub(crate) fn is_valid_txn_arg( // Construct arguments. Walk through the arguments and according to the signature // construct arguments that require so. // TODO: This needs a more solid story and a tighter integration with the VM. -pub(crate) fn construct_args( +pub(crate) fn construct_args( session: &mut SessionExt, - state_view: &M, + code_storage: &InitiaStorage, types: &[Type], args: Vec>, ty_args: &[Type], @@ -250,7 +252,7 @@ pub(crate) fn construct_args( let ty = subst_res.map_err(|e| e.finish(Location::Undefined).into_vm_status())?; let arg = construct_arg( session, - state_view, + code_storage, &ty, allowed_structs, arg, @@ -268,9 +270,9 @@ fn invalid_signature() -> VMStatus { } #[allow(clippy::too_many_arguments)] -fn construct_arg( +fn construct_arg( session: &mut SessionExt, - state_view: &M, + code_storage: &InitiaStorage, ty: &Type, allowed_structs: &ConstructorMap, arg: Vec, @@ -279,7 +281,7 @@ fn construct_arg( is_json: bool, ) -> Result, VMStatus> { if is_json { - return deserialize_json_args(session, state_view, ty, &arg) + return deserialize_json_args(code_storage, session, ty, &arg) .map_err(|e| e.into_vm_status()); } @@ -294,6 +296,7 @@ fn construct_arg( let max_depth = 10; recursively_construct_arg( session, + code_storage, ty, allowed_structs, &mut cursor, @@ -332,6 +335,7 @@ fn construct_arg( // constructed types into the output parameter arg. pub(crate) fn recursively_construct_arg( session: &mut Session, + module_storage: &impl ModuleStorage, ty: &Type, allowed_structs: &ConstructorMap, cursor: &mut Cursor<&[u8]>, @@ -358,6 +362,7 @@ pub(crate) fn recursively_construct_arg( while len > 0 { recursively_construct_arg( session, + module_storage, inner, allowed_structs, cursor, @@ -372,7 +377,7 @@ pub(crate) fn recursively_construct_arg( } Struct { idx, .. } | StructInstantiation { idx, .. } => { let st = session - .get_struct_type(*idx) + .fetch_struct_ty_by_idx(*idx, module_storage) .ok_or_else(invalid_signature)?; let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); @@ -383,6 +388,7 @@ pub(crate) fn recursively_construct_arg( // of the argument. arg.append(&mut validate_and_construct( session, + module_storage, ty, constructor, allowed_structs, @@ -411,6 +417,7 @@ pub(crate) fn recursively_construct_arg( // value and returning the BCS serialized representation. fn validate_and_construct( session: &mut Session, + module_storage: &impl ModuleStorage, expected_type: &Type, constructor: &FunctionId, allowed_structs: &ConstructorMap, @@ -471,6 +478,7 @@ fn validate_and_construct( } let function = session.load_function_with_type_arg_inference( + module_storage, &constructor.module_id, constructor.func_name, expected_type, @@ -485,6 +493,7 @@ fn validate_and_construct( recursively_construct_arg( session, + module_storage, &arg_ty, allowed_structs, cursor, @@ -502,6 +511,7 @@ fn validate_and_construct( args, gas_meter, &mut TraversalContext::new(&storage), + module_storage, )?; let mut ret_vals = serialized_result.return_values; // We know ret_vals.len() == 1 diff --git a/crates/vm/src/verifier/view_function.rs b/crates/vm/src/verifier/view_function.rs index 04589011..af21b1c3 100644 --- a/crates/vm/src/verifier/view_function.rs +++ b/crates/vm/src/verifier/view_function.rs @@ -1,11 +1,11 @@ use crate::{session::SessionExt, verifier::transaction_arg_validation}; +use initia_move_storage::{initia_storage::InitiaStorage, state_view::StateView}; use initia_move_types::metadata::RuntimeModuleMetadataV0; use move_core_types::{ identifier::IdentStr, vm_status::{StatusCode, VMStatus}, }; use move_vm_runtime::LoadedFunction; -use move_vm_types::resolver::MoveResolver; use super::transaction_arg_validation::ALLOWED_STRUCTS; @@ -27,9 +27,9 @@ pub fn determine_is_view( /// Validate view function call. This checks whether the function is marked as a view /// function, and validates the arguments. -pub(crate) fn validate_view_function( +pub(crate) fn validate_view_function_and_construct( session: &mut SessionExt, - move_resolver: &M, + code_storage: &InitiaStorage, args: Vec>, fun_name: &IdentStr, func: &LoadedFunction, @@ -56,7 +56,7 @@ pub(crate) fn validate_view_function( let allowed_structs = &ALLOWED_STRUCTS; let args = transaction_arg_validation::construct_args( session, - move_resolver, + code_storage, func.param_tys(), args, func.ty_args(), diff --git a/lib_test.go b/lib_test.go index 9fac0299..7462fe8b 100644 --- a/lib_test.go +++ b/lib_test.go @@ -56,7 +56,9 @@ func initializeVM(t *testing.T, isMinitia bool) (vm.VM, *api.Lookup) { blockTime := uint64(time.Now().Unix()) vm, err := vm.NewVM(types.InitiaVMConfig{ - AllowUnstable: true, + AllowUnstable: true, + ScriptCacheCapacity: 100, + ModuleCacheCapacity: 500, }) require.NoError(t, err) _, err = vm.Initialize( diff --git a/libmovevm/Cargo.toml b/libmovevm/Cargo.toml index 89d49c10..2cd16b72 100644 --- a/libmovevm/Cargo.toml +++ b/libmovevm/Cargo.toml @@ -50,6 +50,7 @@ move-core-types = { workspace = true } move-vm-types = { workspace = true } move-resource-viewer = { workspace = true } move-binary-format = { workspace = true } +move-vm-runtime = { workspace = true } [build-dependencies] cbindgen = { workspace = true } diff --git a/libmovevm/src/vm.rs b/libmovevm/src/vm.rs index f6658181..5e132700 100644 --- a/libmovevm/src/vm.rs +++ b/libmovevm/src/vm.rs @@ -8,7 +8,6 @@ use crate::Db; use crate::GoStorage; use initia_move_gas::InitiaGasMeter; -use initia_move_storage::state_view_impl::StateViewImpl; use initia_move_types::access_path::AccessPath; use initia_move_types::env::Env; use initia_move_types::errors::BackendError; @@ -31,12 +30,10 @@ pub(crate) fn initialize_vm( let mut storage = GoStorage::new(&db_handle); let mut table_storage = GoTableStorage::new(&db_handle); - let state_view_impl = - StateViewImpl::new_with_deserialize_config(&storage, vm.deserialize_config().clone()); let output = vm.initialize( &api, &env, - &state_view_impl, + &storage, &mut table_storage, module_bundle, allowed_publishers, @@ -60,17 +57,8 @@ pub(crate) fn execute_contract( let mut storage = GoStorage::new(&db_handle); let mut table_storage = GoTableStorage::new(&db_handle); - let state_view_impl = - StateViewImpl::new_with_deserialize_config(&storage, vm.deserialize_config().clone()); - - let output = vm.execute_message( - gas_meter, - &api, - &env, - &state_view_impl, - &mut table_storage, - message, - )?; + let output = + vm.execute_message(gas_meter, &api, &env, &storage, &mut table_storage, message)?; // push write set to storage push_write_set(&mut storage, output.write_set())?; @@ -91,17 +79,8 @@ pub(crate) fn execute_script( let mut table_storage = GoTableStorage::new(&db_handle); // NOTE - storage passed as mut for iterator implementation - let state_view_impl = - StateViewImpl::new_with_deserialize_config(&storage, vm.deserialize_config().clone()); - - let output = vm.execute_message( - gas_meter, - &api, - &env, - &state_view_impl, - &mut table_storage, - message, - )?; + let output = + vm.execute_message(gas_meter, &api, &env, &storage, &mut table_storage, message)?; // push write set to storage push_write_set(&mut storage, output.write_set())?; @@ -122,14 +101,11 @@ pub(crate) fn execute_view_function( let storage = GoStorage::new(&db_handle); let mut table_storage = GoTableStorage::new(&db_handle); - let state_view_impl = - StateViewImpl::new_with_deserialize_config(&storage, vm.deserialize_config().clone()); - let output = vm.execute_view_function( gas_meter, &api, &env, - &state_view_impl, + &storage, &mut table_storage, &view_fn, )?; diff --git a/precompile/binaries/minlib/account.mv b/precompile/binaries/minlib/account.mv index ce61feb2..ac74d88f 100644 Binary files a/precompile/binaries/minlib/account.mv and b/precompile/binaries/minlib/account.mv differ diff --git a/precompile/binaries/minlib/acl.mv b/precompile/binaries/minlib/acl.mv index 6b82accc..85385977 100644 Binary files a/precompile/binaries/minlib/acl.mv and b/precompile/binaries/minlib/acl.mv differ diff --git a/precompile/binaries/minlib/any.mv b/precompile/binaries/minlib/any.mv index b5f562b1..a5bbd540 100644 Binary files a/precompile/binaries/minlib/any.mv and b/precompile/binaries/minlib/any.mv differ diff --git a/precompile/binaries/minlib/ascii.mv b/precompile/binaries/minlib/ascii.mv index 40493e16..bd763871 100644 Binary files a/precompile/binaries/minlib/ascii.mv and b/precompile/binaries/minlib/ascii.mv differ diff --git a/precompile/binaries/minlib/bigdecimal.mv b/precompile/binaries/minlib/bigdecimal.mv index 875416e7..f6f182b8 100644 Binary files a/precompile/binaries/minlib/bigdecimal.mv and b/precompile/binaries/minlib/bigdecimal.mv differ diff --git a/precompile/binaries/minlib/biguint.mv b/precompile/binaries/minlib/biguint.mv index 6bd9837b..5df3f7a4 100644 Binary files a/precompile/binaries/minlib/biguint.mv and b/precompile/binaries/minlib/biguint.mv differ diff --git a/precompile/binaries/minlib/bit_vector.mv b/precompile/binaries/minlib/bit_vector.mv index fc0771af..bc6fbc4c 100644 Binary files a/precompile/binaries/minlib/bit_vector.mv and b/precompile/binaries/minlib/bit_vector.mv differ diff --git a/precompile/binaries/minlib/block.mv b/precompile/binaries/minlib/block.mv index 022294be..9c84f44b 100644 Binary files a/precompile/binaries/minlib/block.mv and b/precompile/binaries/minlib/block.mv differ diff --git a/precompile/binaries/minlib/capability.mv b/precompile/binaries/minlib/capability.mv index bff615cc..8aa96019 100644 Binary files a/precompile/binaries/minlib/capability.mv and b/precompile/binaries/minlib/capability.mv differ diff --git a/precompile/binaries/minlib/code.mv b/precompile/binaries/minlib/code.mv index 5eb5b4f3..06ca1615 100644 Binary files a/precompile/binaries/minlib/code.mv and b/precompile/binaries/minlib/code.mv differ diff --git a/precompile/binaries/minlib/coin.mv b/precompile/binaries/minlib/coin.mv index a9097628..d7483d09 100644 Binary files a/precompile/binaries/minlib/coin.mv and b/precompile/binaries/minlib/coin.mv differ diff --git a/precompile/binaries/minlib/collection.mv b/precompile/binaries/minlib/collection.mv index 55ab3410..07ce7f53 100644 Binary files a/precompile/binaries/minlib/collection.mv and b/precompile/binaries/minlib/collection.mv differ diff --git a/precompile/binaries/minlib/comparator.mv b/precompile/binaries/minlib/comparator.mv index d3ef5487..8ef60db2 100644 Binary files a/precompile/binaries/minlib/comparator.mv and b/precompile/binaries/minlib/comparator.mv differ diff --git a/precompile/binaries/minlib/compare.mv b/precompile/binaries/minlib/compare.mv index dab39640..1256b190 100644 Binary files a/precompile/binaries/minlib/compare.mv and b/precompile/binaries/minlib/compare.mv differ diff --git a/precompile/binaries/minlib/copyable_any.mv b/precompile/binaries/minlib/copyable_any.mv index 4912d5e7..56ccac92 100644 Binary files a/precompile/binaries/minlib/copyable_any.mv and b/precompile/binaries/minlib/copyable_any.mv differ diff --git a/precompile/binaries/minlib/cosmos.mv b/precompile/binaries/minlib/cosmos.mv index ee9e86d5..cac09be4 100644 Binary files a/precompile/binaries/minlib/cosmos.mv and b/precompile/binaries/minlib/cosmos.mv differ diff --git a/precompile/binaries/minlib/dex.mv b/precompile/binaries/minlib/dex.mv index 0d5ebab4..4280ec2e 100644 Binary files a/precompile/binaries/minlib/dex.mv and b/precompile/binaries/minlib/dex.mv differ diff --git a/precompile/binaries/minlib/dispatchable_fungible_asset.mv b/precompile/binaries/minlib/dispatchable_fungible_asset.mv index d61093dd..8ddf36a4 100644 Binary files a/precompile/binaries/minlib/dispatchable_fungible_asset.mv and b/precompile/binaries/minlib/dispatchable_fungible_asset.mv differ diff --git a/precompile/binaries/minlib/ed25519.mv b/precompile/binaries/minlib/ed25519.mv index 81295bfb..fdca2eb0 100644 Binary files a/precompile/binaries/minlib/ed25519.mv and b/precompile/binaries/minlib/ed25519.mv differ diff --git a/precompile/binaries/minlib/fixed_point32.mv b/precompile/binaries/minlib/fixed_point32.mv index c197aa67..46998389 100644 Binary files a/precompile/binaries/minlib/fixed_point32.mv and b/precompile/binaries/minlib/fixed_point32.mv differ diff --git a/precompile/binaries/minlib/fixed_point64.mv b/precompile/binaries/minlib/fixed_point64.mv index e88c61d3..0401e7cf 100644 Binary files a/precompile/binaries/minlib/fixed_point64.mv and b/precompile/binaries/minlib/fixed_point64.mv differ diff --git a/precompile/binaries/minlib/from_bcs.mv b/precompile/binaries/minlib/from_bcs.mv index 69c253e6..2c030eb8 100644 Binary files a/precompile/binaries/minlib/from_bcs.mv and b/precompile/binaries/minlib/from_bcs.mv differ diff --git a/precompile/binaries/minlib/function_info.mv b/precompile/binaries/minlib/function_info.mv index 2313d43e..5111c7d1 100644 Binary files a/precompile/binaries/minlib/function_info.mv and b/precompile/binaries/minlib/function_info.mv differ diff --git a/precompile/binaries/minlib/fungible_asset.mv b/precompile/binaries/minlib/fungible_asset.mv index 9850ce40..dd34c377 100644 Binary files a/precompile/binaries/minlib/fungible_asset.mv and b/precompile/binaries/minlib/fungible_asset.mv differ diff --git a/precompile/binaries/minlib/guid.mv b/precompile/binaries/minlib/guid.mv index d061b091..1b532b8d 100644 Binary files a/precompile/binaries/minlib/guid.mv and b/precompile/binaries/minlib/guid.mv differ diff --git a/precompile/binaries/minlib/hex.mv b/precompile/binaries/minlib/hex.mv index 9a733391..ac7d6c0e 100644 Binary files a/precompile/binaries/minlib/hex.mv and b/precompile/binaries/minlib/hex.mv differ diff --git a/precompile/binaries/minlib/initia_nft.mv b/precompile/binaries/minlib/initia_nft.mv index 74e59982..b7c9afc7 100644 Binary files a/precompile/binaries/minlib/initia_nft.mv and b/precompile/binaries/minlib/initia_nft.mv differ diff --git a/precompile/binaries/minlib/json.mv b/precompile/binaries/minlib/json.mv index ba2f4a98..b312aa49 100644 Binary files a/precompile/binaries/minlib/json.mv and b/precompile/binaries/minlib/json.mv differ diff --git a/precompile/binaries/minlib/managed_coin.mv b/precompile/binaries/minlib/managed_coin.mv index 72fc4864..158500aa 100644 Binary files a/precompile/binaries/minlib/managed_coin.mv and b/precompile/binaries/minlib/managed_coin.mv differ diff --git a/precompile/binaries/minlib/math128.mv b/precompile/binaries/minlib/math128.mv index d2f98227..99ccb71b 100644 Binary files a/precompile/binaries/minlib/math128.mv and b/precompile/binaries/minlib/math128.mv differ diff --git a/precompile/binaries/minlib/math64.mv b/precompile/binaries/minlib/math64.mv index c102411e..ec22cfbf 100644 Binary files a/precompile/binaries/minlib/math64.mv and b/precompile/binaries/minlib/math64.mv differ diff --git a/precompile/binaries/minlib/multisig.mv b/precompile/binaries/minlib/multisig.mv index df6af4e0..175115dc 100644 Binary files a/precompile/binaries/minlib/multisig.mv and b/precompile/binaries/minlib/multisig.mv differ diff --git a/precompile/binaries/minlib/multisig_v2.mv b/precompile/binaries/minlib/multisig_v2.mv index 50094d4a..b7554715 100644 Binary files a/precompile/binaries/minlib/multisig_v2.mv and b/precompile/binaries/minlib/multisig_v2.mv differ diff --git a/precompile/binaries/minlib/nft.mv b/precompile/binaries/minlib/nft.mv index 3f72b425..af22c911 100644 Binary files a/precompile/binaries/minlib/nft.mv and b/precompile/binaries/minlib/nft.mv differ diff --git a/precompile/binaries/minlib/object.mv b/precompile/binaries/minlib/object.mv index f9942a00..dcdaa21c 100644 Binary files a/precompile/binaries/minlib/object.mv and b/precompile/binaries/minlib/object.mv differ diff --git a/precompile/binaries/minlib/object_code_deployment.mv b/precompile/binaries/minlib/object_code_deployment.mv index bb6260f9..e1fdf2b7 100644 Binary files a/precompile/binaries/minlib/object_code_deployment.mv and b/precompile/binaries/minlib/object_code_deployment.mv differ diff --git a/precompile/binaries/minlib/option.mv b/precompile/binaries/minlib/option.mv index a50f65dc..2e75c7b8 100644 Binary files a/precompile/binaries/minlib/option.mv and b/precompile/binaries/minlib/option.mv differ diff --git a/precompile/binaries/minlib/oracle.mv b/precompile/binaries/minlib/oracle.mv index 5f332c26..e2da6cda 100644 Binary files a/precompile/binaries/minlib/oracle.mv and b/precompile/binaries/minlib/oracle.mv differ diff --git a/precompile/binaries/minlib/primary_fungible_store.mv b/precompile/binaries/minlib/primary_fungible_store.mv index bee850f2..d120e781 100644 Binary files a/precompile/binaries/minlib/primary_fungible_store.mv and b/precompile/binaries/minlib/primary_fungible_store.mv differ diff --git a/precompile/binaries/minlib/property_map.mv b/precompile/binaries/minlib/property_map.mv index bec5d0ef..f9dc5967 100644 Binary files a/precompile/binaries/minlib/property_map.mv and b/precompile/binaries/minlib/property_map.mv differ diff --git a/precompile/binaries/minlib/query.mv b/precompile/binaries/minlib/query.mv index 5daa782a..380e0006 100644 Binary files a/precompile/binaries/minlib/query.mv and b/precompile/binaries/minlib/query.mv differ diff --git a/precompile/binaries/minlib/royalty.mv b/precompile/binaries/minlib/royalty.mv index 426a0230..ebc23375 100644 Binary files a/precompile/binaries/minlib/royalty.mv and b/precompile/binaries/minlib/royalty.mv differ diff --git a/precompile/binaries/minlib/secp256k1.mv b/precompile/binaries/minlib/secp256k1.mv index e94c009e..d290f138 100644 Binary files a/precompile/binaries/minlib/secp256k1.mv and b/precompile/binaries/minlib/secp256k1.mv differ diff --git a/precompile/binaries/minlib/simple_map.mv b/precompile/binaries/minlib/simple_map.mv index 7bbd4ca8..69147e65 100644 Binary files a/precompile/binaries/minlib/simple_map.mv and b/precompile/binaries/minlib/simple_map.mv differ diff --git a/precompile/binaries/minlib/simple_nft.mv b/precompile/binaries/minlib/simple_nft.mv index b9a64fe9..20c655e9 100644 Binary files a/precompile/binaries/minlib/simple_nft.mv and b/precompile/binaries/minlib/simple_nft.mv differ diff --git a/precompile/binaries/minlib/soul_bound_token.mv b/precompile/binaries/minlib/soul_bound_token.mv index d0e96619..5fd76206 100644 Binary files a/precompile/binaries/minlib/soul_bound_token.mv and b/precompile/binaries/minlib/soul_bound_token.mv differ diff --git a/precompile/binaries/minlib/string.mv b/precompile/binaries/minlib/string.mv index 302f00c0..f2f46ef1 100644 Binary files a/precompile/binaries/minlib/string.mv and b/precompile/binaries/minlib/string.mv differ diff --git a/precompile/binaries/minlib/string_utils.mv b/precompile/binaries/minlib/string_utils.mv index 84c7e392..ab190016 100644 Binary files a/precompile/binaries/minlib/string_utils.mv and b/precompile/binaries/minlib/string_utils.mv differ diff --git a/precompile/binaries/minlib/table.mv b/precompile/binaries/minlib/table.mv index e2aae1c9..67fef3f0 100644 Binary files a/precompile/binaries/minlib/table.mv and b/precompile/binaries/minlib/table.mv differ diff --git a/precompile/binaries/minlib/timestamp.mv b/precompile/binaries/minlib/timestamp.mv index 688bbbc1..6f06cb8d 100644 Binary files a/precompile/binaries/minlib/timestamp.mv and b/precompile/binaries/minlib/timestamp.mv differ diff --git a/precompile/binaries/minlib/vector.mv b/precompile/binaries/minlib/vector.mv index 95e8befa..989ab5e2 100644 Binary files a/precompile/binaries/minlib/vector.mv and b/precompile/binaries/minlib/vector.mv differ diff --git a/precompile/binaries/minlib/vip_score.mv b/precompile/binaries/minlib/vip_score.mv index 2fa01397..7f8bbc13 100644 Binary files a/precompile/binaries/minlib/vip_score.mv and b/precompile/binaries/minlib/vip_score.mv differ diff --git a/precompile/binaries/stdlib/account.mv b/precompile/binaries/stdlib/account.mv index 377eac11..e2238c5b 100644 Binary files a/precompile/binaries/stdlib/account.mv and b/precompile/binaries/stdlib/account.mv differ diff --git a/precompile/binaries/stdlib/acl.mv b/precompile/binaries/stdlib/acl.mv index 6b82accc..85385977 100644 Binary files a/precompile/binaries/stdlib/acl.mv and b/precompile/binaries/stdlib/acl.mv differ diff --git a/precompile/binaries/stdlib/any.mv b/precompile/binaries/stdlib/any.mv index b5f562b1..a5bbd540 100644 Binary files a/precompile/binaries/stdlib/any.mv and b/precompile/binaries/stdlib/any.mv differ diff --git a/precompile/binaries/stdlib/ascii.mv b/precompile/binaries/stdlib/ascii.mv index 40493e16..bd763871 100644 Binary files a/precompile/binaries/stdlib/ascii.mv and b/precompile/binaries/stdlib/ascii.mv differ diff --git a/precompile/binaries/stdlib/bigdecimal.mv b/precompile/binaries/stdlib/bigdecimal.mv index 875416e7..f6f182b8 100644 Binary files a/precompile/binaries/stdlib/bigdecimal.mv and b/precompile/binaries/stdlib/bigdecimal.mv differ diff --git a/precompile/binaries/stdlib/biguint.mv b/precompile/binaries/stdlib/biguint.mv index 6bd9837b..5df3f7a4 100644 Binary files a/precompile/binaries/stdlib/biguint.mv and b/precompile/binaries/stdlib/biguint.mv differ diff --git a/precompile/binaries/stdlib/bit_vector.mv b/precompile/binaries/stdlib/bit_vector.mv index fc0771af..bc6fbc4c 100644 Binary files a/precompile/binaries/stdlib/bit_vector.mv and b/precompile/binaries/stdlib/bit_vector.mv differ diff --git a/precompile/binaries/stdlib/block.mv b/precompile/binaries/stdlib/block.mv index 022294be..9c84f44b 100644 Binary files a/precompile/binaries/stdlib/block.mv and b/precompile/binaries/stdlib/block.mv differ diff --git a/precompile/binaries/stdlib/capability.mv b/precompile/binaries/stdlib/capability.mv index bff615cc..8aa96019 100644 Binary files a/precompile/binaries/stdlib/capability.mv and b/precompile/binaries/stdlib/capability.mv differ diff --git a/precompile/binaries/stdlib/code.mv b/precompile/binaries/stdlib/code.mv index 5eb5b4f3..06ca1615 100644 Binary files a/precompile/binaries/stdlib/code.mv and b/precompile/binaries/stdlib/code.mv differ diff --git a/precompile/binaries/stdlib/coin.mv b/precompile/binaries/stdlib/coin.mv index a955dacb..5164c437 100644 Binary files a/precompile/binaries/stdlib/coin.mv and b/precompile/binaries/stdlib/coin.mv differ diff --git a/precompile/binaries/stdlib/collection.mv b/precompile/binaries/stdlib/collection.mv index 55ab3410..07ce7f53 100644 Binary files a/precompile/binaries/stdlib/collection.mv and b/precompile/binaries/stdlib/collection.mv differ diff --git a/precompile/binaries/stdlib/comparator.mv b/precompile/binaries/stdlib/comparator.mv index d3ef5487..8ef60db2 100644 Binary files a/precompile/binaries/stdlib/comparator.mv and b/precompile/binaries/stdlib/comparator.mv differ diff --git a/precompile/binaries/stdlib/compare.mv b/precompile/binaries/stdlib/compare.mv index dab39640..1256b190 100644 Binary files a/precompile/binaries/stdlib/compare.mv and b/precompile/binaries/stdlib/compare.mv differ diff --git a/precompile/binaries/stdlib/copyable_any.mv b/precompile/binaries/stdlib/copyable_any.mv index 4912d5e7..56ccac92 100644 Binary files a/precompile/binaries/stdlib/copyable_any.mv and b/precompile/binaries/stdlib/copyable_any.mv differ diff --git a/precompile/binaries/stdlib/cosmos.mv b/precompile/binaries/stdlib/cosmos.mv index ee9e86d5..cac09be4 100644 Binary files a/precompile/binaries/stdlib/cosmos.mv and b/precompile/binaries/stdlib/cosmos.mv differ diff --git a/precompile/binaries/stdlib/dex.mv b/precompile/binaries/stdlib/dex.mv index 0d5ebab4..4280ec2e 100644 Binary files a/precompile/binaries/stdlib/dex.mv and b/precompile/binaries/stdlib/dex.mv differ diff --git a/precompile/binaries/stdlib/dispatchable_fungible_asset.mv b/precompile/binaries/stdlib/dispatchable_fungible_asset.mv index d61093dd..8ddf36a4 100644 Binary files a/precompile/binaries/stdlib/dispatchable_fungible_asset.mv and b/precompile/binaries/stdlib/dispatchable_fungible_asset.mv differ diff --git a/precompile/binaries/stdlib/ed25519.mv b/precompile/binaries/stdlib/ed25519.mv index 81295bfb..fdca2eb0 100644 Binary files a/precompile/binaries/stdlib/ed25519.mv and b/precompile/binaries/stdlib/ed25519.mv differ diff --git a/precompile/binaries/stdlib/fixed_point32.mv b/precompile/binaries/stdlib/fixed_point32.mv index c197aa67..46998389 100644 Binary files a/precompile/binaries/stdlib/fixed_point32.mv and b/precompile/binaries/stdlib/fixed_point32.mv differ diff --git a/precompile/binaries/stdlib/fixed_point64.mv b/precompile/binaries/stdlib/fixed_point64.mv index e88c61d3..0401e7cf 100644 Binary files a/precompile/binaries/stdlib/fixed_point64.mv and b/precompile/binaries/stdlib/fixed_point64.mv differ diff --git a/precompile/binaries/stdlib/from_bcs.mv b/precompile/binaries/stdlib/from_bcs.mv index 69c253e6..2c030eb8 100644 Binary files a/precompile/binaries/stdlib/from_bcs.mv and b/precompile/binaries/stdlib/from_bcs.mv differ diff --git a/precompile/binaries/stdlib/function_info.mv b/precompile/binaries/stdlib/function_info.mv index 2313d43e..5111c7d1 100644 Binary files a/precompile/binaries/stdlib/function_info.mv and b/precompile/binaries/stdlib/function_info.mv differ diff --git a/precompile/binaries/stdlib/fungible_asset.mv b/precompile/binaries/stdlib/fungible_asset.mv index 9f6c1fcc..3b62d882 100644 Binary files a/precompile/binaries/stdlib/fungible_asset.mv and b/precompile/binaries/stdlib/fungible_asset.mv differ diff --git a/precompile/binaries/stdlib/guid.mv b/precompile/binaries/stdlib/guid.mv index d061b091..1b532b8d 100644 Binary files a/precompile/binaries/stdlib/guid.mv and b/precompile/binaries/stdlib/guid.mv differ diff --git a/precompile/binaries/stdlib/hex.mv b/precompile/binaries/stdlib/hex.mv index 9a733391..ac7d6c0e 100644 Binary files a/precompile/binaries/stdlib/hex.mv and b/precompile/binaries/stdlib/hex.mv differ diff --git a/precompile/binaries/stdlib/initia_nft.mv b/precompile/binaries/stdlib/initia_nft.mv index 74e59982..b7c9afc7 100644 Binary files a/precompile/binaries/stdlib/initia_nft.mv and b/precompile/binaries/stdlib/initia_nft.mv differ diff --git a/precompile/binaries/stdlib/json.mv b/precompile/binaries/stdlib/json.mv index ba2f4a98..b312aa49 100644 Binary files a/precompile/binaries/stdlib/json.mv and b/precompile/binaries/stdlib/json.mv differ diff --git a/precompile/binaries/stdlib/managed_coin.mv b/precompile/binaries/stdlib/managed_coin.mv index 72fc4864..158500aa 100644 Binary files a/precompile/binaries/stdlib/managed_coin.mv and b/precompile/binaries/stdlib/managed_coin.mv differ diff --git a/precompile/binaries/stdlib/math128.mv b/precompile/binaries/stdlib/math128.mv index d2f98227..99ccb71b 100644 Binary files a/precompile/binaries/stdlib/math128.mv and b/precompile/binaries/stdlib/math128.mv differ diff --git a/precompile/binaries/stdlib/math64.mv b/precompile/binaries/stdlib/math64.mv index c102411e..ec22cfbf 100644 Binary files a/precompile/binaries/stdlib/math64.mv and b/precompile/binaries/stdlib/math64.mv differ diff --git a/precompile/binaries/stdlib/minitswap.mv b/precompile/binaries/stdlib/minitswap.mv index 8ec3b4d6..d39d07c4 100644 Binary files a/precompile/binaries/stdlib/minitswap.mv and b/precompile/binaries/stdlib/minitswap.mv differ diff --git a/precompile/binaries/stdlib/multisig.mv b/precompile/binaries/stdlib/multisig.mv index df6af4e0..175115dc 100644 Binary files a/precompile/binaries/stdlib/multisig.mv and b/precompile/binaries/stdlib/multisig.mv differ diff --git a/precompile/binaries/stdlib/multisig_v2.mv b/precompile/binaries/stdlib/multisig_v2.mv index 50094d4a..b7554715 100644 Binary files a/precompile/binaries/stdlib/multisig_v2.mv and b/precompile/binaries/stdlib/multisig_v2.mv differ diff --git a/precompile/binaries/stdlib/nft.mv b/precompile/binaries/stdlib/nft.mv index 3f72b425..af22c911 100644 Binary files a/precompile/binaries/stdlib/nft.mv and b/precompile/binaries/stdlib/nft.mv differ diff --git a/precompile/binaries/stdlib/object.mv b/precompile/binaries/stdlib/object.mv index f9942a00..dcdaa21c 100644 Binary files a/precompile/binaries/stdlib/object.mv and b/precompile/binaries/stdlib/object.mv differ diff --git a/precompile/binaries/stdlib/object_code_deployment.mv b/precompile/binaries/stdlib/object_code_deployment.mv index ce8f6fdc..08fb72af 100644 Binary files a/precompile/binaries/stdlib/object_code_deployment.mv and b/precompile/binaries/stdlib/object_code_deployment.mv differ diff --git a/precompile/binaries/stdlib/option.mv b/precompile/binaries/stdlib/option.mv index a50f65dc..2e75c7b8 100644 Binary files a/precompile/binaries/stdlib/option.mv and b/precompile/binaries/stdlib/option.mv differ diff --git a/precompile/binaries/stdlib/oracle.mv b/precompile/binaries/stdlib/oracle.mv index 5f332c26..e2da6cda 100644 Binary files a/precompile/binaries/stdlib/oracle.mv and b/precompile/binaries/stdlib/oracle.mv differ diff --git a/precompile/binaries/stdlib/primary_fungible_store.mv b/precompile/binaries/stdlib/primary_fungible_store.mv index bee850f2..d120e781 100644 Binary files a/precompile/binaries/stdlib/primary_fungible_store.mv and b/precompile/binaries/stdlib/primary_fungible_store.mv differ diff --git a/precompile/binaries/stdlib/property_map.mv b/precompile/binaries/stdlib/property_map.mv index bec5d0ef..f9dc5967 100644 Binary files a/precompile/binaries/stdlib/property_map.mv and b/precompile/binaries/stdlib/property_map.mv differ diff --git a/precompile/binaries/stdlib/query.mv b/precompile/binaries/stdlib/query.mv index 5daa782a..380e0006 100644 Binary files a/precompile/binaries/stdlib/query.mv and b/precompile/binaries/stdlib/query.mv differ diff --git a/precompile/binaries/stdlib/royalty.mv b/precompile/binaries/stdlib/royalty.mv index 426a0230..ebc23375 100644 Binary files a/precompile/binaries/stdlib/royalty.mv and b/precompile/binaries/stdlib/royalty.mv differ diff --git a/precompile/binaries/stdlib/secp256k1.mv b/precompile/binaries/stdlib/secp256k1.mv index e94c009e..d290f138 100644 Binary files a/precompile/binaries/stdlib/secp256k1.mv and b/precompile/binaries/stdlib/secp256k1.mv differ diff --git a/precompile/binaries/stdlib/simple_map.mv b/precompile/binaries/stdlib/simple_map.mv index 7bbd4ca8..69147e65 100644 Binary files a/precompile/binaries/stdlib/simple_map.mv and b/precompile/binaries/stdlib/simple_map.mv differ diff --git a/precompile/binaries/stdlib/simple_nft.mv b/precompile/binaries/stdlib/simple_nft.mv index b9a64fe9..20c655e9 100644 Binary files a/precompile/binaries/stdlib/simple_nft.mv and b/precompile/binaries/stdlib/simple_nft.mv differ diff --git a/precompile/binaries/stdlib/soul_bound_token.mv b/precompile/binaries/stdlib/soul_bound_token.mv index d0e96619..5fd76206 100644 Binary files a/precompile/binaries/stdlib/soul_bound_token.mv and b/precompile/binaries/stdlib/soul_bound_token.mv differ diff --git a/precompile/binaries/stdlib/stableswap.mv b/precompile/binaries/stdlib/stableswap.mv index f48bbf64..1ffa7ed2 100644 Binary files a/precompile/binaries/stdlib/stableswap.mv and b/precompile/binaries/stdlib/stableswap.mv differ diff --git a/precompile/binaries/stdlib/staking.mv b/precompile/binaries/stdlib/staking.mv index 6aa96063..bc269458 100644 Binary files a/precompile/binaries/stdlib/staking.mv and b/precompile/binaries/stdlib/staking.mv differ diff --git a/precompile/binaries/stdlib/string.mv b/precompile/binaries/stdlib/string.mv index 302f00c0..f2f46ef1 100644 Binary files a/precompile/binaries/stdlib/string.mv and b/precompile/binaries/stdlib/string.mv differ diff --git a/precompile/binaries/stdlib/string_utils.mv b/precompile/binaries/stdlib/string_utils.mv index 84c7e392..ab190016 100644 Binary files a/precompile/binaries/stdlib/string_utils.mv and b/precompile/binaries/stdlib/string_utils.mv differ diff --git a/precompile/binaries/stdlib/table.mv b/precompile/binaries/stdlib/table.mv index e2aae1c9..67fef3f0 100644 Binary files a/precompile/binaries/stdlib/table.mv and b/precompile/binaries/stdlib/table.mv differ diff --git a/precompile/binaries/stdlib/timestamp.mv b/precompile/binaries/stdlib/timestamp.mv index 688bbbc1..6f06cb8d 100644 Binary files a/precompile/binaries/stdlib/timestamp.mv and b/precompile/binaries/stdlib/timestamp.mv differ diff --git a/precompile/binaries/stdlib/vector.mv b/precompile/binaries/stdlib/vector.mv index 95e8befa..989ab5e2 100644 Binary files a/precompile/binaries/stdlib/vector.mv and b/precompile/binaries/stdlib/vector.mv differ diff --git a/precompile/modules/initia_stdlib/sources/code.move b/precompile/modules/initia_stdlib/sources/code.move index 312f5016..ee551eb8 100644 --- a/precompile/modules/initia_stdlib/sources/code.move +++ b/precompile/modules/initia_stdlib/sources/code.move @@ -46,23 +46,26 @@ module initia_std::code { /// Upgrade policy is not specified. const EUPGRADE_POLICY_UNSPECIFIED: u64 = 0x3; - /// The publish request args are invalid. - const EINVALID_ARGUMENTS: u64 = 0x4; - /// The operation is expected to be executed by chain signer. - const EINVALID_CHAIN_OPERATOR: u64 = 0x5; + const EINVALID_CHAIN_OPERATOR: u64 = 0x4; /// allowed_publishers argument is invalid. - const EINVALID_ALLOWED_PUBLISHERS: u64 = 0x6; + const EINVALID_ALLOWED_PUBLISHERS: u64 = 0x5; /// The module ID is duplicated. - const EDUPLICATE_MODULE_ID: u64 = 0x7; + const EDUPLICATE_MODULE_ID: u64 = 0x6; /// Not the owner of the package registry. - const ENOT_PACKAGE_OWNER: u64 = 0x8; + const ENOT_PACKAGE_OWNER: u64 = 0x7; /// `code_object` does not exist. - const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 0x9; + const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 0x8; + + /// Dependency could not be resolved to any published package. + const EPACKAGE_DEP_MISSING: u64 = 0x9; + + /// A dependency cannot have a weaker upgrade policy. + const EDEP_WEAKER_POLICY: u64 = 0xA; /// Whether a compatibility check should be performed for upgrades. The check only passes if /// a new module has (a) the same public functions (b) for existing resources, no layout change. @@ -98,6 +101,68 @@ module initia_std::code { module_store.total_modules } + // entry public functions + + #[deprecated] + public entry fun publish( + owner: &signer, + _module_ids: vector, // unused + code: vector>, + upgrade_policy: u8 + ) { + publish_v2(owner, code, upgrade_policy); + } + + public entry fun publish_v2( + owner: &signer, code: vector>, upgrade_policy: u8 + ) { + request_publish(signer::address_of(owner), code, upgrade_policy) + } + + /// This function can be called by the chain to set the allowed publishers. + public entry fun set_allowed_publishers( + chain: &signer, allowed_publishers: vector
+ ) acquires ModuleStore { + assert!( + signer::address_of(chain) == @initia_std, + error::permission_denied(EINVALID_CHAIN_OPERATOR) + ); + assert_allowed(&allowed_publishers, @initia_std); + + let module_store = borrow_global_mut(@initia_std); + module_store.allowed_publishers = allowed_publishers; + } + + // public functions + + public fun freeze_code_object( + publisher: &signer, code_object: Object + ) acquires MetadataStore { + let code_object_addr = object::object_address(&code_object); + assert!( + exists(code_object_addr), + error::not_found(ECODE_OBJECT_DOES_NOT_EXIST) + ); + assert!( + object::is_owner(code_object, signer::address_of(publisher)), + error::permission_denied(ENOT_PACKAGE_OWNER) + ); + + let registry = borrow_global_mut(code_object_addr); + let iter = table::iter_mut( + &mut registry.metadata, + option::none(), + option::none(), + 1 + ); + loop { + if (!table::prepare_mut(iter)) { break }; + + let (_, metadata) = table::next_mut(iter); + metadata.upgrade_policy = UPGRADE_POLICY_IMMUTABLE; + } + } + // private functions fun increase_total_modules(num_modules: u64) acquires ModuleStore { @@ -129,7 +194,9 @@ module initia_std::code { ); } - public entry fun init_genesis( + /// This function is called by the genesis session to initialize the metadata store of + /// stdlib modules. + fun init_genesis( chain: &signer, module_ids: vector, allowed_publishers: vector
) acquires ModuleStore { assert!( @@ -159,60 +226,29 @@ module initia_std::code { increase_total_modules(vector::length(&module_ids)); } - public entry fun set_allowed_publishers( - chain: &signer, allowed_publishers: vector
- ) acquires ModuleStore { - assert!( - signer::address_of(chain) == @initia_std, - error::permission_denied(EINVALID_CHAIN_OPERATOR) - ); - assert_allowed(&allowed_publishers, @initia_std); - - let module_store = borrow_global_mut(@initia_std); - module_store.allowed_publishers = allowed_publishers; - } - - public fun freeze_code_object( - publisher: &signer, code_object: Object - ) acquires MetadataStore { - let code_object_addr = object::object_address(&code_object); - assert!( - exists(code_object_addr), - error::not_found(ECODE_OBJECT_DOES_NOT_EXIST) - ); - assert!( - object::is_owner(code_object, signer::address_of(publisher)), - error::permission_denied(ENOT_PACKAGE_OWNER) - ); - - let registry = borrow_global_mut(code_object_addr); - let iter = table::iter_mut( - &mut registry.metadata, - option::none(), - option::none(), - 1 + /// This function is called by the publish session to verify the publish request + /// and to update the upgrade policy of the modules. + fun verify_publish_request( + publisher: &signer, + module_ids: vector, + vec_dependency_addresses: vector>, + vec_dependency_ids: vector>, + upgrade_policy: u8 + ) acquires ModuleStore, MetadataStore { + verify_modules_upgrade_policy(publisher, module_ids, upgrade_policy); + verify_dependencies_upgrade_policy( + vec_dependency_addresses, + vec_dependency_ids, + upgrade_policy ); - loop { - if (!table::prepare_mut(iter)) { break }; - - let (_, metadata) = table::next_mut(iter); - metadata.upgrade_policy = UPGRADE_POLICY_IMMUTABLE; - } } - /// Publishes a package at the given signer's address. The caller must provide package metadata describing the - /// package. - public entry fun publish( - owner: &signer, - module_ids: vector, // 0x1::coin - code: vector>, + /// Verify the upgrade policy of the modules and record the upgrade policy in the metadata store + /// and update the total_modules count. + fun verify_modules_upgrade_policy( + publisher: &signer, module_ids: vector, // 0x1::coin upgrade_policy: u8 ) acquires ModuleStore, MetadataStore { - // Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered. - assert!( - vector::length(&code) == vector::length(&module_ids), - error::invalid_argument(EINVALID_ARGUMENTS) - ); assert_no_duplication(&module_ids); // Check whether arbitrary publish is allowed or not. @@ -223,11 +259,11 @@ module initia_std::code { error::invalid_argument(EUPGRADE_POLICY_UNSPECIFIED) ); - let addr = signer::address_of(owner); + let addr = signer::address_of(publisher); assert_allowed(&module_store.allowed_publishers, addr); if (!exists(addr)) { - move_to(owner, MetadataStore { metadata: table::new() }); + move_to(publisher, MetadataStore { metadata: table::new() }); }; // Check upgradability @@ -271,13 +307,51 @@ module initia_std::code { if (new_modules > 0) { increase_total_modules(new_modules) }; + } + + /// Verify the dependencies upgrade policy have higher policy than the module itself. + /// The function will be called at module publish verification step by session. + fun verify_dependencies_upgrade_policy( + vec_dependency_addresses: vector>, + vec_dependency_ids: vector>, + upgrade_policy: u8 + ) acquires MetadataStore { + while (vector::length(&vec_dependency_addresses) > 0) { + let dependency_addresses = vector::pop_back(&mut vec_dependency_addresses); + let dependency_ids = vector::pop_back(&mut vec_dependency_ids); + + while (vector::length(&dependency_addresses) > 0) { + let dependency_addr = vector::pop_back(&mut dependency_addresses); + let dependency_id = vector::pop_back(&mut dependency_ids); + + assert!( + exists(dependency_addr), + error::not_found(EPACKAGE_DEP_MISSING) + ); + let dependency_metadata_store = + borrow_global(dependency_addr); + + assert!( + table::contains( + &dependency_metadata_store.metadata, dependency_id + ), + error::not_found(EPACKAGE_DEP_MISSING) + ); + let dependency_upgrade_policy = + table::borrow( + &dependency_metadata_store.metadata, dependency_id + ).upgrade_policy; - // Request publish - request_publish(addr, module_ids, code) + assert!( + dependency_upgrade_policy >= upgrade_policy, + error::invalid_argument(EDEP_WEAKER_POLICY) + ); + }; + } } /// Native function to initiate module loading native fun request_publish( - owner: address, expected_modules: vector, code: vector> + owner: address, code: vector>, upgrade_policy: u8 ); } diff --git a/precompile/modules/initia_stdlib/sources/object_code_deployment.move b/precompile/modules/initia_stdlib/sources/object_code_deployment.move index bf11f594..9a6c79b1 100644 --- a/precompile/modules/initia_stdlib/sources/object_code_deployment.move +++ b/precompile/modules/initia_stdlib/sources/object_code_deployment.move @@ -73,18 +73,29 @@ module initia_std::object_code_deployment { object_address: address } + #[deprecated] /// Creates a new object with a unique address derived from the publisher address and the object seed. /// Publishes the code passed in the function to the newly created object. /// The caller must provide package metadata describing the package via `metadata_serialized` and /// the code to be published via `code`. This contains a vector of modules to be deployed on-chain. public entry fun publish( - publisher: &signer, module_ids: vector, code: vector> + publisher: &signer, _module_ids: vector, code: vector> + ) { + publish_v2(publisher, code); + } + + /// Creates a new object with a unique address derived from the publisher address and the object seed. + /// Publishes the code passed in the function to the newly created object. + /// The caller must provide package metadata describing the package via `metadata_serialized` and + /// the code to be published via `code`. This contains a vector of modules to be deployed on-chain. + public entry fun publish_v2( + publisher: &signer, code: vector> ) { let publisher_address = signer::address_of(publisher); let object_seed = object_seed(publisher_address); let constructor_ref = &object::create_named_object(publisher, object_seed); let code_signer = &object::generate_signer(constructor_ref); - code::publish(code_signer, module_ids, code, 1); + code::publish_v2(code_signer, code, 1); event::emit(Publish { object_address: signer::address_of(code_signer) }); diff --git a/precompile/modules/minitia_stdlib/sources/code.move b/precompile/modules/minitia_stdlib/sources/code.move index 351cd859..69d5630b 100644 --- a/precompile/modules/minitia_stdlib/sources/code.move +++ b/precompile/modules/minitia_stdlib/sources/code.move @@ -46,23 +46,26 @@ module minitia_std::code { /// Upgrade policy is not specified. const EUPGRADE_POLICY_UNSPECIFIED: u64 = 0x3; - /// The publish request args are invalid. - const EINVALID_ARGUMENTS: u64 = 0x4; - /// The operation is expected to be executed by chain signer. - const EINVALID_CHAIN_OPERATOR: u64 = 0x5; + const EINVALID_CHAIN_OPERATOR: u64 = 0x4; /// allowed_publishers argument is invalid. - const EINVALID_ALLOWED_PUBLISHERS: u64 = 0x6; + const EINVALID_ALLOWED_PUBLISHERS: u64 = 0x5; /// The module ID is duplicated. - const EDUPLICATE_MODULE_ID: u64 = 0x7; + const EDUPLICATE_MODULE_ID: u64 = 0x6; /// Not the owner of the package registry. - const ENOT_PACKAGE_OWNER: u64 = 0x8; + const ENOT_PACKAGE_OWNER: u64 = 0x7; /// `code_object` does not exist. - const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 0x9; + const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 0x8; + + /// Dependency could not be resolved to any published package. + const EPACKAGE_DEP_MISSING: u64 = 0x9; + + /// A dependency cannot have a weaker upgrade policy. + const EDEP_WEAKER_POLICY: u64 = 0xA; /// Whether a compatibility check should be performed for upgrades. The check only passes if /// a new module has (a) the same public functions (b) for existing resources, no layout change. @@ -98,6 +101,68 @@ module minitia_std::code { module_store.total_modules } + // entry public functions + + #[deprecated] + public entry fun publish( + owner: &signer, + _module_ids: vector, // unused + code: vector>, + upgrade_policy: u8 + ) { + publish_v2(owner, code, upgrade_policy); + } + + public entry fun publish_v2( + owner: &signer, code: vector>, upgrade_policy: u8 + ) { + request_publish(signer::address_of(owner), code, upgrade_policy) + } + + /// This function can be called by the chain to set the allowed publishers. + public entry fun set_allowed_publishers( + chain: &signer, allowed_publishers: vector
+ ) acquires ModuleStore { + assert!( + signer::address_of(chain) == @minitia_std, + error::permission_denied(EINVALID_CHAIN_OPERATOR) + ); + assert_allowed(&allowed_publishers, @minitia_std); + + let module_store = borrow_global_mut(@minitia_std); + module_store.allowed_publishers = allowed_publishers; + } + + // public functions + + public fun freeze_code_object( + publisher: &signer, code_object: Object + ) acquires MetadataStore { + let code_object_addr = object::object_address(&code_object); + assert!( + exists(code_object_addr), + error::not_found(ECODE_OBJECT_DOES_NOT_EXIST) + ); + assert!( + object::is_owner(code_object, signer::address_of(publisher)), + error::permission_denied(ENOT_PACKAGE_OWNER) + ); + + let registry = borrow_global_mut(code_object_addr); + let iter = table::iter_mut( + &mut registry.metadata, + option::none(), + option::none(), + 1 + ); + loop { + if (!table::prepare_mut(iter)) { break }; + + let (_, metadata) = table::next_mut(iter); + metadata.upgrade_policy = UPGRADE_POLICY_IMMUTABLE; + } + } + // private functions fun increase_total_modules(num_modules: u64) acquires ModuleStore { @@ -129,7 +194,9 @@ module minitia_std::code { ); } - public entry fun init_genesis( + /// This function is called by the genesis session to initialize the metadata store of + /// stdlib modules. + fun init_genesis( chain: &signer, module_ids: vector, allowed_publishers: vector
) acquires ModuleStore { assert!( @@ -159,60 +226,29 @@ module minitia_std::code { increase_total_modules(vector::length(&module_ids)); } - public entry fun set_allowed_publishers( - chain: &signer, allowed_publishers: vector
- ) acquires ModuleStore { - assert!( - signer::address_of(chain) == @minitia_std, - error::permission_denied(EINVALID_CHAIN_OPERATOR) - ); - assert_allowed(&allowed_publishers, @minitia_std); - - let module_store = borrow_global_mut(@minitia_std); - module_store.allowed_publishers = allowed_publishers; - } - - public fun freeze_code_object( - publisher: &signer, code_object: Object - ) acquires MetadataStore { - let code_object_addr = object::object_address(&code_object); - assert!( - exists(code_object_addr), - error::not_found(ECODE_OBJECT_DOES_NOT_EXIST) - ); - assert!( - object::is_owner(code_object, signer::address_of(publisher)), - error::permission_denied(ENOT_PACKAGE_OWNER) - ); - - let registry = borrow_global_mut(code_object_addr); - let iter = table::iter_mut( - &mut registry.metadata, - option::none(), - option::none(), - 1 + /// This function is called by the publish session to verify the publish request + /// and to update the upgrade policy of the modules. + fun verify_publish_request( + publisher: &signer, + module_ids: vector, + vec_dependency_addresses: vector>, + vec_dependency_ids: vector>, + upgrade_policy: u8 + ) acquires ModuleStore, MetadataStore { + verify_modules_upgrade_policy(publisher, module_ids, upgrade_policy); + verify_dependencies_upgrade_policy( + vec_dependency_addresses, + vec_dependency_ids, + upgrade_policy ); - loop { - if (!table::prepare_mut(iter)) { break }; - - let (_, metadata) = table::next_mut(iter); - metadata.upgrade_policy = UPGRADE_POLICY_IMMUTABLE; - } } - /// Publishes a package at the given signer's address. The caller must provide package metadata describing the - /// package. - public entry fun publish( - owner: &signer, - module_ids: vector, // 0x1::coin - code: vector>, + /// Verify the upgrade policy of the modules and record the upgrade policy in the metadata store + /// and update the total_modules count. + fun verify_modules_upgrade_policy( + publisher: &signer, module_ids: vector, // 0x1::coin upgrade_policy: u8 ) acquires ModuleStore, MetadataStore { - // Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered. - assert!( - vector::length(&code) == vector::length(&module_ids), - error::invalid_argument(EINVALID_ARGUMENTS) - ); assert_no_duplication(&module_ids); // Check whether arbitrary publish is allowed or not. @@ -223,11 +259,11 @@ module minitia_std::code { error::invalid_argument(EUPGRADE_POLICY_UNSPECIFIED) ); - let addr = signer::address_of(owner); + let addr = signer::address_of(publisher); assert_allowed(&module_store.allowed_publishers, addr); if (!exists(addr)) { - move_to(owner, MetadataStore { metadata: table::new() }); + move_to(publisher, MetadataStore { metadata: table::new() }); }; // Check upgradability @@ -271,13 +307,51 @@ module minitia_std::code { if (new_modules > 0) { increase_total_modules(new_modules) }; + } + + /// Verify the dependencies upgrade policy have higher policy than the module itself. + /// The function will be called at module publish verification step by session. + fun verify_dependencies_upgrade_policy( + vec_dependency_addresses: vector>, + vec_dependency_ids: vector>, + upgrade_policy: u8 + ) acquires MetadataStore { + while (vector::length(&vec_dependency_addresses) > 0) { + let dependency_addresses = vector::pop_back(&mut vec_dependency_addresses); + let dependency_ids = vector::pop_back(&mut vec_dependency_ids); + + while (vector::length(&dependency_addresses) > 0) { + let dependency_addr = vector::pop_back(&mut dependency_addresses); + let dependency_id = vector::pop_back(&mut dependency_ids); + + assert!( + exists(dependency_addr), + error::not_found(EPACKAGE_DEP_MISSING) + ); + let dependency_metadata_store = + borrow_global(dependency_addr); + + assert!( + table::contains( + &dependency_metadata_store.metadata, dependency_id + ), + error::not_found(EPACKAGE_DEP_MISSING) + ); + let dependency_upgrade_policy = + table::borrow( + &dependency_metadata_store.metadata, dependency_id + ).upgrade_policy; - // Request publish - request_publish(addr, module_ids, code) + assert!( + dependency_upgrade_policy >= upgrade_policy, + error::invalid_argument(EDEP_WEAKER_POLICY) + ); + }; + } } /// Native function to initiate module loading native fun request_publish( - owner: address, expected_modules: vector, code: vector> + owner: address, code: vector>, upgrade_policy: u8 ); } diff --git a/precompile/modules/minitia_stdlib/sources/object_code_deployment.move b/precompile/modules/minitia_stdlib/sources/object_code_deployment.move index d34b2e57..a457cd4f 100644 --- a/precompile/modules/minitia_stdlib/sources/object_code_deployment.move +++ b/precompile/modules/minitia_stdlib/sources/object_code_deployment.move @@ -73,18 +73,29 @@ module minitia_std::object_code_deployment { object_address: address } + #[deprecated] /// Creates a new object with a unique address derived from the publisher address and the object seed. /// Publishes the code passed in the function to the newly created object. /// The caller must provide package metadata describing the package via `metadata_serialized` and /// the code to be published via `code`. This contains a vector of modules to be deployed on-chain. public entry fun publish( - publisher: &signer, module_ids: vector, code: vector> + publisher: &signer, _module_ids: vector, code: vector> + ) { + publish_v2(publisher, code); + } + + /// Creates a new object with a unique address derived from the publisher address and the object seed. + /// Publishes the code passed in the function to the newly created object. + /// The caller must provide package metadata describing the package via `metadata_serialized` and + /// the code to be published via `code`. This contains a vector of modules to be deployed on-chain. + public entry fun publish_v2( + publisher: &signer, code: vector> ) { let publisher_address = signer::address_of(publisher); let object_seed = object_seed(publisher_address); let constructor_ref = &object::create_named_object(publisher, object_seed); let code_signer = &object::generate_signer(constructor_ref); - code::publish(code_signer, module_ids, code, 1); + code::publish_v2(code_signer, code, 1); event::emit(Publish { object_address: signer::address_of(code_signer) }); diff --git a/types/bcs.go b/types/bcs.go index adbecfdf..d54f681e 100644 --- a/types/bcs.go +++ b/types/bcs.go @@ -802,11 +802,15 @@ func BcsDeserializeIdentifier(input []byte) (Identifier, error) { type InitiaVMConfig struct { AllowUnstable bool + ScriptCacheCapacity uint64 + ModuleCacheCapacity uint64 } func (obj *InitiaVMConfig) Serialize(serializer serde.Serializer) error { if err := serializer.IncreaseContainerDepth(); err != nil { return err } if err := serializer.SerializeBool(obj.AllowUnstable); err != nil { return err } + if err := serializer.SerializeU64(obj.ScriptCacheCapacity); err != nil { return err } + if err := serializer.SerializeU64(obj.ModuleCacheCapacity); err != nil { return err } serializer.DecreaseContainerDepth() return nil } @@ -824,6 +828,8 @@ func DeserializeInitiaVMConfig(deserializer serde.Deserializer) (InitiaVMConfig, var obj InitiaVMConfig if err := deserializer.IncreaseContainerDepth(); err != nil { return obj, err } if val, err := deserializer.DeserializeBool(); err == nil { obj.AllowUnstable = val } else { return obj, err } + if val, err := deserializer.DeserializeU64(); err == nil { obj.ScriptCacheCapacity = val } else { return obj, err } + if val, err := deserializer.DeserializeU64(); err == nil { obj.ModuleCacheCapacity = val } else { return obj, err } deserializer.DecreaseContainerDepth() return obj, nil }