feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
build = "build.rs"
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet for WASM contracts"
|
||||
readme = "README.md"
|
||||
include = ["CHANGELOG.md", "README.md", "benchmarks/**", "build.rs", "src/**/*"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive", "max-encoded-len"], workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
log = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, features = [
|
||||
"derive",
|
||||
], workspace = true, default-features = true }
|
||||
smallvec = { features = ["const_generics"], workspace = true }
|
||||
wasmi = { workspace = true }
|
||||
|
||||
# Only used in benchmarking to generate contract code
|
||||
rand = { workspace = true }
|
||||
rand_pcg = { workspace = true }
|
||||
wasm-instrument = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
environmental = { workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-balances = { optional = true, workspace = true }
|
||||
pezpallet-contracts-proc-macro = { workspace = true, default-features = true }
|
||||
pezpallet-contracts-uapi = { workspace = true, default-features = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
array-bytes = { workspace = true, default-features = true }
|
||||
assert_matches = { workspace = true }
|
||||
pezpallet-contracts-fixtures = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
wat = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
xcm-builder = { workspace = true, default-features = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-insecure-randomness-collective-flip = { workspace = true, default-features = true }
|
||||
pezpallet-proxy = { workspace = true, default-features = true }
|
||||
pezpallet-timestamp = { workspace = true, default-features = true }
|
||||
pezpallet-utility = { workspace = true, default-features = true }
|
||||
pezsp-keystore = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"environmental/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances?/std",
|
||||
"pezpallet-insecure-randomness-collective-flip/std",
|
||||
"pezpallet-proxy/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"pezpallet-utility/std",
|
||||
"rand/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"pezsp-api/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-keystore/std",
|
||||
"pezsp-runtime/std",
|
||||
"wasm-instrument/std",
|
||||
"wasmi/std",
|
||||
"xcm-builder/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-contracts-fixtures/runtime-benchmarks",
|
||||
"pezpallet-insecure-randomness-collective-flip/runtime-benchmarks",
|
||||
"pezpallet-proxy/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezpallet-utility/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-insecure-randomness-collective-flip/try-runtime",
|
||||
"pezpallet-proxy/try-runtime",
|
||||
"pezpallet-timestamp/try-runtime",
|
||||
"pezpallet-utility/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,160 @@
|
||||
# Contracts Module
|
||||
|
||||
The Contracts module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts.
|
||||
|
||||
- [`Call`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/enum.Call.html)
|
||||
- [`Config`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/trait.Config.html)
|
||||
- [`Error`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/enum.Error.html)
|
||||
- [`Event`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/enum.Event.html)
|
||||
|
||||
## Overview
|
||||
|
||||
This module extends accounts based on the [`frame_support::traits::fungible`] traits to have smart-contract
|
||||
functionality. It can be used with other modules that implement accounts based on [`frame_support::traits::fungible`].
|
||||
These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls to other contract and
|
||||
non-contract accounts.
|
||||
|
||||
The smart-contract code is stored once, and later retrievable via its `code_hash`. This means that multiple
|
||||
smart-contracts can be instantiated from the same `code`, without replicating the code each time.
|
||||
|
||||
When a smart-contract is called, its associated code is retrieved via the code hash and gets executed. This call can
|
||||
alter the storage entries of the smart-contract account, instantiate new smart-contracts, or call other smart-contracts.
|
||||
|
||||
Finally, when an account is reaped, its associated code and storage of the smart-contract account will also be deleted.
|
||||
|
||||
### Weight
|
||||
|
||||
Senders must specify a [`Weight`](https://docs.pezkuwichain.io/bizinikiwi/master/sp_weights/struct.Weight.html) limit
|
||||
with every call, as all instructions invoked by the smart-contract require weight. Unused weight is refunded after the
|
||||
call, regardless of the execution outcome.
|
||||
|
||||
If the weight limit is reached, then all calls and state changes (including balance transfers) are only reverted at the
|
||||
current call's contract level. For example, if contract A calls B and B runs out of weight mid-call, then all of B's
|
||||
calls are reverted. Assuming correct error handling by contract A, A's other calls and state changes still persist.
|
||||
|
||||
One `ref_time` `Weight` is defined as one picosecond of execution time on the runtime's reference machine.
|
||||
|
||||
### Revert Behaviour
|
||||
|
||||
Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up", and the call will
|
||||
only revert at the specific contract level. For example, if contract A calls contract B, and B fails, A can decide how
|
||||
to handle that failure, either proceeding or reverting A's changes.
|
||||
|
||||
### Off-chain Execution
|
||||
|
||||
In general, a contract execution needs to be deterministic so that all nodes come to the same conclusion when executing
|
||||
it. To that end we disallow any instructions that could cause indeterminism. Most notable are any floating point
|
||||
arithmetic. That said, sometimes contracts are executed off-chain and hence are not subject to consensus. If code is
|
||||
only executed by a single node and implicitly trusted by other actors is such a case. Trusted execution environments
|
||||
come to mind. To that end we allow the execution of indeterministic code for off-chain usages with the following
|
||||
constraints:
|
||||
|
||||
1. No contract can ever be instantiated from an indeterministic code. The only way to execute the code is to use a
|
||||
delegate call from a deterministic contract.
|
||||
2. The code that wants to use this feature needs to depend on `pezpallet-contracts` and use
|
||||
[`bare_call()`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call)
|
||||
directly. This makes sure that by default `pezpallet-contracts` does not expose any indeterminism.
|
||||
|
||||
#### How to use
|
||||
|
||||
An indeterministic code can be deployed on-chain by passing `Determinism::Relaxed` to
|
||||
[`upload_code()`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/struct.Pallet.html#method.upload_code).
|
||||
A deterministic contract can then delegate call into it if and only if it is ran by using
|
||||
[`bare_call()`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call)
|
||||
and passing
|
||||
[`Determinism::Relaxed`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/enum.Determinism.html#variant.Relaxed)
|
||||
to it. **Never use this argument when the contract is called from an on-chain transaction.**
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable functions
|
||||
|
||||
Those are documented in the [reference
|
||||
documentation](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/index.html#dispatchable-functions).
|
||||
|
||||
### Interface exposed to contracts
|
||||
|
||||
Each contract is one WebAssembly module that looks like this:
|
||||
|
||||
```wat
|
||||
(module
|
||||
;; Invoked by pezpallet-contracts when a contract is instantiated.
|
||||
;; No arguments and empty return type.
|
||||
(func (export "deploy"))
|
||||
|
||||
;; Invoked by pezpallet-contracts when a contract is called.
|
||||
;; No arguments and empty return type.
|
||||
(func (export "call"))
|
||||
|
||||
;; If a contract uses memory it must be imported. Memory is optional.
|
||||
;; The maximum allowed memory size depends on the pezpallet-contracts configuration.
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; This is one of many functions that can be imported and is implemented by pezpallet-contracts.
|
||||
;; This function is used to copy the result buffer and flags back to the caller.
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
)
|
||||
```
|
||||
|
||||
The documentation of all importable functions can be found
|
||||
[here](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/api_doc/trait.Current.html).
|
||||
|
||||
## Usage
|
||||
|
||||
This module executes WebAssembly smart contracts. These can potentially be written in any language that compiles to
|
||||
Wasm. However, using a language that specifically targets this module will make things a lot easier. One such language
|
||||
is [`ink!`](https://use.ink). It enables writing WebAssembly-based smart-contracts in the Rust programming language.
|
||||
|
||||
## Debugging
|
||||
|
||||
Contracts can emit messages to the client when called as RPC through the
|
||||
[`debug_message`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_contracts/api_doc/trait.Current.html#tymethod.debug_message)
|
||||
API. This is exposed in [ink!](https://use.ink) via
|
||||
[`ink_env::debug_message()`](https://paritytech.github.io/ink/ink_env/fn.debug_message.html).
|
||||
|
||||
Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if
|
||||
and how those messages are presented to the user.
|
||||
|
||||
This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the
|
||||
`runtime::contracts` target needs to be raised to at least the `debug` level. However, those messages are easy to
|
||||
overlook because of the noise generated by block production. A good starting point for observing them on the console is
|
||||
using this command line in the root directory of the Bizinikiwi repository:
|
||||
|
||||
```bash
|
||||
cargo run --release -- --dev -lerror,runtime::contracts=debug
|
||||
```
|
||||
|
||||
This raises the log level of `runtime::contracts` to `debug` and all other targets to `error` in order to prevent them
|
||||
from spamming the console.
|
||||
|
||||
`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit)
|
||||
|
||||
## Host function tracing
|
||||
|
||||
For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments,
|
||||
and what the result was.
|
||||
|
||||
In order to see these messages on the node console, the log level for the `runtime::contracts::strace` target needs to
|
||||
be raised to the `trace` level.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
cargo run --release -- --dev -lerror,runtime::contracts::strace=trace,runtime::contracts=debug
|
||||
```
|
||||
|
||||
## Unstable Interfaces
|
||||
|
||||
Driven by the desire to have an iterative approach in developing new contract interfaces this pallet contains the
|
||||
concept of an unstable interface. Akin to the rust nightly compiler it allows us to add new interfaces but mark them as
|
||||
unstable so that contract languages can experiment with them and give feedback before we stabilize those.
|
||||
|
||||
In order to access interfaces marked as `#[unstable]` in [`runtime.rs`](src/wasm/runtime.rs) one need to set
|
||||
`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32<true>`. **It should be obvious that any production
|
||||
runtime should never be compiled with this feature: In addition to be subject to change or removal those interfaces
|
||||
might not have proper weights associated with them and are therefore considered unsafe**.
|
||||
|
||||
New interfaces are generally added as unstable and might go through several iterations before they are promoted to a
|
||||
stable interface.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,72 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
/// Get the latest migration version.
|
||||
///
|
||||
/// Find the highest version number from the available migration files.
|
||||
/// Each migration file should follow the naming convention `vXX.rs`, where `XX` is the version
|
||||
/// number.
|
||||
fn get_latest_version() -> u16 {
|
||||
std::fs::read_dir("src/migration")
|
||||
.expect("Folder `src/migration` not found.")
|
||||
.filter_map(|entry| {
|
||||
let file_name = entry.as_ref().ok()?.file_name();
|
||||
let file_name = file_name.to_str()?;
|
||||
if file_name.starts_with('v') && file_name.ends_with(".rs") {
|
||||
let version = &file_name[1..&file_name.len() - 3];
|
||||
let version = version.parse::<u16>().ok()?;
|
||||
|
||||
// Ensure that the version matches the one defined in the file.
|
||||
let path = entry.unwrap().path();
|
||||
let file_content = std::fs::read_to_string(&path).ok()?;
|
||||
assert!(
|
||||
file_content.contains(&format!("const VERSION: u16 = {}", version)),
|
||||
"Invalid MigrationStep::VERSION in {:?}",
|
||||
path
|
||||
);
|
||||
|
||||
return Some(version);
|
||||
}
|
||||
None
|
||||
})
|
||||
.max()
|
||||
.expect("Failed to find any files matching the 'src/migration/vxx.rs' pattern.")
|
||||
}
|
||||
|
||||
/// Generates a module that exposes the latest migration version, and the benchmark migrations type.
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let out_dir = std::env::var("OUT_DIR")?;
|
||||
let path = std::path::Path::new(&out_dir).join("migration_codegen.rs");
|
||||
let mut f = std::fs::File::create(path)?;
|
||||
let version = get_latest_version();
|
||||
write!(
|
||||
f,
|
||||
"
|
||||
pub mod codegen {{
|
||||
use crate::NoopMigration;
|
||||
/// The latest migration version, pulled from the latest migration file.
|
||||
pub const LATEST_MIGRATION_VERSION: u16 = {version};
|
||||
/// The Migration Steps used for benchmarking the migration framework.
|
||||
pub type BenchMigrations = (NoopMigration<{}>, NoopMigration<{version}>);
|
||||
}}",
|
||||
version - 1,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts-fixtures"
|
||||
publish = false
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Fixtures for testing contracts pallet."
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true, default-features = true }
|
||||
pezframe-system = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true, default-features = true }
|
||||
parity-wasm = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
twox-hash = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,295 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Compile contracts to wasm.
|
||||
use anyhow::{bail, Context, Result};
|
||||
use parity_wasm::elements::{deserialize_file, serialize_to_file, Internal};
|
||||
use std::{
|
||||
env, fs,
|
||||
hash::Hasher,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use twox_hash::XxHash32;
|
||||
|
||||
/// Read the file at `path` and return its hash as a hex string.
|
||||
fn file_hash(path: &Path) -> String {
|
||||
let data = fs::read(path).expect("file exists; qed");
|
||||
let mut hasher = XxHash32::default();
|
||||
hasher.write(&data);
|
||||
hasher.write(include_bytes!("build.rs"));
|
||||
let hash = hasher.finish();
|
||||
format!("{:x}", hash)
|
||||
}
|
||||
|
||||
/// A contract entry.
|
||||
struct Entry {
|
||||
/// The path to the contract source file.
|
||||
path: PathBuf,
|
||||
/// The hash of the contract source file.
|
||||
hash: String,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
/// Create a new contract entry from the given path.
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let hash = file_hash(&path);
|
||||
Self { path, hash }
|
||||
}
|
||||
|
||||
/// Return the path to the contract source file.
|
||||
fn path(&self) -> &str {
|
||||
self.path.to_str().expect("path is valid unicode; qed")
|
||||
}
|
||||
|
||||
/// Return the name of the contract.
|
||||
fn name(&self) -> &str {
|
||||
self.path
|
||||
.file_stem()
|
||||
.expect("file exits; qed")
|
||||
.to_str()
|
||||
.expect("name is valid unicode; qed")
|
||||
}
|
||||
|
||||
/// Return whether the contract has already been compiled.
|
||||
fn is_cached(&self, out_dir: &Path) -> bool {
|
||||
out_dir.join(self.name()).join(&self.hash).exists()
|
||||
}
|
||||
|
||||
/// Update the cache file for the contract.
|
||||
fn update_cache(&self, out_dir: &Path) -> Result<()> {
|
||||
let cache_dir = out_dir.join(self.name());
|
||||
|
||||
// clear the cache dir if it exists
|
||||
if cache_dir.exists() {
|
||||
fs::remove_dir_all(&cache_dir)?;
|
||||
}
|
||||
|
||||
// re-populate the cache dir with the new hash
|
||||
fs::create_dir_all(&cache_dir)?;
|
||||
fs::write(out_dir.join(&self.hash), "")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the name of the output wasm file.
|
||||
fn out_wasm_filename(&self) -> String {
|
||||
format!("{}.wasm", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all contract entries from the given source directory.
|
||||
/// Contracts that have already been compiled are filtered out.
|
||||
fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec<Entry> {
|
||||
fs::read_dir(contracts_dir)
|
||||
.expect("src dir exists; qed")
|
||||
.filter_map(|file| {
|
||||
let path = file.expect("file exists; qed").path();
|
||||
if path.extension().map_or(true, |ext| ext != "rs") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry = Entry::new(path);
|
||||
if entry.is_cached(out_dir) {
|
||||
None
|
||||
} else {
|
||||
Some(entry)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Create a `Cargo.toml` to compile the given contract entries.
|
||||
fn create_cargo_toml<'a>(
|
||||
fixtures_dir: &Path,
|
||||
root_cargo_toml: &Path,
|
||||
entries: impl Iterator<Item = &'a Entry>,
|
||||
output_dir: &Path,
|
||||
) -> Result<()> {
|
||||
let root_toml: toml::Value = toml::from_str(&fs::read_to_string(root_cargo_toml)?)?;
|
||||
let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?;
|
||||
let mut set_dep = |name, path| -> Result<()> {
|
||||
cargo_toml["dependencies"][name]["path"] = toml::Value::String(
|
||||
fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(),
|
||||
);
|
||||
Ok(())
|
||||
};
|
||||
set_dep("uapi", "../uapi")?;
|
||||
set_dep("common", "./contracts/common")?;
|
||||
cargo_toml["dependencies"]["polkavm-derive"]["version"] =
|
||||
root_toml["workspace"]["dependencies"]["polkavm-derive"].clone();
|
||||
|
||||
cargo_toml["bin"] = toml::Value::Array(
|
||||
entries
|
||||
.map(|entry| {
|
||||
let name = entry.name();
|
||||
let path = entry.path();
|
||||
toml::Value::Table(toml::toml! {
|
||||
name = name
|
||||
path = path
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let cargo_toml = toml::to_string_pretty(&cargo_toml)?;
|
||||
fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Invoke `cargo fmt` to check that fixtures files are formatted.
|
||||
fn invoke_cargo_fmt<'a>(
|
||||
config_path: &Path,
|
||||
files: impl Iterator<Item = &'a Path>,
|
||||
contract_dir: &Path,
|
||||
) -> Result<()> {
|
||||
// If rustfmt is not installed, skip the check.
|
||||
if !Command::new("rustup")
|
||||
.args(["nightly-2024-04-10", "run", "rustfmt", "--version"])
|
||||
.output()
|
||||
.map_or(false, |o| o.status.success())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let fmt_res = Command::new("rustup")
|
||||
.args(["nightly-2024-04-10", "run", "rustfmt", "--check", "--config-path"])
|
||||
.arg(config_path)
|
||||
.args(files)
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
if fmt_res.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&fmt_res.stdout);
|
||||
let stderr = String::from_utf8_lossy(&fmt_res.stderr);
|
||||
eprintln!("{}\n{}", stdout, stderr);
|
||||
eprintln!(
|
||||
"Fixtures files are not formatted.\n
|
||||
Please run `rustup nightly-2024-04-10 run rustfmt --config-path {} {}/*.rs`",
|
||||
config_path.display(),
|
||||
contract_dir.display()
|
||||
);
|
||||
|
||||
anyhow::bail!("Fixtures files are not formatted")
|
||||
}
|
||||
|
||||
/// Build contracts for wasm.
|
||||
fn invoke_wasm_build(current_dir: &Path) -> Result<()> {
|
||||
let encoded_rustflags = [
|
||||
"-Clink-arg=-zstack-size=65536",
|
||||
"-Clink-arg=--import-memory",
|
||||
"-Clinker-plugin-lto",
|
||||
"-Ctarget-cpu=mvp",
|
||||
"-Dwarnings",
|
||||
]
|
||||
.join("\x1f");
|
||||
|
||||
let build_res = Command::new(env::var("CARGO")?)
|
||||
.current_dir(current_dir)
|
||||
.env("CARGO_TARGET_DIR", current_dir.join("target").display().to_string())
|
||||
.env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags)
|
||||
.env("RUSTC_BOOTSTRAP", "1")
|
||||
.args(["build", "--release", "--target=wasm32-unknown-unknown"])
|
||||
.args(["-Z", "build-std=core,alloc"])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
if build_res.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&build_res.stderr);
|
||||
eprintln!("{}", stderr);
|
||||
bail!("Failed to build wasm contracts");
|
||||
}
|
||||
|
||||
/// Post-process the compiled wasm contracts.
|
||||
fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> {
|
||||
let mut module =
|
||||
deserialize_file(input_path).with_context(|| format!("Failed to read {:?}", input_path))?;
|
||||
if let Some(section) = module.export_section_mut() {
|
||||
section.entries_mut().retain(|entry| {
|
||||
matches!(entry.internal(), Internal::Function(_)) &&
|
||||
(entry.field() == "call" || entry.field() == "deploy")
|
||||
});
|
||||
}
|
||||
|
||||
serialize_to_file(output_path, module).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Write the compiled contracts to the given output directory.
|
||||
fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result<()> {
|
||||
for entry in entries {
|
||||
let wasm_output = entry.out_wasm_filename();
|
||||
post_process_wasm(
|
||||
&build_dir.join("target/wasm32-unknown-unknown/release").join(&wasm_output),
|
||||
&out_dir.join(&wasm_output),
|
||||
)?;
|
||||
|
||||
entry.update_cache(out_dir)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the root path of the wasm workspace.
|
||||
fn find_workspace_root(current_dir: &Path) -> Option<PathBuf> {
|
||||
let mut current_dir = current_dir.to_path_buf();
|
||||
|
||||
while current_dir.parent().is_some() {
|
||||
if current_dir.join("Cargo.toml").exists() {
|
||||
let cargo_toml_contents =
|
||||
std::fs::read_to_string(current_dir.join("Cargo.toml")).ok()?;
|
||||
if cargo_toml_contents.contains("[workspace]") {
|
||||
return Some(current_dir);
|
||||
}
|
||||
}
|
||||
|
||||
current_dir.pop();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into();
|
||||
let contracts_dir = fixtures_dir.join("contracts");
|
||||
let out_dir: PathBuf = env::var("OUT_DIR")?.into();
|
||||
let workspace_root = find_workspace_root(&fixtures_dir).expect("workspace root exists; qed");
|
||||
let root_cargo_toml = workspace_root.join("Cargo.toml");
|
||||
|
||||
let entries = collect_entries(&contracts_dir, &out_dir);
|
||||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let tmp_dir_path = tmp_dir.path();
|
||||
|
||||
create_cargo_toml(&fixtures_dir, &root_cargo_toml, entries.iter(), tmp_dir.path())?;
|
||||
invoke_cargo_fmt(
|
||||
&workspace_root.join(".rustfmt.toml"),
|
||||
entries.iter().map(|entry| &entry.path as _),
|
||||
&contracts_dir,
|
||||
)?;
|
||||
|
||||
invoke_wasm_build(tmp_dir_path)?;
|
||||
|
||||
write_output(tmp_dir_path, &out_dir, entries)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "contracts"
|
||||
version = "0.6.3"
|
||||
edition = "2021"
|
||||
|
||||
# Binary targets are injected dynamically by the build script.
|
||||
[[bin]]
|
||||
|
||||
# All paths or versions are injected dynamically by the build script.
|
||||
[dependencies]
|
||||
common = { package = 'pezpallet-contracts-fixtures-common', path = "" }
|
||||
polkavm-derive = { version = "" }
|
||||
uapi = { package = 'pezpallet-contracts-uapi', path = "", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
@@ -0,0 +1,39 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This fixture tests if account_reentrance_count works as expected.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(callee: [u8; 32],);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let reentrance_count = api::account_reentrance_count(callee);
|
||||
|
||||
// Return the reentrance count.
|
||||
api::return_value(uapi::ReturnFlags::empty(), &reentrance_count.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::output;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
// Initialize buffer with 1s so that we can check that it is overwritten.
|
||||
output!(balance, [1u8; 8], api::balance,);
|
||||
|
||||
// Assert that the balance is 0.
|
||||
assert_eq!(&[0u8; 8], balance);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This calls another contract as passed as its account id.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
callee_input: [u8; 4],
|
||||
callee_addr: [u8; 32],
|
||||
);
|
||||
|
||||
// Call the callee
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
callee_input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This calls the supplied dest and transfers 100 balance during this call and copies
|
||||
//! the return code of this call to the output buffer.
|
||||
//! It also forwards its input to the callee.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
100,
|
||||
callee_addr: [u8; 32],
|
||||
input: [u8],
|
||||
);
|
||||
|
||||
// Call the callee
|
||||
let err_code = match api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&100u64.to_le_bytes(), // Value transferred to the contract.
|
||||
input,
|
||||
None,
|
||||
) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This passes its input to `call_runtime` and returns the return value to its caller.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
// Fixture calls should fit into 100 bytes.
|
||||
input!(100, call: [u8], );
|
||||
|
||||
// Use the call passed as input to call the runtime.
|
||||
let err_code = match api::call_runtime(call) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
512,
|
||||
callee_input: [u8; 4],
|
||||
callee_addr: [u8; 32],
|
||||
call: [u8],
|
||||
);
|
||||
|
||||
// Use the call passed as input to call the runtime.
|
||||
api::call_runtime(call).unwrap();
|
||||
|
||||
// Call the callee
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
callee_input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This fixture calls the account_id with the flags and value.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
256,
|
||||
callee_addr: [u8; 32],
|
||||
flags: u32,
|
||||
value: u64,
|
||||
forwarded_input: [u8],
|
||||
);
|
||||
|
||||
api::call_v2(
|
||||
uapi::CallFlags::from_bits(flags).unwrap(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value.to_le_bytes(), // Value transferred to the contract.
|
||||
forwarded_input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This fixture calls the account_id with the 2D Weight limit.
|
||||
//! It returns the result of the call as output data.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
256,
|
||||
callee_addr: [u8; 32],
|
||||
ref_time: u64,
|
||||
proof_size: u64,
|
||||
forwarded_input: [u8],
|
||||
);
|
||||
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
ref_time,
|
||||
proof_size,
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // value transferred to the contract.
|
||||
forwarded_input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(code_hash: [u8; 32],);
|
||||
|
||||
// The value to transfer on instantiation and calls. Chosen to be greater than existential
|
||||
// deposit.
|
||||
let value = 32768u64.to_le_bytes();
|
||||
let salt = [0u8; 0];
|
||||
|
||||
// Callee will use the first 4 bytes of the input to return an exit status.
|
||||
let input = [0u8, 1, 34, 51, 68, 85, 102, 119];
|
||||
let reverted_input = [1u8, 34, 51, 68, 85, 102, 119];
|
||||
|
||||
// Fail to deploy the contract since it returns a non-zero exit status.
|
||||
let res = api::instantiate_v2(
|
||||
code_hash,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&reverted_input,
|
||||
None,
|
||||
None,
|
||||
&salt,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted)));
|
||||
|
||||
// Fail to deploy the contract due to insufficient ref_time weight.
|
||||
let res = api::instantiate_v2(
|
||||
code_hash, 1u64, // too little ref_time weight
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value, &input, None, None, &salt,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped)));
|
||||
|
||||
// Fail to deploy the contract due to insufficient proof_size weight.
|
||||
let res = api::instantiate_v2(
|
||||
code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
1u64, // Too little proof_size weight
|
||||
None, // No deposit limit.
|
||||
&value, &input, None, None, &salt,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped)));
|
||||
|
||||
// Deploy the contract successfully.
|
||||
let mut callee = [0u8; 32];
|
||||
let callee = &mut &mut callee[..];
|
||||
|
||||
api::instantiate_v2(
|
||||
code_hash,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&input,
|
||||
Some(callee),
|
||||
None,
|
||||
&salt,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(callee.len(), 32);
|
||||
|
||||
// Call the new contract and expect it to return failing exit code.
|
||||
let res = api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&reverted_input,
|
||||
None,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted)));
|
||||
|
||||
// Fail to call the contract due to insufficient ref_time weight.
|
||||
let res = api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
1u64, // Too little ref_time weight.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&input,
|
||||
None,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped)));
|
||||
|
||||
// Fail to call the contract due to insufficient proof_size weight.
|
||||
let res = api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
1u64, // too little proof_size weight
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&input,
|
||||
None,
|
||||
);
|
||||
assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped)));
|
||||
|
||||
// Call the contract successfully.
|
||||
let mut output = [0u8; 4];
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&value,
|
||||
&input,
|
||||
Some(&mut &mut output[..]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&output, &input[4..])
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This fixture calls caller_is_origin `n` times.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(n: u32, );
|
||||
|
||||
for _ in 0..n {
|
||||
let _ = api::caller_is_origin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Call chain extension by passing through input and output of this contract.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(input, 8, func_id: u32,);
|
||||
|
||||
// the chain extension passes through the input and returns it as output
|
||||
let mut output_buffer = [0u8; 32];
|
||||
let output = &mut &mut output_buffer[0..input.len()];
|
||||
|
||||
let ret_id = api::call_chain_extension(func_id, input, Some(output));
|
||||
assert_eq!(ret_id, func_id);
|
||||
|
||||
api::return_value(uapi::ReturnFlags::empty(), output);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Call chain extension two times with the specified func_ids
|
||||
//! It then calls itself once
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::{input, output};
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
input,
|
||||
func_id1: u32,
|
||||
func_id2: u32,
|
||||
stop_recurse: u8,
|
||||
);
|
||||
|
||||
api::call_chain_extension(func_id1, input, None);
|
||||
api::call_chain_extension(func_id2, input, None);
|
||||
|
||||
if stop_recurse == 0 {
|
||||
// Setup next call
|
||||
input[0..4].copy_from_slice(&((3 << 16) | 2u32).to_le_bytes());
|
||||
input[4..8].copy_from_slice(&((3 << 16) | 3u32).to_le_bytes());
|
||||
input[8] = 1u8;
|
||||
|
||||
// Read the contract address.
|
||||
output!(addr, [0u8; 32], api::address,);
|
||||
|
||||
// call self
|
||||
api::call_v2(
|
||||
uapi::CallFlags::ALLOW_REENTRY,
|
||||
addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts-fixtures-common"
|
||||
publish = false
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Common utilities for pezpallet-contracts-fixtures."
|
||||
|
||||
[dependencies]
|
||||
uapi = { package = 'pezpallet-contracts-uapi', path = "../../../uapi", default-features = false }
|
||||
@@ -0,0 +1,154 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#![no_std]
|
||||
|
||||
pub use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
|
||||
/// Utility macro to read input passed to a contract.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// input$!(
|
||||
/// var1: u32, // [0, 4) var1 decoded as u32
|
||||
/// var2: [u8; 32], // [4, 36) var2 decoded as a [u8] slice
|
||||
/// var3: u8, // [36, 37) var3 decoded as a u8
|
||||
/// );
|
||||
///
|
||||
/// // Input and size can be specified as well:
|
||||
/// input$!(
|
||||
/// input, // input buffer (optional)
|
||||
/// 512, // input size (optional)
|
||||
/// var4: u32, // [0, 4) var4 decoded as u32
|
||||
/// var5: [u8], // [4, ..) var5 decoded as a [u8] slice
|
||||
/// );
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! input {
|
||||
(@inner $input:expr, $cursor:expr,) => {};
|
||||
(@size $size:expr, ) => { $size };
|
||||
|
||||
// Match a u8 variable.
|
||||
// e.g input!(var1: u8, );
|
||||
(@inner $input:expr, $cursor:expr, $var:ident: u8, $($rest:tt)*) => {
|
||||
let $var = $input[$cursor];
|
||||
input!(@inner $input, $cursor + 1, $($rest)*);
|
||||
};
|
||||
|
||||
// Size of u8 variable.
|
||||
(@size $size:expr, $var:ident: u8, $($rest:tt)*) => {
|
||||
input!(@size $size + 1, $($rest)*)
|
||||
};
|
||||
|
||||
// Match a u64 variable.
|
||||
// e.g input!(var1: u64, );
|
||||
(@inner $input:expr, $cursor:expr, $var:ident: u64, $($rest:tt)*) => {
|
||||
let $var = u64::from_le_bytes($input[$cursor..$cursor + 8].try_into().unwrap());
|
||||
input!(@inner $input, $cursor + 8, $($rest)*);
|
||||
};
|
||||
|
||||
// Size of u64 variable.
|
||||
(@size $size:expr, $var:ident: u64, $($rest:tt)*) => {
|
||||
input!(@size $size + 8, $($rest)*)
|
||||
};
|
||||
|
||||
// Match a u32 variable.
|
||||
// e.g input!(var1: u32, );
|
||||
(@inner $input:expr, $cursor:expr, $var:ident: u32, $($rest:tt)*) => {
|
||||
let $var = u32::from_le_bytes($input[$cursor..$cursor + 4].try_into().unwrap());
|
||||
input!(@inner $input, $cursor + 4, $($rest)*);
|
||||
};
|
||||
|
||||
// Size of u32 variable.
|
||||
(@size $size:expr, $var:ident: u32, $($rest:tt)*) => {
|
||||
input!(@size $size + 4, $($rest)*)
|
||||
};
|
||||
|
||||
// Match a u8 slice with the remaining bytes.
|
||||
// e.g input!(512, var1: [u8; 32], var2: [u8], );
|
||||
(@inner $input:expr, $cursor:expr, $var:ident: [u8],) => {
|
||||
let $var = &$input[$cursor..];
|
||||
};
|
||||
|
||||
// Match a u8 slice of the given size.
|
||||
// e.g input!(var1: [u8; 32], );
|
||||
(@inner $input:expr, $cursor:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => {
|
||||
let $var = &$input[$cursor..$cursor+$n];
|
||||
input!(@inner $input, $cursor + $n, $($rest)*);
|
||||
};
|
||||
|
||||
// Size of a u8 slice.
|
||||
(@size $size:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => {
|
||||
input!(@size $size + $n, $($rest)*)
|
||||
};
|
||||
|
||||
// Entry point, with the buffer and it's size specified first.
|
||||
// e.g input!(buffer, 512, var1: u32, var2: [u8], );
|
||||
($buffer:ident, $size:expr, $($rest:tt)*) => {
|
||||
let mut $buffer = [0u8; $size];
|
||||
let $buffer = &mut &mut $buffer[..];
|
||||
$crate::api::input($buffer);
|
||||
input!(@inner $buffer, 0, $($rest)*);
|
||||
};
|
||||
|
||||
// Entry point, with the name of the buffer specified and size of the input buffer computed.
|
||||
// e.g input!(buffer, var1: u32, var2: u64, );
|
||||
($buffer: ident, $($rest:tt)*) => {
|
||||
input!($buffer, input!(@size 0, $($rest)*), $($rest)*);
|
||||
};
|
||||
|
||||
// Entry point, with the size of the input buffer computed.
|
||||
// e.g input!(var1: u32, var2: u64, );
|
||||
($($rest:tt)*) => {
|
||||
input!(buffer, $($rest)*);
|
||||
};
|
||||
}
|
||||
|
||||
/// Utility macro to invoke a host function that expect a `output: &mut &mut [u8]` as last argument.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// // call `api::caller` and store the output in `caller`
|
||||
/// output!(caller, [0u8; 32], api::caller,);
|
||||
///
|
||||
/// // call `api::get_storage` and store the output in `address`
|
||||
/// output!(address, [0u8; 32], api::get_storage, &[1u8; 32]);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! output {
|
||||
($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => {
|
||||
let mut $output = $buffer;
|
||||
let $output = &mut &mut $output[..];
|
||||
$host_fn($($arg,)* $output);
|
||||
};
|
||||
}
|
||||
|
||||
/// Similar to `output!` but unwraps the result.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_output {
|
||||
($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => {
|
||||
let mut $output = $buffer;
|
||||
let $output = &mut &mut $output[..];
|
||||
$host_fn($($arg,)* $output).unwrap();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This calls another contract as passed as its account id. It also creates some storage.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
buffer,
|
||||
input: [u8; 4],
|
||||
callee: [u8; 32],
|
||||
deposit_limit: [u8; 8],
|
||||
);
|
||||
|
||||
// create 4 byte of storage before calling
|
||||
api::set_storage(buffer, &[1u8; 4]);
|
||||
|
||||
// Call the callee
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
Some(deposit_limit),
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// create 8 byte of storage after calling
|
||||
// item of 12 bytes because we override 4 bytes
|
||||
api::set_storage(buffer, &[1u8; 12]);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This instantiates another contract and passes some input to its constructor.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
input: [u8; 4],
|
||||
code_hash: [u8; 32],
|
||||
deposit_limit: [u8; 8],
|
||||
);
|
||||
|
||||
let value = 10_000u64.to_le_bytes();
|
||||
let salt = [0u8; 0];
|
||||
let mut address = [0u8; 32];
|
||||
let address = &mut &mut address[..];
|
||||
|
||||
api::instantiate_v2(
|
||||
code_hash,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
Some(deposit_limit),
|
||||
&value,
|
||||
input,
|
||||
Some(address),
|
||||
None,
|
||||
&salt,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Return the deployed contract address.
|
||||
api::return_value(uapi::ReturnFlags::empty(), address);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This calls another contract as passed as its account id. It also creates some transient storage.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
buffer,
|
||||
len: u32,
|
||||
input: [u8; 4],
|
||||
callee: [u8; 32],
|
||||
);
|
||||
|
||||
let data = [0u8; 16 * 1024];
|
||||
let value = &data[..len as usize];
|
||||
#[allow(deprecated)]
|
||||
api::set_transient_storage(buffer, value);
|
||||
|
||||
// Call the callee
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None,
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
/// Called by the tests.
|
||||
///
|
||||
/// The `call` function expects data in a certain format in the input buffer.
|
||||
///
|
||||
/// 1. The first byte encodes an identifier for the crypto hash function under test. (*)
|
||||
/// 2. The rest encodes the input data that is directly fed into the crypto hash function chosen in
|
||||
/// 1.
|
||||
///
|
||||
/// The `deploy` function then computes the chosen crypto hash function
|
||||
/// given the input and puts the result into the output buffer.
|
||||
/// After contract execution the test driver then asserts that the returned
|
||||
/// values are equal to the expected bytes for the input and chosen hash
|
||||
/// function.
|
||||
///
|
||||
/// (*) The possible value for the crypto hash identifiers can be found below:
|
||||
///
|
||||
/// | value | Algorithm | Bit Width |
|
||||
/// |-------|-----------|-----------|
|
||||
/// | 0 | SHA2 | 256 |
|
||||
/// | 1 | KECCAK | 256 |
|
||||
/// | 2 | BLAKE2 | 256 |
|
||||
/// | 3 | BLAKE2 | 128 |
|
||||
/// ---------------------------------
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
256,
|
||||
chosen_hash_fn: u8,
|
||||
input: [u8],
|
||||
);
|
||||
|
||||
match chosen_hash_fn {
|
||||
1 => {
|
||||
let mut output = [0u8; 32];
|
||||
api::hash_sha2_256(input, &mut output);
|
||||
api::return_value(uapi::ReturnFlags::empty(), &output);
|
||||
},
|
||||
2 => {
|
||||
let mut output = [0u8; 32];
|
||||
api::hash_keccak_256(input, &mut output);
|
||||
api::return_value(uapi::ReturnFlags::empty(), &output);
|
||||
},
|
||||
3 => {
|
||||
let mut output = [0u8; 32];
|
||||
api::hash_blake2_256(input, &mut output);
|
||||
api::return_value(uapi::ReturnFlags::empty(), &output);
|
||||
},
|
||||
4 => {
|
||||
let mut output = [0u8; 16];
|
||||
api::hash_blake2_128(input, &mut output);
|
||||
api::return_value(uapi::ReturnFlags::empty(), &output);
|
||||
},
|
||||
_ => panic!("unknown crypto hash function identifier"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Emit a debug message with an invalid utf-8 code.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
api::debug_message(b"\xFC").unwrap();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Emit a "Hello World!" debug message but assume that logging is disabled.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
api::debug_message(b"Hello World!").unwrap();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Emit a "Hello World!" debug message.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
api::debug_message(b"Hello World!").unwrap();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(code_hash: [u8; 32],);
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1u8;
|
||||
|
||||
let mut value = [0u8; 32];
|
||||
let value = &mut &mut value[..];
|
||||
value[0] = 2u8;
|
||||
|
||||
api::set_storage(&key, value);
|
||||
api::get_storage(&key, value).unwrap();
|
||||
assert!(value[0] == 2u8);
|
||||
|
||||
let input = [0u8; 0];
|
||||
api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap();
|
||||
|
||||
api::get_storage(&[1u8], value).unwrap();
|
||||
assert!(value[0] == 1u8);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::output;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1u8;
|
||||
|
||||
// Place a value in storage.
|
||||
let mut value = [0u8; 32];
|
||||
let value = &mut &mut value[..];
|
||||
value[0] = 1u8;
|
||||
api::set_storage(&key, value);
|
||||
|
||||
// Assert that `value_transferred` is equal to the value
|
||||
// passed to the `caller` contract: 1337.
|
||||
output!(value_transferred, [0u8; 8], api::value_transferred,);
|
||||
let value_transferred = u64::from_le_bytes(value_transferred[..].try_into().unwrap());
|
||||
assert_eq!(value_transferred, 1337);
|
||||
|
||||
// Assert that ALICE is the caller of the contract.
|
||||
output!(caller, [0u8; 32], api::caller,);
|
||||
assert_eq!(&caller[..], &[1u8; 32]);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(code_hash: [u8; 32],);
|
||||
|
||||
// Delegate call into passed code hash.
|
||||
let input = [0u8; 0];
|
||||
api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
const ADDRESS_KEY: [u8; 32] = [0u8; 32];
|
||||
const VALUE: [u8; 8] = [0, 0, 1u8, 0, 0, 0, 0, 0];
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
input!(code_hash: [u8; 32],);
|
||||
|
||||
let input = [0u8; 0];
|
||||
let mut address = [0u8; 32];
|
||||
let address = &mut &mut address[..];
|
||||
let salt = [71u8, 17u8];
|
||||
|
||||
api::instantiate_v2(
|
||||
code_hash,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&VALUE,
|
||||
&input,
|
||||
Some(address),
|
||||
None,
|
||||
&salt,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Return the deployed contract address.
|
||||
api::set_storage(&ADDRESS_KEY, address);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
let mut callee_addr = [0u8; 32];
|
||||
let callee_addr = &mut &mut callee_addr[..];
|
||||
api::get_storage(&ADDRESS_KEY, callee_addr).unwrap();
|
||||
|
||||
// Calling the destination contract with non-empty input data should fail.
|
||||
let res = api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&VALUE,
|
||||
&[0u8; 1],
|
||||
None,
|
||||
);
|
||||
assert!(matches!(res, Err(uapi::ReturnErrorCode::CalleeTrapped)));
|
||||
|
||||
// Call the destination contract regularly, forcing it to self-destruct.
|
||||
api::call_v2(
|
||||
uapi::CallFlags::empty(),
|
||||
callee_addr,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size weight to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&VALUE,
|
||||
&[0u8; 0],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::output;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
output!(balance, [0u8; 8], api::balance,);
|
||||
let balance = u64::from_le_bytes(balance[..].try_into().unwrap());
|
||||
|
||||
output!(minimum_balance, [0u8; 8], api::minimum_balance,);
|
||||
let minimum_balance = u64::from_le_bytes(minimum_balance[..].try_into().unwrap());
|
||||
|
||||
// Make the transferred value exceed the balance by adding the minimum balance.
|
||||
let balance = balance + minimum_balance;
|
||||
|
||||
// Try to self-destruct by sending more balance to the 0 address.
|
||||
// The call will fail because a contract transfer has a keep alive requirement.
|
||||
let res = api::transfer(&[0u8; 32], &balance.to_le_bytes());
|
||||
assert!(matches!(res, Err(uapi::ReturnErrorCode::TransferFailed)));
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
signature: [u8; 65],
|
||||
hash: [u8; 32],
|
||||
);
|
||||
|
||||
let mut output = [0u8; 33];
|
||||
api::ecdsa_recover(
|
||||
&signature[..].try_into().unwrap(),
|
||||
&hash[..].try_into().unwrap(),
|
||||
&mut output,
|
||||
)
|
||||
.unwrap();
|
||||
api::return_value(uapi::ReturnFlags::empty(), &output);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
let buffer = [1u8, 2, 3, 4];
|
||||
api::deposit_event(&[0u8; 0], &buffer);
|
||||
api::return_value(uapi::ReturnFlags::empty(), &buffer);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
unreachable!()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(len: u32,);
|
||||
|
||||
let buffer = [0u8; 16 * 1024 + 1];
|
||||
let data = &buffer[..len as usize];
|
||||
|
||||
api::deposit_event(&[0u8; 0], data);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add(a: f32, b: f32) -> f32 {
|
||||
a + b
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(buffer, 36, code_hash: [u8; 32],);
|
||||
let input = &buffer[32..];
|
||||
|
||||
let err_code = match api::instantiate_v2(
|
||||
code_hash,
|
||||
0u64, // How much ref_time weight to devote for the execution. 0 = all.
|
||||
0u64, /* How much proof_size weight to devote for the execution. 0 =
|
||||
* all. */
|
||||
None, // No deposit limit.
|
||||
&10_000u64.to_le_bytes(), // Value to transfer.
|
||||
input,
|
||||
None,
|
||||
None,
|
||||
&[0u8; 0], // Empty salt.
|
||||
) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
// Exit with success and take transfer return code to the output buffer.
|
||||
api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This contract tests the behavior of locking / unlocking delegate_dependencies when delegate
|
||||
//! calling into a contract.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
const ALICE: [u8; 32] = [1u8; 32];
|
||||
|
||||
/// Load input data and perform the action specified by the input.
|
||||
/// If `delegate_call` is true, then delegate call into the contract.
|
||||
fn load_input(delegate_call: bool) {
|
||||
input!(
|
||||
action: u32,
|
||||
code_hash: [u8; 32],
|
||||
);
|
||||
|
||||
match action {
|
||||
// 1 = Lock delegate dependency
|
||||
1 => {
|
||||
api::lock_delegate_dependency(code_hash);
|
||||
},
|
||||
// 2 = Unlock delegate dependency
|
||||
2 => {
|
||||
api::unlock_delegate_dependency(code_hash);
|
||||
},
|
||||
// 3 = Terminate
|
||||
3 => {
|
||||
api::terminate_v1(&ALICE);
|
||||
},
|
||||
// Everything else is a noop
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if delegate_call {
|
||||
api::delegate_call(uapi::CallFlags::empty(), code_hash, &[], None).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
load_input(false);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
load_input(true);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Does two stores to two separate storage items
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
size1: u32,
|
||||
size2: u32,
|
||||
);
|
||||
|
||||
let buffer = [0u8; 16 * 1024];
|
||||
|
||||
// Place a values in storage sizes are specified in the input buffer.
|
||||
// We don't care about the contents of the storage item.
|
||||
api::set_storage(&[1u8; 32], &buffer[0..size1 as _]);
|
||||
api::set_storage(&[2u8; 32], &buffer[0..size2 as _]);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
api::return_value(uapi::ReturnFlags::empty(), &2u32.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
ok_trap_revert();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
ok_trap_revert();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn ok_trap_revert() {
|
||||
input!(buffer, 4,);
|
||||
match buffer.first().unwrap_or(&0) {
|
||||
1 => api::return_value(uapi::ReturnFlags::REVERT, &[0u8; 0]),
|
||||
2 => panic!(),
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This fixture tests if read-only call works as expected.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
256,
|
||||
callee_addr: [u8; 32],
|
||||
callee_input: [u8],
|
||||
);
|
||||
|
||||
// Call the callee
|
||||
api::call_v2(
|
||||
uapi::CallFlags::READ_ONLY,
|
||||
callee_addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
callee_input,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This fixture calls itself as many times as passed as argument.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::{input, output};
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(calls_left: u32, );
|
||||
|
||||
// own address
|
||||
output!(addr, [0u8; 32], api::address,);
|
||||
|
||||
if calls_left == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
api::call_v2(
|
||||
uapi::CallFlags::ALLOW_REENTRY,
|
||||
addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much deposit_limit to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
&(calls_left - 1).to_le_bytes(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This fixture tests if account_reentrance_count works as expected.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::{input, output};
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(expected_reentrance_count: u32,);
|
||||
|
||||
// Read the contract address.
|
||||
output!(addr, [0u8; 32], api::address,);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let reentrance_count = api::reentrance_count();
|
||||
assert_eq!(reentrance_count, expected_reentrance_count);
|
||||
|
||||
// Re-enter 5 times in a row and assert that the reentrant counter works as expected.
|
||||
if expected_reentrance_count != 5 {
|
||||
let count = (expected_reentrance_count + 1).to_le_bytes();
|
||||
|
||||
api::call_v2(
|
||||
uapi::CallFlags::ALLOW_REENTRY,
|
||||
addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value transferred to the contract.
|
||||
&count,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This fixture tests if account_reentrance_count works as expected.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
input,
|
||||
code_hash: [u8; 32],
|
||||
call_stack_height: u32,
|
||||
);
|
||||
|
||||
let call_stack_height = call_stack_height + 1;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let reentrance_count = api::reentrance_count();
|
||||
|
||||
// Reentrance count stays 0.
|
||||
assert_eq!(reentrance_count, 0);
|
||||
|
||||
// Re-enter 5 times in a row and assert that the reentrant counter works as expected.
|
||||
if call_stack_height != 5 {
|
||||
let mut input = [0u8; 36];
|
||||
input[0..32].copy_from_slice(code_hash);
|
||||
input[32..36].copy_from_slice(&call_stack_height.to_le_bytes());
|
||||
api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
call();
|
||||
}
|
||||
|
||||
/// Reads the first byte as the exit status and copy all but the first 4 bytes of the input as
|
||||
/// output data.
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
input, 128,
|
||||
exit_status: [u8; 4],
|
||||
output: [u8],
|
||||
);
|
||||
|
||||
// Burn some PoV, clear_storage consumes some PoV as in order to clear the storage we need to we
|
||||
// need to read its size first.
|
||||
api::clear_storage_v1(b"");
|
||||
|
||||
let exit_status = uapi::ReturnFlags::from_bits(exit_status[0] as u32).unwrap();
|
||||
api::return_value(exit_status, output);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
#[allow(clippy::empty_loop)]
|
||||
loop {}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::{input, output};
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
const DJANGO: [u8; 32] = [4u8; 32];
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
// If the input data is not empty, then recursively call self with empty input data.
|
||||
// This should trap instead of self-destructing since a contract cannot be removed, while it's
|
||||
// in the execution stack. If the recursive call traps, then trap here as well.
|
||||
input!(input, 4,);
|
||||
|
||||
if !input.is_empty() {
|
||||
output!(addr, [0u8; 32], api::address,);
|
||||
api::call_v2(
|
||||
uapi::CallFlags::ALLOW_REENTRY,
|
||||
addr,
|
||||
0u64, // How much ref_time to devote for the execution. 0 = all.
|
||||
0u64, // How much proof_size to devote for the execution. 0 = all.
|
||||
None, // No deposit limit.
|
||||
&0u64.to_le_bytes(), // Value to transfer.
|
||||
&[0u8; 0],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
// Try to terminate and give balance to django.
|
||||
api::terminate_v1(&DJANGO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
api::terminate_v1(&[0u8; 32]);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {}
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(addr: [u8; 32],);
|
||||
api::set_code_hash(addr).unwrap();
|
||||
|
||||
// we return 1 after setting new code_hash
|
||||
// next `call` will NOT return this value, because contract code has been changed
|
||||
api::return_value(uapi::ReturnFlags::empty(), &1u32.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
api::set_storage(&[0u8; 32], &[0u8; 4]);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(len: u32, );
|
||||
|
||||
let buffer = [0u8; 16 * 1024];
|
||||
let data = &buffer[..len as usize];
|
||||
|
||||
// Place a garbage value in the transient storage, with the size specified by the call input.
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1;
|
||||
|
||||
#[allow(deprecated)]
|
||||
api::set_transient_storage(&key, data);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
signature: [u8; 64],
|
||||
pub_key: [u8; 32],
|
||||
msg: [u8; 11],
|
||||
);
|
||||
|
||||
let exit_status = match api::sr25519_verify(
|
||||
&signature.try_into().unwrap(),
|
||||
msg,
|
||||
&pub_key.try_into().unwrap(),
|
||||
) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
// Exit with success and take transfer return code to the output buffer.
|
||||
api::return_value(uapi::ReturnFlags::empty(), &exit_status.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This contract tests the storage APIs. It sets and clears storage values using the different
|
||||
//! versions of the storage APIs.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::unwrap_output;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
const KEY: [u8; 32] = [1u8; 32];
|
||||
const VALUE_1: [u8; 4] = [1u8; 4];
|
||||
const VALUE_2: [u8; 4] = [2u8; 4];
|
||||
const VALUE_3: [u8; 4] = [3u8; 4];
|
||||
|
||||
api::set_storage(&KEY, &VALUE_1);
|
||||
assert_eq!(api::contains_storage(&KEY), Some(VALUE_1.len() as _));
|
||||
unwrap_output!(val, [0u8; 4], api::get_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_1);
|
||||
|
||||
let existing = api::set_storage_v1(&KEY, &VALUE_2);
|
||||
assert_eq!(existing, Some(VALUE_1.len() as _));
|
||||
unwrap_output!(val, [0u8; 4], api::get_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_2);
|
||||
|
||||
api::clear_storage(&KEY);
|
||||
assert_eq!(api::contains_storage(&KEY), None);
|
||||
|
||||
let existing = api::set_storage_v2(&KEY, &VALUE_3);
|
||||
assert_eq!(existing, None);
|
||||
assert_eq!(api::contains_storage_v1(&KEY), Some(VALUE_1.len() as _));
|
||||
unwrap_output!(val, [0u8; 32], api::get_storage_v1, &KEY);
|
||||
assert_eq!(**val, VALUE_3);
|
||||
|
||||
api::clear_storage_v1(&KEY);
|
||||
assert_eq!(api::contains_storage_v1(&KEY), None);
|
||||
let existing = api::set_storage_v2(&KEY, &VALUE_3);
|
||||
assert_eq!(existing, None);
|
||||
unwrap_output!(val, [0u8; 32], api::take_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_3);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(len: u32, );
|
||||
|
||||
let mut buffer = [0u8; 16 * 1024 + 1];
|
||||
let data = &buffer[..len as usize];
|
||||
|
||||
// Place a garbage value in storage, the size of which is specified by the call input.
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1;
|
||||
|
||||
api::set_storage(&key, data);
|
||||
|
||||
let data = &mut &mut buffer[..];
|
||||
api::get_storage(&key, data).unwrap();
|
||||
assert_eq!(data.len(), len as usize);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(len: u32, );
|
||||
|
||||
let buffer = [0u8; 16 * 1024 + 1];
|
||||
let data = &buffer[..len as usize];
|
||||
|
||||
// Place a garbage value in storage, the size of which is specified by the call input.
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1;
|
||||
|
||||
api::set_storage(&key, data);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {
|
||||
input!(len: u32, );
|
||||
|
||||
let buffer = [0u8; 16 * 1024 + 1];
|
||||
let data = &buffer[..len as usize];
|
||||
|
||||
// place a garbage value in storage, the size of which is specified by the call input.
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 1;
|
||||
|
||||
api::set_storage(&key, data);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {}
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate common;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
let ret_code = match api::transfer(&[0u8; 32], &100u64.to_le_bytes()) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
// Exit with success and take transfer return code to the output buffer.
|
||||
api::return_value(uapi::ReturnFlags::empty(), &ret_code.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This contract tests the transient storage APIs.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::unwrap_output;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
const KEY: [u8; 32] = [1u8; 32];
|
||||
const VALUE_1: [u8; 4] = [1u8; 4];
|
||||
const VALUE_2: [u8; 4] = [2u8; 4];
|
||||
const VALUE_3: [u8; 4] = [3u8; 4];
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
let existing = api::set_transient_storage(&KEY, &VALUE_1);
|
||||
assert_eq!(existing, None);
|
||||
assert_eq!(api::contains_transient_storage(&KEY), Some(VALUE_1.len() as _));
|
||||
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_1);
|
||||
|
||||
let existing = api::set_transient_storage(&KEY, &VALUE_2);
|
||||
assert_eq!(existing, Some(VALUE_1.len() as _));
|
||||
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_2);
|
||||
|
||||
api::clear_transient_storage(&KEY);
|
||||
assert_eq!(api::contains_transient_storage(&KEY), None);
|
||||
|
||||
let existing = api::set_transient_storage(&KEY, &VALUE_3);
|
||||
assert_eq!(existing, None);
|
||||
unwrap_output!(val, [0u8; 32], api::take_transient_storage, &KEY);
|
||||
assert_eq!(**val, VALUE_3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(512, msg: [u8],);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let err_code = match api::xcm_execute(msg) {
|
||||
Ok(_) => 0u32,
|
||||
Err(code) => code as u32,
|
||||
};
|
||||
|
||||
api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes());
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use common::input;
|
||||
use uapi::{HostFn, HostFnImpl as api};
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn deploy() {}
|
||||
|
||||
#[no_mangle]
|
||||
#[polkavm_derive::polkavm_export]
|
||||
pub extern "C" fn call() {
|
||||
input!(
|
||||
512,
|
||||
dest: [u8; 3],
|
||||
msg: [u8],
|
||||
);
|
||||
|
||||
let mut message_id = [0u8; 32];
|
||||
|
||||
#[allow(deprecated)]
|
||||
api::xcm_send(dest, msg, &mut message_id).unwrap();
|
||||
api::return_value(uapi::ReturnFlags::empty(), &message_id);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use pezsp_runtime::traits::Hash;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
/// Load a given wasm module and returns a wasm binary contents along with it's hash.
|
||||
/// Use the legacy compile_module as fallback, if the rust fixture does not exist yet.
|
||||
pub fn compile_module<T>(
|
||||
fixture_name: &str,
|
||||
) -> anyhow::Result<(Vec<u8>, <T::Hashing as Hash>::Output)>
|
||||
where
|
||||
T: pezframe_system::Config,
|
||||
{
|
||||
let out_dir: PathBuf = env!("OUT_DIR").into();
|
||||
let fixture_path = out_dir.join(format!("{fixture_name}.wasm"));
|
||||
let binary = fs::read(fixture_path)?;
|
||||
let code_hash = T::Hashing::hash(&binary);
|
||||
Ok((binary, code_hash))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn out_dir_should_have_compiled_mocks() {
|
||||
let out_dir: std::path::PathBuf = env!("OUT_DIR").into();
|
||||
assert!(out_dir.join("dummy.wasm").exists());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts-mock-network"
|
||||
version = "3.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "A mock network for testing pezpallet-contracts"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive", "max-encoded-len"], workspace = true }
|
||||
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-assets = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-contracts = { workspace = true, default-features = true }
|
||||
pezpallet-contracts-uapi = { workspace = true }
|
||||
pezpallet-message-queue = { workspace = true, default-features = true }
|
||||
pezpallet-timestamp = { workspace = true, default-features = true }
|
||||
pezpallet-xcm = { workspace = true }
|
||||
pezkuwi-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
|
||||
pezkuwi-teyrchain-primitives = { workspace = true, default-features = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-keystore = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true, default-features = true }
|
||||
xcm-executor = { workspace = true }
|
||||
xcm-simulator = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-contracts-fixtures = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-contracts/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"pezpallet-xcm/std",
|
||||
"scale-info/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-keystore/std",
|
||||
"pezsp-runtime/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-contracts-fixtures/runtime-benchmarks",
|
||||
"pezpallet-contracts/runtime-benchmarks",
|
||||
"pezpallet-message-queue/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezpallet-xcm/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
"xcm-simulator/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod mocks;
|
||||
pub mod primitives;
|
||||
pub mod relay_chain;
|
||||
pub mod teyrchain;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::primitives::{AccountId, UNITS};
|
||||
pub use pezpallet_contracts::test_utils::{ALICE, BOB};
|
||||
use pezsp_runtime::BuildStorage;
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
pub use xcm_simulator::TestExt;
|
||||
use xcm_simulator::{decl_test_network, decl_test_relay_chain, decl_test_teyrchain};
|
||||
|
||||
// Accounts
|
||||
pub const ADMIN: pezsp_runtime::AccountId32 = pezsp_runtime::AccountId32::new([0u8; 32]);
|
||||
|
||||
// Balances
|
||||
pub const INITIAL_BALANCE: u128 = 1_000_000_000 * UNITS;
|
||||
|
||||
decl_test_teyrchain! {
|
||||
pub struct ParaA {
|
||||
Runtime = teyrchain::Runtime,
|
||||
XcmpMessageHandler = teyrchain::MsgQueue,
|
||||
DmpMessageHandler = teyrchain::MsgQueue,
|
||||
new_ext = para_ext(1),
|
||||
}
|
||||
}
|
||||
|
||||
decl_test_relay_chain! {
|
||||
pub struct Relay {
|
||||
Runtime = relay_chain::Runtime,
|
||||
RuntimeCall = relay_chain::RuntimeCall,
|
||||
RuntimeEvent = relay_chain::RuntimeEvent,
|
||||
XcmConfig = relay_chain::XcmConfig,
|
||||
MessageQueue = relay_chain::MessageQueue,
|
||||
System = relay_chain::System,
|
||||
new_ext = relay_ext(),
|
||||
}
|
||||
}
|
||||
|
||||
decl_test_network! {
|
||||
pub struct MockNet {
|
||||
relay_chain = Relay,
|
||||
teyrchains = vec![
|
||||
(1, ParaA),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relay_sovereign_account_id() -> AccountId {
|
||||
let location: Location = (Parent,).into();
|
||||
teyrchain::SovereignAccountOf::convert_location(&location).unwrap()
|
||||
}
|
||||
|
||||
pub fn teyrchain_sovereign_account_id(para: u32) -> AccountId {
|
||||
let location: Location = (Teyrchain(para),).into();
|
||||
relay_chain::SovereignAccountOf::convert_location(&location).unwrap()
|
||||
}
|
||||
|
||||
pub fn teyrchain_account_sovereign_account_id(
|
||||
para: u32,
|
||||
who: pezsp_runtime::AccountId32,
|
||||
) -> AccountId {
|
||||
let location: Location = (
|
||||
Teyrchain(para),
|
||||
AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() },
|
||||
)
|
||||
.into();
|
||||
relay_chain::SovereignAccountOf::convert_location(&location).unwrap()
|
||||
}
|
||||
|
||||
pub fn para_ext(para_id: u32) -> pezsp_io::TestExternalities {
|
||||
use teyrchain::{MsgQueue, Runtime, System};
|
||||
|
||||
let mut t = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(relay_sovereign_account_id(), INITIAL_BALANCE),
|
||||
(BOB, INITIAL_BALANCE),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
pezpallet_assets::GenesisConfig::<Runtime> {
|
||||
assets: vec![
|
||||
(0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token
|
||||
],
|
||||
metadata: Default::default(),
|
||||
accounts: vec![
|
||||
(0u128, ALICE, INITIAL_BALANCE),
|
||||
(0u128, relay_sovereign_account_id(), INITIAL_BALANCE),
|
||||
],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
pezsp_tracing::try_init_simple();
|
||||
System::set_block_number(1);
|
||||
MsgQueue::set_para_id(para_id.into());
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn relay_ext() -> pezsp_io::TestExternalities {
|
||||
use relay_chain::{Runtime, System};
|
||||
|
||||
let mut t = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(teyrchain_sovereign_account_id(1), INITIAL_BALANCE),
|
||||
(teyrchain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
pub type TeyrchainPalletXcm = pezpallet_xcm::Pallet<teyrchain::Runtime>;
|
||||
pub type TeyrchainBalances = pezpallet_balances::Pallet<teyrchain::Runtime>;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod msg_queue;
|
||||
pub mod relay_message_queue;
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain runtime mock.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezkuwi_primitives::BlockNumber as RelayBlockNumber;
|
||||
use pezkuwi_teyrchain_primitives::primitives::{
|
||||
DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler,
|
||||
};
|
||||
use pezsp_runtime::traits::{Get, Hash};
|
||||
|
||||
use xcm::{latest::prelude::*, VersionedXcm};
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type TeyrchainId<T: Config> = StorageValue<_, ParaId, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
/// A queue of received DMP messages
|
||||
pub(super) type ReceivedDmp<T: Config> = StorageValue<_, Vec<Xcm<T::RuntimeCall>>, ValueQuery>;
|
||||
|
||||
impl<T: Config> Get<ParaId> for Pallet<T> {
|
||||
fn get() -> ParaId {
|
||||
TeyrchainId::<T>::get()
|
||||
}
|
||||
}
|
||||
|
||||
pub type MessageId = [u8; 32];
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Some XCM was executed OK.
|
||||
Success(Option<T::Hash>),
|
||||
/// Some XCM failed.
|
||||
Fail(Option<T::Hash>, XcmError),
|
||||
/// Bad XCM version used.
|
||||
BadVersion(Option<T::Hash>),
|
||||
/// Bad XCM format used.
|
||||
BadFormat(Option<T::Hash>),
|
||||
|
||||
// DMP
|
||||
/// Downward message is invalid XCM.
|
||||
InvalidFormat(MessageId),
|
||||
/// Downward message is unsupported version of XCM.
|
||||
UnsupportedVersion(MessageId),
|
||||
/// Downward message executed with the given outcome.
|
||||
ExecutedDownward(MessageId, Outcome),
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn set_para_id(para_id: ParaId) {
|
||||
TeyrchainId::<T>::put(para_id);
|
||||
}
|
||||
|
||||
pub fn teyrchain_id() -> ParaId {
|
||||
TeyrchainId::<T>::get()
|
||||
}
|
||||
|
||||
pub fn received_dmp() -> Vec<Xcm<T::RuntimeCall>> {
|
||||
ReceivedDmp::<T>::get()
|
||||
}
|
||||
|
||||
fn handle_xcmp_message(
|
||||
sender: ParaId,
|
||||
_sent_at: RelayBlockNumber,
|
||||
xcm: VersionedXcm<T::RuntimeCall>,
|
||||
max_weight: Weight,
|
||||
) -> Result<Weight, XcmError> {
|
||||
let hash = Encode::using_encoded(&xcm, T::Hashing::hash);
|
||||
let mut message_hash = Encode::using_encoded(&xcm, pezsp_io::hashing::blake2_256);
|
||||
let (result, event) = match Xcm::<T::RuntimeCall>::try_from(xcm) {
|
||||
Ok(xcm) => {
|
||||
let location = (Parent, Teyrchain(sender.into()));
|
||||
match T::XcmExecutor::prepare_and_execute(
|
||||
location,
|
||||
xcm,
|
||||
&mut message_hash,
|
||||
max_weight,
|
||||
Weight::zero(),
|
||||
) {
|
||||
Outcome::Error(InstructionError { error, .. }) =>
|
||||
(Err(error), Event::Fail(Some(hash), error)),
|
||||
Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))),
|
||||
// As far as the caller is concerned, this was dispatched without error, so
|
||||
// we just report the weight used.
|
||||
Outcome::Incomplete {
|
||||
used, error: InstructionError { error, .. }, ..
|
||||
} => (Ok(used), Event::Fail(Some(hash), error)),
|
||||
}
|
||||
},
|
||||
Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))),
|
||||
};
|
||||
Self::deposit_event(event);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> XcmpMessageHandler for Pallet<T> {
|
||||
fn handle_xcmp_messages<'a, I: Iterator<Item = (ParaId, RelayBlockNumber, &'a [u8])>>(
|
||||
iter: I,
|
||||
max_weight: Weight,
|
||||
) -> Weight {
|
||||
for (sender, sent_at, data) in iter {
|
||||
let mut data_ref = data;
|
||||
let _ = XcmpMessageFormat::decode(&mut data_ref)
|
||||
.expect("Simulator encodes with versioned xcm format; qed");
|
||||
|
||||
let mut remaining_fragments = data_ref;
|
||||
while !remaining_fragments.is_empty() {
|
||||
if let Ok(xcm) =
|
||||
VersionedXcm::<T::RuntimeCall>::decode(&mut remaining_fragments)
|
||||
{
|
||||
let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight);
|
||||
} else {
|
||||
debug_assert!(false, "Invalid incoming XCMP message data");
|
||||
}
|
||||
}
|
||||
}
|
||||
max_weight
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> DmpMessageHandler for Pallet<T> {
|
||||
fn handle_dmp_messages(
|
||||
iter: impl Iterator<Item = (RelayBlockNumber, Vec<u8>)>,
|
||||
limit: Weight,
|
||||
) -> Weight {
|
||||
for (_i, (_sent_at, data)) in iter.enumerate() {
|
||||
let mut id = pezsp_io::hashing::blake2_256(&data[..]);
|
||||
let maybe_versioned = VersionedXcm::<T::RuntimeCall>::decode(&mut &data[..]);
|
||||
match maybe_versioned {
|
||||
Err(_) => {
|
||||
Self::deposit_event(Event::InvalidFormat(id));
|
||||
},
|
||||
Ok(versioned) => match Xcm::try_from(versioned) {
|
||||
Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)),
|
||||
Ok(x) => {
|
||||
let outcome = T::XcmExecutor::prepare_and_execute(
|
||||
Parent,
|
||||
x.clone(),
|
||||
&mut id,
|
||||
limit,
|
||||
Weight::zero(),
|
||||
);
|
||||
ReceivedDmp::<T>::append(x);
|
||||
Self::deposit_event(Event::ExecutedDownward(id, outcome));
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
limit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use pezframe_support::{parameter_types, weights::Weight};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_simulator::{
|
||||
AggregateMessageOrigin, ProcessMessage, ProcessMessageError, UmpQueueId, WeightMeter,
|
||||
};
|
||||
|
||||
use crate::relay_chain::{RuntimeCall, XcmConfig};
|
||||
|
||||
parameter_types! {
|
||||
/// Amount of weight that can be spent per block to service messages.
|
||||
pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000);
|
||||
pub const MessageQueueHeapSize: u32 = 65_536;
|
||||
pub const MessageQueueMaxStale: u32 = 16;
|
||||
}
|
||||
|
||||
/// Message processor to handle any messages that were enqueued into the `MessageQueue` pallet.
|
||||
pub struct MessageProcessor;
|
||||
impl ProcessMessage for MessageProcessor {
|
||||
type Origin = AggregateMessageOrigin;
|
||||
|
||||
fn process_message(
|
||||
message: &[u8],
|
||||
origin: Self::Origin,
|
||||
meter: &mut WeightMeter,
|
||||
id: &mut [u8; 32],
|
||||
) -> Result<bool, ProcessMessageError> {
|
||||
let para = match origin {
|
||||
AggregateMessageOrigin::Ump(UmpQueueId::Para(para)) => para,
|
||||
};
|
||||
xcm_builder::ProcessXcmMessage::<
|
||||
Junction,
|
||||
xcm_executor::XcmExecutor<XcmConfig>,
|
||||
RuntimeCall,
|
||||
>::process_message(message, Junction::Teyrchain(para.into()), meter, id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub type Balance = u128;
|
||||
|
||||
pub const UNITS: Balance = 10_000_000_000;
|
||||
pub const CENTS: Balance = UNITS / 100; // 100_000_000
|
||||
|
||||
pub type AccountId = pezsp_runtime::AccountId32;
|
||||
pub type AssetIdForAssets = u128;
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Relay chain runtime mock.
|
||||
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{Contains, Disabled, Everything, Nothing},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
use pezframe_system::EnsureRoot;
|
||||
use pezsp_core::{ConstU32, H256};
|
||||
use pezsp_runtime::traits::IdentityLookup;
|
||||
|
||||
use pezkuwi_runtime_teyrchains::{configuration, origin, shared};
|
||||
use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_builder::{
|
||||
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom,
|
||||
AllowTopLevelPaidExecutionFrom, ChildSystemTeyrchainAsSuperuser, ChildTeyrchainAsNative,
|
||||
ChildTeyrchainConvertsVia, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible,
|
||||
FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete,
|
||||
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin,
|
||||
};
|
||||
use xcm_executor::{Config, XcmExecutor};
|
||||
|
||||
use super::{
|
||||
mocks::relay_message_queue::*,
|
||||
primitives::{AccountId, Balance},
|
||||
};
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Block = Block;
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::pezsp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type DbWeight = ();
|
||||
type BaseCallFilter = Everything;
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub ExistentialDeposit: Balance = 1;
|
||||
pub const MaxLocks: u32 = 50;
|
||||
pub const MaxReserves: u32 = 50;
|
||||
}
|
||||
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type MaxLocks = MaxLocks;
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type MaxReserves = MaxReserves;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ConstU32<0>;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
impl shared::Config for Runtime {
|
||||
type DisabledValidators = ();
|
||||
}
|
||||
|
||||
impl configuration::Config for Runtime {
|
||||
type WeightInfo = configuration::TestWeightInfo;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub RelayNetwork: NetworkId = ByGenesis([0; 32]);
|
||||
pub const TokenLocation: Location = Here.into_location();
|
||||
pub UniversalLocation: InteriorLocation = RelayNetwork::get().into();
|
||||
pub UnitWeightCost: u64 = 1_000;
|
||||
}
|
||||
|
||||
pub type SovereignAccountOf = (
|
||||
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
|
||||
AccountId32Aliases<RelayNetwork, AccountId>,
|
||||
ChildTeyrchainConvertsVia<ParaId, AccountId>,
|
||||
);
|
||||
|
||||
pub type LocalBalancesTransactor =
|
||||
FungibleAdapter<Balances, IsConcrete<TokenLocation>, SovereignAccountOf, AccountId, ()>;
|
||||
|
||||
pub type AssetTransactors = LocalBalancesTransactor;
|
||||
|
||||
type LocalOriginConverter = (
|
||||
SovereignSignedViaLocation<SovereignAccountOf, RuntimeOrigin>,
|
||||
ChildTeyrchainAsNative<origin::Origin, RuntimeOrigin>,
|
||||
SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
|
||||
ChildSystemTeyrchainAsSuperuser<ParaId, RuntimeOrigin>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000);
|
||||
pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) =
|
||||
(AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024);
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
}
|
||||
|
||||
pub struct ChildrenTeyrchains;
|
||||
impl Contains<Location> for ChildrenTeyrchains {
|
||||
fn contains(location: &Location) -> bool {
|
||||
matches!(location.unpack(), (0, [Teyrchain(_)]))
|
||||
}
|
||||
}
|
||||
|
||||
pub type XcmRouter = crate::RelayChainXcmRouter;
|
||||
pub type Barrier = WithComputedOrigin<
|
||||
(
|
||||
AllowExplicitUnpaidExecutionFrom<ChildrenTeyrchains>,
|
||||
AllowTopLevelPaidExecutionFrom<Everything>,
|
||||
AllowSubscriptionsFrom<Everything>,
|
||||
),
|
||||
UniversalLocation,
|
||||
ConstU32<1>,
|
||||
>;
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = XcmRouter;
|
||||
type XcmEventEmitter = XcmPallet;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = LocalOriginConverter;
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
type Weigher = FixedWeightBounds<XcmInstructionWeight, RuntimeCall, MaxInstructions>;
|
||||
type Trader = FixedRateOfFungible<TokensPerSecondPerMegabyte, ()>;
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetLocker = XcmPallet;
|
||||
type AssetExchanger = ();
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
type PalletInstancesInfo = AllPalletsWithSystem;
|
||||
type FeeManager = ();
|
||||
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||
type MessageExporter = ();
|
||||
type UniversalAliases = Nothing;
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = Nothing;
|
||||
type TransactionalProcessor = FrameTransactionalProcessor;
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = XcmPallet;
|
||||
}
|
||||
|
||||
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>;
|
||||
|
||||
impl pezpallet_xcm::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmRouter = XcmRouter;
|
||||
type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmExecuteFilter = Everything;
|
||||
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||
type XcmTeleportFilter = Everything;
|
||||
type XcmReserveTransferFilter = Everything;
|
||||
type Weigher = FixedWeightBounds<XcmInstructionWeight, RuntimeCall, MaxInstructions>;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pezpallet_xcm::CurrentXcmVersion;
|
||||
type Currency = Balances;
|
||||
type CurrencyMatcher = IsConcrete<TokenLocation>;
|
||||
type TrustedLockers = ();
|
||||
type SovereignAccountOf = SovereignAccountOf;
|
||||
type MaxLockers = ConstU32<8>;
|
||||
type MaxRemoteLockConsumers = ConstU32<0>;
|
||||
type RemoteLockConsumerIdentifier = ();
|
||||
type WeightInfo = pezpallet_xcm::TestWeightInfo;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
// Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`.
|
||||
type AuthorizedAliasConsideration = Disabled;
|
||||
}
|
||||
|
||||
impl origin::Config for Runtime {}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
impl pezpallet_message_queue::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Size = u32;
|
||||
type HeapSize = MessageQueueHeapSize;
|
||||
type MaxStale = MessageQueueMaxStale;
|
||||
type ServiceWeight = MessageQueueServiceWeight;
|
||||
type IdleMaxServiceWeight = ();
|
||||
type MessageProcessor = MessageProcessor;
|
||||
type QueueChangeHandler = ();
|
||||
type WeightInfo = ();
|
||||
type QueuePausedQuery = ();
|
||||
}
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
ParasOrigin: origin,
|
||||
XcmPallet: pezpallet_xcm,
|
||||
MessageQueue: pezpallet_message_queue,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,201 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
primitives::{AccountId, CENTS},
|
||||
relay_chain,
|
||||
teyrchain::{self, Runtime},
|
||||
teyrchain_account_sovereign_account_id, MockNet, ParaA, Relay, TeyrchainBalances, ALICE, BOB,
|
||||
INITIAL_BALANCE,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::traits::{fungibles::Mutate, Currency};
|
||||
use pezpallet_contracts::{test_utils::builder::*, Code};
|
||||
use pezpallet_contracts_fixtures::compile_module;
|
||||
use pezpallet_contracts_uapi::ReturnErrorCode;
|
||||
use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm};
|
||||
use xcm_simulator::TestExt;
|
||||
|
||||
macro_rules! assert_return_code {
|
||||
( $x:expr , $y:expr $(,)? ) => {{
|
||||
assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32);
|
||||
}};
|
||||
}
|
||||
|
||||
fn bare_call(dest: pezsp_runtime::AccountId32) -> BareCallBuilder<teyrchain::Runtime> {
|
||||
BareCallBuilder::<teyrchain::Runtime>::bare_call(ALICE, dest)
|
||||
}
|
||||
|
||||
/// Instantiate the tests contract, and fund it with some balance and assets.
|
||||
fn instantiate_test_contract(name: &str) -> AccountId {
|
||||
let (wasm, _) = compile_module::<Runtime>(name).unwrap();
|
||||
|
||||
// Instantiate contract.
|
||||
let contract_addr = ParaA::execute_with(|| {
|
||||
BareInstantiateBuilder::<teyrchain::Runtime>::bare_instantiate(ALICE, Code::Upload(wasm))
|
||||
.build_and_unwrap_account_id()
|
||||
});
|
||||
|
||||
// Funds contract account with some balance and assets.
|
||||
ParaA::execute_with(|| {
|
||||
teyrchain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE);
|
||||
teyrchain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE).unwrap();
|
||||
});
|
||||
Relay::execute_with(|| {
|
||||
let sovereign_account = teyrchain_account_sovereign_account_id(1u32, contract_addr.clone());
|
||||
relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE);
|
||||
});
|
||||
|
||||
contract_addr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xcm_execute() {
|
||||
MockNet::reset();
|
||||
|
||||
let contract_addr = instantiate_test_contract("xcm_execute");
|
||||
|
||||
// Execute XCM instructions through the contract.
|
||||
ParaA::execute_with(|| {
|
||||
let amount: u128 = 10 * CENTS;
|
||||
let assets: Asset = (Here, amount).into();
|
||||
let beneficiary = AccountId32 { network: None, id: BOB.clone().into() };
|
||||
|
||||
// The XCM used to transfer funds to Bob.
|
||||
let message: Xcm<()> = Xcm::builder_unsafe()
|
||||
.withdraw_asset(assets.clone())
|
||||
.deposit_asset(assets, beneficiary)
|
||||
.build();
|
||||
|
||||
let result = bare_call(contract_addr.clone())
|
||||
.data(VersionedXcm::V4(message).encode())
|
||||
.build();
|
||||
|
||||
assert_eq!(result.gas_consumed, result.gas_required);
|
||||
assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success);
|
||||
|
||||
// Check if the funds are subtracted from the account of Alice and added to the account of
|
||||
// Bob.
|
||||
let initial = INITIAL_BALANCE;
|
||||
assert_eq!(TeyrchainBalances::free_balance(BOB), initial + amount);
|
||||
assert_eq!(TeyrchainBalances::free_balance(&contract_addr), initial - amount);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xcm_execute_incomplete() {
|
||||
MockNet::reset();
|
||||
|
||||
let contract_addr = instantiate_test_contract("xcm_execute");
|
||||
let amount = 10 * CENTS;
|
||||
|
||||
// Execute XCM instructions through the contract.
|
||||
ParaA::execute_with(|| {
|
||||
let assets: Asset = (Here, amount).into();
|
||||
let beneficiary = AccountId32 { network: None, id: BOB.clone().into() };
|
||||
|
||||
// The XCM used to transfer funds to Bob.
|
||||
let message: Xcm<()> = Xcm::builder_unsafe()
|
||||
.withdraw_asset(assets.clone())
|
||||
// This will fail as the contract does not have enough balance to complete both
|
||||
// withdrawals.
|
||||
.withdraw_asset((Here, INITIAL_BALANCE))
|
||||
.buy_execution(assets.clone(), Unlimited)
|
||||
.deposit_asset(assets, beneficiary)
|
||||
.build();
|
||||
|
||||
let result = bare_call(contract_addr.clone())
|
||||
.data(VersionedXcm::V4(message).encode())
|
||||
.build();
|
||||
|
||||
assert_eq!(result.gas_consumed, result.gas_required);
|
||||
assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed);
|
||||
|
||||
assert_eq!(TeyrchainBalances::free_balance(BOB), INITIAL_BALANCE);
|
||||
assert_eq!(TeyrchainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xcm_execute_reentrant_call() {
|
||||
MockNet::reset();
|
||||
|
||||
let contract_addr = instantiate_test_contract("xcm_execute");
|
||||
|
||||
ParaA::execute_with(|| {
|
||||
let transact_call = teyrchain::RuntimeCall::Contracts(pezpallet_contracts::Call::call {
|
||||
dest: contract_addr.clone(),
|
||||
gas_limit: 1_000_000.into(),
|
||||
storage_deposit_limit: None,
|
||||
data: vec![],
|
||||
value: 0u128,
|
||||
});
|
||||
|
||||
// The XCM used to transfer funds to Bob.
|
||||
let message: Xcm<teyrchain::RuntimeCall> = Xcm::builder_unsafe()
|
||||
.transact(OriginKind::Native, 1_000_000_000, transact_call.encode())
|
||||
.expect_transact_status(MaybeErrorCode::Success)
|
||||
.build();
|
||||
|
||||
let result = bare_call(contract_addr.clone())
|
||||
.data(VersionedXcm::V4(message).encode())
|
||||
.build_and_unwrap_result();
|
||||
|
||||
assert_return_code!(&result, ReturnErrorCode::XcmExecutionFailed);
|
||||
|
||||
// Funds should not change hands as the XCM transact failed.
|
||||
assert_eq!(TeyrchainBalances::free_balance(BOB), INITIAL_BALANCE);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xcm_send() {
|
||||
MockNet::reset();
|
||||
let contract_addr = instantiate_test_contract("xcm_send");
|
||||
let amount = 1_000 * CENTS;
|
||||
let fee = teyrchain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm`
|
||||
|
||||
// Send XCM instructions through the contract, to transfer some funds from the contract
|
||||
// derivative account to Alice on the relay chain.
|
||||
ParaA::execute_with(|| {
|
||||
let dest = VersionedLocation::V4(Parent.into());
|
||||
let assets: Asset = (Here, amount).into();
|
||||
let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() };
|
||||
|
||||
let message: Xcm<()> = Xcm::builder()
|
||||
.withdraw_asset(assets.clone())
|
||||
.buy_execution((Here, fee), Unlimited)
|
||||
.deposit_asset(assets, beneficiary)
|
||||
.build();
|
||||
|
||||
let result = bare_call(contract_addr.clone())
|
||||
.data((dest, VersionedXcm::V4(message)).encode())
|
||||
.build_and_unwrap_result();
|
||||
|
||||
let mut data = &result.data[..];
|
||||
XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id");
|
||||
});
|
||||
|
||||
Relay::execute_with(|| {
|
||||
let derived_contract_addr = &teyrchain_account_sovereign_account_id(1, contract_addr);
|
||||
assert_eq!(
|
||||
INITIAL_BALANCE - amount,
|
||||
relay_chain::Balances::free_balance(derived_contract_addr)
|
||||
);
|
||||
assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchain runtime mock.
|
||||
|
||||
mod contracts_config;
|
||||
use crate::{
|
||||
mocks::msg_queue::pallet as mock_msg_queue,
|
||||
primitives::{AccountId, AssetIdForAssets, Balance},
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{
|
||||
AsEnsureOriginWithArg, Contains, ContainsPair, Disabled, Everything, EverythingBut, Nothing,
|
||||
},
|
||||
weights::{
|
||||
constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
|
||||
Weight,
|
||||
},
|
||||
};
|
||||
use pezframe_system::{EnsureRoot, EnsureSigned};
|
||||
use pezpallet_xcm::XcmPassthrough;
|
||||
use pezsp_core::{ConstU32, ConstU64, H256};
|
||||
use pezsp_runtime::traits::{Get, IdentityLookup, MaybeEquivalence};
|
||||
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_builder::{
|
||||
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom,
|
||||
ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds,
|
||||
FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, NativeAsset,
|
||||
NoChecking, ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32,
|
||||
SovereignSignedViaLocation, WithComputedOrigin,
|
||||
};
|
||||
use xcm_executor::{traits::JustTry, Config, XcmExecutor};
|
||||
|
||||
pub type SovereignAccountOf =
|
||||
(AccountId32Aliases<RelayNetwork, AccountId>, ParentIsPreset<AccountId>);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
type Hash = H256;
|
||||
type Hashing = ::pezsp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type DbWeight = ();
|
||||
type BaseCallFilter = Everything;
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub ExistentialDeposit: Balance = 1;
|
||||
pub const MaxLocks: u32 = 50;
|
||||
pub const MaxReserves: u32 = 50;
|
||||
}
|
||||
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type AccountStore = System;
|
||||
type Balance = Balance;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ConstU32<0>;
|
||||
type MaxLocks = MaxLocks;
|
||||
type MaxReserves = MaxReserves;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type WeightInfo = ();
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AssetDeposit: u128 = 1_000_000;
|
||||
pub const MetadataDepositBase: u128 = 1_000_000;
|
||||
pub const MetadataDepositPerByte: u128 = 100_000;
|
||||
pub const AssetAccountDeposit: u128 = 1_000_000;
|
||||
pub const ApprovalDeposit: u128 = 1_000_000;
|
||||
pub const AssetsStringLimit: u32 = 50;
|
||||
pub const RemoveItemsLimit: u32 = 50;
|
||||
}
|
||||
|
||||
impl pezpallet_assets::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type AssetId = AssetIdForAssets;
|
||||
type ReserveData = ();
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<AccountId>>;
|
||||
type ForceOrigin = EnsureRoot<AccountId>;
|
||||
type AssetDeposit = AssetDeposit;
|
||||
type MetadataDepositBase = MetadataDepositBase;
|
||||
type MetadataDepositPerByte = MetadataDepositPerByte;
|
||||
type AssetAccountDeposit = AssetAccountDeposit;
|
||||
type ApprovalDeposit = ApprovalDeposit;
|
||||
type StringLimit = AssetsStringLimit;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
type Extra = ();
|
||||
type WeightInfo = ();
|
||||
type RemoveItemsLimit = RemoveItemsLimit;
|
||||
type AssetIdParameter = AssetIdForAssets;
|
||||
type CallbackHandle = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0);
|
||||
pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const KsmLocation: Location = Location::parent();
|
||||
pub const TokenLocation: Location = Here.into_location();
|
||||
pub const RelayNetwork: NetworkId = ByGenesis([0; 32]);
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Teyrchain(MsgQueue::teyrchain_id().into())].into();
|
||||
}
|
||||
|
||||
pub type XcmOriginToCallOrigin = (
|
||||
SovereignSignedViaLocation<SovereignAccountOf, RuntimeOrigin>,
|
||||
ParentAsSuperuser<RuntimeOrigin>,
|
||||
SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
|
||||
XcmPassthrough<RuntimeOrigin>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000);
|
||||
pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1_000_000_000_000, 1024 * 1024);
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
pub ForeignPrefix: Location = (Parent,).into();
|
||||
pub CheckingAccount: AccountId = PezkuwiXcm::check_account();
|
||||
pub TrustedLockPairs: (Location, AssetFilter) =
|
||||
(Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible }));
|
||||
}
|
||||
|
||||
pub fn estimate_message_fee(number_of_instructions: u64) -> u128 {
|
||||
let weight = estimate_weight(number_of_instructions);
|
||||
|
||||
estimate_fee_for_weight(weight)
|
||||
}
|
||||
|
||||
pub fn estimate_weight(number_of_instructions: u64) -> Weight {
|
||||
XcmInstructionWeight::get().saturating_mul(number_of_instructions)
|
||||
}
|
||||
|
||||
pub fn estimate_fee_for_weight(weight: Weight) -> u128 {
|
||||
let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get();
|
||||
|
||||
units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) +
|
||||
units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128)
|
||||
}
|
||||
|
||||
pub type LocalBalancesTransactor =
|
||||
FungibleAdapter<Balances, IsConcrete<TokenLocation>, SovereignAccountOf, AccountId, ()>;
|
||||
|
||||
pub struct FromLocationToAsset<Location, AssetId>(PhantomData<(Location, AssetId)>);
|
||||
impl MaybeEquivalence<Location, AssetIdForAssets>
|
||||
for FromLocationToAsset<Location, AssetIdForAssets>
|
||||
{
|
||||
fn convert(value: &Location) -> Option<AssetIdForAssets> {
|
||||
match value.unpack() {
|
||||
(1, []) => Some(0 as AssetIdForAssets),
|
||||
(1, [Teyrchain(para_id)]) => Some(*para_id as AssetIdForAssets),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_back(_id: &AssetIdForAssets) -> Option<Location> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub type ForeignAssetsTransactor = FungiblesAdapter<
|
||||
Assets,
|
||||
ConvertedConcreteId<
|
||||
AssetIdForAssets,
|
||||
Balance,
|
||||
FromLocationToAsset<Location, AssetIdForAssets>,
|
||||
JustTry,
|
||||
>,
|
||||
SovereignAccountOf,
|
||||
AccountId,
|
||||
NoChecking,
|
||||
CheckingAccount,
|
||||
>;
|
||||
|
||||
/// Means for transacting assets on this chain
|
||||
pub type AssetTransactors = (LocalBalancesTransactor, ForeignAssetsTransactor);
|
||||
|
||||
pub struct ParentRelay;
|
||||
impl Contains<Location> for ParentRelay {
|
||||
fn contains(location: &Location) -> bool {
|
||||
location.contains_parents_only(1)
|
||||
}
|
||||
}
|
||||
pub struct ThisTeyrchain;
|
||||
impl Contains<Location> for ThisTeyrchain {
|
||||
fn contains(location: &Location) -> bool {
|
||||
matches!(location.unpack(), (0, [Junction::AccountId32 { .. }]))
|
||||
}
|
||||
}
|
||||
|
||||
pub type XcmRouter = crate::TeyrchainXcmRouter<MsgQueue>;
|
||||
|
||||
pub type Barrier = (
|
||||
xcm_builder::AllowUnpaidExecutionFrom<ThisTeyrchain>,
|
||||
WithComputedOrigin<
|
||||
(AllowExplicitUnpaidExecutionFrom<ParentRelay>, AllowTopLevelPaidExecutionFrom<Everything>),
|
||||
UniversalLocation,
|
||||
ConstU32<1>,
|
||||
>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub NftCollectionOne: AssetFilter
|
||||
= Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) });
|
||||
pub NftCollectionOneForRelay: (AssetFilter, Location)
|
||||
= (NftCollectionOne::get(), Parent.into());
|
||||
pub RelayNativeAsset: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId((Parent, Here).into()) });
|
||||
pub RelayNativeAssetForRelay: (AssetFilter, Location) = (RelayNativeAsset::get(), Parent.into());
|
||||
}
|
||||
pub type TrustedTeleporters =
|
||||
(xcm_builder::Case<NftCollectionOneForRelay>, xcm_builder::Case<RelayNativeAssetForRelay>);
|
||||
pub type TrustedReserves = EverythingBut<xcm_builder::Case<NftCollectionOneForRelay>>;
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = XcmRouter;
|
||||
type XcmEventEmitter = PezkuwiXcm;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = XcmOriginToCallOrigin;
|
||||
type IsReserve = (NativeAsset, TrustedReserves);
|
||||
type IsTeleporter = TrustedTeleporters;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
type Weigher = FixedWeightBounds<XcmInstructionWeight, RuntimeCall, MaxInstructions>;
|
||||
type Trader = FixedRateOfFungible<TokensPerSecondPerMegabyte, ()>;
|
||||
type ResponseHandler = PezkuwiXcm;
|
||||
type AssetTrap = PezkuwiXcm;
|
||||
type AssetLocker = PezkuwiXcm;
|
||||
type AssetExchanger = ();
|
||||
type AssetClaims = PezkuwiXcm;
|
||||
type SubscriptionService = PezkuwiXcm;
|
||||
type PalletInstancesInfo = AllPalletsWithSystem;
|
||||
type FeeManager = ();
|
||||
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||
type MessageExporter = ();
|
||||
type UniversalAliases = Nothing;
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = Nothing;
|
||||
type TransactionalProcessor = FrameTransactionalProcessor;
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = PezkuwiXcm;
|
||||
}
|
||||
|
||||
impl mock_msg_queue::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||
}
|
||||
|
||||
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>;
|
||||
|
||||
pub struct TrustedLockerCase<T>(PhantomData<T>);
|
||||
impl<T: Get<(Location, AssetFilter)>> ContainsPair<Location, Asset> for TrustedLockerCase<T> {
|
||||
fn contains(origin: &Location, asset: &Asset) -> bool {
|
||||
let (o, a) = T::get();
|
||||
a.matches(asset) && &o == origin
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_xcm::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmRouter = XcmRouter;
|
||||
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmExecuteFilter = Everything;
|
||||
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||
type XcmTeleportFilter = Nothing;
|
||||
type XcmReserveTransferFilter = Everything;
|
||||
type Weigher = FixedWeightBounds<XcmInstructionWeight, RuntimeCall, MaxInstructions>;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pezpallet_xcm::CurrentXcmVersion;
|
||||
type Currency = Balances;
|
||||
type CurrencyMatcher = IsConcrete<TokenLocation>;
|
||||
type TrustedLockers = TrustedLockerCase<TrustedLockPairs>;
|
||||
type SovereignAccountOf = SovereignAccountOf;
|
||||
type MaxLockers = ConstU32<8>;
|
||||
type MaxRemoteLockConsumers = ConstU32<0>;
|
||||
type RemoteLockConsumerIdentifier = ();
|
||||
type WeightInfo = pezpallet_xcm::TestWeightInfo;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
// Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`.
|
||||
type AuthorizedAliasConsideration = Disabled;
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
impl pezpallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<1>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Runtime
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
MsgQueue: mock_msg_queue,
|
||||
PezkuwiXcm: pezpallet_xcm,
|
||||
Contracts: pezpallet_contracts,
|
||||
Assets: pezpallet_assets,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Bizinikiwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Bizinikiwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Bizinikiwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{Balances, Runtime, RuntimeCall, RuntimeEvent};
|
||||
use crate::teyrchain::RuntimeHoldReason;
|
||||
use pezframe_support::{derive_impl, parameter_types};
|
||||
|
||||
parameter_types! {
|
||||
pub Schedule: pezpallet_contracts::Schedule<Runtime> = Default::default();
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_contracts::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_contracts::Config for Runtime {
|
||||
type AddressGenerator = pezpallet_contracts::DefaultAddressGenerator;
|
||||
type CallStack = [pezpallet_contracts::Frame<Self>; 5];
|
||||
type Currency = Balances;
|
||||
type Schedule = Schedule;
|
||||
type Time = super::Timestamp;
|
||||
type Xcm = pezpallet_xcm::Pallet<Self>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts-proc-macro"
|
||||
version = "18.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Procedural macros used in pallet_contracts"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { features = ["full"], workspace = true }
|
||||
@@ -0,0 +1,922 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Procedural macros used in the contracts module.
|
||||
//!
|
||||
//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides
|
||||
//! boilerplate of defining external environment for a wasm module.
|
||||
|
||||
use core::cmp::Reverse;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DeriveInput,
|
||||
Fields, FnArg, Ident,
|
||||
};
|
||||
|
||||
/// This derives `Debug` for a struct where each field must be of some numeric type.
|
||||
/// It interprets each field as its represents some weight and formats it as times so that
|
||||
/// it is readable by humans.
|
||||
#[proc_macro_derive(WeightDebug)]
|
||||
pub fn derive_weight_debug(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let data = if let Data::Struct(data) = &input.data {
|
||||
data
|
||||
} else {
|
||||
return quote_spanned! {
|
||||
name.span() =>
|
||||
compile_error!("WeightDebug is only supported for structs.");
|
||||
}
|
||||
.into();
|
||||
};
|
||||
|
||||
let fields = match &data.fields {
|
||||
Fields::Named(fields) => {
|
||||
let recurse = fields.named.iter().filter_map(|f| {
|
||||
let name = f.ident.as_ref()?;
|
||||
if name.to_string().starts_with('_') {
|
||||
return None;
|
||||
}
|
||||
let ret = quote_spanned! { f.span() =>
|
||||
formatter.field(stringify!(#name), &HumanWeight(self.#name));
|
||||
};
|
||||
Some(ret)
|
||||
});
|
||||
quote! {
|
||||
#( #recurse )*
|
||||
}
|
||||
},
|
||||
Fields::Unnamed(fields) => quote_spanned! {
|
||||
fields.span() =>
|
||||
compile_error!("Unnamed fields are not supported")
|
||||
},
|
||||
Fields::Unit => quote!(),
|
||||
};
|
||||
|
||||
let tokens = quote! {
|
||||
impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause {
|
||||
fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
use ::pezsp_runtime::{FixedPointNumber, FixedU128 as Fixed};
|
||||
use ::core::{fmt, write};
|
||||
|
||||
struct HumanWeight(Weight);
|
||||
|
||||
impl fmt::Debug for HumanWeight {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.0.ref_time() > 1_000_000_000 {
|
||||
write!(
|
||||
formatter,
|
||||
"{} ms, {} bytes",
|
||||
Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000_000).into_inner() / Fixed::accuracy(),
|
||||
self.0.proof_size()
|
||||
)
|
||||
} else if self.0.ref_time() > 1_000_000 {
|
||||
write!(
|
||||
formatter,
|
||||
"{} µs, {} bytes",
|
||||
Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000).into_inner() / Fixed::accuracy(),
|
||||
self.0.proof_size()
|
||||
)
|
||||
} else if self.0.ref_time() > 1_000 {
|
||||
write!(
|
||||
formatter,
|
||||
"{} ns, {} bytes",
|
||||
Fixed::saturating_from_rational(self.0.ref_time(), 1_000).into_inner() / Fixed::accuracy(),
|
||||
self.0.proof_size()
|
||||
)
|
||||
} else {
|
||||
write!(formatter, "{} ps, {} bytes", self.0.ref_time(), self.0.proof_size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut formatter = formatter.debug_struct(stringify!(#name));
|
||||
#fields
|
||||
formatter.finish()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
/// Parsed environment definition.
|
||||
struct EnvDef {
|
||||
host_funcs: Vec<HostFn>,
|
||||
}
|
||||
|
||||
/// Parsed host function definition.
|
||||
struct HostFn {
|
||||
item: syn::ItemFn,
|
||||
version: u8,
|
||||
name: String,
|
||||
returns: HostFnReturn,
|
||||
is_stable: bool,
|
||||
alias_to: Option<String>,
|
||||
/// Formulating the predicate inverted makes the expression using it simpler.
|
||||
not_deprecated: bool,
|
||||
cfg: Option<syn::Attribute>,
|
||||
}
|
||||
|
||||
enum HostFnReturn {
|
||||
Unit,
|
||||
U32,
|
||||
U64,
|
||||
ReturnCode,
|
||||
}
|
||||
|
||||
impl HostFnReturn {
|
||||
fn to_wasm_sig(&self) -> TokenStream2 {
|
||||
let ok = match self {
|
||||
Self::Unit => quote! { () },
|
||||
Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 },
|
||||
Self::U64 => quote! { ::core::primitive::u64 },
|
||||
};
|
||||
quote! {
|
||||
::core::result::Result<#ok, ::wasmi::Error>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for HostFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
self.item.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl HostFn {
|
||||
pub fn try_from(mut item: syn::ItemFn) -> syn::Result<Self> {
|
||||
let err = |span, msg| {
|
||||
let msg = format!("Invalid host function definition.\n{}", msg);
|
||||
syn::Error::new(span, msg)
|
||||
};
|
||||
|
||||
// process attributes
|
||||
let msg =
|
||||
"Only #[version(<u8>)], #[unstable], #[prefixed_alias], #[cfg], #[mutating] and #[deprecated] attributes are allowed.";
|
||||
let span = item.span();
|
||||
let mut attrs = item.attrs.clone();
|
||||
attrs.retain(|a| !a.path().is_ident("doc"));
|
||||
let mut maybe_version = None;
|
||||
let mut is_stable = true;
|
||||
let mut alias_to = None;
|
||||
let mut not_deprecated = true;
|
||||
let mut mutating = false;
|
||||
let mut cfg = None;
|
||||
while let Some(attr) = attrs.pop() {
|
||||
let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string();
|
||||
match ident.as_str() {
|
||||
"version" => {
|
||||
if maybe_version.is_some() {
|
||||
return Err(err(span, "#[version] can only be specified once"));
|
||||
}
|
||||
maybe_version =
|
||||
Some(attr.parse_args::<syn::LitInt>().and_then(|lit| lit.base10_parse())?);
|
||||
},
|
||||
"unstable" => {
|
||||
if !is_stable {
|
||||
return Err(err(span, "#[unstable] can only be specified once"));
|
||||
}
|
||||
is_stable = false;
|
||||
},
|
||||
"prefixed_alias" => {
|
||||
alias_to = Some(item.sig.ident.to_string());
|
||||
item.sig.ident = syn::Ident::new(
|
||||
&format!("seal_{}", &item.sig.ident.to_string()),
|
||||
item.sig.ident.span(),
|
||||
);
|
||||
},
|
||||
"deprecated" => {
|
||||
if !not_deprecated {
|
||||
return Err(err(span, "#[deprecated] can only be specified once"));
|
||||
}
|
||||
not_deprecated = false;
|
||||
},
|
||||
"mutating" => {
|
||||
if mutating {
|
||||
return Err(err(span, "#[mutating] can only be specified once"));
|
||||
}
|
||||
mutating = true;
|
||||
},
|
||||
"cfg" => {
|
||||
if cfg.is_some() {
|
||||
return Err(err(span, "#[cfg] can only be specified once"));
|
||||
}
|
||||
cfg = Some(attr);
|
||||
},
|
||||
id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))),
|
||||
}
|
||||
}
|
||||
|
||||
if mutating {
|
||||
let stmt = syn::parse_quote! {
|
||||
if ctx.ext().is_read_only() {
|
||||
return Err(Error::<E::T>::StateChangeDenied.into());
|
||||
}
|
||||
};
|
||||
item.block.stmts.insert(0, stmt);
|
||||
}
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if !(is_stable || not_deprecated) {
|
||||
return Err(err(span, "#[deprecated] is mutually exclusive with #[unstable]"));
|
||||
}
|
||||
|
||||
// process arguments: The first and second args are treated differently (ctx, memory)
|
||||
// they must exist and be `ctx: _` and `memory: _`.
|
||||
let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _";
|
||||
let special_args = item
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.take(2)
|
||||
.enumerate()
|
||||
.map(|(i, arg)| is_valid_special_arg(i, arg))
|
||||
.fold(0u32, |acc, valid| if valid { acc + 1 } else { acc });
|
||||
|
||||
if special_args != 2 {
|
||||
return Err(err(span, msg));
|
||||
}
|
||||
|
||||
// process return type
|
||||
let msg = r#"Should return one of the following:
|
||||
- Result<(), TrapReason>,
|
||||
- Result<ReturnErrorCode, TrapReason>,
|
||||
- Result<u64, TrapReason>,
|
||||
- Result<u32, TrapReason>"#;
|
||||
let ret_ty = match item.clone().sig.output {
|
||||
syn::ReturnType::Type(_, ty) => Ok(ty.clone()),
|
||||
_ => Err(err(span, &msg)),
|
||||
}?;
|
||||
match *ret_ty {
|
||||
syn::Type::Path(tp) => {
|
||||
let result = &tp.path.segments.last().ok_or(err(span, &msg))?;
|
||||
let (id, span) = (result.ident.to_string(), result.ident.span());
|
||||
id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?;
|
||||
|
||||
match &result.arguments {
|
||||
syn::PathArguments::AngleBracketed(group) => {
|
||||
if group.args.len() != 2 {
|
||||
return Err(err(span, &msg));
|
||||
};
|
||||
|
||||
let arg2 = group.args.last().ok_or(err(span, &msg))?;
|
||||
|
||||
let err_ty = match arg2 {
|
||||
syn::GenericArgument::Type(ty) => Ok(ty.clone()),
|
||||
_ => Err(err(arg2.span(), &msg)),
|
||||
}?;
|
||||
|
||||
match err_ty {
|
||||
syn::Type::Path(tp) => Ok(tp
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.ok_or(err(arg2.span(), &msg))?
|
||||
.ident
|
||||
.to_string()),
|
||||
_ => Err(err(tp.span(), &msg)),
|
||||
}?
|
||||
.eq("TrapReason")
|
||||
.then_some(())
|
||||
.ok_or(err(span, &msg))?;
|
||||
|
||||
let arg1 = group.args.first().ok_or(err(span, &msg))?;
|
||||
let ok_ty = match arg1 {
|
||||
syn::GenericArgument::Type(ty) => Ok(ty.clone()),
|
||||
_ => Err(err(arg1.span(), &msg)),
|
||||
}?;
|
||||
let ok_ty_str = match ok_ty {
|
||||
syn::Type::Path(tp) => Ok(tp
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.ok_or(err(arg1.span(), &msg))?
|
||||
.ident
|
||||
.to_string()),
|
||||
syn::Type::Tuple(tt) => {
|
||||
if !tt.elems.is_empty() {
|
||||
return Err(err(arg1.span(), &msg));
|
||||
};
|
||||
Ok("()".to_string())
|
||||
},
|
||||
_ => Err(err(ok_ty.span(), &msg)),
|
||||
}?;
|
||||
let returns = match ok_ty_str.as_str() {
|
||||
"()" => Ok(HostFnReturn::Unit),
|
||||
"u32" => Ok(HostFnReturn::U32),
|
||||
"u64" => Ok(HostFnReturn::U64),
|
||||
"ReturnErrorCode" => Ok(HostFnReturn::ReturnCode),
|
||||
_ => Err(err(arg1.span(), &msg)),
|
||||
}?;
|
||||
|
||||
Ok(Self {
|
||||
item,
|
||||
version: maybe_version.unwrap_or_default(),
|
||||
name,
|
||||
returns,
|
||||
is_stable,
|
||||
alias_to,
|
||||
not_deprecated,
|
||||
cfg,
|
||||
})
|
||||
},
|
||||
_ => Err(err(span, &msg)),
|
||||
}
|
||||
},
|
||||
_ => Err(err(span, &msg)),
|
||||
}
|
||||
}
|
||||
|
||||
fn module(&self) -> String {
|
||||
format!("seal{}", self.version)
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvDef {
|
||||
pub fn try_from(item: syn::ItemMod) -> syn::Result<Self> {
|
||||
let span = item.span();
|
||||
let err = |msg| syn::Error::new(span, msg);
|
||||
let items = &item
|
||||
.content
|
||||
.as_ref()
|
||||
.ok_or(err("Invalid environment definition, expected `mod` to be inlined."))?
|
||||
.1;
|
||||
|
||||
let extract_fn = |i: &syn::Item| match i {
|
||||
syn::Item::Fn(i_fn) => Some(i_fn.clone()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let selector = |a: &syn::Attribute| a.path().is_ident("prefixed_alias");
|
||||
|
||||
let aliases = items
|
||||
.iter()
|
||||
.filter_map(extract_fn)
|
||||
.filter(|i| i.attrs.iter().any(selector))
|
||||
.map(|i| HostFn::try_from(i));
|
||||
|
||||
let host_funcs = items
|
||||
.iter()
|
||||
.filter_map(extract_fn)
|
||||
.map(|mut i| {
|
||||
i.attrs.retain(|i| !selector(i));
|
||||
i
|
||||
})
|
||||
.map(|i| HostFn::try_from(i))
|
||||
.chain(aliases)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self { host_funcs })
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool {
|
||||
let FnArg::Typed(pat) = arg else { return false };
|
||||
let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false };
|
||||
let name_ok = match idx {
|
||||
0 => ident == "ctx" || ident == "_ctx",
|
||||
1 => ident == "memory" || ident == "_memory",
|
||||
_ => false,
|
||||
};
|
||||
if !name_ok {
|
||||
return false;
|
||||
}
|
||||
matches!(*pat.ty, syn::Type::Infer(_))
|
||||
}
|
||||
|
||||
fn expand_func_doc(func: &HostFn) -> TokenStream2 {
|
||||
// Remove auxiliary args: `ctx: _` and `memory: _`
|
||||
let func_decl = {
|
||||
let mut sig = func.item.sig.clone();
|
||||
sig.inputs = sig
|
||||
.inputs
|
||||
.iter()
|
||||
.skip(2)
|
||||
.map(|p| p.clone())
|
||||
.collect::<Punctuated<FnArg, Comma>>();
|
||||
sig.to_token_stream()
|
||||
};
|
||||
let func_doc = {
|
||||
let func_docs = if let Some(origin_fn) = &func.alias_to {
|
||||
let alias_doc = format!(
|
||||
"This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.",
|
||||
origin_fn,
|
||||
);
|
||||
quote! { #[doc = #alias_doc] }
|
||||
} else {
|
||||
let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| {
|
||||
let docs = d.to_token_stream();
|
||||
quote! { #docs }
|
||||
});
|
||||
quote! { #( #docs )* }
|
||||
};
|
||||
let deprecation_notice = if !func.not_deprecated {
|
||||
let warning = "\n # Deprecated\n\n \
|
||||
This function is deprecated and will be removed in future versions.\n \
|
||||
No new code or contracts with this API can be deployed.";
|
||||
quote! { #[doc = #warning] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let import_notice = {
|
||||
let info = format!(
|
||||
"\n# Wasm Import Statement\n```wat\n(import \"seal{}\" \"{}\" (func ...))\n```",
|
||||
func.version, func.name,
|
||||
);
|
||||
quote! { #[doc = #info] }
|
||||
};
|
||||
let unstable_notice = if !func.is_stable {
|
||||
let warning = "\n # Unstable\n\n \
|
||||
This function is unstable and it is a subject to change (or removal) in the future.\n \
|
||||
Do not deploy a contract using it to a production chain.";
|
||||
quote! { #[doc = #warning] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
quote! {
|
||||
#deprecation_notice
|
||||
#func_docs
|
||||
#import_notice
|
||||
#unstable_notice
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#func_doc
|
||||
#func_decl;
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands documentation for host functions.
|
||||
fn expand_docs(def: &EnvDef) -> TokenStream2 {
|
||||
// Create the `Current` trait with only the newest versions
|
||||
// we sort so that only the newest versions make it into `docs`
|
||||
let mut current_docs = std::collections::HashMap::new();
|
||||
let mut funcs: Vec<_> = def.host_funcs.iter().filter(|f| f.alias_to.is_none()).collect();
|
||||
funcs.sort_unstable_by_key(|func| Reverse(func.version));
|
||||
for func in funcs {
|
||||
if current_docs.contains_key(&func.name) {
|
||||
continue;
|
||||
}
|
||||
current_docs.insert(func.name.clone(), expand_func_doc(&func));
|
||||
}
|
||||
let current_docs = current_docs.values();
|
||||
|
||||
// Create the `legacy` module with all functions
|
||||
// Maps from version to list of functions that have this version
|
||||
let mut legacy_doc = std::collections::BTreeMap::<u8, Vec<TokenStream2>>::new();
|
||||
for func in def.host_funcs.iter() {
|
||||
legacy_doc.entry(func.version).or_default().push(expand_func_doc(&func));
|
||||
}
|
||||
let legacy_doc = legacy_doc.into_iter().map(|(version, funcs)| {
|
||||
let doc = format!("All functions available in the **seal{}** module", version);
|
||||
let version = Ident::new(&format!("Version{version}"), Span::call_site());
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
pub trait #version {
|
||||
#( #funcs )*
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
/// Contains only the latest version of each function.
|
||||
///
|
||||
/// In reality there are more functions available but they are all obsolete: When a function
|
||||
/// is updated a new **version** is added and the old versions stays available as-is.
|
||||
/// We only list the newest version here. Some functions are available under additional
|
||||
/// names (aliases) for historic reasons which are omitted here.
|
||||
///
|
||||
/// If you want an overview of all the functions available to a contact all you need
|
||||
/// to look at is this trait. It contains only the latest version of each
|
||||
/// function and no aliases. If you are writing a contract(language) from scratch
|
||||
/// this is where you should look at.
|
||||
pub trait Current {
|
||||
#( #current_docs )*
|
||||
}
|
||||
#( #legacy_doc )*
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands environment definition.
|
||||
/// Should generate source code for:
|
||||
/// - implementations of the host functions to be added to the wasm runtime environment (see
|
||||
/// `expand_impls()`).
|
||||
fn expand_env(def: &EnvDef, docs: bool) -> TokenStream2 {
|
||||
let impls = expand_impls(def);
|
||||
let docs = docs.then(|| expand_docs(def)).unwrap_or(TokenStream2::new());
|
||||
let stable_api_count = def.host_funcs.iter().filter(|f| f.is_stable).count();
|
||||
|
||||
quote! {
|
||||
pub struct Env;
|
||||
|
||||
#[cfg(test)]
|
||||
pub const STABLE_API_COUNT: usize = #stable_api_count;
|
||||
|
||||
#impls
|
||||
/// Documentation of the API (host functions) available to contracts.
|
||||
///
|
||||
/// The `Current` trait might be the most useful doc to look at. The versioned
|
||||
/// traits only exist for reference: If trying to find out if a specific version of
|
||||
/// `pezpallet-contracts` contains a certain function.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This module is not meant to be used by any code. Rather, it is meant to be
|
||||
/// consumed by humans through rustdoc.
|
||||
#[cfg(doc)]
|
||||
pub mod api_doc {
|
||||
use super::{TrapReason, ReturnErrorCode};
|
||||
#docs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates for every host function:
|
||||
/// - real implementation, to register it in the contract execution environment;
|
||||
/// - dummy implementation, to be used as mocks for contract validation step.
|
||||
fn expand_impls(def: &EnvDef) -> TokenStream2 {
|
||||
let impls = expand_functions(def, ExpandMode::Impl);
|
||||
let dummy_impls = expand_functions(def, ExpandMode::MockImpl);
|
||||
let bench_impls = expand_functions(def, ExpandMode::BenchImpl);
|
||||
|
||||
quote! {
|
||||
impl<'a, E: Ext> crate::wasm::Environment<crate::wasm::runtime::Runtime<'a, E>> for Env
|
||||
{
|
||||
fn define(
|
||||
store: &mut ::wasmi::Store<crate::wasm::Runtime<E>>,
|
||||
linker: &mut ::wasmi::Linker<crate::wasm::Runtime<E>>,
|
||||
allow_unstable: AllowUnstableInterface,
|
||||
allow_deprecated: AllowDeprecatedInterface,
|
||||
) -> Result<(),::wasmi::errors::LinkerError> {
|
||||
#impls
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct BenchEnv<E>(::core::marker::PhantomData<E>);
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<E: Ext> BenchEnv<E> {
|
||||
#bench_impls
|
||||
}
|
||||
|
||||
impl crate::wasm::Environment<()> for Env
|
||||
{
|
||||
fn define(
|
||||
store: &mut ::wasmi::Store<()>,
|
||||
linker: &mut ::wasmi::Linker<()>,
|
||||
allow_unstable: AllowUnstableInterface,
|
||||
allow_deprecated: AllowDeprecatedInterface,
|
||||
) -> Result<(), ::wasmi::errors::LinkerError> {
|
||||
#dummy_impls
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ExpandMode {
|
||||
Impl,
|
||||
BenchImpl,
|
||||
MockImpl,
|
||||
}
|
||||
|
||||
impl ExpandMode {
|
||||
fn expand_blocks(&self) -> bool {
|
||||
match *self {
|
||||
ExpandMode::Impl | ExpandMode::BenchImpl => true,
|
||||
ExpandMode::MockImpl => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn host_state(&self) -> TokenStream2 {
|
||||
match *self {
|
||||
ExpandMode::Impl | ExpandMode::BenchImpl => quote! { crate::wasm::runtime::Runtime<E> },
|
||||
ExpandMode::MockImpl => quote! { () },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 {
|
||||
let impls = def.host_funcs.iter().map(|f| {
|
||||
// skip the context and memory argument
|
||||
let params = f.item.sig.inputs.iter().skip(2);
|
||||
let module = f.module();
|
||||
let cfg = &f.cfg;
|
||||
let name = &f.name;
|
||||
let body = &f.item.block;
|
||||
let wasm_output = f.returns.to_wasm_sig();
|
||||
let output = &f.item.sig.output;
|
||||
let is_stable = f.is_stable;
|
||||
let not_deprecated = f.not_deprecated;
|
||||
|
||||
// wrapped host function body call with host function traces
|
||||
// see https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/bizinikiwi/pezframe/contracts#host-function-tracing
|
||||
let wrapped_body_with_trace = {
|
||||
let trace_fmt_args = params.clone().filter_map(|arg| match arg {
|
||||
syn::FnArg::Receiver(_) => None,
|
||||
syn::FnArg::Typed(p) => {
|
||||
match *p.pat.clone() {
|
||||
syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::<Vec<_>>().join(", ");
|
||||
let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);
|
||||
|
||||
quote! {
|
||||
let result = #body;
|
||||
if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
|
||||
use core::fmt::Write;
|
||||
let mut msg = alloc::string::String::default();
|
||||
let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result);
|
||||
ctx.ext().append_debug_buffer(&msg);
|
||||
}
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// If we don't expand blocks (implementing for `()`) we change a few things:
|
||||
// - We replace any code by unreachable!
|
||||
// - Allow unused variables as the code that uses is not expanded
|
||||
// - We don't need to map the error as we simply panic if they code would ever be executed
|
||||
let expand_blocks = expand_mode.expand_blocks();
|
||||
let inner = match expand_mode {
|
||||
ExpandMode::Impl => {
|
||||
quote! { || #output {
|
||||
let (memory, ctx) = __caller__
|
||||
.data()
|
||||
.memory()
|
||||
.expect("Memory must be set when setting up host data; qed")
|
||||
.data_and_store_mut(&mut __caller__);
|
||||
#wrapped_body_with_trace
|
||||
} }
|
||||
},
|
||||
ExpandMode::BenchImpl => {
|
||||
let body = &body.stmts;
|
||||
quote!{
|
||||
#(#body)*
|
||||
}
|
||||
},
|
||||
ExpandMode::MockImpl => {
|
||||
quote! { || -> #wasm_output {
|
||||
// This is part of the implementation for `Environment<()>` which is not
|
||||
// meant to be actually executed. It is only for validation which will
|
||||
// never call host functions.
|
||||
::core::unreachable!()
|
||||
} }
|
||||
},
|
||||
};
|
||||
|
||||
let into_host = if expand_blocks {
|
||||
quote! {
|
||||
|reason| {
|
||||
::wasmi::Error::host(reason)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
|reason| { reason }
|
||||
}
|
||||
};
|
||||
let allow_unused = if expand_blocks {
|
||||
quote! { }
|
||||
} else {
|
||||
quote! { #[allow(unused_variables)] }
|
||||
};
|
||||
let sync_gas_before = if expand_blocks {
|
||||
quote! {
|
||||
// Write gas from wasmi into pezpallet-contracts before entering the host function.
|
||||
let __gas_left_before__ = {
|
||||
let fuel =
|
||||
__caller__.get_fuel().expect("Fuel metering is enabled; qed");
|
||||
__caller__
|
||||
.data_mut()
|
||||
.ext()
|
||||
.gas_meter_mut()
|
||||
.sync_from_executor(fuel)
|
||||
.map_err(TrapReason::from)
|
||||
.map_err(#into_host)?
|
||||
};
|
||||
|
||||
// Charge gas for host function execution.
|
||||
__caller__.data_mut().charge_gas(crate::wasm::RuntimeCosts::HostFn)
|
||||
.map_err(TrapReason::from)
|
||||
.map_err(#into_host)?;
|
||||
}
|
||||
} else {
|
||||
quote! { }
|
||||
};
|
||||
// Write gas from pezpallet-contracts into wasmi after leaving the host function.
|
||||
let sync_gas_after = if expand_blocks {
|
||||
quote! {
|
||||
let fuel = __caller__
|
||||
.data_mut()
|
||||
.ext()
|
||||
.gas_meter_mut()
|
||||
.sync_to_executor(__gas_left_before__)
|
||||
.map_err(|err| {
|
||||
let err = TrapReason::from(err);
|
||||
wasmi::Error::host(err)
|
||||
})?;
|
||||
__caller__
|
||||
.set_fuel(fuel.into())
|
||||
.expect("Fuel metering is enabled; qed");
|
||||
}
|
||||
} else {
|
||||
quote! { }
|
||||
};
|
||||
|
||||
match expand_mode {
|
||||
ExpandMode::BenchImpl => {
|
||||
let name = Ident::new(&format!("{module}_{name}"), Span::call_site());
|
||||
quote! {
|
||||
pub fn #name(ctx: &mut crate::wasm::Runtime<E>, memory: &mut [u8], #(#params),*) #output {
|
||||
#inner
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let host_state = expand_mode.host_state();
|
||||
quote! {
|
||||
// We need to allow all interfaces when runtime benchmarks are performed because
|
||||
// we generate the weights even when those interfaces are not enabled. This
|
||||
// is necessary as the decision whether we allow unstable or deprecated functions
|
||||
// is a decision made at runtime. Generation of the weights happens statically.
|
||||
#cfg
|
||||
if ::core::cfg!(feature = "runtime-benchmarks") ||
|
||||
((#is_stable || __allow_unstable__) && (#not_deprecated || __allow_deprecated__))
|
||||
{
|
||||
#allow_unused
|
||||
linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output {
|
||||
#sync_gas_before
|
||||
let mut func = #inner;
|
||||
let result = func().map_err(#into_host).map(::core::convert::Into::into);
|
||||
#sync_gas_after
|
||||
result
|
||||
}))?;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
match expand_mode {
|
||||
ExpandMode::BenchImpl => {
|
||||
quote! {
|
||||
#( #impls )*
|
||||
}
|
||||
},
|
||||
_ => quote! {
|
||||
let __allow_unstable__ = matches!(allow_unstable, AllowUnstableInterface::Yes);
|
||||
let __allow_deprecated__ = matches!(allow_deprecated, AllowDeprecatedInterface::Yes);
|
||||
#( #impls )*
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a host functions set that can be imported by contract wasm code.
|
||||
///
|
||||
/// **NB**: Be advised that all functions defined by this macro
|
||||
/// will panic if called with unexpected arguments.
|
||||
///
|
||||
/// It's up to you as the user of this macro to check signatures of wasm code to be executed
|
||||
/// and reject the code if any imported function has a mismatched signature.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is
|
||||
/// because the module `seal0` is the default when no module is specified.
|
||||
///
|
||||
/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the
|
||||
/// appropriate attribute as follows:
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// #[version(2)]
|
||||
/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnErrorCode, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
///
|
||||
/// #[version(3)]
|
||||
/// #[unstable]
|
||||
/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// The function `bar` is additionally annotated with `unstable` which removes it from the stable
|
||||
/// interface. Check out the README to learn about unstable functions.
|
||||
///
|
||||
/// In legacy versions of pezpallet_contracts, it was a naming convention that all host functions had
|
||||
/// to be named with the `seal_` prefix. For the sake of backwards compatibility, each host function
|
||||
/// now can get a such prefix-named alias function generated by marking it by the
|
||||
/// `#[prefixed_alias]` attribute:
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// #[version(1)]
|
||||
/// #[prefixed_alias]
|
||||
/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnErrorCode, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
///
|
||||
/// #[version(42)]
|
||||
/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In this example, the following host functions will be generated by the macro:
|
||||
/// - `foo()` in module `seal1`,
|
||||
/// - `seal_foo()` in module `seal1`,
|
||||
/// - `bar()` in module `seal42`.
|
||||
///
|
||||
/// Only following return types are allowed for the host functions defined with the macro:
|
||||
/// - `Result<(), TrapReason>`,
|
||||
/// - `Result<ReturnErrorCode, TrapReason>`,
|
||||
/// - `Result<u32, TrapReason>`.
|
||||
///
|
||||
/// The macro expands to `pub struct Env` declaration, with the following traits implementations:
|
||||
/// - `pezpallet_contracts::wasm::Environment<Runtime<E>> where E: Ext`
|
||||
/// - `pezpallet_contracts::wasm::Environment<()>`
|
||||
///
|
||||
/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful
|
||||
/// when only checking whether a code can be instantiated without actually executing any code.
|
||||
///
|
||||
/// # Generating Documentation
|
||||
///
|
||||
/// Passing `doc` attribute to the macro (like `#[define_env(doc)]`) will make it also expand
|
||||
/// additional `pezpallet_contracts::api_doc::seal0`, `pezpallet_contracts::api_doc::seal1`,
|
||||
/// `...` modules each having its `Api` trait containing functions holding documentation for every
|
||||
/// host function defined by the macro.
|
||||
///
|
||||
/// # Deprecated Interfaces
|
||||
///
|
||||
/// An interface can be annotated with `#[deprecated]`. It is mutually exclusive with `#[unstable]`.
|
||||
/// Deprecated interfaces have the following properties:
|
||||
/// - New contract codes utilizing those interfaces cannot be uploaded.
|
||||
/// - New contracts from existing codes utilizing those interfaces cannot be instantiated.
|
||||
/// - Existing contracts containing those interfaces still work.
|
||||
///
|
||||
/// Those interfaces will eventually be removed.
|
||||
///
|
||||
/// To build up these docs, run:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// cargo doc
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) {
|
||||
let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attribute:
|
||||
- `#[define_env]`
|
||||
- `#[define_env(doc)]`"#;
|
||||
let span = TokenStream2::from(attr).span();
|
||||
return syn::Error::new(span, msg).to_compile_error().into();
|
||||
}
|
||||
|
||||
let item = syn::parse_macro_input!(item as syn::ItemMod);
|
||||
|
||||
match EnvDef::try_from(item) {
|
||||
Ok(mut def) => expand_env(&mut def, !attr.is_empty()).into(),
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Functions that deal with address derivation.
|
||||
|
||||
use crate::{CodeHash, Config};
|
||||
use codec::{Decode, Encode};
|
||||
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
|
||||
|
||||
/// Provides the contract address generation method.
|
||||
///
|
||||
/// See [`DefaultAddressGenerator`] for the default implementation.
|
||||
///
|
||||
/// # Note for implementors
|
||||
///
|
||||
/// 1. Make sure that there are no collisions, different inputs never lead to the same output.
|
||||
/// 2. Make sure that the same inputs lead to the same output.
|
||||
pub trait AddressGenerator<T: Config> {
|
||||
/// The address of a contract based on the given instantiate parameters.
|
||||
///
|
||||
/// Changing the formular for an already deployed chain is fine as long as no collisions
|
||||
/// with the old formular. Changes only affect existing contracts.
|
||||
fn contract_address(
|
||||
deploying_address: &T::AccountId,
|
||||
code_hash: &CodeHash<T>,
|
||||
input_data: &[u8],
|
||||
salt: &[u8],
|
||||
) -> T::AccountId;
|
||||
}
|
||||
|
||||
/// Default address generator.
|
||||
///
|
||||
/// This is the default address generator used by contract instantiation. Its result
|
||||
/// is only dependent on its inputs. It can therefore be used to reliably predict the
|
||||
/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There
|
||||
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
|
||||
/// Formula:
|
||||
/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
|
||||
pub struct DefaultAddressGenerator;
|
||||
|
||||
impl<T: Config> AddressGenerator<T> for DefaultAddressGenerator {
|
||||
/// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
|
||||
fn contract_address(
|
||||
deploying_address: &T::AccountId,
|
||||
code_hash: &CodeHash<T>,
|
||||
input_data: &[u8],
|
||||
salt: &[u8],
|
||||
) -> T::AccountId {
|
||||
let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt)
|
||||
.using_encoded(T::Hashing::hash);
|
||||
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||
.expect("infinite length input; no invalid inputs for type; qed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
benchmarking::{Contract, WasmModule},
|
||||
exec::{Ext, Key, Stack},
|
||||
storage::meter::Meter,
|
||||
transient_storage::MeterEntry,
|
||||
wasm::Runtime,
|
||||
BalanceOf, Config, DebugBufferVec, Determinism, Error, ExecReturnValue, GasMeter, Origin,
|
||||
Schedule, TypeInfo, WasmBlob, Weight,
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Encode, HasCompact};
|
||||
use core::fmt::Debug;
|
||||
use pezframe_benchmarking::benchmarking;
|
||||
use pezsp_core::Get;
|
||||
|
||||
type StackExt<'a, T> = Stack<'a, T, WasmBlob<T>>;
|
||||
|
||||
/// A prepared contract call ready to be executed.
|
||||
pub struct PreparedCall<'a, T: Config> {
|
||||
func: wasmi::Func,
|
||||
store: wasmi::Store<Runtime<'a, StackExt<'a, T>>>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> PreparedCall<'a, T> {
|
||||
pub fn call(mut self) -> ExecReturnValue {
|
||||
let result = self.func.call(&mut self.store, &[], &mut []);
|
||||
WasmBlob::<T>::process_result(self.store, result).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder used to prepare a contract call.
|
||||
pub struct CallSetup<T: Config> {
|
||||
contract: Contract<T>,
|
||||
dest: T::AccountId,
|
||||
origin: Origin<T>,
|
||||
gas_meter: GasMeter<T>,
|
||||
storage_meter: Meter<T>,
|
||||
schedule: Schedule<T>,
|
||||
value: BalanceOf<T>,
|
||||
debug_message: Option<DebugBufferVec<T>>,
|
||||
determinism: Determinism,
|
||||
data: Vec<u8>,
|
||||
transient_storage_size: u32,
|
||||
}
|
||||
|
||||
impl<T> Default for CallSetup<T>
|
||||
where
|
||||
T: Config + pezpallet_balances::Config,
|
||||
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(WasmModule::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CallSetup<T>
|
||||
where
|
||||
T: Config + pezpallet_balances::Config,
|
||||
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
|
||||
{
|
||||
/// Setup a new call for the given module.
|
||||
pub fn new(module: WasmModule<T>) -> Self {
|
||||
let contract = Contract::<T>::new(module.clone(), vec![]).unwrap();
|
||||
let dest = contract.account_id.clone();
|
||||
let origin = Origin::from_account_id(contract.caller.clone());
|
||||
|
||||
let storage_meter = Meter::new(&origin, None, 0u32.into()).unwrap();
|
||||
|
||||
// Whitelist contract account, as it is already accounted for in the call benchmark
|
||||
benchmarking::add_to_whitelist(
|
||||
pezframe_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
|
||||
);
|
||||
|
||||
// Whitelist the contract's contractInfo as it is already accounted for in the call
|
||||
// benchmark
|
||||
benchmarking::add_to_whitelist(
|
||||
crate::ContractInfoOf::<T>::hashed_key_for(&contract.account_id).into(),
|
||||
);
|
||||
|
||||
Self {
|
||||
contract,
|
||||
dest,
|
||||
origin,
|
||||
gas_meter: GasMeter::new(Weight::MAX),
|
||||
storage_meter,
|
||||
schedule: T::Schedule::get(),
|
||||
value: 0u32.into(),
|
||||
debug_message: None,
|
||||
determinism: Determinism::Enforced,
|
||||
data: vec![],
|
||||
transient_storage_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the meter's storage deposit limit.
|
||||
pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf<T>) {
|
||||
self.storage_meter = Meter::new(&self.origin, Some(balance), 0u32.into()).unwrap();
|
||||
}
|
||||
|
||||
/// Set the call's origin.
|
||||
pub fn set_origin(&mut self, origin: Origin<T>) {
|
||||
self.origin = origin;
|
||||
}
|
||||
|
||||
/// Set the contract's balance.
|
||||
pub fn set_balance(&mut self, value: BalanceOf<T>) {
|
||||
self.contract.set_balance(value);
|
||||
}
|
||||
|
||||
/// Set the call's input data.
|
||||
pub fn set_data(&mut self, value: Vec<u8>) {
|
||||
self.data = value;
|
||||
}
|
||||
|
||||
/// Set the transient storage size.
|
||||
pub fn set_transient_storage_size(&mut self, size: u32) {
|
||||
self.transient_storage_size = size;
|
||||
}
|
||||
|
||||
/// Set the debug message.
|
||||
pub fn enable_debug_message(&mut self) {
|
||||
self.debug_message = Some(Default::default());
|
||||
}
|
||||
|
||||
/// Get the debug message.
|
||||
pub fn debug_message(&self) -> Option<DebugBufferVec<T>> {
|
||||
self.debug_message.clone()
|
||||
}
|
||||
|
||||
/// Get the call's input data.
|
||||
pub fn data(&self) -> Vec<u8> {
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
/// Get the call's contract.
|
||||
pub fn contract(&self) -> Contract<T> {
|
||||
self.contract.clone()
|
||||
}
|
||||
|
||||
/// Build the call stack.
|
||||
pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob<T>) {
|
||||
let mut ext = StackExt::bench_new_call(
|
||||
self.dest.clone(),
|
||||
self.origin.clone(),
|
||||
&mut self.gas_meter,
|
||||
&mut self.storage_meter,
|
||||
&self.schedule,
|
||||
self.value,
|
||||
self.debug_message.as_mut(),
|
||||
self.determinism,
|
||||
);
|
||||
if self.transient_storage_size > 0 {
|
||||
Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
|
||||
}
|
||||
ext
|
||||
}
|
||||
|
||||
/// Prepare a call to the module.
|
||||
pub fn prepare_call<'a>(
|
||||
ext: &'a mut StackExt<'a, T>,
|
||||
module: WasmBlob<T>,
|
||||
input: Vec<u8>,
|
||||
) -> PreparedCall<'a, T> {
|
||||
let (func, store) = module.bench_prepare_call(ext, input);
|
||||
PreparedCall { func, store }
|
||||
}
|
||||
|
||||
/// Add transient_storage
|
||||
fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
|
||||
let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
|
||||
ext.transient_storage().meter().current_mut().limit = size;
|
||||
for i in 1u32.. {
|
||||
let mut key_data = i.to_le_bytes().to_vec();
|
||||
while key_data.last() == Some(&0) {
|
||||
key_data.pop();
|
||||
}
|
||||
let key = Key::<T>::try_from_var(key_data).unwrap();
|
||||
if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
|
||||
// Restore previous settings.
|
||||
ext.transient_storage().meter().current_mut().limit = limit;
|
||||
ext.transient_storage().meter().current_mut().amount = amount;
|
||||
if e == Error::<T>::OutOfTransientStorage.into() {
|
||||
break;
|
||||
} else {
|
||||
return Err("Initialization of the transient storage failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! memory(
|
||||
($($bytes:expr,)*) => {
|
||||
vec![]
|
||||
.into_iter()
|
||||
$(.chain($bytes))*
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! build_runtime(
|
||||
($runtime:ident, $memory:ident: [$($segment:expr,)*]) => {
|
||||
$crate::build_runtime!($runtime, _contract, $memory: [$($segment,)*]);
|
||||
};
|
||||
($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => {
|
||||
$crate::build_runtime!($runtime, $contract);
|
||||
let mut $memory = $crate::memory!($($bytes,)*);
|
||||
};
|
||||
($runtime:ident, $contract:ident) => {
|
||||
let mut setup = CallSetup::<T>::default();
|
||||
let $contract = setup.contract();
|
||||
let input = setup.data();
|
||||
let (mut ext, _) = setup.ext();
|
||||
let mut $runtime = crate::wasm::Runtime::new(&mut ext, input);
|
||||
};
|
||||
);
|
||||
@@ -0,0 +1,363 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Functions to procedurally construct contract code used for benchmarking.
|
||||
//!
|
||||
//! In order to be able to benchmark events that are triggered by contract execution
|
||||
//! (API calls into seal, individual instructions), we need to generate contracts that
|
||||
//! perform those events. Because those contracts can get very big we cannot simply define
|
||||
//! them as text (.wat) as this will be too slow and consume too much memory. Therefore
|
||||
//! we define this simple definition of a contract that can be passed to `create_code` that
|
||||
//! compiles it down into a `WasmModule` that can be used as a contract's code.
|
||||
|
||||
use crate::Config;
|
||||
use alloc::{borrow::ToOwned, vec, vec::Vec};
|
||||
use pezframe_support::traits::Get;
|
||||
use pezsp_runtime::{traits::Hash, Saturating};
|
||||
use wasm_instrument::parity_wasm::{
|
||||
builder,
|
||||
elements::{
|
||||
self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section,
|
||||
ValueType,
|
||||
},
|
||||
};
|
||||
|
||||
/// The location where to put the generated code.
|
||||
pub enum Location {
|
||||
/// Generate all code into the `call` exported function.
|
||||
Call,
|
||||
/// Generate all code into the `deploy` exported function.
|
||||
Deploy,
|
||||
}
|
||||
|
||||
/// Pass to `create_code` in order to create a compiled `WasmModule`.
|
||||
///
|
||||
/// This exists to have a more declarative way to describe a wasm module than to use
|
||||
/// parity-wasm directly. It is tailored to fit the structure of contracts that are
|
||||
/// needed for benchmarking.
|
||||
#[derive(Default)]
|
||||
pub struct ModuleDefinition {
|
||||
/// Imported memory attached to the module. No memory is imported if `None`.
|
||||
pub memory: Option<ImportedMemory>,
|
||||
/// Initializers for the imported memory.
|
||||
pub data_segments: Vec<DataSegment>,
|
||||
/// Creates the supplied amount of i64 mutable globals initialized with random values.
|
||||
pub num_globals: u32,
|
||||
/// List of functions that the module should import. They start with index 0.
|
||||
pub imported_functions: Vec<ImportedFunction>,
|
||||
/// Function body of the exported `deploy` function. Body is empty if `None`.
|
||||
/// Its index is `imported_functions.len()`.
|
||||
pub deploy_body: Option<FuncBody>,
|
||||
/// Function body of the exported `call` function. Body is empty if `None`.
|
||||
/// Its index is `imported_functions.len() + 1`.
|
||||
pub call_body: Option<FuncBody>,
|
||||
/// Function body of a non-exported function with index `imported_functions.len() + 2`.
|
||||
pub aux_body: Option<FuncBody>,
|
||||
/// The amount of I64 arguments the aux function should have.
|
||||
pub aux_arg_num: u32,
|
||||
/// Create a table containing function pointers.
|
||||
pub table: Option<TableSegment>,
|
||||
/// Create a section named "dummy" of the specified size. This is useful in order to
|
||||
/// benchmark the overhead of loading and storing codes of specified sizes. The dummy
|
||||
/// section only contributes to the size of the contract but does not affect execution.
|
||||
pub dummy_section: u32,
|
||||
}
|
||||
|
||||
pub struct TableSegment {
|
||||
/// How many elements should be created inside the table.
|
||||
pub num_elements: u32,
|
||||
/// The function index with which all table elements should be initialized.
|
||||
pub function_index: u32,
|
||||
}
|
||||
|
||||
pub struct DataSegment {
|
||||
pub offset: u32,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImportedMemory {
|
||||
pub min_pages: u32,
|
||||
pub max_pages: u32,
|
||||
}
|
||||
|
||||
impl ImportedMemory {
|
||||
pub fn max<T: Config>() -> Self {
|
||||
let pages = max_pages::<T>();
|
||||
Self { min_pages: pages, max_pages: pages }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImportedFunction {
|
||||
pub module: &'static str,
|
||||
pub name: &'static str,
|
||||
pub params: Vec<ValueType>,
|
||||
pub return_type: Option<ValueType>,
|
||||
}
|
||||
|
||||
/// A wasm module ready to be put on chain.
|
||||
#[derive(Clone)]
|
||||
pub struct WasmModule<T: Config> {
|
||||
pub code: Vec<u8>,
|
||||
pub hash: <T::Hashing as Hash>::Output,
|
||||
}
|
||||
|
||||
impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
|
||||
fn from(def: ModuleDefinition) -> Self {
|
||||
// internal functions start at that offset.
|
||||
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
|
||||
|
||||
// Every contract must export "deploy" and "call" functions.
|
||||
let mut contract = builder::module()
|
||||
// deploy function (first internal function)
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.with_body(
|
||||
def.deploy_body
|
||||
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
|
||||
)
|
||||
.build()
|
||||
// call function (second internal function)
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.with_body(
|
||||
def.call_body
|
||||
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
|
||||
)
|
||||
.build()
|
||||
.export()
|
||||
.field("deploy")
|
||||
.internal()
|
||||
.func(func_offset)
|
||||
.build()
|
||||
.export()
|
||||
.field("call")
|
||||
.internal()
|
||||
.func(func_offset + 1)
|
||||
.build();
|
||||
|
||||
// If specified we add an additional internal function
|
||||
if let Some(body) = def.aux_body {
|
||||
let mut signature = contract.function().signature();
|
||||
for _ in 0..def.aux_arg_num {
|
||||
signature = signature.with_param(ValueType::I64);
|
||||
}
|
||||
contract = signature.build().with_body(body).build();
|
||||
}
|
||||
|
||||
// Grant access to linear memory.
|
||||
// Every contract module is required to have an imported memory.
|
||||
// If no memory is specified in the passed ModuleDefinition, then
|
||||
// default to (1, 1).
|
||||
let (init, max) = if let Some(memory) = &def.memory {
|
||||
(memory.min_pages, Some(memory.max_pages))
|
||||
} else {
|
||||
(1, Some(1))
|
||||
};
|
||||
|
||||
contract = contract.import().path("env", "memory").external().memory(init, max).build();
|
||||
|
||||
// Import supervisor functions. They start with idx 0.
|
||||
for func in def.imported_functions {
|
||||
let sig = builder::signature()
|
||||
.with_params(func.params)
|
||||
.with_results(func.return_type)
|
||||
.build_sig();
|
||||
let sig = contract.push_signature(sig);
|
||||
contract = contract
|
||||
.import()
|
||||
.module(func.module)
|
||||
.field(func.name)
|
||||
.with_external(elements::External::Function(sig))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Initialize memory
|
||||
for data in def.data_segments {
|
||||
contract = contract
|
||||
.data()
|
||||
.offset(Instruction::I32Const(data.offset as i32))
|
||||
.value(data.value)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Add global variables
|
||||
if def.num_globals > 0 {
|
||||
use rand::{distributions::Standard, prelude::*};
|
||||
let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
|
||||
for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
|
||||
contract = contract
|
||||
.global()
|
||||
.value_type()
|
||||
.i64()
|
||||
.mutable()
|
||||
.init_expr(Instruction::I64Const(val))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
// Add function pointer table
|
||||
if let Some(table) = def.table {
|
||||
contract = contract
|
||||
.table()
|
||||
.with_min(table.num_elements)
|
||||
.with_max(Some(table.num_elements))
|
||||
.with_element(0, vec![table.function_index; table.num_elements as usize])
|
||||
.build();
|
||||
}
|
||||
|
||||
// Add the dummy section
|
||||
if def.dummy_section > 0 {
|
||||
contract = contract.with_section(Section::Custom(CustomSection::new(
|
||||
"dummy".to_owned(),
|
||||
vec![42; def.dummy_section as usize],
|
||||
)));
|
||||
}
|
||||
|
||||
let code = contract.build().into_bytes().unwrap();
|
||||
let hash = T::Hashing::hash(&code);
|
||||
Self { code: code.into(), hash }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> WasmModule<T> {
|
||||
/// Creates a wasm module with an empty `call` and `deploy` function and nothing else.
|
||||
pub fn dummy() -> Self {
|
||||
ModuleDefinition::default().into()
|
||||
}
|
||||
|
||||
/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
|
||||
pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
|
||||
// We want the module to have the size `dummy_bytes`.
|
||||
// This is not completely correct as the overhead grows when the contract grows
|
||||
// because of variable length integer encoding. However, it is good enough to be that
|
||||
// close for benchmarking purposes.
|
||||
let module_overhead = 65;
|
||||
ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
dummy_section: dummy_bytes.saturating_sub(module_overhead),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of
|
||||
/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
|
||||
/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
|
||||
/// `code_location`: Whether to place the code into `deploy` or `call`.
|
||||
pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
|
||||
use self::elements::Instruction::{End, GetLocal, If, Return};
|
||||
// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
|
||||
// We do one expansion less to account for the code section and function body
|
||||
// size fields inside the binary wasm module representation which are leb128 encoded
|
||||
// and therefore grow in size when the contract grows. We are not allowed to overshoot
|
||||
// because of the maximum code size that is enforced by `instantiate_with_code`.
|
||||
let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
|
||||
const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
|
||||
let mut locals = vec![Local::new(1, ValueType::I32)];
|
||||
if use_float {
|
||||
locals.push(Local::new(1, ValueType::F32));
|
||||
locals.push(Local::new(2, ValueType::F32));
|
||||
locals.push(Local::new(3, ValueType::F32));
|
||||
expansions.saturating_dec();
|
||||
}
|
||||
let mut module =
|
||||
ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
|
||||
let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
|
||||
match code_location {
|
||||
Location::Call => module.call_body = body,
|
||||
Location::Deploy => module.deploy_body = body,
|
||||
}
|
||||
module.into()
|
||||
}
|
||||
|
||||
/// Creates a wasm module that calls the imported function `noop` `repeat` times.
|
||||
pub fn noop(repeat: u32) -> Self {
|
||||
let pages = max_pages::<T>();
|
||||
ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "noop",
|
||||
params: vec![],
|
||||
return_type: None,
|
||||
}],
|
||||
// Write the output buffer size. The output size will be overwritten by the
|
||||
// supervisor with the real size when calling the getter. Since this size does not
|
||||
// change between calls it suffices to start with an initial value and then just
|
||||
// leave as whatever value was written there.
|
||||
data_segments: vec![DataSegment {
|
||||
offset: 0,
|
||||
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
|
||||
}],
|
||||
call_body: Some(body::repeated(
|
||||
repeat,
|
||||
&[
|
||||
Instruction::Call(0), // call the imported function
|
||||
],
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
|
||||
pub mod body {
|
||||
use super::*;
|
||||
|
||||
pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {
|
||||
repeated_with_locals(&[], repetitions, instructions)
|
||||
}
|
||||
|
||||
pub fn repeated_with_locals(
|
||||
locals: &[Local],
|
||||
repetitions: u32,
|
||||
instructions: &[Instruction],
|
||||
) -> FuncBody {
|
||||
let instructions = Instructions::new(
|
||||
instructions
|
||||
.iter()
|
||||
.cycle()
|
||||
.take(instructions.len() * usize::try_from(repetitions).unwrap())
|
||||
.cloned()
|
||||
.chain(core::iter::once(Instruction::End))
|
||||
.collect(),
|
||||
);
|
||||
FuncBody::new(locals.to_vec(), instructions)
|
||||
}
|
||||
|
||||
pub fn repeated_with_locals_using<const N: usize>(
|
||||
locals: &[Local],
|
||||
repetitions: u32,
|
||||
mut f: impl FnMut() -> [Instruction; N],
|
||||
) -> FuncBody {
|
||||
let mut instructions = Vec::new();
|
||||
for _ in 0..repetitions {
|
||||
instructions.extend(f());
|
||||
}
|
||||
instructions.push(Instruction::End);
|
||||
FuncBody::new(locals.to_vec(), Instructions::new(instructions))
|
||||
}
|
||||
}
|
||||
|
||||
/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
|
||||
pub fn max_pages<T: Config>() -> u32 {
|
||||
T::Schedule::get().limits.memory_pages
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/// ! For instruction benchmarking we do not instantiate a full contract but merely the
|
||||
/// ! sandbox to execute the Wasm code. This is because we do not need the full
|
||||
/// ! environment that provides the seal interface as imported functions.
|
||||
use super::{code::WasmModule, Config};
|
||||
use crate::wasm::{
|
||||
AllowDeprecatedInterface, AllowUnstableInterface, Determinism, Environment, LoadedModule,
|
||||
LoadingMode, WasmBlob,
|
||||
};
|
||||
use pezsp_core::Get;
|
||||
use wasmi::{errors::LinkerError, CompilationMode, Func, Linker, StackLimits, Store};
|
||||
|
||||
/// Minimal execution environment without any imported functions.
|
||||
pub struct Sandbox {
|
||||
entry_point: Func,
|
||||
store: Store<()>,
|
||||
}
|
||||
|
||||
impl Sandbox {
|
||||
/// Invoke the `call` function of a contract code and panic on any execution error.
|
||||
pub fn invoke(&mut self) {
|
||||
self.entry_point.call(&mut self.store, &[], &mut []).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> From<&WasmModule<T>> for Sandbox {
|
||||
/// Creates an instance from the supplied module.
|
||||
/// Sets the execution engine fuel level to `u64::MAX`.
|
||||
fn from(module: &WasmModule<T>) -> Self {
|
||||
let contract = LoadedModule::new::<T>(
|
||||
&module.code,
|
||||
Determinism::Relaxed,
|
||||
Some(StackLimits::default()),
|
||||
LoadingMode::Checked,
|
||||
CompilationMode::Eager,
|
||||
)
|
||||
.expect("Failed to load Wasm module");
|
||||
|
||||
let (mut store, _memory, instance) = WasmBlob::<T>::instantiate::<EmptyEnv, _>(
|
||||
contract,
|
||||
(),
|
||||
&<T>::Schedule::get(),
|
||||
// We are testing with an empty environment anyways
|
||||
AllowDeprecatedInterface::No,
|
||||
)
|
||||
.expect("Failed to create benchmarking Sandbox instance");
|
||||
|
||||
// Set fuel for wasmi execution.
|
||||
store
|
||||
.set_fuel(u64::MAX)
|
||||
.expect("We've set up engine to fuel consuming mode; qed");
|
||||
|
||||
let entry_point = instance
|
||||
.start(&mut store)
|
||||
.unwrap()
|
||||
.get_export(&store, "call")
|
||||
.unwrap()
|
||||
.into_func()
|
||||
.unwrap();
|
||||
Self { entry_point, store }
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyEnv;
|
||||
|
||||
impl Environment<()> for EmptyEnv {
|
||||
fn define(
|
||||
_: &mut Store<()>,
|
||||
_: &mut Linker<()>,
|
||||
_: AllowUnstableInterface,
|
||||
_: AllowDeprecatedInterface,
|
||||
) -> Result<(), LinkerError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A mechanism for runtime authors to augment the functionality of contracts.
|
||||
//!
|
||||
//! The runtime is able to call into any contract and retrieve the result using
|
||||
//! [`bare_call`](crate::Pallet::bare_call). This already allows customization of runtime
|
||||
//! behaviour by user generated code (contracts). However, often it is more straightforward
|
||||
//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter
|
||||
//! one a "chain extension" because it allows the chain to extend the set of functions that are
|
||||
//! callable by a contract.
|
||||
//!
|
||||
//! In order to create a chain extension the runtime author implements the [`ChainExtension`]
|
||||
//! trait and declares it in this pallet's [configuration Trait](crate::Config). All types
|
||||
//! required for this endeavour are defined or re-exported in this module. There is an
|
||||
//! implementation on `()` which can be used to signal that no chain extension is available.
|
||||
//!
|
||||
//! # Using multiple chain extensions
|
||||
//!
|
||||
//! Often there is a need for having multiple chain extensions. This is often the case when
|
||||
//! some generally useful off-the-shelf extensions should be included. To have multiple chain
|
||||
//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like
|
||||
//! this `type Extensions = (ExtensionA, ExtensionB)`.
|
||||
//!
|
||||
//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple.
|
||||
//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions
|
||||
//! should be used when the contract calls a chain extensions. Extensions which are generally
|
||||
//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry)
|
||||
//! so that no collisions with other vendors will occur.
|
||||
//!
|
||||
//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with
|
||||
//! the registry.**
|
||||
//!
|
||||
//! # Security
|
||||
//!
|
||||
//! The chain author alone is responsible for the security of the chain extension.
|
||||
//! This includes avoiding the exposure of exploitable functions and charging the
|
||||
//! appropriate amount of weight. In order to do so benchmarks must be written and the
|
||||
//! [`charge_weight`](Environment::charge_weight) function must be called **before**
|
||||
//! carrying out any action that causes the consumption of the chargeable weight.
|
||||
//! It cannot be overstated how delicate of a process the creation of a chain extension
|
||||
//! is. Check whether using [`bare_call`](crate::Pallet::bare_call) suffices for the
|
||||
//! use case at hand.
|
||||
//!
|
||||
//! # Benchmarking
|
||||
//!
|
||||
//! The builtin contract callable functions that pezpallet-contracts provides all have
|
||||
//! benchmarks that determine the correct weight that an invocation of these functions
|
||||
//! induces. In order to be able to charge the correct weight for the functions defined
|
||||
//! by a chain extension benchmarks must be written, too. In the near future this crate
|
||||
//! will provide the means for easier creation of those specialized benchmarks.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! The ink-examples repository maintains an
|
||||
//! [end-to-end example](https://github.com/use-ink/ink-examples/tree/v5.x.x/rand-extension)
|
||||
//! on how to use a chain extension in order to provide new features to ink! contracts.
|
||||
|
||||
use crate::{
|
||||
wasm::{Runtime, RuntimeCosts},
|
||||
Error,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
pub use crate::{exec::Ext, gas::ChargedAmount, storage::meter::Diff, Config};
|
||||
pub use pezframe_system::Config as SysConfig;
|
||||
pub use pezpallet_contracts_uapi::ReturnFlags;
|
||||
|
||||
/// Result that returns a [`DispatchError`] on error.
|
||||
pub type Result<T> = core::result::Result<T, DispatchError>;
|
||||
|
||||
/// A trait used to extend the set of contract callable functions.
|
||||
///
|
||||
/// In order to create a custom chain extension this trait must be implemented and supplied
|
||||
/// to the pallet contracts configuration trait as the associated type of the same name.
|
||||
/// Consult the [module documentation](self) for a general explanation of chain extensions.
|
||||
///
|
||||
/// # Lifetime
|
||||
///
|
||||
/// The extension will be [`Default`] initialized at the beginning of each call
|
||||
/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension
|
||||
/// can be used as a per-call scratch buffer.
|
||||
pub trait ChainExtension<C: Config> {
|
||||
/// Call the chain extension logic.
|
||||
///
|
||||
/// This is the only function that needs to be implemented in order to write a
|
||||
/// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension`
|
||||
/// imported wasm function.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `env`: Access to the remaining arguments and the execution environment.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// In case of `Err` the contract execution is immediately suspended and the passed error
|
||||
/// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit
|
||||
/// behaviour.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The [`Self::call`] can be invoked within a read-only context, where any state-changing calls
|
||||
/// are disallowed. This information can be obtained using `env.ext().is_read_only()`. It is
|
||||
/// crucial for the implementer to handle this scenario appropriately.
|
||||
fn call<E: Ext<T = C>>(&mut self, env: Environment<E, InitState>) -> Result<RetVal>;
|
||||
|
||||
/// Determines whether chain extensions are enabled for this chain.
|
||||
///
|
||||
/// The default implementation returns `true`. Therefore it is not necessary to overwrite
|
||||
/// this function when implementing a chain extension. In case of `false` the deployment of
|
||||
/// a contract that references `seal_call_chain_extension` will be denied and calling this
|
||||
/// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling
|
||||
/// into [`call`](Self::call).
|
||||
fn enabled() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`ChainExtension`] that can be composed with other extensions using a tuple.
|
||||
///
|
||||
/// An extension that implements this trait can be put in a tuple in order to have multiple
|
||||
/// extensions available. The tuple implementation routes requests based on the first two
|
||||
/// most significant bytes of the `id` passed to `call`.
|
||||
///
|
||||
/// If this extensions is to be used by multiple runtimes consider
|
||||
/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there
|
||||
/// are no collisions with other vendors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Currently, we support tuples of up to ten registered chain extensions. If more chain extensions
|
||||
/// are needed consider opening an issue.
|
||||
pub trait RegisteredChainExtension<C: Config>: ChainExtension<C> {
|
||||
/// The extensions globally unique identifier.
|
||||
const ID: u16;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(10)]
|
||||
#[tuple_types_custom_trait_bound(RegisteredChainExtension<C>)]
|
||||
impl<C: Config> ChainExtension<C> for Tuple {
|
||||
fn call<E: Ext<T = C>>(&mut self, mut env: Environment<E, InitState>) -> Result<RetVal> {
|
||||
for_tuples!(
|
||||
#(
|
||||
if (Tuple::ID == env.ext_id()) && Tuple::enabled() {
|
||||
return Tuple.call(env);
|
||||
}
|
||||
)*
|
||||
);
|
||||
Err(Error::<E::T>::NoChainExtension.into())
|
||||
}
|
||||
|
||||
fn enabled() -> bool {
|
||||
for_tuples!(
|
||||
#(
|
||||
if Tuple::enabled() {
|
||||
return true;
|
||||
}
|
||||
)*
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the exit behaviour and return value of a chain extension.
|
||||
pub enum RetVal {
|
||||
/// The chain extensions returns the supplied value to its calling contract.
|
||||
Converging(u32),
|
||||
/// The control does **not** return to the calling contract.
|
||||
///
|
||||
/// Use this to stop the execution of the contract when the chain extension returns.
|
||||
/// The semantic is the same as for calling `seal_return`: The control returns to
|
||||
/// the caller of the currently executing contract yielding the supplied buffer and
|
||||
/// flags.
|
||||
Diverging { flags: ReturnFlags, data: Vec<u8> },
|
||||
}
|
||||
|
||||
/// Grants the chain extension access to its parameters and execution environment.
|
||||
///
|
||||
/// It uses [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html)
|
||||
/// to enforce the correct usage of the parameters passed to the chain extension.
|
||||
pub struct Environment<'a, 'b, E: Ext, S: State> {
|
||||
/// The actual data of this type.
|
||||
inner: Inner<'a, 'b, E>,
|
||||
/// `S` is only used in the type system but never as value.
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// Functions that are available in every state of this type.
|
||||
impl<'a, 'b, E: Ext, S: State> Environment<'a, 'b, E, S> {
|
||||
/// The function id within the `id` passed by a contract.
|
||||
///
|
||||
/// It returns the two least significant bytes of the `id` passed by a contract as the other
|
||||
/// two bytes represent the chain extension itself (the code which is calling this function).
|
||||
pub fn func_id(&self) -> u16 {
|
||||
(self.inner.id & 0x0000FFFF) as u16
|
||||
}
|
||||
|
||||
/// The chain extension id within the `id` passed by a contract.
|
||||
///
|
||||
/// It returns the two most significant bytes of the `id` passed by a contract which represent
|
||||
/// the chain extension itself (the code which is calling this function).
|
||||
pub fn ext_id(&self) -> u16 {
|
||||
(self.inner.id >> 16) as u16
|
||||
}
|
||||
|
||||
/// Charge the passed `amount` of weight from the overall limit.
|
||||
///
|
||||
/// It returns `Ok` when there the remaining weight budget is larger than the passed
|
||||
/// `weight`. It returns `Err` otherwise. In this case the chain extension should
|
||||
/// abort the execution and pass through the error.
|
||||
///
|
||||
/// The returned value can be used to with [`Self::adjust_weight`]. Other than that
|
||||
/// it has no purpose.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Weight is synonymous with gas in bizinikiwi.
|
||||
pub fn charge_weight(&mut self, amount: Weight) -> Result<ChargedAmount> {
|
||||
self.inner.runtime.charge_gas(RuntimeCosts::ChainExtension(amount))
|
||||
}
|
||||
|
||||
/// Adjust a previously charged amount down to its actual amount.
|
||||
///
|
||||
/// This is when a maximum a priori amount was charged and then should be partially
|
||||
/// refunded to match the actual amount.
|
||||
pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) {
|
||||
self.inner
|
||||
.runtime
|
||||
.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight))
|
||||
}
|
||||
|
||||
/// Grants access to the execution environment of the current contract call.
|
||||
///
|
||||
/// Consult the functions on the returned type before re-implementing those functions.
|
||||
pub fn ext(&mut self) -> &mut E {
|
||||
self.inner.runtime.ext()
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that are only available in the initial state of this type.
|
||||
///
|
||||
/// Those are the functions that determine how the arguments to the chain extensions
|
||||
/// should be consumed.
|
||||
impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
|
||||
/// Creates a new environment for consumption by a chain extension.
|
||||
///
|
||||
/// It is only available to this crate because only the wasm runtime module needs to
|
||||
/// ever create this type. Chain extensions merely consume it.
|
||||
pub(crate) fn new(
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
memory: &'a mut [u8],
|
||||
id: u32,
|
||||
input_ptr: u32,
|
||||
input_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32,
|
||||
) -> Self {
|
||||
Environment {
|
||||
inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr },
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use all arguments as integer values.
|
||||
pub fn only_in(self) -> Environment<'a, 'b, E, OnlyInState> {
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Use input arguments as integer and output arguments as pointer to a buffer.
|
||||
pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, PrimInBufOutState> {
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Use input and output arguments as pointers to a buffer.
|
||||
pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, BufInBufOutState> {
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions to use the input arguments as integers.
|
||||
impl<'a, 'b, E: Ext, S: PrimIn> Environment<'a, 'b, E, S> {
|
||||
/// The `input_ptr` argument.
|
||||
pub fn val0(&self) -> u32 {
|
||||
self.inner.input_ptr
|
||||
}
|
||||
|
||||
/// The `input_len` argument.
|
||||
pub fn val1(&self) -> u32 {
|
||||
self.inner.input_len
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions to use the output arguments as integers.
|
||||
impl<'a, 'b, E: Ext, S: PrimOut> Environment<'a, 'b, E, S> {
|
||||
/// The `output_ptr` argument.
|
||||
pub fn val2(&self) -> u32 {
|
||||
self.inner.output_ptr
|
||||
}
|
||||
|
||||
/// The `output_len_ptr` argument.
|
||||
pub fn val3(&self) -> u32 {
|
||||
self.inner.output_len_ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions to use the input arguments as pointer to a buffer.
|
||||
impl<'a, 'b, E: Ext, S: BufIn> Environment<'a, 'b, E, S> {
|
||||
/// Reads `min(max_len, in_len)` from contract memory.
|
||||
///
|
||||
/// This does **not** charge any weight. The caller must make sure that the an
|
||||
/// appropriate amount of weight is charged **before** reading from contract memory.
|
||||
/// The reason for that is that usually the costs for reading data and processing
|
||||
/// said data cannot be separated in a benchmark. Therefore a chain extension would
|
||||
/// charge the overall costs either using `max_len` (worst case approximation) or using
|
||||
/// [`in_len()`](Self::in_len).
|
||||
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
|
||||
self.inner.runtime.read_sandbox_memory(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
self.inner.input_len.min(max_len),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reads `min(buffer.len(), in_len) from contract memory.
|
||||
///
|
||||
/// This takes a mutable pointer to a buffer fills it with data and shrinks it to
|
||||
/// the size of the actual data. Apart from supporting pre-allocated buffers it is
|
||||
/// equivalent to to [`read()`](Self::read).
|
||||
pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> {
|
||||
let len = buffer.len();
|
||||
let sliced = {
|
||||
let buffer = core::mem::take(buffer);
|
||||
&mut buffer[..len.min(self.inner.input_len as usize)]
|
||||
};
|
||||
self.inner.runtime.read_sandbox_memory_into_buf(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
sliced,
|
||||
)?;
|
||||
*buffer = sliced;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads and decodes a type with a size fixed at compile time from contract memory.
|
||||
///
|
||||
/// This function is secure and recommended for all input types of fixed size
|
||||
/// as long as the cost of reading the memory is included in the overall already charged
|
||||
/// weight of the chain extension. This should usually be the case when fixed input types
|
||||
/// are used.
|
||||
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
|
||||
self.inner
|
||||
.runtime
|
||||
.read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr)
|
||||
}
|
||||
|
||||
/// Reads and decodes a type with a dynamic size from contract memory.
|
||||
///
|
||||
/// Make sure to include `len` in your weight calculations.
|
||||
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
|
||||
self.inner.runtime.read_sandbox_memory_as_unbounded(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
len,
|
||||
)
|
||||
}
|
||||
|
||||
/// The length of the input as passed in as `input_len`.
|
||||
///
|
||||
/// A chain extension would use this value to calculate the dynamic part of its
|
||||
/// weight. For example a chain extension that calculates the hash of some passed in
|
||||
/// bytes would use `in_len` to charge the costs of hashing that amount of bytes.
|
||||
/// This also subsumes the act of copying those bytes as a benchmarks measures both.
|
||||
pub fn in_len(&self) -> u32 {
|
||||
self.inner.input_len
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions to use the output arguments as pointer to a buffer.
|
||||
impl<'a, 'b, E: Ext, S: BufOut> Environment<'a, 'b, E, S> {
|
||||
/// Write the supplied buffer to contract memory.
|
||||
///
|
||||
/// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned.
|
||||
/// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer
|
||||
/// by supplying the guard value of `pezpallet-contracts::SENTINEL` as `out_ptr`. The
|
||||
/// `weight_per_byte` is only charged when the write actually happens and is not skipped or
|
||||
/// failed due to a too small output buffer.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
buffer: &[u8],
|
||||
allow_skip: bool,
|
||||
weight_per_byte: Option<Weight>,
|
||||
) -> Result<()> {
|
||||
self.inner.runtime.write_sandbox_output(
|
||||
self.inner.memory,
|
||||
self.inner.output_ptr,
|
||||
self.inner.output_len_ptr,
|
||||
buffer,
|
||||
allow_skip,
|
||||
|len| {
|
||||
weight_per_byte.map(|w| RuntimeCosts::ChainExtension(w.saturating_mul(len.into())))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual data of an `Environment`.
|
||||
///
|
||||
/// All data is put into this struct to easily pass it around as part of the typestate
|
||||
/// pattern. Also it creates the opportunity to box this struct in the future in case it
|
||||
/// gets too large.
|
||||
struct Inner<'a, 'b, E: Ext> {
|
||||
/// The runtime contains all necessary functions to interact with the running contract.
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
/// Reference to the contracts memory.
|
||||
memory: &'a mut [u8],
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
id: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
input_ptr: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
input_len: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
output_ptr: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
output_len_ptr: u32,
|
||||
}
|
||||
|
||||
/// Any state of an [`Environment`] implements this trait.
|
||||
/// See [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html).
|
||||
pub trait State: sealed::Sealed {}
|
||||
|
||||
/// A state that uses primitive inputs.
|
||||
pub trait PrimIn: State {}
|
||||
|
||||
/// A state that uses primitive outputs.
|
||||
pub trait PrimOut: State {}
|
||||
|
||||
/// A state that uses a buffer as input.
|
||||
pub trait BufIn: State {}
|
||||
|
||||
/// A state that uses a buffer as output.
|
||||
pub trait BufOut: State {}
|
||||
|
||||
/// The initial state of an [`Environment`].
|
||||
pub enum InitState {}
|
||||
|
||||
/// A state that uses all arguments as primitive inputs.
|
||||
pub enum OnlyInState {}
|
||||
|
||||
/// A state that uses two arguments as primitive inputs and the other two as buffer output.
|
||||
pub enum PrimInBufOutState {}
|
||||
|
||||
/// Uses a buffer for input and a buffer for output.
|
||||
pub enum BufInBufOutState {}
|
||||
|
||||
mod sealed {
|
||||
use super::*;
|
||||
|
||||
/// Trait to prevent users from implementing `State` for anything else.
|
||||
pub trait Sealed {}
|
||||
|
||||
impl Sealed for InitState {}
|
||||
impl Sealed for OnlyInState {}
|
||||
impl Sealed for PrimInBufOutState {}
|
||||
impl Sealed for BufInBufOutState {}
|
||||
|
||||
impl State for InitState {}
|
||||
impl State for OnlyInState {}
|
||||
impl State for PrimInBufOutState {}
|
||||
impl State for BufInBufOutState {}
|
||||
|
||||
impl PrimIn for OnlyInState {}
|
||||
impl PrimOut for OnlyInState {}
|
||||
impl PrimIn for PrimInBufOutState {}
|
||||
impl BufOut for PrimInBufOutState {}
|
||||
impl BufIn for BufInBufOutState {}
|
||||
impl BufOut for BufInBufOutState {}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub use crate::{
|
||||
exec::{ExecResult, ExportedFunction},
|
||||
primitives::ExecReturnValue,
|
||||
};
|
||||
use crate::{Config, LOG_TARGET};
|
||||
|
||||
/// Umbrella trait for all interfaces that serves for debugging.
|
||||
pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {}
|
||||
|
||||
impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {}
|
||||
|
||||
/// Defines methods to capture contract calls, enabling external observers to
|
||||
/// measure, trace, and react to contract interactions.
|
||||
pub trait Tracing<T: Config> {
|
||||
/// The type of [`CallSpan`] that is created by this trait.
|
||||
type CallSpan: CallSpan;
|
||||
|
||||
/// Creates a new call span to encompass the upcoming contract execution.
|
||||
///
|
||||
/// This method should be invoked just before the execution of a contract and
|
||||
/// marks the beginning of a traceable span of execution.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `contract_address` - The address of the contract that is about to be executed.
|
||||
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
|
||||
/// * `input_data` - The raw input data of the call.
|
||||
fn new_call_span(
|
||||
contract_address: &T::AccountId,
|
||||
entry_point: ExportedFunction,
|
||||
input_data: &[u8],
|
||||
) -> Self::CallSpan;
|
||||
}
|
||||
|
||||
/// Defines a span of execution for a contract call.
|
||||
pub trait CallSpan {
|
||||
/// Called just after the execution of a contract.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `output` - The raw output of the call.
|
||||
fn after_call(self, output: &ExecReturnValue);
|
||||
}
|
||||
|
||||
impl<T: Config> Tracing<T> for () {
|
||||
type CallSpan = ();
|
||||
|
||||
fn new_call_span(
|
||||
contract_address: &T::AccountId,
|
||||
entry_point: ExportedFunction,
|
||||
input_data: &[u8],
|
||||
) {
|
||||
log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl CallSpan for () {
|
||||
fn after_call(self, output: &ExecReturnValue) {
|
||||
log::trace!(target: LOG_TARGET, "call result {output:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an interface for intercepting contract calls.
|
||||
pub trait CallInterceptor<T: Config> {
|
||||
/// Allows to intercept contract calls and decide whether they should be executed or not.
|
||||
/// If the call is intercepted, the mocked result of the call is returned.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `contract_address` - The address of the contract that is about to be executed.
|
||||
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
|
||||
/// * `input_data` - The raw input data of the call.
|
||||
///
|
||||
/// # Expected behavior
|
||||
///
|
||||
/// This method should return:
|
||||
/// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call
|
||||
/// is returned.
|
||||
/// * `None` - otherwise, i.e. the call should be executed normally.
|
||||
fn intercept_call(
|
||||
contract_address: &T::AccountId,
|
||||
entry_point: &ExportedFunction,
|
||||
input_data: &[u8],
|
||||
) -> Option<ExecResult>;
|
||||
}
|
||||
|
||||
impl<T: Config> CallInterceptor<T> for () {
|
||||
fn intercept_call(
|
||||
_contract_address: &T::AccountId,
|
||||
_entry_point: &ExportedFunction,
|
||||
_input_data: &[u8],
|
||||
) -> Option<ExecResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,399 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{exec::ExecError, Config, Error};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo},
|
||||
weights::Weight,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use pezsp_core::Get;
|
||||
use pezsp_runtime::{traits::Zero, DispatchError};
|
||||
|
||||
#[cfg(test)]
|
||||
use std::{any::Any, fmt::Debug};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ChargedAmount(Weight);
|
||||
|
||||
impl ChargedAmount {
|
||||
pub fn amount(&self) -> Weight {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Meter for syncing the gas between the executor and the gas meter.
|
||||
#[derive(DefaultNoBound)]
|
||||
struct EngineMeter<T: Config> {
|
||||
fuel: u64,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> EngineMeter<T> {
|
||||
/// Create a meter with the given fuel limit.
|
||||
fn new(limit: Weight) -> Self {
|
||||
Self {
|
||||
fuel: limit.ref_time().saturating_div(T::Schedule::get().ref_time_by_fuel()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the fuel left to the given value.
|
||||
/// Returns the amount of Weight consumed since the last update.
|
||||
fn set_fuel(&mut self, fuel: u64) -> Weight {
|
||||
let consumed = self
|
||||
.fuel
|
||||
.saturating_sub(fuel)
|
||||
.saturating_mul(T::Schedule::get().ref_time_by_fuel());
|
||||
self.fuel = fuel;
|
||||
Weight::from_parts(consumed, 0)
|
||||
}
|
||||
|
||||
/// Charge the given amount of gas.
|
||||
/// Returns the amount of fuel left.
|
||||
fn charge_ref_time(&mut self, ref_time: u64) -> Result<Syncable, DispatchError> {
|
||||
let amount = ref_time
|
||||
.checked_div(T::Schedule::get().ref_time_by_fuel())
|
||||
.ok_or(Error::<T>::InvalidSchedule)?;
|
||||
|
||||
self.fuel.checked_sub(amount).ok_or_else(|| Error::<T>::OutOfGas)?;
|
||||
Ok(Syncable(self.fuel))
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to capture the gas left before entering a host function.
|
||||
///
|
||||
/// Has to be consumed in order to sync back the gas after leaving the host function.
|
||||
#[must_use]
|
||||
pub struct RefTimeLeft(u64);
|
||||
|
||||
/// Resource that needs to be synced to the executor.
|
||||
///
|
||||
/// Wrapped to make sure that the resource will be synced back the the executor.
|
||||
#[must_use]
|
||||
pub struct Syncable(u64);
|
||||
|
||||
impl From<Syncable> for u64 {
|
||||
fn from(from: Syncable) -> u64 {
|
||||
from.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub trait TestAuxiliaries {}
|
||||
#[cfg(not(test))]
|
||||
impl<T> TestAuxiliaries for T {}
|
||||
|
||||
#[cfg(test)]
|
||||
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
|
||||
#[cfg(test)]
|
||||
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
|
||||
|
||||
/// This trait represents a token that can be used for charging `GasMeter`.
|
||||
/// There is no other way of charging it.
|
||||
///
|
||||
/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
|
||||
/// for consistency). If inlined there should be no observable difference compared
|
||||
/// to a hand-written code.
|
||||
pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
|
||||
/// Return the amount of gas that should be taken by this token.
|
||||
///
|
||||
/// This function should be really lightweight and must not fail. It is not
|
||||
/// expected that implementors will query the storage or do any kinds of heavy operations.
|
||||
///
|
||||
/// That said, implementors of this function still can run into overflows
|
||||
/// while calculating the amount. In this case it is ok to use saturating operations
|
||||
/// since on overflow they will return `max_value` which should consume all gas.
|
||||
fn weight(&self) -> Weight;
|
||||
|
||||
/// Returns true if this token is expected to influence the lowest gas limit.
|
||||
fn influence_lowest_gas_limit(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a type-erased trait object of what used to be a `Token`.
|
||||
#[cfg(test)]
|
||||
pub struct ErasedToken {
|
||||
pub description: String,
|
||||
pub token: Box<dyn Any>,
|
||||
}
|
||||
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GasMeter<T: Config> {
|
||||
gas_limit: Weight,
|
||||
/// Amount of gas left from initial gas limit. Can reach zero.
|
||||
gas_left: Weight,
|
||||
/// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value.
|
||||
gas_left_lowest: Weight,
|
||||
/// The amount of resources that was consumed by the execution engine.
|
||||
/// We have to track it separately in order to avoid the loss of precision that happens when
|
||||
/// converting from ref_time to the execution engine unit.
|
||||
engine_meter: EngineMeter<T>,
|
||||
_phantom: PhantomData<T>,
|
||||
#[cfg(test)]
|
||||
tokens: Vec<ErasedToken>,
|
||||
}
|
||||
|
||||
impl<T: Config> GasMeter<T> {
|
||||
pub fn new(gas_limit: Weight) -> Self {
|
||||
GasMeter {
|
||||
gas_limit,
|
||||
gas_left: gas_limit,
|
||||
gas_left_lowest: gas_limit,
|
||||
engine_meter: EngineMeter::new(gas_limit),
|
||||
_phantom: PhantomData,
|
||||
#[cfg(test)]
|
||||
tokens: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new gas meter by removing gas from the current meter.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Passing `0` as amount is interpreted as "all remaining gas".
|
||||
pub fn nested(&mut self, amount: Weight) -> Self {
|
||||
let amount = Weight::from_parts(
|
||||
if amount.ref_time().is_zero() {
|
||||
self.gas_left().ref_time()
|
||||
} else {
|
||||
amount.ref_time()
|
||||
},
|
||||
if amount.proof_size().is_zero() {
|
||||
self.gas_left().proof_size()
|
||||
} else {
|
||||
amount.proof_size()
|
||||
},
|
||||
)
|
||||
.min(self.gas_left);
|
||||
self.gas_left -= amount;
|
||||
GasMeter::new(amount)
|
||||
}
|
||||
|
||||
/// Absorb the remaining gas of a nested meter after we are done using it.
|
||||
pub fn absorb_nested(&mut self, nested: Self) {
|
||||
self.gas_left_lowest = (self.gas_left + nested.gas_limit)
|
||||
.saturating_sub(nested.gas_required())
|
||||
.min(self.gas_left_lowest);
|
||||
self.gas_left += nested.gas_left;
|
||||
}
|
||||
|
||||
/// Account for used gas.
|
||||
///
|
||||
/// Amount is calculated by the given `token`.
|
||||
///
|
||||
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
|
||||
/// amount of gas has lead to overflow.
|
||||
///
|
||||
/// NOTE that amount isn't consumed if there is not enough gas. This is considered
|
||||
/// safe because we always charge gas before performing any resource-spending action.
|
||||
#[inline]
|
||||
pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
// Unconditionally add the token to the storage.
|
||||
let erased_tok =
|
||||
ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
|
||||
self.tokens.push(erased_tok);
|
||||
}
|
||||
let amount = token.weight();
|
||||
// It is OK to not charge anything on failure because we always charge _before_ we perform
|
||||
// any action
|
||||
self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::<T>::OutOfGas)?;
|
||||
Ok(ChargedAmount(amount))
|
||||
}
|
||||
|
||||
/// Adjust a previously charged amount down to its actual amount.
|
||||
///
|
||||
/// This is when a maximum a priori amount was charged and then should be partially
|
||||
/// refunded to match the actual amount.
|
||||
pub fn adjust_gas<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
|
||||
if token.influence_lowest_gas_limit() {
|
||||
self.gas_left_lowest = self.gas_left_lowest();
|
||||
}
|
||||
let adjustment = charged_amount.0.saturating_sub(token.weight());
|
||||
self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
|
||||
}
|
||||
|
||||
/// Hand over the gas metering responsibility from the executor to this meter.
|
||||
///
|
||||
/// Needs to be called when entering a host function to update this meter with the
|
||||
/// gas that was tracked by the executor. It tracks the latest seen total value
|
||||
/// in order to compute the delta that needs to be charged.
|
||||
pub fn sync_from_executor(&mut self, engine_fuel: u64) -> Result<RefTimeLeft, DispatchError> {
|
||||
let weight_consumed = self.engine_meter.set_fuel(engine_fuel);
|
||||
self.gas_left
|
||||
.checked_reduce(weight_consumed)
|
||||
.ok_or_else(|| Error::<T>::OutOfGas)?;
|
||||
Ok(RefTimeLeft(self.gas_left.ref_time()))
|
||||
}
|
||||
|
||||
/// Hand over the gas metering responsibility from this meter to the executor.
|
||||
///
|
||||
/// Needs to be called when leaving a host function in order to calculate how much
|
||||
/// gas needs to be charged from the **executor**. It updates the last seen executor
|
||||
/// total value so that it is correct when `sync_from_executor` is called the next time.
|
||||
///
|
||||
/// It is important that this does **not** actually sync with the executor. That has
|
||||
/// to be done by the caller.
|
||||
pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result<Syncable, DispatchError> {
|
||||
let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time());
|
||||
self.engine_meter.charge_ref_time(ref_time_consumed)
|
||||
}
|
||||
|
||||
/// Returns the amount of gas that is required to run the same call.
|
||||
///
|
||||
/// This can be different from `gas_spent` because due to `adjust_gas` the amount of
|
||||
/// spent gas can temporarily drop and be refunded later.
|
||||
pub fn gas_required(&self) -> Weight {
|
||||
self.gas_limit.saturating_sub(self.gas_left_lowest())
|
||||
}
|
||||
|
||||
/// Returns how much gas was spent
|
||||
pub fn gas_consumed(&self) -> Weight {
|
||||
self.gas_limit.saturating_sub(self.gas_left)
|
||||
}
|
||||
|
||||
/// Returns how much gas left from the initial budget.
|
||||
pub fn gas_left(&self) -> Weight {
|
||||
self.gas_left
|
||||
}
|
||||
|
||||
/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
|
||||
pub fn into_dispatch_result<R, E>(
|
||||
self,
|
||||
result: Result<R, E>,
|
||||
base_weight: Weight,
|
||||
) -> DispatchResultWithPostInfo
|
||||
where
|
||||
E: Into<ExecError>,
|
||||
{
|
||||
let post_info = PostDispatchInfo {
|
||||
actual_weight: Some(self.gas_consumed().saturating_add(base_weight)),
|
||||
pays_fee: Default::default(),
|
||||
};
|
||||
|
||||
result
|
||||
.map(|_| post_info)
|
||||
.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
|
||||
}
|
||||
|
||||
fn gas_left_lowest(&self) -> Weight {
|
||||
self.gas_left_lowest.min(self.gas_left)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn tokens(&self) -> &[ErasedToken] {
|
||||
&self.tokens
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{GasMeter, Token, Weight};
|
||||
use crate::tests::Test;
|
||||
|
||||
/// A simple utility macro that helps to match against a
|
||||
/// list of tokens.
|
||||
macro_rules! match_tokens {
|
||||
($tokens_iter:ident,) => {
|
||||
};
|
||||
($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
|
||||
{
|
||||
let next = ($tokens_iter).next().unwrap();
|
||||
let pattern = $x;
|
||||
|
||||
// Note that we don't specify the type name directly in this macro,
|
||||
// we only have some expression $x of some type. At the same time, we
|
||||
// have an iterator of Box<dyn Any> and to downcast we need to specify
|
||||
// the type which we want downcast to.
|
||||
//
|
||||
// So what we do is we assign `_pattern_typed_next_ref` to a variable which has
|
||||
// the required type.
|
||||
//
|
||||
// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
|
||||
// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
|
||||
|
||||
let mut _pattern_typed_next_ref = &pattern;
|
||||
_pattern_typed_next_ref = match next.token.downcast_ref() {
|
||||
Some(p) => {
|
||||
assert_eq!(p, &pattern);
|
||||
p
|
||||
}
|
||||
None => {
|
||||
panic!("expected type {} got {}", stringify!($x), next.description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match_tokens!($tokens_iter, $($rest)*);
|
||||
};
|
||||
}
|
||||
|
||||
/// A trivial token that charges the specified number of gas units.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct SimpleToken(u64);
|
||||
impl Token<Test> for SimpleToken {
|
||||
fn weight(&self) -> Weight {
|
||||
Weight::from_parts(self.0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
|
||||
assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracing() {
|
||||
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
|
||||
assert!(!gas_meter.charge(SimpleToken(1)).is_err());
|
||||
|
||||
let mut tokens = gas_meter.tokens().iter();
|
||||
match_tokens!(tokens, SimpleToken(1),);
|
||||
}
|
||||
|
||||
// This test makes sure that nothing can be executed if there is no gas.
|
||||
#[test]
|
||||
fn refuse_to_execute_anything_if_zero() {
|
||||
let mut gas_meter = GasMeter::<Test>::new(Weight::zero());
|
||||
assert!(gas_meter.charge(SimpleToken(1)).is_err());
|
||||
}
|
||||
|
||||
// Make sure that the gas meter does not charge in case of overcharge
|
||||
#[test]
|
||||
fn overcharge_does_not_charge() {
|
||||
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(200, 0));
|
||||
|
||||
// The first charge is should lead to OOG.
|
||||
assert!(gas_meter.charge(SimpleToken(300)).is_err());
|
||||
|
||||
// The gas meter should still contain the full 200.
|
||||
assert!(gas_meter.charge(SimpleToken(200)).is_ok());
|
||||
}
|
||||
|
||||
// Charging the exact amount that the user paid for should be
|
||||
// possible.
|
||||
#[test]
|
||||
fn charge_exact_amount() {
|
||||
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(25, 0));
|
||||
assert!(!gas_meter.charge(SimpleToken(25)).is_err());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,658 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Multi-block Migration framework for pezpallet-contracts.
|
||||
//!
|
||||
//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be
|
||||
//! executed across multiple blocks.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number.
|
||||
//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`.
|
||||
//!
|
||||
//! ## Example:
|
||||
//!
|
||||
//! To configure a migration to `v11` for a runtime using `v10` of pezpallet-contracts on the chain,
|
||||
//! you would set the `Migrations` type as follows:
|
||||
//!
|
||||
//! ```
|
||||
//! use pezpallet_contracts::migration::{v10, v11};
|
||||
//! # pub enum Runtime {};
|
||||
//! # struct Currency;
|
||||
//! type Migrations = (v10::Migration<Runtime, Currency>, v11::Migration<Runtime>);
|
||||
//! ```
|
||||
//!
|
||||
//! ## Notes:
|
||||
//!
|
||||
//! - Migrations should always be tested with `try-runtime` before being deployed.
|
||||
//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work
|
||||
//! and that you have included the required steps.
|
||||
//!
|
||||
//! ## Low Level / Implementation Details
|
||||
//!
|
||||
//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of
|
||||
//! performing the actual migration, we set a custom storage item [`MigrationInProgress`].
|
||||
//! This storage item defines a [`Cursor`] for the current migration.
|
||||
//!
|
||||
//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its
|
||||
//! value holds a cursor for the current migration step. These migration steps are executed during
|
||||
//! [`Hooks<BlockNumber>::on_idle`] or when the [`Pallet::migrate`] dispatchable is
|
||||
//! called.
|
||||
//!
|
||||
//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns
|
||||
//! a `MigrationInProgress` error.
|
||||
|
||||
pub mod v09;
|
||||
pub mod v10;
|
||||
pub mod v11;
|
||||
pub mod v12;
|
||||
pub mod v13;
|
||||
pub mod v14;
|
||||
pub mod v15;
|
||||
pub mod v16;
|
||||
include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs"));
|
||||
|
||||
use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET};
|
||||
use codec::{Codec, Decode};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{ConstU32, OnRuntimeUpgrade},
|
||||
weights::WeightMeter,
|
||||
};
|
||||
use pezsp_runtime::Saturating;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed";
|
||||
const PROOF_DECODE: &str =
|
||||
"We encode to the same type in this trait only. No other code touches this item; qed";
|
||||
|
||||
fn invalid_version(version: StorageVersion) -> ! {
|
||||
panic!("Required migration {version:?} not supported by this runtime. This is a bug.");
|
||||
}
|
||||
|
||||
/// The cursor used to encode the position (usually the last iterated key) of the current migration
|
||||
/// step.
|
||||
pub type Cursor = BoundedVec<u8, ConstU32<1024>>;
|
||||
|
||||
/// IsFinished describes whether a migration is finished or not.
|
||||
pub enum IsFinished {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// A trait that allows to migrate storage from one version to another.
|
||||
///
|
||||
/// The migration is done in steps. The migration is finished when
|
||||
/// `step()` returns `IsFinished::Yes`.
|
||||
pub trait MigrationStep: Codec + MaxEncodedLen + Default {
|
||||
/// Returns the version of the migration.
|
||||
const VERSION: u16;
|
||||
|
||||
/// Returns the maximum weight that can be consumed in a single step.
|
||||
fn max_step_weight() -> Weight;
|
||||
|
||||
/// Process one step of the migration.
|
||||
///
|
||||
/// Returns whether the migration is finished.
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished;
|
||||
|
||||
/// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater
|
||||
/// than `max_block_weight`.
|
||||
fn integrity_test(max_block_weight: Weight) {
|
||||
if Self::max_step_weight().any_gt(max_block_weight) {
|
||||
panic!(
|
||||
"Invalid max_step_weight for Migration {}. Value should be lower than {}",
|
||||
Self::VERSION,
|
||||
max_block_weight
|
||||
);
|
||||
}
|
||||
|
||||
let len = <Self as MaxEncodedLen>::max_encoded_len();
|
||||
let max = Cursor::bound();
|
||||
if len > max {
|
||||
panic!(
|
||||
"Migration {} has size {} which is bigger than the maximum of {}",
|
||||
Self::VERSION,
|
||||
len,
|
||||
max,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute some pre-checks prior to running the first step of this migration.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Execute some post-checks after running the last step of this migration.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A noop migration that can be used when there is no migration to be done for a given version.
|
||||
#[doc(hidden)]
|
||||
#[derive(pezframe_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)]
|
||||
pub struct NoopMigration<const N: u16>;
|
||||
|
||||
impl<const N: u16> MigrationStep for NoopMigration<N> {
|
||||
const VERSION: u16 = N;
|
||||
fn max_step_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished {
|
||||
log::debug!(target: LOG_TARGET, "Noop migration for version {}", N);
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
use crate::migration::MigrationStep;
|
||||
pub trait Sealed {}
|
||||
#[impl_trait_for_tuples::impl_for_tuples(10)]
|
||||
#[tuple_types_custom_trait_bound(MigrationStep)]
|
||||
impl Sealed for Tuple {}
|
||||
}
|
||||
|
||||
/// Defines a sequence of migrations.
|
||||
///
|
||||
/// The sequence must be defined by a tuple of migrations, each of which must implement the
|
||||
/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps.
|
||||
pub trait MigrateSequence: private::Sealed {
|
||||
/// Returns the range of versions that this migrations sequence can handle.
|
||||
/// Migrations must be ordered by their versions with no gaps.
|
||||
///
|
||||
/// The following code will fail to compile:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use pezpallet_contracts::{NoopMigration, MigrateSequence};
|
||||
/// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE;
|
||||
/// ```
|
||||
/// The following code will compile:
|
||||
/// ```
|
||||
/// # use pezpallet_contracts::{NoopMigration, MigrateSequence};
|
||||
/// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE;
|
||||
/// ```
|
||||
const VERSION_RANGE: (u16, u16);
|
||||
|
||||
/// Returns the default cursor for the given version.
|
||||
fn new(version: StorageVersion) -> Cursor;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step(_version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(_version: StorageVersion, _state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the migration step until the available weight is consumed.
|
||||
fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult;
|
||||
|
||||
/// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater
|
||||
/// than `max_block_weight`.
|
||||
fn integrity_test(max_block_weight: Weight);
|
||||
|
||||
/// Returns whether migrating from `in_storage` to `target` is supported.
|
||||
///
|
||||
/// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target).
|
||||
fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool {
|
||||
let (low, high) = Self::VERSION_RANGE;
|
||||
target == high && in_storage + 1 == low
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs all necessary migrations based on `StorageVersion`.
|
||||
///
|
||||
/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations
|
||||
/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step
|
||||
/// by step migration works.
|
||||
pub struct Migration<T: Config, const TEST_ALL_STEPS: bool = true>(PhantomData<T>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
|
||||
fn run_all_steps() -> Result<(), TryRuntimeError> {
|
||||
let mut meter = &mut WeightMeter::new();
|
||||
let name = <Pallet<T>>::name();
|
||||
loop {
|
||||
let in_progress_version = <Pallet<T>>::on_chain_storage_version() + 1;
|
||||
let state = T::Migrations::pre_upgrade_step(in_progress_version)?;
|
||||
let before = meter.consumed();
|
||||
let status = Self::migrate(&mut meter);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: Migration step {:?} weight = {}",
|
||||
in_progress_version,
|
||||
meter.consumed() - before
|
||||
);
|
||||
T::Migrations::post_upgrade_step(in_progress_version, state)?;
|
||||
if matches!(status, MigrateResult::Completed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let name = <Pallet<T>>::name();
|
||||
log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, const TEST_ALL_STEPS: bool> OnRuntimeUpgrade for Migration<T, TEST_ALL_STEPS> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let name = <Pallet<T>>::name();
|
||||
let in_code_version = <Pallet<T>>::in_code_storage_version();
|
||||
let on_chain_version = <Pallet<T>>::on_chain_storage_version();
|
||||
|
||||
if on_chain_version == in_code_version {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: No Migration performed storage_version = latest_version = {:?}",
|
||||
&on_chain_version
|
||||
);
|
||||
return T::WeightInfo::on_runtime_upgrade_noop();
|
||||
}
|
||||
|
||||
// In case a migration is already in progress we create the next migration
|
||||
// (if any) right when the current one finishes.
|
||||
if Self::in_progress() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: Migration already in progress {:?}",
|
||||
&on_chain_version
|
||||
);
|
||||
|
||||
return T::WeightInfo::on_runtime_upgrade_in_progress();
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.",
|
||||
);
|
||||
|
||||
let cursor = T::Migrations::new(on_chain_version + 1);
|
||||
MigrationInProgress::<T>::set(Some(cursor));
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
if TEST_ALL_STEPS {
|
||||
Self::run_all_steps().unwrap();
|
||||
}
|
||||
|
||||
T::WeightInfo::on_runtime_upgrade()
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
// We can't really do much here as our migrations do not happen during the runtime upgrade.
|
||||
// Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate
|
||||
// over our migrations.
|
||||
let on_chain_version = <Pallet<T>>::on_chain_storage_version();
|
||||
let in_code_version = <Pallet<T>>::in_code_storage_version();
|
||||
|
||||
if on_chain_version == in_code_version {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)",
|
||||
<Pallet<T>>::name(), on_chain_version, in_code_version
|
||||
);
|
||||
|
||||
ensure!(
|
||||
T::Migrations::is_upgrade_supported(on_chain_version, in_code_version),
|
||||
"Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)"
|
||||
);
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
if !TEST_ALL_STEPS {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ===");
|
||||
|
||||
// Ensure that the hashing algorithm is correct for each storage map.
|
||||
if let Some(hash) = crate::CodeInfoOf::<T>::iter_keys().next() {
|
||||
crate::CodeInfoOf::<T>::get(hash).expect("CodeInfo exists for hash; qed");
|
||||
}
|
||||
if let Some(hash) = crate::PristineCode::<T>::iter_keys().next() {
|
||||
crate::PristineCode::<T>::get(hash).expect("PristineCode exists for hash; qed");
|
||||
}
|
||||
if let Some(account_id) = crate::ContractInfoOf::<T>::iter_keys().next() {
|
||||
crate::ContractInfoOf::<T>::get(account_id)
|
||||
.expect("ContractInfo exists for account_id; qed");
|
||||
}
|
||||
if let Some(nonce) = crate::DeletionQueue::<T>::iter_keys().next() {
|
||||
crate::DeletionQueue::<T>::get(nonce).expect("DeletionQueue exists for nonce; qed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of running the migration.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum MigrateResult {
|
||||
/// No migration was performed
|
||||
NoMigrationPerformed,
|
||||
/// No migration currently in progress
|
||||
NoMigrationInProgress,
|
||||
/// A migration is in progress
|
||||
InProgress { steps_done: u32 },
|
||||
/// All migrations are completed
|
||||
Completed,
|
||||
}
|
||||
|
||||
/// The result of running a migration step.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum StepResult {
|
||||
InProgress { cursor: Cursor, steps_done: u32 },
|
||||
Completed { steps_done: u32 },
|
||||
}
|
||||
|
||||
impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
|
||||
/// Verify that each migration's step of the [`Config::Migrations`] sequence fits into
|
||||
/// `Cursor`.
|
||||
pub(crate) fn integrity_test() {
|
||||
let max_weight = <T as pezframe_system::Config>::BlockWeights::get().max_block;
|
||||
T::Migrations::integrity_test(max_weight)
|
||||
}
|
||||
|
||||
/// Execute the multi-step migration.
|
||||
/// Returns whether or not a migration is in progress
|
||||
pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult {
|
||||
let name = <Pallet<T>>::name();
|
||||
|
||||
if meter.try_consume(T::WeightInfo::migrate()).is_err() {
|
||||
return MigrateResult::NoMigrationPerformed;
|
||||
}
|
||||
|
||||
MigrationInProgress::<T>::mutate_exists(|progress| {
|
||||
let Some(cursor_before) = progress.as_mut() else {
|
||||
meter.consume(T::WeightInfo::migration_noop());
|
||||
return MigrateResult::NoMigrationInProgress;
|
||||
};
|
||||
|
||||
// if a migration is running it is always upgrading to the next version
|
||||
let storage_version = <Pallet<T>>::on_chain_storage_version();
|
||||
let in_progress_version = storage_version + 1;
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: Migrating from {:?} to {:?},",
|
||||
storage_version,
|
||||
in_progress_version,
|
||||
);
|
||||
|
||||
let result =
|
||||
match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter)
|
||||
{
|
||||
StepResult::InProgress { cursor, steps_done } => {
|
||||
*progress = Some(cursor);
|
||||
MigrateResult::InProgress { steps_done }
|
||||
},
|
||||
StepResult::Completed { steps_done } => {
|
||||
in_progress_version.put::<Pallet<T>>();
|
||||
if <Pallet<T>>::in_code_storage_version() != in_progress_version {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: Next migration is {:?},",
|
||||
in_progress_version + 1
|
||||
);
|
||||
*progress = Some(T::Migrations::new(in_progress_version + 1));
|
||||
MigrateResult::InProgress { steps_done }
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{name}: All migrations done. At version {:?},",
|
||||
in_progress_version
|
||||
);
|
||||
*progress = None;
|
||||
MigrateResult::Completed
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_migrated() -> DispatchResult {
|
||||
if Self::in_progress() {
|
||||
Err(Error::<T>::MigrationInProgress.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn in_progress() -> bool {
|
||||
MigrationInProgress::<T>::exists()
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(10)]
|
||||
#[tuple_types_custom_trait_bound(MigrationStep)]
|
||||
impl MigrateSequence for Tuple {
|
||||
const VERSION_RANGE: (u16, u16) = {
|
||||
let mut versions: (u16, u16) = (0, 0);
|
||||
for_tuples!(
|
||||
#(
|
||||
match versions {
|
||||
(0, 0) => {
|
||||
versions = (Tuple::VERSION, Tuple::VERSION);
|
||||
},
|
||||
(min_version, last_version) if Tuple::VERSION == last_version + 1 => {
|
||||
versions = (min_version, Tuple::VERSION);
|
||||
},
|
||||
_ => panic!("Migrations must be ordered by their versions with no gaps.")
|
||||
}
|
||||
)*
|
||||
);
|
||||
versions
|
||||
};
|
||||
|
||||
fn new(version: StorageVersion) -> Cursor {
|
||||
for_tuples!(
|
||||
#(
|
||||
if version == Tuple::VERSION {
|
||||
return Tuple::default().encode().try_into().expect(PROOF_ENCODE)
|
||||
}
|
||||
)*
|
||||
);
|
||||
invalid_version(version)
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
/// Execute the pre-checks of the step associated with this version.
|
||||
fn pre_upgrade_step(version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
|
||||
for_tuples!(
|
||||
#(
|
||||
if version == Tuple::VERSION {
|
||||
return Tuple::pre_upgrade_step()
|
||||
}
|
||||
)*
|
||||
);
|
||||
invalid_version(version)
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
/// Execute the post-checks of the step associated with this version.
|
||||
fn post_upgrade_step(version: StorageVersion, state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
for_tuples!(
|
||||
#(
|
||||
if version == Tuple::VERSION {
|
||||
return Tuple::post_upgrade_step(state)
|
||||
}
|
||||
)*
|
||||
);
|
||||
invalid_version(version)
|
||||
}
|
||||
|
||||
fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult {
|
||||
for_tuples!(
|
||||
#(
|
||||
if version == Tuple::VERSION {
|
||||
let mut migration = <Tuple as Decode>::decode(&mut cursor)
|
||||
.expect(PROOF_DECODE);
|
||||
let max_weight = Tuple::max_step_weight();
|
||||
let mut steps_done = 0;
|
||||
while meter.can_consume(max_weight) {
|
||||
steps_done.saturating_accrue(1);
|
||||
if matches!(migration.step(meter), IsFinished::Yes) {
|
||||
return StepResult::Completed{ steps_done }
|
||||
}
|
||||
}
|
||||
return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done }
|
||||
}
|
||||
)*
|
||||
);
|
||||
invalid_version(version)
|
||||
}
|
||||
|
||||
fn integrity_test(max_block_weight: Weight) {
|
||||
for_tuples!(
|
||||
#(
|
||||
Tuple::integrity_test(max_block_weight);
|
||||
)*
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
migration::codegen::LATEST_MIGRATION_VERSION,
|
||||
tests::{ExtBuilder, Test},
|
||||
};
|
||||
|
||||
#[derive(Default, Encode, Decode, MaxEncodedLen)]
|
||||
struct MockMigration<const N: u16> {
|
||||
// MockMigration<N> needs `N` steps to finish
|
||||
count: u16,
|
||||
}
|
||||
|
||||
impl<const N: u16> MigrationStep for MockMigration<N> {
|
||||
const VERSION: u16 = N;
|
||||
fn max_step_weight() -> Weight {
|
||||
Weight::from_all(1)
|
||||
}
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
assert!(self.count != N);
|
||||
self.count += 1;
|
||||
meter.consume(Weight::from_all(1));
|
||||
if self.count == N {
|
||||
IsFinished::Yes
|
||||
} else {
|
||||
IsFinished::No
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_version_matches_last_migration_file() {
|
||||
assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_range_works() {
|
||||
let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE;
|
||||
assert_eq!(range, (1, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_upgrade_supported_works() {
|
||||
type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>);
|
||||
assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11)));
|
||||
assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11)));
|
||||
assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn steps_works() {
|
||||
type Migrations = (MockMigration<2>, MockMigration<3>);
|
||||
let version = StorageVersion::new(2);
|
||||
let mut cursor = Migrations::new(version);
|
||||
|
||||
let mut meter = WeightMeter::with_limit(Weight::from_all(1));
|
||||
let result = Migrations::steps(version, &cursor, &mut meter);
|
||||
cursor = alloc::vec![1u8, 0].try_into().unwrap();
|
||||
assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 });
|
||||
assert_eq!(meter.consumed(), Weight::from_all(1));
|
||||
|
||||
let mut meter = WeightMeter::with_limit(Weight::from_all(1));
|
||||
assert_eq!(
|
||||
Migrations::steps(version, &cursor, &mut meter),
|
||||
StepResult::Completed { steps_done: 1 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_migration_in_progress_works() {
|
||||
type TestMigration = Migration<Test>;
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
|
||||
assert_eq!(
|
||||
TestMigration::migrate(&mut WeightMeter::new()),
|
||||
MigrateResult::NoMigrationInProgress
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_works() {
|
||||
type TestMigration = Migration<Test, false>;
|
||||
|
||||
ExtBuilder::default()
|
||||
.set_storage_version(LATEST_MIGRATION_VERSION - 2)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION - 2);
|
||||
TestMigration::on_runtime_upgrade();
|
||||
for (version, status) in [
|
||||
(LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }),
|
||||
(LATEST_MIGRATION_VERSION, MigrateResult::Completed),
|
||||
] {
|
||||
assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status);
|
||||
assert_eq!(
|
||||
<Pallet<Test>>::on_chain_storage_version(),
|
||||
StorageVersion::new(version)
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
TestMigration::migrate(&mut WeightMeter::new()),
|
||||
MigrateResult::NoMigrationInProgress
|
||||
);
|
||||
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Update `CodeStorage` with the new `determinism` field.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound, Identity,
|
||||
};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
mod v8 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeStorage<T: Config> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_dummy_code<T: Config>(len: usize) {
|
||||
use pezsp_runtime::traits::Hash;
|
||||
let module = v8::PrefabWasmModule {
|
||||
instruction_weights_version: 0,
|
||||
initial: 0,
|
||||
maximum: 0,
|
||||
code: alloc::vec![42u8; len],
|
||||
};
|
||||
let hash = T::Hashing::hash(&module.code);
|
||||
v8::CodeStorage::<T>::insert(hash, module);
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
pub determinism: Determinism,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_code_hash: Option<CodeHash<T>>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 9;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v9_migration_step(T::MaxCodeLen::get())
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
|
||||
v8::CodeStorage::<T>::iter_from(v8::CodeStorage::<T>::hashed_key_for(last_key))
|
||||
} else {
|
||||
v8::CodeStorage::<T>::iter()
|
||||
};
|
||||
|
||||
if let Some((key, old)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating contract code {:?}", key);
|
||||
let len = old.code.len() as u32;
|
||||
let module = PrefabWasmModule {
|
||||
instruction_weights_version: old.instruction_weights_version,
|
||||
initial: old.initial,
|
||||
maximum: old.maximum,
|
||||
code: old.code,
|
||||
determinism: Determinism::Enforced,
|
||||
};
|
||||
CodeStorage::<T>::insert(key, module);
|
||||
self.last_code_hash = Some(key);
|
||||
meter.consume(T::WeightInfo::v9_migration_step(len));
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more contracts code to migrate");
|
||||
meter.consume(T::WeightInfo::v9_migration_step(0));
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let sample: Vec<_> = v8::CodeStorage::<T>::iter().take(100).collect();
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Taking sample of {} contract codes", sample.len());
|
||||
Ok(sample.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let sample = <Vec<(CodeHash<T>, v8::PrefabWasmModule)> as Decode>::decode(&mut &state[..])
|
||||
.expect("pre_upgrade_step provides a valid state; qed");
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Validating sample of {} contract codes", sample.len());
|
||||
for (code_hash, old) in sample {
|
||||
let module = CodeStorage::<T>::get(&code_hash).unwrap();
|
||||
ensure!(
|
||||
module.instruction_weights_version == old.instruction_weights_version,
|
||||
"invalid instruction weights version"
|
||||
);
|
||||
ensure!(module.determinism == Determinism::Enforced, "invalid determinism");
|
||||
ensure!(module.initial == old.initial, "invalid initial");
|
||||
ensure!(module.maximum == old.maximum, "invalid maximum");
|
||||
ensure!(module.code == old.code, "invalid code");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Don't rely on reserved balances keeping an account alive
|
||||
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/44>.
|
||||
|
||||
use crate::{
|
||||
exec::AccountIdOf,
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use core::{
|
||||
cmp::{max, min},
|
||||
ops::Deref,
|
||||
};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
storage_alias,
|
||||
traits::{
|
||||
tokens::{fungible::Inspect, Fortitude::Polite, Preservation::Preserve},
|
||||
ExistenceRequirement, ReservableCurrency,
|
||||
},
|
||||
weights::WeightMeter,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use pezsp_core::hexdisplay::HexDisplay;
|
||||
use pezsp_runtime::{
|
||||
traits::{Hash, TrailingZeroInput, Zero},
|
||||
Perbill, Saturating,
|
||||
};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
mod v9 {
|
||||
use super::*;
|
||||
|
||||
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T, OldCurrency))]
|
||||
pub struct ContractInfo<T: Config, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
pub trie_id: TrieId,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T, OldCurrency>,
|
||||
pub storage_item_deposit: BalanceOf<T, OldCurrency>,
|
||||
pub storage_base_deposit: BalanceOf<T, OldCurrency>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
ContractInfo<T, OldCurrency>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_contract_info<T: Config, OldCurrency>(
|
||||
account: T::AccountId,
|
||||
info: crate::ContractInfo<T>,
|
||||
) where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
|
||||
{
|
||||
let info = v9::ContractInfo {
|
||||
trie_id: info.trie_id,
|
||||
code_hash: info.code_hash,
|
||||
storage_bytes: Default::default(),
|
||||
storage_items: Default::default(),
|
||||
storage_byte_deposit: Default::default(),
|
||||
storage_item_deposit: Default::default(),
|
||||
storage_base_deposit: Default::default(),
|
||||
};
|
||||
v9::ContractInfoOf::<T, OldCurrency>::insert(account, info);
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct DepositAccount<T: Config>(AccountIdOf<T>);
|
||||
|
||||
impl<T: Config> Deref for DepositAccount<T> {
|
||||
type Target = AccountIdOf<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T, OldCurrency))]
|
||||
pub struct ContractInfo<T: Config, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
pub trie_id: TrieId,
|
||||
deposit_account: DepositAccount<T>,
|
||||
pub code_hash: CodeHash<T>,
|
||||
storage_bytes: u32,
|
||||
storage_items: u32,
|
||||
pub storage_byte_deposit: v9::BalanceOf<T, OldCurrency>,
|
||||
storage_item_deposit: v9::BalanceOf<T, OldCurrency>,
|
||||
storage_base_deposit: v9::BalanceOf<T, OldCurrency>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config, OldCurrency = ()> {
|
||||
last_account: Option<T::AccountId>,
|
||||
_phantom: PhantomData<(T, OldCurrency)>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
ContractInfo<T, OldCurrency>,
|
||||
>;
|
||||
|
||||
/// Formula: `hash("contract_depo_v1" ++ contract_addr)`
|
||||
fn deposit_address<T: Config>(
|
||||
contract_addr: &<T as pezframe_system::Config>::AccountId,
|
||||
) -> <T as pezframe_system::Config>::AccountId {
|
||||
let entropy = (b"contract_depo_v1", contract_addr)
|
||||
.using_encoded(<T as pezframe_system::Config>::Hashing::hash);
|
||||
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||
.expect("infinite length input; no invalid inputs for type; qed")
|
||||
}
|
||||
|
||||
impl<T: Config, OldCurrency: 'static> MigrationStep for Migration<T, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>
|
||||
+ Inspect<<T as pezframe_system::Config>::AccountId, Balance = v9::BalanceOf<T, OldCurrency>>,
|
||||
{
|
||||
const VERSION: u16 = 10;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v10_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_account) = self.last_account.take() {
|
||||
v9::ContractInfoOf::<T, OldCurrency>::iter_from(
|
||||
v9::ContractInfoOf::<T, OldCurrency>::hashed_key_for(last_account),
|
||||
)
|
||||
} else {
|
||||
v9::ContractInfoOf::<T, OldCurrency>::iter()
|
||||
};
|
||||
|
||||
if let Some((account, contract)) = iter.next() {
|
||||
let min_balance = <OldCurrency as pezframe_support::traits::Currency<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::minimum_balance();
|
||||
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
|
||||
|
||||
// Get the new deposit account address
|
||||
let deposit_account: DepositAccount<T> = DepositAccount(deposit_address::<T>(&account));
|
||||
|
||||
// Calculate the existing deposit, that should be reserved on the contract account
|
||||
let old_deposit = contract
|
||||
.storage_base_deposit
|
||||
.saturating_add(contract.storage_item_deposit)
|
||||
.saturating_add(contract.storage_byte_deposit);
|
||||
|
||||
// Unreserve the existing deposit
|
||||
// Note we can't use repatriate_reserve, because it only works with existing accounts
|
||||
let remaining = OldCurrency::unreserve(&account, old_deposit);
|
||||
if !remaining.is_zero() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Partially unreserved. Remaining {:?} out of {:?} asked",
|
||||
remaining,
|
||||
old_deposit
|
||||
);
|
||||
}
|
||||
|
||||
// Attempt to transfer the old deposit to the deposit account.
|
||||
let amount = old_deposit
|
||||
.saturating_sub(min_balance)
|
||||
.min(OldCurrency::reducible_balance(&account, Preserve, Polite));
|
||||
|
||||
let new_deposit = OldCurrency::transfer(
|
||||
&account,
|
||||
&deposit_account,
|
||||
amount,
|
||||
ExistenceRequirement::KeepAlive,
|
||||
)
|
||||
.map(|_| {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Transferred deposit ({:?}) to deposit account",
|
||||
amount
|
||||
);
|
||||
amount
|
||||
})
|
||||
// If it fails we fallback to minting the ED.
|
||||
.unwrap_or_else(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to transfer the base deposit, reason: {:?}",
|
||||
err
|
||||
);
|
||||
let _ = OldCurrency::deposit_creating(&deposit_account, min_balance);
|
||||
min_balance
|
||||
});
|
||||
|
||||
// Calculate the new base_deposit to store in the contract:
|
||||
// Ideally, it should be the same as the old one
|
||||
// Ideally, it should be at least 2xED (for the contract and deposit accounts).
|
||||
// It can't be more than the `new_deposit`.
|
||||
let new_base_deposit = min(
|
||||
max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)),
|
||||
new_deposit,
|
||||
);
|
||||
|
||||
// Calculate the ratio to adjust storage_byte and storage_item deposits.
|
||||
let new_deposit_without_base = new_deposit.saturating_sub(new_base_deposit);
|
||||
let old_deposit_without_base =
|
||||
old_deposit.saturating_sub(contract.storage_base_deposit);
|
||||
let ratio = Perbill::from_rational(new_deposit_without_base, old_deposit_without_base);
|
||||
|
||||
// Calculate the new storage deposits based on the ratio
|
||||
let storage_byte_deposit = ratio.mul_ceil(contract.storage_byte_deposit);
|
||||
let storage_item_deposit = ratio.mul_ceil(contract.storage_item_deposit);
|
||||
|
||||
// Recalculate the new base deposit, instead of using new_base_deposit to avoid rounding
|
||||
// errors
|
||||
let storage_base_deposit = new_deposit
|
||||
.saturating_sub(storage_byte_deposit)
|
||||
.saturating_sub(storage_item_deposit);
|
||||
|
||||
let new_contract_info = ContractInfo {
|
||||
trie_id: contract.trie_id,
|
||||
deposit_account,
|
||||
code_hash: contract.code_hash,
|
||||
storage_bytes: contract.storage_bytes,
|
||||
storage_items: contract.storage_items,
|
||||
storage_byte_deposit,
|
||||
storage_item_deposit,
|
||||
storage_base_deposit,
|
||||
};
|
||||
|
||||
ContractInfoOf::<T, OldCurrency>::insert(&account, new_contract_info);
|
||||
|
||||
// Store last key for next migration step
|
||||
self.last_account = Some(account);
|
||||
|
||||
meter.consume(T::WeightInfo::v10_migration_step());
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "Done Migrating contract info");
|
||||
meter.consume(T::WeightInfo::v10_migration_step());
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let sample: Vec<_> = v9::ContractInfoOf::<T, OldCurrency>::iter().take(10).collect();
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
|
||||
Ok(sample.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let sample = <Vec<(T::AccountId, v9::ContractInfo<T, OldCurrency>)> as Decode>::decode(
|
||||
&mut &state[..],
|
||||
)
|
||||
.expect("pre_upgrade_step provides a valid state; qed");
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
|
||||
for (account, old_contract) in sample {
|
||||
log::debug!(target: LOG_TARGET, "===");
|
||||
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
|
||||
let contract = ContractInfoOf::<T, OldCurrency>::get(&account).unwrap();
|
||||
ensure!(old_contract.trie_id == contract.trie_id, "invalid trie_id");
|
||||
ensure!(old_contract.code_hash == contract.code_hash, "invalid code_hash");
|
||||
ensure!(old_contract.storage_bytes == contract.storage_bytes, "invalid storage_bytes");
|
||||
ensure!(old_contract.storage_items == contract.storage_items, "invalid storage_items");
|
||||
|
||||
let deposit = <OldCurrency as pezframe_support::traits::Currency<_>>::total_balance(
|
||||
&contract.deposit_account,
|
||||
);
|
||||
ensure!(
|
||||
deposit ==
|
||||
contract
|
||||
.storage_base_deposit
|
||||
.saturating_add(contract.storage_item_deposit)
|
||||
.saturating_add(contract.storage_byte_deposit),
|
||||
"deposit mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Overflowing bounded DeletionQueue.
|
||||
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/47>.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
Config, Pallet, TrieId, Weight, LOG_TARGET,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
mod v10 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct DeletedContract {
|
||||
pub(crate) trie_id: TrieId,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type DeletionQueue<T: Config> = StorageValue<Pallet<T>, Vec<DeletedContract>>;
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct DeletionQueueManager<T: Config> {
|
||||
insert_counter: u32,
|
||||
delete_counter: u32,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", feature = "try-runtime"))]
|
||||
pub fn fill_old_queue<T: Config>(len: usize) {
|
||||
let queue: Vec<v10::DeletedContract> =
|
||||
core::iter::repeat_with(|| v10::DeletedContract { trie_id: Default::default() })
|
||||
.take(len)
|
||||
.collect();
|
||||
v10::DeletionQueue::<T>::set(Some(queue));
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type DeletionQueue<T: Config> = StorageMap<Pallet<T>, Twox64Concat, u32, TrieId>;
|
||||
|
||||
#[storage_alias]
|
||||
type DeletionQueueCounter<T: Config> = StorageValue<Pallet<T>, DeletionQueueManager<T>, ValueQuery>;
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 11;
|
||||
|
||||
// It would be more correct to make our use the now removed [DeletionQueueDepth](https://github.com/pezkuwichain/kurdistan-sdk/issues/47/files#diff-70e9723e9db62816e35f6f885b6770a8449c75a6c2733e9fa7a245fe52c4656c)
|
||||
// but in practice the queue is always empty, so 128 is a good enough approximation for not
|
||||
// underestimating the weight of our migration.
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v11_migration_step(128)
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let Some(old_queue) = v10::DeletionQueue::<T>::take() else {
|
||||
meter.consume(T::WeightInfo::v11_migration_step(0));
|
||||
return IsFinished::Yes;
|
||||
};
|
||||
let len = old_queue.len();
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Migrating deletion queue with {} deleted contracts",
|
||||
old_queue.len()
|
||||
);
|
||||
|
||||
if !old_queue.is_empty() {
|
||||
let mut queue = DeletionQueueManager::<T>::default();
|
||||
for contract in old_queue {
|
||||
<DeletionQueue<T>>::insert(queue.insert_counter, contract.trie_id);
|
||||
queue.insert_counter += 1;
|
||||
}
|
||||
|
||||
<DeletionQueueCounter<T>>::set(queue);
|
||||
}
|
||||
|
||||
meter.consume(T::WeightInfo::v11_migration_step(len as u32));
|
||||
IsFinished::Yes
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let old_queue = v10::DeletionQueue::<T>::take().unwrap_or_default();
|
||||
|
||||
if old_queue.is_empty() {
|
||||
let len = 10u32;
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Injecting {len} entries to deletion queue to test migration"
|
||||
);
|
||||
fill_old_queue::<T>(len as usize);
|
||||
return Ok(len.encode());
|
||||
}
|
||||
|
||||
Ok((old_queue.len() as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let len = <u32 as Decode>::decode(&mut &state[..])
|
||||
.expect("pre_upgrade_step provides a valid state; qed");
|
||||
let counter = <DeletionQueueCounter<T>>::get();
|
||||
ensure!(counter.insert_counter == len, "invalid insert counter");
|
||||
ensure!(counter.delete_counter == 0, "invalid delete counter");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
|
||||
//! repay deposits.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*, storage_alias, traits::ReservableCurrency, weights::WeightMeter,
|
||||
DefaultNoBound, Identity,
|
||||
};
|
||||
use scale_info::prelude::format;
|
||||
use pezsp_core::hexdisplay::HexDisplay;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
use pezsp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
|
||||
|
||||
mod v11 {
|
||||
use super::*;
|
||||
|
||||
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T, OldCurrency))]
|
||||
pub struct OwnerInfo<T: Config, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
pub owner: AccountIdOf<T>,
|
||||
#[codec(compact)]
|
||||
pub deposit: BalanceOf<T, OldCurrency>,
|
||||
#[codec(compact)]
|
||||
pub refcount: u64,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
pub determinism: Determinism,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type OwnerInfoOf<T: Config, OldCurrency> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T, OldCurrency>>;
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeStorage<T: Config> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T, OldCurrency))]
|
||||
pub struct CodeInfo<T: Config, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
owner: AccountIdOf<T>,
|
||||
#[codec(compact)]
|
||||
deposit: v11::BalanceOf<T, OldCurrency>,
|
||||
#[codec(compact)]
|
||||
refcount: u64,
|
||||
determinism: Determinism,
|
||||
code_len: u32,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeInfoOf<T: Config, OldCurrency> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
|
||||
|
||||
#[storage_alias]
|
||||
pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_dummy_code<T: Config, OldCurrency>(len: usize, account: T::AccountId)
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
|
||||
{
|
||||
use pezsp_runtime::traits::Hash;
|
||||
|
||||
let code = alloc::vec![42u8; len];
|
||||
let hash = T::Hashing::hash(&code);
|
||||
PristineCode::<T>::insert(hash, code.clone());
|
||||
|
||||
let module = v11::PrefabWasmModule {
|
||||
instruction_weights_version: Default::default(),
|
||||
initial: Default::default(),
|
||||
maximum: Default::default(),
|
||||
code,
|
||||
determinism: Determinism::Enforced,
|
||||
};
|
||||
v11::CodeStorage::<T>::insert(hash, module);
|
||||
|
||||
let info = v11::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX };
|
||||
v11::OwnerInfoOf::<T, OldCurrency>::insert(hash, info);
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
OldCurrency::Balance: From<BalanceOf<T>>,
|
||||
{
|
||||
last_code_hash: Option<CodeHash<T>>,
|
||||
_phantom: PhantomData<OldCurrency>,
|
||||
}
|
||||
|
||||
impl<T: Config, OldCurrency> MigrationStep for Migration<T, OldCurrency>
|
||||
where
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
|
||||
OldCurrency::Balance: From<BalanceOf<T>>,
|
||||
{
|
||||
const VERSION: u16 = 12;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
|
||||
v11::OwnerInfoOf::<T, OldCurrency>::iter_from(
|
||||
v11::OwnerInfoOf::<T, OldCurrency>::hashed_key_for(last_key),
|
||||
)
|
||||
} else {
|
||||
v11::OwnerInfoOf::<T, OldCurrency>::iter()
|
||||
};
|
||||
if let Some((hash, old_info)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash);
|
||||
|
||||
let module = v11::CodeStorage::<T>::take(hash)
|
||||
.expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
|
||||
|
||||
let code_len = module.code.len();
|
||||
// We print this to measure the impact of the migration.
|
||||
// Storage removed: deleted PrefabWasmModule's encoded len.
|
||||
// Storage added: determinism field encoded len (as all other CodeInfo fields are the
|
||||
// same as in the deleted OwnerInfo).
|
||||
log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
|
||||
|
||||
// Storage usage prices could change over time, and accounts who uploaded their
|
||||
// contracts code before the storage deposits where introduced, had not been ever
|
||||
// charged with any deposit for that (see migration v6).
|
||||
//
|
||||
// This is why deposit to be refunded here is calculated as follows:
|
||||
//
|
||||
// 1. Calculate the deposit amount for storage before the migration, given current
|
||||
// prices.
|
||||
// 2. Given current reserved deposit amount, calculate the correction factor.
|
||||
// 3. Calculate the deposit amount for storage after the migration, given current
|
||||
// prices.
|
||||
// 4. Calculate real deposit amount to be reserved after the migration.
|
||||
let price_per_byte = T::DepositPerByte::get();
|
||||
let price_per_item = T::DepositPerItem::get();
|
||||
let bytes_before = module
|
||||
.encoded_size()
|
||||
.saturating_add(code_len)
|
||||
.saturating_add(v11::OwnerInfo::<T, OldCurrency>::max_encoded_len())
|
||||
as u32;
|
||||
let items_before = 3u32;
|
||||
let deposit_expected_before = price_per_byte
|
||||
.saturating_mul(bytes_before.into())
|
||||
.saturating_add(price_per_item.saturating_mul(items_before.into()));
|
||||
let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
|
||||
.unwrap_or_default()
|
||||
.min(FixedU128::from_u32(1));
|
||||
let bytes_after =
|
||||
code_len.saturating_add(CodeInfo::<T, OldCurrency>::max_encoded_len()) as u32;
|
||||
let items_after = 2u32;
|
||||
let deposit_expected_after = price_per_byte
|
||||
.saturating_mul(bytes_after.into())
|
||||
.saturating_add(price_per_item.saturating_mul(items_after.into()));
|
||||
let deposit = ratio.saturating_mul_int(deposit_expected_after);
|
||||
|
||||
let info = CodeInfo::<T, OldCurrency> {
|
||||
determinism: module.determinism,
|
||||
owner: old_info.owner,
|
||||
deposit: deposit.into(),
|
||||
refcount: old_info.refcount,
|
||||
code_len: code_len as u32,
|
||||
};
|
||||
|
||||
let amount = old_info.deposit.saturating_sub(info.deposit);
|
||||
if !amount.is_zero() {
|
||||
OldCurrency::unreserve(&info.owner, amount);
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Deposit refunded: {:?} Balance, to: {:?}",
|
||||
&amount,
|
||||
HexDisplay::from(&info.owner.encode())
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"new deposit: {:?} >= old deposit: {:?}",
|
||||
&info.deposit,
|
||||
&old_info.deposit
|
||||
);
|
||||
}
|
||||
CodeInfoOf::<T, OldCurrency>::insert(hash, info);
|
||||
|
||||
self.last_code_hash = Some(hash);
|
||||
|
||||
meter.consume(T::WeightInfo::v12_migration_step(code_len as u32));
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
|
||||
meter.consume(T::WeightInfo::v12_migration_step(0));
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let len = 100;
|
||||
log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
|
||||
let sample: Vec<_> = v11::OwnerInfoOf::<T, OldCurrency>::iter()
|
||||
.take(len)
|
||||
.map(|(k, v)| {
|
||||
let module = v11::CodeStorage::<T>::get(k)
|
||||
.expect("No PrefabWasmModule found for code_hash: {:?}");
|
||||
let info: CodeInfo<T, OldCurrency> = CodeInfo {
|
||||
determinism: module.determinism,
|
||||
deposit: v.deposit,
|
||||
refcount: v.refcount,
|
||||
owner: v.owner,
|
||||
code_len: module.code.len() as u32,
|
||||
};
|
||||
(k, info)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let storage: u32 =
|
||||
v11::CodeStorage::<T>::iter().map(|(_k, v)| v.encoded_size() as u32).sum();
|
||||
let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
|
||||
v11::OwnerInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| deposit += v.deposit);
|
||||
|
||||
Ok((sample, deposit, storage).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let state = <(
|
||||
Vec<(CodeHash<T>, CodeInfo<T, OldCurrency>)>,
|
||||
v11::BalanceOf<T, OldCurrency>,
|
||||
u32,
|
||||
) as Decode>::decode(&mut &state[..])
|
||||
.unwrap();
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len());
|
||||
for (hash, old) in state.0 {
|
||||
let info = CodeInfoOf::<T, OldCurrency>::get(&hash)
|
||||
.expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
|
||||
ensure!(info.determinism == old.determinism, "invalid determinism");
|
||||
ensure!(info.owner == old.owner, "invalid owner");
|
||||
ensure!(info.refcount == old.refcount, "invalid refcount");
|
||||
}
|
||||
|
||||
if let Some((k, _)) = v11::CodeStorage::<T>::iter().next() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"CodeStorage is still NOT empty, found code_hash: {:?}",
|
||||
k
|
||||
);
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
|
||||
}
|
||||
if let Some((k, _)) = v11::OwnerInfoOf::<T, OldCurrency>::iter().next() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"OwnerInfoOf is still NOT empty, found code_hash: {:?}",
|
||||
k
|
||||
);
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
|
||||
}
|
||||
|
||||
let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
|
||||
let mut items = 0u32;
|
||||
let mut storage_info = 0u32;
|
||||
CodeInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| {
|
||||
deposit += v.deposit;
|
||||
items += 1;
|
||||
storage_info += v.encoded_size() as u32;
|
||||
});
|
||||
let mut storage_code = 0u32;
|
||||
PristineCode::<T>::iter().for_each(|(_k, v)| {
|
||||
storage_code += v.len() as u32;
|
||||
});
|
||||
let (_, old_deposit, storage_module) = state;
|
||||
// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
|
||||
// I.e. code info adds up 1 byte per record.
|
||||
let info_bytes_added = items;
|
||||
// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
|
||||
let storage_removed = storage_module.saturating_sub(info_bytes_added);
|
||||
// module+code+info - bytes
|
||||
let storage_was = storage_module
|
||||
.saturating_add(storage_code)
|
||||
.saturating_add(storage_info)
|
||||
.saturating_sub(info_bytes_added);
|
||||
// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
|
||||
// items per code).
|
||||
let items_removed = items;
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Storage freed, bytes: {} (of {}), items: {} (of {})",
|
||||
storage_removed,
|
||||
storage_was,
|
||||
items_removed,
|
||||
items_removed * 3,
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Deposits returned, total: {:?} Balance (of {:?} Balance)",
|
||||
old_deposit.saturating_sub(deposit),
|
||||
old_deposit,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Add `delegate_dependencies` to `ContractInfo`.
|
||||
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/49>.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
AccountIdOf, BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound};
|
||||
use pezsp_runtime::BoundedBTreeMap;
|
||||
|
||||
mod v12 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
pub trie_id: TrieId,
|
||||
pub deposit_account: AccountIdOf<T>,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
pub storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
ContractInfo<T>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
|
||||
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
|
||||
let entropy = (b"contract_depo_v1", account.clone()).using_encoded(T::Hashing::hash);
|
||||
let deposit_account = Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||
.expect("infinite length input; no invalid inputs for type; qed");
|
||||
let info = v12::ContractInfo {
|
||||
trie_id: info.trie_id.clone(),
|
||||
deposit_account,
|
||||
code_hash: info.code_hash,
|
||||
storage_bytes: Default::default(),
|
||||
storage_items: Default::default(),
|
||||
storage_byte_deposit: Default::default(),
|
||||
storage_item_deposit: Default::default(),
|
||||
storage_base_deposit: Default::default(),
|
||||
};
|
||||
v12::ContractInfoOf::<T>::insert(account, info);
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> =
|
||||
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
|
||||
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
trie_id: TrieId,
|
||||
deposit_account: AccountIdOf<T>,
|
||||
code_hash: CodeHash<T>,
|
||||
storage_bytes: u32,
|
||||
storage_items: u32,
|
||||
storage_byte_deposit: BalanceOf<T>,
|
||||
storage_item_deposit: BalanceOf<T>,
|
||||
storage_base_deposit: BalanceOf<T>,
|
||||
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_account: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 13;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v13_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_account) = self.last_account.take() {
|
||||
v12::ContractInfoOf::<T>::iter_from(v12::ContractInfoOf::<T>::hashed_key_for(
|
||||
last_account,
|
||||
))
|
||||
} else {
|
||||
v12::ContractInfoOf::<T>::iter()
|
||||
};
|
||||
|
||||
if let Some((key, old)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating contract {:?}", key);
|
||||
let info = ContractInfo {
|
||||
trie_id: old.trie_id,
|
||||
deposit_account: old.deposit_account,
|
||||
code_hash: old.code_hash,
|
||||
storage_bytes: old.storage_bytes,
|
||||
storage_items: old.storage_items,
|
||||
storage_byte_deposit: old.storage_byte_deposit,
|
||||
storage_item_deposit: old.storage_item_deposit,
|
||||
storage_base_deposit: old.storage_base_deposit,
|
||||
delegate_dependencies: Default::default(),
|
||||
};
|
||||
ContractInfoOf::<T>::insert(key.clone(), info);
|
||||
self.last_account = Some(key);
|
||||
meter.consume(T::WeightInfo::v13_migration_step());
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more contracts to migrate");
|
||||
meter.consume(T::WeightInfo::v13_migration_step());
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Update the code owner balance, make the code upload deposit balance to be held instead of
|
||||
//! reserved. Since [`Currency`](pezframe_support::traits::Currency) has been
|
||||
//! [deprecated](https://github.com/pezkuwichain/kurdistan-sdk/issues/40), we need the deposits to be
|
||||
//! handled by the [`pezframe_support::traits::fungible`] traits.
|
||||
|
||||
use crate::{
|
||||
exec::AccountIdOf,
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
BalanceOf, CodeHash, Config, Determinism, HoldReason, Pallet, Weight, LOG_TARGET,
|
||||
};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use codec::{Decode, Encode};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use environmental::Vec;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezframe_support::traits::fungible::{Inspect, InspectHold};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
storage_alias,
|
||||
traits::{fungible::MutateHold, ReservableCurrency},
|
||||
weights::WeightMeter,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use pezsp_core::hexdisplay::HexDisplay;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
use pezsp_runtime::{traits::Zero, Saturating};
|
||||
|
||||
mod v13 {
|
||||
use super::*;
|
||||
|
||||
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T, OldCurrency))]
|
||||
pub struct CodeInfo<T, OldCurrency>
|
||||
where
|
||||
T: Config,
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
pub owner: AccountIdOf<T>,
|
||||
#[codec(compact)]
|
||||
pub deposit: v13::BalanceOf<T, OldCurrency>,
|
||||
#[codec(compact)]
|
||||
pub refcount: u64,
|
||||
pub determinism: Determinism,
|
||||
pub code_len: u32,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeInfoOf<T: Config, OldCurrency> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_dummy_code<T: Config, OldCurrency>(account: T::AccountId)
|
||||
where
|
||||
T: Config,
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
|
||||
{
|
||||
use alloc::vec;
|
||||
use pezsp_runtime::traits::Hash;
|
||||
|
||||
let len = T::MaxCodeLen::get();
|
||||
let code = vec![42u8; len as usize];
|
||||
let hash = T::Hashing::hash(&code);
|
||||
|
||||
let info = v13::CodeInfo {
|
||||
owner: account,
|
||||
deposit: 10_000u32.into(),
|
||||
refcount: u64::MAX,
|
||||
determinism: Determinism::Enforced,
|
||||
code_len: len,
|
||||
};
|
||||
v13::CodeInfoOf::<T, OldCurrency>::insert(hash, info);
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
#[derive(Encode, Decode)]
|
||||
/// Accounts for the balance allocation of a code owner.
|
||||
struct BalanceAllocation<T, OldCurrency>
|
||||
where
|
||||
T: Config,
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
/// Total reserved balance as code upload deposit for the owner.
|
||||
reserved: v13::BalanceOf<T, OldCurrency>,
|
||||
/// Total balance of the owner.
|
||||
total: v13::BalanceOf<T, OldCurrency>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T, OldCurrency>
|
||||
where
|
||||
T: Config,
|
||||
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
{
|
||||
last_code_hash: Option<CodeHash<T>>,
|
||||
_phantom: PhantomData<(T, OldCurrency)>,
|
||||
}
|
||||
|
||||
impl<T, OldCurrency> MigrationStep for Migration<T, OldCurrency>
|
||||
where
|
||||
T: Config,
|
||||
OldCurrency: 'static + ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
|
||||
BalanceOf<T>: From<OldCurrency::Balance>,
|
||||
{
|
||||
const VERSION: u16 = 14;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v14_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_hash) = self.last_code_hash.take() {
|
||||
v13::CodeInfoOf::<T, OldCurrency>::iter_from(
|
||||
v13::CodeInfoOf::<T, OldCurrency>::hashed_key_for(last_hash),
|
||||
)
|
||||
} else {
|
||||
v13::CodeInfoOf::<T, OldCurrency>::iter()
|
||||
};
|
||||
|
||||
if let Some((hash, code_info)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating code upload deposit for 0x{:?}", HexDisplay::from(&code_info.owner.encode()));
|
||||
|
||||
let remaining = OldCurrency::unreserve(&code_info.owner, code_info.deposit);
|
||||
|
||||
if remaining > Zero::zero() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Code owner's account 0x{:?} for code {:?} has some non-unreservable deposit {:?} from a total of {:?} that will remain in reserved.",
|
||||
HexDisplay::from(&code_info.owner.encode()),
|
||||
hash,
|
||||
remaining,
|
||||
code_info.deposit
|
||||
);
|
||||
}
|
||||
|
||||
let unreserved = code_info.deposit.saturating_sub(remaining);
|
||||
let amount = BalanceOf::<T>::from(unreserved);
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Holding {:?} on the code owner's account 0x{:?} for code {:?}.",
|
||||
amount,
|
||||
HexDisplay::from(&code_info.owner.encode()),
|
||||
hash,
|
||||
);
|
||||
|
||||
T::Currency::hold(
|
||||
&HoldReason::CodeUploadDepositReserve.into(),
|
||||
&code_info.owner,
|
||||
amount,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to hold {:?} from the code owner's account 0x{:?} for code {:?}, reason: {:?}.",
|
||||
amount,
|
||||
HexDisplay::from(&code_info.owner.encode()),
|
||||
hash,
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
self.last_code_hash = Some(hash);
|
||||
meter.consume(T::WeightInfo::v14_migration_step());
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more code upload deposit to migrate");
|
||||
meter.consume(T::WeightInfo::v14_migration_step());
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let info: Vec<_> = v13::CodeInfoOf::<T, OldCurrency>::iter().collect();
|
||||
|
||||
let mut owner_balance_allocation =
|
||||
BTreeMap::<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>>::new();
|
||||
|
||||
// Calculates the balance allocation by accumulating the code upload deposits of all codes
|
||||
// owned by an owner.
|
||||
for (_, code_info) in info {
|
||||
owner_balance_allocation
|
||||
.entry(code_info.owner.clone())
|
||||
.and_modify(|alloc| {
|
||||
alloc.reserved = alloc.reserved.saturating_add(code_info.deposit);
|
||||
})
|
||||
.or_insert(BalanceAllocation {
|
||||
reserved: code_info.deposit,
|
||||
total: OldCurrency::total_balance(&code_info.owner),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(owner_balance_allocation.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let owner_balance_allocation =
|
||||
<BTreeMap<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>> as Decode>::decode(
|
||||
&mut &state[..],
|
||||
)
|
||||
.expect("pre_upgrade_step provides a valid state; qed");
|
||||
|
||||
let mut total_held: BalanceOf<T> = Zero::zero();
|
||||
let count = owner_balance_allocation.len();
|
||||
for (owner, old_balance_allocation) in owner_balance_allocation {
|
||||
let held =
|
||||
T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &owner);
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Validating code upload deposit for owner 0x{:?}, reserved: {:?}, held: {:?}",
|
||||
HexDisplay::from(&owner.encode()),
|
||||
old_balance_allocation.reserved,
|
||||
held
|
||||
);
|
||||
ensure!(held == old_balance_allocation.reserved.into(), "Held amount mismatch");
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Validating total balance for owner 0x{:?}, new: {:?}, old: {:?}",
|
||||
HexDisplay::from(&owner.encode()),
|
||||
T::Currency::total_balance(&owner),
|
||||
old_balance_allocation.total
|
||||
);
|
||||
ensure!(
|
||||
T::Currency::total_balance(&owner) ==
|
||||
BalanceOf::<T>::decode(&mut &old_balance_allocation.total.encode()[..])
|
||||
.unwrap(),
|
||||
"Balance mismatch "
|
||||
);
|
||||
total_held += held;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Code owners processed: {:?}.",
|
||||
count
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Total held amount for code upload deposit: {:?}",
|
||||
total_held
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Move contracts' _reserved_ balance from the `deposit_account` to be _held_ in the contract's
|
||||
//! account instead. Since [`Currency`](pezframe_support::traits::Currency) has been
|
||||
//! [deprecated](https://github.com/pezkuwichain/kurdistan-sdk/issues/40), we need the deposits to be
|
||||
//! handled by the [`pezframe_support::traits::fungible`] traits instead. For this transfer the
|
||||
//! balance from the deposit account to the contract's account and hold it in there.
|
||||
//! Then the deposit account is not needed anymore and we can get rid of it.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
AccountIdOf, BalanceOf, CodeHash, Config, HoldReason, Pallet, TrieId, Weight, LOG_TARGET,
|
||||
};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezframe_support::traits::fungible::InspectHold;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
storage_alias,
|
||||
traits::{
|
||||
fungible::{Mutate, MutateHold},
|
||||
tokens::{fungible::Inspect, Fortitude, Preservation},
|
||||
},
|
||||
weights::WeightMeter,
|
||||
BoundedBTreeMap, DefaultNoBound,
|
||||
};
|
||||
use pezframe_system::Pallet as System;
|
||||
use pezsp_core::hexdisplay::HexDisplay;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
use pezsp_runtime::{traits::Zero, Saturating};
|
||||
|
||||
mod v14 {
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
pub trie_id: TrieId,
|
||||
pub deposit_account: AccountIdOf<T>,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
pub storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
pub delegate_dependencies:
|
||||
BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
ContractInfo<T>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
|
||||
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
|
||||
let entropy = (b"contract_depo_v1", account.clone()).using_encoded(T::Hashing::hash);
|
||||
let deposit_account = Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||
.expect("infinite length input; no invalid inputs for type; qed");
|
||||
let info = v14::ContractInfo {
|
||||
trie_id: info.trie_id.clone(),
|
||||
deposit_account,
|
||||
code_hash: info.code_hash,
|
||||
storage_bytes: Default::default(),
|
||||
storage_items: Default::default(),
|
||||
storage_byte_deposit: info.storage_byte_deposit,
|
||||
storage_item_deposit: Default::default(),
|
||||
storage_base_deposit: info.storage_base_deposit(),
|
||||
delegate_dependencies: info.delegate_dependencies().clone(),
|
||||
};
|
||||
v14::ContractInfoOf::<T>::insert(account, info);
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
struct ContractInfo<T: Config> {
|
||||
pub trie_id: TrieId,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
pub storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
pub delegate_dependencies:
|
||||
BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type ContractInfoOf<T: Config> =
|
||||
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_account: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 15;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v15_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_account) = self.last_account.take() {
|
||||
v14::ContractInfoOf::<T>::iter_from(v14::ContractInfoOf::<T>::hashed_key_for(
|
||||
last_account,
|
||||
))
|
||||
} else {
|
||||
v14::ContractInfoOf::<T>::iter()
|
||||
};
|
||||
|
||||
if let Some((account, old_contract)) = iter.next() {
|
||||
let deposit_account = &old_contract.deposit_account;
|
||||
System::<T>::dec_consumers(deposit_account);
|
||||
|
||||
// Get the deposit balance to transfer.
|
||||
let total_deposit_balance = T::Currency::total_balance(deposit_account);
|
||||
let reducible_deposit_balance = T::Currency::reducible_balance(
|
||||
deposit_account,
|
||||
Preservation::Expendable,
|
||||
Fortitude::Force,
|
||||
);
|
||||
|
||||
if total_deposit_balance > reducible_deposit_balance {
|
||||
// This should never happen, as by design all balance in the deposit account is
|
||||
// storage deposit and therefore reducible after decrementing the consumer
|
||||
// reference.
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Deposit account 0x{:?} for contract 0x{:?} has some non-reducible balance {:?} from a total of {:?} that will remain in there.",
|
||||
HexDisplay::from(&deposit_account.encode()),
|
||||
HexDisplay::from(&account.encode()),
|
||||
total_deposit_balance.saturating_sub(reducible_deposit_balance),
|
||||
total_deposit_balance
|
||||
);
|
||||
}
|
||||
|
||||
// Move balance reserved from the deposit account back to the contract account.
|
||||
// Let the deposit account die.
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Transferring {:?} from the deposit account 0x{:?} to the contract 0x{:?}.",
|
||||
reducible_deposit_balance,
|
||||
HexDisplay::from(&deposit_account.encode()),
|
||||
HexDisplay::from(&account.encode())
|
||||
);
|
||||
let transferred_deposit_balance = T::Currency::transfer(
|
||||
deposit_account,
|
||||
&account,
|
||||
reducible_deposit_balance,
|
||||
Preservation::Expendable,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to transfer {:?} from the deposit account 0x{:?} to the contract 0x{:?}, reason: {:?}.",
|
||||
reducible_deposit_balance,
|
||||
HexDisplay::from(&deposit_account.encode()),
|
||||
HexDisplay::from(&account.encode()),
|
||||
err
|
||||
);
|
||||
Zero::zero()
|
||||
});
|
||||
|
||||
// Hold the reserved balance.
|
||||
if transferred_deposit_balance == Zero::zero() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"No balance to hold as storage deposit on the contract 0x{:?}.",
|
||||
HexDisplay::from(&account.encode())
|
||||
);
|
||||
} else {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Holding {:?} as storage deposit on the contract 0x{:?}.",
|
||||
transferred_deposit_balance,
|
||||
HexDisplay::from(&account.encode())
|
||||
);
|
||||
|
||||
T::Currency::hold(
|
||||
&HoldReason::StorageDepositReserve.into(),
|
||||
&account,
|
||||
transferred_deposit_balance,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to hold {:?} as storage deposit on the contract 0x{:?}, reason: {:?}.",
|
||||
transferred_deposit_balance,
|
||||
HexDisplay::from(&account.encode()),
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
log::debug!(target: LOG_TARGET, "===");
|
||||
let info = ContractInfo {
|
||||
trie_id: old_contract.trie_id,
|
||||
code_hash: old_contract.code_hash,
|
||||
storage_bytes: old_contract.storage_bytes,
|
||||
storage_items: old_contract.storage_items,
|
||||
storage_byte_deposit: old_contract.storage_byte_deposit,
|
||||
storage_item_deposit: old_contract.storage_item_deposit,
|
||||
storage_base_deposit: old_contract.storage_base_deposit,
|
||||
delegate_dependencies: old_contract.delegate_dependencies,
|
||||
};
|
||||
ContractInfoOf::<T>::insert(account.clone(), info);
|
||||
|
||||
// Store last key for next migration step
|
||||
self.last_account = Some(account);
|
||||
|
||||
meter.consume(T::WeightInfo::v15_migration_step());
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::info!(target: LOG_TARGET, "Done Migrating Storage Deposits.");
|
||||
meter.consume(T::WeightInfo::v15_migration_step());
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let sample: Vec<_> = v14::ContractInfoOf::<T>::iter().take(100).collect();
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
|
||||
|
||||
let state: Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> = sample
|
||||
.iter()
|
||||
.map(|(account, contract)| {
|
||||
(
|
||||
account.clone(),
|
||||
contract.clone(),
|
||||
T::Currency::total_balance(&account),
|
||||
T::Currency::total_balance(&contract.deposit_account),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(state.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let sample =
|
||||
<Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> as Decode>::decode(
|
||||
&mut &state[..],
|
||||
)
|
||||
.expect("pre_upgrade_step provides a valid state; qed");
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
|
||||
for (account, old_contract, old_account_balance, old_deposit_balance) in sample {
|
||||
log::debug!(target: LOG_TARGET, "===");
|
||||
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
|
||||
|
||||
let on_hold =
|
||||
T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account);
|
||||
let account_balance = T::Currency::total_balance(&account);
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Validating balances match. Old deposit account's balance: {:?}. Contract's on hold: {:?}. Old contract's total balance: {:?}, Contract's total balance: {:?}.",
|
||||
old_deposit_balance,
|
||||
on_hold,
|
||||
old_account_balance,
|
||||
account_balance
|
||||
);
|
||||
ensure!(
|
||||
old_account_balance.saturating_add(old_deposit_balance) == account_balance,
|
||||
"total balance mismatch"
|
||||
);
|
||||
ensure!(old_deposit_balance == on_hold, "deposit mismatch");
|
||||
ensure!(
|
||||
!System::<T>::account_exists(&old_contract.deposit_account),
|
||||
"deposit account still exists"
|
||||
);
|
||||
|
||||
let migration_contract_info = ContractInfoOf::<T>::try_get(&account).unwrap();
|
||||
let crate_contract_info = crate::ContractInfoOf::<T>::try_get(&account).unwrap();
|
||||
ensure!(
|
||||
migration_contract_info.trie_id == crate_contract_info.trie_id,
|
||||
"trie_id mismatch"
|
||||
);
|
||||
ensure!(
|
||||
migration_contract_info.code_hash == crate_contract_info.code_hash,
|
||||
"code_hash mismatch"
|
||||
);
|
||||
ensure!(
|
||||
migration_contract_info.storage_byte_deposit ==
|
||||
crate_contract_info.storage_byte_deposit,
|
||||
"storage_byte_deposit mismatch"
|
||||
);
|
||||
ensure!(
|
||||
migration_contract_info.storage_base_deposit ==
|
||||
crate_contract_info.storage_base_deposit(),
|
||||
"storage_base_deposit mismatch"
|
||||
);
|
||||
ensure!(
|
||||
&migration_contract_info.delegate_dependencies ==
|
||||
crate_contract_info.delegate_dependencies(),
|
||||
"delegate_dependencies mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Remove ED from storage base deposit.
|
||||
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/116>.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, WeightMeter, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{pezpallet_prelude::*, storage_alias, DefaultNoBound};
|
||||
use pezsp_runtime::{BoundedBTreeMap, Saturating};
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_contract_info<T: Config>(
|
||||
account: T::AccountId,
|
||||
info: &crate::ContractInfo<T>,
|
||||
) -> BalanceOf<T> {
|
||||
let storage_base_deposit = Pallet::<T>::min_balance() + 1u32.into();
|
||||
ContractInfoOf::<T>::insert(
|
||||
account,
|
||||
ContractInfo {
|
||||
trie_id: info.trie_id.clone(),
|
||||
code_hash: info.code_hash,
|
||||
storage_bytes: Default::default(),
|
||||
storage_items: Default::default(),
|
||||
storage_byte_deposit: Default::default(),
|
||||
storage_item_deposit: Default::default(),
|
||||
storage_base_deposit,
|
||||
delegate_dependencies: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
storage_base_deposit
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> =
|
||||
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
|
||||
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
trie_id: TrieId,
|
||||
code_hash: CodeHash<T>,
|
||||
storage_bytes: u32,
|
||||
storage_items: u32,
|
||||
storage_byte_deposit: BalanceOf<T>,
|
||||
storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_account: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 16;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v16_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
|
||||
let mut iter = if let Some(last_account) = self.last_account.take() {
|
||||
ContractInfoOf::<T>::iter_keys_from(ContractInfoOf::<T>::hashed_key_for(last_account))
|
||||
} else {
|
||||
ContractInfoOf::<T>::iter_keys()
|
||||
};
|
||||
|
||||
if let Some(key) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating contract {:?}", key);
|
||||
ContractInfoOf::<T>::mutate(key.clone(), |info| {
|
||||
let ed = Pallet::<T>::min_balance();
|
||||
let mut updated_info = info.take().expect("Item exists; qed");
|
||||
updated_info.storage_base_deposit.saturating_reduce(ed);
|
||||
*info = Some(updated_info);
|
||||
});
|
||||
self.last_account = Some(key);
|
||||
meter.consume(T::WeightInfo::v16_migration_step());
|
||||
IsFinished::No
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more contracts to migrate");
|
||||
meter.consume(T::WeightInfo::v16_migration_step());
|
||||
IsFinished::Yes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A crate that hosts a common definitions that are relevant for the pezpallet-contracts.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezpallet_contracts_uapi::ReturnFlags;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
DispatchError, RuntimeDebug,
|
||||
};
|
||||
|
||||
/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and
|
||||
/// `ContractsApi::instantiate`.
|
||||
///
|
||||
/// It contains the execution result together with some auxiliary information.
|
||||
///
|
||||
/// #Note
|
||||
///
|
||||
/// It has been extended to include `events` at the end of the struct while not bumping the
|
||||
/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data
|
||||
/// should be ignored to avoid any potential compatibility issues.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct ContractResult<R, Balance, EventRecord> {
|
||||
/// How much weight was consumed during execution.
|
||||
pub gas_consumed: Weight,
|
||||
/// How much weight is required as gas limit in order to execute this call.
|
||||
///
|
||||
/// This value should be used to determine the weight limit for on-chain execution.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This can only different from [`Self::gas_consumed`] when weight pre charging
|
||||
/// is used. Currently, only `seal_call_runtime` makes use of pre charging.
|
||||
/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
|
||||
/// when a non-zero `gas_limit` argument is supplied.
|
||||
pub gas_required: Weight,
|
||||
/// How much balance was paid by the origin into the contract's deposit account in order to
|
||||
/// pay for storage.
|
||||
///
|
||||
/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
|
||||
/// is `Err`. This is because on error all storage changes are rolled back including the
|
||||
/// payment of the deposit.
|
||||
pub storage_deposit: StorageDeposit<Balance>,
|
||||
/// An optional debug message. This message is only filled when explicitly requested
|
||||
/// by the code that calls into the contract. Otherwise it is empty.
|
||||
///
|
||||
/// The contained bytes are valid UTF-8. This is not declared as `String` because
|
||||
/// this type is not allowed within the runtime.
|
||||
///
|
||||
/// Clients should not make any assumptions about the format of the buffer.
|
||||
/// They should just display it as-is. It is **not** only a collection of log lines
|
||||
/// provided by a contract but a formatted buffer with different sections.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The debug message is never generated during on-chain execution. It is reserved for
|
||||
/// RPC calls.
|
||||
pub debug_message: Vec<u8>,
|
||||
/// The execution result of the wasm code.
|
||||
pub result: R,
|
||||
/// The events that were emitted during execution. It is an option as event collection is
|
||||
/// optional.
|
||||
pub events: Option<Vec<EventRecord>>,
|
||||
}
|
||||
|
||||
/// Result type of a `bare_call` call as well as `ContractsApi::call`.
|
||||
pub type ContractExecResult<Balance, EventRecord> =
|
||||
ContractResult<Result<ExecReturnValue, DispatchError>, Balance, EventRecord>;
|
||||
|
||||
/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`.
|
||||
pub type ContractInstantiateResult<AccountId, Balance, EventRecord> =
|
||||
ContractResult<Result<InstantiateReturnValue<AccountId>, DispatchError>, Balance, EventRecord>;
|
||||
|
||||
/// Result type of a `bare_code_upload` call.
|
||||
pub type CodeUploadResult<CodeHash, Balance> =
|
||||
Result<CodeUploadReturnValue<CodeHash, Balance>, DispatchError>;
|
||||
|
||||
/// Result type of a `get_storage` call.
|
||||
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
|
||||
|
||||
/// The possible errors that can happen querying the storage of a contract.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
|
||||
pub enum ContractAccessError {
|
||||
/// The given address doesn't point to a contract.
|
||||
DoesntExist,
|
||||
/// Storage key cannot be decoded from the provided input data.
|
||||
KeyDecodingFailed,
|
||||
/// Storage is migrating. Try again later.
|
||||
MigrationInProgress,
|
||||
}
|
||||
|
||||
/// Output of a contract call or instantiation which ran to completion.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct ExecReturnValue {
|
||||
/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
|
||||
pub flags: ReturnFlags,
|
||||
/// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ExecReturnValue {
|
||||
/// The contract did revert all storage changes.
|
||||
pub fn did_revert(&self) -> bool {
|
||||
self.flags.contains(ReturnFlags::REVERT)
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a successful contract instantiation.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct InstantiateReturnValue<AccountId> {
|
||||
/// The output of the called constructor.
|
||||
pub result: ExecReturnValue,
|
||||
/// The account id of the new contract.
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
/// The result of successfully uploading a contract.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
|
||||
pub struct CodeUploadReturnValue<CodeHash, Balance> {
|
||||
/// The key under which the new code is stored.
|
||||
pub code_hash: CodeHash,
|
||||
/// The deposit that was reserved at the caller. Is zero when the code already existed.
|
||||
pub deposit: Balance,
|
||||
}
|
||||
|
||||
/// Reference to an existing code hash or a new wasm module.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub enum Code<Hash> {
|
||||
/// A wasm module as raw bytes.
|
||||
Upload(Vec<u8>),
|
||||
/// The code hash of an on-chain wasm blob.
|
||||
Existing(Hash),
|
||||
}
|
||||
|
||||
/// The amount of balance that was either charged or refunded in order to pay for storage.
|
||||
#[derive(
|
||||
Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo,
|
||||
)]
|
||||
pub enum StorageDeposit<Balance> {
|
||||
/// The transaction reduced storage consumption.
|
||||
///
|
||||
/// This means that the specified amount of balance was transferred from the involved
|
||||
/// deposit accounts to the origin.
|
||||
Refund(Balance),
|
||||
/// The transaction increased storage consumption.
|
||||
///
|
||||
/// This means that the specified amount of balance was transferred from the origin
|
||||
/// to the involved deposit accounts.
|
||||
Charge(Balance),
|
||||
}
|
||||
|
||||
impl<Balance: Zero> Default for StorageDeposit<Balance> {
|
||||
fn default() -> Self {
|
||||
Self::Charge(Zero::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Balance: Zero + Copy> StorageDeposit<Balance> {
|
||||
/// Returns how much balance is charged or `0` in case of a refund.
|
||||
pub fn charge_or_zero(&self) -> Balance {
|
||||
match self {
|
||||
Self::Charge(amount) => *amount,
|
||||
Self::Refund(_) => Zero::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
Self::Charge(amount) => amount.is_zero(),
|
||||
Self::Refund(amount) => amount.is_zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Balance> StorageDeposit<Balance>
|
||||
where
|
||||
Balance: Saturating + Ord + Copy,
|
||||
{
|
||||
/// This is essentially a saturating signed add.
|
||||
pub fn saturating_add(&self, rhs: &Self) -> Self {
|
||||
use StorageDeposit::*;
|
||||
match (self, rhs) {
|
||||
(Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)),
|
||||
(Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)),
|
||||
(Charge(lhs), Refund(rhs)) =>
|
||||
if lhs >= rhs {
|
||||
Charge(lhs.saturating_sub(*rhs))
|
||||
} else {
|
||||
Refund(rhs.saturating_sub(*lhs))
|
||||
},
|
||||
(Refund(lhs), Charge(rhs)) =>
|
||||
if lhs > rhs {
|
||||
Refund(lhs.saturating_sub(*rhs))
|
||||
} else {
|
||||
Charge(rhs.saturating_sub(*lhs))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// This is essentially a saturating signed sub.
|
||||
pub fn saturating_sub(&self, rhs: &Self) -> Self {
|
||||
use StorageDeposit::*;
|
||||
match (self, rhs) {
|
||||
(Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)),
|
||||
(Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)),
|
||||
(Charge(lhs), Charge(rhs)) =>
|
||||
if lhs >= rhs {
|
||||
Charge(lhs.saturating_sub(*rhs))
|
||||
} else {
|
||||
Refund(rhs.saturating_sub(*lhs))
|
||||
},
|
||||
(Refund(lhs), Refund(rhs)) =>
|
||||
if lhs > rhs {
|
||||
Refund(lhs.saturating_sub(*rhs))
|
||||
} else {
|
||||
Charge(rhs.saturating_sub(*lhs))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// If the amount of deposit (this type) is constrained by a `limit` this calculates how
|
||||
/// much balance (if any) is still available from this limit.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// In case of a refund the return value can be larger than `limit`.
|
||||
pub fn available(&self, limit: &Balance) -> Balance {
|
||||
use StorageDeposit::*;
|
||||
match self {
|
||||
Charge(amount) => limit.saturating_sub(*amount),
|
||||
Refund(amount) => limit.saturating_add(*amount),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This module contains the cost schedule and supporting code that constructs a
|
||||
//! sane default schedule from a `WeightInfo` implementation.
|
||||
|
||||
use crate::{weights::WeightInfo, Config};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::DefaultNoBound;
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Definition of the cost schedule and other parameterizations for the wasm vm.
|
||||
///
|
||||
/// Its [`Default`] implementation is the designated way to initialize this type. It uses
|
||||
/// the benchmarked information supplied by [`Config::WeightInfo`]. All of its fields are
|
||||
/// public and can therefore be modified. For example in order to change some of the limits
|
||||
/// and set a custom instruction weight version the following code could be used:
|
||||
/// ```rust
|
||||
/// use pezpallet_contracts::{Schedule, Limits, InstructionWeights, Config};
|
||||
///
|
||||
/// fn create_schedule<T: Config>() -> Schedule<T> {
|
||||
/// Schedule {
|
||||
/// limits: Limits {
|
||||
/// memory_pages: 16,
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// instruction_weights: InstructionWeights {
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// .. Default::default()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
|
||||
#[cfg_attr(feature = "runtime-benchmarks", derive(pezframe_support::DebugNoBound))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, DefaultNoBound, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Schedule<T: Config> {
|
||||
/// Describes the upper limits on various metrics.
|
||||
pub limits: Limits,
|
||||
|
||||
/// The weights for individual wasm instructions.
|
||||
pub instruction_weights: InstructionWeights<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> Schedule<T> {
|
||||
/// Returns the reference time per engine fuel.
|
||||
pub fn ref_time_by_fuel(&self) -> u64 {
|
||||
self.instruction_weights.base as u64
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the upper limits on various metrics.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "runtime-benchmarks", derive(Debug))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
|
||||
pub struct Limits {
|
||||
/// The maximum number of topics supported by an event.
|
||||
pub event_topics: u32,
|
||||
|
||||
/// Maximum number of memory pages allowed for a contract.
|
||||
pub memory_pages: u32,
|
||||
|
||||
/// The maximum length of a subject in bytes used for PRNG generation.
|
||||
pub subject_len: u32,
|
||||
|
||||
/// The maximum size of a storage value and event payload in bytes.
|
||||
pub payload_len: u32,
|
||||
|
||||
/// The maximum node runtime memory. This is for integrity checks only and does not affect the
|
||||
/// real setting.
|
||||
pub runtime_memory: u32,
|
||||
|
||||
/// The maximum validator node runtime memory. This is for integrity checks only and does not
|
||||
/// affect the real setting.
|
||||
pub validator_runtime_memory: u32,
|
||||
|
||||
/// The additional ref_time added to the `deposit_event` host function call per event data
|
||||
/// byte.
|
||||
pub event_ref_time: u64,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// The maximum memory size in bytes that a contract can occupy.
|
||||
pub fn max_memory_size(&self) -> u32 {
|
||||
self.memory_pages * 64 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
/// Gas metering of Wasm executed instructions is being done on the engine side.
|
||||
/// This struct holds a reference value used to gas units scaling between host and engine.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "runtime-benchmarks", derive(pezframe_support::DebugNoBound))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct InstructionWeights<T: Config> {
|
||||
/// Base instruction `ref_time` Weight.
|
||||
/// Should match to wasmi's `1` fuel (see <https://github.com/wasmi-labs/wasmi/issues/701>).
|
||||
pub base: u32,
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
event_topics: 4,
|
||||
memory_pages: 16,
|
||||
subject_len: 32,
|
||||
payload_len: 16 * 1024,
|
||||
runtime_memory: 1024 * 1024 * 128,
|
||||
validator_runtime_memory: 1024 * 1024 * 512,
|
||||
event_ref_time: 60_000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Default for InstructionWeights<T> {
|
||||
/// We execute 6 different instructions therefore we have to divide the actual
|
||||
/// computed gas costs by 6 to have a rough estimate as to how expensive each
|
||||
/// single executed instruction is going to be.
|
||||
fn default() -> Self {
|
||||
let instr_cost = T::WeightInfo::instr_i64_load_store(1)
|
||||
.saturating_sub(T::WeightInfo::instr_i64_load_store(0))
|
||||
.ref_time() as u32;
|
||||
let base = instr_cost / 6;
|
||||
Self { base, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This module contains routines for accessing and altering a contract related state.
|
||||
|
||||
pub mod meter;
|
||||
|
||||
use crate::{
|
||||
exec::{AccountIdOf, Key},
|
||||
weights::WeightInfo,
|
||||
BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter,
|
||||
Error, TrieId, SENTINEL,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
storage::child::{self, ChildInfo},
|
||||
weights::{Weight, WeightMeter},
|
||||
CloneNoBound, DefaultNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::Get;
|
||||
use pezsp_io::KillStorageResult;
|
||||
use pezsp_runtime::{
|
||||
traits::{Hash, Saturating, Zero},
|
||||
BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug,
|
||||
};
|
||||
|
||||
use self::meter::Diff;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account.
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
/// Unique ID for the subtree encoded as a bytes vector.
|
||||
pub trie_id: TrieId,
|
||||
/// The code associated with a given account.
|
||||
pub code_hash: CodeHash<T>,
|
||||
/// How many bytes of storage are accumulated in this contract's child trie.
|
||||
storage_bytes: u32,
|
||||
/// How many items of storage are accumulated in this contract's child trie.
|
||||
storage_items: u32,
|
||||
/// This records to how much deposit the accumulated `storage_bytes` amount to.
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
/// This records to how much deposit the accumulated `storage_items` amount to.
|
||||
storage_item_deposit: BalanceOf<T>,
|
||||
/// This records how much deposit is put down in order to pay for the contract itself.
|
||||
///
|
||||
/// We need to store this information separately so it is not used when calculating any refunds
|
||||
/// since the base deposit can only ever be refunded on contract termination.
|
||||
storage_base_deposit: BalanceOf<T>,
|
||||
/// Map of code hashes and deposit balances.
|
||||
///
|
||||
/// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added
|
||||
/// to the map can not be removed from the chain state and can be safely used for delegate
|
||||
/// calls.
|
||||
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
impl<T: Config> ContractInfo<T> {
|
||||
/// Constructs a new contract info **without** writing it to storage.
|
||||
///
|
||||
/// This returns an `Err` if an contract with the supplied `account` already exists
|
||||
/// in storage.
|
||||
pub fn new(
|
||||
account: &AccountIdOf<T>,
|
||||
nonce: u64,
|
||||
code_hash: CodeHash<T>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
if <ContractInfoOf<T>>::contains_key(account) {
|
||||
return Err(Error::<T>::DuplicateContract.into());
|
||||
}
|
||||
|
||||
let trie_id = {
|
||||
let buf = (account, nonce).using_encoded(T::Hashing::hash);
|
||||
buf.as_ref()
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
|
||||
};
|
||||
|
||||
let contract = Self {
|
||||
trie_id,
|
||||
code_hash,
|
||||
storage_bytes: 0,
|
||||
storage_items: 0,
|
||||
storage_byte_deposit: Zero::zero(),
|
||||
storage_item_deposit: Zero::zero(),
|
||||
storage_base_deposit: Zero::zero(),
|
||||
delegate_dependencies: Default::default(),
|
||||
};
|
||||
|
||||
Ok(contract)
|
||||
}
|
||||
|
||||
/// Returns the number of locked delegate dependencies.
|
||||
pub fn delegate_dependencies_count(&self) -> usize {
|
||||
self.delegate_dependencies.len()
|
||||
}
|
||||
|
||||
/// Associated child trie unique id is built from the hash part of the trie id.
|
||||
pub fn child_trie_info(&self) -> ChildInfo {
|
||||
ChildInfo::new_default(self.trie_id.as_ref())
|
||||
}
|
||||
|
||||
/// The deposit paying for the accumulated storage generated within the contract's child trie.
|
||||
pub fn extra_deposit(&self) -> BalanceOf<T> {
|
||||
self.storage_byte_deposit.saturating_add(self.storage_item_deposit)
|
||||
}
|
||||
|
||||
/// Same as [`Self::extra_deposit`] but including the base deposit.
|
||||
pub fn total_deposit(&self) -> BalanceOf<T> {
|
||||
self.extra_deposit().saturating_add(self.storage_base_deposit)
|
||||
}
|
||||
|
||||
/// Returns the storage base deposit of the contract.
|
||||
pub fn storage_base_deposit(&self) -> BalanceOf<T> {
|
||||
self.storage_base_deposit
|
||||
}
|
||||
|
||||
/// Reads a storage kv pair of a contract.
|
||||
///
|
||||
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
||||
/// contract doesn't store under the given `key` `None` is returned.
|
||||
pub fn read(&self, key: &Key<T>) -> Option<Vec<u8>> {
|
||||
child::get_raw(&self.child_trie_info(), key.hash().as_slice())
|
||||
}
|
||||
|
||||
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
||||
///
|
||||
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||
/// was deleted.
|
||||
pub fn size(&self, key: &Key<T>) -> Option<u32> {
|
||||
child::len(&self.child_trie_info(), key.hash().as_slice())
|
||||
}
|
||||
|
||||
/// Update a storage entry into a contract's kv storage.
|
||||
///
|
||||
/// If the `new_value` is `None` then the kv pair is removed. If `take` is true
|
||||
/// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`].
|
||||
///
|
||||
/// This function also records how much storage was created or removed if a `storage_meter`
|
||||
/// is supplied. It should only be absent for testing or benchmarking code.
|
||||
pub fn write(
|
||||
&self,
|
||||
key: &Key<T>,
|
||||
new_value: Option<Vec<u8>>,
|
||||
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
||||
take: bool,
|
||||
) -> Result<WriteOutcome, DispatchError> {
|
||||
let hashed_key = key.hash();
|
||||
self.write_raw(&hashed_key, new_value, storage_meter, take)
|
||||
}
|
||||
|
||||
/// Update a storage entry into a contract's kv storage.
|
||||
/// Function used in benchmarks, which can simulate prefix collision in keys.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn bench_write_raw(
|
||||
&self,
|
||||
key: &[u8],
|
||||
new_value: Option<Vec<u8>>,
|
||||
take: bool,
|
||||
) -> Result<WriteOutcome, DispatchError> {
|
||||
self.write_raw(key, new_value, None, take)
|
||||
}
|
||||
|
||||
fn write_raw(
|
||||
&self,
|
||||
key: &[u8],
|
||||
new_value: Option<Vec<u8>>,
|
||||
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
||||
take: bool,
|
||||
) -> Result<WriteOutcome, DispatchError> {
|
||||
let child_trie_info = &self.child_trie_info();
|
||||
let (old_len, old_value) = if take {
|
||||
let val = child::get_raw(child_trie_info, key);
|
||||
(val.as_ref().map(|v| v.len() as u32), val)
|
||||
} else {
|
||||
(child::len(child_trie_info, key), None)
|
||||
};
|
||||
|
||||
if let Some(storage_meter) = storage_meter {
|
||||
let mut diff = meter::Diff::default();
|
||||
match (old_len, new_value.as_ref().map(|v| v.len() as u32)) {
|
||||
(Some(old_len), Some(new_len)) =>
|
||||
if new_len > old_len {
|
||||
diff.bytes_added = new_len - old_len;
|
||||
} else {
|
||||
diff.bytes_removed = old_len - new_len;
|
||||
},
|
||||
(None, Some(new_len)) => {
|
||||
diff.bytes_added = new_len;
|
||||
diff.items_added = 1;
|
||||
},
|
||||
(Some(old_len), None) => {
|
||||
diff.bytes_removed = old_len;
|
||||
diff.items_removed = 1;
|
||||
},
|
||||
(None, None) => (),
|
||||
}
|
||||
storage_meter.charge(&diff);
|
||||
}
|
||||
|
||||
match &new_value {
|
||||
Some(new_value) => child::put_raw(child_trie_info, key, new_value),
|
||||
None => child::kill(child_trie_info, key),
|
||||
}
|
||||
|
||||
Ok(match (old_len, old_value) {
|
||||
(None, _) => WriteOutcome::New,
|
||||
(Some(old_len), None) => WriteOutcome::Overwritten(old_len),
|
||||
(Some(_), Some(old_value)) => WriteOutcome::Taken(old_value),
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets and returns the contract base deposit.
|
||||
///
|
||||
/// The base deposit is updated when the `code_hash` of the contract changes, as it depends on
|
||||
/// the deposit paid to upload the contract's code.
|
||||
pub fn update_base_deposit(&mut self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
|
||||
let info_deposit =
|
||||
Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() }
|
||||
.update_contract::<T>(None)
|
||||
.charge_or_zero();
|
||||
|
||||
// Instantiating the contract prevents its code to be deleted, therefore the base deposit
|
||||
// includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit
|
||||
// to prevent abuse.
|
||||
let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
|
||||
|
||||
let deposit = info_deposit.saturating_add(upload_deposit);
|
||||
self.storage_base_deposit = deposit;
|
||||
deposit
|
||||
}
|
||||
|
||||
/// Adds a new delegate dependency to the contract.
|
||||
/// The `amount` is the amount of funds that will be reserved for the dependency.
|
||||
///
|
||||
/// Returns an error if the maximum number of delegate_dependencies is reached or if
|
||||
/// the delegate dependency already exists.
|
||||
pub fn lock_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: CodeHash<T>,
|
||||
amount: BalanceOf<T>,
|
||||
) -> DispatchResult {
|
||||
self.delegate_dependencies
|
||||
.try_insert(code_hash, amount)
|
||||
.map_err(|_| Error::<T>::MaxDelegateDependenciesReached)?
|
||||
.map_or(Ok(()), |_| Err(Error::<T>::DelegateDependencyAlreadyExists))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Removes the delegate dependency from the contract and returns the deposit held for this
|
||||
/// dependency.
|
||||
///
|
||||
/// Returns an error if the entry doesn't exist.
|
||||
pub fn unlock_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: &CodeHash<T>,
|
||||
) -> Result<BalanceOf<T>, DispatchError> {
|
||||
self.delegate_dependencies
|
||||
.remove(code_hash)
|
||||
.ok_or(Error::<T>::DelegateDependencyNotFound.into())
|
||||
}
|
||||
|
||||
/// Returns the delegate_dependencies of the contract.
|
||||
pub fn delegate_dependencies(
|
||||
&self,
|
||||
) -> &BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies> {
|
||||
&self.delegate_dependencies
|
||||
}
|
||||
|
||||
/// Push a contract's trie to the deletion queue for lazy removal.
|
||||
///
|
||||
/// You must make sure that the contract is also removed when queuing the trie for deletion.
|
||||
pub fn queue_trie_for_deletion(&self) {
|
||||
DeletionQueueManager::<T>::load().insert(self.trie_id.clone());
|
||||
}
|
||||
|
||||
/// Calculates the weight that is necessary to remove one key from the trie and how many
|
||||
/// of those keys can be deleted from the deletion queue given the supplied weight limit.
|
||||
pub fn deletion_budget(meter: &WeightMeter) -> (Weight, u32) {
|
||||
let base_weight = T::WeightInfo::on_process_deletion_queue_batch();
|
||||
let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) -
|
||||
T::WeightInfo::on_initialize_per_trie_key(0);
|
||||
|
||||
// `weight_per_key` being zero makes no sense and would constitute a failure to
|
||||
// benchmark properly. We opt for not removing any keys at all in this case.
|
||||
let key_budget = meter
|
||||
.limit()
|
||||
.saturating_sub(base_weight)
|
||||
.checked_div_per_component(&weight_per_key)
|
||||
.unwrap_or(0) as u32;
|
||||
|
||||
(weight_per_key, key_budget)
|
||||
}
|
||||
|
||||
/// Delete as many items from the deletion queue possible within the supplied weight limit.
|
||||
pub fn process_deletion_queue_batch(meter: &mut WeightMeter) {
|
||||
if meter.try_consume(T::WeightInfo::on_process_deletion_queue_batch()).is_err() {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut queue = <DeletionQueueManager<T>>::load();
|
||||
if queue.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (weight_per_key, budget) = Self::deletion_budget(&meter);
|
||||
let mut remaining_key_budget = budget;
|
||||
while remaining_key_budget > 0 {
|
||||
let Some(entry) = queue.next() else { break };
|
||||
|
||||
#[allow(deprecated)]
|
||||
let outcome = child::kill_storage(
|
||||
&ChildInfo::new_default(&entry.trie_id),
|
||||
Some(remaining_key_budget),
|
||||
);
|
||||
|
||||
match outcome {
|
||||
// This happens when our budget wasn't large enough to remove all keys.
|
||||
KillStorageResult::SomeRemaining(keys_removed) => {
|
||||
remaining_key_budget.saturating_reduce(keys_removed);
|
||||
break;
|
||||
},
|
||||
KillStorageResult::AllRemoved(keys_removed) => {
|
||||
entry.remove();
|
||||
// charge at least one key even if none were removed.
|
||||
remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget)))
|
||||
}
|
||||
|
||||
/// Returns the code hash of the contract specified by `account` ID.
|
||||
pub fn load_code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
||||
<ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`].
|
||||
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
|
||||
pub enum WriteOutcome {
|
||||
/// No value existed at the specified key.
|
||||
New,
|
||||
/// A value of the returned length was overwritten.
|
||||
Overwritten(u32),
|
||||
/// The returned value was taken out of storage before being overwritten.
|
||||
///
|
||||
/// This is only returned when specifically requested because it causes additional work
|
||||
/// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`]
|
||||
/// is returned instead.
|
||||
Taken(Vec<u8>),
|
||||
}
|
||||
|
||||
impl WriteOutcome {
|
||||
/// Extracts the size of the overwritten value or `0` if there
|
||||
/// was no value in storage.
|
||||
pub fn old_len(&self) -> u32 {
|
||||
match self {
|
||||
Self::New => 0,
|
||||
Self::Overwritten(len) => *len,
|
||||
Self::Taken(value) => value.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the size of the overwritten value or `SENTINEL` if there
|
||||
/// was no value in storage.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We cannot use `0` as sentinel value because there could be a zero sized
|
||||
/// storage entry which is different from a non existing one.
|
||||
pub fn old_len_with_sentinel(&self) -> u32 {
|
||||
match self {
|
||||
Self::New => SENTINEL,
|
||||
Self::Overwritten(len) => *len,
|
||||
Self::Taken(value) => value.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manage the removal of contracts storage that are marked for deletion.
|
||||
///
|
||||
/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible
|
||||
/// immediately, but the deletion of the storage items it has accumulated is performed
|
||||
/// later by pulling the contract from the queue in the `on_idle` hook.
|
||||
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct DeletionQueueManager<T: Config> {
|
||||
/// Counter used as a key for inserting a new deleted contract in the queue.
|
||||
/// The counter is incremented after each insertion.
|
||||
insert_counter: u32,
|
||||
/// The index used to read the next element to be deleted in the queue.
|
||||
/// The counter is incremented after each deletion.
|
||||
delete_counter: u32,
|
||||
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// View on a contract that is marked for deletion.
|
||||
struct DeletionQueueEntry<'a, T: Config> {
|
||||
/// the trie id of the contract to delete.
|
||||
trie_id: TrieId,
|
||||
|
||||
/// A mutable reference on the queue so that the contract can be removed, and none can be added
|
||||
/// or read in the meantime.
|
||||
queue: &'a mut DeletionQueueManager<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> DeletionQueueEntry<'a, T> {
|
||||
/// Remove the contract from the deletion queue.
|
||||
fn remove(self) {
|
||||
<DeletionQueue<T>>::remove(self.queue.delete_counter);
|
||||
self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1);
|
||||
<DeletionQueueCounter<T>>::set(self.queue.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> DeletionQueueManager<T> {
|
||||
/// Load the `DeletionQueueCounter`, so we can perform read or write operations on the
|
||||
/// DeletionQueue storage.
|
||||
fn load() -> Self {
|
||||
<DeletionQueueCounter<T>>::get()
|
||||
}
|
||||
|
||||
/// Returns `true` if the queue contains no elements.
|
||||
fn is_empty(&self) -> bool {
|
||||
self.insert_counter.wrapping_sub(self.delete_counter) == 0
|
||||
}
|
||||
|
||||
/// Insert a contract in the deletion queue.
|
||||
fn insert(&mut self, trie_id: TrieId) {
|
||||
<DeletionQueue<T>>::insert(self.insert_counter, trie_id);
|
||||
self.insert_counter = self.insert_counter.wrapping_add(1);
|
||||
<DeletionQueueCounter<T>>::set(self.clone());
|
||||
}
|
||||
|
||||
/// Fetch the next contract to be deleted.
|
||||
///
|
||||
/// Note:
|
||||
/// we use the delete counter to get the next value to read from the queue and thus don't pay
|
||||
/// the cost of an extra call to `pezsp_io::storage::next_key` to lookup the next entry in the map
|
||||
fn next(&mut self) -> Option<DeletionQueueEntry<'_, T>> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry = <DeletionQueue<T>>::get(self.delete_counter);
|
||||
entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<T: Config> DeletionQueueManager<T> {
|
||||
pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self {
|
||||
Self { insert_counter, delete_counter, _phantom: Default::default() }
|
||||
}
|
||||
pub fn as_test_tuple(&self) -> (u32, u32) {
|
||||
(self.insert_counter, self.delete_counter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This module contains functions to meter the storage deposit.
|
||||
|
||||
use crate::{
|
||||
storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason,
|
||||
Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
|
||||
};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use pezframe_support::{
|
||||
ensure,
|
||||
traits::{
|
||||
fungible::{Mutate, MutateHold},
|
||||
tokens::{
|
||||
Fortitude, Fortitude::Polite, Precision, Preservation, Restriction, WithdrawConsequence,
|
||||
},
|
||||
Get,
|
||||
},
|
||||
DefaultNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
DispatchError, FixedPointNumber, FixedU128,
|
||||
};
|
||||
|
||||
/// Deposit that uses the native fungible's balance type.
|
||||
pub type DepositOf<T> = Deposit<BalanceOf<T>>;
|
||||
|
||||
/// A production root storage meter that actually charges from its origin.
|
||||
pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
|
||||
|
||||
/// A production nested storage meter that actually charges from its origin.
|
||||
pub type NestedMeter<T> = RawMeter<T, ReservingExt, Nested>;
|
||||
|
||||
/// A production storage meter that actually charges from its origin.
|
||||
///
|
||||
/// This can be used where we want to be generic over the state (Root vs. Nested).
|
||||
pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
|
||||
|
||||
/// A trait that allows to decouple the metering from the charging of balance.
|
||||
///
|
||||
/// This mostly exists for testing so that the charging can be mocked.
|
||||
pub trait Ext<T: Config> {
|
||||
/// This checks whether `origin` is able to afford the storage deposit limit.
|
||||
///
|
||||
/// It is necessary to do this check beforehand so that the charge won't fail later on.
|
||||
///
|
||||
/// `origin`: The origin of the call stack from which is responsible for putting down a deposit.
|
||||
/// `limit`: The limit with which the meter was constructed.
|
||||
/// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should
|
||||
/// be left inside the `origin` account.
|
||||
///
|
||||
/// Returns the limit that should be used by the meter. If origin can't afford the `limit`
|
||||
/// it returns `Err`.
|
||||
fn check_limit(
|
||||
origin: &T::AccountId,
|
||||
limit: Option<BalanceOf<T>>,
|
||||
min_leftover: BalanceOf<T>,
|
||||
) -> Result<BalanceOf<T>, DispatchError>;
|
||||
/// This is called to inform the implementer that some balance should be charged due to
|
||||
/// some interaction of the `origin` with a `contract`.
|
||||
///
|
||||
/// The balance transfer can either flow from `origin` to `contract` or the other way
|
||||
/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
|
||||
/// It should be used in combination with `check_limit` to check that no more balance than this
|
||||
/// limit is ever charged.
|
||||
fn charge(
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
amount: &DepositOf<T>,
|
||||
state: &ContractState<T>,
|
||||
) -> Result<(), DispatchError>;
|
||||
}
|
||||
|
||||
/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged.
|
||||
///
|
||||
/// It uses [`pezframe_support::traits::fungible::Mutate`] in order to do accomplish the reserves.
|
||||
pub enum ReservingExt {}
|
||||
|
||||
/// Used to implement a type state pattern for the meter.
|
||||
///
|
||||
/// It is sealed and cannot be implemented outside of this module.
|
||||
pub trait State: private::Sealed {}
|
||||
|
||||
/// State parameter that constitutes a meter that is in its root state.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Root;
|
||||
|
||||
/// State parameter that constitutes a meter that is in its nested state.
|
||||
/// Its value indicates whether the nested meter has its own limit.
|
||||
#[derive(DefaultNoBound, RuntimeDebugNoBound)]
|
||||
pub enum Nested {
|
||||
#[default]
|
||||
DerivedLimit,
|
||||
OwnLimit,
|
||||
}
|
||||
|
||||
impl State for Root {}
|
||||
impl State for Nested {}
|
||||
|
||||
/// A type that allows the metering of consumed or freed storage of a single contract call stack.
|
||||
#[derive(DefaultNoBound, RuntimeDebugNoBound)]
|
||||
pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
|
||||
/// The limit of how much balance this meter is allowed to consume.
|
||||
limit: BalanceOf<T>,
|
||||
/// The amount of balance that was used in this meter and all of its already absorbed children.
|
||||
total_deposit: DepositOf<T>,
|
||||
/// The amount of storage changes that were recorded in this meter alone.
|
||||
own_contribution: Contribution<T>,
|
||||
/// List of charges that should be applied at the end of a contract stack execution.
|
||||
///
|
||||
/// We only have one charge per contract hence the size of this vector is
|
||||
/// limited by the maximum call depth.
|
||||
charges: Vec<Charge<T>>,
|
||||
/// We store the nested state to determine if it has a special limit for sub-call.
|
||||
nested: S,
|
||||
/// Type parameter only used in impls.
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
/// This type is used to describe a storage change when charging from the meter.
|
||||
#[derive(Default, RuntimeDebugNoBound)]
|
||||
pub struct Diff {
|
||||
/// How many bytes were added to storage.
|
||||
pub bytes_added: u32,
|
||||
/// How many bytes were removed from storage.
|
||||
pub bytes_removed: u32,
|
||||
/// How many storage items were added to storage.
|
||||
pub items_added: u32,
|
||||
/// How many storage items were removed from storage.
|
||||
pub items_removed: u32,
|
||||
}
|
||||
|
||||
impl Diff {
|
||||
/// Calculate how much of a charge or refund results from applying the diff and store it
|
||||
/// in the passed `info` if any.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// In case `None` is passed for `info` only charges are calculated. This is because refunds
|
||||
/// are calculated pro rata of the existing storage within a contract and hence need extract
|
||||
/// this information from the passed `info`.
|
||||
pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
|
||||
let per_byte = T::DepositPerByte::get();
|
||||
let per_item = T::DepositPerItem::get();
|
||||
let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
|
||||
let items_added = self.items_added.saturating_sub(self.items_removed);
|
||||
let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
|
||||
let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
|
||||
|
||||
// Without any contract info we can only calculate diffs which add storage
|
||||
let info = if let Some(info) = info {
|
||||
info
|
||||
} else {
|
||||
debug_assert_eq!(self.bytes_removed, 0);
|
||||
debug_assert_eq!(self.items_removed, 0);
|
||||
return bytes_deposit.saturating_add(&items_deposit);
|
||||
};
|
||||
|
||||
// Refunds are calculated pro rata based on the accumulated storage within the contract
|
||||
let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
|
||||
let items_removed = self.items_removed.saturating_sub(self.items_added);
|
||||
let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
|
||||
.unwrap_or_default()
|
||||
.min(FixedU128::from_u32(1));
|
||||
bytes_deposit = bytes_deposit
|
||||
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
|
||||
let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
|
||||
.unwrap_or_default()
|
||||
.min(FixedU128::from_u32(1));
|
||||
items_deposit = items_deposit
|
||||
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
|
||||
|
||||
// We need to update the contract info structure with the new deposits
|
||||
info.storage_bytes =
|
||||
info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
|
||||
info.storage_items =
|
||||
info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
|
||||
match &bytes_deposit {
|
||||
Deposit::Charge(amount) =>
|
||||
info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount),
|
||||
Deposit::Refund(amount) =>
|
||||
info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount),
|
||||
}
|
||||
match &items_deposit {
|
||||
Deposit::Charge(amount) =>
|
||||
info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount),
|
||||
Deposit::Refund(amount) =>
|
||||
info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount),
|
||||
}
|
||||
|
||||
bytes_deposit.saturating_add(&items_deposit)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diff {
|
||||
fn saturating_add(&self, rhs: &Self) -> Self {
|
||||
Self {
|
||||
bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
|
||||
bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
|
||||
items_added: self.items_added.saturating_add(rhs.items_added),
|
||||
items_removed: self.items_removed.saturating_add(rhs.items_removed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a contract.
|
||||
///
|
||||
/// In case of termination the beneficiary is indicated.
|
||||
#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
|
||||
pub enum ContractState<T: Config> {
|
||||
Alive,
|
||||
Terminated { beneficiary: AccountIdOf<T> },
|
||||
}
|
||||
|
||||
/// Records information to charge or refund a plain account.
|
||||
///
|
||||
/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
|
||||
/// this we can do all the refunds before doing any charge. This way a plain account can use
|
||||
/// more deposit than it has balance as along as it is covered by a refund. This
|
||||
/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
|
||||
/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
|
||||
/// call. In that case the limit is enforced once the call is returned, rolling it back if
|
||||
/// exhausted.
|
||||
#[derive(RuntimeDebugNoBound, Clone)]
|
||||
struct Charge<T: Config> {
|
||||
contract: T::AccountId,
|
||||
amount: DepositOf<T>,
|
||||
state: ContractState<T>,
|
||||
}
|
||||
|
||||
/// Records the storage changes of a storage meter.
|
||||
#[derive(RuntimeDebugNoBound)]
|
||||
enum Contribution<T: Config> {
|
||||
/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
|
||||
Alive(Diff),
|
||||
/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
|
||||
/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
|
||||
Checked(DepositOf<T>),
|
||||
/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
|
||||
/// in order to calculate the refund. Upon termination the `reducible_balance` in the
|
||||
/// contract's account is transferred to the [`beneficiary`].
|
||||
Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
|
||||
}
|
||||
|
||||
impl<T: Config> Contribution<T> {
|
||||
/// See [`Diff::update_contract`].
|
||||
fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
|
||||
match self {
|
||||
Self::Alive(diff) => diff.update_contract::<T>(info),
|
||||
Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) =>
|
||||
deposit.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Default for Contribution<T> {
|
||||
fn default() -> Self {
|
||||
Self::Alive(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that apply to all states.
|
||||
impl<T, E, S> RawMeter<T, E, S>
|
||||
where
|
||||
T: Config,
|
||||
E: Ext<T>,
|
||||
S: State + Default + Debug,
|
||||
{
|
||||
/// Create a new child that has its `limit`.
|
||||
/// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent.
|
||||
///
|
||||
/// This is called whenever a new subcall is initiated in order to track the storage
|
||||
/// usage for this sub call separately. This is necessary because we want to exchange balance
|
||||
/// with the current contract we are interacting with.
|
||||
pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
|
||||
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
|
||||
// If a special limit is specified higher than it is available,
|
||||
// we want to enforce the lesser limit to the nested meter, to fail in the sub-call.
|
||||
let limit = self.available().min(limit);
|
||||
if limit.is_zero() {
|
||||
RawMeter { limit: self.available(), ..Default::default() }
|
||||
} else {
|
||||
RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Absorb a child that was spawned to handle a sub call.
|
||||
///
|
||||
/// This should be called whenever a sub call comes to its end and it is **not** reverted.
|
||||
/// This does the actual balance transfer from/to `origin` and `contract` based on the
|
||||
/// overall storage consumption of the call. It also updates the supplied contract info.
|
||||
///
|
||||
/// In case a contract reverted the child meter should just be dropped in order to revert
|
||||
/// any changes it recorded.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `absorbed`: The child storage meter that should be absorbed.
|
||||
/// - `origin`: The origin that spawned the original root meter.
|
||||
/// - `contract`: The contract's account that this sub call belongs to.
|
||||
/// - `info`: The info of the contract in question. `None` if the contract was terminated.
|
||||
pub fn absorb(
|
||||
&mut self,
|
||||
absorbed: RawMeter<T, E, Nested>,
|
||||
contract: &T::AccountId,
|
||||
info: Option<&mut ContractInfo<T>>,
|
||||
) {
|
||||
let own_deposit = absorbed.own_contribution.update_contract(info);
|
||||
self.total_deposit = self
|
||||
.total_deposit
|
||||
.saturating_add(&absorbed.total_deposit)
|
||||
.saturating_add(&own_deposit);
|
||||
self.charges.extend_from_slice(&absorbed.charges);
|
||||
if !own_deposit.is_zero() {
|
||||
self.charges.push(Charge {
|
||||
contract: contract.clone(),
|
||||
amount: own_deposit,
|
||||
state: absorbed.contract_state(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of balance that is still available from the original `limit`.
|
||||
fn available(&self) -> BalanceOf<T> {
|
||||
self.total_deposit.available(&self.limit)
|
||||
}
|
||||
|
||||
/// Returns the state of the currently executed contract.
|
||||
fn contract_state(&self) -> ContractState<T> {
|
||||
match &self.own_contribution {
|
||||
Contribution::Terminated { deposit: _, beneficiary } =>
|
||||
ContractState::Terminated { beneficiary: beneficiary.clone() },
|
||||
_ => ContractState::Alive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that only apply to the root state.
|
||||
impl<T, E> RawMeter<T, E, Root>
|
||||
where
|
||||
T: Config,
|
||||
E: Ext<T>,
|
||||
{
|
||||
/// Create new storage meter for the specified `origin` and `limit`.
|
||||
///
|
||||
/// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible.
|
||||
pub fn new(
|
||||
origin: &Origin<T>,
|
||||
limit: Option<BalanceOf<T>>,
|
||||
min_leftover: BalanceOf<T>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
// Check the limit only if the origin is not root.
|
||||
return match origin {
|
||||
Origin::Root => Ok(Self {
|
||||
limit: limit.unwrap_or(T::DefaultDepositLimit::get()),
|
||||
..Default::default()
|
||||
}),
|
||||
Origin::Signed(o) => {
|
||||
let limit = E::check_limit(o, limit, min_leftover)?;
|
||||
Ok(Self { limit, ..Default::default() })
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// The total amount of deposit that should change hands as result of the execution
|
||||
/// that this meter was passed into. This will also perform all the charges accumulated
|
||||
/// in the whole contract stack.
|
||||
///
|
||||
/// This drops the root meter in order to make sure it is only called when the whole
|
||||
/// execution did finish.
|
||||
pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
|
||||
// Only refund or charge deposit if the origin is not root.
|
||||
let origin = match origin {
|
||||
Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
|
||||
Origin::Signed(o) => o,
|
||||
};
|
||||
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
|
||||
E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
|
||||
}
|
||||
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
|
||||
E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
|
||||
}
|
||||
Ok(self.total_deposit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that only apply to the nested state.
|
||||
impl<T, E> RawMeter<T, E, Nested>
|
||||
where
|
||||
T: Config,
|
||||
E: Ext<T>,
|
||||
{
|
||||
/// Charges `diff` from the meter.
|
||||
pub fn charge(&mut self, diff: &Diff) {
|
||||
match &mut self.own_contribution {
|
||||
Contribution::Alive(own) => *own = own.saturating_add(diff),
|
||||
_ => panic!("Charge is never called after termination; qed"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds a deposit charge.
|
||||
///
|
||||
/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
|
||||
/// change. This is the case when a `delegate_dependency` is added or removed, or when the
|
||||
/// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the
|
||||
/// deposit charge separately from the storage charge.
|
||||
pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
|
||||
self.total_deposit = self.total_deposit.saturating_add(&amount);
|
||||
self.charges.push(Charge { contract, amount, state: ContractState::Alive });
|
||||
}
|
||||
|
||||
/// Charges from `origin` a storage deposit for contract instantiation.
|
||||
///
|
||||
/// This immediately transfers the balance in order to create the account.
|
||||
pub fn charge_instantiate(
|
||||
&mut self,
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
contract_info: &mut ContractInfo<T>,
|
||||
code_info: &CodeInfo<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
|
||||
|
||||
// We need to make sure that the contract's account exists.
|
||||
let ed = Pallet::<T>::min_balance();
|
||||
self.total_deposit = Deposit::Charge(ed);
|
||||
T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
|
||||
|
||||
// A consumer is added at account creation and removed it on termination, otherwise the
|
||||
// runtime could remove the account. As long as a contract exists its account must exist.
|
||||
// With the consumer, a correct runtime cannot remove the account.
|
||||
System::<T>::inc_consumers(contract)?;
|
||||
|
||||
let deposit = contract_info.update_base_deposit(&code_info);
|
||||
let deposit = Deposit::Charge(deposit);
|
||||
|
||||
self.charge_deposit(contract.clone(), deposit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call to tell the meter that the currently executing contract was terminated.
|
||||
///
|
||||
/// This will manipulate the meter so that all storage deposit accumulated in
|
||||
/// `contract_info` will be refunded to the `origin` of the meter. And the free
|
||||
/// (`reducible_balance`) will be sent to the `beneficiary`.
|
||||
pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: T::AccountId) {
|
||||
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
|
||||
self.own_contribution = Contribution::Terminated {
|
||||
deposit: Deposit::Refund(info.total_deposit()),
|
||||
beneficiary,
|
||||
};
|
||||
}
|
||||
|
||||
/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
|
||||
/// as possible to allow later refunds to offset earlier charges.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We normally need to call this **once** for every call stack and not for every cross contract
|
||||
/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called
|
||||
/// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is
|
||||
/// used.
|
||||
pub fn enforce_limit(
|
||||
&mut self,
|
||||
info: Option<&mut ContractInfo<T>>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let deposit = self.own_contribution.update_contract(info);
|
||||
let total_deposit = self.total_deposit.saturating_add(&deposit);
|
||||
// We don't want to override a `Terminated` with a `Checked`.
|
||||
if matches!(self.contract_state(), ContractState::Alive) {
|
||||
self.own_contribution = Contribution::Checked(deposit);
|
||||
}
|
||||
if let Deposit::Charge(amount) = total_deposit {
|
||||
if amount > self.limit {
|
||||
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to
|
||||
/// enforce its special limit if needed.
|
||||
pub fn enforce_subcall_limit(
|
||||
&mut self,
|
||||
info: Option<&mut ContractInfo<T>>,
|
||||
) -> Result<(), DispatchError> {
|
||||
match self.nested {
|
||||
Nested::OwnLimit => self.enforce_limit(info),
|
||||
Nested::DerivedLimit => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Ext<T> for ReservingExt {
|
||||
fn check_limit(
|
||||
origin: &T::AccountId,
|
||||
limit: Option<BalanceOf<T>>,
|
||||
min_leftover: BalanceOf<T>,
|
||||
) -> Result<BalanceOf<T>, DispatchError> {
|
||||
// We are sending the `min_leftover` and the `min_balance` from the origin
|
||||
// account as part of a contract call. Hence origin needs to have those left over
|
||||
// as free balance after accounting for all deposits.
|
||||
let max = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite)
|
||||
.saturating_sub(min_leftover)
|
||||
.saturating_sub(Pallet::<T>::min_balance());
|
||||
let default = max.min(T::DefaultDepositLimit::get());
|
||||
let limit = limit.unwrap_or(default);
|
||||
ensure!(
|
||||
limit <= max &&
|
||||
matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success),
|
||||
<Error<T>>::StorageDepositNotEnoughFunds,
|
||||
);
|
||||
Ok(limit)
|
||||
}
|
||||
|
||||
fn charge(
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
amount: &DepositOf<T>,
|
||||
state: &ContractState<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
match amount {
|
||||
Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
|
||||
Deposit::Charge(amount) => {
|
||||
// This could fail if the `origin` does not have enough liquidity. Ideally, though,
|
||||
// this should have been checked before with `check_limit`.
|
||||
T::Currency::transfer_and_hold(
|
||||
&HoldReason::StorageDepositReserve.into(),
|
||||
origin,
|
||||
contract,
|
||||
*amount,
|
||||
Precision::Exact,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
)?;
|
||||
|
||||
Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld {
|
||||
from: origin.clone(),
|
||||
to: contract.clone(),
|
||||
amount: *amount,
|
||||
});
|
||||
},
|
||||
Deposit::Refund(amount) => {
|
||||
let transferred = T::Currency::transfer_on_hold(
|
||||
&HoldReason::StorageDepositReserve.into(),
|
||||
contract,
|
||||
origin,
|
||||
*amount,
|
||||
Precision::BestEffort,
|
||||
Restriction::Free,
|
||||
Fortitude::Polite,
|
||||
)?;
|
||||
|
||||
Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased {
|
||||
from: contract.clone(),
|
||||
to: origin.clone(),
|
||||
amount: transferred,
|
||||
});
|
||||
|
||||
if transferred < *amount {
|
||||
// This should never happen, if it does it means that there is a bug in the
|
||||
// runtime logic. In the rare case this happens we try to refund as much as we
|
||||
// can, thus the `Precision::BestEffort`.
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.",
|
||||
amount, contract, origin, transferred,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
if let ContractState::<T>::Terminated { beneficiary } = state {
|
||||
System::<T>::dec_consumers(&contract);
|
||||
// Whatever is left in the contract is sent to the termination beneficiary.
|
||||
T::Currency::transfer(
|
||||
&contract,
|
||||
&beneficiary,
|
||||
T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
|
||||
Preservation::Expendable,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
impl Sealed for super::Root {}
|
||||
impl Sealed for super::Nested {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
exec::AccountIdOf,
|
||||
tests::{Test, ALICE, BOB, CHARLIE},
|
||||
};
|
||||
use pezframe_support::parameter_types;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
type TestMeter = RawMeter<Test, TestExt, Root>;
|
||||
|
||||
parameter_types! {
|
||||
static TestExtTestValue: TestExt = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct LimitCheck {
|
||||
origin: AccountIdOf<Test>,
|
||||
limit: BalanceOf<Test>,
|
||||
min_leftover: BalanceOf<Test>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct Charge {
|
||||
origin: AccountIdOf<Test>,
|
||||
contract: AccountIdOf<Test>,
|
||||
amount: DepositOf<Test>,
|
||||
state: ContractState<Test>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TestExt {
|
||||
limit_checks: Vec<LimitCheck>,
|
||||
charges: Vec<Charge>,
|
||||
}
|
||||
|
||||
impl TestExt {
|
||||
fn clear(&mut self) {
|
||||
self.limit_checks.clear();
|
||||
self.charges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Ext<Test> for TestExt {
|
||||
fn check_limit(
|
||||
origin: &AccountIdOf<Test>,
|
||||
limit: Option<BalanceOf<Test>>,
|
||||
min_leftover: BalanceOf<Test>,
|
||||
) -> Result<BalanceOf<Test>, DispatchError> {
|
||||
let limit = limit.unwrap_or(42);
|
||||
TestExtTestValue::mutate(|ext| {
|
||||
ext.limit_checks
|
||||
.push(LimitCheck { origin: origin.clone(), limit, min_leftover })
|
||||
});
|
||||
Ok(limit)
|
||||
}
|
||||
|
||||
fn charge(
|
||||
origin: &AccountIdOf<Test>,
|
||||
contract: &AccountIdOf<Test>,
|
||||
amount: &DepositOf<Test>,
|
||||
state: &ContractState<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
TestExtTestValue::mutate(|ext| {
|
||||
ext.charges.push(Charge {
|
||||
origin: origin.clone(),
|
||||
contract: contract.clone(),
|
||||
amount: amount.clone(),
|
||||
state: state.clone(),
|
||||
})
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_ext() {
|
||||
TestExtTestValue::mutate(|ext| ext.clear())
|
||||
}
|
||||
|
||||
struct ChargingTestCase {
|
||||
origin: Origin<Test>,
|
||||
deposit: DepositOf<Test>,
|
||||
expected: TestExt,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StorageInfo {
|
||||
bytes: u32,
|
||||
items: u32,
|
||||
bytes_deposit: BalanceOf<Test>,
|
||||
items_deposit: BalanceOf<Test>,
|
||||
}
|
||||
|
||||
fn new_info(info: StorageInfo) -> ContractInfo<Test> {
|
||||
ContractInfo::<Test> {
|
||||
trie_id: Default::default(),
|
||||
code_hash: Default::default(),
|
||||
storage_bytes: info.bytes,
|
||||
storage_items: info.items,
|
||||
storage_byte_deposit: info.bytes_deposit,
|
||||
storage_item_deposit: info.items_deposit,
|
||||
storage_base_deposit: Default::default(),
|
||||
delegate_dependencies: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_reserves_balance_works() {
|
||||
clear_ext();
|
||||
|
||||
TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_charge_works() {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
|
||||
// an empty charge does not create a `Charge` entry
|
||||
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
|
||||
nested0.charge(&Default::default());
|
||||
meter.absorb(nested0, &BOB, None);
|
||||
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charging_works() {
|
||||
let test_cases = vec![
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::from_account_id(ALICE),
|
||||
deposit: Deposit::Refund(28),
|
||||
expected: TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(10),
|
||||
state: ContractState::Alive,
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(20),
|
||||
state: ContractState::Alive,
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(2),
|
||||
state: ContractState::Alive,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::Root,
|
||||
deposit: Deposit::Charge(0),
|
||||
expected: TestExt { limit_checks: vec![], charges: vec![] },
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap();
|
||||
assert_eq!(meter.available(), 100);
|
||||
|
||||
let mut nested0_info = new_info(StorageInfo {
|
||||
bytes: 100,
|
||||
items: 5,
|
||||
bytes_deposit: 100,
|
||||
items_deposit: 10,
|
||||
});
|
||||
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
|
||||
nested0.charge(&Diff {
|
||||
bytes_added: 108,
|
||||
bytes_removed: 5,
|
||||
items_added: 1,
|
||||
items_removed: 2,
|
||||
});
|
||||
nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
|
||||
|
||||
let mut nested1_info = new_info(StorageInfo {
|
||||
bytes: 100,
|
||||
items: 10,
|
||||
bytes_deposit: 100,
|
||||
items_deposit: 20,
|
||||
});
|
||||
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
|
||||
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
|
||||
nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
|
||||
|
||||
let mut nested2_info = new_info(StorageInfo {
|
||||
bytes: 100,
|
||||
items: 7,
|
||||
bytes_deposit: 100,
|
||||
items_deposit: 20,
|
||||
});
|
||||
let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
|
||||
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
|
||||
nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
|
||||
|
||||
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
|
||||
meter.absorb(nested0, &BOB, Some(&mut nested0_info));
|
||||
|
||||
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
|
||||
|
||||
assert_eq!(nested0_info.extra_deposit(), 112);
|
||||
assert_eq!(nested1_info.extra_deposit(), 110);
|
||||
assert_eq!(nested2_info.extra_deposit(), 100);
|
||||
|
||||
assert_eq!(TestExtTestValue::get(), test_case.expected)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn termination_works() {
|
||||
let test_cases = vec![
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::from_account_id(ALICE),
|
||||
deposit: Deposit::Refund(108),
|
||||
expected: TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(120),
|
||||
state: ContractState::Terminated { beneficiary: CHARLIE },
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(12),
|
||||
state: ContractState::Alive,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::Root,
|
||||
deposit: Deposit::Charge(0),
|
||||
expected: TestExt { limit_checks: vec![], charges: vec![] },
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap();
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
|
||||
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
|
||||
nested0.charge(&Diff {
|
||||
bytes_added: 5,
|
||||
bytes_removed: 1,
|
||||
items_added: 3,
|
||||
items_removed: 1,
|
||||
});
|
||||
nested0.charge(&Diff { items_added: 2, ..Default::default() });
|
||||
|
||||
let mut nested1_info = new_info(StorageInfo {
|
||||
bytes: 100,
|
||||
items: 10,
|
||||
bytes_deposit: 100,
|
||||
items_deposit: 20,
|
||||
});
|
||||
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
|
||||
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
|
||||
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
|
||||
nested1.terminate(&nested1_info, CHARLIE);
|
||||
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
|
||||
nested0.absorb(nested1, &CHARLIE, None);
|
||||
|
||||
meter.absorb(nested0, &BOB, None);
|
||||
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
|
||||
assert_eq!(TestExtTestValue::get(), test_case.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Shared utilities for testing contracts.
|
||||
//! This is not part of the tests module because it is made public for other crates to use.
|
||||
#![cfg(feature = "std")]
|
||||
use pezframe_support::weights::Weight;
|
||||
pub use pezsp_runtime::AccountId32;
|
||||
|
||||
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
|
||||
pub const BOB: AccountId32 = AccountId32::new([2u8; 32]);
|
||||
pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]);
|
||||
pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]);
|
||||
|
||||
pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024);
|
||||
pub mod builder;
|
||||
@@ -0,0 +1,220 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::GAS_LIMIT;
|
||||
use crate::{
|
||||
AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, Config,
|
||||
ContractExecResult, ContractInstantiateResult, DebugInfo, Determinism, EventRecordOf,
|
||||
ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight,
|
||||
};
|
||||
use codec::{Encode, HasCompact};
|
||||
use core::fmt::Debug;
|
||||
use pezframe_support::pezpallet_prelude::DispatchResultWithPostInfo;
|
||||
use paste::paste;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
/// Helper macro to generate a builder for contract API calls.
|
||||
macro_rules! builder {
|
||||
// Entry point to generate a builder for the given method.
|
||||
(
|
||||
$method:ident($($field:ident: $type:ty,)*) -> $result:ty;
|
||||
$($extra:item)*
|
||||
) => {
|
||||
paste!{
|
||||
builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result; $($extra)*);
|
||||
}
|
||||
};
|
||||
// Generate the builder struct and its methods.
|
||||
(
|
||||
$name:ident,
|
||||
$method:ident($($field:ident: $type:ty,)*) -> $result:ty;
|
||||
$($extra:item)*
|
||||
) => {
|
||||
#[doc = concat!("A builder to construct a ", stringify!($method), " call")]
|
||||
pub struct $name<T: Config> {
|
||||
$($field: $type,)*
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<T: Config> $name<T>
|
||||
where
|
||||
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
|
||||
{
|
||||
$(
|
||||
#[doc = concat!("Set the ", stringify!($field))]
|
||||
pub fn $field(mut self, value: $type) -> Self {
|
||||
self.$field = value;
|
||||
self
|
||||
}
|
||||
)*
|
||||
|
||||
#[doc = concat!("Build the ", stringify!($method), " call")]
|
||||
pub fn build(self) -> $result {
|
||||
Pallet::<T>::$method(
|
||||
$(self.$field,)*
|
||||
)
|
||||
}
|
||||
|
||||
$($extra)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder!(
|
||||
instantiate_with_code(
|
||||
origin: OriginFor<T>,
|
||||
value: BalanceOf<T>,
|
||||
gas_limit: Weight,
|
||||
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
|
||||
code: Vec<u8>,
|
||||
data: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
) -> DispatchResultWithPostInfo;
|
||||
|
||||
/// Create an [`InstantiateWithCodeBuilder`] with default values.
|
||||
pub fn instantiate_with_code(origin: OriginFor<T>, code: Vec<u8>) -> Self {
|
||||
Self {
|
||||
origin: origin,
|
||||
value: 0u32.into(),
|
||||
gas_limit: GAS_LIMIT,
|
||||
storage_deposit_limit: None,
|
||||
code,
|
||||
data: vec![],
|
||||
salt: vec![],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
builder!(
|
||||
instantiate(
|
||||
origin: OriginFor<T>,
|
||||
value: BalanceOf<T>,
|
||||
gas_limit: Weight,
|
||||
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
|
||||
code_hash: CodeHash<T>,
|
||||
data: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
) -> DispatchResultWithPostInfo;
|
||||
|
||||
/// Create an [`InstantiateBuilder`] with default values.
|
||||
pub fn instantiate(origin: OriginFor<T>, code_hash: CodeHash<T>) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
value: 0u32.into(),
|
||||
gas_limit: GAS_LIMIT,
|
||||
storage_deposit_limit: None,
|
||||
code_hash,
|
||||
data: vec![],
|
||||
salt: vec![],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
builder!(
|
||||
bare_instantiate(
|
||||
origin: AccountIdOf<T>,
|
||||
value: BalanceOf<T>,
|
||||
gas_limit: Weight,
|
||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||
code: Code<CodeHash<T>>,
|
||||
data: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
debug: DebugInfo,
|
||||
collect_events: CollectEvents,
|
||||
) -> ContractInstantiateResult<AccountIdOf<T>, BalanceOf<T>, EventRecordOf<T>>;
|
||||
|
||||
/// Build the instantiate call and unwrap the result.
|
||||
pub fn build_and_unwrap_result(self) -> InstantiateReturnValue<AccountIdOf<T>> {
|
||||
self.build().result.unwrap()
|
||||
}
|
||||
|
||||
/// Build the instantiate call and unwrap the account id.
|
||||
pub fn build_and_unwrap_account_id(self) -> AccountIdOf<T> {
|
||||
self.build().result.unwrap().account_id
|
||||
}
|
||||
|
||||
pub fn bare_instantiate(origin: AccountIdOf<T>, code: Code<CodeHash<T>>) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
value: 0u32.into(),
|
||||
gas_limit: GAS_LIMIT,
|
||||
storage_deposit_limit: None,
|
||||
code,
|
||||
data: vec![],
|
||||
salt: vec![],
|
||||
debug: DebugInfo::Skip,
|
||||
collect_events: CollectEvents::Skip,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
builder!(
|
||||
call(
|
||||
origin: OriginFor<T>,
|
||||
dest: AccountIdLookupOf<T>,
|
||||
value: BalanceOf<T>,
|
||||
gas_limit: Weight,
|
||||
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
|
||||
data: Vec<u8>,
|
||||
) -> DispatchResultWithPostInfo;
|
||||
|
||||
/// Create a [`CallBuilder`] with default values.
|
||||
pub fn call(origin: OriginFor<T>, dest: AccountIdLookupOf<T>) -> Self {
|
||||
CallBuilder {
|
||||
origin,
|
||||
dest,
|
||||
value: 0u32.into(),
|
||||
gas_limit: GAS_LIMIT,
|
||||
storage_deposit_limit: None,
|
||||
data: vec![],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
builder!(
|
||||
bare_call(
|
||||
origin: AccountIdOf<T>,
|
||||
dest: AccountIdOf<T>,
|
||||
value: BalanceOf<T>,
|
||||
gas_limit: Weight,
|
||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||
data: Vec<u8>,
|
||||
debug: DebugInfo,
|
||||
collect_events: CollectEvents,
|
||||
determinism: Determinism,
|
||||
) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>>;
|
||||
|
||||
/// Build the call and unwrap the result.
|
||||
pub fn build_and_unwrap_result(self) -> ExecReturnValue {
|
||||
self.build().result.unwrap()
|
||||
}
|
||||
|
||||
/// Create a [`BareCallBuilder`] with default values.
|
||||
pub fn bare_call(origin: AccountIdOf<T>, dest: AccountIdOf<T>) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
dest,
|
||||
value: 0u32.into(),
|
||||
gas_limit: GAS_LIMIT,
|
||||
storage_deposit_limit: None,
|
||||
data: vec![],
|
||||
debug: DebugInfo::Skip,
|
||||
collect_events: CollectEvents::Skip,
|
||||
determinism: Determinism::Enforced,
|
||||
}
|
||||
}
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user