From ccb33daf6b08b13747761bf0864814678c56f6af Mon Sep 17 00:00:00 2001 From: Kasper Dissing Bargsteen Date: Fri, 15 Sep 2023 13:48:06 +0200 Subject: [PATCH] Move the testing library to rust-SCs repository Also removes the parts of the CI related to the testing library. --- .github/workflows/ci.yaml | 40 +- .gitmodules | 6 +- cargo-concordium/Cargo.toml | 6 +- concordium-base | 1 + concordium-rust-sdk | 1 - concordium-smart-contract-testing/.gitignore | 1 - .../CHANGELOG.md | 47 - concordium-smart-contract-testing/Cargo.toml | 28 - concordium-smart-contract-testing/README.md | 17 - .../src/constants.rs | 49 - .../src/impls.rs | 2304 ----------------- .../src/invocation/impls.rs | 1720 ------------ .../src/invocation/mod.rs | 17 - .../src/invocation/types.rs | 177 -- concordium-smart-contract-testing/src/lib.rs | 111 - .../src/types.rs | 959 ------- .../tests/account-signature-checks.rs | 128 - .../tests/all_new_host_functions.rs | 23 - .../tests/basics.rs | 103 - .../tests/checkpointing.rs | 398 --- .../tests/counter.rs | 102 - .../tests/error_codes.rs | 242 -- .../tests/fallback.rs | 93 - .../tests/helpers/mod.rs | 25 - .../tests/iterator.rs | 67 - .../tests/queries.rs | 710 ----- .../tests/recorder.rs | 71 - .../tests/relaxed_restrictions.rs | 165 -- .../tests/self_balance.rs | 145 -- .../tests/transfer.rs | 123 - .../tests/upgrades.rs | 697 ----- 31 files changed, 12 insertions(+), 8564 deletions(-) create mode 160000 concordium-base delete mode 160000 concordium-rust-sdk delete mode 100644 concordium-smart-contract-testing/.gitignore delete mode 100644 concordium-smart-contract-testing/CHANGELOG.md delete mode 100644 concordium-smart-contract-testing/Cargo.toml delete mode 100644 concordium-smart-contract-testing/README.md delete mode 100644 concordium-smart-contract-testing/src/constants.rs delete mode 100644 concordium-smart-contract-testing/src/impls.rs delete mode 100644 concordium-smart-contract-testing/src/invocation/impls.rs delete mode 100644 concordium-smart-contract-testing/src/invocation/mod.rs delete mode 100644 concordium-smart-contract-testing/src/invocation/types.rs delete mode 100644 concordium-smart-contract-testing/src/lib.rs delete mode 100644 concordium-smart-contract-testing/src/types.rs delete mode 100644 concordium-smart-contract-testing/tests/account-signature-checks.rs delete mode 100644 concordium-smart-contract-testing/tests/all_new_host_functions.rs delete mode 100644 concordium-smart-contract-testing/tests/basics.rs delete mode 100644 concordium-smart-contract-testing/tests/checkpointing.rs delete mode 100644 concordium-smart-contract-testing/tests/counter.rs delete mode 100644 concordium-smart-contract-testing/tests/error_codes.rs delete mode 100644 concordium-smart-contract-testing/tests/fallback.rs delete mode 100644 concordium-smart-contract-testing/tests/helpers/mod.rs delete mode 100644 concordium-smart-contract-testing/tests/iterator.rs delete mode 100644 concordium-smart-contract-testing/tests/queries.rs delete mode 100644 concordium-smart-contract-testing/tests/recorder.rs delete mode 100644 concordium-smart-contract-testing/tests/relaxed_restrictions.rs delete mode 100644 concordium-smart-contract-testing/tests/self_balance.rs delete mode 100644 concordium-smart-contract-testing/tests/transfer.rs delete mode 100644 concordium-smart-contract-testing/tests/upgrades.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7db0c84d..664b3e83 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,10 +16,10 @@ on: - synchronize - ready_for_review paths: - - cargo-concordium/**/* - - concordium-smart-contract-testing/**/* - - rustfmt.toml - - concordium-base + - 'cargo-concordium/**/*.rs' + - 'cargo-concordium/**/*.toml' + - 'rustfmt.toml' + - 'concordium-base' workflow_dispatch: # allows manual trigger @@ -38,7 +38,6 @@ jobs: matrix: build-dir: - 'cargo-concordium' - - 'concordium-smart-contract-testing' steps: - name: Checkout uses: actions/checkout@v3 @@ -54,33 +53,6 @@ jobs: run: | cargo fmt -- --color=always --check - "lint_clippy_libraries": - name: ${{ matrix.build-dir }} lint:clippy - # Don't run on draft pull requests - if: ${{ !github.event.pull_request.draft }} - runs-on: ubuntu-latest - strategy: - matrix: - build-dir: - - 'concordium-smart-contract-testing' - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ env.RUST_CLIPPY }} - override: true - components: clippy - - name: Clippy - working-directory: ${{ matrix.build-dir }} - run: | - git config --global url."https://github.com/".insteadOf "git@github.com:" - cargo clippy --color=always --tests --benches -- -Dclippy::all - # For binaries we additionally commit the lock file. And we want to ensure # that the file is up-to-date. "lint_clippy_binaries": @@ -120,7 +92,6 @@ jobs: matrix: build-dir: - 'cargo-concordium' - - 'concordium-smart-contract-testing' steps: - name: Checkout uses: actions/checkout@v3 @@ -146,7 +117,6 @@ jobs: matrix: build-dir: - 'cargo-concordium' - - 'concordium-smart-contract-testing' steps: - name: Checkout uses: actions/checkout@v3 @@ -160,4 +130,4 @@ jobs: override: true - name: Test working-directory: ${{ matrix.build-dir }} - run: cargo test -- --skip io_tests # Skip the I/O tests in the testing library. + run: cargo test diff --git a/.gitmodules b/.gitmodules index e48adb56..6c2315e1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "concordium-rust-sdk"] - path = concordium-rust-sdk - url = git@github.com:Concordium/concordium-rust-sdk.git +[submodule "concordium-base"] + path = concordium-base + url = git@github.com:Concordium/concordium-base.git diff --git a/cargo-concordium/Cargo.toml b/cargo-concordium/Cargo.toml index 965a442f..db50d4d7 100644 --- a/cargo-concordium/Cargo.toml +++ b/cargo-concordium/Cargo.toml @@ -29,15 +29,15 @@ rand = { version = "=0.7", features = ["small_rng"] } cargo_metadata = "0.15" [dependencies.concordium-wasm] -path = "../concordium-rust-sdk/concordium-base/smart-contracts/wasm-transform" +path = "../concordium-base/smart-contracts/wasm-transform" version = "3.0" [dependencies.concordium-smart-contract-engine] version = "3.0" -path = "../concordium-rust-sdk/concordium-base/smart-contracts/wasm-chain-integration/" +path = "../concordium-base/smart-contracts/wasm-chain-integration/" features = ["display-state"] [dependencies.concordium-contracts-common] version = "8.0" -path = "../concordium-rust-sdk/concordium-base/concordium-contracts-common/concordium-contracts-common" +path = "../concordium-base/concordium-contracts-common/concordium-contracts-common" features = ["derive-serde", "std"] diff --git a/concordium-base b/concordium-base new file mode 160000 index 00000000..90c37d48 --- /dev/null +++ b/concordium-base @@ -0,0 +1 @@ +Subproject commit 90c37d48d918be50096b5f3f518364c2f7f7c1cf diff --git a/concordium-rust-sdk b/concordium-rust-sdk deleted file mode 160000 index 0173cb0d..00000000 --- a/concordium-rust-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0173cb0d952a559873cb6ce87ea9ba65e5fde81b diff --git a/concordium-smart-contract-testing/.gitignore b/concordium-smart-contract-testing/.gitignore deleted file mode 100644 index 03314f77..00000000 --- a/concordium-smart-contract-testing/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock diff --git a/concordium-smart-contract-testing/CHANGELOG.md b/concordium-smart-contract-testing/CHANGELOG.md deleted file mode 100644 index 7e2c529e..00000000 --- a/concordium-smart-contract-testing/CHANGELOG.md +++ /dev/null @@ -1,47 +0,0 @@ -# Changelog - -## Unreleased changes - -- Add functionality for setting the exchange rates and block time of the chain based on queries from an external node. - - Configured via a builder pattern, see `Chain::builder`. -- Add methods to `Chain`: - - `external_query_block` to get the default block used for external queries - - `block_time` to get the block time - - `tick_block_time` to increase the block time by a `Duration` -- Add the following types by re-exporting them from internal crates: - - `BlockHash` - - `Timestamp` which `SlotTime` is an alias of. - - `Duration` - - `Endpoint` -- Add methods to the `Chain` for adding external accounts and contracts and for invoking contracts on an external node. - - See the `Chain` method `contract_invoke_external` for more details. -- Bump minimum supported Rust version to `1.66`. - -## 3.0.0 - -- Support protocol 6 semantics, in particular validation assumes protocol 6 - semantics now, allowing sign extension instructions and new operations for - querying account's public keys, and checking signatures with account's keys. -- Add `AccountSignatures` structure. -- Add an `AccountAccessStructure` to the `Account` structure. This is a breaking - change. Extend the constructors of `Account` to allow constructing accounts - with this access structure. - -## 2.0.0 - -- Include `ContractTraceElement`s for failed contract executions, including internal failures. - - Introduce the enum `DebugTraceElement`, which has information about trace elements, including failed ones and the cause of error, and the energy usage. - - On the `ContractInvokeSuccess` type: - - Change the type of the `trace_elements` field to be `Vec` instead of `Vec`. (breaking change) - - Add a helper method, `effective_trace_elements()`, to retrieve the "effective" trace elements, i.e., elements that were *not* rolled back. - - These are the elements that were previously returned in the `trace_elements` field. - - There is also a version of the method which clones: `effective_trace_elements_cloned()`. - - To migrate existing code, replace `some_update.trace_elements` with `some_update.effective_trace_elements_cloned()`. - - Add a helper method, `rollbacks_occurred()`, to determine whether any internal failures or rollbacks occurred. - - On the `ContractInvokeError` type: - - Include the field `trace_elements` of type `Vec` with the trace elements that were hitherto discarded. (breaking change) - - To migrate, include the new field or use `..` when pattern matching on the type. - -## 1.0.0 - -- The initial release of the library. diff --git a/concordium-smart-contract-testing/Cargo.toml b/concordium-smart-contract-testing/Cargo.toml deleted file mode 100644 index f4bf3fc8..00000000 --- a/concordium-smart-contract-testing/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "concordium-smart-contract-testing" -version = "3.0.0" -edition = "2021" -rust-version = "1.66" -license = "MPL-2.0" -readme = "README.md" -description = "A companion crate to `concordium-std` that supports off-chain end-to-end testing of smart contracts." -homepage = "https://github.com/Concordium/concordium-smart-contract-tools" -repository = "https://github.com/Concordium/concordium-smart-contract-tools" -exclude = ["tests"] # Do not publish tests. - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -concordium_base = {version = "3.0", path = "../concordium-rust-sdk/concordium-base/rust-src/concordium_base"} -concordium-smart-contract-engine = {version = "3.0", path = "../concordium-rust-sdk/concordium-base/smart-contracts/wasm-chain-integration"} -concordium-wasm = {version = "3.0", path = "../concordium-rust-sdk/concordium-base/smart-contracts/wasm-transform"} -concordium-rust-sdk = {version = "3.0", path = "../concordium-rust-sdk"} -tokio = { version = "1.28", features = ["rt-multi-thread", "time"] } -sha2 = "0.10" -anyhow = "1" -thiserror = "1.0" -num-bigint = "0.4" -num-integer = "0.1" - -[dev-dependencies] -rand = "0.7" diff --git a/concordium-smart-contract-testing/README.md b/concordium-smart-contract-testing/README.md deleted file mode 100644 index a2ff49f5..00000000 --- a/concordium-smart-contract-testing/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Concordium Smart Contract Testing - -A library that supports writing integration tests in Rust for Concordium smart -contracts. - -This is a companion to [concordium-std](https://crates.io/crates/concordium-std) -which is used to write smart contracts in Rust. - -### Documentation - -- [docs.rs](https://docs.rs/concordium-smart-contract-testing) - - -### MSRV - -The minimum supported Rust version can be found in the `Cargo.toml` manifest -file. Changes in MSRV will be accompanied by at least a minor version bump. diff --git a/concordium-smart-contract-testing/src/constants.rs b/concordium-smart-contract-testing/src/constants.rs deleted file mode 100644 index 0d18726a..00000000 --- a/concordium-smart-contract-testing/src/constants.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Various constants. - -use concordium_base::base::Energy; - -// Energy constants from Cost.hs in concordium-base. - -/// Cost of querying the account balance from within a smart contract instance. -pub(crate) const CONTRACT_INSTANCE_QUERY_ACCOUNT_BALANCE_COST: Energy = Energy { energy: 200 }; - -/// Cost of querying the contract balance from within a smart contract instance. -pub(crate) const CONTRACT_INSTANCE_QUERY_CONTRACT_BALANCE_COST: Energy = Energy { energy: 200 }; - -/// Cost of querying the current exchange rates from within a smart contract -/// instance. -pub(crate) const CONTRACT_INSTANCE_QUERY_EXCHANGE_RATE_COST: Energy = Energy { energy: 100 }; - -/// Base cost querying account keys. In addition to this cost there is a cost -/// based on the number of returned keys. -pub(crate) const CONTRACT_INSTANCE_QUERY_ACCOUNT_KEYS_BASE_COST: Energy = Energy { energy: 200 }; - -/// Cost of returning the account keys, based on the number of keys. -/// Each key is 32 bytes, and there is a bit of administrative overhead. -pub(crate) fn contract_instance_query_account_keys_return_cost(num_keys: u32) -> Energy { - Energy { - energy: u64::from(num_keys) * 3, - } -} - -/// Cost **in energy** of verification of an ed25519 signature. -/// This should match the cost of -/// [`verify_ed22519_cost`](concordium_smart_contract_engine::constants::verify_ed25519_cost) -/// except the latter is the cost in interpreter energy, and this on is in -/// [`Energy`]. -pub(crate) fn verify_ed25519_energy_cost(num_sigs: u32, message_len: u32) -> Energy { - Energy { - energy: u64::from(num_sigs) * (100 + u64::from(message_len) / 10), - } -} - -/// The base cost of initializing a contract instance to cover administrative -/// costs. Even if no code is run and no instance created. -pub(crate) const INITIALIZE_CONTRACT_INSTANCE_BASE_COST: Energy = Energy { energy: 300 }; - -/// Cost of creating an empty smart contract instance. -pub(crate) const INITIALIZE_CONTRACT_INSTANCE_CREATE_COST: Energy = Energy { energy: 200 }; - -/// The base cost of updating a contract instance to cover administrative -/// costs. Even if no code is run. -pub(crate) const UPDATE_CONTRACT_INSTANCE_BASE_COST: Energy = Energy { energy: 300 }; diff --git a/concordium-smart-contract-testing/src/impls.rs b/concordium-smart-contract-testing/src/impls.rs deleted file mode 100644 index d0664c54..00000000 --- a/concordium-smart-contract-testing/src/impls.rs +++ /dev/null @@ -1,2304 +0,0 @@ -use crate::{ - constants, - invocation::{ChangeSet, EntrypointInvocationHandler, TestConfigurationError}, - types::*, -}; -use anyhow::anyhow; -use concordium_base::{ - base::{AccountThreshold, Energy, InsufficientEnergy}, - constants::MAX_WASM_MODULE_SIZE, - contracts_common::{ - self, AccountAddress, AccountBalance, Address, Amount, ChainMetadata, ContractAddress, - Duration, ExchangeRate, ExchangeRates, ModuleReference, OwnedPolicy, SlotTime, Timestamp, - }, - hashes::BlockHash, - smart_contracts::{ContractEvent, ModuleSource, WasmModule, WasmVersion}, - transactions::{ - self, cost, AccountAccessStructure, InitContractPayload, UpdateContractPayload, - }, -}; -use concordium_rust_sdk::{self as sdk, v2::Endpoint}; -use concordium_smart_contract_engine::{ - v0, - v1::{self, InvokeResponse}, - InterpreterEnergy, -}; -use concordium_wasm::validate::ValidationConfig; -use num_bigint::BigUint; -use num_integer::Integer; -use sdk::types::smart_contracts::InvokeContractResult; -use std::{ - collections::{BTreeMap, BTreeSet}, - future::Future, - path::Path, - sync::Arc, -}; -use tokio::{runtime, time::timeout}; - -/// The timeout duration set for queries with an external node. -const EXTERNAL_NODE_QUERY_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(10); - -/// The timeout duration set for connecting to an external node. -const EXTERNAL_NODE_CONNECT_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(3); - -impl Default for Chain { - fn default() -> Self { Self::new() } -} - -impl ChainParameters { - /// Create a new [`ChainParameters`](Self) where - /// - `block_time` defaults to `0`, - /// - `micro_ccd_per_euro` defaults to `50000 / 1` - /// - `euro_per_energy` defaults to `1 / 50000`. - /// - /// With these exchange rates, one energy costs one microCCD. - pub fn new() -> Self { - Self::new_with_time_and_rates( - Timestamp::from_timestamp_millis(0), - ExchangeRate::new_unchecked(50000, 1), - ExchangeRate::new_unchecked(1, 50000), - ) - .expect("Parameters are in range.") - } - - /// Create a new [`ChainParameters`](Self) with a specified `block_time` - /// where - /// - `micro_ccd_per_euro` defaults to `50000 / 1` - /// - `euro_per_energy` defaults to `1 / 50000`. - pub fn new_with_time(block_time: SlotTime) -> Self { - Self { - block_time, - ..Self::new() - } - } - - /// Create a new [`ChainParameters`](Self) where all the configurable - /// parameters are provided. - /// - /// Returns an error if the exchange rates provided makes one energy cost - /// more than `u64::MAX / 100_000_000_000`. - pub fn new_with_time_and_rates( - block_time: SlotTime, - micro_ccd_per_euro: ExchangeRate, - euro_per_energy: ExchangeRate, - ) -> Result { - // Ensure the exchange rates are within a valid range. - check_exchange_rates(euro_per_energy, micro_ccd_per_euro)?; - - Ok(Self { - block_time, - micro_ccd_per_euro, - euro_per_energy, - }) - } - - /// Helper function for converting [`Energy`] to [`Amount`] using the two - /// [`ExchangeRate`]s `euro_per_energy` and `micro_ccd_per_euro`. - pub fn calculate_energy_cost(&self, energy: Energy) -> Amount { - energy_to_amount(energy, self.euro_per_energy, self.micro_ccd_per_euro) - } -} - -impl ChainBuilder { - /// Create a new [`ChainBuilder`] for constructing the [`Chain`]. - /// - /// Can also be created via the [`Chain::builder`] method. - /// - /// To complete the building process, use [`ChainBuilder::build`], see the - /// example below. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// - /// let chain = ChainBuilder::new() - /// // Use zero or more builder methods, for example: - /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1)) - /// .block_time(Timestamp::from_timestamp_millis(123)) - /// // Then build: - /// .build() - /// .unwrap(); - /// ``` - pub fn new() -> Self { - Self { - external_node_endpoint: None, - external_query_block: None, - micro_ccd_per_euro: None, - micro_ccd_per_euro_from_external: false, - euro_per_energy: None, - euro_per_energy_from_external: false, - block_time: None, - block_time_from_external: false, - } - } - - /// Configure a connection to an external Concordium node. - /// - /// The connection can be used for getting the current exchange rates - /// between CCD, Euro and Energy. - /// - /// # Example - /// - /// ```no_run - /// # use concordium_smart_contract_testing::*; - /// let chain = Chain::builder() - /// .external_node_connection(Endpoint::from_static( - /// "http://node.testnet.concordium.com:20000", - /// )) - /// .build() - /// .unwrap(); - /// ``` - pub fn external_node_connection(mut self, endpoint: impl Into) -> Self { - self.external_node_endpoint = Some(endpoint.into()); - self - } - - /// Configure the block to be used for all external queries. - /// - /// If this is not set, then the last final block will be queried during - /// [`ChainBuilder::build`] and saved, so it can be used for future queries. - /// - /// This can only be used in combination with - /// [`external_node_connection`][Self::external_node_connection]. - /// - /// To view the configured block, see [`Chain::external_query_block`]. - /// - /// # Example - /// - /// ```no_run - /// # use concordium_smart_contract_testing::*; - /// let chain = Chain::builder() - /// .external_node_connection(Endpoint::from_static( - /// "http://node.testnet.concordium.com:20000", - /// )) - /// .external_query_block( - /// "95ff82f26892a2327c3e7ac582224a54d75c367341fbff209bce552d81349eb0" - /// .parse() - /// .unwrap(), - /// ) - /// .build() - /// .unwrap(); - /// ``` - pub fn external_query_block(mut self, query_block: BlockHash) -> Self { - self.external_query_block = Some(query_block); - self - } - - /// Configure the 'microCCD per euro' exchange rate. - /// - /// By default the rate is `50000 / 1`. - /// - /// This cannot be used together with - /// [`ChainBuilder::micro_ccd_per_euro_from_external`]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1)) - /// .build() - /// .unwrap(); - /// ``` - pub fn micro_ccd_per_euro(mut self, exchange_rate: ExchangeRate) -> Self { - self.micro_ccd_per_euro = Some(exchange_rate); - self - } - - /// Configure the 'euro per energy' exchange rate. - /// - /// By default the rate is `1 / 50000`. - /// - /// This cannot be used together with - /// [`ChainBuilder::euro_per_energy_from_external`]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .euro_per_energy(ExchangeRate::new_unchecked(1, 50000)) - /// .build() - /// .unwrap(); - /// ``` - pub fn euro_per_energy(mut self, exchange_rate: ExchangeRate) -> Self { - self.euro_per_energy = Some(exchange_rate); - self - } - - /// Configure the exchange rate between microCCD and euro using the external - /// node connection. - /// - /// This can only be used in combination with - /// [`external_node_connection`][Self::external_node_connection], and it - /// cannot be used together with - /// [`micro_ccd_per_euro`][Self::micro_ccd_per_euro]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .external_node_connection(Endpoint::from_static( - /// "http://node.testnet.concordium.com:20000", - /// )) - /// .micro_ccd_per_euro_from_external() - /// .build() - /// .unwrap(); - /// ``` - pub fn micro_ccd_per_euro_from_external(mut self) -> Self { - self.micro_ccd_per_euro_from_external = true; - self - } - - /// Configure the exchange rate between euro and energy using the external - /// node connection. - /// - /// This can only be used in combination with - /// [`external_node_connection`][Self::external_node_connection], and it - /// cannot be used together with - /// [`euro_per_energy`][Self::euro_per_energy]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .external_node_connection(Endpoint::from_static( - /// "http://node.testnet.concordium.com:20000", - /// )) - /// .euro_per_energy_from_external() - /// .build() - /// .unwrap(); - /// ``` - pub fn euro_per_energy_from_external(mut self) -> Self { - self.euro_per_energy_from_external = true; - self - } - - /// Configure the block time. - /// - /// By default the block time is `0`. - /// - /// This cannot be used in combination with - /// [`ChainBuilder::block_time_from_external`]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .block_time(Timestamp::from_timestamp_millis(1687440701000)) - /// .build() - /// .unwrap(); - /// ``` - pub fn block_time(mut self, block_time: Timestamp) -> Self { - self.block_time = Some(block_time); - self - } - - /// Configure the block time using the external node connection. - /// - /// This can only be used in combination with - /// [`external_node_connection`][Self::external_node_connection], and it - /// cannot be used together with - /// [`block_time`][Self::block_time]. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let chain = ChainBuilder::new() - /// .external_node_connection(Endpoint::from_static( - /// "http://node.testnet.concordium.com:20000", - /// )) - /// .block_time_from_external() - /// .build() - /// .unwrap(); - /// ``` - pub fn block_time_from_external(mut self) -> Self { - self.block_time_from_external = true; - self - } - - /// Build the [`Chain`] with the configured options. - /// - /// # Example - /// ``` - /// # use concordium_smart_contract_testing::*; - /// - /// let chain = Chain::builder() - /// // Use zero or more builder methods, for example: - /// .euro_per_energy(ExchangeRate::new_unchecked(1, 50000)) - /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1)) - /// // Then build: - /// .build() - /// .unwrap(); - /// ``` - pub fn build(self) -> Result { - // Create the chain with default parameters. - let mut chain = Chain::new(); - - // Setup the external node connection if provided. This also forwards and sets - // the external query block. - if let Some(endpoint) = self.external_node_endpoint { - chain.setup_external_node_connection(endpoint, self.external_query_block)?; - } - - // Check for conflicting exchange rate configurations. - if self.micro_ccd_per_euro.is_some() && self.micro_ccd_per_euro_from_external { - return Err(ChainBuilderError::ConflictingMicroCCDPerEuro); - } - if self.euro_per_energy.is_some() && self.euro_per_energy_from_external { - return Err(ChainBuilderError::ConflictingEuroPerEnergy); - } - - // Set the exchange rates via an external query. - if self.micro_ccd_per_euro_from_external || self.euro_per_energy_from_external { - let exchange_rates = chain.get_exchange_rates_via_external_node()?; - if self.micro_ccd_per_euro_from_external { - chain.parameters.micro_ccd_per_euro = exchange_rates.micro_ccd_per_euro; - } - if self.euro_per_energy_from_external { - chain.parameters.euro_per_energy = exchange_rates.euro_per_energy; - } - } - - // Set the exchange rates directly. - if let Some(micro_ccd_per_euro) = self.micro_ccd_per_euro { - chain.parameters.micro_ccd_per_euro = micro_ccd_per_euro; - } - if let Some(euro_per_energy) = self.euro_per_energy { - chain.parameters.euro_per_energy = euro_per_energy; - } - - // Check the exchange rates and return early if they are invalid. - check_exchange_rates( - chain.parameters.euro_per_energy, - chain.parameters.micro_ccd_per_euro, - )?; - - match (self.block_time, self.block_time_from_external) { - (Some(_), true) => return Err(ChainBuilderError::ConflictingBlockTime), - (Some(block_time), false) => { - chain.parameters.block_time = block_time; - } - (None, true) => { - chain.set_block_time_via_external_node()?; - } - (None, false) => (), - } - - // Replace the default block time if provided. - if let Some(block_time) = self.block_time { - chain.parameters.block_time = block_time; - } - - Ok(chain) - } -} - -impl Default for ChainBuilder { - fn default() -> Self { Self::new() } -} - -impl Chain { - /// Get a [`ChainBuilder`] for constructing a new [`Chain`] with a builder - /// pattern. - /// - /// See the [`ChainBuilder`] for more details. - pub fn builder() -> ChainBuilder { ChainBuilder::new() } - - /// Create a new [`Chain`](Self) where all the configurable parameters are - /// provided. - /// - /// Returns an error if the exchange rates provided makes one energy cost - /// more than `u64::MAX / 100_000_000_000`. - /// - /// *For more configuration options and flexibility, use the builder - /// pattern. See [`Chain::builder`].* - pub fn new_with_time_and_rates( - block_time: SlotTime, - micro_ccd_per_euro: ExchangeRate, - euro_per_energy: ExchangeRate, - ) -> Result { - Ok(Self { - parameters: ChainParameters::new_with_time_and_rates( - block_time, - micro_ccd_per_euro, - euro_per_energy, - )?, - accounts: BTreeMap::new(), - modules: BTreeMap::new(), - contracts: BTreeMap::new(), - next_contract_index: 0, - external_node_connection: None, - }) - } - - /// Create a new [`Chain`](Self) with a specified `block_time` where - /// - `micro_ccd_per_euro` defaults to `50000 / 1` - /// - `euro_per_energy` defaults to `1 / 50000`. - /// - /// *For more configuration options and flexibility, use the builder - /// pattern. See [`Chain::builder`].* - pub fn new_with_time(block_time: SlotTime) -> Self { - Self { - parameters: ChainParameters::new_with_time(block_time), - ..Self::new() - } - } - - /// Create a new [`Chain`](Self) where - /// - `block_time` defaults to `0`, - /// - `micro_ccd_per_euro` defaults to `50000 / 1` - /// - `euro_per_energy` defaults to `1 / 50000`. - /// - /// With these exchange rates, one energy costs one microCCD. - /// - /// *For more configuration options and flexibility, use the builder - /// pattern. See [`Chain::builder`].* - pub fn new() -> Self { - Self::new_with_time_and_rates( - Timestamp::from_timestamp_millis(0), - ExchangeRate::new_unchecked(50000, 1), - ExchangeRate::new_unchecked(1, 50000), - ) - .expect("Rates known to be within range.") - } - - /// Helper function for converting [`Energy`] to [`Amount`] using the two - /// [`ExchangeRate`]s `euro_per_energy` and `micro_ccd_per_euro`. - pub fn calculate_energy_cost(&self, energy: Energy) -> Amount { - self.parameters.calculate_energy_cost(energy) - } - - /// Get the state of the contract if it exists in the [`Chain`](Self). - pub fn get_contract(&self, address: ContractAddress) -> Option<&Contract> { - self.contracts.get(&address) - } - - /// Get the the module if it exists in the [`Chain`](Self). - pub fn get_module(&self, module: ModuleReference) -> Option<&ContractModule> { - self.modules.get(&module) - } - - /// Get the state of the account if it exists in the [`Chain`](Self). - /// Account addresses that are aliases will return the same account. - pub fn get_account(&self, address: AccountAddress) -> Option<&Account> { - self.accounts.get(&address.into()) - } - - /// Deploy a smart contract module. - /// - /// The `WasmModule` can be loaded from disk with either - /// [`module_load_v1`] or [`module_load_v1_raw`]. - /// - /// Parameters: - /// - `signer`: the signer with a number of keys, which affects the cost. - /// - `sender`: the sender account. - /// - `module`: the v1 wasm module. - pub fn module_deploy_v1( - &mut self, - signer: Signer, - sender: AccountAddress, - wasm_module: WasmModule, - ) -> Result { - // For maintainers: - // - // This function does not correspond exactly to what happens in the node. - // There a user is also expected to give a max energy bound and the failures are - // slightly different. There it is possible to fail with "out of energy" - // error whereas here we only fail with "insufficient funds" if the user does - // not have enough CCD to pay. - // - // If users use our tools to deploy modules the costs are calculated for them so - // that deployment should never fail with out of energy. Not requiring energy - // provides a more ergonomic experience. - let Ok(sender_account) = self.accounts - .get_mut(&sender.into()) - .ok_or(AccountDoesNotExist { address: sender }) else { - // Ensure sender account exists. - return Err(ModuleDeployError { - kind: ModuleDeployErrorKind::SenderDoesNotExist(AccountDoesNotExist { - address: sender, - }), - energy_used: 0.into(), - transaction_fee: Amount::zero(), - }); - }; - - // Only v1 modules are supported in this testing library. - // This error case does not exist in the node, so we don't need to match a - // specific cost. We charge 0 for it. - if wasm_module.version != WasmVersion::V1 { - return Err(ModuleDeployError { - kind: ModuleDeployErrorKind::UnsupportedModuleVersion( - wasm_module.version, - ), - energy_used: 0.into(), - transaction_fee: Amount::zero(), - }); - } - - let parameters = &self.parameters; - let check_header_energy = { - // +1 for the tag, +8 for size and version - let payload_size = 1 - + 8 - + wasm_module.source.size() - + transactions::construct::TRANSACTION_HEADER_SIZE; - cost::base_cost(payload_size, signer.num_keys) - }; - - // Calculate the deploy module cost. - let deploy_module_energy = cost::deploy_module(wasm_module.source.size()); - let energy_used = check_header_energy + deploy_module_energy; - let transaction_fee = parameters.calculate_energy_cost(energy_used); - - // Check if the account has sufficient balance to cover the transaction fee. - // This fee corresponds to the energy_reserved that our tools calculate when - // sending the transaction to the node. The account is not charged in the node - // unless it has sufficient balance to pay for the full deployment (and thus all - // the energy). - if sender_account.balance.available() < transaction_fee { - return Err(ModuleDeployError { - kind: ModuleDeployErrorKind::InsufficientFunds, - energy_used: 0.into(), - transaction_fee: Amount::zero(), - }); - }; - - // Charge the account. - sender_account.balance.total -= transaction_fee; - - // Construct the artifact. - let artifact = - match concordium_wasm::utils::instantiate_with_metering::( - ValidationConfig::V1, - &v1::ConcordiumAllowedImports { - support_upgrade: true, - }, - wasm_module.source.as_ref(), - ) { - Ok(artifact) => artifact, - Err(err) => { - return Err(ModuleDeployError { - kind: ModuleInvalidError(err).into(), - energy_used, - transaction_fee, - }) - } - }; - - let module_reference: ModuleReference = wasm_module.get_module_ref(); - - // Ensure module hasn't been deployed before. - if self.modules.contains_key(&module_reference) { - return Err(ModuleDeployError { - kind: ModuleDeployErrorKind::DuplicateModule(module_reference), - energy_used, - transaction_fee, - }); - } - self.modules.insert(module_reference, ContractModule { - // we follow protocol 6 semantics, and don't count the custom section size towards - // module size. - size: wasm_module - .source - .size() - .saturating_sub(artifact.custom_sections_size), - artifact: Arc::new(artifact.artifact), - }); - Ok(ModuleDeploySuccess { - module_reference, - energy_used, - transaction_fee, - }) - } - - /// Initialize a contract. - /// - /// **Parameters:** - /// - `signer`: the signer with a number of keys, which affects the cost. - /// - `sender`: The account paying for the transaction. Will also become - /// the owner of the contract created. - /// - `energy_reserved`: Amount of energy reserved for executing the init - /// method. - /// - `payload`: - /// - `amount`: The initial balance of the contract. Subtracted from the - /// `sender` account. - /// - `mod_ref`: The reference to the a module that has already been - /// deployed. - /// - `init_name`: Name of the contract to initialize. - /// - `param`: Parameter provided to the init method. - pub fn contract_init( - &mut self, - signer: Signer, - sender: AccountAddress, - energy_reserved: Energy, - payload: InitContractPayload, - ) -> Result { - let mut remaining_energy = energy_reserved; - if !self.account_exists(sender) { - return Err(self.convert_to_init_error( - ContractInitErrorKind::SenderDoesNotExist(AccountDoesNotExist { address: sender }), - energy_reserved, - remaining_energy, - )); - } - - let res = self.contract_init_worker( - signer, - sender, - energy_reserved, - payload, - &mut remaining_energy, - ); - - let (res, transaction_fee) = match res { - Ok(s) => { - let transaction_fee = s.transaction_fee; - (Ok(s), transaction_fee) - } - Err(e) => { - let err = self.convert_to_init_error(e, energy_reserved, remaining_energy); - let transaction_fee = err.transaction_fee; - (Err(err), transaction_fee) - } - }; - - // Charge the account. - self.account_mut(sender) - .expect("existence already checked") - .balance - .total -= transaction_fee; - res - } - - /// Helper method for initializing contracts, which does most of the actual - /// work. - /// - /// The main reason for splitting init in two is to have this method return - /// early if it runs out of energy. `contract_init` will then always - /// ensure to charge the account for the energy used. - fn contract_init_worker( - &mut self, - signer: Signer, - sender: AccountAddress, - energy_reserved: Energy, - payload: InitContractPayload, - remaining_energy: &mut Energy, - ) -> Result { - // Get the account and check that it has sufficient balance to pay for the - // reserved_energy and amount. - let account_info = self.account(sender)?; - - let energy_reserved_cost = self.parameters.calculate_energy_cost(energy_reserved); - - // Check that the account can pay for the reserved energy. - if account_info.balance.available() < energy_reserved_cost { - return Err(ContractInitErrorKind::InsufficientFunds); - } - - // Compute the base cost for checking the transaction header. - let check_header_cost = { - // 1 byte for the tag. - let transaction_size = - transactions::construct::TRANSACTION_HEADER_SIZE + 1 + payload.size() as u64; - transactions::cost::base_cost(transaction_size, signer.num_keys) - }; - - // Charge the header cost. - remaining_energy.tick_energy(check_header_cost)?; - - // Ensure that the parameter has a valid size. - if payload.param.as_ref().len() > contracts_common::constants::MAX_PARAMETER_LEN { - return Err(ContractInitErrorKind::ParameterTooLarge); - } - - // Charge the base cost for initializing a contract. - remaining_energy.tick_energy(constants::INITIALIZE_CONTRACT_INSTANCE_BASE_COST)?; - - // Check that the account also has enough funds to pay for the amount (in - // addition to the reserved energy). - if account_info.balance.available() < energy_reserved_cost + payload.amount { - return Err(ContractInitErrorKind::AmountTooLarge); - } - - // Lookup module. - let module = self.contract_module(payload.mod_ref)?; - let lookup_cost = lookup_module_cost(&module); - - // Charge the cost for looking up the module. - remaining_energy.tick_energy(lookup_cost)?; - - // Ensure the module contains the provided init name. - let init_name = payload.init_name.as_contract_name().get_chain_name(); - if module.artifact.export.get(init_name).is_none() { - return Err(ContractInitErrorKind::ContractNotPresentInModule { - name: payload.init_name, - }); - } - - // Sender policies have a very bespoke serialization in - // order to allow skipping portions of them in smart contracts. - let sender_policies = { - let mut out = Vec::new(); - account_info - .policy - .serial_for_smart_contract(&mut out) - .expect("Writing to a vector should succeed."); - out - }; - - // Construct the context. - let init_ctx = v0::InitContext { - metadata: ChainMetadata { - slot_time: self.parameters.block_time, - }, - init_origin: sender, - sender_policies, - }; - // Initialize contract - // We create an empty loader as no caching is used in this testing library - // presently, so the loader is not used. - let mut loader = v1::trie::Loader::new(&[][..]); - - let energy_given_to_interpreter = to_interpreter_energy(*remaining_energy); - let res = v1::invoke_init( - module.artifact, - init_ctx, - v1::InitInvocation { - amount: payload.amount, - init_name, - parameter: payload.param.as_ref(), - energy: energy_given_to_interpreter, - }, - false, // We only support protocol P5 and up, so no limiting. - loader, - ); - // Handle the result - match res { - Ok(v1::InitResult::Success { - logs, - return_value: _, /* Ignore return value for now, since our tools do not support - * it for inits, currently. */ - remaining_energy: remaining_interpreter_energy, - mut state, - }) => { - let contract_address = self.create_contract_address(); - let mut collector = v1::trie::SizeCollector::default(); - - let persisted_state = state.freeze(&mut loader, &mut collector); - - // Perform the subtraction in the more finegrained (*1000) `InterpreterEnergy`, - // and *then* convert to `Energy`. This is how it is done in the node, and if we - // swap the operations, it can result in a small discrepancy due to rounding. - let energy_used_in_interpreter = from_interpreter_energy( - energy_given_to_interpreter.saturating_sub(remaining_interpreter_energy), - ); - remaining_energy.tick_energy(energy_used_in_interpreter)?; - - // Charge one energy per stored state byte. - let energy_for_state_storage = Energy::from(collector.collect()); - remaining_energy.tick_energy(energy_for_state_storage)?; - - // Charge the constant cost for initializing a contract. - remaining_energy - .tick_energy(constants::INITIALIZE_CONTRACT_INSTANCE_CREATE_COST)?; - - let contract = Contract { - module_reference: payload.mod_ref, - contract_name: payload.init_name, - state: persisted_state, - owner: sender, - self_balance: payload.amount, - address: contract_address, - }; - - // Save the contract. - self.contracts.insert(contract_address, contract); - - // Subtract the amount from the invoker. - self.account_mut(sender) - .expect("Account known to exist") - .balance - .total -= payload.amount; - - let energy_used = energy_reserved - *remaining_energy; - let transaction_fee = self.parameters.calculate_energy_cost(energy_used); - - Ok(ContractInitSuccess { - contract_address, - events: contract_events_from_logs(logs), - energy_used, - transaction_fee, - }) - } - Ok(v1::InitResult::Reject { - reason, - return_value, - remaining_energy: remaining_interpreter_energy, - }) => { - let energy_used_in_interpreter = from_interpreter_energy( - energy_given_to_interpreter.saturating_sub(remaining_interpreter_energy), - ); - remaining_energy.tick_energy(energy_used_in_interpreter)?; - Err(ContractInitErrorKind::ExecutionError { - error: InitExecutionError::Reject { - reason, - return_value, - }, - }) - } - Ok(v1::InitResult::Trap { - error, - remaining_energy: remaining_interpreter_energy, - }) => { - let energy_used_in_interpreter = from_interpreter_energy( - energy_given_to_interpreter.saturating_sub(remaining_interpreter_energy), - ); - remaining_energy.tick_energy(energy_used_in_interpreter)?; - Err(ContractInitErrorKind::ExecutionError { - error: InitExecutionError::Trap { - error: error.into(), - }, - }) - } - Ok(v1::InitResult::OutOfEnergy) => { - *remaining_energy = Energy::from(0); - Err(ContractInitErrorKind::ExecutionError { - error: InitExecutionError::OutOfEnergy, - }) - } - Err(error) => Err(ContractInitErrorKind::ExecutionError { - error: InitExecutionError::Trap { - error: error.into(), - }, - }), - } - } - - /// Helper method that handles contract invocation. - /// - /// *Preconditions:* - /// - `invoker` exists. - /// - `sender` exists. - /// - `invoker` has sufficient balance to pay for `energy_reserved`. - fn contract_invocation_worker( - &self, - invoker: AccountAddress, - sender: Address, - energy_reserved: Energy, - amount_reserved_for_energy: Amount, - payload: UpdateContractPayload, - remaining_energy: &mut Energy, - ) -> Result<(InvokeResponse, ChangeSet, Vec), ContractInvokeError> { - // Check if the contract to invoke exists. - if !self.contract_exists(payload.address) { - return Err(self.convert_to_invoke_error( - ContractDoesNotExist { - address: payload.address, - } - .into(), - Vec::new(), - energy_reserved, - *remaining_energy, - )); - } - - // Ensure that the parameter has a valid size. - if payload.message.as_ref().len() > contracts_common::constants::MAX_PARAMETER_LEN { - return Err(self.convert_to_invoke_error( - ContractInvokeErrorKind::ParameterTooLarge, - Vec::new(), - energy_reserved, - *remaining_energy, - )); - } - - // Check that the invoker has sufficient funds to pay for amount (in addition to - // the energy reserved, which is already checked). - if self - .account(invoker) - .expect("Precondition violation: must already exist") - .balance - .available() - < amount_reserved_for_energy + payload.amount - { - return Err(self.convert_to_invoke_error( - ContractInvokeErrorKind::AmountTooLarge, - Vec::new(), - energy_reserved, - *remaining_energy, - )); - } - - let mut contract_invocation = EntrypointInvocationHandler { - changeset: ChangeSet::new(), - remaining_energy, - energy_reserved, - chain: self, - reserved_amount: amount_reserved_for_energy, - invoker, - // Starts at 1 since 0 is the "initial state" of all contracts in the current - // transaction. - next_contract_modification_index: 1, - }; - - match contract_invocation.invoke_entrypoint(invoker, sender, payload) { - Ok((result, trace_elements)) => { - Ok((result, contract_invocation.changeset, trace_elements)) - } - Err(err) => Err(self.convert_to_invoke_error( - err.into(), - Vec::new(), - energy_reserved, - *remaining_energy, - )), - } - } - - fn contract_invocation_process_response( - &self, - result: InvokeResponse, - trace_elements: Vec, - energy_reserved: Energy, - remaining_energy: Energy, - state_changed: bool, - ) -> Result { - match result { - v1::InvokeResponse::Success { new_balance, data } => { - let energy_used = energy_reserved - remaining_energy; - let transaction_fee = self.parameters.calculate_energy_cost(energy_used); - Ok(ContractInvokeSuccess { - trace_elements, - energy_used, - transaction_fee, - return_value: data.unwrap_or_default(), - state_changed, - new_balance, - }) - } - v1::InvokeResponse::Failure { kind } => Err(self.convert_to_invoke_error( - ContractInvokeErrorKind::ExecutionError { failure_kind: kind }, - trace_elements, - energy_reserved, - remaining_energy, - )), - } - } - - /// Update a contract by calling one of its entrypoints. - /// - /// If successful, all changes will be saved. - /// - /// **Parameters:** - /// - `signer`: a [`Signer`] with a number of keys. The number of keys - /// affects the cost of the transaction. - /// - `invoker`: the account paying for the transaction. - /// - `sender`: the sender of the message, can be an account or contract. - /// For top-level invocations, such as those caused by sending a contract - /// update transaction on the chain, the `sender` is always the - /// `invoker`. Here we provide extra freedom for testing invocations - /// where the sender differs. - /// - `energy_reserved`: the maximum energy that can be used in the update. - /// - `payload`: The data detailing which contract and receive method to - /// call etc. - pub fn contract_update( - &mut self, - signer: Signer, - invoker: AccountAddress, - sender: Address, - energy_reserved: Energy, - payload: UpdateContractPayload, - ) -> Result { - // Ensure the sender exists. - if !self.address_exists(sender) { - // This situation never happens on the chain since to send a message the sender - // is verified upfront. So what we do here is custom behaviour, and we reject - // without consuming any energy. - return Err(ContractInvokeError { - energy_used: Energy::from(0), - transaction_fee: Amount::zero(), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::SenderDoesNotExist(sender), - }); - } - - // Ensure the invoker exists. - let Ok(account_info) = self.account(invoker) else { - return Err(ContractInvokeError { - energy_used: Energy::from(0), - transaction_fee: Amount::zero(), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::InvokerDoesNotExist( - AccountDoesNotExist { address: invoker }, - ), - }); - }; - - // Compute the base cost for checking the transaction header. - let check_header_cost = { - // 1 byte for the tag. - let transaction_size = - transactions::construct::TRANSACTION_HEADER_SIZE + 1 + payload.size() as u64; - transactions::cost::base_cost(transaction_size, signer.num_keys) - }; - - // Charge the header cost. - let mut remaining_energy = - energy_reserved - .checked_sub(check_header_cost) - .ok_or(ContractInvokeError { - energy_used: Energy::from(0), - transaction_fee: Amount::zero(), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::OutOfEnergy, - })?; - - let invoker_amount_reserved_for_nrg = - self.parameters.calculate_energy_cost(energy_reserved); - - // Ensure the account has sufficient funds to pay for the energy. - if account_info.balance.available() < invoker_amount_reserved_for_nrg { - let energy_used = energy_reserved - remaining_energy; - return Err(ContractInvokeError { - energy_used, - transaction_fee: self.parameters.calculate_energy_cost(energy_used), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::InsufficientFunds, - }); - } - - let contract_address = payload.address; - let res = self.contract_invocation_worker( - invoker, - sender, - energy_reserved, - invoker_amount_reserved_for_nrg, - payload, - &mut remaining_energy, - ); - let res = match res { - Ok((result, changeset, trace_elements)) => { - // Charge energy for contract storage. Or return an error if out - // of energy. - let state_changed = if matches!(result, v1::InvokeResponse::Success { .. }) { - let res = changeset.persist( - &mut remaining_energy, - contract_address, - &mut self.accounts, - &mut self.contracts, - ); - res.map_err(|_| self.invocation_out_of_energy_error(energy_reserved))? - } else { - // An error occurred, so state hasn't changed. - false - }; - self.contract_invocation_process_response( - result, - trace_elements, - energy_reserved, - remaining_energy, - state_changed, - ) - } - Err(e) => Err(e), - }; - - let transaction_fee = match &res { - Ok(s) => s.transaction_fee, - Err(e) => e.transaction_fee, - }; - // Charge for execution. - self.account_mut(invoker) - .expect("existence already checked") - .balance - .total -= transaction_fee; - res - } - - /// Invoke a contract by calling an entrypoint. - /// - /// Similar to [`Chain::contract_update`](Self::contract_update) except that - /// all changes are discarded afterwards. Typically used for "view" - /// functions. - /// - /// **Parameters:** - /// - `invoker`: the account used as invoker. Since this isn't a - /// transaction, it won't be charged. - /// - `sender`: the sender. Can be either a contract address or an account - /// address. - /// - `energy_reserved`: the maximum energy that can be used in the update. - /// - `payload`: The data detailing which contract and receive method to - /// call etc. - pub fn contract_invoke( - &self, - invoker: AccountAddress, - sender: Address, - energy_reserved: Energy, - payload: UpdateContractPayload, - ) -> Result { - // Ensure the sender exists. - if !self.address_exists(sender) { - return Err(ContractInvokeError { - energy_used: Energy::from(0), - transaction_fee: Amount::zero(), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::SenderDoesNotExist(sender), - }); - } - - let Some(account_info) = self.accounts.get(&invoker.into()) else { - return Err(ContractInvokeError { - energy_used: Energy::from(0), - transaction_fee: Amount::zero(), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::InvokerDoesNotExist( - AccountDoesNotExist { address: invoker }, - ), - }); - }; - - let invoker_amount_reserved_for_nrg = - self.parameters.calculate_energy_cost(energy_reserved); - - if account_info.balance.available() < invoker_amount_reserved_for_nrg { - let energy_used = Energy::from(0); - return Err(ContractInvokeError { - energy_used, - transaction_fee: self.parameters.calculate_energy_cost(energy_used), - trace_elements: Vec::new(), - kind: ContractInvokeErrorKind::InsufficientFunds, - }); - } - - let mut remaining_energy = energy_reserved; - - let contract_address = payload.address; - - let res = self.contract_invocation_worker( - invoker, - sender, - energy_reserved, - invoker_amount_reserved_for_nrg, - payload, - &mut remaining_energy, - ); - match res { - Ok((result, changeset, trace_elements)) => { - // Charge energy for contract storage. Or return an error if out - // of energy. - let state_changed = if matches!(result, v1::InvokeResponse::Success { .. }) { - changeset - .collect_energy_for_state(&mut remaining_energy, contract_address) - .map_err(|_| self.invocation_out_of_energy_error(energy_reserved))? - } else { - // An error occurred, so state hasn't changed. - false - }; - self.contract_invocation_process_response( - result, - trace_elements, - energy_reserved, - remaining_energy, - state_changed, - ) - } - Err(e) => Err(e), - } - } - - /// Invoke an external contract entrypoint. - /// - /// Similar to [`Chain::contract_invoke`](Self::contract_invoke) except that - /// it invokes/dry runs a contract on the external node. - /// - /// **Parameters:** - /// - `invoker`: the account used as invoker. - /// - The account must exist on the connected node. - /// - `sender`: the sender, can also be a contract. - /// - The sender must exist on the connected node. - /// - `energy_reserved`: the maximum energy that can be used in the update. - /// - `payload`: The data detailing which contract and receive method to - /// call etc. - /// - `block`: The block in which the invocation will be simulated, as if - /// it was at the end of the block. If `None` is provided, the - /// `external_query_block` is used instead. - /// - /// # Example: - /// - /// ```no_run - /// # use concordium_smart_contract_testing::*; - /// let mut chain = Chain::builder() - /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000")) - /// .build() - /// .unwrap(); - /// - /// // Set up an external contract. - /// let external_contract = - /// chain.add_external_contract(ContractAddress::new(1010, 0)).unwrap(); - /// - /// // Set up an external account. - /// let external_acc = - /// chain.add_external_account(" - /// 3U4sfVSqGG6XK8g6eho2qRYtnHc4MWJBG1dfxdtPGbfHwFxini".parse().unwrap()). - /// unwrap(); - /// - /// let res = chain.contract_invoke_external( - /// Some(ExternalAddress::Account(external_acc)), - /// 10000.into(), - /// InvokeExternalContractPayload { - /// amount: Amount::zero(), - /// address: external_contract, - /// receive_name: - /// OwnedReceiveName::new_unchecked("my_contract.view".to_string()), - /// message: OwnedParameter::empty(), - /// }, - /// None, - /// ); - /// ``` - pub fn contract_invoke_external( - &self, - sender: Option, - energy_reserved: Energy, - payload: InvokeExternalContractPayload, - block: Option, - ) -> Result { - let connection = self.external_node_connection().unwrap(); - - // Make the invocation. - let invoke_result: InvokeContractResult = - connection.with_client(block, |block_identifier, mut client| async move { - let invoke_result = client - .invoke_instance( - block_identifier, - &sdk::types::smart_contracts::ContractContext { - invoker: sender.map(|ext_addr| ext_addr.to_address()), - contract: payload.address.address, - amount: payload.amount, - method: payload.receive_name, - parameter: payload.message, - energy: energy_reserved, - }, - ) - .await? - .response; - Ok::<_, ExternalNodeError>(invoke_result) - })?; - - // Convert the result. - match invoke_result { - InvokeContractResult::Success { - return_value, - events, - used_energy, - } => Ok(ContractInvokeExternalSuccess { - trace_elements: events, - energy_used: used_energy, - return_value: return_value.map(|rv| rv.value).unwrap_or_default(), - }), - InvokeContractResult::Failure { - return_value, - reason, - used_energy, - } => Err(ContractInvokeExternalError::Failure { - reason, - energy_used: used_energy, - return_value: return_value.map(|rv| rv.value).unwrap_or_default(), - }), - } - } - - /// Create an account. - /// - /// If an account with a matching address already exists this method will - /// replace it and return the old account. - /// - /// Note that if the first 29-bytes of an account are identical, then - /// they are *considered aliases* on each other in all methods. - /// See the example below: - /// - /// ``` - /// # use concordium_smart_contract_testing::*; - /// let mut chain = Chain::new(); - /// let acc = AccountAddress([ - /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /// 0, 0, - /// ]); - /// let acc_alias = AccountAddress([ - /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - /// 2, 3, // Only last three bytes differ. - /// ]); - /// - /// chain.create_account(Account::new(acc, Amount::from_ccd(123))); - /// assert_eq!( - /// chain.account_balance_available(acc_alias), // Using the alias for lookup. - /// Some(Amount::from_ccd(123)) - /// ); - /// ``` - pub fn create_account(&mut self, account: Account) -> Option { - self.accounts.insert(account.address.into(), account) - } - - /// Add an external account from a connected external node. - /// - /// If the account exists on the external node at the time of the - /// `external_query_block`, then an [`ExernalAccountAddress`] is returned. - /// The address can be used with [`Chain::contract_invoke_external`]. - /// Otherwise, an error is returned. - /// - /// Barring external node errors, the method is idempotent, and so it can be - /// called multiple times with the same effect. - pub fn add_external_account( - &mut self, - address: AccountAddress, - ) -> Result { - let connection = self.external_node_connection_mut()?; - - let external_addr = - connection.with_client(None, |block_identifier, mut client| async move { - // Try to get the account info to verify the existence of the account, but - // discard the result. - client - .get_account_info( - &sdk::v2::AccountIdentifier::Address(address), - block_identifier, - ) - .await?; - Ok::<_, ExternalNodeError>(ExternalAccountAddress { address }) - })?; - - connection.accounts.insert(external_addr); - - Ok(external_addr) - } - - /// Add an external contract from a connected external node. - /// - /// If the contract exists on the external node at the time of the - /// `external_query_block`, then an [`ExernalContractAddress`] is returned. - /// The address can be used with [`Chain::contract_invoke_external`]. - /// Otherwise, an error is returned. - /// - /// Barring external node errors, the method is idempotent, and so it can be - /// called multiple times with the same effect. - pub fn add_external_contract( - &mut self, - address: ContractAddress, - ) -> Result { - let connection = self.external_node_connection_mut()?; - - let external_addr = - connection.with_client(None, |block_identifier, mut client| async move { - // Try to get the contract instance info to verify the existence of the - // contract, but discard the result. - client.get_instance_info(address, block_identifier).await?; - Ok::<_, ExternalNodeError>(ExternalContractAddress { address }) - })?; - - connection.contracts.insert(external_addr); - - Ok(external_addr) - } - - /// Create a contract address by giving it the next available index. - fn create_contract_address(&mut self) -> ContractAddress { - let index = self.next_contract_index; - let subindex = 0; - self.next_contract_index += 1; - ContractAddress::new(index, subindex) - } - - /// Returns the balance of an account if it exists. - pub fn account_balance(&self, address: AccountAddress) -> Option { - self.accounts.get(&address.into()).map(|ai| ai.balance) - } - - /// Returns the available balance of an account if it exists. - pub fn account_balance_available(&self, address: AccountAddress) -> Option { - self.accounts - .get(&address.into()) - .map(|ai| ai.balance.available()) - } - - /// Returns the balance of an contract if it exists. - pub fn contract_balance(&self, address: ContractAddress) -> Option { - self.contracts.get(&address).map(|ci| ci.self_balance) - } - - /// Helper method for looking up part of the state of a smart contract, - /// which is a key-value store. - pub fn contract_state_lookup(&self, address: ContractAddress, key: &[u8]) -> Option> { - let mut loader = v1::trie::Loader::new(&[][..]); - self.contracts.get(&address)?.state.lookup(&mut loader, key) - } - - /// Return a clone of the [`ContractModule`] (which has an `Arc` around the - /// artifact so cloning is cheap). - fn contract_module( - &self, - module_ref: ModuleReference, - ) -> Result { - let module = self.modules.get(&module_ref).ok_or(ModuleDoesNotExist { - module_reference: module_ref, - })?; - Ok(module.clone()) - } - - /// Returns an immutable reference to an [`Account`]. - pub fn account(&self, address: AccountAddress) -> Result<&Account, AccountDoesNotExist> { - self.accounts - .get(&address.into()) - .ok_or(AccountDoesNotExist { address }) - } - - /// Returns a mutable reference to [`Account`]. - fn account_mut( - &mut self, - address: AccountAddress, - ) -> Result<&mut Account, AccountDoesNotExist> { - self.accounts - .get_mut(&address.into()) - .ok_or(AccountDoesNotExist { address }) - } - - /// Check whether an [`Account`] exists. - pub fn account_exists(&self, address: AccountAddress) -> bool { - self.accounts.contains_key(&address.into()) - } - - /// Check whether a [`Contract`] exists. - pub fn contract_exists(&self, address: ContractAddress) -> bool { - self.contracts.contains_key(&address) - } - - /// Check whether an object with the [`Address`] exists. - /// - /// That is, if it is an account address, whether the account exists, - /// and if it is a contract address, whether the contract exists. - fn address_exists(&self, address: Address) -> bool { - match address { - Address::Account(acc) => self.account_exists(acc), - Address::Contract(contr) => self.contract_exists(contr), - } - } - - /// Convert a [`ContractInvokeErrorKind`] to a - /// [`ContractInvokeError`] by calculating the `energy_used` and - /// `transaction_fee`. - /// - /// If the `kind` is an out of energy, then `0` is used instead of the - /// `remaining_energy` parameter, as it will likely not be `0` due to short - /// circuiting during execution. - fn convert_to_invoke_error( - &self, - kind: ContractInvokeErrorKind, - trace_elements: Vec, - energy_reserved: Energy, - remaining_energy: Energy, - ) -> ContractInvokeError { - let remaining_energy = if matches!(kind, ContractInvokeErrorKind::OutOfEnergy) { - 0.into() - } else { - remaining_energy - }; - let energy_used = energy_reserved - remaining_energy; - let transaction_fee = self.parameters.calculate_energy_cost(energy_used); - ContractInvokeError { - energy_used, - transaction_fee, - trace_elements, - kind, - } - } - - /// Construct a [`ContractInvokeErrorKind`] of the `OutOfEnergy` kind with - /// the energy and transaction fee fields based on the `energy_reserved` - /// parameter. - fn invocation_out_of_energy_error(&self, energy_reserved: Energy) -> ContractInvokeError { - self.convert_to_invoke_error( - ContractInvokeErrorKind::OutOfEnergy, - Vec::new(), - energy_reserved, - Energy::from(0), - ) - } - - /// Convert a [`ContractInitErrorKind`] to a - /// [`ContractInitError`] by calculating the `energy_used` and - /// `transaction_fee`. - fn convert_to_init_error( - &self, - kind: ContractInitErrorKind, - energy_reserved: Energy, - remaining_energy: Energy, - ) -> ContractInitError { - let energy_used = energy_reserved - remaining_energy; - let transaction_fee = self.parameters.calculate_energy_cost(energy_used); - ContractInitError { - energy_used, - transaction_fee, - kind, - } - } - - /// Try to set the exchange rates on the chain. - /// - /// Will fail if they result in the cost of one energy being larger than - /// `u64::MAX / 100_000_000_000`. - pub fn set_exchange_rates( - &mut self, - micro_ccd_per_euro: ExchangeRate, - euro_per_energy: ExchangeRate, - ) -> Result<(), ExchangeRateError> { - // Ensure the exchange rates are within a valid range. - check_exchange_rates(euro_per_energy, micro_ccd_per_euro)?; - self.parameters.micro_ccd_per_euro = micro_ccd_per_euro; - self.parameters.euro_per_energy = euro_per_energy; - Ok(()) - } - - /// Get the microCCD per euro and euro per energy exchange rates by querying - /// an external node using the external query block. - fn get_exchange_rates_via_external_node(&self) -> Result { - let connection = self.external_node_connection()?; - - // Get the values from the external node. - connection.with_client(None, |block_identifier, mut client| async move { - let (euro_per_energy, micro_ccd_per_euro) = match client - .get_block_chain_parameters(block_identifier) - .await? - .response - { - sdk::v2::ChainParameters::V0(p) => (p.euro_per_energy, p.micro_ccd_per_euro), - sdk::v2::ChainParameters::V1(p) => (p.euro_per_energy, p.micro_ccd_per_euro), - sdk::v2::ChainParameters::V2(p) => (p.euro_per_energy, p.micro_ccd_per_euro), - }; - Ok(ExchangeRates { - euro_per_energy, - micro_ccd_per_euro, - }) - }) - } - - /// Tick the block time on the [`Chain`] by a [`Duration`]. - /// - /// Returns an error if ticking causes the block time to overflow. - /// - /// # Example - /// - /// ``` - /// # use concordium_smart_contract_testing::*; - /// - /// // Block time defaults to 0. - /// let mut chain = Chain::new(); - /// - /// // Increase block time by 123 milliseconds. - /// chain.tick_block_time(Duration::from_millis(123)).unwrap(); - /// - /// // Block time has now increased. - /// assert_eq!(chain.block_time(), Timestamp::from_timestamp_millis(123)); - /// ``` - pub fn tick_block_time(&mut self, duration: Duration) -> Result<(), BlockTimeOverflow> { - self.parameters.block_time = self - .parameters - .block_time - .checked_add(duration) - .ok_or(BlockTimeOverflow)?; - Ok(()) - } - - /// Set the block time by querying the external node. - /// - /// The default query block is always used. - /// - /// The external node must be setup prior to this call via the method - /// [`Chain::setup_external_node_connection`], otherwise an error is - /// returned. - fn set_block_time_via_external_node(&mut self) -> Result<(), ExternalNodeError> { - let connection = self.external_node_connection_mut()?; - - // Get the timestamp in milliseconds. - let timestamp = - connection.with_client(None, |block_identifier, mut client| async move { - Ok(client - .get_block_info(block_identifier) - .await? - .response - .block_slot_time - .timestamp_millis() as u64) // The node never returns - // timestamps < 0, so it is safe - // to cast it to `u64`. - })?; - // Update the block time. - self.parameters.block_time = Timestamp::from_timestamp_millis(timestamp); - - Ok(()) - } - - /// Set up a connection to an external Concordium node. - /// - /// This method also queries the block info for one of two reasons: - /// 1) If `query_block` is provided, its existence is checked. - /// 2) Otherwise, the last final block is queried to get its blockhash which - /// will be saved in [`ExternalNodeConnection`]. - fn setup_external_node_connection( - &mut self, - endpoint: Endpoint, - query_block: Option, - ) -> Result<(), SetupExternalNodeError> { - // Create the Tokio runtime. This should never fail, unless nested runtimes are - // created. - let runtime = runtime::Builder::new_multi_thread() - // Enable time, so timeouts can be used. - .enable_time() - // Enable I/O, so networking and other types of calls are possible. - .enable_io() - .build() - .expect("Internal error: Could not create Tokio runtime."); - - // A future for getting the client. Executed below. - let get_client = async { - // Try to create the client, which also checks that the connection is valid. - let client = sdk::v2::Client::new(endpoint).await?; - Ok::(client) - }; - - // A future for checking the block. - let get_block_info = |mut client: sdk::v2::Client| async move { - let block_identifier = if let Some(query_block) = query_block { - sdk::v2::BlockIdentifier::Given(query_block) - } else { - sdk::v2::BlockIdentifier::LastFinal - }; - - let block_hash = match client.get_block_info(block_identifier).await { - Ok(res) => res.block_hash, - Err(sdk::v2::QueryError::NotFound) => { - return Err(SetupExternalNodeError::QueryBlockDoesNotExist { - // It should never be possible to get `NotFound` when querying `LastFinal`, - // and so, the `query_block` must be `Some`. - query_block: query_block.expect( - "Internal error: Got `QueryError::NotFound` for when querying last \ - final block.", - ), - }); - } - Err(sdk::v2::QueryError::RPCError(error)) => { - return Err(SetupExternalNodeError::CannotCheckQueryBlockExistence { error }) - } - }; - Ok(block_hash) - }; - - // Get the client synchronously by blocking until the async returns. - let (client, checked_query_block) = runtime.block_on(async { - let client = timeout(EXTERNAL_NODE_CONNECT_TIMEOUT, get_client) - .await - .map_err(|_| SetupExternalNodeError::ConnectTimeout)??; - let checked_query_block = - timeout(EXTERNAL_NODE_QUERY_TIMEOUT, get_block_info(client.clone())) - .await - .map_err(|_| SetupExternalNodeError::CheckQueryBlockTimeout)??; - Ok::<_, SetupExternalNodeError>((client, checked_query_block)) - })?; - - // Set or replace the node connection. - self.external_node_connection = Some(ExternalNodeConnection { - client, - runtime, - query_block: checked_query_block, - accounts: BTreeSet::new(), - contracts: BTreeSet::new(), - }); - - Ok(()) - } - - /// Try to get a mutable reference to [`ExternalNodeConnection`] or return - /// an error. - /// - /// The connection is only available, if the [`Chain`] has been set up with - /// an external node connection via - /// [`ChainBuilder::external_node_connection`] in the [`ChainBuilder`]. - fn external_node_connection_mut( - &mut self, - ) -> Result<&mut ExternalNodeConnection, ExternalNodeNotConfigured> { - match &mut self.external_node_connection { - None => Err(ExternalNodeNotConfigured), - Some(data) => Ok(data), - } - } - - /// Try to get an immutable reference to [`ExternalNodeConnection`] or - /// return an error. - /// - /// The connection is only available, if the [`Chain`] has been set up with - /// an external node connection via - /// [`ChainBuilder::external_node_connection`] in the [`ChainBuilder`]. - fn external_node_connection( - &self, - ) -> Result<&ExternalNodeConnection, ExternalNodeNotConfigured> { - match &self.external_node_connection { - None => Err(ExternalNodeNotConfigured), - Some(data) => Ok(data), - } - } - - /// Return the current microCCD per euro exchange rate. - pub fn micro_ccd_per_euro(&self) -> ExchangeRate { self.parameters.micro_ccd_per_euro } - - /// Return the current euro per energy exchange rate. - pub fn euro_per_energy(&self) -> ExchangeRate { self.parameters.euro_per_energy } - - /// Return the current block time. - pub fn block_time(&self) -> Timestamp { self.parameters.block_time } - - /// Return the block used for external queries by default. - /// - /// The block can be set with [`ChainBuilder::external_query_block`] when - /// building the [`Chain`]. - /// - /// This method returns an error if the external node has not been - /// configured. - pub fn external_query_block(&self) -> Result { - self.external_node_connection().map(|conn| conn.query_block) - } -} - -impl ExternalNodeConnection { - /// Execute an async task with the [`sdk::v2::Client`]. - /// - /// If a block is provided, it will be used for the query. Otherwise, it - /// will use the default query block. - /// - /// If the task takes longer than [`EXTERNAL_NODE_TIMEOUT_DURATION`] then - /// the connection times out and an [`Err(ExternalNodeError::Timeout)`] is - /// returned. - /// - /// *This method cannot be nested, as that will cause a panic.* - fn with_client( - &self, - block: Option, - f: F, - ) -> Result - where - F: FnOnce(sdk::v2::BlockIdentifier, sdk::v2::Client) -> Fut, - Fut: Future>, { - // Get the block identifier, either using the provided block or the default - // query block. - let block_identifier = if let Some(block) = block { - sdk::v2::BlockIdentifier::Given(block) - } else { - sdk::v2::BlockIdentifier::Given(self.query_block) - }; - // Clone the client so it can be moved to the async block. - let client = self.client.clone(); - // Run the future and timeout if it takes too long. - self.runtime.block_on(async move { - timeout(EXTERNAL_NODE_QUERY_TIMEOUT, f(block_identifier, client)) - .await - .map_err(|_| ExternalNodeError::QueryTimeout)? - }) - } -} - -impl Account { - /// Create new [`Account`](Self) with the provided account policy and keys. - pub fn new_with_policy_and_keys( - address: AccountAddress, - balance: AccountBalance, - policy: OwnedPolicy, - keys: AccountAccessStructure, - ) -> Self { - Self { - balance, - policy, - address, - keys, - } - } - - /// Create new [`Account`](Self) with the provided account keys and balance. - /// See [`new`][Self::new] for what the default policy is. - pub fn new_with_keys( - address: AccountAddress, - balance: AccountBalance, - keys: AccountAccessStructure, - ) -> Self { - Self { - balance, - policy: Self::empty_policy(), - address, - keys, - } - } - - /// Create new [`Account`](Self) with the provided account policy. - /// The account keys are initialized with an [`AccountAccessStructure`] - /// with a threshold of 1, and no keys. So it is impossible to verify any - /// signatures with the access structure. - pub fn new_with_policy( - address: AccountAddress, - balance: AccountBalance, - policy: OwnedPolicy, - ) -> Self { - Self::new_with_policy_and_keys(address, balance, policy, AccountAccessStructure { - threshold: AccountThreshold::try_from(1u8).expect("1 is a valid threshold."), - keys: BTreeMap::new(), - }) - } - - /// Create a new [`Account`](Self) with the provided balance and a default - /// account policy and default account access structure. - /// - /// See [`new`][Self::new] for what the default policy is. - pub fn new_with_balance(address: AccountAddress, balance: AccountBalance) -> Self { - Self::new_with_policy(address, balance, Self::empty_policy()) - } - - /// Create new [`Account`](Self) with the provided total balance. - /// - /// The `policy` will have: - /// - `identity_provider`: 0, - /// - `created_at`: unix epoch, - /// - `valid_to`: unix epoch + `u64::MAX` milliseconds, - /// - `items`: none, - /// - /// The account keys are initialized with an [`AccountAccessStructure`] - /// with a threshold of 1, and no keys. So it is impossible to verify any - /// signatures with the access structure. - /// - /// The [`AccountBalance`] will be created with the provided - /// `total_balance`. - pub fn new(address: AccountAddress, total_balance: Amount) -> Self { - Self::new_with_policy( - address, - AccountBalance { - total: total_balance, - staked: Amount::zero(), - locked: Amount::zero(), - }, - Self::empty_policy(), - ) - } - - /// Helper for creating an empty policy. - /// - /// It has identity provider `0`, no items, and is valid from unix epoch - /// until unix epoch + u64::MAX milliseconds. - fn empty_policy() -> OwnedPolicy { - OwnedPolicy { - identity_provider: 0, - created_at: Timestamp::from_timestamp_millis(0), - valid_to: Timestamp::from_timestamp_millis(u64::MAX), - items: Vec::new(), - } - } -} - -/// Load a raw wasm module, i.e. one **without** the prefix of 4 version -/// bytes and 4 module length bytes. -/// The module still has to be a valid V1 smart contract module. -pub fn module_load_v1_raw(module_path: impl AsRef) -> Result { - let module_path = module_path.as_ref(); - // To avoid reading a giant file, we open the file for reading, check its size - // and then load the contents. - let (mut reader, metadata) = std::fs::File::open(module_path) - .and_then(|reader| reader.metadata().map(|metadata| (reader, metadata))) - .map_err(|e| ModuleLoadError { - path: module_path.to_path_buf(), - kind: e.into(), - })?; - if metadata.len() > MAX_WASM_MODULE_SIZE.into() { - return Err(ModuleLoadError { - path: module_path.to_path_buf(), - kind: ModuleLoadErrorKind::ReadModule( - anyhow!("Maximum size of a Wasm module is {}", MAX_WASM_MODULE_SIZE).into(), - ), - }); - } - // We cannot deserialize directly to [`ModuleSource`] as it expects the first - // four bytes to be the length, which it isn't for this raw file. - let mut buffer = Vec::new(); - std::io::Read::read_to_end(&mut reader, &mut buffer).map_err(|e| ModuleLoadError { - path: module_path.to_path_buf(), - kind: ModuleLoadErrorKind::OpenFile(e), /* This is unlikely to happen, since - * we already opened it. */ - })?; - Ok(WasmModule { - version: WasmVersion::V1, - source: ModuleSource::from(buffer), - }) -} - -/// Load a v1 wasm module as it is output from `cargo concordium build`, -/// i.e. **including** the prefix of 4 version bytes and 4 module length -/// bytes. -pub fn module_load_v1(module_path: impl AsRef) -> Result { - let module_path = module_path.as_ref(); - // To avoid reading a giant file, we just open the file for reading and then - // parse it as a wasm module, which checks the length up front. - let mut reader = std::fs::File::open(module_path).map_err(|e| ModuleLoadError { - path: module_path.to_path_buf(), - kind: e.into(), - })?; - let module: WasmModule = - concordium_base::common::from_bytes(&mut reader).map_err(|e| ModuleLoadError { - path: module_path.to_path_buf(), - kind: ModuleLoadErrorKind::ReadModule(e.into()), - })?; - if module.version != WasmVersion::V1 { - return Err(ModuleLoadError { - path: module_path.to_path_buf(), - kind: ModuleLoadErrorKind::UnsupportedModuleVersion(module.version), - }); - } - Ok(module) -} - -impl Signer { - /// Create a signer which always signs with one key. - pub const fn with_one_key() -> Self { Self { num_keys: 1 } } - - /// Create a signer with a non-zero number of keys. - pub const fn with_keys(num_keys: u32) -> Result { - if num_keys == 0 { - return Err(ZeroKeysError); - } - Ok(Self { num_keys }) - } -} - -impl ContractInvokeError { - /// Try to extract the value returned. - /// - /// This only returns `Some` if the contract rejected on its own. - /// As opposed to when it runs out of energy, traps, or similar, in which - /// case there won't be a return value. - pub fn return_value(&self) -> Option<&[u8]> { - match &self.kind { - ContractInvokeErrorKind::ExecutionError { - failure_kind: v1::InvokeFailure::ContractReject { data, .. }, - } => Some(data), - _ => None, - } - } -} - -impl From for ContractInitErrorKind { - #[inline(always)] - fn from(_: InsufficientEnergy) -> Self { Self::OutOfEnergy } -} - -impl From for ContractInvokeErrorKind { - fn from(err: TestConfigurationError) -> Self { - match err { - TestConfigurationError::OutOfEnergy => Self::OutOfEnergy, - TestConfigurationError::BalanceOverflow => Self::BalanceOverflow, - } - } -} - -/// Convert [`Energy`] to [`InterpreterEnergy`] by multiplying by `1000`. -pub(crate) fn to_interpreter_energy(energy: Energy) -> InterpreterEnergy { - InterpreterEnergy::from(energy.energy * 1000) -} - -/// Convert [`InterpreterEnergy`] to [`Energy`] by dividing by `1000`. -pub(crate) fn from_interpreter_energy(interpreter_energy: InterpreterEnergy) -> Energy { - Energy::from(interpreter_energy.energy / 1000) -} - -/// Calculate the energy for looking up a [`ContractModule`]. -pub(crate) fn lookup_module_cost(module: &ContractModule) -> Energy { - // The ratio is from Concordium/Cost.hs::lookupModule - Energy::from(module.size / 50) -} - -/// Calculate the microCCD(mCCD) cost of energy(NRG) using the two exchange -/// rates provided. -/// -/// To find the mCCD/NRG exchange rate: -/// ```markdown -/// euro mCCD euro * mCCD mCCD -/// ---- * ---- = ----------- = ---- -/// NRG euro NRG * euro NRG -/// ``` -/// -/// To convert the `energy` parameter to mCCD (the vertical lines represent -/// ceiling): -/// ```markdown -/// ⌈ mCCD ⌉ ⌈ NRG * mCCD ⌉ -/// | NRG * ---- | = | ---------- | = mCCD -/// | NRG | | NRG | -/// ``` -pub fn energy_to_amount( - energy: Energy, - euro_per_energy: ExchangeRate, - micro_ccd_per_euro: ExchangeRate, -) -> Amount { - let micro_ccd_per_energy_numerator: BigUint = - BigUint::from(euro_per_energy.numerator()) * micro_ccd_per_euro.numerator(); - let micro_ccd_per_energy_denominator: BigUint = - BigUint::from(euro_per_energy.denominator()) * micro_ccd_per_euro.denominator(); - let cost: BigUint = (micro_ccd_per_energy_numerator * energy.energy) - .div_ceil(µ_ccd_per_energy_denominator); - let cost: u64 = u64::try_from(cost).expect( - "Should never overflow since reasonable exchange rates are ensured when constructing the \ - [`Chain`].", - ); - Amount::from_micro_ccd(cost) -} - -/// Helper function that checks the validity of the exchange rates. -/// -/// More specifically, it checks that the cost of one energy is <= `u64::MAX / -/// `100_000_000_000`, which ensures that overflows won't occur. -fn check_exchange_rates( - euro_per_energy: ExchangeRate, - micro_ccd_per_euro: ExchangeRate, -) -> Result<(), ExchangeRateError> { - let micro_ccd_per_energy_numerator: BigUint = - BigUint::from(euro_per_energy.numerator()) * micro_ccd_per_euro.numerator(); - let micro_ccd_per_energy_denominator: BigUint = - BigUint::from(euro_per_energy.denominator()) * micro_ccd_per_euro.denominator(); - let max_allowed_micro_ccd_to_energy = u64::MAX / 100_000_000_000u64; - let micro_ccd_per_energy = - u64::try_from(micro_ccd_per_energy_numerator / micro_ccd_per_energy_denominator) - .map_err(|_| ExchangeRateError)?; - if micro_ccd_per_energy > max_allowed_micro_ccd_to_energy { - return Err(ExchangeRateError); - } - Ok(()) -} - -/// A helper function for converting `[v0::Logs]` into [`Vec`]. -pub(crate) fn contract_events_from_logs(logs: v0::Logs) -> Vec { - logs.logs.into_iter().map(ContractEvent::from).collect() -} - -impl From for ExternalNodeError { - fn from(_: ExternalNodeNotConfigured) -> Self { Self::NotConfigured } -} - -impl From for ChainBuilderError { - fn from(_: ExchangeRateError) -> Self { Self::ExchangeRateError } -} - -#[cfg(test)] -mod tests { - use concordium_base::base::AccountAddressEq; - - use super::*; - - /// A few checks that test whether the function behavior matches its doc - /// comments. - #[test] - fn check_exchange_rates_works() { - let max_allowed_micro_ccd_per_energy = u64::MAX / 100_000_000_000; - check_exchange_rates( - ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy + 1, 1), - ExchangeRate::new_unchecked(1, 1), - ) - .expect_err("should fail"); - - check_exchange_rates( - ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy / 2 + 1, 1), - ExchangeRate::new_unchecked(2, 1), - ) - .expect_err("should fail"); - - check_exchange_rates( - ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy, 1), - ExchangeRate::new_unchecked(1, 1), - ) - .expect("should succeed"); - - check_exchange_rates( - ExchangeRate::new_unchecked(50000, 1), - ExchangeRate::new_unchecked(1, 50000), - ) - .expect("should succeed"); - } - - /// Test that account aliases are seen as one account. - #[test] - fn test_account_aliases() { - let mut chain = Chain::new(); - let acc = AccountAddress([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]); - let acc_alias = AccountAddress([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 2, 3, // Last three bytes can differ for aliases. - ]); - let acc_other = AccountAddress([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 2, 3, 4, // This differs on last four bytes, so it is a different account. - ]); - let acc_eq: AccountAddressEq = acc.into(); - let acc_alias_eq: AccountAddressEq = acc_alias.into(); - let acc_other_eq: AccountAddressEq = acc_other.into(); - - let expected_amount = Amount::from_ccd(10); - let expected_amount_other = Amount::from_ccd(123); - - chain.create_account(Account::new(acc, expected_amount)); - chain.create_account(Account::new(acc_other, expected_amount_other)); - - assert_eq!(acc_eq, acc_alias_eq); - assert_ne!(acc_eq, acc_other_eq); - - assert_eq!(acc_eq.cmp(&acc_alias_eq), std::cmp::Ordering::Equal); - assert_eq!(acc_eq.cmp(&acc_other_eq), std::cmp::Ordering::Less); - - assert_eq!( - chain.account_balance_available(acc_alias), - Some(expected_amount) - ); - assert_eq!( - chain.account_balance_available(acc_other), - Some(expected_amount_other) - ); - } - - /// Test that building a chain with valid parameters succeeds. - /// - /// This test does *not* include external node endpoint, see - /// [`test_chain_builder_with_valid_parameters_and_with_io`] for the reason. - #[test] - fn test_chain_builder_with_valid_parameters() { - let micro_ccd_per_euro = ExchangeRate::new_unchecked(123, 1); - let euro_per_energy = ExchangeRate::new_unchecked(1, 1234); - let block_time = Timestamp::from_timestamp_millis(12345); - let chain = Chain::builder() - .micro_ccd_per_euro(micro_ccd_per_euro) - .euro_per_energy(euro_per_energy) - .block_time(block_time) - .build() - .unwrap(); - - assert_eq!(chain.micro_ccd_per_euro(), micro_ccd_per_euro); - assert_eq!(chain.euro_per_energy(), euro_per_energy); - assert_eq!(chain.block_time(), block_time); - } - - /// Test that building a chain with exchange rates that are out of bounds - /// fails with the right error. - #[test] - fn test_chain_builder_with_invalid_exchange_rates() { - let micro_ccd_per_euro = ExchangeRate::new_unchecked(1000000, 1); - let euro_per_energy = ExchangeRate::new_unchecked(100000000, 1); - let error = Chain::builder() - .micro_ccd_per_euro(micro_ccd_per_euro) - .euro_per_energy(euro_per_energy) - .build() - .unwrap_err(); - - assert!(matches!(error, ChainBuilderError::ExchangeRateError)); - } -} - -/// Tests that use I/O (network) and should therefore *not* be run in the CI. -/// -/// To skip the tests use `cargo test -- --skip io_tests` -#[cfg(test)] -mod io_tests { - use super::*; - use crate::*; - - /// Test that building a chain using the external node parameters works. - #[test] - fn test_chain_builder_with_valid_parameters_and_external_node() { - let chain = Chain::builder() - .micro_ccd_per_euro_from_external() - .euro_per_energy_from_external() - .external_query_block( - "45c53a19cd782a8de981941feb5e0f875cefaba8d2cda958e76f471a4710a797" // A block from testnet. - .parse() - .unwrap(), - ) - .external_node_connection(Endpoint::from_static( - "http://node.testnet.concordium.com:20000", - )) - .block_time_from_external() - .build() - .unwrap(); - - // These values are queried manually from the node. - assert_eq!( - chain.micro_ccd_per_euro(), - ExchangeRate::new_unchecked(10338559485590134784, 79218205097) - ); - assert_eq!( - chain.euro_per_energy(), - ExchangeRate::new_unchecked(1, 50000) - ); - assert_eq!( - chain.block_time(), - Timestamp::from_timestamp_millis(1687865059500) - ); - } - - /// Test that the correct error is returned when an unknown query block is - /// given. - /// - /// The block used is one from mainnet, which is extremely unlikely to also - /// appear on testnet. - #[test] - fn test_block_time_from_unknown_block() { - let err = Chain::builder() - .external_node_connection(Endpoint::from_static( - "http://node.testnet.concordium.com:20000", - )) - .external_query_block( - "4f38c7e63645c59e9bf32f7ca837a029810b21c439f7492c3cebe229a2e3ea07" - .parse() - .unwrap(), // A block from mainnet. - ) - .build() - .unwrap_err(); - assert!(matches!(err, ChainBuilderError::SetupExternalNodeError { - error: SetupExternalNodeError::CannotCheckQueryBlockExistence { .. }, - })); - } - - /// Invoke an external contract and check that it succeeds. Also check that - /// the energy is correct. - #[test] - fn test_contract_invoke_external() { - let mut chain = Chain::builder() - .external_node_connection(Endpoint::from_static( - "http://node.testnet.concordium.com:20000", - )) - .build() - .unwrap(); - - // A CIS-2 contract. - let external_contr = chain - .add_external_contract(ContractAddress::new(5089, 0)) - .unwrap(); - - let external_acc = chain - .add_external_account( - "3U4sfVSqGG6XK8g6eho2qRYtnHc4MWJBG1dfxdtPGbfHwFxini" - .parse() - .unwrap(), - ) - .unwrap(); - - let res = chain - .contract_invoke_external( - Some(external_acc.into()), - 10000.into(), - InvokeExternalContractPayload { - amount: Amount::zero(), - address: external_contr, - receive_name: OwnedReceiveName::new_unchecked("cis2_multi.view".into()), - message: OwnedParameter::empty(), - }, - None, - ) - .unwrap(); - assert_eq!(res.energy_used, 1851.into()); - } -} diff --git a/concordium-smart-contract-testing/src/invocation/impls.rs b/concordium-smart-contract-testing/src/invocation/impls.rs deleted file mode 100644 index a84eabb4..00000000 --- a/concordium-smart-contract-testing/src/invocation/impls.rs +++ /dev/null @@ -1,1720 +0,0 @@ -use super::types::*; -use crate::{ - constants::{self, verify_ed25519_energy_cost}, - impls::{ - contract_events_from_logs, from_interpreter_energy, lookup_module_cost, - to_interpreter_energy, - }, - types::{Account, BalanceError, Contract, ContractModule, TransferError}, - AccountSignatures, DebugTraceElement, ExecutionError, InvokeExecutionError, -}; -use concordium_base::{ - base::{AccountAddressEq, Energy, InsufficientEnergy}, - common, - contracts_common::{ - to_bytes, AccountAddress, AccountBalance, Address, Amount, ChainMetadata, ContractAddress, - ExchangeRates, ModuleReference, OwnedEntrypointName, OwnedReceiveName, - }, - smart_contracts::{ - ContractTraceElement, InstanceUpdatedEvent, OwnedContractName, OwnedParameter, WasmVersion, - }, - transactions::{verify_data_signature, AccountAccessStructure, UpdateContractPayload}, -}; -use concordium_smart_contract_engine::{ - v0, - v1::{self, trie, InvokeResponse}, - ExecResult, InterpreterEnergy, -}; -use concordium_wasm::artifact::{self, CompiledFunction}; -use std::collections::{btree_map, BTreeMap}; - -impl<'a, 'b> EntrypointInvocationHandler<'a, 'b> { - /// Used for handling the *initial* part of invoking an entrypoint. - /// - /// **Preconditions:** - /// - `invoker` exists - /// - `invoker` has sufficient balance to pay for `remaining_energy` - /// - `sender` exists - /// - if the contract (`contract_address`) exists, then its `module` must - /// also exist. - fn invoke_entrypoint_initial( - &mut self, - invoker: AccountAddress, - sender: Address, - payload: UpdateContractPayload, - trace_elements_checkpoint: usize, - ) -> Result< - Result<(v1::ReceiveResult, InvocationData), InvokeResponse>, - TestConfigurationError, - > { - // Charge the base cost for updating a contract. - self.remaining_energy - .tick_energy(constants::UPDATE_CONTRACT_INSTANCE_BASE_COST)?; - - // Move the amount from the sender to the contract, if any. - // And get the new self_balance. - let instance_self_balance = if payload.amount.micro_ccd() > 0 { - let transfer_result = match sender { - Address::Account(sender_account) => self.transfer_from_account_to_contract( - payload.amount, - sender_account, - payload.address, - ), - Address::Contract(sender_contract) => self.transfer_from_contract_to_contract( - payload.amount, - sender_contract, - payload.address, - ), - }; - match transfer_result { - Ok(new_balance_from) => new_balance_from, - Err(transfer_error) => { - let kind = match transfer_error { - TransferError::BalanceError { - error: BalanceError::Overflow, - } => { - // Balance overflows are unrecoverable and short circuit. - return Err(TestConfigurationError::BalanceOverflow); - } - TransferError::BalanceError { - error: BalanceError::Insufficient, - } => v1::InvokeFailure::InsufficientAmount, - TransferError::ToMissing => v1::InvokeFailure::NonExistentContract, - }; - // Return early. - return Ok(Err(v1::InvokeResponse::Failure { kind })); - } - } - } else { - match self.contract_balance(payload.address) { - Some(self_balance) => self_balance, - None => { - // Return early. - return Ok(Err(v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentContract, - })); - } - } - }; - - // Get the instance and artifact. To be used in several places. - let instance = self - .chain - .contracts - .get(&payload.address) - .expect("Contract known to exist at this point"); - let module = self.contract_module(instance); - - // Construct the receive name (or fallback receive name) and ensure its presence - // in the contract. Also returns the contract name and entrypoint name as they - // are needed further down. - let (contract_name, receive_name, entrypoint_name) = { - let borrowed_receive_name = payload.receive_name.as_receive_name(); - let contract_name = borrowed_receive_name.contract_name(); - let owned_contract_name = - OwnedContractName::new_unchecked(format!("init_{}", contract_name)); - let owned_entrypoint_name = borrowed_receive_name.entrypoint_name().to_owned(); - let receive_name = borrowed_receive_name.get_chain_name(); - let fallback_receive_name = format!("{}.", contract_name); - if module.artifact.has_entrypoint(receive_name) { - ( - owned_contract_name, - payload.receive_name, - owned_entrypoint_name, - ) - } else if module - .artifact - .has_entrypoint(fallback_receive_name.as_str()) - { - ( - owned_contract_name, - OwnedReceiveName::new_unchecked(fallback_receive_name), - owned_entrypoint_name, - ) - } else { - // Return early. - return Ok(Err(v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentEntrypoint, - })); - } - }; - - // Subtract the cost of looking up the module - self.remaining_energy - .tick_energy(lookup_module_cost(&module))?; - - // Sender policies have a very bespoke serialization in - // order to allow skipping portions of them in smart contracts. - let sender_policies = { - let mut out = Vec::new(); - let policy = &self - .chain - .account(invoker) - .expect("Precondition violation: invoker must exist.") - .policy; - policy - .serial_for_smart_contract(&mut out) - .expect("Writing to a vector should succeed."); - out - }; - - // Construct the receive context - let receive_ctx = v1::ReceiveContext { - // This should be the entrypoint specified, even if we end up - // calling the fallback entrypoint, as this will be visible to the - // contract via a host function. - entrypoint: entrypoint_name.clone(), - common: v0::ReceiveContext { - metadata: ChainMetadata { - slot_time: self.chain.parameters.block_time, - }, - invoker, - self_address: payload.address, - self_balance: instance_self_balance, - sender, - owner: instance.owner, - sender_policies, - }, - }; - - let mod_idx_before_invoke = self.next_contract_modification_index; - - // Construct the instance state - let mut loader = v1::trie::Loader::new(&[][..]); // An empty loader is fine currently, as we do not use caching in this lib. - let mut mutable_state = self.contract_state(payload.address); - let mut mutable_state = mutable_state.make_fresh_generation(&mut loader); - let inner = mutable_state.get_inner(&mut loader); - let instance_state = v1::InstanceState::new(loader, inner); - - // Get the initial result from invoking receive - let initial_result = self.run_interpreter(|energy| { - v1::invoke_receive( - module.artifact, - receive_ctx, - v1::ReceiveInvocation { - amount: payload.amount, - // This will either be the one provided on the fallback receive name. - receive_name: receive_name.as_receive_name(), - parameter: payload.message.as_ref(), - energy, - }, - instance_state, - v1::ReceiveParams::new_p6(), - ) - })?; - // Set up some data needed for recursively processing the receive until the end, - // i.e. beyond interrupts. - Ok(Ok((initial_result, InvocationData { - sender, - address: payload.address, - contract_name, - entrypoint: entrypoint_name, - parameter: payload.message, - amount: payload.amount, - state: mutable_state, - trace_elements_checkpoint, - next_mod_idx_checkpoint: mod_idx_before_invoke, - mod_idx_before_invoke, - }))) - } - - /// Used for handling contract entrypoint invocations internally. - /// - /// **Preconditions:** - /// - `invoker` exists - /// - `invoker` has sufficient balance to pay for `remaining_energy` - /// - `sender` exists - /// - if the contract (`contract_address`) exists, then its `module` must - /// also exist. - pub(crate) fn invoke_entrypoint( - &mut self, - invoker: AccountAddress, - sender: Address, - payload: UpdateContractPayload, - ) -> Result<(InvokeResponse, Vec), TestConfigurationError> { - let mut stack = Vec::new(); - let mut trace_elements = Vec::new(); - stack.push(Next::Initial { - sender, - payload, - trace_elements_checkpoint: 0, - }); - // Initialized to a dummy value. This will always be set or the function will - // terminate with an Err. - let mut invoke_response: Option = None; - while let Some(invocation_data) = stack.pop() { - let (receive_result, mut invocation_data) = match invocation_data { - Next::Resume { - mut data, - config, - response, - } => { - match response { - Some(response) => { - let receive_result = self.run_interpreter(|energy| { - v1::resume_receive( - config, - response, - energy, - &mut data.state, - false, /* the state never changes on interrupts that have - * immediate handlers */ - // An empty loader is fine currently, as we do not use - // caching in this lib. - v1::trie::Loader::new(&[][..]), - ) - })?; - (receive_result, data) - } - None => { - // we are resuming from a contract call - let (success, call_response) = match invoke_response - .take() - .expect("Response should be available") - { - v1::InvokeResponse::Success { - data: return_value, .. - } => { - let invoke_response = v1::InvokeResponse::Success { - // The balance returned by `invoke_entrypoint` - // is the balance of the contract called. But we - // are interested in the new balance of the caller. - new_balance: self.contract_balance_unchecked(data.address), - data: return_value, - }; - (true, invoke_response) - } - failure => (false, failure), - }; - - // Remove the last state changes if the invocation failed. - let state_changed = if !success { - self.rollback(); - false // We rolled back, so no changes were - // made to this contract. - } else { - let mod_idx_after_invoke = self.modification_index(data.address); - let state_changed = - mod_idx_after_invoke != data.mod_idx_before_invoke; - if state_changed { - // Update the state field with the newest value from the - // changeset. - data.state = self.contract_state(data.address); - } - state_changed - }; - - // Add resume event - let resume_event = ContractTraceElement::Resumed { - address: data.address, - success, - }; - - self.push_regular_trace_element( - &mut trace_elements, - resume_event, - data.entrypoint.clone(), - ); - let receive_result = self.run_interpreter(|energy| { - v1::resume_receive( - config, - call_response, - energy, - &mut data.state, - state_changed, - // An empty loader is fine currently, as we do not use - // caching in this lib. - v1::trie::Loader::new(&[][..]), - ) - })?; - (receive_result, data) - } - } - } - Next::Initial { - sender, - payload, - trace_elements_checkpoint, - } => { - match self.invoke_entrypoint_initial( - invoker, - sender, - payload, - trace_elements_checkpoint, - )? { - Ok(x) => x, - Err(ier) => { - // invocation has failed. No more to do for this call. - // Since no traces were produced we don't have to roll them back. - invoke_response = Some(ier); - continue; - } - } - } - }; - - match receive_result { - v1::ReceiveResult::Success { - logs, - state_changed, - return_value, - remaining_energy, - } => { - // Update the remaining_energy field. - self.update_energy(remaining_energy); - - let update_event = ContractTraceElement::Updated { - data: InstanceUpdatedEvent { - contract_version: WasmVersion::V1, - address: invocation_data.address, - instigator: invocation_data.sender, - amount: invocation_data.amount, - message: invocation_data.parameter.clone(), - receive_name: OwnedReceiveName::construct_unchecked( - invocation_data.contract_name.as_contract_name(), - invocation_data.entrypoint.as_entrypoint_name(), - ), - events: contract_events_from_logs(logs), - }, - }; - // Add update event - self.push_regular_trace_element( - &mut trace_elements, - update_event, - invocation_data.entrypoint.clone(), - ); - - // Save changes to changeset. - if state_changed { - self.save_state_changes( - invocation_data.address, - &mut invocation_data.state, - ); - } - - invoke_response = Some(v1::InvokeResponse::Success { - new_balance: self.contract_balance_unchecked(invocation_data.address), - data: Some(return_value), - }); - } - v1::ReceiveResult::Interrupt { - remaining_energy, - state_changed, - logs, - config, - interrupt, - } => { - // Update the remaining_energy field. - self.update_energy(remaining_energy); - // Create the interrupt event, which will be included for transfers, calls, - // and upgrades, but not for the remaining - // interrupts. - let interrupt_event = ContractTraceElement::Interrupted { - address: invocation_data.address, - events: contract_events_from_logs(logs), - }; - // Remember what state we are in before invoking. - // This is used to report, upon resume, whether the contracts's - // state has changed. - invocation_data.mod_idx_before_invoke = if state_changed { - self.save_state_changes(invocation_data.address, &mut invocation_data.state) - } else { - self.modification_index(invocation_data.address) - }; - match interrupt { - v1::Interrupt::Transfer { to, amount } => { - // Add the interrupt event - self.push_regular_trace_element( - &mut trace_elements, - interrupt_event, - invocation_data.entrypoint.clone(), - ); - - let response = match self.transfer_from_contract_to_account( - amount, - invocation_data.address, - to, - ) { - Ok(new_balance) => v1::InvokeResponse::Success { - new_balance, - data: None, - }, - Err(err) => { - let kind = match err { - TransferError::ToMissing => { - v1::InvokeFailure::NonExistentAccount - } - - TransferError::BalanceError { - error: BalanceError::Insufficient, - } => v1::InvokeFailure::InsufficientAmount, - - TransferError::BalanceError { - error: BalanceError::Overflow, - } => { - // Balance overflows are unrecoverable and short - // circuit. - return Err(TestConfigurationError::BalanceOverflow); - } - }; - v1::InvokeResponse::Failure { kind } - } - }; - - let success = matches!(response, v1::InvokeResponse::Success { .. }); - if success { - // Add transfer event - let transfer_event = ContractTraceElement::Transferred { - from: invocation_data.address, - amount, - to, - }; - self.push_regular_trace_element( - &mut trace_elements, - transfer_event, - invocation_data.entrypoint.clone(), - ); - } - // Add resume event - let resume_event = ContractTraceElement::Resumed { - address: invocation_data.address, - success, - }; - self.push_regular_trace_element( - &mut trace_elements, - resume_event, - invocation_data.entrypoint.clone(), - ); - - self.remaining_energy.tick_energy( - concordium_base::transactions::cost::SIMPLE_TRANSFER, - )?; - - stack.push(Next::Resume { - data: invocation_data, - // the state never changes on transfers - config, - response: Some(response), - }); - } - v1::Interrupt::Call { - address, - parameter, - name, - amount, - } => { - // Add the interrupt event - self.push_regular_trace_element( - &mut trace_elements, - interrupt_event, - invocation_data.entrypoint.clone(), - ); - - match self - .chain - .contracts - .get(&address) - .map(|c| c.contract_name.as_contract_name()) - { - // The contract to call does not exist. - None => { - let response = v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentContract, - }; - // Add resume event - let resume_event = ContractTraceElement::Resumed { - address: invocation_data.address, - success: false, - }; - self.push_regular_trace_element( - &mut trace_elements, - resume_event, - invocation_data.entrypoint.clone(), - ); - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - Some(contract_name) => { - // Make a checkpoint before calling another contract so that we - // may roll back. - self.checkpoint(); - - let receive_name = OwnedReceiveName::construct_unchecked( - contract_name, - name.as_entrypoint_name(), - ); - let message = OwnedParameter::new_unchecked(parameter); - - let sender = Address::Contract(invocation_data.address); - // Remember to continue the current execution after handling the - // call. - stack.push(Next::Resume { - data: invocation_data, - config, - response: None, - }); - - // Add the call to the stack to execute. - stack.push(Next::Initial { - sender, - payload: UpdateContractPayload { - amount, - address, - receive_name, - message, - }, - trace_elements_checkpoint: trace_elements.len(), - }); - } - }; - } - v1::Interrupt::Upgrade { module_ref } => { - // Add the interrupt event. - self.push_regular_trace_element( - &mut trace_elements, - interrupt_event, - invocation_data.entrypoint.clone(), - ); - - // Charge a base cost. - self.remaining_energy - .tick_energy(constants::INITIALIZE_CONTRACT_INSTANCE_BASE_COST)?; - - let response = match self.chain.modules.get(&module_ref) { - None => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::UpgradeInvalidModuleRef, - }, - Some(module) => { - // Charge for the module lookup. - self.remaining_energy - .tick_energy(lookup_module_cost(module))?; - - if module.artifact.export.contains_key( - invocation_data - .contract_name - .as_contract_name() - .get_chain_name(), - ) { - // Update module reference in the changeset. - let old_module_ref = self.save_module_upgrade( - invocation_data.address, - module_ref, - ); - - // Charge for the initialization cost. - self.remaining_energy.tick_energy( - constants::INITIALIZE_CONTRACT_INSTANCE_CREATE_COST, - )?; - - let upgrade_event = ContractTraceElement::Upgraded { - address: invocation_data.address, - from: old_module_ref, - to: module_ref, - }; - - self.push_regular_trace_element( - &mut trace_elements, - upgrade_event, - invocation_data.entrypoint.clone(), - ); - - v1::InvokeResponse::Success { - new_balance: self.contract_balance_unchecked( - invocation_data.address, - ), - data: None, - } - } else { - v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::UpgradeInvalidContractName, - } - } - } - }; - - let success = matches!(response, v1::InvokeResponse::Success { .. }); - let resumed_event = ContractTraceElement::Resumed { - address: invocation_data.address, - success, - }; - self.push_regular_trace_element( - &mut trace_elements, - resumed_event, - invocation_data.entrypoint.clone(), - ); - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - v1::Interrupt::QueryAccountBalance { address } => { - let response = match self.account_balance(address) { - Some(balance) => v1::InvokeResponse::Success { - new_balance: self - .contract_balance_unchecked(invocation_data.address), - data: Some(to_bytes(&balance)), - }, - None => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentAccount, - }, - }; - - self.remaining_energy.tick_energy( - constants::CONTRACT_INSTANCE_QUERY_ACCOUNT_BALANCE_COST, - )?; - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - v1::Interrupt::QueryContractBalance { address } => { - let response = match self.contract_balance(address) { - None => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentContract, - }, - Some(bal) => v1::InvokeResponse::Success { - // Balance of contract querying. Won't change. Notice the - // `data.address`. - new_balance: self - .contract_balance_unchecked(invocation_data.address), - data: Some(to_bytes(&bal)), - }, - }; - - self.remaining_energy.tick_energy( - constants::CONTRACT_INSTANCE_QUERY_CONTRACT_BALANCE_COST, - )?; - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - v1::Interrupt::QueryExchangeRates => { - let exchange_rates = ExchangeRates { - euro_per_energy: self.chain.parameters.euro_per_energy, - micro_ccd_per_euro: self.chain.parameters.micro_ccd_per_euro, - }; - - let response = v1::InvokeResponse::Success { - new_balance: self - .contract_balance_unchecked(invocation_data.address), - data: Some(to_bytes(&exchange_rates)), - }; - - self.remaining_energy.tick_energy( - constants::CONTRACT_INSTANCE_QUERY_EXCHANGE_RATE_COST, - )?; - - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - v1::Interrupt::CheckAccountSignature { address, payload } => { - self.remaining_energy.tick_energy( - constants::CONTRACT_INSTANCE_QUERY_ACCOUNT_KEYS_BASE_COST, - )?; - // Due to borrow checker limitations we don't use self.account_keys here - // since it leads to clashes with the mutable borrow of - // self.remaining_energy below. - let response = - match self.chain.accounts.get(&address.into()).map(|a| &a.keys) { - Some(keys) => { - // attempt to deserialize the payload after - // looking up the account. - // This is the order in the scheduler as - // well, and the order matters - // since the response to the contract is - // different depending on failure kind. - match deserial_signature_and_data_from_contract(&payload) { - Ok((sigs, data)) => { - let num_sigs = sigs.num_signatures(); - self.remaining_energy.tick_energy( - // This cannot overflow on any data that can be - // supplied. - // Data_len will always be at most u32, and the - // number of - // signatures is at most 256*256. - verify_ed25519_energy_cost( - num_sigs, - data.len() as u32, - ), - )?; - if verify_data_signature(keys, data, &sigs.into()) { - v1::InvokeResponse::Success { - new_balance: self - .contract_balance_unchecked( - invocation_data.address, - ), - data: None, - } - } else { - v1::InvokeResponse::Failure { - kind: - v1::InvokeFailure::SignatureCheckFailed, - } - } - } - Err(_) => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::SignatureDataMalformed, - }, - } - } - None => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentAccount, - }, - }; - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - v1::Interrupt::QueryAccountKeys { address } => { - self.remaining_energy.tick_energy( - constants::CONTRACT_INSTANCE_QUERY_ACCOUNT_KEYS_BASE_COST, - )?; - let response = match self.account_keys(address) { - Some(keys) => { - let response_data = common::to_bytes(&keys); - let num_keys = keys.num_keys(); - self.remaining_energy.tick_energy( - constants::contract_instance_query_account_keys_return_cost( - num_keys, - ), - )?; - v1::InvokeResponse::Success { - // Balance of contract querying. Does not change for this - // request. - new_balance: self - .contract_balance_unchecked(invocation_data.address), - data: Some(response_data), - } - } - None => v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::NonExistentAccount, - }, - }; - stack.push(Next::Resume { - data: invocation_data, - config, - response: Some(response), - }); - } - } - } - v1::ReceiveResult::Reject { - reason, - return_value, - remaining_energy, - } => { - self.update_energy(remaining_energy); - // Remove the failure stack traces from the list and include them in a failure - // element. - let failure_traces = - trace_elements.split_off(invocation_data.trace_elements_checkpoint); - - // Create a `WithFailures` element even if `failure_traces` is empty, as the - // reject reason and energy usage is still relevant. - let with_failure = DebugTraceElement::WithFailures { - contract_address: invocation_data.address, - entrypoint: invocation_data.entrypoint.clone(), - error: InvokeExecutionError::Reject { - reason, - return_value: return_value.clone(), - }, - trace_elements: failure_traces, - energy_used: self.energy_used(), - }; - trace_elements.push(with_failure); - - // Reset the next modification index as well. - self.next_contract_modification_index = invocation_data.next_mod_idx_checkpoint; - invoke_response = Some(v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::ContractReject { - code: reason, - data: return_value, - }, - }); - } - v1::ReceiveResult::Trap { - error, - remaining_energy, - } => { - self.update_energy(remaining_energy); - // Remove the failure stack traces from the list and include them in a failure - // element. - let failure_traces = - trace_elements.split_off(invocation_data.trace_elements_checkpoint); - - // Create a `WithFailures` element even if `failure_traces` is empty, as the - // error and energy usage is still relevant. - let with_failure = DebugTraceElement::WithFailures { - contract_address: invocation_data.address, - entrypoint: invocation_data.entrypoint.clone(), - error: InvokeExecutionError::Trap { - error: ExecutionError(error), - }, - trace_elements: failure_traces, - energy_used: self.energy_used(), - }; - trace_elements.push(with_failure); - - // Reset the next modification index as well. - self.next_contract_modification_index = invocation_data.next_mod_idx_checkpoint; - invoke_response = Some(v1::InvokeResponse::Failure { - kind: v1::InvokeFailure::RuntimeError, - }); - } - // Convert this to an error here, so that we will short circuit processing. - v1::ReceiveResult::OutOfEnergy => return Err(TestConfigurationError::OutOfEnergy), - } - } - Ok(( - invoke_response.expect("Response should have been set."), - trace_elements, - )) - } - - /// Make a transfer from a contract to an account in the changeset. - /// - /// Returns the new balance of `from`. - /// - /// **Preconditions:** - /// - Assumes that `from` contract exists. - fn transfer_from_contract_to_account( - &mut self, - amount: Amount, - from: ContractAddress, - to: AccountAddress, - ) -> Result { - // Ensure the `to` account exists. - if !self.chain.accounts.contains_key(&to.into()) { - return Err(TransferError::ToMissing); - } - - // Make the transfer. - let new_balance = self.change_contract_balance(from, AmountDelta::Negative(amount))?; - self.change_account_balance(to, AmountDelta::Positive(amount)) - .expect("Cannot fail when adding"); - - Ok(new_balance) - } - - /// Make a transfer between contracts in the changeset. - /// - /// Returns the new balance of `to`. - /// - /// **Preconditions:** - /// - Assumes that `from` contract exists. - fn transfer_from_contract_to_contract( - &mut self, - amount: Amount, - from: ContractAddress, - to: ContractAddress, - ) -> Result { - // Ensure the `to` contract exists. - if !self.chain.contracts.contains_key(&to) { - return Err(TransferError::ToMissing); - } - - // Make the transfer. - self.change_contract_balance(from, AmountDelta::Negative(amount))?; - let new_balance = self.change_contract_balance(to, AmountDelta::Positive(amount))?; - Ok(new_balance) - } - - /// Make a transfer from an account to a contract in the changeset. - /// - /// Returns the new balance of `to`. - /// - /// **Preconditions:** - /// - Assumes that `from` account exists. - fn transfer_from_account_to_contract( - &mut self, - amount: Amount, - from: AccountAddress, - to: ContractAddress, - ) -> Result { - // Ensure the `to` account exists. - if !self.chain.contracts.contains_key(&to) { - return Err(TransferError::ToMissing); - } - - // Make the transfer. - self.change_account_balance(from, AmountDelta::Negative(amount))?; - let new_balance = self.change_contract_balance(to, AmountDelta::Positive(amount))?; - Ok(new_balance) - } - - /// Changes the contract balance in the topmost checkpoint on the changeset. - /// - /// If contract isn't already present in the changeset, it is added. - /// - /// Returns an error if the change in balance would go below `0`, which is a - /// valid error, or if the amounts would overflow, which is an unrecoverable - /// configuration error in the tests. - /// Otherwise, it returns the new balance of the contract. - /// - /// The precondition is not part of the error type, as this is an internal - /// helper function that is only called when the precondition is met. - /// - /// Returns the new balance. - /// - /// **Preconditions:** - /// - Contract must exist. - fn change_contract_balance( - &mut self, - address: ContractAddress, - delta: AmountDelta, - ) -> Result { - match self.changeset.current_mut().contracts.entry(address) { - btree_map::Entry::Vacant(vac) => { - // get original balance - let original_balance = self - .chain - .contracts - .get(&address) - .expect("Precondition violation: contract assumed to exist") - .self_balance; - // Try to apply the balance or return an error if insufficient funds. - let new_contract_balance = delta.apply_to_balance(original_balance)?; - // Insert the changes into the changeset. - vac.insert(ContractChanges { - self_balance_delta: delta, - ..ContractChanges::new(original_balance) - }); - Ok(new_contract_balance) - } - btree_map::Entry::Occupied(mut occ) => { - let contract_changes = occ.get_mut(); - let new_delta = contract_changes.self_balance_delta.add_delta(delta); - // Try to apply the balance or return an error if insufficient funds. - let new_contract_balance = - new_delta.apply_to_balance(contract_changes.self_balance_original)?; - contract_changes.self_balance_delta = new_delta; - Ok(new_contract_balance) - } - } - } - - /// Changes the account balance in the topmost checkpoint on the changeset. - /// - /// If account isn't already present in the changeset, it is added. - /// - /// Returns an error if a negative delta is provided which exceeds the - /// available balance of the account. Otherwise, it returns the new - /// available balance of the account. - /// Otherwise, it returns the new balance of the account. - /// - /// The precondition is not part of the error type, as this is an internal - /// helper function that is only called when the precondition is met. - /// - /// **Preconditions:** - /// - Account must exist. - fn change_account_balance( - &mut self, - address: AccountAddress, - delta: AmountDelta, - ) -> Result { - match self.changeset.current_mut().accounts.entry(address.into()) { - btree_map::Entry::Vacant(vac) => { - // get original balance - let mut original_balance = self - .chain - .accounts - .get(&address.into()) - .expect("Precondition violation: account assumed to exist") - .balance - .available(); - if self.invoker == address { - // It has been checked that the invoker account has sufficient balance for - // paying. - original_balance -= self.reserved_amount; - } - // Try to apply the balance or return an error if insufficient funds. - let new_account_balance = delta.apply_to_balance(original_balance)?; - // Insert the changes into the changeset. - vac.insert(AccountChanges { - original_balance, - balance_delta: delta, - }); - Ok(new_account_balance) - } - btree_map::Entry::Occupied(mut occ) => { - let account_changes = occ.get_mut(); - let new_delta = account_changes.balance_delta.add_delta(delta); - // Try to apply the balance or return an error if insufficient funds. - let new_account_balance = - new_delta.apply_to_balance(account_changes.original_balance)?; - account_changes.balance_delta = new_delta; - Ok(new_account_balance) - } - } - } - - /// Returns the contract balance from the topmost checkpoint on the - /// changeset. Or, alternatively, from persistence. - /// - /// **Preconditions:** - /// - Contract must exist. - fn contract_balance_unchecked(&self, address: ContractAddress) -> Amount { - self.contract_balance(address) - .expect("Precondition violation: contract must exist") - } - - /// Looks up the contract balance from the topmost checkpoint on the - /// changeset. Or, alternatively, from persistence. - fn contract_balance(&self, address: ContractAddress) -> Option { - match self.changeset.current().contracts.get(&address) { - Some(changes) => Some(changes.current_balance()), - None => self.chain.contracts.get(&address).map(|c| c.self_balance), - } - } - - /// Returns the contract module from the topmost checkpoint on - /// the changeset. Or, alternatively, from persistence. - /// - /// **Preconditions:** - /// - Contract instance must exist (and therefore also the artifact). - /// - If the changeset contains a module reference, then it must refer a - /// deployed module. - fn contract_module(&self, contract: &Contract) -> ContractModule { - match self - .changeset - .current() - .contracts - .get(&contract.address) - .and_then(|c| c.module) - { - // Contract has been upgrade, new module exists. - Some(new_module) => self - .chain - .modules - .get(&new_module) - .expect("Precondition violation: module must exist.") - .clone(), - // Contract hasn't been upgraded. Use persisted module. - None => self - .chain - .modules - .get(&contract.module_reference) - .expect("Precondition violation: module must exist.") - .clone(), - } - } - - /// Get the contract state, either from the changeset or by thawing it from - /// persistence. - /// - /// **Preconditions:** - /// - Contract instance must exist. - fn contract_state(&self, address: ContractAddress) -> trie::MutableState { - match self - .changeset - .current() - .contracts - .get(&address) - .and_then(|c| c.state.clone()) - { - // Contract state has been modified. - Some(modified_state) => modified_state, - // Contract state hasn't been modified. Thaw from persistence. - None => self - .chain - .contracts - .get(&address) - .expect("Precondition violation: contract must exist") - .state - .thaw(), - } - } - - /// Looks up the account balance for an account by first checking - /// the changeset, then the persisted values. - fn account_balance(&self, address: AccountAddress) -> Option { - let mut account_balance = self - .chain - .accounts - .get(&address.into()) - .map(|a| a.balance)?; - match self - .changeset - .current() - .accounts - .get(&address.into()) - .map(|a| a.current_balance()) - { - // Account exists in changeset. - // Return the staked and locked balances from persistence, as they can't change during - // entrypoint invocation. - Some(total) => Some(AccountBalance { - total, - staked: account_balance.staked, - locked: account_balance.locked, - }), - // Account doesn't exist in changeset. - None => { - if self.invoker == address { - account_balance.total -= self.reserved_amount; - } - Some(account_balance) - } - } - } - - /// Looks up the account keys. - fn account_keys(&self, address: AccountAddress) -> Option<&AccountAccessStructure> { - // The account keys cannot change during a smart contract transaction, so - // there is no need to check in the changeset. - self.chain.accounts.get(&address.into()).map(|a| &a.keys) - } - - /// Saves a mutable state for a contract in the changeset. - /// - /// If the contract already has an entry in the changeset, the old state - /// will be replaced. Otherwise, the entry is created and the state is - /// added. - /// - /// This method also increments the `self.next_contract_modification_index`. - /// - /// Returns the `modification_index` set for the contract. - /// - /// **Preconditions:** - /// - Contract must exist. - fn save_state_changes( - &mut self, - address: ContractAddress, - state: &mut trie::MutableState, - ) -> u32 { - let state = state.clone(); - let modification_index = self.next_contract_modification_index; - match self.changeset.current_mut().contracts.entry(address) { - btree_map::Entry::Vacant(vac) => { - let original_balance = self - .chain - .contracts - .get(&address) - .expect("Precondition violation: contract must exist.") - .self_balance; - vac.insert(ContractChanges { - state: Some(state), - modification_index, - ..ContractChanges::new(original_balance) - }); - } - btree_map::Entry::Occupied(mut occ) => { - let changes = occ.get_mut(); - changes.state = Some(state); - changes.modification_index = modification_index; - } - } - self.next_contract_modification_index += 1; - modification_index - } - - /// Saves a new module reference for the contract in the changeset. - /// - /// If the contract already has an entry in the changeset, the old module is - /// replaced. Otherwise, the entry is created and the module is added. - /// - /// Returns the previous module, which is either the one from persistence, - /// or the most recent one from the changeset. - /// - /// **Preconditions:** - /// - Contract must exist. - /// - Module must exist. - fn save_module_upgrade( - &mut self, - address: ContractAddress, - module_reference: ModuleReference, - ) -> ModuleReference { - match self.changeset.current_mut().contracts.entry(address) { - btree_map::Entry::Vacant(vac) => { - let contract = self - .chain - .contracts - .get(&address) - .expect("Precondition violation: contract must exist."); - let old_module_ref = contract.module_reference; - let original_balance = contract.self_balance; - vac.insert(ContractChanges { - module: Some(module_reference), - ..ContractChanges::new(original_balance) - }); - old_module_ref - } - btree_map::Entry::Occupied(mut occ) => { - let changes = occ.get_mut(); - let old_module_ref = match changes.module { - Some(old_module) => old_module, - None => { - self.chain - .contracts - .get(&address) - .expect("Precondition violation: contract must exist.") - .module_reference - } - }; - changes.module = Some(module_reference); - old_module_ref - } - } - } - - /// Returns the modification index for a contract. - /// - /// It looks it up in the changeset, and if it isn't there, it will return - /// `0`. - fn modification_index(&self, address: ContractAddress) -> u32 { - self.changeset - .current() - .contracts - .get(&address) - .map_or(0, |c| c.modification_index) - } - - /// Makes a new checkpoint. - fn checkpoint(&mut self) { self.changeset.checkpoint(); } - - /// Roll back to the previous checkpoint. - fn rollback(&mut self) { self.changeset.rollback(); } - - /// Update the `remaining_energy` field by converting the input to - /// [`InterpreterEnergy`] and then [`Energy`]. - fn update_energy(&mut self, remaining_energy: u64) { - *self.remaining_energy = from_interpreter_energy(InterpreterEnergy::from(remaining_energy)); - } - - /// Run the interpreter with the provided function and the - /// `self.remaining_energy`. - /// - /// This function ensures that the energy calculations is handled as in the - /// node. - fn run_interpreter( - &mut self, - f: F, - ) -> Result, InsufficientEnergy> - where - F: FnOnce(InterpreterEnergy) -> ExecResult>, - { - let available_interpreter_energy = to_interpreter_energy(*self.remaining_energy); - let res = match f(available_interpreter_energy) { - Ok(res) => res, - Err(err) => { - // An error occurred in the interpreter and it doesn't return the remaining - // energy. We convert this to a trap and set the energy to the - // last known amount. - return Ok(v1::ReceiveResult::Trap { - error: err, - remaining_energy: available_interpreter_energy.energy, - }); - } - }; - let mut subtract_then_convert = |remaining_energy| -> Result { - let remaining_energy = InterpreterEnergy::from(remaining_energy); - // Using `saturating_sub` here should be ok since we should never be able to use - // more energy than what is available. - let used_energy = from_interpreter_energy( - available_interpreter_energy.saturating_sub(remaining_energy), - ); - self.remaining_energy.tick_energy(used_energy)?; - Ok(to_interpreter_energy(*self.remaining_energy).energy) - }; - match res { - v1::ReceiveResult::Success { - logs, - state_changed, - return_value, - remaining_energy, - } => { - let remaining_energy = subtract_then_convert(remaining_energy)?; - Ok(v1::ReceiveResult::Success { - logs, - state_changed, - return_value, - remaining_energy, - }) - } - - v1::ReceiveResult::Interrupt { - remaining_energy, - state_changed, - logs, - config, - interrupt, - } => { - let remaining_energy = subtract_then_convert(remaining_energy)?; - - Ok(v1::ReceiveResult::Interrupt { - remaining_energy, - state_changed, - logs, - config, - interrupt, - }) - } - v1::ReceiveResult::Reject { - reason, - return_value, - remaining_energy, - } => Ok(v1::ReceiveResult::Reject { - reason, - return_value, - remaining_energy: subtract_then_convert(remaining_energy)?, - }), - v1::ReceiveResult::Trap { - error, - remaining_energy, - } => Ok(v1::ReceiveResult::Trap { - error, - remaining_energy: subtract_then_convert(remaining_energy)?, - }), - // Convert this to an error so that we will short-circuit the processing. - v1::ReceiveResult::OutOfEnergy => Err(InsufficientEnergy), - } - } - - /// The energy used so far in this transaction. - fn energy_used(&self) -> Energy { self.energy_reserved - *self.remaining_energy } - - /// Helper for that constructs and pushes a [`DebugTraceElement::Regular`] - /// to the `trace_elements` list provided. - fn push_regular_trace_element( - &self, - trace_elements: &mut Vec, - trace_element: ContractTraceElement, - entrypoint: OwnedEntrypointName, - ) { - let energy_used = self.energy_used(); - let new = DebugTraceElement::Regular { - entrypoint, - trace_element, - energy_used, - }; - trace_elements.push(new); - } -} - -/// A pair of the signatures, and the data. -type DeserializedSignatureAndData<'a> = (AccountSignatures, &'a [u8]); - -/// Deserialize the signatures and data from the slice. -/// Note that this does not ensure that the entire data is read, i.e., there can -/// be leftover data in the slice, which matches the behaviour in the node. -fn deserial_signature_and_data_from_contract( - payload: &[u8], -) -> anyhow::Result { - // Imported locally only since it is critical that we use the right Deserial - // trait. - use concordium_base::contracts_common::Deserial; - let mut source = concordium_base::contracts_common::Cursor::new(payload); - let data_len = u32::deserial(&mut source)?; - let data = &payload[source.offset..][..data_len as usize]; - source.offset += data_len as usize; - let signatures = AccountSignatures::deserial(&mut source)?; - Ok((signatures, data)) -} - -impl ChangeSet { - /// Creates a new changeset with one empty [`Changes`] element on the - /// stack.. - pub(crate) fn new() -> Self { - Self { - stack: vec![Changes { - contracts: BTreeMap::new(), - accounts: BTreeMap::new(), - }], - } - } - - /// Make a checkpoint by putting a clone of the top element onto the stack. - fn checkpoint(&mut self) { - let cloned_top_element = self.current().clone(); - self.stack.push(cloned_top_element); - } - - /// Perform a rollback by popping the top element of the stack. - fn rollback(&mut self) { - self.stack - .pop() - .expect("Internal error: change set stack should never be empty."); - } - - /// Get an immutable reference the current (latest) checkpoint. - fn current(&self) -> &Changes { - self.stack - .last() - .expect("Internal error: change set stack should never be empty.") - } - - /// Get a mutable reference to the current (latest) checkpoint. - fn current_mut(&mut self) -> &mut Changes { - self.stack - .last_mut() - .expect("Internal error: change set stack should never be empty.") - } - - /// Try to persist all changes from the changeset. - /// - /// If the energy needed for storing extra state is larger than the - /// `remaining_energy`, then: - /// - no changes will be persisted, - /// - an [`OutOfEnergy`] error is returned. - /// - /// Otherwise, it returns whether the state of the provided - /// `invoked_contract` was changed. - /// - /// **Preconditions:** - /// - All contracts, modules, accounts referred must exist in persistence. - /// - All amount deltas must be valid (i.e. not cause underflows when added - /// to balance). - pub(crate) fn persist( - mut self, - remaining_energy: &mut Energy, - invoked_contract: ContractAddress, - persisted_accounts: &mut BTreeMap, - persisted_contracts: &mut BTreeMap, - ) -> Result { - let current = self.current_mut(); - let mut invoked_contract_has_state_changes = false; - // Persist contract changes and collect the total increase in states sizes. - let mut collector = v1::trie::SizeCollector::default(); - let mut loader = v1::trie::Loader::new(&[][..]); // An empty loader is fine currently, as we do not use caching in this lib. - - let mut frozen_states: BTreeMap = BTreeMap::new(); - - // Create frozen versions of all the states, to compute the energy needed. - for (addr, changes) in current.contracts.iter_mut() { - if let Some(modified_state) = &mut changes.state { - frozen_states.insert(*addr, modified_state.freeze(&mut loader, &mut collector)); - } - } - - // One energy per extra byte of state. - let energy_for_state_increase = Energy::from(collector.collect()); - - // Return an error if out of energy. - remaining_energy.tick_energy(energy_for_state_increase)?; - - // Then persist all the changes. - for (addr, changes) in current.contracts.iter_mut() { - let contract = persisted_contracts - .get_mut(addr) - .expect("Precondition violation: contract must exist"); - // Update balance. - if !changes.self_balance_delta.is_zero() { - contract.self_balance = changes - .self_balance_delta - .apply_to_balance(changes.self_balance_original) - .expect("Precondition violation: amount delta causes underflow"); - } - // Update module reference. - if let Some(new_module_ref) = changes.module { - contract.module_reference = new_module_ref; - } - // Update state. - if changes.state.is_some() { - if *addr == invoked_contract { - invoked_contract_has_state_changes = true; - } - // Replace with the frozen state we created earlier. - contract.state = frozen_states - .remove(addr) - .expect("Known to exist since we just added it."); - } - } - // Persist account changes. - for (addr, changes) in current.accounts.iter() { - let account = persisted_accounts - .get_mut(addr) - .expect("Precondition violation: account must exist"); - // Update balance. - if !changes.balance_delta.is_zero() { - account.balance.total = changes - .balance_delta - .apply_to_balance(account.balance.total) - .expect("Precondition violation: amount delta causes underflow"); - } - } - - Ok(invoked_contract_has_state_changes) - } - - /// Traverses the last checkpoint in the changeset and collects the energy - /// needed to be charged for additional state bytes. - /// - /// Returns an [`OutOfEnergy`] error if the energy needed for storing the - /// extra state is larger than `remaining_energy`. - /// - /// Otherwise, it returns whether the state of the provided - /// `invoked_contract` has changed. - pub(crate) fn collect_energy_for_state( - mut self, - remaining_energy: &mut Energy, - invoked_contract: ContractAddress, - ) -> Result { - let mut invoked_contract_has_state_changes = false; - let mut loader = v1::trie::Loader::new(&[][..]); // An empty loader is fine currently, as we do not use caching in this lib. - let mut collector = v1::trie::SizeCollector::default(); - for (addr, changes) in self.current_mut().contracts.iter_mut() { - if let Some(modified_state) = &mut changes.state { - if *addr == invoked_contract { - invoked_contract_has_state_changes = true; - } - modified_state.freeze(&mut loader, &mut collector); - } - } - - // One energy per extra byte in the state. - let energy_for_state_increase = Energy::from(collector.collect()); - - // Return an error if we run out of energy. - remaining_energy.tick_energy(energy_for_state_increase)?; - - Ok(invoked_contract_has_state_changes) - } -} - -impl Default for ChangeSet { - fn default() -> Self { Self::new() } -} - -impl AmountDelta { - /// Create a new [`Self`], with the value `+0`. - fn new() -> Self { Self::Positive(Amount::zero()) } - - /// Subtract an [`Amount`] from [`Self`]. - fn subtract_amount(self, amount: Amount) -> Self { - match self { - Self::Positive(current) => { - if current >= amount { - Self::Positive(current.subtract_micro_ccd(amount.micro_ccd)) - } else { - Self::Negative(amount.subtract_micro_ccd(current.micro_ccd)) - } - } - Self::Negative(current) => Self::Negative(current.add_micro_ccd(amount.micro_ccd)), - } - } - - /// Add an [`Amount`] from [`Self`]. - fn add_amount(self, amount: Amount) -> Self { - match self { - Self::Positive(current) => Self::Positive(current.add_micro_ccd(amount.micro_ccd)), - Self::Negative(current) => { - if current >= amount { - Self::Negative(current.subtract_micro_ccd(amount.micro_ccd)) - } else { - Self::Positive(amount.subtract_micro_ccd(current.micro_ccd)) - } - } - } - } - - /// Add two [`Self`] to create a new one. - fn add_delta(self, delta: AmountDelta) -> Self { - match delta { - AmountDelta::Positive(d) => self.add_amount(d), - AmountDelta::Negative(d) => self.subtract_amount(d), - } - } - - /// Whether the [`Self`] is zero (either `+0` or `-0`). - fn is_zero(&self) -> bool { - match self { - AmountDelta::Positive(d) => d.micro_ccd == 0, - AmountDelta::Negative(d) => d.micro_ccd == 0, - } - } - - /// Returns a new balance with the `AmountDelta` applied, or a - /// [`BalanceError`] error if the change would result in a negative - /// balance or an overflow. - fn apply_to_balance(&self, balance: Amount) -> Result { - match self { - AmountDelta::Positive(d) => balance.checked_add(*d).ok_or(BalanceError::Overflow), - AmountDelta::Negative(d) => balance.checked_sub(*d).ok_or(BalanceError::Insufficient), - } - } -} - -impl ContractChanges { - /// Create a new `Self`. The original balance must be provided, all other - /// fields take on default values. - fn new(original_balance: Amount) -> Self { - Self { - modification_index: 0, - self_balance_delta: AmountDelta::new(), - self_balance_original: original_balance, - state: None, - module: None, - } - } - - /// Get the current balance by adding the original balance and the balance - /// delta. - /// - /// **Preconditions:** - /// - `balance_delta + original_balance` must be larger than `0`. - fn current_balance(&self) -> Amount { - self.self_balance_delta - .apply_to_balance(self.self_balance_original) - .expect("Precondition violation: invalid `balance_delta`.") - } -} - -impl AccountChanges { - /// Get the current balance by adding the original balance and the balance - /// delta. - /// - /// **Preconditions:** - /// - `balance_delta + original_balance` must be larger than `0`. - fn current_balance(&self) -> Amount { - self.balance_delta - .apply_to_balance(self.original_balance) - .expect("Precondition violation: invalid `balance_delta`.") - } -} - -impl From for TestConfigurationError { - fn from(_: InsufficientEnergy) -> Self { Self::OutOfEnergy } -} - -#[cfg(test)] -mod tests { - mod amount_delta { - use crate::{invocation::types::AmountDelta, Amount}; - #[test] - fn test() { - let mut x = AmountDelta::new(); - assert_eq!(x, AmountDelta::Positive(Amount::zero())); - - let one = Amount::from_ccd(1); - let two = Amount::from_ccd(2); - let three = Amount::from_ccd(3); - let five = Amount::from_ccd(5); - - x = x.subtract_amount(one); // -1 CCD - x = x.subtract_amount(one); // -2 CCD - assert_eq!(x, AmountDelta::Negative(two)); - x = x.add_amount(five); // +3 CCD - assert_eq!(x, AmountDelta::Positive(three)); - x = x.subtract_amount(five); // -2 CCD - assert_eq!(x, AmountDelta::Negative(two)); - x = x.add_amount(two); // 0 - - x = x.add_amount(Amount::from_micro_ccd(1)); // 1 mCCD - assert_eq!(x, AmountDelta::Positive(Amount::from_micro_ccd(1))); - x = x.subtract_amount(Amount::from_micro_ccd(2)); // -1 mCCD - assert_eq!(x, AmountDelta::Negative(Amount::from_micro_ccd(1))); - } - } -} diff --git a/concordium-smart-contract-testing/src/invocation/mod.rs b/concordium-smart-contract-testing/src/invocation/mod.rs deleted file mode 100644 index 27bfafd0..00000000 --- a/concordium-smart-contract-testing/src/invocation/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Functionality and types for invoking contract entrypoints. -//! -//! Contract invocation is effectful and transactional. -//! We therefore keep track of changes during execution in a -//! [`ChangeSet`][types::ChangeSet]. -//! -//! Once the execution (transaction) has finished, the changes can then be -//! persisted (saved) or discarded, dependent on whether it succeeded or not. -//! -//! The changes that may occur are: -//! - Mutations to contract state, -//! - Contract upgrades (changing the module), -//! - Balances of contracts and accounts. - -mod impls; -mod types; -pub(crate) use types::{ChangeSet, EntrypointInvocationHandler, TestConfigurationError}; diff --git a/concordium-smart-contract-testing/src/invocation/types.rs b/concordium-smart-contract-testing/src/invocation/types.rs deleted file mode 100644 index 2ba24136..00000000 --- a/concordium-smart-contract-testing/src/invocation/types.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::Chain; -use concordium_base::{ - base::{AccountAddressEq, Energy}, - contracts_common::{ - AccountAddress, Address, Amount, ContractAddress, ModuleReference, OwnedContractName, - OwnedEntrypointName, - }, - smart_contracts::OwnedParameter, - transactions::UpdateContractPayload, -}; -use concordium_smart_contract_engine::v1::{ - trie::MutableState, InvokeResponse, ReceiveContext, ReceiveInterruptedState, -}; -use concordium_wasm::artifact::CompiledFunction; -use std::collections::BTreeMap; - -/// A type that supports invoking a contract entrypoint. -pub(crate) struct EntrypointInvocationHandler<'a, 'b> { - /// Amount reserved for execution. This is used to return the correct - /// balance of the invoker account. - pub(crate) reserved_amount: Amount, - /// Address of the invoker of the transaction. This is used to return the - /// correct balance of the invoker account. - pub(crate) invoker: AccountAddress, - /// The changeset which keeps track of - /// changes to accounts, modules, and contracts that occur during an - /// invocation. - pub(crate) changeset: ChangeSet, - /// The energy remaining for execution. - pub(crate) remaining_energy: &'a mut Energy, - /// The energy reserved for the execution. Used for calculating intermediate - /// energy usages in contract trace elements. - pub(crate) energy_reserved: Energy, - /// An immutable reference to the chain, used for looking up information, - /// including contracts, modules, and accounts. - pub(crate) chain: &'b Chain, - /// The next contract modification index to be given out. - /// The index is global per transaction, which is why this field is - /// needed. - pub(crate) next_contract_modification_index: u32, -} - -/// This auxiliary type is used in `invoke_entrypoint` from impls.rs to keep -/// track of the "to do list" in the form of a stack. It stores the necessary -/// information to continue execution until all actions have been processed. -pub(super) enum Next { - /// The next action is to resume execution after handling the interrupt. - Resume { - data: InvocationData, - config: Box>>>, - /// This is [`None`] if we are going to resume after a call to a - /// contract. And [`Some`] if we have an immediate handler that - /// immediately produces a response. - response: Option, - }, - /// The next action is to start executing an entrypoint. - Initial { - sender: Address, - payload: UpdateContractPayload, - /// If execution of the entrypoint fails then it does not produce any - /// trace. We store the trace in one "global" (per transaction) - /// vector. This field is used to determine how far back we need - /// to roll back (i.e., clean up) the elements in that trace in - /// case of contract invocation failure. - trace_elements_checkpoint: usize, - }, -} - -/// The set of [`Changes`] represented as a stack. -// For maintainers. It would be better if `Changes` had a form of copy-on-write. -// At the moment we make a full clone of the changes when we need to checkpoint. -// This is OKish, since people generally don't have that complex protocols, but can be made more -// robust, resistant to malicious input. -#[derive(Debug, Clone)] -pub(crate) struct ChangeSet { - /// The stack of changes. - pub(super) stack: Vec, -} - -/// Data held for accounts and contracts during the execution of a contract -/// entrypoint. -#[derive(Clone, Debug)] -pub(super) struct Changes { - /// The contracts which have changes. - pub(super) contracts: BTreeMap, - /// The accounts which have changes. These are indexed by account address - /// equivalence classes so that account aliases are resolved to the same - /// account. - pub(super) accounts: BTreeMap, -} - -/// Data held for an account during the execution of a contract entrypoint. -#[derive(Clone, Debug)] -pub(super) struct AccountChanges { - /// The original balance. - /// - /// For the `invoker`, this will be the `original_balance - reserved_amount` - /// (from [`EntrypointInvocationHandler`]). - /// - /// Should never be modified. - pub(super) original_balance: Amount, - /// The change in the account balance. - pub(super) balance_delta: AmountDelta, -} - -/// Data held for a contract during the execution of a contract entrypoint. -#[derive(Clone, Debug)] -pub(super) struct ContractChanges { - /// An index that is used to check whether a caller contract has been - /// modified after invoking another contract (due to reentrancy). - pub(super) modification_index: u32, - /// Represents how much the contract's self balance has changed. - pub(super) self_balance_delta: AmountDelta, - /// The original contract balance, i.e. the one that is persisted. Should - /// never be modified. - pub(super) self_balance_original: Amount, - /// The potentially modified contract state. - pub(super) state: Option, - /// The potentially changed module. - pub(super) module: Option, -} - -/// Data needed to recursively process a contract entrypoint to completion. -/// -/// In particular, this keeps the data necessary for resuming a contract -/// entrypoint after an interrupt. -/// -/// One `InvocationData` is created for each time -/// [`EntrypointInvocationHandler::invoke_entrypoint`] is called. -#[derive(Debug)] -pub(super) struct InvocationData { - /// The sender. - pub(super) sender: Address, - /// The contract being called. - pub(super) address: ContractAddress, - /// The name of the contract. - pub(super) contract_name: OwnedContractName, - /// The entrypoint to execute. - pub(super) entrypoint: OwnedEntrypointName, - /// The amount sent from the sender to the contract. - pub(super) amount: Amount, - /// The parameter given to the entrypoint. - pub(super) parameter: OwnedParameter, - /// The current state. - pub(super) state: MutableState, - /// A checkpoint in the list of trace elements. - /// We reset to this size in case of failure of execution. - pub(super) trace_elements_checkpoint: usize, - /// A checkpoint on the next modification index. - /// We reset `next_modification_index` to this value in case of failure of - /// execution. - pub(super) next_mod_idx_checkpoint: u32, - /// The modification index before making an invocation. - /// Differs from the `next_mod_idx_checkpoint` in that this value can be - /// altered during the execution of a single entrypoint. - pub(super) mod_idx_before_invoke: u32, -} - -/// A positive or negative delta in for an [`Amount`]. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum AmountDelta { - /// A positive delta. - Positive(Amount), - /// A negative delta. - Negative(Amount), -} - -/// Errors that occur due to the configuration of the test. -#[derive(Debug)] -pub(crate) enum TestConfigurationError { - /// The method ran out of energy. - OutOfEnergy, - /// The balance of an account or contract oveflowed while adding a new - /// [`Amount`]. On the chain there is roughly 10 billion CCD, which - /// means that overflows of amounts cannot occur. - BalanceOverflow, -} diff --git a/concordium-smart-contract-testing/src/lib.rs b/concordium-smart-contract-testing/src/lib.rs deleted file mode 100644 index 51bf89a5..00000000 --- a/concordium-smart-contract-testing/src/lib.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! # Concordium Smart Contract Testing -//! -//! This library supports writing integration tests in Rust for Concordium smart -//! contracts. -//! -//! To use the library, you must add it to your `Cargo.toml` file under the -//! `[dev-dependencies]` section. The library requries the rust edition `2021` -//! or greater. -//! -//! ```toml -//! [package] -//! # ... -//! edition = "2021" -//! -//! [dev-dependencies] -//! concordium-smart-contract-testing = "1.0" -//! ``` -//! -//! ## Basic usage -//! -//! ```no_run -//! use concordium_smart_contract_testing::*; -//! -//! // Create a "chain" with default parameters. -//! let mut chain = Chain::new(); -//! -//! // Define an account address to be used. -//! const ACC: AccountAddress = AccountAddress([0;32]); -//! -//! // Create an account with 10000 CCD in balance. -//! chain.create_account(Account::new(ACC, Amount::from_ccd(1000))); -//! -//! // Deploy a smart contract module (built with [Cargo Concordium](https://developer.concordium.software/en/mainnet/smart-contracts/guides/setup-tools.html#cargo-concordium)). -//! let deployment = chain -//! .module_deploy_v1( -//! Signer::with_one_key(), -//! ACC, -//! module_load_v1("path/to/contract.wasm.v1").unwrap()) -//! .unwrap(); -//! -//! // Initialize a smart contract from the deployed module. -//! let initialization = chain -//! .contract_init( -//! Signer::with_one_key(), // Used for specifying the number of signatures. -//! ACC, // Invoker account. -//! Energy::from(10000), // Maximum energy allowed for initializing. -//! InitContractPayload { -//! mod_ref: deployment.module_reference, // Module to initialize from. -//! init_name: OwnedContractName::new_unchecked("init_my_contract".into()), // Contract to init. -//! param: OwnedParameter::from_serial(&"my_param").unwrap(), // Any type implementing [`Serial`] can be used. -//! amount: Amount::zero(), // CCD to send the contract. -//! } -//! ) -//! .unwrap(); -//! -//! // Update the initialized contract. -//! let update = chain -//! .contract_update( -//! Signer::with_one_key(), // Used for specifying the number of signatures. -//! ACC, // Invoker account. -//! Address::Account(ACC), // Sender (can also be a contract). -//! Energy::from(10000), // Maximum energy allowed for the update. -//! UpdateContractPayload { -//! address: initialization.contract_address, // The contract to update. -//! receive_name: OwnedReceiveName::new_unchecked("my_contract.my_entrypoint".into()), // The receive function to call. -//! message: OwnedParameter::from_serial(&42u8).unwrap(), // The parameter sent to the contract. -//! amount: Amount::from_ccd(100), // Sending the contract 100 CCD. -//! } -//! ) -//! .unwrap(); -//! -//! // Check the trace elements produced (updates, interrupts, resumes, transfers, etc.). -//! assert!(matches!(update.effective_trace_elements().collect::>()[..], [ContractTraceElement::Updated{..}])); -//! -//! // Check the return value. -//! assert_eq!(update.return_value, to_bytes(&84u8)); -//! -//! // Check the balances of both contracts and accounts. -//! assert_eq!(chain.contract_balance(initialization.contract_address), Some(Amount::from_ccd(100))); -//! assert_eq!(chain.account_balance_available(ACC), Some( -//! Amount::from_ccd(1000) -//! - Amount::from_ccd(100) // Amount sent to contract. -//! - deployment.transaction_fee -//! - initialization.transaction_fee -//! - update.transaction_fee)); -//! -//! ``` -mod constants; -mod impls; -mod invocation; -mod types; -pub use impls::{module_load_v1, module_load_v1_raw}; -pub use types::*; - -// Re-export types. -pub use concordium_base::{ - base::Energy, - common::types::{CredentialIndex, KeyIndex}, - contracts_common::{ - from_bytes, to_bytes, AccountAddress, AccountBalance, AccountThreshold, Address, Amount, - ContractAddress, ContractName, Duration, EntrypointName, ExchangeRate, ModuleReference, - OwnedContractName, OwnedEntrypointName, OwnedParameter, OwnedReceiveName, Parameter, - ReceiveName, SignatureThreshold, SlotTime, Timestamp, - }, - hashes::BlockHash, - id::types::{CredentialPublicKeys, VerifyKey}, - smart_contracts::{ContractEvent, ContractTraceElement, InstanceUpdatedEvent, WasmVersion}, - transactions::{AccountAccessStructure, InitContractPayload, UpdateContractPayload}, -}; -pub use concordium_rust_sdk::{types::RejectReason, v2::Endpoint}; -pub use concordium_smart_contract_engine::v1::InvokeFailure; diff --git a/concordium-smart-contract-testing/src/types.rs b/concordium-smart-contract-testing/src/types.rs deleted file mode 100644 index 93fdaf48..00000000 --- a/concordium-smart-contract-testing/src/types.rs +++ /dev/null @@ -1,959 +0,0 @@ -use concordium_base::{ - base::{AccountAddressEq, Energy}, - common::types::{CredentialIndex, KeyIndex, Signature}, - constants::ED25519_SIGNATURE_LENGTH, - contracts_common::{ - self, AccountAddress, AccountBalance, Address, Amount, ContractAddress, ExchangeRate, - ModuleReference, OwnedContractName, OwnedEntrypointName, OwnedPolicy, SlotTime, Timestamp, - }, - hashes::BlockHash, - id::types::SchemeId, - smart_contracts::{ - ContractEvent, ContractTraceElement, InstanceUpdatedEvent, OwnedParameter, - OwnedReceiveName, WasmVersion, - }, - transactions::AccountAccessStructure, -}; -use concordium_rust_sdk as sdk; -use concordium_smart_contract_engine::v1::{self, trie, ReturnValue}; -use concordium_wasm::artifact; -use std::{ - collections::{BTreeMap, BTreeSet}, - path::PathBuf, - sync::Arc, -}; -use thiserror::Error; - -/// A smart contract module. -#[derive(Debug, Clone)] -pub struct ContractModule { - /// Size of the module in bytes. Used for cost accounting. - pub(crate) size: u64, - /// The runnable module. - pub(crate) artifact: Arc>, -} - -/// The chain parameters. -#[derive(Debug)] -pub(crate) struct ChainParameters { - /// The block time viewable inside the smart contracts. - /// Defaults to `0`. - pub(crate) block_time: SlotTime, - /// MicroCCD per Euro ratio. - pub(crate) micro_ccd_per_euro: ExchangeRate, - /// Euro per Energy ratio. - pub(crate) euro_per_energy: ExchangeRate, -} - -/// The connection and runtime needed for communicating with an external node. -#[derive(Debug)] -pub(crate) struct ExternalNodeConnection { - /// An instantiated v2 Client from the Rust SDK. Used for communicating with - /// a node. - pub(crate) client: concordium_rust_sdk::v2::Client, - /// A Tokio runtime used to execute the async methods of the `client`. - pub(crate) runtime: tokio::runtime::Runtime, - /// The block used for queries. - pub(crate) query_block: BlockHash, - /// External accounts that are verified to exist in the `query_block`. - pub(crate) accounts: BTreeSet, - /// External contracts that are verified to exist in the `query_block`. - pub(crate) contracts: BTreeSet, -} - -/// Represents the blockchain and supports a number of operations, including -/// creating accounts, deploying modules, initializing contract, updating -/// contracts and invoking contracts. -#[derive(Debug)] -pub struct Chain { - pub(crate) parameters: ChainParameters, - /// Accounts and info about them. - /// This uses [`AccountAddressEq`] to ensure that account aliases are seen - /// as one account. - pub(crate) accounts: BTreeMap, - /// Smart contract modules. - pub(crate) modules: BTreeMap, - /// Smart contract instances. - pub(crate) contracts: BTreeMap, - /// Next contract index to use when creating a new instance. - pub(crate) next_contract_index: u64, - /// An optional connection to an external node. - pub(crate) external_node_connection: Option, -} - -/// A builder for the [`Chain`]. -#[derive(Debug)] -pub struct ChainBuilder { - /// The configured endpoint for an external node connection. - pub(crate) external_node_endpoint: Option, - /// The block hash to be used for external queries. If this is not set, then - /// the last final block hash is used instead. - pub(crate) external_query_block: Option, - /// The configured exchange rate between microCCD and euro. - pub(crate) micro_ccd_per_euro: Option, - /// Whether the microCCD/euro exchange rate should be set via the external - /// node. - pub(crate) micro_ccd_per_euro_from_external: bool, - /// The configured exchange rate between euro and energy. - pub(crate) euro_per_energy: Option, - /// Whether the euro/energy exchange rate should be set via the external - /// node. - pub(crate) euro_per_energy_from_external: bool, - /// The configured block time. - pub(crate) block_time: Option, - /// Whether the block time should be set via the external node. - pub(crate) block_time_from_external: bool, -} - -/// A smart contract instance. -#[derive(Clone, Debug)] -pub struct Contract { - /// The address of this contract. - pub address: ContractAddress, - /// The module which contains this contract. - pub module_reference: ModuleReference, - /// The name of the contract. - pub contract_name: OwnedContractName, - /// The contract state. - pub state: trie::PersistentState, - /// The owner of the contract. - pub owner: AccountAddress, - /// The balance of the contract. - pub self_balance: Amount, -} - -/// An account. -#[derive(Clone, Debug)] -pub struct Account { - pub address: AccountAddress, - /// The account balance. - pub balance: AccountBalance, - /// Account policy. - pub policy: OwnedPolicy, - /// Account's public keys. - pub keys: AccountAccessStructure, -} - -/// A signature with account's keys. -#[derive(Debug, Clone)] -pub struct AccountSignatures { - /// It is assumed that the inner `Signature` will always be for ed25519 - /// scheme, so will have length 64 bytes. - pub(crate) sigs: BTreeMap>, -} - -impl From for BTreeMap> { - fn from(value: AccountSignatures) -> Self { value.sigs } -} - -impl From>> for AccountSignatures { - fn from(sigs: BTreeMap>) -> Self { - Self { sigs } - } -} - -impl AccountSignatures { - /// Return the number of signatures contained in the structure. - pub fn num_signatures(&self) -> u32 { self.sigs.values().map(|v| v.len() as u32).sum() } -} - -impl contracts_common::Serial for AccountSignatures { - fn serial(&self, out: &mut W) -> Result<(), W::Err> { - (self.sigs.len() as u8).serial(out)?; - for (k, v) in self.sigs.iter() { - k.serial(out)?; - (v.len() as u8).serial(out)?; - for (ki, sig) in v.iter() { - ki.serial(out)?; - // ed25519 scheme tag. - 0u8.serial(out)?; - out.write_all(&sig.sig)?; - } - } - Ok(()) - } -} - -impl contracts_common::Deserial for AccountSignatures { - fn deserial(source: &mut R) -> contracts_common::ParseResult { - // We essentially unroll the definitions of `deserial_map_no_length` here since - // the inner type, the Signature, does not have exactly the right - // serialization instance that we need. - use contracts_common::Get; - let outer_len = u8::deserial(source)?; - let mut last = None; - let mut sigs = BTreeMap::new(); - for _ in 0..outer_len { - let idx = source.get()?; - if last >= Some(idx) { - return Err(contracts_common::ParseError {}); - } - last = Some(idx); - let inner_len: u8 = source.get()?; - let mut inner_map = BTreeMap::new(); - let mut last_inner = None; - for _ in 0..inner_len { - let k = source.get()?; - let sig = match source.get()? { - SchemeId::Ed25519 => { - let mut sig = vec![0u8; ED25519_SIGNATURE_LENGTH]; - source.read_exact(&mut sig)?; - Signature { sig } - } - }; - - if let Some((old_k, old_v)) = last_inner.take() { - if k <= old_k { - return Err(contracts_common::ParseError {}); - } - inner_map.insert(old_k, old_v); - } - last_inner = Some((k, sig)); - } - if let Some((k, v)) = last_inner { - inner_map.insert(k, v); - } - sigs.insert(idx, inner_map); - } - Ok(Self { sigs }) - } -} - -/// A signer with a number of keys, the amount of which affects the cost of -/// transactions. -#[derive(Copy, Clone, Debug)] -pub struct Signer { - /// The number of keys used for signing. - pub(crate) num_keys: u32, -} - -/// A transfer from a contract to an account. -#[derive(Debug, PartialEq, Eq)] -pub struct Transfer { - /// The sender contract. - pub from: ContractAddress, - /// The amount transferred. - pub amount: Amount, - /// The receive account. - pub to: AccountAddress, -} - -/// Represents a successful deployment of a [`ContractModule`]. -#[derive(Debug, PartialEq, Eq)] -pub struct ModuleDeploySuccess { - /// The reference of the module deployed. - pub module_reference: ModuleReference, - /// The energy used for deployment. - pub energy_used: Energy, - /// Cost of transaction. - pub transaction_fee: Amount, -} - -/// An error that occurred while deploying a [`ContractModule`]. -#[derive(Debug, Error)] -#[error( - "Module deployment failed after consuming {energy_used}NRG ({transaction_fee} microCCD) with \ - error {kind}." -)] -pub struct ModuleDeployError { - /// The energy used for deployment. - pub energy_used: Energy, - /// The transaction fee. This is the amount charged to the `sender` - /// account. - pub transaction_fee: Amount, - /// The specific reason for why the deployment failed. - pub kind: ModuleDeployErrorKind, -} - -/// The specific kind of error that occurred while deploying a -/// [`ContractModule`]. -#[derive(Debug, Error)] -pub enum ModuleDeployErrorKind { - /// The module provided is not valid. - #[error("Module is invalid due to: {0}")] - InvalidModule(#[from] ModuleInvalidError), - /// The sender account does not have sufficient funds to pay for the - /// deployment. - #[error("Sender does not have sufficient funds to pay for the energy")] - InsufficientFunds, - /// The sender account deploying the module does not exist. - #[error("Sender account {} does not exist", 0.0)] - SenderDoesNotExist(#[from] AccountDoesNotExist), - /// The module has already been deployed. - #[error("Module with reference {0} already exists")] - DuplicateModule(ModuleReference), - /// The module version is not supported. - #[error("Wasm version {0} is not supported")] - UnsupportedModuleVersion(WasmVersion), -} - -/// An error that can occur while loading a smart contract module. -#[derive(Debug, Error)] -#[error("Could not load the module file '{path}' due to: {kind}")] -pub struct ModuleLoadError { - /// The module file. - pub path: PathBuf, - /// The reason why loading the module failed. - pub kind: ModuleLoadErrorKind, -} - -/// The specific reason why loading a module failed. -#[derive(Debug, Error)] -pub enum ModuleLoadErrorKind { - /// Failed to open the module file for reading. - #[error("Could not open the file for reading to: {0}")] - OpenFile(#[from] std::io::Error), - /// Failed to read the module from the file. - #[error("Could not read the module due to: {0}")] - ReadModule(#[from] ModuleReadError), - /// The module version is not supported. - #[error("The module has wasm version {0}, which is not supported")] - UnsupportedModuleVersion(WasmVersion), -} - -/// The error produced when trying to read a smart contract -/// module from a file. -#[derive(Debug, Error)] -#[error("The module could not be read due to: {0}")] -pub struct ModuleReadError(#[from] pub(crate) anyhow::Error); - -/// The error produced when trying to parse a smart contract module. -#[derive(Debug, Error)] -#[error("The module is invalid to: {0}")] -pub struct ModuleInvalidError(#[from] pub(crate) anyhow::Error); - -/// Represents a successful initialization of a contract. -#[derive(Debug)] -pub struct ContractInitSuccess { - /// The address of the new instance. - pub contract_address: ContractAddress, - /// Contract events (logs) produced during initialization. - pub events: Vec, - /// Energy used. - pub energy_used: Energy, - /// Cost of transaction. - pub transaction_fee: Amount, -} - -/// An error that occurred in [`Chain::contract_init`]. -#[derive(Debug, Error)] -#[error( - "Contract initialization failed after consuming {energy_used}NRG ({transaction_fee} microCCD) \ - with error {kind}." -)] -pub struct ContractInitError { - /// Energy used. - pub energy_used: Energy, - /// The transaction fee. This is the amount charged to the `sender` - /// account. - pub transaction_fee: Amount, - /// The specific reason for why the initialization failed. - pub kind: ContractInitErrorKind, -} - -/// Types of errors that can occur in [`Chain::contract_init`]. -#[derive(Debug, Error)] -pub enum ContractInitErrorKind { - /// Initialization during execution. - #[error("Failed with an execution error: {error:?}")] - ExecutionError { - /// The reason for why the contract initialization failed. - error: InitExecutionError, - }, - /// Ran out of energy. - #[error("Ran out of energy")] - OutOfEnergy, - /// Module has not been deployed in the test environment. - #[error("{0}")] - ModuleDoesNotExist(#[from] ModuleDoesNotExist), - /// The specified contract does not exist in the module. - #[error("The contract (init name) '{name}' does not exist in the module")] - ContractNotPresentInModule { - /// The name of the contract (init method) which is not present. - name: OwnedContractName, - }, - /// The sender account has not been created in test environment. - #[error("Sender missing: {0}")] - SenderDoesNotExist(#[from] AccountDoesNotExist), - /// The invoker account does not have enough funds to pay for the energy - /// reserved. - #[error("Invoker does not have enough funds to pay for the energy")] - InsufficientFunds, - /// The invoker account does not have enough funds to pay for the amount. - /// However it does it have enough funds for the energy reserved. - #[error("Invoker does not have enough funds to pay for the amount")] - AmountTooLarge, - /// The parameter is too large. - #[error("The provided parameter exceeds the maximum size allowed")] - ParameterTooLarge, -} - -/// The reason for why a contract initialization failed during execution. -#[derive(Debug)] -pub enum InitExecutionError { - /// The contract rejected. - Reject { - /// The error code for why it rejected. - reason: i32, - /// The return value. - return_value: ReturnValue, - }, - /// The contract trapped. - Trap { error: ExecutionError }, - /// The contract ran out of energy. - OutOfEnergy, -} - -/// An error that occurred while executing a contract init or receive function. -#[derive(Debug, Error)] -#[error("The contract execution halted due to: {0}")] -pub struct ExecutionError(#[from] pub(crate) anyhow::Error); - -/// Represents a successful contract update (or invocation). -#[derive(Debug)] -pub struct ContractInvokeSuccess { - /// Host events that occurred. This includes interrupts, resumes, and - /// upgrades. - pub trace_elements: Vec, - /// Energy used. - pub energy_used: Energy, - /// Cost of transaction. - pub transaction_fee: Amount, - /// The returned value. - pub return_value: ReturnValue, - /// Whether the state of the invoked contract was changed. - pub state_changed: bool, - /// The new balance of the smart contract. - pub new_balance: Amount, -} - -/// Represents a successful external contract invocation. -#[derive(Debug)] -pub struct ContractInvokeExternalSuccess { - /// Host events that occurred. This includes interrupts, resumes, and - /// upgrades. - pub trace_elements: Vec, - /// The energy used. - pub energy_used: Energy, - /// The returned value. - pub return_value: ReturnValue, -} - -impl ContractInvokeSuccess { - /// Extract all the events logged by all the contracts in the invocation. - /// The events are returned in the order that they are emitted, and are - /// paired with the address of the contract that emitted it. - /// - /// Only events from effective trace elements are included. See - /// [`Self::effective_trace_elements`] for more details. - pub fn events(&self) -> impl Iterator { - self.effective_trace_elements().flat_map(|cte| { - if let ContractTraceElement::Updated { data } = cte { - Some((data.address, data.events.as_slice())) - } else { - None - } - }) - } - - /// Extract the transfers **to accounts** that occurred during - /// invocation. The return value is an iterator over triples `(from, amount, - /// to)` where `from` is the sender contract, and `to` is the receiver - /// account. The transfers are returned in the order that they occurred. - /// - /// Only tranfers from effective trace elements are included. See - /// [`Self::effective_trace_elements`] for more details. - pub fn account_transfers( - &self, - ) -> impl Iterator + '_ { - self.effective_trace_elements().flat_map(|cte| { - if let ContractTraceElement::Transferred { from, amount, to } = cte { - Some((*from, *amount, *to)) - } else { - None - } - }) - } - - /// Get an iterator over references of all the [`ContractTraceElement`]s - /// that have *not* been rolled back. - /// - /// The trace elements returned here corresponds to the ones returned by the - /// node. - /// - /// See also [`Self::effective_trace_elements_cloned`] for a version with - /// clones. - pub fn effective_trace_elements(&self) -> impl Iterator { - self.trace_elements.iter().filter_map(|cte| match cte { - DebugTraceElement::Regular { trace_element, .. } => Some(trace_element), - DebugTraceElement::WithFailures { .. } => None, - }) - } - - /// Get an iterator over clones of all the [`ContractTraceElement`]s that - /// have *not* been rolled back. - /// - /// The trace elements returned here corresponds to the ones returned by the - /// node. - /// - /// See also [`Self::effective_trace_elements`] for a version with - /// references. - pub fn effective_trace_elements_cloned(&self) -> Vec { - self.trace_elements - .iter() - .filter_map(|cte| match cte { - DebugTraceElement::Regular { trace_element, .. } => Some(trace_element.clone()), - DebugTraceElement::WithFailures { .. } => None, - }) - .collect() - } - - /// Get the successful trace elements grouped by which contract they - /// originated from. - pub fn trace_elements(&self) -> BTreeMap> { - let mut map: BTreeMap> = BTreeMap::new(); - for event in self.effective_trace_elements() { - map.entry(Self::extract_contract_address(event)) - .and_modify(|v| v.push(event.clone())) - .or_insert_with(|| vec![event.clone()]); - } - map - } - - /// Get the contract address that this event relates to. - /// This means the `address` field for all variant except `Transferred`, - /// where it returns the `from`. - fn extract_contract_address(element: &ContractTraceElement) -> ContractAddress { - match element { - ContractTraceElement::Interrupted { address, .. } => *address, - ContractTraceElement::Resumed { address, .. } => *address, - ContractTraceElement::Upgraded { address, .. } => *address, - ContractTraceElement::Updated { - data: InstanceUpdatedEvent { address, .. }, - } => *address, - ContractTraceElement::Transferred { from, .. } => *from, - } - } - - /// Get the successful contract updates that happened in the transaction. - /// The order is the order of returns. Concretely, if A calls B (and no - /// other calls are made) then first there will be "B updated" event, - /// followed by "A updated", assuming the invocation of both succeeded. - pub fn updates(&self) -> impl Iterator { - self.effective_trace_elements().filter_map(|e| { - if let ContractTraceElement::Updated { data } = e { - Some(data) - } else { - None - } - }) - } - - /// Check whether any rollbacks occurred. - /// - /// That is, whether any contract calls failed which lead to state and - /// balances being rolled back. - /// - /// If `true` is returned, the relevant traces can be seen in the - /// `self.trace_elements` vector. - pub fn rollbacks_occurred(&self) -> bool { - self.trace_elements - .iter() - .any(|element| matches!(element, DebugTraceElement::WithFailures { .. })) - } -} - -/// A wrapper for [`ContractTraceElement`], which provides additional -/// information for testing and debugging. Most notably, it contains trace -/// elements for failures, which are normally discarded by the node. -#[derive(Debug)] -pub enum DebugTraceElement { - /// A regular trace element with some additional data, e.g., energy usage - /// and the entrypoint. - /// This variant may be included included in the `WithFailures` list of - /// trace elements. - Regular { - /// The entrypoint. - entrypoint: OwnedEntrypointName, - /// The trace element. - trace_element: ContractTraceElement, - /// The energy used so far. - energy_used: Energy, - }, - /// One or multiple trace elements that fail. Useful for debugging. - /// This variant also contains additional information, such as the error, - /// entrypoint, and energy usage. - WithFailures { - /// The address of the contract which failed. - /// This will always match the address in the last element in - /// `trace_elements` if the vector isn't empty. - contract_address: ContractAddress, - /// The entrypoint which failed. - /// This will always match the address in the last element in - /// `trace_elements` if the vector isn't empty. - entrypoint: OwnedEntrypointName, - /// The error returned. - error: InvokeExecutionError, - /// Intermediate [`DebugTraceElement`]s which occurred prior to failing. - /// These are the elements which are normally discared by the node. - trace_elements: Vec, - /// The energy used so far. - energy_used: Energy, - }, -} - -/// The reason for why a contract invocation failed during execution. -#[derive(Debug)] -pub enum InvokeExecutionError { - /// The contract rejected. - Reject { - /// The error code for why it rejected. - reason: i32, - /// The return value. - return_value: ReturnValue, - }, - /// The contract trapped. - Trap { error: ExecutionError }, -} - -/// An error that occurred during a [`Chain::contract_update`] or -/// [`Chain::contract_invoke`]. -#[derive(Debug, Error)] -#[error( - "Contract invocation failed after using {energy_used}NRG ({transaction_fee} microCCD) with \ - error {kind}." -)] -pub struct ContractInvokeError { - /// The energy used. - pub energy_used: Energy, - /// The transaction fee. For [`Chain::contract_update`], this is the amount - /// charged to the `invoker` account. - pub transaction_fee: Amount, - /// Trace elements that occurred before the contract failed. - pub trace_elements: Vec, - /// The specific reason for why the invocation failed. - pub kind: ContractInvokeErrorKind, -} - -/// The error kinds that can occur during [`Chain::contract_update`] or -/// [`Chain::contract_invoke`]. -#[derive(Debug, Error)] -pub enum ContractInvokeErrorKind { - /// Invocation failed during execution. - #[error("Failed during execution: {failure_kind:?}")] - ExecutionError { failure_kind: v1::InvokeFailure }, - /// Ran out of energy. - #[error("Ran out of energy")] - OutOfEnergy, - /// The balance of an account or contract overflowed. - /// If you are seeing this error, lower the [`Amount`]s used in your tests. - #[error("The balance of an account or contract overflowed")] - BalanceOverflow, - /// Module has not been deployed in test environment. - #[error("{0}")] - ModuleDoesNotExist(#[from] ModuleDoesNotExist), - /// Contract instance has not been initialized in the test environment. - #[error("{0}")] - ContractDoesNotExist(#[from] ContractDoesNotExist), - /// Entrypoint does not exist and neither does the fallback entrypoint. - #[error("{0}")] - EntrypointDoesNotExist(#[from] EntrypointDoesNotExist), - /// The invoker account has not been created in the test environment. - #[error("Invoker missing: {0}")] - InvokerDoesNotExist(#[from] AccountDoesNotExist), - /// The sender does not exist in the test environment. - #[error("Sender missing: the object with address '{0}' does not exist")] - SenderDoesNotExist(Address), - /// The invoker account does not have enough funds to pay for the energy - /// reserved. - #[error("Invoker does not have enough funds to pay for the energy")] - InsufficientFunds, - /// The invoker account does not have enough funds to pay for the amount. - /// However it does it have enough funds for the energy reserved. - #[error("Invoker does not have enough funds to pay for the amount")] - AmountTooLarge, - /// The parameter is too large. - #[error("The provided parameter exceeds the maximum size allowed")] - ParameterTooLarge, -} - -/// The error returned when external contract invocations fail. -#[derive(Debug, Error)] -pub enum ContractInvokeExternalError { - /// The external contract invocation was executed, but resulted in a - /// failure. - #[error( - "External contract invocation was executed, but failed after using {energy_used}NRG with \ - error {reason:?}." - )] - Failure { - /// The reason why the invoke failed. - reason: sdk::types::RejectReason, - /// The energy used before failure. - energy_used: Energy, - /// The value returned. - return_value: ReturnValue, - }, - /// The external contract invocation failed due to an external node error. - #[error("External contract invocation failed due to an external node error: {error}")] - ExternalNodeError { - #[from] - /// The external node error. - error: ExternalNodeError, - }, -} - -/// A balance error which can occur when transferring [`Amount`]s. -#[derive(Debug, PartialEq, Eq, Error)] -pub(crate) enum BalanceError { - /// The sender had insufficient balance. - #[error("The sender had insufficient balance.")] - Insufficient, - /// The balance change resulted in an overflow. - /// - /// This is a configuration error in the tests, where unrealistic balances - /// have been set, and should thus be *unrecoverable*. - /// - /// On the chain there is roughly 10 billion CCD, so an overflow wil never - /// occur when adding CCDs. - #[error("An overflow on CCD amounts occurred.")] - Overflow, -} - -/// Errors related to transfers. -#[derive(Debug, PartialEq, Eq, Error)] -pub(crate) enum TransferError { - /// The receiver does not exist. - #[error("The receiver does not exist.")] - ToMissing, - /// A balance error occurred. - #[error("A balance error occurred: {error:?}")] - BalanceError { - #[from] - error: BalanceError, - }, -} - -/// The entrypoint does not exist. -#[derive(PartialEq, Eq, Debug, Error)] -#[error("Entrypoint '{entrypoint}' does not exist.")] -pub struct EntrypointDoesNotExist { - /// The missing entrypoint. - pub entrypoint: OwnedEntrypointName, -} - -/// The contract module does not exist. -#[derive(Debug, Error)] -#[error("Module '{module_reference}' does not exist.")] -pub struct ModuleDoesNotExist { - /// The reference of the missing module. - pub module_reference: ModuleReference, -} - -/// The contract instance does not exist. -#[derive(Debug, Error)] -#[error("Contract instance '{address}' does not exist.")] -pub struct ContractDoesNotExist { - /// The address of the missing contract. - pub address: ContractAddress, -} - -/// The account does not exist. -#[derive(Debug, Error)] -#[error("Account '{address}' does not exist.")] -pub struct AccountDoesNotExist { - /// The address of the missing account. - pub address: AccountAddress, -} - -/// The provided exchange rates are not valid. -/// Meaning that they do not correspond to one energy costing less than -/// `u64::MAX / 100_000_000_000`. -#[derive(Debug, Error)] -#[error("An exchange rate was too high.")] -pub struct ExchangeRateError; - -/// A [`Signer`] cannot be created with `0` keys. -#[derive(Debug, Error)] -#[error("Any signer must have at least one key.")] -pub struct ZeroKeysError; - -/// Errors that occur while setting up the connection to an external node. -#[derive(Debug, thiserror::Error)] -pub enum SetupExternalNodeError { - /// It was not possible to connect to a node on the provided endpoint. - #[error("Could not connect to the provided endpoint due to: {error}")] - CannotConnect { - /// The inner error. - #[from] - error: sdk::endpoints::Error, - }, - /// The attempt to connect to an external node timed out. - #[error("The attempt to connect to an external node timed out.")] - ConnectTimeout, - /// The query to check the `external_query_block` timed out. - #[error("The query to check the `external_query_block` timed out.")] - CheckQueryBlockTimeout, - /// The specified external query block does not exist. - #[error("The specified external query block {query_block} does not exist.")] - QueryBlockDoesNotExist { query_block: BlockHash }, - /// Could not check the existence of the specified query block or the last - /// final block. - #[error( - "Could not check the existence of the specified query block or the last final block due \ - to: {error}" - )] - CannotCheckQueryBlockExistence { - /// The inner error. - error: sdk::v2::RPCError, - }, -} - -/// Errors that occur while trying to communicate with an external node. -#[derive(Debug, Error)] -pub enum ExternalNodeError { - /// An external node has not been configured. - #[error("An external node has not been configured.")] - NotConfigured, - /// The query could not be performed. - #[error("Could not perform the query: {error}")] - QueryError { - #[from] - error: sdk::endpoints::QueryError, - }, - /// The query timed out. - #[error("The query timed out.")] - QueryTimeout, -} - -/// The error returned when an external node has not been configured prior to -/// using it. -#[derive(Debug, Error, PartialEq, Eq)] -#[error("An external node has not been configured.")] -pub struct ExternalNodeNotConfigured; - -#[derive(Debug, Error)] -pub enum ChainBuilderError { - /// The provided exchange rates are not valid. - /// Meaning that they do not correspond to one energy costing less than - /// `u64::MAX / 100_000_000_000`. - #[error("An exchange rate was too high.")] - ExchangeRateError, - /// An error occurred while setting up the connection to an external node. - #[error("Error occurred while setting up the connection to an external node: {error}")] - SetupExternalNodeError { - #[from] - error: SetupExternalNodeError, - }, - /// Error occurred while using the external node for querying chain - /// parameters such as the block time or exchange rates. - #[error("Error occurred while using the external node for querying chain parameters: {error}")] - ExternalNodeError { - #[from] - error: ExternalNodeError, - }, - /// Could not configure the block time because both the - /// [`ChainBuilder::block_time`] and - /// [`ChainBuilder::block_time_from_external`] were provided, which is not - /// allowed. - #[error( - "Conflicting block time configuration: `block_time` and `block_time_from_external` cannot \ - both be used." - )] - ConflictingBlockTime, - /// Could not configure the microCCD/euro exchange rate because both the - /// [`ChainBuilder::micro_ccd_per_euro`] and - /// [`ChainBuilder::micro_ccd_per_euro_from_external`] were provided, which - /// is not allowed. - #[error( - "Conflicting microCCD per euro configuration: `micro_ccd_per_euro` and \ - `micro_ccd_per_euro_from_external` cannot both be used." - )] - ConflictingMicroCCDPerEuro, - /// Could not configure the euro/energy exchange rate because both the - /// [`ChainBuilder::euro_per_energy`] and - /// [`ChainBuilder::euro_per_energy_from_external`] were provided, which is - /// not allowed. - #[error( - "Conflicting euro per energy configuration: `euro_per_energy` and \ - `euro_per_energy_from_external` cannot both be used." - )] - ConflictingEuroPerEnergy, - /// A configuration option that requires an external node connection was - /// used without [`ChainBuilder::external_node_connection`]. - #[error( - "A configuration method that requires an external node connection was called without \ - `ChainBuilder::external_node_connection`." - )] - MissingExternalConnection, -} - -/// The block time overflowed during a call to `Chain::tick_block_time`. -#[derive(Debug, Error, PartialEq, Eq)] -#[error("The block time overflowed during a call to `Chain::tick_block_time`.")] -pub struct BlockTimeOverflow; - -/// The contract address of an contract on an external node. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct ExternalContractAddress { - /// The contract address. - pub(crate) address: ContractAddress, -} - -/// The address of an account on an external node. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct ExternalAccountAddress { - /// The account address. - pub(crate) address: AccountAddress, -} - -/// Either an external contract address or an external account address. -/// -/// External means that it is an entity that exists on an external node. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub enum ExternalAddress { - Account(ExternalAccountAddress), - Contract(ExternalContractAddress), -} - -impl ExternalAddress { - /// Convert to an [`Address`]. - /// - /// This is an internal method instead of a [`From`] implementation, as it - /// should be difficult to conflate external and regular addresses. - pub(crate) fn to_address(self) -> Address { - match self { - ExternalAddress::Account(ExternalAccountAddress { address }) => { - Address::Account(address) - } - ExternalAddress::Contract(ExternalContractAddress { address }) => { - Address::Contract(address) - } - } - } -} - -/// Data needed to invoke an external smart contract instance. -/// -/// This is nearly identical to [`UpdateContractPayload`] except that it uses an -/// [`ExternalContractAddress`] instead of an [`ContractAddress`]. -#[derive(Debug, Clone)] -pub struct InvokeExternalContractPayload { - /// Send the given amount of CCD together with the message to the - /// contract instance. - pub amount: Amount, - /// Address of the external contract instance to invoke. - pub address: ExternalContractAddress, - /// Name of the method to invoke on the contract. - pub receive_name: OwnedReceiveName, - /// Message to send to the contract instance. - pub message: OwnedParameter, -} - -impl From for ExternalAddress { - fn from(addr: ExternalAccountAddress) -> Self { Self::Account(addr) } -} - -impl From for ExternalAddress { - fn from(addr: ExternalContractAddress) -> Self { Self::Contract(addr) } -} diff --git a/concordium-smart-contract-testing/tests/account-signature-checks.rs b/concordium-smart-contract-testing/tests/account-signature-checks.rs deleted file mode 100644 index 0dc3b2c8..00000000 --- a/concordium-smart-contract-testing/tests/account-signature-checks.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! This module contains tests for checking account signatures, and retrieving -//! account's public keys. -use concordium_base::{ - contracts_common::{self, AccountBalance, AccountThreshold, SignatureThreshold}, - id::types::AccountKeys, - transactions::AccountAccessStructure, -}; -use concordium_smart_contract_testing::*; -mod helpers; - -/// Test that we can query the correct account keys. -#[test] -fn test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - let mut csprng = rand::thread_rng(); - let acc_keys = AccountKeys::generate( - AccountThreshold::TWO, - &[ - (3.into(), SignatureThreshold::TWO, &[ - 7.into(), - 8.into(), - 17.into(), - ]), - (7.into(), SignatureThreshold::ONE, &[ - 3.into(), - 8.into(), - 33.into(), - ]), - (37.into(), SignatureThreshold::ONE, &[ - 2.into(), - 8.into(), - 255.into(), - ]), - (254.into(), SignatureThreshold::TWO, &[ - 1.into(), - 2.into(), - 3.into(), - ]), - ], - &mut csprng, - ); - let acc_structure: AccountAccessStructure = (&acc_keys).into(); - chain.create_account(Account::new_with_keys( - helpers::ACC_0, - AccountBalance { - total: initial_balance, - staked: Amount::zero(), - locked: Amount::zero(), - }, - acc_structure.clone(), - )); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("account-signature-checks.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_contract".into()), - mod_ref: res_deploy.module_reference, - - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_invoke_get_keys = chain - .contract_invoke( - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.get_keys".into()), - message: OwnedParameter::from_serial(&helpers::ACC_0) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Querying contract should work"); - let rv = - contracts_common::from_bytes::(&res_invoke_get_keys.return_value) - .expect("Return value should be deserializable."); - assert_eq!( - rv, acc_structure, - "Retrieved account structure does not match the expected one." - ); - - // Data is a serialization of a 30-element byte array with 4 byte length prefix - // (in little endian). - let data: [u8; 34] = [ - 30, 0, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, - ]; - let signatures = AccountSignatures::from(acc_keys.sign_data(&data[4..])); - - let res_invoke_check_signature = chain - .contract_invoke( - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.check_signature".into()), - message: OwnedParameter::from_serial(&(helpers::ACC_0, data, &signatures)) - .expect("Enough space."), - amount: Amount::zero(), - }, - ) - .expect("Querying contract should work"); - let rv = contracts_common::from_bytes::(&res_invoke_check_signature.return_value) - .expect("Return value should be deserializable."); - assert_eq!( - rv, 0, - "Signature check should succeed, the return value should be 0." - ); -} diff --git a/concordium-smart-contract-testing/tests/all_new_host_functions.rs b/concordium-smart-contract-testing/tests/all_new_host_functions.rs deleted file mode 100644 index c523ddb0..00000000 --- a/concordium-smart-contract-testing/tests/all_new_host_functions.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! This module tests that a module containing all the new V1 host functions is -//! accepted. It serves as a basic integration test. Individual functions either -//! have tests in wasm-chain-integration, or as part of other scheduler tests if -//! they require more complex interactions with the chain. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_all_new_host_functions() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("all-new-host-functions.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); -} diff --git a/concordium-smart-contract-testing/tests/basics.rs b/concordium-smart-contract-testing/tests/basics.rs deleted file mode 100644 index 7f0e526b..00000000 --- a/concordium-smart-contract-testing/tests/basics.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! This module contains tests that test various basic things, such as state -//! reentry and energy usage and amounts charged. -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn basics() { - let mut chain = Chain::new_with_time_and_rates( - SlotTime::from_timestamp_millis(0), - // Set a specific value, taken from testnet, to compare the exact amounts charged. - ExchangeRate::new_unchecked(3127635127520773120, 24857286553), - ExchangeRate::new_unchecked(1, 50000), - ) - .expect("Values known to be in range."); - - let initial_balance = Amount::from_ccd(100_000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let deployment = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("fib.wasm")).expect("Module should exist."), - ) - .expect("Deploying valid module should work"); - - let init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - amount: Amount::zero(), - mod_ref: deployment.module_reference, - init_name: OwnedContractName::new_unchecked("init_fib".into()), - param: OwnedParameter::empty(), - }, - ) - .expect("Initializing valid contract should work"); - - let update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - amount: Amount::zero(), - address: init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("fib.receive".into()), - message: OwnedParameter::from_serial(&6u64).expect("Parameter has valid size"), - }, - ) - .expect("Updating valid contract should work"); - - let view = chain - .contract_invoke( - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - amount: Amount::zero(), - address: init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("fib.view".into()), - message: OwnedParameter::empty(), - }, - ) - .expect("Invoking get should work"); - - // Check the state and return values. - assert!(chain.get_contract(init.contract_address).is_some()); - assert!(update.state_changed); - let expected_res = u64::to_le_bytes(13); - assert_eq!(update.return_value, expected_res); - // Assert that the updated state is persisted. - assert_eq!(view.return_value, expected_res); - - // Check that the account was correctly charged for all transactions. - // This also asserts that the account wasn't charged for the invoke. - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some( - initial_balance - - deployment.transaction_fee - - init.transaction_fee - - update.transaction_fee - ) - ); - - // Check that the energy usage matches the node. - assert_eq!(deployment.energy_used, 1067.into()); - assert_eq!(init.energy_used, 771.into()); - assert_eq!(update.energy_used, 8198.into()); - assert_eq!(view.energy_used, 316.into()); - - // Check that the amounts charged matches the node. - assert_eq!( - deployment.transaction_fee, - Amount::from_micro_ccd(2_685_078) - ); - assert_eq!(init.transaction_fee, Amount::from_micro_ccd(1_940_202)); - assert_eq!(update.transaction_fee, Amount::from_micro_ccd(20_630_050)); -} diff --git a/concordium-smart-contract-testing/tests/checkpointing.rs b/concordium-smart-contract-testing/tests/checkpointing.rs deleted file mode 100644 index db096b9e..00000000 --- a/concordium-smart-contract-testing/tests/checkpointing.rs +++ /dev/null @@ -1,398 +0,0 @@ -//! This module contains tests for the checkpointing/rollback functionality of -//! the library. -//! -//! When a contract entrypoint execution fails, any changes it has -//! made must be rolled back. That is also the case if a nested contract call -//! fails. -use concordium_smart_contract_testing::*; -mod helpers; - -/// This test has the following call pattern: -/// A -/// --> B -/// --> A -/// <-- -/// B(trap) -/// A <-- -/// The state at A should be left unchanged by the changes of the -/// 'inner' invocation on contract A. A correctly perceives B's -/// trapping signal. Only V1 contracts are being used. -#[test] -fn test_case_1() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("checkpointing.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init_a = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_a".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_init_b = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_b".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let forward_parameter = ( - res_init_a.contract_address, - 0u16, // length of empty parameter - EntrypointName::new_unchecked("a_modify"), - Amount::zero(), - ); - let forward_parameter_len = to_bytes(&forward_parameter).len(); - let parameter = ( - res_init_b.contract_address, - forward_parameter_len as u16, - forward_parameter, - EntrypointName::new_unchecked("b_forward_crash"), - Amount::zero(), - ); - - let update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_a.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.a_modify_proxy".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size"), - // We supply one microCCD as we expect a trap - // (see contract for details). - amount: Amount::from_micro_ccd(1), - }, - ) - .expect("Updating contract should succeed"); - - // Check that rollbacks occurred. - assert!(update.rollbacks_occurred()); - - // Check that all the trace elements are as expected, including the ones - // resulting in a failure. Some imports to simplify the names in the assert. - use ContractTraceElement::*; - use DebugTraceElement::*; - use InvokeExecutionError::*; - assert!(matches!(&update.trace_elements[..], [ - Regular { - trace_element: Interrupted { .. }, - .. - }, - WithFailures { - error: Trap { .. }, - trace_elements, - .. - }, - Regular { - trace_element: Resumed { .. }, - .. - }, - Regular { - trace_element: Updated { .. }, - .. - } - ] if matches!(trace_elements[..], [Regular { trace_element: Interrupted {..}, ..}, Regular { trace_element: Updated {..}, .. }, Regular { trace_element: Resumed {..}, .. }]))); -} - -/// This test has the following call pattern: -/// A -/// --> B -/// --> A (no modification, just lookup entry) -/// <-- -/// B -/// A <-- -/// -/// The state at A should be left unchanged. -/// The iterator initialized at the outer A should point to the same -/// entry as before the call. That is, the iterator should not -/// be affected by the inner iterator. Only V1 contracts are -/// being used. -#[test] -fn test_case_2() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("checkpointing.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init_a = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_a".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_init_b = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_b".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let forward_parameter = ( - res_init_a.contract_address, - 0u16, // length of empty parameter - EntrypointName::new_unchecked("a_no_modify"), - Amount::zero(), - ); - let forward_parameter_len = to_bytes(&forward_parameter).len(); - let parameter = ( - res_init_b.contract_address, - forward_parameter_len as u16, - forward_parameter, - EntrypointName::new_unchecked("b_forward"), - Amount::zero(), - ); - - let trace = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_a.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.a_modify_proxy".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size"), - // We supply zero microCCD as we're instructing the contract to not expect - // state modifications. Also, the contract does not expect - // errors, i.e., a trap signal from underlying invocations. - // The 'inner' call to contract A does not modify the state. - // See the contract for details. - amount: Amount::zero(), - }, - ) - .expect("Updating contract should succeed"); - // Make sure that we have exactly 3 updates with the right entrypoints (meaning - // that all calls succeeded). - let mut updates = trace.updates(); - // first the inner "a_no_modify" executed to completion. - let update_a_no_modify = updates.next().expect("Expected an event."); - assert_eq!(update_a_no_modify.address, res_init_a.contract_address); - assert_eq!(update_a_no_modify.receive_name, "a.a_no_modify"); - let update_b = updates.next().expect("Expected an event."); - assert_eq!(update_b.address, res_init_b.contract_address); - assert_eq!(update_b.receive_name, "b.b_forward"); - let update_a_modify_proxy = updates.next().expect("Expected an event."); - assert_eq!(update_a_modify_proxy.address, res_init_a.contract_address); - assert_eq!(update_a_modify_proxy.receive_name, "a.a_modify_proxy"); - assert!(updates.next().is_none(), "No more updates expected."); - - // Check that no rollbacks occurred. - assert!(!trace.rollbacks_occurred()); -} - -/// This test has the following call pattern: -/// A -/// --> Transfer -/// A <-- -/// -/// The state at A should be left unchanged. -/// The iterator initialized at A should after the call point to the -/// same entry as before the call. Only V1 contracts are being -/// used. -#[test] -fn test_case_3() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - chain.create_account(Account::new(helpers::ACC_1, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("checkpointing.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init_a = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_a".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_b".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_a.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.a_modify_proxy".into()), - message: OwnedParameter::from_serial(&helpers::ACC_1) - .expect("Parameter has valid size"), - // We supply three micro CCDs as we're instructing the contract to carry out a - // transfer instead of a call. See the contract for - // details. - amount: Amount::from_micro_ccd(3), - }, - ) - .expect("Updating contract should succeed"); -} - -/// This test has the following call pattern: -/// A -/// --> B -/// --> A modify -/// <-- -/// B -/// A <-- -/// -/// The state at A should have changed according to the 'inner' -/// invocation on contract A. Only V1 contracts are being used. -#[test] -fn test_case_4() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("checkpointing.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init_a = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_a".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_init_b = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_b".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let forward_parameter = ( - res_init_a.contract_address, - 0u16, // length of empty parameter - EntrypointName::new_unchecked("a_modify"), - Amount::zero(), - ); - let forward_parameter_len = to_bytes(&forward_parameter).len(); - let parameter = ( - res_init_b.contract_address, - forward_parameter_len as u16, - forward_parameter, - EntrypointName::new_unchecked("b_forward"), - Amount::zero(), - ); - - let update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_a.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.a_modify_proxy".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size"), - // We supply four CCDs as we're instructing the contract to expect state - // modifications being made from the 'inner' contract A - // call to be in effect when returned to the caller (a.a_modify_proxy). - // See the contract for details. - amount: Amount::from_micro_ccd(4), - }, - ) - .expect("Updating contract should succeed"); - - // Check that no rollbacks occurred. - assert!(!update.rollbacks_occurred()); -} diff --git a/concordium-smart-contract-testing/tests/counter.rs b/concordium-smart-contract-testing/tests/counter.rs deleted file mode 100644 index c6b9845e..00000000 --- a/concordium-smart-contract-testing/tests/counter.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! This module tests calling a contract from a contract and inspecting the -//! return message. Concretely it invokes a counter contract that maintains a -//! 64-bit counter in its state. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_counter() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("call-counter.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_counter".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("counter.inc".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_counter_state(&mut chain, res_init.contract_address, 1); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("counter.inc".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_counter_state(&mut chain, res_init.contract_address, 2); - - let parameter = ( - res_init.contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("inc"), - Amount::zero(), - ); - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("counter.inc10".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_counter_state(&mut chain, res_init.contract_address, 12); -} - -/// Looks up in the root of the state trie and compares the value with the -/// `expected`. -fn assert_counter_state(chain: &mut Chain, contract_address: ContractAddress, expected: u64) { - assert_eq!( - chain - .contract_state_lookup(contract_address, &[0, 0, 0, 0, 0, 0, 0, 0]) - .unwrap(), - u64::to_le_bytes(expected) - ); -} diff --git a/concordium-smart-contract-testing/tests/error_codes.rs b/concordium-smart-contract-testing/tests/error_codes.rs deleted file mode 100644 index 64eeff82..00000000 --- a/concordium-smart-contract-testing/tests/error_codes.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! This module tests invoking a V1 contract which invokes an operation which -//! fails. The test is to make sure error codes are correctly returned to the -//! contract. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_error_codes() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("caller.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_caller".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // Invoke an entrypoint that calls the "fail" entrypoint. - // The expected return code is - // 0x0100_ffff_ffef - // because - // - the return value is pushed (hence 01) - // - the call to "fail" fails with a "logic error" (hence the 00) - // - the return value is -17 (which when converted with two's complement i32 is - // ffff_ffef) - let parameter_0 = ( - 1u32, // instruction - res_init.contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("fail"), - Amount::zero(), - ); - let res_update_0 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_0) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_0.return_value, - u64::to_le_bytes(0x0100_ffff_ffef) - ); - - // Invoke an entrypoint that tries to transfer an amount that it does not have - // via contract invoke. The expected return code is - // 0x0001_0000_0000 - // because - // - there is no return value (hence 00) - // - the call fails with "insufficient funds" (hence 01) - // - the remaining is set to 0 since there is no logic error - let parameter_1 = ( - 1u32, // instruction - res_init.contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("fail"), - Amount::from_micro_ccd(10_000), - ); - let res_update_1 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_1) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_1.return_value, - u64::to_le_bytes(0x0001_0000_0000) - ); - - // Invoke an entrypoint that traps - // The expected return code is - // 0x0002_0000_0000 - // because - // - there is no return value (hence 00) - // - the call fails with "missing account" (hence 02) - // - the remaining is set to 0 since there is no logic error - let parameter_2 = ( - 0u32, // instruction - AccountAddress([9; 32]), // Account which doesn't exist - Amount::zero(), - ); - let res_update_2 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_2) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_2.return_value, - u64::to_le_bytes(0x0002_0000_0000) - ); - - // Invoke an entrypoint that tries to invoke a non-existing contract. - // The expected return code is - // 0x0003_0000_0000 - // because - // - there is no return value (hence 00) - // - the call fails with "missing contract" (hence 03) - // - the remaining is set to 0 since there is no logic error - let parameter_3 = ( - 1u32, // instruction - ContractAddress::new(1234, 5678), // Address which does not exist. - OwnedParameter::empty(), - EntrypointName::new_unchecked("fail"), - Amount::zero(), - ); - let res_update_3 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_3) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_3.return_value, - u64::to_le_bytes(0x0003_0000_0000) - ); - - // Invoke an entrypoint that tries to invoke a non-existing entrypoint. - // The expected return code is - // 0x0004_0000_0000 - // because - // - there is no return value (hence 00) - // - the call fails with "invalid entrypoint" (hence 04) - // - the remaining is set to 0 since there is no logic error - let parameter_4 = ( - 1u32, // instruction - res_init.contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("nonexisting"), - Amount::zero(), - ); - let res_update_4 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_4) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_4.return_value, - u64::to_le_bytes(0x0004_0000_0000) - ); - - // Test 5 is omitted as it uses a v0 contract which is not supported in this - // library. - - // |Invoke an entrypoint that traps - // The expected return code is - // 0x0006_0000_0000 - // because - // - there is no return value (hence 00) - // - the call fails with "trap" (hence 06) - // - the remaining is set to 0 since there is no logic error - let parameter_6 = ( - 1u32, // instruction - res_init.contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("trap"), - Amount::zero(), - ); - let res_update_6 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("caller.call".into()), - message: OwnedParameter::from_serial(¶meter_6) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - assert_eq!( - res_update_6.return_value, - u64::to_le_bytes(0x0006_0000_0000) - ); -} diff --git a/concordium-smart-contract-testing/tests/fallback.rs b/concordium-smart-contract-testing/tests/fallback.rs deleted file mode 100644 index 34e9c2c0..00000000 --- a/concordium-smart-contract-testing/tests/fallback.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Tests for the contract default method/fallback functionality. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_fallback() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("fallback.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init_two = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_two".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_init_one = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_one".into()), - param: OwnedParameter::from_serial(&res_init_two.contract_address) - .expect("Parameter has valid size"), /* Pass in address of contract - * "two". */ - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // Invoke the fallback directly. This should fail with execution failure/trap - // because it will redirect to "two." which does not exist. Hence this will fail - // and the fallback will try to look up a non-existing return value. - let res_invoke_1 = chain - .contract_invoke( - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_one.contract_address, - receive_name: OwnedReceiveName::new_unchecked("one.".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect_err("should fail"); - match res_invoke_1.kind { - ContractInvokeErrorKind::ExecutionError { - failure_kind: InvokeFailure::RuntimeError, - .. - } => (), - _ => panic!("Test failed, expected a runtime error."), - } - - // Invoke "two.do" via "one.do" and the fallback. - let parameter = OwnedParameter::from_serial(&"ASDF").expect("Parameter has valid size."); - let res_invoke_2 = chain - .contract_invoke( - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init_one.contract_address, - receive_name: OwnedReceiveName::new_unchecked("one.do".into()), - message: parameter.clone(), - amount: Amount::zero(), - }, - ) - .expect("Invoke should succeed."); - assert_eq!(res_invoke_2.return_value, parameter.as_ref()); // Parameter is - // returned - // via the fallback. -} diff --git a/concordium-smart-contract-testing/tests/helpers/mod.rs b/concordium-smart-contract-testing/tests/helpers/mod.rs deleted file mode 100644 index f5a92ac5..00000000 --- a/concordium-smart-contract-testing/tests/helpers/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Some helpers and constants that are used in most or all of the tests in this -//! folder. -use concordium_smart_contract_testing::*; - -/// Relative path to the wasm test contracts. -pub(crate) const WASM_TEST_FOLDER: &str = - "../concordium-rust-sdk/concordium-base/smart-contracts/testdata/contracts/v1"; - -/// Test account 0. -pub(crate) const ACC_0: AccountAddress = AccountAddress([0; 32]); - -/// Test account 1. -/// Dead code is allowed to avoid a warning when running `cargo test`. -/// `cargo test` compiles each test module independently and for the ones that -/// do not use `ACC_1` a warning is produced. -#[allow(dead_code)] -pub(crate) const ACC_1: AccountAddress = AccountAddress([1; 32]); - -/// Get the path to a wasm test file in wasm test folder. -/// -/// This is simply prepends the test folder path to the file name: -/// `WASM_TEST_FOLDER/{file_name}`. -pub(crate) fn wasm_test_file(file_name: &str) -> String { - format!("{WASM_TEST_FOLDER}/{file_name}") -} diff --git a/concordium-smart-contract-testing/tests/iterator.rs b/concordium-smart-contract-testing/tests/iterator.rs deleted file mode 100644 index 957a1926..00000000 --- a/concordium-smart-contract-testing/tests/iterator.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! This module tests calling a contract which makes use of an iterator. -//! The checks are being performed in the contract itself so if invoking the -//! contract completes successfully then this implies that the tests have done -//! so as well. Note. as per above no checks are being performed in this file -//! wrt. the state etc. after execution etc. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_iterator() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("iterator.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_iterator".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("iterator.iteratetest".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Should succeed"); - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("iterator.lockingtest".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Should succeed."); -} diff --git a/concordium-smart-contract-testing/tests/queries.rs b/concordium-smart-contract-testing/tests/queries.rs deleted file mode 100644 index 3ae8e61c..00000000 --- a/concordium-smart-contract-testing/tests/queries.rs +++ /dev/null @@ -1,710 +0,0 @@ -//! This module contains tests related to the chain queries that are available -//! for smart contracts. -//! -//! Namely queries for: -//! - the balance of a contract, -//! - the balances of an account, -//! - the exhange rates. -use concordium_smart_contract_testing::*; -mod helpers; - -mod query_account_balance { - use super::*; - - /// Queries the balance of another account and asserts that it is as - /// expected. - #[test] - fn test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - chain.create_account(Account::new(helpers::ACC_1, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-account-balance.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // The contract will query the balance of helpers::ACC_1 and assert that the - // three balances match this input. - let input_param = ( - helpers::ACC_1, - initial_balance, - Amount::zero(), - Amount::zero(), - ); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some( - initial_balance - - res_deploy.transaction_fee - - res_init.transaction_fee - - res_update.transaction_fee - ) - ); - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } - - /// Queries the balance of the invoker account, which will have have the - /// expected balance of: - /// prior_balance - amount_sent - amount_to_cover_reserved_NRG. - #[test] - fn invoker_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - chain.create_account(Account::new(helpers::ACC_1, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-account-balance.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let update_amount = Amount::from_ccd(123); - let energy_limit = Energy::from(100000); - let invoker_reserved_amount = update_amount + chain.calculate_energy_cost(energy_limit); - - // The contract will query the balance of helpers::ACC_1, which is also the - // invoker, and assert that the three balances match this input. - let expected_balance = initial_balance - invoker_reserved_amount; - let input_param = ( - helpers::ACC_1, - expected_balance, - Amount::zero(), - Amount::zero(), - ); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_1, - Address::Account(helpers::ACC_1), - energy_limit, - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: update_amount, - }, - ) - .expect("Updating valid contract should work"); - - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some(initial_balance - res_deploy.transaction_fee - res_init.transaction_fee) - ); - assert_eq!( - chain.account_balance_available(helpers::ACC_1), - // Differs from `expected_balance` as it only includes the actual amount charged - // for the NRG use. Not the reserved amount. - Some(initial_balance - res_update.transaction_fee - update_amount) - ); - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } - - /// Makes a transfer to an account, then queries its balance and asserts - /// that it is as expected. - #[test] - fn transfer_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - chain.create_account(Account::new(helpers::ACC_1, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "queries-account-balance-transfer.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let amount_to_send = Amount::from_ccd(123); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: amount_to_send, // Make sure the contract has CCD to transfer. - }, - ) - .expect("Initializing valid contract should work"); - - let amount_to_send = Amount::from_ccd(123); - let expected_balance = initial_balance + amount_to_send; - let input_param = ( - helpers::ACC_1, - amount_to_send, - expected_balance, - Amount::zero(), - Amount::zero(), - ); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some( - initial_balance - - res_deploy.transaction_fee - - res_init.transaction_fee - - res_update.transaction_fee - - amount_to_send - ) - ); - assert_eq!( - chain.account_balance_available(helpers::ACC_1), - Some(initial_balance + amount_to_send) - ); - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Transferred { .. }, - ContractTraceElement::Resumed { .. }, - ContractTraceElement::Updated { .. } - ] - )); - } - - #[test] - fn balance_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - chain.create_account(Account::new(helpers::ACC_1, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-account-balance.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // The contract will query the balance of helpers::ACC_1 and assert that the - // three balances match this input. - let input_param = ( - helpers::ACC_1, - initial_balance, - Amount::zero(), - Amount::zero(), - ); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some( - initial_balance - - res_deploy.transaction_fee - - res_init.transaction_fee - - res_update.transaction_fee - ) - ); - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } - - /// Queries the balance of a missing account and asserts that it returns - /// the correct error. - #[test] - fn missing_account_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "queries-account-balance-missing-account.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // The account to query, which doesn't exist in this test case. - let input_param = helpers::ACC_1; - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert_eq!( - chain.account_balance_available(helpers::ACC_0), - Some( - initial_balance - - res_deploy.transaction_fee - - res_init.transaction_fee - - res_update.transaction_fee - ) - ); - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } -} - -mod query_contract_balance { - use super::*; - - /// Test querying the balance of another contract, which exists. Asserts - /// that the balance is as expected. - #[test] - fn test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let init_amount = Amount::from_ccd(123); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-contract-balance.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_init_other = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: init_amount, // Set up another contract with `init_amount` balance - }, - ) - .expect("Initializing valid contract should work"); - - // check that the other contract has `self_balance == init_amount`. - let input_param = (res_init_other.contract_address, init_amount); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } - - /// Test querying the balance of the contract instance itself. This - /// should include the amount sent to it in the update transaction. - #[test] - fn query_self_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let init_amount = Amount::from_ccd(123); - let update_amount = Amount::from_ccd(456); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-contract-balance.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: init_amount, - }, - ) - .expect("Initializing valid contract should work"); - - // check that the other contract has `self_balance == init_amount`. - let input_param = (res_init.contract_address, init_amount + update_amount); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: update_amount, - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } - - /// Test querying the balance after a transfer of CCD. - #[test] - fn query_self_after_transfer_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let init_amount = Amount::from_ccd(123); - let update_amount = Amount::from_ccd(456); - let transfer_amount = Amount::from_ccd(78); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "queries-contract-balance-transfer.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: init_amount, - }, - ) - .expect("Initializing valid contract should work"); - - let input_param = ( - helpers::ACC_0, - transfer_amount, - res_init.contract_address, - init_amount + update_amount - transfer_amount, - ); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: update_amount, - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Transferred { .. }, - ContractTraceElement::Resumed { .. }, - ContractTraceElement::Updated { .. } - ] - )); - } - - /// Test querying the balance of a contract that doesn't exist. - #[test] - fn missing_contract_test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "queries-contract-balance-missing-contract.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // Non-existent contract address. - let input_param = ContractAddress::new(123, 456); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } -} - -mod query_exchange_rates { - - use super::*; - - /// Test querying the exchange rates. - #[test] - fn test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("queries-exchange-rates.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // Non-existent contract address. - let input_param = (chain.euro_per_energy(), chain.micro_ccd_per_euro()); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.query".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - } -} diff --git a/concordium-smart-contract-testing/tests/recorder.rs b/concordium-smart-contract-testing/tests/recorder.rs deleted file mode 100644 index 9b70afa5..00000000 --- a/concordium-smart-contract-testing/tests/recorder.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! This module tests basic V1 state operations with the recorder contract. - -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_recorder() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("record-parameters.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_recorder".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("recorder.record_u64".into()), - message: OwnedParameter::from_serial(&20u64) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Update failed"); - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("recorder.record_u64".into()), - message: OwnedParameter::from_serial(&40u64) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Update failed"); - // Assert that all 60 values were inserted in the state. - for key in 0..60u64 { - assert!(chain - .contract_state_lookup(res_init.contract_address, &u64::to_le_bytes(key)) - .is_some()); - } -} diff --git a/concordium-smart-contract-testing/tests/relaxed_restrictions.rs b/concordium-smart-contract-testing/tests/relaxed_restrictions.rs deleted file mode 100644 index ef841076..00000000 --- a/concordium-smart-contract-testing/tests/relaxed_restrictions.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! This module tests the relaxed smart contract restrictions introduced in P5 -//! for V1 contracts. -//! -//! This will only check that the P5 limits are in effect, as the testing -//! library only supports the most current protocol version (for now, at least). -//! -//! The limit changes in P5 are: -//! - Parameter size limit: 1kb -> 65kb -//! - Return value size limit: 16kb -> no limit (apart from energy) -//! - Number of logs: 64 -> no limit (apart from energy) -//! - Cost of parameters: -//! - Of size <= 1kb: base cost + 1NRG / 1 *kilobyte* (same as before P5) -//! - Of size > 1 kb: base cost + 1NRG / 1 *byte* -use concordium_smart_contract_testing::*; -mod helpers; - -/// Test the new parameter size limit on both init and update. -#[test] -fn test_new_parameter_limit() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let parameter = mk_parameter(65535, 65535); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("relaxed-restrictions.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(80000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_relax".into()), - param: parameter.clone(), // Check parameter size limit on init. - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(700000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("relax.param".into()), - message: parameter, // Check parameter size limit on updates. - amount: Amount::zero(), - }, - ) - .expect("Updating contract should succeed"); -} - -/// Test the new return value limit. -#[test] -fn test_new_return_value_limit() { - let (mut chain, contract_address) = deploy_and_init(); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - receive_name: OwnedReceiveName::new_unchecked("relax.return-value".into()), - message: OwnedParameter::from_serial(&100_000u32) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating contract should succeed"); -} - -/// Test the new number of logs limit. -#[test] -fn test_new_log_limit() { - let (mut chain, contract_address) = deploy_and_init(); - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - receive_name: OwnedReceiveName::new_unchecked("relax.logs".into()), - message: OwnedParameter::from_serial(&64u32) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating contract should succeed"); -} - -/// Helper for deploying and initializing the `relaxed-restrictions.wasm` -/// contract. -fn deploy_and_init() -> (Chain, ContractAddress) { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("relaxed-restrictions.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_relax".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - (chain, res_init.contract_address) -} - -/// Helper for creating a parameter of a specific size. -/// -/// The `internal_param_size` is the size of the parameter passed to the -/// `param-aux` entrypoint. This is used to check the parameter size limit -/// inside the wasm interpreter. -/// -/// The `desired_size` is the desired total length of the parameter produced by -/// this function. It is used to check the parameter size limit checked in the -/// testing library. -/// -/// The parameter returned will contain -/// - `internal_param_size` (2 bytes) -/// - the entrypoint `"param-aux"` (2 + 9 bytes) -/// - filler `1u8` bytes (remaining until `desired_size` is reached) -fn mk_parameter(internal_param_size: u16, desired_size: u32) -> OwnedParameter { - let entrypoint = OwnedEntrypointName::new_unchecked("param-aux".into()); - let filler_size = desired_size - - 2 // internal_param_size - - 2 // entrypoint name len - - 9 // entrypoint name - - 4; // length of filler vector - let filler = vec![1u8; filler_size as usize]; - OwnedParameter::from_serial(&(internal_param_size, entrypoint, filler)) - .expect("Parameter has valid size.") -} diff --git a/concordium-smart-contract-testing/tests/self_balance.rs b/concordium-smart-contract-testing/tests/self_balance.rs deleted file mode 100644 index 5ae74d7d..00000000 --- a/concordium-smart-contract-testing/tests/self_balance.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! This module tests that the correct self-balance is exposed to V1 contracts. -//! In essense that the self-balance is updated by the invoke. -//! -//! See more details about the specific test inside the `self-balance.wat` and -//! `self-blaance-nested.wat` files. -use concordium_smart_contract_testing::*; -mod helpers; - -/// Invoke an entrypoint and transfer to ourselves. -/// The before and after self-balances are the same. -#[test] -fn test_invoke_1() { - let (mut chain, contract_address, _) = deploy_and_init("self-balance.wasm", "init_transfer"); - - let parameter = ( - 1u32, // instruction - contract_address, - OwnedParameter::empty(), - EntrypointName::new_unchecked("accept"), - Amount::from_micro_ccd(0), - ); - let result = chain.contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - amount: Amount::from_micro_ccd(123), - receive_name: OwnedReceiveName::new_unchecked("transfer.forward".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size."), - }, - ); - assert_success( - result, - Amount::from_micro_ccd(123), - Amount::from_micro_ccd(123), - "Self selfBalance", - ); -} - -/// Invoke an entrypoint and transfer to another instance. -/// The before and after balances are different. -/// The key difference from `test_invoke_1` is that the contract address in the -/// parameter is different. -#[test] -fn test_invoke_2() { - let (mut chain, self_address, mod_ref) = deploy_and_init("self-balance.wasm", "init_transfer"); - - let res_init_another = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref, - init_name: OwnedContractName::new_unchecked("init_transfer".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let parameter = ( - 1u32, // instruction - res_init_another.contract_address, // Transfer to another contract instance. - OwnedParameter::empty(), - EntrypointName::new_unchecked("accept"), - Amount::from_micro_ccd(100), - ); - let result = chain.contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: self_address, - receive_name: OwnedReceiveName::new_unchecked("transfer.forward".into()), - message: OwnedParameter::from_serial(¶meter) - .expect("Parameter has valid size."), - amount: Amount::from_micro_ccd(123), - }, - ); - assert_success( - result, - Amount::from_micro_ccd(123), - Amount::from_micro_ccd(23), - "Self selfBalance", - ); -} - -/// Helper for deploying and initializing the provided contract. -fn deploy_and_init( - file_name: &str, - contract_name: &str, -) -> (Chain, ContractAddress, ModuleReference) { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file(file_name)).expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked(contract_name.into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - ( - chain, - res_init.contract_address, - res_deploy.module_reference, - ) -} - -/// Helper for asserting the success. -fn assert_success( - result: Result, - expected_before: Amount, - expected_after: Amount, - error_message: &str, -) { - if let Ok(success) = result { - assert_eq!( - success.return_value, - to_bytes(&(expected_before, expected_after)) - ) - } else { - panic!("Test failed ( {} )", error_message) - } -} diff --git a/concordium-smart-contract-testing/tests/transfer.rs b/concordium-smart-contract-testing/tests/transfer.rs deleted file mode 100644 index 428b050f..00000000 --- a/concordium-smart-contract-testing/tests/transfer.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! This module contains tests for transfers fr&om a contract to an account. -//! See more details about the specific test inside the `transfer.wat` file. -use concordium_smart_contract_testing::*; -mod helpers; - -#[test] -fn test_transfer() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(10000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("transfer.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_transfer".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let contract_address = res_init.contract_address; - - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - receive_name: OwnedReceiveName::new_unchecked("transfer.forward".into()), - message: OwnedParameter::from_serial(&helpers::ACC_0) - .expect("Parameter has valid size"), - amount: Amount::from_micro_ccd(123), - }, - ) - .expect("Updating contract should succeed"); - // Contract should have forwarded the amount and thus have balance == 0. - assert_eq!( - Amount::zero(), - chain.get_contract(contract_address).unwrap().self_balance - ); - - // Deposit 1000 micro CCD. - chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - receive_name: OwnedReceiveName::new_unchecked("transfer.deposit".into()), - message: OwnedParameter::empty(), - amount: Amount::from_micro_ccd(1000), - }, - ) - .expect("Updating contract should succeed"); - - // Tell it to send 17 mCCD to helpers::ACC_0. - let parameter = OwnedParameter::from_serial(&(helpers::ACC_0, Amount::from_micro_ccd(17))) - .expect("Parameter has valid size"); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(10000), - UpdateContractPayload { - address: contract_address, - receive_name: OwnedReceiveName::new_unchecked("transfer.send".into()), - message: parameter.clone(), - amount: Amount::zero(), - }, - ) - .expect("Updating contract should succeed"); - // Contract should have 1000 - 17 microCCD in balance. - assert_eq!( - Amount::from_micro_ccd(1000 - 17), - chain.get_contract(contract_address).unwrap().self_balance - ); - assert_eq!(res_update.effective_trace_elements_cloned()[..], [ - ContractTraceElement::Interrupted { - address: contract_address, - events: Vec::new(), - }, - ContractTraceElement::Transferred { - from: contract_address, - amount: Amount::from_micro_ccd(17), - to: helpers::ACC_0, - }, - ContractTraceElement::Resumed { - address: contract_address, - success: true, - }, - ContractTraceElement::Updated { - data: InstanceUpdatedEvent { - address: contract_address, - amount: Amount::zero(), - receive_name: OwnedReceiveName::new_unchecked("transfer.send".into()), - contract_version: concordium_base::smart_contracts::WasmVersion::V1, - instigator: Address::Account(helpers::ACC_0), - message: parameter, - events: Vec::new(), - }, - } - ]) -} diff --git a/concordium-smart-contract-testing/tests/upgrades.rs b/concordium-smart-contract-testing/tests/upgrades.rs deleted file mode 100644 index bd9d4328..00000000 --- a/concordium-smart-contract-testing/tests/upgrades.rs +++ /dev/null @@ -1,697 +0,0 @@ -//! This module contains tests for the native smart contract upgrade -//! functionality. -use concordium_smart_contract_testing::*; -mod helpers; - -/// Test a basic upgrade, ensuring that the new module is in place by -/// checking the available entrypoints. -#[test] -fn test() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - // Deploy the two modules `upgrading_0`, `upgrading_1` - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading_0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading_1.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - // Initialize `upgrading_0`. - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_a".into()), - mod_ref: res_deploy_0.module_reference, - - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // Upgrade the contract to the `upgrading_1` module by calling the `bump` - // entrypoint. - let res_update_upgrade = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.bump".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - // Call the `newfun` entrypoint which only exists in `upgrading_1`. - let res_update_new = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("a.newfun".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating the `newfun` from the `upgrading_1` module should work"); - - assert!( - matches!(res_update_upgrade.effective_trace_elements_cloned()[..], [ - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Upgraded { from, to, .. }, - ContractTraceElement::Resumed { .. }, - ContractTraceElement::Updated { .. }, - ] if from == res_deploy_0.module_reference && to == res_deploy_1.module_reference) - ); - assert!(matches!( - res_update_new.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); -} - -/// The contract in this test, triggers an upgrade and then in the same -/// invocation, calls a function in the upgraded module. -/// Checking the new module is being used. -#[test] -fn test_self_invoke() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-self-invoke0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-self-invoke1.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - mod_ref: res_deploy_0.module_reference, - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!( - res_update.effective_trace_elements_cloned()[..], - [ - // Invoking `contract.name` - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Updated { .. }, - ContractTraceElement::Resumed { .. }, - // Making the upgrade - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Upgraded { .. }, - ContractTraceElement::Resumed { .. }, - // Invoking contract.name again - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Updated { .. }, - ContractTraceElement::Resumed { .. }, - // The successful update - ContractTraceElement::Updated { .. }, - ] - )); -} - -/// Test upgrading to a module that doesn't exist (it uses module -/// `[0u8;32]` inside the contract). The contract checks whether -/// the expected error is returned. -#[test] -fn test_missing_module() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-missing-module.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!(res_update.effective_trace_elements_cloned()[..], [ - ContractTraceElement::Interrupted { .. }, - // No upgrade event, as it is supposed to fail. - ContractTraceElement::Resumed { success, .. }, - ContractTraceElement::Updated { .. }, - ] if !success )); -} - -/// Test upgrading to a module where there isn't a matching contract -/// name. The contract checks whether the expected error is -/// returned. -#[test] -fn test_missing_contract() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-missing-contract0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-missing-contract1.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - mod_ref: res_deploy_0.module_reference, - - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!(res_update.effective_trace_elements_cloned()[..], [ - ContractTraceElement::Interrupted { .. }, - // No upgrade event, as it is supposed to fail. - ContractTraceElement::Resumed { success, .. }, - ContractTraceElement::Updated { .. }, - ] if !success )); -} - -/// Test upgrading twice in the same transaction. The effect of the -/// second upgrade should be in effect at the end. -#[test] -fn test_twice_in_one_transaction() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-twice0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-twice1.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_2 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-twice2.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - mod_ref: res_deploy_0.module_reference, - - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let input_param = (res_deploy_1.module_reference, res_deploy_2.module_reference); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(100000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - assert!(matches!(res_update.effective_trace_elements_cloned()[..], [ - // Invoke the contract itself to check the name entrypoint return value. - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Updated { .. }, - ContractTraceElement::Resumed { .. }, - // Upgrade from module 0 to 1 - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Upgraded { from: first_from, to: first_to, .. }, - ContractTraceElement::Resumed { .. }, - // Invoke the contract itself to check the name again. - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Updated { .. }, - ContractTraceElement::Resumed { .. }, - // Upgrade again - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Upgraded { from: second_from, to: second_to, .. }, - ContractTraceElement::Resumed { .. }, - // Invoke itself again to check name a final time. - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Updated { .. }, - ContractTraceElement::Resumed { .. }, - // Final update event - ContractTraceElement::Updated { .. }, - ] if first_from == res_deploy_0.module_reference - && first_to == res_deploy_1.module_reference - && second_from == res_deploy_1.module_reference - && second_to == res_deploy_2.module_reference)); -} - -/// Test upgrading to a module where there isn't a matching contract -/// name. The contract checks whether the expected error is -/// returned. -#[test] -fn test_chained_contract() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-chained0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - // High number here tests we don't have stack overflows due to recursion or - // other accidental stack usage. - let number_of_upgrades: u32 = 1_000; - let input_param = (number_of_upgrades, res_deploy.module_reference); - - let res_update = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1_000_000_000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&input_param) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Updating valid contract should work"); - - // Per upgrade: 3 events for invoking itself + 3 events for the upgrade. - // Ends with 4 extra events: 3 events for an upgrade and 1 event for succesful - // update. - assert_eq!( - res_update.effective_trace_elements_cloned().len() as u32, - 6 * number_of_upgrades + 4 - ) -} - -/// Tests whether a contract which triggers a succesful upgrade, -/// but rejects the transaction from another cause, rollbacks the -/// upgrade as well. -#[test] -fn test_reject() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-reject0.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file("upgrading-reject1.wasm")) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - mod_ref: res_deploy_0.module_reference, - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_update_upgrade = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect_err("should fail"); - - let res_update_new_feature = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.new_feature".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect_err("should fail"); - - // Check the return value manually returned by the contract. - match res_update_upgrade.kind { - ContractInvokeErrorKind::ExecutionError { failure_kind, .. } => match failure_kind { - InvokeFailure::ContractReject { code, .. } if code == -1 => (), - _ => panic!("Expected ContractReject with code == -1"), - }, - _ => panic!("Expected Err(ContractUpdateError::ExecutionError)"), - } - - // Assert that the new_feature entrypoint doesn't exist since the upgrade - // failed. - assert!(matches!( - res_update_new_feature.kind, - ContractInvokeErrorKind::ExecutionError { - failure_kind: InvokeFailure::NonExistentEntrypoint, - } - )); -} - -/// Tests calling an entrypoint introduced by an upgrade of the module -/// can be called and whether an entrypoint removed by an upgrade fail -/// with the appropriate reject reason. -#[test] -fn test_changing_entrypoint() { - let mut chain = Chain::new(); - let initial_balance = Amount::from_ccd(1000000); - chain.create_account(Account::new(helpers::ACC_0, initial_balance)); - - let res_deploy_0 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "upgrading-changing-entrypoints0.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_deploy_1 = chain - .module_deploy_v1( - Signer::with_one_key(), - helpers::ACC_0, - module_load_v1_raw(helpers::wasm_test_file( - "upgrading-changing-entrypoints1.wasm", - )) - .expect("module should exist"), - ) - .expect("Deploying valid module should work"); - - let res_init = chain - .contract_init( - Signer::with_one_key(), - helpers::ACC_0, - Energy::from(10000), - InitContractPayload { - init_name: OwnedContractName::new_unchecked("init_contract".into()), - param: OwnedParameter::empty(), - mod_ref: res_deploy_0.module_reference, - - amount: Amount::zero(), - }, - ) - .expect("Initializing valid contract should work"); - - let res_update_old_feature_0 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.old_feature".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Updating old_feature on old module should work."); - - let res_update_new_feature_0 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.new_feature".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect_err("Updating new_feature on old module should _not_ work"); - - let res_update_upgrade = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.upgrade".into()), - message: OwnedParameter::from_serial(&res_deploy_1.module_reference) - .expect("Parameter has valid size"), - amount: Amount::zero(), - }, - ) - .expect("Upgrading contract should work."); - - let res_update_old_feature_1 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.old_feature".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect_err("Updating old_feature on _new_ module should _not_ work."); - - let res_update_new_feature_1 = chain - .contract_update( - Signer::with_one_key(), - helpers::ACC_0, - Address::Account(helpers::ACC_0), - Energy::from(1000000), - UpdateContractPayload { - address: res_init.contract_address, - receive_name: OwnedReceiveName::new_unchecked("contract.new_feature".into()), - message: OwnedParameter::empty(), - amount: Amount::zero(), - }, - ) - .expect("Updating new_feature on _new_ module should work"); - - assert!(matches!( - res_update_old_feature_0.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); - assert!(matches!( - res_update_new_feature_0.kind, - ContractInvokeErrorKind::ExecutionError { - failure_kind: InvokeFailure::NonExistentEntrypoint, - } - )); - assert!(matches!( - res_update_upgrade.effective_trace_elements_cloned()[..], - [ - ContractTraceElement::Interrupted { .. }, - ContractTraceElement::Upgraded { .. }, - ContractTraceElement::Resumed { .. }, - ContractTraceElement::Updated { .. }, - ] - )); - assert!(matches!( - res_update_old_feature_1.kind, - ContractInvokeErrorKind::ExecutionError { - failure_kind: InvokeFailure::NonExistentEntrypoint, - } - )); - assert!(matches!( - res_update_new_feature_1.effective_trace_elements_cloned()[..], - [ContractTraceElement::Updated { .. }] - )); -}