diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..27df093d28 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,153 @@ +version: 2.1 +commands: + install-rust: + steps: + - run: + name: Install Rust + command: | + sudo apt-get update + sudo apt-get -y install apt-utils cmake pkg-config libssl-dev git llvm clang + if [ ! -d /home/circleci/.cargo ]; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + fi + source ~/.cargo/env + rustup install $RUST_VERSION + rustup default stable + rustup install nightly + rustup target add wasm32-unknown-unknown --toolchain=nightly + export RUSTC_WRAPPER="" # sccache is uninstalled at this point so it must be unset here for `wasm-gc` install + command -v wasm-gc || cargo install --git https://github.com/alexcrichton/wasm-gc --force + rustc --version; cargo --version; rustup --version + install-sccache: + steps: + - run: + name: Install sccache + command: | + curl -L https://github.com/mozilla/sccache/releases/download/0.2.10/sccache-0.2.10-x86_64-unknown-linux-musl.tar.gz | tar -xz + chmod +x sccache-0.2.10-x86_64-unknown-linux-musl/sccache + mv sccache-0.2.10-x86_64-unknown-linux-musl/sccache ~/.cargo/bin/sccache + sccache --version + restore-cache: + steps: + - restore_cache: + name: Restore sccache + key: sccache-{{ arch }}-{{ .Environment.CIRCLE_JOB }} + save-cache: + steps: + - save_cache: + name: Save sccache + # We use {{ epoch }} to always upload a fresh cache: + # Of course, restore_cache will not find this exact key, + # but it will fall back to the closest key (aka the most recent). + # See https://discuss.circleci.com/t/add-mechanism-to-update-existing-cache-key/9014/13 + key: sccache-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ epoch }} + paths: + - "~/.cache/sccache" + save-target-cache: + steps: + - save_cache: + name: Save target cache + paths: + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/debug/incremental + key: target-cache-{{ arch }}-{{ .Environment.RUST_VERSION }}-{{ .Environment.CIRCLE_BRANCH }}-{{ epoch }} + restore-target-cache: + steps: + - restore_cache: + name: Restore target cache + key: target-cache-{{ arch }}-{{ .Environment.RUST_VERSION }}-{{ .Environment.CIRCLE_BRANCH }} + cargo-check: + steps: + - run: + name: Build + command: cargo check + no_output_timeout: 30m + cargo-build-test: + steps: + - run: + command: cargo test --no-run --release --all + no_output_timeout: 30m + cargo-run-test: + steps: + - run: + command: cargo test --release --all + no_output_timeout: 30m +jobs: + build: + machine: + image: ubuntu-1604:201903-01 + steps: + - run: echo "successfully built and tested" + build-bin: + machine: + image: ubuntu-1604:201903-01 + resource_class: large + environment: + BASH_ENV: ~/.cargo/env + RUST_VERSION: 1.41.0 + RUSTC_WRAPPER: sccache + SCCACHE_CACHE_SIZE: 10G + steps: + - checkout + - install-rust + - install-sccache + - restore-cache + - cargo-check + - save-cache + build-test-and-run: + machine: + image: ubuntu-1604:201903-01 + resource_class: large + environment: + BASH_ENV: ~/.cargo/env + RUST_VERSION: 1.41.0 + RUSTC_WRAPPER: sccache + SCCACHE_CACHE_SIZE: 10G + steps: + - checkout + - install-rust + - install-sccache + - restore-cache + - cargo-build-test + - save-cache + - cargo-run-test + publish-docker: + machine: + image: ubuntu-1604:201903-01 + steps: + - checkout + - run: + name: Build and publish Docker image + command: | + docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD + if [ -z "${CIRCLE_TAG}" ]; then + GIT_SHORT="${CIRCLE_SHA1:0:8}" + VERSION="$(grep package -C 5 Cargo.toml | grep version | cut -d \" -f2)" + DOCKER_TAG="$VERSION-$GIT_SHORT" + else + DOCKER_TAG="${CIRCLE_TAG}" + fi + docker build --pull -t plugnet/plugblockchain:latest -t plugnet/plugblockchain:$DOCKER_TAG -f .maintain/Dockerfile . + docker push plugnet/plugblockchain:$DOCKER_TAG + docker push plugnet/plugblockchain:latest + no_output_timeout: 60m +workflows: + version: 2 + build-test-publish: + jobs: + - build-bin + - build-test-and-run + - build: + requires: + - build-bin + - build-test-and-run + - publish-docker: + requires: + - build + filters: + branches: + only: + - /^[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+)*$/ + - master diff --git a/.maintain/Dockerfile b/.maintain/Dockerfile index 7cba85c544..0dfd84b933 100644 --- a/.maintain/Dockerfile +++ b/.maintain/Dockerfile @@ -1,34 +1,37 @@ # Note: We don't use Alpine and its packaged Rust/Cargo because they're too often out of date, -# preventing them from being used to build Substrate/Polkadot. +# preventing them from being used to build Substrate/Polkadot/Plug. FROM phusion/baseimage:0.10.2 as builder -LABEL maintainer="chevdor@gmail.com" -LABEL description="This is the build stage for Substrate. Here we create the binary." +LABEL description="Pl^g build image: The Pl^g binary is built here." ENV DEBIAN_FRONTEND=noninteractive ARG PROFILE=release -WORKDIR /substrate +WORKDIR /plug -COPY . /substrate +COPY . /plug RUN apt-get update && \ apt-get dist-upgrade -y -o Dpkg::Options::="--force-confold" && \ - apt-get install -y cmake pkg-config libssl-dev git clang + apt-get install -y \ + clang \ + cmake \ + git \ + libssl-dev \ + pkg-config RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \ export PATH="$PATH:$HOME/.cargo/bin" && \ + rustup target add wasm32-unknown-unknown && \ rustup toolchain install nightly && \ rustup target add wasm32-unknown-unknown --toolchain nightly && \ - rustup default nightly && \ rustup default stable && \ cargo build "--$PROFILE" # ===== SECOND STAGE ====== FROM phusion/baseimage:0.10.2 -LABEL maintainer="chevdor@gmail.com" -LABEL description="This is the 2nd stage: a very small image where we copy the Substrate binary." +LABEL description="Pl^g runner image: A minimal image for running Pl^g." ARG PROFILE=release RUN mv /usr/share/ca* /tmp && \ @@ -36,20 +39,20 @@ RUN mv /usr/share/ca* /tmp && \ mv /tmp/ca-certificates /usr/share/ && \ mkdir -p /root/.local/share/Polkadot && \ ln -s /root/.local/share/Polkadot /data && \ - useradd -m -u 1000 -U -s /bin/sh -d /substrate substrate + useradd -m -u 1000 -U -s /bin/sh -d /plug plug -COPY --from=builder /substrate/target/$PROFILE/substrate /usr/local/bin +COPY --from=builder /plug/target/$PROFILE/plug /usr/local/bin # checks -RUN ldd /usr/local/bin/substrate && \ - /usr/local/bin/substrate --version +RUN ldd /usr/local/bin/plug && \ + /usr/local/bin/plug --version # Shrinking RUN rm -rf /usr/lib/python* && \ rm -rf /usr/bin /usr/sbin /usr/share/man -USER substrate +USER plug EXPOSE 30333 9933 9944 VOLUME ["/data"] -CMD ["/usr/local/bin/substrate"] +CMD ["/usr/local/bin/plug"] diff --git a/.maintain/rename-crates-for-2.0.sh b/.maintain/rename-crates-for-2.0.sh index 5d873d26cd..69e665efde 100755 --- a/.maintain/rename-crates-for-2.0.sh +++ b/.maintain/rename-crates-for-2.0.sh @@ -76,11 +76,11 @@ TO_RENAME=( "sr-api-test sp-api-test" "sr-arithmetic sp-arithmetic" "sr-arithmetic-fuzzer sp-arithmetic-fuzzer" - "sr-io sp-io" + "sp-io sp-io" "sr-primitives sp-runtime" "sr-sandbox sp-sandbox" "sr-staking-primitives sp-staking" - "sr-std sp-std" + "sp-std sp-std" "sr-version sp-version" "substrate-state-machine sp-state-machine" "substrate-transaction-pool-runtime-api sp-transaction-pool" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..32ce076d3f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,80 @@ +# Changelog +Track changes made between Plug and it's upstream project `Substrate`. +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## Added +- Add `MultiCurrencyAccounting` trait to support multi currency accounting in contracts module (#39) + +- `frame/contracts/src/exec.rs` + - Add optional doughnut type to `ExecutionContext` struct (#44) + - Add `Ext::doughnut()` to get an optional doughnut from a contract (#44) + +- `frame/system/src/lib.rs` + - Add `fn ensure_verified_contract_call()` to verify a doughnut attached to origin at `Contract::call()` (#47) + +- `frame/support/src/additional_traits.rs` + - Add `DummyDispatchVerifier` struct for easier mock up in tests (#47) + +- `frame/contracts/src/exec.rs` + - Add `DelegatedRuntimeCall` variant to `DeferredAction` enum (#48) + - Add `fn note_delegated_dispatch_call()` to dispatch a delegated contract call (#48) + +- `frame/contracts/src/wasm/runtime.rs` + - Add `ext_delegated_dispatch_call` method to dispatch a delegated contract cal (#48) + +## Changed +- Renamed trait `AssetIdProvider` to `AssetIdAuthority` to reflect it's 'read from chain' behaviour (#39) +- Make GA imbalance types currency aware so that issuance is managed properly on Drop (#39) +- Generic asset create() is root only and requires the root account nominates an owner for the new currency. +- Create flexible interface for handling contract gas payment + +- `frame/contracts/src/wasm/runtime.rs` + - Change `ext_call` method to verify doughnut if attached to a contract (#44) + +------ + +## [1.0.0-rc1] - 2019-10-21 + +### Added +- `prml/attestation/*` + - Added attestation runtime module + +- `prml/doughnut/*` + - Add `PlugDoughnut` wrapper struct to allow doughnut integration with `SignedExtension` hooks + - Add `PlugDoughnutDispatcher` as the defacto Plug implementor for `DelegatedDispatchVerifier` + +- `node/runtime/src/lib.rs` + - Add doughnut proof as an optional first parameter to the `node/runtime` `SignedExtra` payload allowing doughnut's to be added to extrinsics + - Add `DelegatedDispatchVerifier` and `Doughnut` proof type to `system::Trait` type bounds + +- `primitives/runtime/src/traits.rs` + - Blanket impl SignedExtension for `Option` to allow Optional in extrinsics + - Add `MaybeDoughnut` trait for SignedExtension type to allow extracting doughnut from `SignedExtra` tuple + - impl `MaybeDoughnut` for SignedExtension macro tuple of all lengths + +### Changed +- `core/sr-primitives/src/traits.rs` + - Make trait `SignedExtension::pre_dispatch` method receive self by reference (`&self`), instead of move (`self`) + +- `frame/staking/*` + - Add `RewardCurrency` type to allow paying out staking rewards in a different currency to the staked currency + - Change `fn make_payout()` so that RewardCurrency is paid to the stash account and not added to the total stake, if the reward currency is not the staked currency + +- `frame/system/src/lib.rs` and `frame/support/src/origin.rs` + - Add `DelegatedOrigin` variant to `RawOrigin` for delegated transactions + - Add `MaybeDoughnutRef` trait for extracting doughnut proof from `origin` without move in runtime module methods + +- `frame/support/src/dispatch.rs` + - Add `DelegatedDispatchVerifier` check to `decl_module!` expansion. This allows doughnut proofs to be checked when an extrinsic is dispatched using the `::DelegatedDispatchVerifier` impl + +- Renamed binary to `plug` changes made to (`Cargo.toml`s and `Dockerfile` to support this) + +### Removed + +- The majority of `docs/` is substrate specific and has been removed +- `README.adoc` is substrate specific and has been removed diff --git a/Cargo.lock b/Cargo.lock index 48b14599b1..2834d5744d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,6 +326,12 @@ dependencies = [ "which 2.0.1", ] +[[package]] +name = "bit_reverse" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99528ca30abb9495c7e106bf7c3177b257c62040fc0f2909fe470b0f43097296" + [[package]] name = "bitflags" version = "1.2.1" @@ -1135,6 +1141,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" +[[package]] +name = "doughnut_rs" +version = "0.3.0" +source = "git+https://github.com/cennznet/doughnut-rs?branch=0.3.0#0bbfb54f9c353b005b34659137e34d91383f6c71" +dependencies = [ + "bit_reverse", + "ed25519-dalek", + "parity-scale-codec", + "primitive-types", + "schnorrkel 0.1.1", +] + [[package]] name = "ed25519-dalek" version = "1.0.0-pre.3" @@ -1483,9 +1501,11 @@ dependencies = [ "pallet-indices", "pallet-transaction-payment", "parity-scale-codec", + "prml-doughnut", "serde", "sp-core", "sp-io", + "sp-keyring", "sp-runtime", "sp-std", ] @@ -3571,6 +3591,7 @@ dependencies = [ "pallet-utility", "pallet-vesting", "parity-scale-codec", + "prml-doughnut", "rustc-hex", "serde", "sp-api", @@ -3636,6 +3657,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", + "prml-doughnut", "serde", "sp-api", "sp-block-builder", @@ -4397,6 +4419,7 @@ dependencies = [ "frame-system", "pallet-authorship", "pallet-balances", + "pallet-generic-asset", "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", @@ -4934,6 +4957,30 @@ dependencies = [ "uint", ] +[[package]] +name = "prml-attestation" +version = "2.0.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "sp-core", +] + +[[package]] +name = "prml-doughnut" +version = "2.0.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", +] + [[package]] name = "proc-macro-crate" version = "0.1.4" @@ -5916,7 +5963,7 @@ dependencies = [ "sc-network-test", "sc-service", "sc-telemetry", - "schnorrkel", + "schnorrkel 0.8.5", "serde", "sp-api", "sp-application-crypto", @@ -6263,6 +6310,7 @@ dependencies = [ "unsigned-varint 0.3.1", "void", "wasm-timer", + "yamux", "zeroize 1.1.0", ] @@ -6614,6 +6662,24 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "schnorrkel" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eff518f9bed3d803a0d002af0ab96339b0ebbedde3bec98a684986134b7a39" +dependencies = [ + "clear_on_drop", + "curve25519-dalek 1.2.3", + "ed25519-dalek", + "failure", + "merlin", + "rand 0.6.5", + "rand_chacha 0.1.1", + "sha2", + "sha3", + "subtle 2.2.2", +] + [[package]] name = "schnorrkel" version = "0.8.5" @@ -7118,7 +7184,7 @@ name = "sp-consensus-babe" version = "0.8.0-dev" dependencies = [ "parity-scale-codec", - "schnorrkel", + "schnorrkel 0.8.5", "sp-api", "sp-application-crypto", "sp-consensus", @@ -7165,7 +7231,7 @@ dependencies = [ "rand 0.7.3", "regex", "rustc-hex", - "schnorrkel", + "schnorrkel 0.8.5", "serde", "serde_json", "sha2", @@ -7301,6 +7367,7 @@ dependencies = [ name = "sp-runtime" version = "2.0.0-dev" dependencies = [ + "doughnut_rs", "impl-trait-for-tuples", "log 0.4.8", "parity-scale-codec", @@ -7654,7 +7721,7 @@ checksum = "3be511be555a3633e71739a79e4ddff6a6aaa6579fa6114182a51d72c3eb93c5" dependencies = [ "hmac", "pbkdf2", - "schnorrkel", + "schnorrkel 0.8.5", "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 1ac2beb052..08335d27a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,8 @@ members = [ "primitives/transaction-pool", "primitives/trie", "primitives/wasm-interface", + "prml/attestation", + "prml/doughnut", "test-utils/client", "test-utils/runtime", "test-utils/runtime/client", diff --git a/README.md b/README.md index cd00013d1a..92ed8a3c37 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,7 @@ -# Substrate · [![GitHub license](https://img.shields.io/github/license/paritytech/substrate)](LICENSE) [![GitLab Status](https://gitlab.parity.io/parity/substrate/badges/master/pipeline.svg)](https://gitlab.parity.io/parity/substrate/pipelines) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.adoc) +# Plug +[![CircleCI](https://circleci.com/gh/plugblockchain/plug-blockchain.svg?style=svg)](https://circleci.com/gh/plugblockchain/plug-blockchain) -Substrate is a next-generation framework for blockchain innovation. +The fully permissioned blockchain framework built on [Substrate](https://github.com/paritytech/substrate). -## Trying it out - -Simply go to [substrate.dev](https://substrate.dev) and follow the [getting started](https://substrate.dev/docs/en/overview/getting-started/) instructions. - -## Contributions & Code of Conduct - -Please follow the contributions guidelines as outlined in [`docs/CONTRIBUTING.adoc`](docs/CONTRIBUTING.adoc). In all communications and contributions, this project follows the [Contributor Covenant Code of Conduct](docs/CODE_OF_CONDUCT.adoc). - -## Security - -The security policy and procedures can be found in [`docs/SECURITY.md`](docs/SECURITY.md). - -## License - -Substrate is [GPL 3.0 licensed](LICENSE). +## Build +See https://github.com/paritytech/substrate#6-building diff --git a/bin/node-template/pallets/template/src/mock.rs b/bin/node-template/pallets/template/src/mock.rs index 2ea81ffb45..d7c723a32e 100644 --- a/bin/node-template/pallets/template/src/mock.rs +++ b/bin/node-template/pallets/template/src/mock.rs @@ -39,6 +39,8 @@ impl system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index b5a89febce..95d10655b4 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -22,6 +22,7 @@ timestamp = { version = "2.0.0-dev", default-features = false, package = "pallet transaction-payment = { version = "2.0.0-dev", default-features = false, package = "pallet-transaction-payment", path = "../../../frame/transaction-payment" } frame-executive = { version = "2.0.0-dev", default-features = false, path = "../../../frame/executive" } serde = { version = "1.0.101", optional = true, features = ["derive"] } + sp-api = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/api" } sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "2.0.0-dev"} sp-consensus-aura = { version = "0.8.0-dev", default-features = false, path = "../../../primitives/consensus/aura" } @@ -34,6 +35,7 @@ sp-session = { version = "2.0.0-dev", default-features = false, path = "../../.. sp-std = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/std" } sp-transaction-pool = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } sp-version = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/version" } +prml-doughnut = { path = "../../../prml/doughnut", default-features = false } template = { version = "2.0.0-dev", default-features = false, path = "../pallets/template", package = "pallet-template" } @@ -68,5 +70,6 @@ std = [ "system/std", "timestamp/std", "transaction-payment/std", + "prml-doughnut/std", "template/std", ] diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index afc18177ce..34abde93e9 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -25,16 +25,19 @@ use sp_version::RuntimeVersion; #[cfg(feature = "std")] use sp_version::NativeVersion; +use prml_doughnut::{DoughnutRuntime, PlugDoughnut}; + // A few exports that help ease life for downstream crates. #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use timestamp::Call as TimestampCall; pub use balances::Call as BalancesCall; -pub use sp_runtime::{Permill, Perbill}; +pub use sp_runtime::{Permill, Perbill, Doughnut}; pub use frame_support::{ StorageValue, construct_runtime, parameter_types, traits::Randomness, weights::Weight, + additional_traits::DummyDispatchVerifier, }; /// Importing a template pallet @@ -149,6 +152,10 @@ impl system::Trait for Runtime { type Origin = Origin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; + /// The runtime proof of delegation type (Doughnut) + type Doughnut = PlugDoughnut; + /// The runtime delegated dispatch verifier + type DelegatedDispatchVerifier = DummyDispatchVerifier; /// Maximum weight of each block. type MaximumBlockWeight = MaximumBlockWeight; /// Maximum size of all encoded transactions (in bytes) that are allowed in one block. @@ -169,6 +176,13 @@ impl system::Trait for Runtime { type AccountData = balances::AccountData; } +impl DoughnutRuntime for Runtime { + type AccountId = ::AccountId; + type Call = Call; + type Doughnut = ::Doughnut; + type TimestampProvider = timestamp::Module; +} + impl aura::Trait for Runtime { type AuthorityId = AuraId; } @@ -275,6 +289,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The SignedExtension to the basic transaction logic. pub type SignedExtra = ( + Option>, system::CheckVersion, system::CheckGenesis, system::CheckEra, @@ -355,7 +370,6 @@ impl_runtime_apis! { fn slot_duration() -> u64 { Aura::slot_duration() } - fn authorities() -> Vec { Aura::authorities() } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 7f17f1eaa6..3a69107384 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "node-cli" version = "2.0.0-dev" -authors = ["Parity Technologies "] -description = "Substrate node implementation in Rust." +authors = ["Plug Developers ", "Parity Technologies "] +description = "Plug node implementation in Rust." build = "build.rs" edition = "2018" license = "GPL-3.0" -default-run = "substrate" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" +default-run = "plug" +homepage = "https://centrality.ai/" +repository = "https://github.com/plugblockchain/plug-blockchain" [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on linux, see @@ -16,13 +16,13 @@ repository = "https://github.com/paritytech/substrate/" wasm-opt = false [badges] -travis-ci = { repository = "paritytech/substrate", branch = "master" } +circle-ci = { repository = "plugblockchain/plug-blockchain", branch = "develop" } maintenance = { status = "actively-developed" } -is-it-maintained-issue-resolution = { repository = "paritytech/substrate" } -is-it-maintained-open-issues = { repository = "paritytech/substrate" } +is-it-maintained-issue-resolution = { repository = "plugblockchain/plug-blockchain" } +is-it-maintained-open-issues = { repository = "plugblockchain/plug-blockchain" } [[bin]] -name = "substrate" +name = "plug" path = "bin/main.rs" required-features = ["cli"] diff --git a/bin/node/cli/bin/main.rs b/bin/node/cli/bin/main.rs index 8c4412667b..084612ad58 100644 --- a/bin/node/cli/bin/main.rs +++ b/bin/node/cli/bin/main.rs @@ -14,19 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Substrate Node CLI +//! Plug Node CLI #![warn(missing_docs)] fn main() -> sc_cli::Result<()> { let version = sc_cli::VersionInfo { - name: "Substrate Node", + name: "Plug Node", commit: env!("VERGEN_SHA_SHORT"), version: env!("CARGO_PKG_VERSION"), - executable_name: "substrate", - author: "Parity Technologies ", - description: "Generic substrate node", - support_url: "https://github.com/paritytech/substrate/issues/new", + executable_name: "plug", + author: "Plug Developers ", + description: "Generic plug node", + support_url: "https://github.com/plugblockchain/plug-blockchain/issues/new", copyright_start_year: 2017, }; diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index d163cdf391..353951d7d3 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -60,10 +60,6 @@ pub type ChainSpec = sc_service::ChainSpec< GenesisConfig, Extensions, >; -/// Flaming Fir testnet generator -pub fn flaming_fir_config() -> Result { - ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..]) -} fn session_keys( grandpa: GrandpaId, diff --git a/bin/node/cli/src/factory_impl.rs b/bin/node/cli/src/factory_impl.rs index 1d1eabe29c..2fd15c73d8 100644 --- a/bin/node/cli/src/factory_impl.rs +++ b/bin/node/cli/src/factory_impl.rs @@ -51,6 +51,7 @@ type Number = <::Header as HeaderT>::Number; impl FactoryState { fn build_extra(index: node_primitives::Index, phase: u64) -> node_runtime::SignedExtra { ( + None, frame_system::CheckVersion::new(), frame_system::CheckGenesis::new(), frame_system::CheckEra::from(Era::mortal(256, phase)), @@ -122,7 +123,7 @@ impl RuntimeAdapter for FactoryState { (*amount).into() ) ) - }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ())) + }, key, ((), version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ())) } fn inherent_extrinsics(&self) -> InherentData { diff --git a/bin/node/cli/src/lib.rs b/bin/node/cli/src/lib.rs index 789d6a6913..829e6b7d80 100644 --- a/bin/node/cli/src/lib.rs +++ b/bin/node/cli/src/lib.rs @@ -55,8 +55,6 @@ pub enum ChainSpec { Development, /// Whatever the current runtime is, with simple Alice/Bob auths. LocalTestnet, - /// The Flaming Fir testnet. - FlamingFir, /// Whatever the current runtime is with the "global testnet" defaults. StagingTestnet, } @@ -65,7 +63,6 @@ pub enum ChainSpec { impl ChainSpec { pub(crate) fn load(self) -> Result { Ok(match self { - ChainSpec::FlamingFir => chain_spec::flaming_fir_config()?, ChainSpec::Development => chain_spec::development_config(), ChainSpec::LocalTestnet => chain_spec::local_testnet_config(), ChainSpec::StagingTestnet => chain_spec::staging_testnet_config(), @@ -76,7 +73,6 @@ impl ChainSpec { match s { "dev" => Some(ChainSpec::Development), "local" => Some(ChainSpec::LocalTestnet), - "" | "fir" | "flaming-fir" => Some(ChainSpec::FlamingFir), "staging" => Some(ChainSpec::StagingTestnet), _ => None, } diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 70dd0521de..66bad121de 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -586,6 +586,7 @@ mod tests { let check_weight = frame_system::CheckWeight::new(); let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0); let extra = ( + None, check_version, check_genesis, check_era, @@ -597,7 +598,7 @@ mod tests { let raw_payload = SignedPayload::from_raw( function, extra, - (version, genesis_hash, genesis_hash, (), (), (), ()) + ((), version, genesis_hash, genesis_hash, (), (), (), ()) ); let signature = raw_payload.using_encoded(|payload| { signer.sign(payload) diff --git a/bin/node/cli/tests/build_spec_works.rs b/bin/node/cli/tests/build_spec_works.rs index 2eca71a5b5..c3cb82d6a0 100644 --- a/bin/node/cli/tests/build_spec_works.rs +++ b/bin/node/cli/tests/build_spec_works.rs @@ -21,8 +21,7 @@ use tempfile::tempdir; #[test] fn build_spec_works() { let base_path = tempdir().expect("could not create a temp dir"); - - let output = Command::new(cargo_bin("substrate")) + let output = Command::new(cargo_bin("plug")) .args(&["build-spec", "--dev", "-d"]) .arg(base_path.path()) .output() diff --git a/bin/node/cli/tests/check_block_works.rs b/bin/node/cli/tests/check_block_works.rs index e4c93c9e88..cee913c506 100644 --- a/bin/node/cli/tests/check_block_works.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -26,10 +26,10 @@ mod common; fn check_block_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_command_for_a_while(base_path.path(), false); + common::run_dev_node_for_a_while(base_path.path()); - let status = Command::new(cargo_bin("substrate")) - .args(&["check-block", "-d"]) + let status = Command::new(cargo_bin("plug")) + .args(&["check-block", "--dev", "--pruning", "archive", "-d"]) .arg(base_path.path()) .arg("1") .status() diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index 682a30bcc0..46988b3a5d 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -27,13 +27,18 @@ use nix::unistd::Pid; /// /// Returns the `Some(exit status)` or `None` if the process did not finish in the given time. pub fn wait_for(child: &mut Child, secs: usize) -> Option { - for _ in 0..secs { + for i in 0..secs { match child.try_wait().unwrap() { - Some(status) => return Some(status), + Some(status) => { + if i > 5 { + eprintln!("Child process took {} seconds to exit gracefully", i); + } + return Some(status) + }, None => thread::sleep(Duration::from_secs(1)), } } - eprintln!("Took to long to exit. Killing..."); + eprintln!("Took too long to exit (> {} seconds). Killing...", secs); let _ = child.kill(); child.wait().unwrap(); @@ -41,14 +46,11 @@ pub fn wait_for(child: &mut Child, secs: usize) -> Option { } /// Run the node for a while (30 seconds) -pub fn run_command_for_a_while(base_path: &Path, dev: bool) { - let mut cmd = Command::new(cargo_bin("substrate")); - - if dev { - cmd.arg("--dev"); - } +pub fn run_dev_node_for_a_while(base_path: &Path) { + let mut cmd = Command::new(cargo_bin("plug")); let mut cmd = cmd + .args(&["--dev"]) .arg("-d") .arg(base_path) .spawn() diff --git a/bin/node/cli/tests/factory.rs b/bin/node/cli/tests/factory.rs index 2930cd52e2..a2f72a3839 100644 --- a/bin/node/cli/tests/factory.rs +++ b/bin/node/cli/tests/factory.rs @@ -26,7 +26,7 @@ mod common; fn factory_works() { let base_path = tempdir().expect("could not create a temp dir"); - let status = Command::new(cargo_bin("substrate")) + let status = Command::new(cargo_bin("plug")) .stdout(Stdio::null()) .args(&["factory", "--dev", "-d"]) .arg(base_path.path()) diff --git a/bin/node/cli/tests/import_export_and_revert_work.rs b/bin/node/cli/tests/import_export_and_revert_work.rs index e109aa279e..2b46666943 100644 --- a/bin/node/cli/tests/import_export_and_revert_work.rs +++ b/bin/node/cli/tests/import_export_and_revert_work.rs @@ -27,10 +27,10 @@ fn import_export_and_revert_work() { let base_path = tempdir().expect("could not create a temp dir"); let exported_blocks = base_path.path().join("exported_blocks"); - common::run_command_for_a_while(base_path.path(), false); + common::run_dev_node_for_a_while(base_path.path()); - let status = Command::new(cargo_bin("substrate")) - .args(&["export-blocks", "-d"]) + let status = Command::new(cargo_bin("plug")) + .args(&["export-blocks", "--dev", "--pruning", "archive", "-d"]) .arg(base_path.path()) .arg(&exported_blocks) .status() @@ -42,16 +42,16 @@ fn import_export_and_revert_work() { let _ = fs::remove_dir_all(base_path.path().join("db")); - let status = Command::new(cargo_bin("substrate")) - .args(&["import-blocks", "-d"]) + let status = Command::new(cargo_bin("plug")) + .args(&["import-blocks", "--dev", "--pruning", "archive", "-d"]) .arg(base_path.path()) .arg(&exported_blocks) .status() .unwrap(); assert!(status.success()); - let status = Command::new(cargo_bin("substrate")) - .args(&["revert", "-d"]) + let status = Command::new(cargo_bin("plug")) + .args(&["revert", "--dev", "--pruning", "archive", "-d"]) .arg(base_path.path()) .status() .unwrap(); diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs index 0bd48c3693..308832b4cd 100644 --- a/bin/node/cli/tests/inspect_works.rs +++ b/bin/node/cli/tests/inspect_works.rs @@ -26,10 +26,10 @@ mod common; fn inspect_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_command_for_a_while(base_path.path(), false); + common::run_dev_node_for_a_while(base_path.path()); - let status = Command::new(cargo_bin("substrate")) - .args(&["inspect", "-d"]) + let status = Command::new(cargo_bin("plug")) + .args(&["inspect", "--dev", "--pruning", "archive", "-d"]) .arg(base_path.path()) .args(&["block", "1"]) .status() diff --git a/bin/node/cli/tests/purge_chain_works.rs b/bin/node/cli/tests/purge_chain_works.rs index 42a5bc3ce1..a927ad78eb 100644 --- a/bin/node/cli/tests/purge_chain_works.rs +++ b/bin/node/cli/tests/purge_chain_works.rs @@ -17,6 +17,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; +use std::fs; mod common; @@ -25,9 +26,9 @@ mod common; fn purge_chain_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_command_for_a_while(base_path.path(), true); + common::run_dev_node_for_a_while(base_path.path()); - let status = Command::new(cargo_bin("substrate")) + let status = Command::new(cargo_bin("plug")) .args(&["purge-chain", "--dev", "-d"]) .arg(base_path.path()) .arg("-y") @@ -38,4 +39,6 @@ fn purge_chain_works() { // Make sure that the `dev` chain folder exists, but the `db` is deleted. assert!(base_path.path().join("chains/dev/").exists()); assert!(!base_path.path().join("chains/dev/db").exists()); + + let _ = fs::remove_dir_all(base_path); } diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index 67efedccbe..1b48490e41 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -28,7 +28,7 @@ fn running_the_node_works_and_can_be_interrupted() { fn run_command_and_kill(signal: Signal) { let base_path = tempdir().expect("could not create a temp dir"); - let mut cmd = Command::new(cargo_bin("substrate")) + let mut cmd = Command::new(cargo_bin("plug")) .args(&["--dev", "-d"]) .arg(base_path.path()) .spawn() @@ -47,4 +47,4 @@ fn running_the_node_works_and_can_be_interrupted() { run_command_and_kill(SIGINT); run_command_and_kill(SIGTERM); -} +} \ No newline at end of file diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index eb629029c6..f02c535ca7 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -53,6 +53,8 @@ use self::common::{*, sign}; pub const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY; /// Default transfer fee +// NOTE: Transfer fee increased by 1 byte * TransactionByteFee as we include Option in SignedExtra. +// Option always takes up one byte in extrinsic payload fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed64) -> Balance { let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance); diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 1a92aeca6b..97959b88fc 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -123,7 +123,7 @@ fn should_submit_signed_twice_from_the_same_account() { let s = state.read(); fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { let extra = tx.signature.unwrap().2; - extra.3 + extra.4 } let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); @@ -175,7 +175,7 @@ fn submitted_transaction_should_be_valid() { let res = Executive::validate_transaction(extrinsic); assert_eq!(res.unwrap(), ValidTransaction { - priority: 2_411_002_000_000, + priority: 2_421_002_000_000, requires: vec![], provides: vec![(address, 0).encode()], longevity: 127, diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index 97e8f50c27..38d725bc44 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -24,6 +24,8 @@ use sp_runtime::{ generic, traits::{Verify, BlakeTwo256, IdentifyAccount}, OpaqueExtrinsic, MultiSignature }; +pub use sp_runtime::Doughnut; + /// An index to a block. pub type BlockNumber = u32; diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index ddb1f16898..fbce34ee3f 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -69,6 +69,7 @@ pallet-utility = { version = "2.0.0-dev", default-features = false, path = "../. pallet-transaction-payment = { version = "2.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" } pallet-vesting = { version = "2.0.0-dev", default-features = false, path = "../../../frame/vesting" } +prml-doughnut = { path = "../../../prml/doughnut", default-features = false } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } @@ -105,6 +106,7 @@ std = [ "sp-offchain/std", "pallet-offences/std", "sp-core/std", + "prml-doughnut/std", "pallet-randomness-collective-flip/std", "sp-std/std", "rustc-hex", diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 646dc24f57..5de0dc27a4 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -60,7 +60,7 @@ impl> Convert for LinearWeightToFee { /// Update the given multiplier based on the following formula /// -/// diff = (previous_block_weight - target_weight) +/// diff = (target_weight - previous_block_weight) /// v = 0.00004 /// next_weight = weight * (1 + (v . diff) + (v . diff)^2 / 2) /// diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e91eca4e40..e0241380e6 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -62,7 +62,7 @@ pub use pallet_staking::StakerStatus; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustment}; +use impls::{Author, CurrencyToVoteHandler, LinearWeightToFee, TargetedFeeAdjustment}; /// Constant values used within the runtime. pub mod constants; @@ -75,13 +75,13 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); /// Runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), - impl_name: create_runtime_str!("substrate-node"), + impl_name: create_runtime_str!("plug-node"), authoring_version: 10, // Per convention: if the runtime behavior changes, increment spec_version // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 225, + spec_version: 219, impl_version: 0, apis: RUNTIME_API_VERSIONS, }; @@ -124,6 +124,8 @@ impl frame_system::Trait for Runtime { type Header = generic::Header; type Event = Event; type BlockHashCount = BlockHashCount; + type Doughnut = prml_doughnut::PlugDoughnut; + type DelegatedDispatchVerifier = prml_doughnut::PlugDoughnutDispatcher; type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; @@ -142,6 +144,13 @@ parameter_types! { pub const MaxSignatories: u16 = 100; } +impl prml_doughnut::DoughnutRuntime for Runtime { + type AccountId = ::AccountId; + type Call = Call; + type Doughnut = ::Doughnut; + type TimestampProvider = pallet_timestamp::Module; +} + impl pallet_utility::Trait for Runtime { type Event = Event; type Call = Call; @@ -220,7 +229,7 @@ impl pallet_authorship::Trait for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; type UncleGenerations = UncleGenerations; type FilterUncle = (); - type EventHandler = (Staking, ImOnline); + type EventHandler = Staking; } impl_opaque_keys! { @@ -272,6 +281,8 @@ parameter_types! { impl pallet_staking::Trait for Runtime { type Currency = Balances; + type RewardCurrency = Balances; + type CurrencyToReward = Balance; type Time = Timestamp; type CurrencyToVote = CurrencyToVoteHandler; type RewardRemainder = Treasury; @@ -422,6 +433,7 @@ impl pallet_contracts::Trait for Runtime { type ComputeDispatchFee = pallet_contracts::DefaultDispatchFeeComputor; type TrieIdGenerator = pallet_contracts::TrieIdFromParentCounter; type GasPayment = (); + type GasHandler = (); type RentPayment = (); type SignedClaimHandicap = pallet_contracts::DefaultSignedClaimHandicap; type TombstoneDeposit = TombstoneDeposit; @@ -526,6 +538,7 @@ impl frame_system::offchain::CreateTransaction for .saturating_sub(1); let tip = 0; let extra: SignedExtra = ( + None, frame_system::CheckVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), @@ -640,8 +653,10 @@ pub type Block = generic::Block; pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. +/// The `SignedExtension` payload for transactions in the plug runtime. +/// It can contain a doughnut delegation proof as it's second value. pub type SignedExtra = ( + Option<::Doughnut>, frame_system::CheckVersion, frame_system::CheckGenesis, frame_system::CheckEra, @@ -769,7 +784,7 @@ impl_runtime_apis! { input_data: Vec, ) -> ContractExecResult { let exec_result = - Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data); + Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data, None); match exec_result { Ok(v) => ContractExecResult::Success { status: v.status, diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 6b0d06875d..6295e11d04 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -68,6 +68,7 @@ pub fn to_session_keys( /// Returns transaction extra. pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { ( + None, frame_system::CheckVersion::new(), frame_system::CheckGenesis::new(), frame_system::CheckEra::from(Era::mortal(256, 0)), diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index 33209692ca..ee112df904 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -677,6 +677,7 @@ fn create_extrinsic( { let extra = |i: Index, f: Balance| { ( + None, frame_system::CheckVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(Era::Immortal), @@ -690,6 +691,7 @@ fn create_extrinsic( function, extra(index, 0), ( + (), VERSION.spec_version as u32, genesis_hash, genesis_hash, diff --git a/client/cli/src/commands/export_blocks_cmd.rs b/client/cli/src/commands/export_blocks_cmd.rs index 8db650ae8c..cdfa463c6d 100644 --- a/client/cli/src/commands/export_blocks_cmd.rs +++ b/client/cli/src/commands/export_blocks_cmd.rs @@ -22,15 +22,14 @@ use log::info; use structopt::StructOpt; use sc_service::{ Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, - config::DatabaseConfig, + config::DatabaseConfig, Roles, }; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use crate::error; use crate::VersionInfo; use crate::runtime::run_until_exit; -use crate::params::SharedParams; -use crate::params::BlockNumber; +use crate::params::{SharedParams, BlockNumber, PruningParams}; /// The `export-blocks` command used to export blocks. #[derive(Debug, StructOpt, Clone)] @@ -58,6 +57,10 @@ pub struct ExportBlocksCmd { #[allow(missing_docs)] #[structopt(flatten)] pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub pruning_params: PruningParams, } impl ExportBlocksCmd { @@ -106,6 +109,7 @@ impl ExportBlocksCmd { F: FnOnce(&str) -> Result>, String>, { self.shared_params.update_config(&mut config, spec_factory, version)?; + self.pruning_params.update_config(&mut config, Roles::FULL, true)?; config.use_in_memory_keystore()?; Ok(()) diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs index 9ab86986cd..f0c534898e 100644 --- a/client/cli/src/commands/revert_cmd.rs +++ b/client/cli/src/commands/revert_cmd.rs @@ -17,14 +17,13 @@ use std::fmt::Debug; use structopt::StructOpt; use sc_service::{ - Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, Roles, }; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use crate::error; use crate::VersionInfo; -use crate::params::BlockNumber; -use crate::params::SharedParams; +use crate::params::{BlockNumber, SharedParams, PruningParams}; /// The `revert` command used revert the chain to a previous state. #[derive(Debug, StructOpt, Clone)] @@ -36,6 +35,10 @@ pub struct RevertCmd { #[allow(missing_docs)] #[structopt(flatten)] pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub pruning_params: PruningParams, } impl RevertCmd { @@ -72,6 +75,7 @@ impl RevertCmd { F: FnOnce(&str) -> Result>, String>, { self.shared_params.update_config(&mut config, spec_factory, version)?; + self.pruning_params.update_config(&mut config, Roles::FULL, true)?; config.use_in_memory_keystore()?; Ok(()) diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 98809a38ae..36c62bc979 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -15,10 +15,7 @@ // along with Substrate. If not, see . use structopt::StructOpt; -use sc_service::{ - Configuration, RuntimeGenesis, - config::DatabaseConfig, PruningMode, -}; +use sc_service::{Configuration, RuntimeGenesis, config::DatabaseConfig}; use crate::error; use crate::arg_enums::{ @@ -26,17 +23,14 @@ use crate::arg_enums::{ DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING }; +use crate::params::PruningParams; /// Parameters for block import. #[derive(Debug, StructOpt, Clone)] pub struct ImportParams { - /// Specify the state pruning mode, a number of blocks to keep or 'archive'. - /// - /// Default is to keep all block states if the node is running as a - /// validator (i.e. 'archive'), otherwise state is only kept for the last - /// 256 blocks. - #[structopt(long = "pruning", value_name = "PRUNING_MODE")] - pub pruning: Option, + #[allow(missing_docs)] + #[structopt(flatten)] + pub pruning_params: PruningParams, /// Force start with unsafe pruning settings. /// @@ -87,7 +81,7 @@ impl ImportParams { /// Put block import CLI params into `config` object. pub fn update_config( &self, - config: &mut Configuration, + mut config: &mut Configuration, role: sc_service::Roles, is_dev: bool, ) -> error::Result<()> @@ -102,27 +96,7 @@ impl ImportParams { config.state_cache_size = self.state_cache_size; - // by default we disable pruning if the node is an authority (i.e. - // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the - // node is an authority and pruning is enabled explicitly, then we error - // unless `unsafe_pruning` is set. - config.pruning = match &self.pruning { - Some(ref s) if s == "archive" => PruningMode::ArchiveAll, - None if role == sc_service::Roles::AUTHORITY => PruningMode::ArchiveAll, - None => PruningMode::default(), - Some(s) => { - if role == sc_service::Roles::AUTHORITY && !self.unsafe_pruning { - return Err(error::Error::Input( - "Validators should run with state pruning disabled (i.e. archive). \ - You can ignore this check with `--unsafe-pruning`.".to_string() - )); - } - - PruningMode::keep_blocks(s.parse() - .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string()))? - ) - }, - }; + self.pruning_params.update_config(&mut config, role, self.unsafe_pruning)?; config.wasm_method = self.wasm_method.into(); @@ -144,6 +118,7 @@ impl ImportParams { exec_all_or(exec.execution_offchain_worker, DEFAULT_EXECUTION_OFFCHAIN_WORKER), other: exec_all_or(exec.execution_other, DEFAULT_EXECUTION_OTHER), }; + Ok(()) } } diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs index 75509afa42..f684cab336 100644 --- a/client/cli/src/params/mod.rs +++ b/client/cli/src/params/mod.rs @@ -19,6 +19,7 @@ mod transaction_pool_params; mod shared_params; mod node_key_params; mod network_configuration_params; +mod pruning_params; use std::str::FromStr; use std::fmt::Debug; @@ -28,6 +29,7 @@ pub use crate::params::transaction_pool_params::*; pub use crate::params::shared_params::*; pub use crate::params::node_key_params::*; pub use crate::params::network_configuration_params::*; +pub use crate::params::pruning_params::*; /// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a decimal. #[derive(Debug, Clone)] diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs new file mode 100644 index 0000000000..ad64d757dc --- /dev/null +++ b/client/cli/src/params/pruning_params.rs @@ -0,0 +1,69 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +use structopt::StructOpt; +use sc_service::{Configuration, RuntimeGenesis, PruningMode}; + +use crate::error; + +/// Parameters to define the pruning mode +#[derive(Debug, StructOpt, Clone)] +pub struct PruningParams { + /// Specify the state pruning mode, a number of blocks to keep or 'archive'. + /// + /// Default is to keep all block states if the node is running as a + /// validator (i.e. 'archive'), otherwise state is only kept for the last + /// 256 blocks. + #[structopt(long = "pruning", value_name = "PRUNING_MODE")] + pub pruning: Option, +} + +impl PruningParams { + /// Put block pruning CLI params into `config` object. + pub fn update_config( + &self, + mut config: &mut Configuration, + role: sc_service::Roles, + unsafe_pruning: bool, + ) -> error::Result<()> + where + G: RuntimeGenesis, + { + // by default we disable pruning if the node is an authority (i.e. + // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the + // node is an authority and pruning is enabled explicitly, then we error + // unless `unsafe_pruning` is set. + config.pruning = match &self.pruning { + Some(ref s) if s == "archive" => PruningMode::ArchiveAll, + None if role == sc_service::Roles::AUTHORITY => PruningMode::ArchiveAll, + None => PruningMode::default(), + Some(s) => { + if role == sc_service::Roles::AUTHORITY && !unsafe_pruning { + return Err(error::Error::Input( + "Validators should run with state pruning disabled (i.e. archive). \ + You can ignore this check with `--unsafe-pruning`.".to_string() + )); + } + + PruningMode::keep_blocks(s.parse() + .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string()))? + ) + }, + }; + + Ok(()) + } +} diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 48a6fee638..0c2ba2eb36 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -57,6 +57,7 @@ thiserror = "1" unsigned-varint = { version = "0.3.1", features = ["futures", "futures-codec"] } void = "1.0.2" zeroize = "1.0.0" +yamux = "0.4.2" [dev-dependencies] async-std = "1.5" diff --git a/client/service/src/chain_ops.rs b/client/service/src/chain_ops.rs index a0724f3e1d..a868943f3a 100644 --- a/client/service/src/chain_ops.rs +++ b/client/service/src/chain_ops.rs @@ -244,8 +244,8 @@ impl< if json { serde_json::to_writer(&mut output, &block) .map_err(|e| format!("Error writing JSON: {}", e))?; - } else { - output.write_all(&block.encode())?; + } else { + output.write_all(&block.encode())?; } }, // Reached end of the chain. @@ -298,4 +298,4 @@ impl< Err(e) => Box::pin(future::err(format!("Error reading block: {:?}", e).into())), } } -} +} \ No newline at end of file diff --git a/client/telemetry/src/worker.rs b/client/telemetry/src/worker.rs index 8f43bb612a..ef1f9fa067 100644 --- a/client/telemetry/src/worker.rs +++ b/client/telemetry/src/worker.rs @@ -98,10 +98,10 @@ impl TelemetryWorker { .map_ok(|data| BytesMut::from(data.as_ref())); future::ready(Ok::<_, io::Error>(connec)) }) - }); + }) + .timeout(CONNECT_TIMEOUT); let transport = transport - .timeout(CONNECT_TIMEOUT) .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) .map(|out, _| { let out = out diff --git a/client/transaction-pool/src/testing/pool.rs b/client/transaction-pool/src/testing/pool.rs index 6984877eef..3cd61e87c9 100644 --- a/client/transaction-pool/src/testing/pool.rs +++ b/client/transaction-pool/src/testing/pool.rs @@ -164,7 +164,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { } fn block_event(id: u64) -> ChainEvent { - ChainEvent::NewBlock { + ChainEvent::::NewBlock { id: BlockId::number(id), is_new_best: true, retracted: vec![], @@ -173,7 +173,7 @@ fn block_event(id: u64) -> ChainEvent { } fn block_event_with_retracted(id: u64, retracted: Vec) -> ChainEvent { - ChainEvent::NewBlock { + ChainEvent::::NewBlock { id: BlockId::number(id), is_new_best: true, retracted: retracted, @@ -233,6 +233,12 @@ fn should_resubmit_from_retracted_during_maintenance() { pool.api.push_block(1, vec![]); pool.api.push_fork_block(retracted_hash, vec![xt.clone()]); + let _event = ChainEvent::::NewBlock { + id: BlockId::Number(1), + is_new_best: true, + header: header(1), + retracted: vec![retracted_hash] + }; let event = block_event_with_retracted(1, vec![retracted_hash]); @@ -314,7 +320,7 @@ fn should_push_watchers_during_maintaince() { let header_hash = pool.api.push_block(1, vec![tx0, tx1, tx2]).hash(); block_on(pool.maintain(block_event(1))); - let event = ChainEvent::Finalized { hash: header_hash.clone() }; + let event = ChainEvent::::Finalized { hash: header_hash.clone() }; block_on(pool.maintain(event)); // then @@ -356,7 +362,7 @@ fn finalization() { pool.api.push_block(2, vec![xt.clone()]); let header = pool.api.chain().read().header_by_number.get(&2).cloned().unwrap(); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Hash(header.hash()), is_new_best: true, header: header.clone(), @@ -364,7 +370,7 @@ fn finalization() { }; block_on(pool.maintain(event)); - let event = ChainEvent::Finalized { hash: header.hash() }; + let event = ChainEvent::::Finalized { hash: header.hash() }; block_on(pool.maintain(event)); let mut stream = futures::executor::block_on_stream(watcher); @@ -407,7 +413,7 @@ fn fork_aware_finalization() { canon_watchers.push((watcher, header.hash())); assert_eq!(pool.status().ready, 1); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Number(2), is_new_best: true, header: header.clone(), @@ -416,7 +422,7 @@ fn fork_aware_finalization() { b1 = header.hash(); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); - let event = ChainEvent::Finalized { hash: b1 }; + let event = ChainEvent::::Finalized { hash: b1 }; block_on(pool.maintain(event)); } @@ -426,7 +432,7 @@ fn fork_aware_finalization() { from_dave_watcher = block_on(pool.submit_and_watch(&BlockId::number(1), from_dave.clone())) .expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Hash(header.hash()), is_new_best: true, header: header.clone(), @@ -443,7 +449,7 @@ fn fork_aware_finalization() { assert_eq!(pool.status().ready, 1); let header = pool.api.push_fork_block_with_parent(c2, vec![from_bob.clone()]); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Hash(header.hash()), is_new_best: true, header: header.clone(), @@ -461,7 +467,7 @@ fn fork_aware_finalization() { let header = pool.api.push_block(3, vec![from_charlie.clone()]); canon_watchers.push((watcher, header.hash())); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Number(3), is_new_best: true, header: header.clone(), @@ -469,7 +475,7 @@ fn fork_aware_finalization() { }; block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); - let event = ChainEvent::Finalized { hash: header.hash() }; + let event = ChainEvent::::Finalized { hash: header.hash() }; block_on(pool.maintain(event)); } @@ -481,7 +487,7 @@ fn fork_aware_finalization() { let header = pool.api.push_block(4, vec![xt.clone()]); canon_watchers.push((w, header.hash())); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Hash(header.hash()), is_new_best: true, header: header.clone(), @@ -490,7 +496,7 @@ fn fork_aware_finalization() { d1 = header.hash(); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); - let event = ChainEvent::Finalized { hash: d1 }; + let event = ChainEvent::::Finalized { hash: d1 }; block_on(pool.maintain(event)); } @@ -500,7 +506,7 @@ fn fork_aware_finalization() { { let header = pool.api.push_block(5, vec![from_dave, from_bob]); e1 = header.hash(); - let event = ChainEvent::NewBlock { + let event = ChainEvent::::NewBlock { id: BlockId::Hash(header.hash()), is_new_best: true, header: header.clone(), @@ -508,7 +514,7 @@ fn fork_aware_finalization() { }; block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); - block_on(pool.maintain(ChainEvent::Finalized { hash: e1 })); + block_on(pool.maintain(ChainEvent::::Finalized { hash: e1 })); } diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 9755fa0e40..389b518be0 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -1,23 +1,20 @@ Thank you for your Pull Request! +Before submitting, please run through the following checklist: -Before you submitting, please check that: - -- [ ] You added a brief description of the PR, e.g.: +- [ ] Add a description summarizing the PR, e.g.: - What does it do? - - What important points reviewers should know? + - What important points should reviewers know? - Is there something left for follow-up PRs? -- [ ] You labeled the PR with appropriate labels if you have permissions to do so. -- [ ] You mentioned a related issue if this PR related to it, e.g. `Fixes #228` or `Related #1337`. -- [ ] You asked any particular reviewers to review. If you aren't sure, start with GH suggestions. -- [ ] Your PR adheres [the style guide](https://github.com/paritytech/polkadot/wiki/Style-Guide) + - For large diffs please include a changelog in the description with a bulleted "Adds | Changes | Fixes | Removes" sections +- [ ] Add Apache 2.0 license header for new files +- [ ] Mention the related issue, if any, e.g. `Fixes #228` or `Related #1337`. +- [ ] Unit tests +- [ ] Integration tests, if appropriate +- [ ] Request reviewers. If you aren't sure, start with GH suggestions. +- [ ] Update rustdoc comments with changes +- [ ] Update README with changes, if appropriate +- [ ] Adhere to [the style guide](https://wiki.parity.io/Substrate-Style-Guide) - In particular, mind the maximal line length. - There is no commented code checked in unless necessary. - - Any panickers have a proof or removed. -- [ ] You bumped the runtime version if there are breaking changes in the **runtime**. -- [ ] You updated any rustdocs which may have changed -- [ ] Has the PR altered the external API or interfaces used by Polkadot? Do you have the corresponding Polkadot PR ready? - -After you've read this notice feel free to remove it. -Thank you! - -✄ ----------------------------------------------------------------------------- + - Any panics have a proof or removed. +- [ ] Bumped the runtime version if there are breaking changes in the **runtime**. diff --git a/docs/README.adoc b/docs/README.adoc index 8d762fee05..e40ea8377a 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -443,7 +443,7 @@ substrate-test-runtime, substrate-transaction-graph, sp-transaction-pool, substrate-trie * Substrate Runtime [source, shell] -sr-api, sr-io, sr-primitives, sr-sandbox, sr-std, sr-version +sr-api, sp-io, sr-primitives, sr-sandbox, sp-std, sr-version * FRAME Core [source, shell] frame-metadata, frame-support, frame-system diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 042ff89913..d7e04fb5ad 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -293,6 +293,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/aura/src/mock.rs b/frame/aura/src/mock.rs index 05a161ee49..56be6a0837 100644 --- a/frame/aura/src/mock.rs +++ b/frame/aura/src/mock.rs @@ -61,6 +61,8 @@ impl frame_system::Trait for Test { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index 8ee4931e48..8cf9ef051a 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -100,7 +100,7 @@ mod tests { testing::{Header, UintAuthorityId}, traits::{ConvertInto, IdentityLookup, OpaqueKeys}, Perbill, KeyTypeId, }; - use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; + use frame_support::{impl_outer_origin, parameter_types, weights::Weight, additional_traits::DummyDispatchVerifier}; type AuthorityDiscovery = Module; @@ -155,6 +155,8 @@ mod tests { type MaximumBlockWeight = MaximumBlockWeight; type AvailableBlockRatio = AvailableBlockRatio; type MaximumBlockLength = MaximumBlockLength; + type Doughnut = (); + type DelegatedDispatchVerifier = DummyDispatchVerifier; type Version = (); type ModuleToIndex = (); type AccountData = (); diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index d3c1bf752a..40bbe0f2f4 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -182,7 +182,7 @@ decl_error! { } decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, system = frame_system { type Error = Error; fn on_initialize(now: T::BlockNumber) { @@ -431,6 +431,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 2ec083728e..a0e96bd115 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -64,6 +64,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type MaximumBlockLength = MaximumBlockLength; type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index be3fa14c7a..728adf9de7 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -18,6 +18,7 @@ frame-support = { version = "2.0.0-dev", default-features = false, path = "../su frame-system = { version = "2.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] +sp-io = { version = "2.0.0-dev", path = "../../primitives/io" } sp-core = { version = "2.0.0-dev", path = "../../primitives/core" } pallet-transaction-payment = { version = "2.0.0-dev", path = "../transaction-payment" } diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 4dbaf4b8a8..bd5490148a 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -168,7 +168,8 @@ use frame_support::{ WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement, Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive, ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status - } + }, + additional_traits::DummyDispatchVerifier }; use sp_runtime::{ RuntimeDebug, DispatchResult, DispatchError, @@ -934,6 +935,8 @@ impl, I: Instance> frame_system::Trait for ElevatedTrait { type Header = T::Header; type Event = (); type BlockHashCount = T::BlockHashCount; + type Doughnut = T::Doughnut; + type DelegatedDispatchVerifier = DummyDispatchVerifier; type MaximumBlockWeight = T::MaximumBlockWeight; type MaximumBlockLength = T::MaximumBlockLength; type AvailableBlockRatio = T::AvailableBlockRatio; diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index 98c7c856bc..61a0050581 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -149,14 +149,14 @@ macro_rules! decl_tests { Error::<$test, _>::LiquidityRestrictions ); assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(1), + &ChargeTransactionPayment::from(1), &1, CALL, info_from_weight(1), 1, ).is_err()); assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(0), + &ChargeTransactionPayment::from(0), &1, CALL, info_from_weight(1), @@ -167,14 +167,14 @@ macro_rules! decl_tests { assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); assert_ok!(>::reserve(&1, 1)); assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(1), + &ChargeTransactionPayment::from(1), &1, CALL, info_from_weight(1), 1, ).is_err()); assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(0), + &ChargeTransactionPayment::from(0), &1, CALL, info_from_weight(1), @@ -237,7 +237,7 @@ macro_rules! decl_tests { // account 5 should not exist // ext_deposit is 10, value is 9, not satisfies for ext_deposit assert_noop!( - Balances::transfer(Some(1).into(), 5, 9), + Balances::transfer((Some(1), None).into(), 5, 9), Error::<$test, _>::ExistentialDeposit, ); assert_eq!(Balances::is_dead_account(&5), true); // account 5 should not exist @@ -264,7 +264,7 @@ macro_rules! decl_tests { assert_eq!(System::account_nonce(&2), 1); // account 4 tries to take index 1 for account 5. - assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69)); + assert_ok!(Balances::transfer((Some(4), None).into(), 5, 256 * 1 + 0x69)); assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69); assert_eq!(Balances::is_dead_account(&5), false); @@ -275,7 +275,7 @@ macro_rules! decl_tests { assert_eq!(Balances::is_dead_account(&2), true); // account 4 tries to take index 1 again for account 6. - assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69)); + assert_ok!(Balances::transfer((Some(4), None).into(), 6, 256 * 1 + 0x69)); assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69); assert_eq!(Balances::is_dead_account(&6), false); }); @@ -302,7 +302,7 @@ macro_rules! decl_tests { assert_eq!(System::account_nonce(&2), 1); assert_eq!(Balances::total_balance(&2), 2000); // index 1 (account 2) becomes zombie - assert_ok!(Balances::transfer(Some(2).into(), 5, 1901)); + assert_ok!(Balances::transfer((Some(2), None).into(), 5, 1901)); assert_eq!(Balances::total_balance(&2), 0); assert_eq!(Balances::total_balance(&5), 1901); assert_eq!(System::account_nonce(&2), 0); @@ -326,7 +326,7 @@ macro_rules! decl_tests { fn balance_transfer_works() { <$ext_builder>::default().build().execute_with(|| { let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::transfer(Some(1).into(), 2, 69)); + assert_ok!(Balances::transfer((Some(1), None).into(), 2, 69)); assert_eq!(Balances::total_balance(&1), 42); assert_eq!(Balances::total_balance(&2), 69); }); @@ -337,7 +337,7 @@ macro_rules! decl_tests { <$ext_builder>::default().build().execute_with(|| { let _ = Balances::deposit_creating(&1, 111); assert_noop!( - Balances::force_transfer(Some(2).into(), 1, 2, 69), + Balances::force_transfer((Some(2), None).into(), 1, 2, 69), BadOrigin, ); assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); @@ -369,7 +369,7 @@ macro_rules! decl_tests { let _ = Balances::deposit_creating(&1, 111); assert_ok!(Balances::reserve(&1, 69)); assert_noop!( - Balances::transfer(Some(1).into(), 2, 69), + Balances::transfer((Some(1), None).into(), 2, 69), Error::<$test, _>::InsufficientBalance, ); }); @@ -512,7 +512,7 @@ macro_rules! decl_tests { Balances::make_free_balance_be(&2, 1); assert_err!( - Balances::transfer(Some(1).into(), 2, u64::max_value()), + Balances::transfer((Some(1), None).into(), 2, u64::max_value()), Error::<$test, _>::Overflow, ); @@ -560,7 +560,7 @@ macro_rules! decl_tests { // Transfer funds from account 1 of such amount that after this transfer // the balance of account 1 will be below the existential threshold. // This should lead to the removal of all balance of this account. - assert_ok!(Balances::transfer(Some(1).into(), 2, 20)); + assert_ok!(Balances::transfer((Some(1), None).into(), 2, 20)); // Verify free balance removal of account 1. assert_eq!(Balances::free_balance(1), 0); @@ -587,7 +587,7 @@ macro_rules! decl_tests { <$ext_builder>::default().existential_deposit(1).build().execute_with(|| { let _ = Balances::deposit_creating(&1, 100); assert_noop!( - Balances::transfer_keep_alive(Some(1).into(), 2, 100), + Balances::transfer_keep_alive((Some(1), None).into(), 2, 100), Error::<$test, _>::KeepAlive ); assert_eq!(Balances::is_dead_account(&1), false); diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index 3a5c2178f8..de42d73742 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -65,6 +65,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = DummyDispatchVerifier; type AccountData = super::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index 861c197212..bc4b1c64d7 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -65,6 +65,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = super::AccountData; type OnNewAccount = (); type OnKilledAccount = Module; @@ -113,9 +115,6 @@ impl ExtBuilder { } pub fn monied(mut self, monied: bool) -> Self { self.monied = monied; - if self.existential_deposit == 0 { - self.existential_deposit = 1; - } self } pub fn set_associated_consts(&self) { diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index 3ad4a9a8a0..92d7bf5bc2 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -154,7 +154,7 @@ macro_rules! impl_benchmark { $crate::benchmarking::wipe_db(); // first one is set_identity. - let components = , RawOrigin>>::components(&selected_benchmark); + let components = , RawOrigin>>::components(&selected_benchmark); // results go here let mut results: Vec<$crate::BenchmarkResults> = Vec::new(); // Select the component we will be benchmarking. Each component will be benchmarked. @@ -175,7 +175,7 @@ macro_rules! impl_benchmark { // Run the benchmark `repeat` times. for _ in 0..repeat { // Set up the externalities environment for the setup we want to benchmark. - let (call, caller) = , RawOrigin>>::instance(&selected_benchmark, &c)?; + let (call, caller) = , RawOrigin>>::instance(&selected_benchmark, &c)?; // Commit the externalities to the database, flushing the DB cache. // This will enable worst case scenario for reading from the database. $crate::benchmarking::commit_db(); @@ -383,7 +383,7 @@ macro_rules! benchmark_backend { #[allow(non_camel_case_types)] struct $name; #[allow(unused_variables)] - impl $crate::BenchmarkingSetup, RawOrigin> for $name { + impl $crate::BenchmarkingSetup, RawOrigin> for $name { fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { vec! [ $( @@ -393,7 +393,7 @@ macro_rules! benchmark_backend { } fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result<(crate::Call, RawOrigin), &'static str> + -> Result<(crate::Call, RawOrigin), &'static str> { $( let $common = $common_from; @@ -439,25 +439,25 @@ macro_rules! selected_benchmark { } // Allow us to select a benchmark from the list of available benchmarks. - impl $crate::BenchmarkingSetup, RawOrigin> for SelectedBenchmark { + impl $crate::BenchmarkingSetup, RawOrigin> for SelectedBenchmark { fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { match self { $( Self::$bench => <$bench as $crate::BenchmarkingSetup< T, Call, - RawOrigin, + RawOrigin, >>::components(&$bench), )* } } fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result<(Call, RawOrigin), &'static str> + -> Result<(Call, RawOrigin), &'static str> { match self { $( Self::$bench => <$bench as $crate::BenchmarkingSetup< T, Call, - RawOrigin, + RawOrigin, >>::instance(&$bench, components), )* } } diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 605cda3cb7..9dcfb66ed6 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -433,6 +433,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs index 52dddb177b..fd704612c7 100644 --- a/frame/contracts/rpc/src/lib.rs +++ b/frame/contracts/rpc/src/lib.rs @@ -72,7 +72,7 @@ impl From for Error { /// A struct that encodes RPC parameters required for a call to a smart-contract. #[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all="camelCase")] #[serde(deny_unknown_fields)] pub struct CallRequest { origin: AccountId, @@ -159,10 +159,7 @@ pub struct Contracts { impl Contracts { /// Create new `Contracts` with the given reference to the client. pub fn new(client: Arc) -> Self { - Contracts { - client, - _marker: Default::default(), - } + Contracts { client, _marker: Default::default() } } } impl @@ -192,14 +189,15 @@ where let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + self.client.info().best_hash + )); let CallRequest { origin, dest, value, gas_limit, - input_data, + input_data } = call_request; let gas_limit = gas_limit.to_number().map_err(|e| Error { code: ErrorCode::InvalidParams, diff --git a/frame/contracts/src/doughnut_integration.rs b/frame/contracts/src/doughnut_integration.rs new file mode 100644 index 0000000000..864a4f62ea --- /dev/null +++ b/frame/contracts/src/doughnut_integration.rs @@ -0,0 +1,721 @@ +// Copyright 2019 Plug New Zealand Limited +// +// 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. + +#![allow(unused_must_use)] + +use crate::{ + ComputeDispatchFee, ContractAddressFor, GenesisConfig, Module, RawEvent, Trait, + TrieId, Schedule, TrieIdGenerator, +}; +use codec::{Encode, Decode}; +use hex_literal::*; +use sp_core::storage::well_known_keys; +use sp_runtime::{ + Perbill, traits::{BlakeTwo256, Hash, IdentityLookup, PlugDoughnutApi}, + testing::{Header, H256}, +}; +use frame_support::{ + assert_ok, assert_err, impl_outer_dispatch, impl_outer_event, impl_outer_origin, + parameter_types, StorageValue, traits::{Currency, Get}, weights::Weight, + additional_traits::DelegatedDispatchVerifier, +}; +use std::cell::RefCell; +use frame_system::{self as system, EventRecord, Phase, RawOrigin}; + +mod contract { + // Re-export contents of the root. This basically + // needs to give a name for the current crate. + // This hack is required for `impl_outer_event!`. + pub use super::super::*; +} +impl_outer_event! { + pub enum MetaEvent for Test { + system, + pallet_balances, + contract, + } +} +impl_outer_origin! { + pub enum Origin for Test { } +} +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + balances::Balances, + contract::Contract, + frame_system::System, + } +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); + static TRANSFER_FEE: RefCell = RefCell::new(0); + static INSTANTIATION_FEE: RefCell = RefCell::new(0); + static BLOCK_GAS_LIMIT: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) } +} + +pub struct BlockGasLimit; +impl Get for BlockGasLimit { + fn get() -> u64 { BLOCK_GAS_LIMIT.with(|v| *v.borrow()) } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; + +#[derive(Default, Clone, Eq, PartialEq, Debug, Encode, Decode)] +pub struct MockDoughnut { + /// A mock branching point for verify_runtime_to_contract_call, as a doughnut is verified at different level. + /// A doughnut is first verified at runtime via Contract::call(). + runtime_verifiable: bool, + /// A mock branching point for verify_contract_to_contract_call, as a doughnut is verified at different level. + /// A doughnut is then verified at contract execution via ext_call(). + contract_verifiable: bool, +} +impl MockDoughnut { + pub fn set_runtime_verifiable(mut self, verifiable: bool) -> Self { + self.runtime_verifiable = verifiable; + self + } + pub fn set_contract_verifiable(mut self, verifiable: bool) -> Self { + self.contract_verifiable = verifiable; + self + } +} + +impl PlugDoughnutApi for MockDoughnut { + type PublicKey = [u8; 32]; + type Timestamp = u32; + type Signature = (); + fn holder(&self) -> Self::PublicKey { Default::default() } + fn issuer(&self) -> Self::PublicKey { Default::default() } + fn expiry(&self) -> Self::Timestamp { 0 } + fn not_before(&self) -> Self::Timestamp { 0 } + fn payload(&self) -> Vec { Vec::default() } + fn signature(&self) -> Self::Signature {} + fn signature_version(&self) -> u8 { 0 } + fn get_domain(&self, _domain: &str) -> Option<&[u8]> { None } +} + +pub struct MockDispatchVerifier; +impl DelegatedDispatchVerifier for MockDispatchVerifier { + type Doughnut = MockDoughnut; + type AccountId = u64; + const DOMAIN: &'static str = ""; + fn verify_dispatch( + _doughnut: &Self::Doughnut, + _module: &str, + _method: &str, + ) -> Result<(), &'static str> { + Ok(()) + } + fn verify_runtime_to_contract_call( + _caller: &Self::AccountId, + doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + if doughnut.runtime_verifiable { + Ok(()) + } else { + Err("Doughnut runtime to contract call verification is not implemented for this domain") + } + } + fn verify_contract_to_contract_call( + _caller: &Self::AccountId, + doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + if doughnut.contract_verifiable { + Ok(()) + } else { + Err("Doughnut contract to contract call verification is not implemented for this domain") + } + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = MetaEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type Doughnut = MockDoughnut; + type DelegatedDispatchVerifier = MockDispatchVerifier; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +impl pallet_balances::Trait for Test { + type Balance = u64; + type DustRemoval = (); + type Event = MetaEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} +impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +parameter_types! { + pub const SignedClaimHandicap: u64 = 2; + pub const TombstoneDeposit: u64 = 16; + pub const StorageSizeOffset: u32 = 8; + pub const RentByteFee: u64 = 4; + pub const RentDepositOffset: u64 = 10_000; + pub const SurchargeReward: u64 = 150; + pub const TransactionBaseFee: u64 = 2; + pub const TransactionByteFee: u64 = 6; + pub const ContractFee: u64 = 21; + pub const CallBaseFee: u64 = 135; + pub const InstantiateBaseFee: u64 = 175; + pub const MaxDepth: u32 = 100; + pub const MaxValueSize: u32 = 16_384; +} +impl Trait for Test { + type Currency = Balances; + type Time = Timestamp; + type Randomness = Randomness; + type Call = Call; + type DetermineContractAddress = DummyContractAddressFor; + type Event = MetaEvent; + type ComputeDispatchFee = DummyComputeDispatchFee; + type TrieIdGenerator = DummyTrieIdGenerator; + type GasPayment = (); + type GasHandler = (); + type RentPayment = (); + type SignedClaimHandicap = SignedClaimHandicap; + type TombstoneDeposit = TombstoneDeposit; + type StorageSizeOffset = StorageSizeOffset; + type RentByteFee = RentByteFee; + type RentDepositOffset = RentDepositOffset; + type SurchargeReward = SurchargeReward; + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type ContractFee = ContractFee; + type CallBaseFee = CallBaseFee; + type InstantiateBaseFee = InstantiateBaseFee; + type MaxDepth = MaxDepth; + type MaxValueSize = MaxValueSize; + type BlockGasLimit = BlockGasLimit; +} + +type Balances = pallet_balances::Module; +type Timestamp = pallet_timestamp::Module; +type Contract = Module; +type System = frame_system::Module; +type Randomness = pallet_randomness_collective_flip::Module; + +pub struct DummyContractAddressFor; +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 { + *origin + 1 + } +} + +pub struct DummyTrieIdGenerator; +impl TrieIdGenerator for DummyTrieIdGenerator { + fn trie_id(account_id: &u64) -> TrieId { + let new_seed = super::AccountCounter::mutate(|v| { + *v = v.wrapping_add(1); + *v + }); + + // TODO: see https://github.com/paritytech/substrate/issues/2325 + let mut res = vec![]; + res.extend_from_slice(well_known_keys::CHILD_STORAGE_KEY_PREFIX); + res.extend_from_slice(b"default:"); + res.extend_from_slice(&new_seed.to_le_bytes()); + res.extend_from_slice(&account_id.to_le_bytes()); + res + } +} + +pub struct DummyComputeDispatchFee; +impl ComputeDispatchFee for DummyComputeDispatchFee { + fn compute_dispatch_fee(_call: &Call) -> u64 { + 69 + } +} + +const ALICE: u64 = 1; +const BOB: u64 = 2; +const CHARLIE: u64 = 3; +const DJANGO: u64 = 4; + +pub struct ExtBuilder { + existential_deposit: u64, + gas_price: u64, + block_gas_limit: u64, + transfer_fee: u64, + instantiation_fee: u64, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 0, + gas_price: 2, + block_gas_limit: 100_000_000, + transfer_fee: 0, + instantiation_fee: 0, + } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + TRANSFER_FEE.with(|v| *v.borrow_mut() = self.transfer_fee); + INSTANTIATION_FEE.with(|v| *v.borrow_mut() = self.instantiation_fee); + BLOCK_GAS_LIMIT.with(|v| *v.borrow_mut() = self.block_gas_limit); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![], + }.assimilate_storage(&mut t).unwrap(); + GenesisConfig:: { + current_schedule: Schedule { + enable_println: true, + ..Default::default() + }, + gas_price: self.gas_price, + }.assimilate_storage(&mut t).unwrap(); + sp_io::TestExternalities::new(t) + } +} + +/// Generate Wasm binary and code hash from wabt source. +fn compile_module(wabt_module: &str) + -> Result<(Vec, ::Output), wabt::Error> + where T: frame_system::Trait +{ + let wasm = wabt::wat2wasm(wabt_module)?; + let code_hash = T::Hashing::hash(&wasm); + Ok((wasm, code_hash)) +} + +const CODE_RETURN_WITH_DATA: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; Deploy routine is the same as call. + (func (export "deploy") (result i32) + (call $call) + ) + + ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data. + (func $call (export "call") (result i32) + (local $buf_size i32) + (local $exit_status i32) + + ;; Find out the size of the scratch buffer + (set_local $buf_size (call $ext_scratch_size)) + + ;; Copy scratch buffer into this contract memory. + (call $ext_scratch_read + (i32.const 0) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (get_local $buf_size) ;; Count of bytes to copy. + ) + + ;; Copy all but the first 4 bytes of the input data as the output data. + (call $ext_scratch_write + (i32.const 4) ;; Pointer to the data to return. + (i32.sub ;; Count of bytes to copy. + (get_local $buf_size) + (i32.const 4) + ) + ) + + ;; Return the first 4 bytes of the input data as the exit status. + (i32.load (i32.const 0)) + ) +) +"#; + +const CODE_CALLER_CONTRACT: &str = r#" +(module + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; Declare local variables. + (local $exit_code i32) + + ;; Copy code hash from scratch buffer into this contract's memory. + (call $ext_scratch_read + (i32.const 24) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 32) ;; Count of bytes to copy. + ) + + ;; Deploy the contract successfully. + (set_local $exit_code + (call $ext_instantiate + (i32.const 24) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + + ;; Call the contract successfully. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + ) + + (data (i32.const 0) "\00\80") + (data (i32.const 8) "\00\11\22\33\44\55\66\77") +) +"#; + +#[test] +fn contract_to_contract_call_executes_with_verifiable_doughnut() { + let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); + let verifiable_doughnut = MockDoughnut::default() + .set_runtime_verifiable(true) + .set_contract_verifiable(true); + let delegated_origin = RawOrigin::from((Some(ALICE), Some(verifiable_doughnut.clone()))); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + caller_code_hash.into(), + vec![], + )); + // Call BOB contract, which attempts to instantiate and call the callee contract + assert_ok!(Contract::call( + delegated_origin.clone().into(), + BOB, + 0, + 200_000, + callee_code_hash.as_ref().to_vec(), + )); + }); +} + +#[test] +fn contract_to_contract_call_executes_without_doughnut() { + let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); + let delegated_origin = RawOrigin::from((Some(ALICE), None)); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + caller_code_hash.into(), + vec![], + )); + // Call BOB contract, which attempts to instantiate and call the callee contract + assert_ok!(Contract::call( + delegated_origin.into(), + BOB, + 0, + 200_000, + callee_code_hash.as_ref().to_vec(), + )); + }); +} + +#[test] +fn contract_to_contract_call_returns_error_with_unverifiable_doughnut() { + let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); + + // Doughnut is first verified at runtime before execution, + // hence runtime_verifiable set to true to bypass the check. + let unverifiable_doughnut = MockDoughnut::default() + .set_runtime_verifiable(true) + .set_contract_verifiable(false); + let delegated_origin = RawOrigin::from((Some(ALICE), Some(unverifiable_doughnut.clone()))); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + caller_code_hash.into(), + vec![], + )); + // Call BOB contract, which attempts to instantiate and call the callee contract + assert_err!( + Contract::call( + delegated_origin.into(), + BOB, + 0, + 200_000, + callee_code_hash.as_ref().to_vec(), + ), + "contract trapped during execution", // expected as $exit_code = 0x11 from $ext_call + ); + }); +} + +const CODE_DELEGATED_DISPATCH_CALL: &str = r#" +(module + (import "env" "ext_delegated_dispatch_call" (func $ext_delegated_dispatch_call (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_delegated_dispatch_call + (i32.const 8) ;; Pointer to the start of encoded call buffer + (i32.const 11) ;; Length of the buffer + ) + ) + (func (export "deploy")) + + ;; Encoding of balance transfer of 50 to Charlie + (data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8") +) +"#; + +#[test] +fn delegated_contract_to_runtime_call_executes_with_verifiable_doughnut() { + // Ensure we are using the correct encoding (of a call) above to test + let encoded = Encode::encode(&Call::Balances(pallet_balances::Call::transfer(CHARLIE, 50))); + assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); + + let (wasm, code_hash) = compile_module::(CODE_DELEGATED_DISPATCH_CALL).unwrap(); + let verifiable_doughnut = MockDoughnut::default() + .set_runtime_verifiable(true) + .set_contract_verifiable(true); + let delegated_origin = RawOrigin::from((Some(DJANGO), Some(verifiable_doughnut.clone()))); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + Balances::deposit_creating(&DJANGO, 1_000_000); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // contract account BOB is created by instantiating the contract. + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + + // DJANGO is calling the contract BOB. + assert_ok!(Contract::call( + delegated_origin.clone().into(), + BOB, + 0, + 100_000, + vec![], + )); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::system(frame_system::RawEvent::NewAccount(ALICE)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::pallet_balances(pallet_balances::RawEvent::Endowed(ALICE, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::system(frame_system::RawEvent::NewAccount(DJANGO)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::pallet_balances(pallet_balances::RawEvent::Endowed(DJANGO, 1_000_000)), + topics: vec![], + }, + + // Events from Contract::put_code + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + + // Contract::instantiate + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::pallet_balances(pallet_balances::RawEvent::Endowed(BOB, 100)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), + topics: vec![], + }, + + // Dispatching the call. + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::system(frame_system::RawEvent::NewAccount(CHARLIE)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::pallet_balances(pallet_balances::RawEvent::Endowed(CHARLIE, 50)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::pallet_balances(pallet_balances::RawEvent::Transfer(DJANGO, CHARLIE, 50)), + topics: vec![], + }, + + // Event emited as a result of dispatch. + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::DelegatedDispatched(DJANGO, verifiable_doughnut, true)), + topics: vec![], + } + ] + ); + }); +} + +#[test] +fn delegated_runtime_to_contract_call_returns_error_with_unverifiable_doughnut() { + // Ensure we are using the correct encoding (of a call) above to test + let encoded = Encode::encode(&Call::Balances(pallet_balances::Call::transfer(CHARLIE, 50))); + assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); + + let (wasm, code_hash) = compile_module::(CODE_DELEGATED_DISPATCH_CALL).unwrap(); + + // Because doughnut is first verified at runtime before contract call execution, + // Contract::call should return error even if it's verifiable at ext_call level + let unverifiable_doughnut = MockDoughnut::default() + .set_runtime_verifiable(false) + .set_contract_verifiable(true); + let delegated_origin = RawOrigin::from((Some(DJANGO), Some(unverifiable_doughnut.clone()))); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + Balances::deposit_creating(&DJANGO, 1_000_000); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // contract account BOB is created by instantiating the contract. + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + + // DJANGO is calling the contract BOB. + assert_err!( + Contract::call( + delegated_origin.clone().into(), + BOB, + 0, + 100_000, + vec![], + ), + "Doughnut runtime to contract call verification is not implemented for this domain", + ); + }); +} diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 53a6c484fc..6b7f98ded5 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -28,6 +28,7 @@ use frame_support::{ }; pub type AccountIdOf = ::AccountId; +pub type DoughnutOf = ::Doughnut; pub type CallOf = ::Call; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; @@ -129,6 +130,9 @@ pub trait Ext { input_data: Vec, ) -> ExecResult; + /// Notes a call dispatch. + fn note_delegated_dispatch_call(&mut self, doughnut: DoughnutOf, call: CallOf); + /// Notes a call dispatch. fn note_dispatch_call(&mut self, call: CallOf); @@ -144,9 +148,15 @@ pub trait Ext { /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; + /// `origin` is the account which authored the original extrinsic leading to the current contract call(s). It does not change on nested calls as with `caller`. + fn origin(&self) -> &AccountIdOf; + /// Returns a reference to the account id of the current contract. fn address(&self) -> &AccountIdOf; + /// Returns the doughnut of the current contract. + fn doughnut(&self) -> Option<&DoughnutOf>; + /// Returns the balance of the current contract. /// /// The `value_transferred` is already added. @@ -252,6 +262,12 @@ pub enum DeferredAction { /// The event to deposit. event: Event, }, + DelegatedRuntimeCall { + /// The relevent doughnut that gives the contract permission to operate. + doughnut: T::Doughnut, + /// The call to dispatch. + call: ::Call, + }, DispatchRuntimeCall { /// The account id of the contract who dispatched this call. origin: T::AccountId, @@ -285,6 +301,8 @@ pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub loader: &'a L, pub timestamp: MomentOf, pub block_number: T::BlockNumber, + pub origin: T::AccountId, + pub doughnut: Option<&'a T::Doughnut>, } impl<'a, T, E, V, L> ExecutionContext<'a, T, V, L> @@ -297,11 +315,11 @@ where /// /// The specified `origin` address will be used as `sender` for. The `origin` must be a regular /// account (not a contract). - pub fn top_level(origin: T::AccountId, cfg: &'a Config, vm: &'a V, loader: &'a L) -> Self { + pub fn top_level(origin: T::AccountId, cfg: &'a Config, vm: &'a V, loader: &'a L, doughnut: Option<&'a T::Doughnut>) -> Self { ExecutionContext { parent: None, self_trie_id: None, - self_account: origin, + self_account: origin.clone(), overlay: OverlayAccountDb::::new(&DirectAccountDb), depth: 0, deferred: Vec::new(), @@ -310,6 +328,8 @@ where loader: &loader, timestamp: T::Time::now(), block_number: >::block_number(), + origin: origin.clone(), + doughnut: doughnut, } } @@ -328,6 +348,8 @@ where loader: self.loader, timestamp: self.timestamp.clone(), block_number: self.block_number.clone(), + origin: self.origin.clone(), + doughnut: self.doughnut, } } @@ -515,12 +537,14 @@ where { let timestamp = self.timestamp.clone(); let block_number = self.block_number.clone(); + let origin = self.origin.clone(); CallContext { ctx: self, caller, value_transferred: value, timestamp, block_number, + origin } } @@ -669,6 +693,7 @@ struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm + 'b, L: Loader> { value_transferred: BalanceOf, timestamp: MomentOf, block_number: T::BlockNumber, + origin: T::AccountId, // origin has the same value as caller except for nested calls } impl<'a, 'b: 'a, T, E, V, L> Ext for CallContext<'a, 'b, T, V, L> @@ -716,6 +741,13 @@ where self.ctx.call(to.clone(), value, gas_meter, input_data) } + fn note_delegated_dispatch_call(&mut self, doughnut: DoughnutOf, call: CallOf) { + self.ctx.deferred.push(DeferredAction::DelegatedRuntimeCall { + doughnut, + call, + }); + } + fn note_dispatch_call(&mut self, call: CallOf) { self.ctx.deferred.push(DeferredAction::DispatchRuntimeCall { origin: self.ctx.self_account.clone(), @@ -743,10 +775,18 @@ where &self.ctx.self_account } + fn doughnut(&self) -> Option<&DoughnutOf> { + self.ctx.doughnut + } + fn caller(&self) -> &T::AccountId { &self.caller } + fn origin(&self) -> &T::AccountId { + &self.origin + } + fn balance(&self) -> BalanceOf { self.ctx.overlay.get_balance(&self.ctx.self_account) } @@ -946,7 +986,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, exec_ch).unwrap(); assert_matches!( @@ -958,6 +998,33 @@ mod tests { assert_eq!(&*test_data.borrow(), &vec![0, 1]); } + #[test] + fn origin_is_correct_at_top_level() { + ExtBuilder::default().build().execute_with(|| { + let value = Default::default(); + let vm = MockVm::new(); + let loader = MockLoader::empty(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); + + assert_eq!(*ctx.new_call_context(BOB, value).origin(), ALICE); + }); + } + + #[test] + fn origin_is_correct_for_nested_calls() { + ExtBuilder::default().build().execute_with(|| { + let value = Default::default(); + let vm = MockVm::new(); + let loader = MockLoader::empty(); + let cfg = Config::preload(); + let ctx = ExecutionContext::top_level(BOB, &cfg, &vm, &loader, None); + let mut nested = ctx.nested(ALICE, None); + + assert_eq!(*nested.new_call_context(CHARLIE, value).origin(), BOB); + }); + } + #[test] fn base_fees() { let origin = ALICE; @@ -968,7 +1035,7 @@ mod tests { let vm = MockVm::new(); let loader = MockLoader::empty(); let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 0); @@ -988,7 +1055,7 @@ mod tests { let vm = MockVm::new(); let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); @@ -1014,7 +1081,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 0); @@ -1046,7 +1113,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 0); @@ -1076,7 +1143,7 @@ mod tests { let vm = MockVm::new(); let loader = MockLoader::empty(); let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 0); @@ -1102,7 +1169,7 @@ mod tests { let vm = MockVm::new(); let loader = MockLoader::empty(); let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 15); @@ -1130,7 +1197,7 @@ mod tests { let vm = MockVm::new(); let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 15); @@ -1164,7 +1231,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&origin, 0); let result = ctx.call( @@ -1201,7 +1268,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); let result = ctx.call( @@ -1232,7 +1299,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); let result = ctx.call( @@ -1260,7 +1327,7 @@ mod tests { // This one tests passing the input data into a contract via call. ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, input_data_ch).unwrap(); let result = ctx.call( @@ -1285,7 +1352,7 @@ mod tests { // This one tests passing the input data into a contract via instantiate. ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1); @@ -1334,7 +1401,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&BOB, 1); ctx.overlay.instantiate_contract(&BOB, recurse_ch).unwrap(); @@ -1380,7 +1447,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&dest, bob_ch).unwrap(); ctx.overlay.instantiate_contract(&CHARLIE, charlie_ch).unwrap(); @@ -1421,7 +1488,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.instantiate_contract(&BOB, bob_ch).unwrap(); ctx.overlay.instantiate_contract(&CHARLIE, charlie_ch).unwrap(); @@ -1445,7 +1512,7 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); assert_matches!( ctx.instantiate( @@ -1470,7 +1537,7 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( @@ -1510,7 +1577,7 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( @@ -1555,7 +1622,7 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1000); ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); @@ -1614,7 +1681,7 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1000); ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); @@ -1647,7 +1714,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader, None); ctx.overlay.set_balance(&ALICE, 1); diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index c8572daaa4..ba0471c233 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -14,14 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use crate::{GasSpent, Module, Trait, BalanceOf, NegativeImbalanceOf}; +use crate::{Module, Trait, BalanceOf}; use sp_std::convert::TryFrom; use sp_runtime::traits::{ CheckedMul, Zero, SaturatedConversion, AtLeast32Bit, UniqueSaturatedInto, }; use frame_support::{ - traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReason}, StorageValue, - dispatch::DispatchError, + traits::{Currency, ExistenceRequirement, OnUnbalanced, WithdrawReason}, dispatch::DispatchError, }; #[cfg(test)] @@ -185,7 +184,7 @@ impl GasMeter { } /// Returns how much gas was spent. - fn spent(&self) -> Gas { + pub fn spent(&self) -> Gas { self.limit - self.gas_left } @@ -195,6 +194,33 @@ impl GasMeter { } } +/// +/// This trait allow customization on how the `GasMeter` will be filled and emptied. +/// +/// If your economic model does not require upfront payment for the gas to be filled, +/// you can impl this trait with your own business logic +/// +pub trait GasHandler { + /// This function fills the gas meter with a certain amount of gas + /// Default behaviour will withdraw currency from the user's balance upfront to pay for the gas + fn fill_gas(transactor: &T::AccountId, gas_limit: Gas) -> Result, DispatchError> + { + buy_gas::(transactor, gas_limit) + } + + /// This function empties the remaining gas in the gas meter + /// Default behaviour will deposit currency into the user's balance to refund un-used gas + fn empty_unused_gas( + transactor: &T::AccountId, + gas_meter: GasMeter, + ) + { + refund_unused_gas(transactor, gas_meter); + } +} + +impl GasHandler for () {} + /// Buy the given amount of gas. /// /// Cost is calculated by multiplying the gas cost (taken from the storage) by the `gas_limit`. @@ -202,7 +228,7 @@ impl GasMeter { pub fn buy_gas( transactor: &T::AccountId, gas_limit: Gas, -) -> Result<(GasMeter, NegativeImbalanceOf), DispatchError> { +) -> Result, DispatchError> { // Buy the specified amount of gas. let gas_price = >::gas_price(); let cost = if gas_price.is_zero() { @@ -220,29 +246,21 @@ pub fn buy_gas( ExistenceRequirement::KeepAlive )?; - Ok((GasMeter::with_limit(gas_limit, gas_price), imbalance)) + T::GasPayment::on_unbalanced(imbalance); + + Ok(GasMeter::with_limit(gas_limit, gas_price)) } /// Refund the unused gas. pub fn refund_unused_gas( transactor: &T::AccountId, gas_meter: GasMeter, - imbalance: NegativeImbalanceOf, ) { - let gas_spent = gas_meter.spent(); let gas_left = gas_meter.gas_left(); - // Increase total spent gas. - // This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which - // also has Gas type. - GasSpent::mutate(|block_gas_spent| *block_gas_spent += gas_spent); - // Refund gas left by the price it was bought at. let refund = gas_meter.gas_price * gas_left.unique_saturated_into(); - let refund_imbalance = T::Currency::deposit_creating(transactor, refund); - if let Ok(imbalance) = imbalance.offset(refund_imbalance) { - T::GasPayment::on_unbalanced(imbalance); - } + let _imbalance = T::Currency::deposit_creating(transactor, refund); } /// A little handy utility for converting a value in balance units into approximate value in gas units diff --git a/frame/contracts/src/gas_tests.rs b/frame/contracts/src/gas_tests.rs new file mode 100644 index 0000000000..31de8d4349 --- /dev/null +++ b/frame/contracts/src/gas_tests.rs @@ -0,0 +1,355 @@ +// Copyright 2019-2020 +// by Centrality Investments Ltd. +// and Parity Technologies (UK) Ltd. +// This file is part of Substrate. +// +// This file tests an alternative implementation of GasHandler trait in gas.rs +// This trait allows customized implementation when filling and emptying gas meters +// +// The Default substrate charges the user upfront when gas is filled, then refund the user +// on when emptying unused gas. +// + +#![cfg(test)] +#![allow(unused)] + +// Set up the GasTest struct (Test Environment) +use crate::{ + gas::{buy_gas, Gas, GasHandler, GasMeter, Token}, + tests::ExtBuilder, + BalanceOf, ComputeDispatchFee, ContractAddressFor, Module, Trait, TrieId, TrieIdGenerator, +}; + +use frame_support::{ + dispatch::DispatchError, + impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types, + traits::{Currency, Get}, + weights::Weight, + StorageValue, +}; + +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; +use sp_std::cell::RefCell; + +mod contract { + // Re-export contents of the root. This basically + // needs to give a name for the current crate. + // This hack is required for `impl_outer_event!`. + pub use super::super::*; +} + +use pallet_balances as balances; +impl_outer_event! { + pub enum MetaEvent for GasTest { + balances, contract, frame_system, + } +} + +impl_outer_origin! { + pub enum Origin for GasTest where system = frame_system{} +} +impl_outer_dispatch! { + pub enum Call for GasTest where origin: Origin { + balances::Balances, + contract::Contract, + frame_system::System, + } +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); + static TRANSFER_FEE: RefCell = RefCell::new(0); + static INSTANTIATION_FEE: RefCell = RefCell::new(0); + static BLOCK_GAS_LIMIT: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +pub struct TransferFee; +impl Get for TransferFee { + fn get() -> u64 { + TRANSFER_FEE.with(|v| *v.borrow()) + } +} + +pub struct CreationFee; +impl Get for CreationFee { + fn get() -> u64 { + INSTANTIATION_FEE.with(|v| *v.borrow()) + } +} + +pub struct BlockGasLimit; +impl Get for BlockGasLimit { + fn get() -> u64 { + BLOCK_GAS_LIMIT.with(|v| *v.borrow()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct GasTest; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for GasTest { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = MetaEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +impl balances::Trait for GasTest { + /// The type for recording an account's balance. + type Balance = u64; + type DustRemoval = (); + type Event = MetaEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} +impl pallet_timestamp::Trait for GasTest { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +parameter_types! { + pub const SignedClaimHandicap: u64 = 2; + pub const TombstoneDeposit: u64 = 16; + pub const StorageSizeOffset: u32 = 8; + pub const RentByteFee: u64 = 4; + pub const RentDepositOffset: u64 = 10_000; + pub const SurchargeReward: u64 = 150; + pub const TransactionBaseFee: u64 = 2; + pub const TransactionByteFee: u64 = 6; + pub const ContractFee: u64 = 21; + pub const CallBaseFee: u64 = 135; + pub const InstantiateBaseFee: u64 = 175; + pub const MaxDepth: u32 = 100; + pub const MaxValueSize: u32 = 16_384; +} +impl Trait for GasTest { + type Currency = Balances; + type Time = Timestamp; + type Randomness = Randomness; + type Call = Call; + type DetermineContractAddress = DummyContractAddressFor; + type Event = MetaEvent; + type ComputeDispatchFee = DummyComputeDispatchFee; + type TrieIdGenerator = DummyTrieIdGenerator; + type GasPayment = (); + type GasHandler = TestGasHandler; + type RentPayment = (); + type SignedClaimHandicap = SignedClaimHandicap; + type TombstoneDeposit = TombstoneDeposit; + type StorageSizeOffset = StorageSizeOffset; + type RentByteFee = RentByteFee; + type RentDepositOffset = RentDepositOffset; + type SurchargeReward = SurchargeReward; + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type ContractFee = ContractFee; + type CallBaseFee = CallBaseFee; + type InstantiateBaseFee = InstantiateBaseFee; + type MaxDepth = MaxDepth; + type MaxValueSize = MaxValueSize; + type BlockGasLimit = BlockGasLimit; +} + +type Balances = balances::Module; +type Timestamp = pallet_timestamp::Module; +type Contract = Module; +type System = frame_system::Module; +type Randomness = pallet_randomness_collective_flip::Module; + +pub struct DummyContractAddressFor; +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 { + *origin + 1 + } +} + +pub struct DummyTrieIdGenerator; +impl TrieIdGenerator for DummyTrieIdGenerator { + fn trie_id(account_id: &u64) -> TrieId { + use sp_core::storage::well_known_keys; + + let new_seed = super::AccountCounter::mutate(|v| { + *v = v.wrapping_add(1); + *v + }); + + // TODO: see https://github.com/paritytech/substrate/issues/2325 + let mut res = vec![]; + res.extend_from_slice(well_known_keys::CHILD_STORAGE_KEY_PREFIX); + res.extend_from_slice(b"default:"); + res.extend_from_slice(&new_seed.to_le_bytes()); + res.extend_from_slice(&account_id.to_le_bytes()); + res + } +} + +pub struct DummyComputeDispatchFee; +impl ComputeDispatchFee for DummyComputeDispatchFee { + fn compute_dispatch_fee(call: &Call) -> u64 { + 69 + } +} + +// A trivial token that has a 1:1 cost with gas +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct SimpleToken(u64); +impl Token for SimpleToken { + type Metadata = (); + fn calculate_amount(&self, _metadata: &()) -> u64 { + self.0 + } +} + +const ALICE: u64 = 1; +// End of GasTest setup + +/// +/// This is an alternative implementation of GasHandler trait used for testing +/// +/// `fill_gas` will simply fill the gas meter without charging the user +/// `empty_unused_gas` will charge the user based on the actual amount of gas spent +/// +pub struct TestGasHandler; +impl GasHandler for TestGasHandler { + fn fill_gas( + _transactor: &::AccountId, + gas_limit: Gas, + ) -> Result, DispatchError> { + // fills the gas meter without charging the user + Ok(GasMeter::with_limit(gas_limit, 1)) + } + + fn empty_unused_gas( + transactor: &::AccountId, + gas_meter: GasMeter, + ) { + // charge the users based on the amount of gas used + buy_gas::(transactor, gas_meter.spent()); + } +} + +pub struct NoChargeGasHandler; +impl GasHandler for NoChargeGasHandler { + fn fill_gas( + _transactor: &::AccountId, + gas_limit: Gas, + ) -> Result, DispatchError> { + // fills the gas meter without charging the user + Ok(GasMeter::with_limit(gas_limit, 1)) + } + + fn empty_unused_gas( + transactor: &::AccountId, + gas_meter: GasMeter, + ) { + // Do not charge the transactor. Give gas for free. + } +} + +#[test] +// Tests that the user is not charged when filling up gas meters +fn customized_fill_gas_does_not_charge_the_user() { + ExtBuilder::default() + .existential_deposit(50) + .gas_price(1) + .build() + .execute_with(|| { + // Create test account + Balances::deposit_creating(&ALICE, 1000); + + let gas_limit = 500; + let mut gas_meter = NoChargeGasHandler::fill_gas(&ALICE, gas_limit).unwrap(); + // Charge as if the whole gas_limit is used + gas_meter.charge(&(), SimpleToken(gas_limit)); + NoChargeGasHandler::empty_unused_gas(&ALICE, gas_meter); + + // Check the user is not charged + assert_eq!(Balances::free_balance(&ALICE), 1000); + }); +} + +#[test] +// Tests that the user is charged on "emptying" unused gas +fn user_is_charged_on_empty_unused_gas() { + ExtBuilder::default() + .existential_deposit(50) + .gas_price(1) + .build() + .execute_with(|| { + // Fill the meter + let gas_used = 250; + Balances::deposit_creating(&ALICE, 1000); + let gas_meter_result = TestGasHandler::fill_gas(&ALICE, 500); + assert!(gas_meter_result.is_ok()); + let mut gas_meter = gas_meter_result.unwrap(); + + // Estimate the cost of gas used. Gas price is set to 1 + let expected_remaining_balance = Balances::free_balance(&ALICE) - gas_used; + + // Spend half the gas, then empty the gas meter, the user should be charged here + gas_meter.charge(&(), SimpleToken(gas_used)); + TestGasHandler::empty_unused_gas(&ALICE, gas_meter); + + assert_eq!(Balances::free_balance(&ALICE), expected_remaining_balance); + }); +} + +#[test] +// Tests that if the gas meter run out of gas, the user is charged the full amount of gas consumed. +fn user_is_charged_if_out_of_gas() { + ExtBuilder::default() + .existential_deposit(50) + .gas_price(1) + .build() + .execute_with(|| { + // Fill the meter + let gas_limit = 500; + Balances::deposit_creating(&ALICE, 1000); + let gas_meter_result = TestGasHandler::fill_gas(&ALICE, gas_limit); + let mut gas_meter = gas_meter_result.unwrap(); + + // We expect all the gas will be used up + let expected_remaining_balance = Balances::free_balance(&ALICE) - gas_limit; + + // Spend more gas than the `gas_limit`, then empty the gas meter, the user should be charged here + gas_meter.charge(&(), SimpleToken(gas_limit + 1)); + TestGasHandler::empty_unused_gas(&ALICE, gas_meter); + + assert_eq!(Balances::free_balance(&ALICE), expected_remaining_balance); + }); +} diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 571ae9700c..b6adc918c5 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -98,12 +98,15 @@ mod rent; #[cfg(test)] mod tests; +mod gas_tests; +#[cfg(test)] +mod doughnut_integration; use crate::exec::ExecutionContext; use crate::account_db::{AccountDb, DirectAccountDb}; use crate::wasm::{WasmLoader, WasmVm}; -pub use crate::gas::{Gas, GasMeter}; +pub use crate::gas::{Gas, GasMeter, GasHandler}; pub use crate::exec::{ExecResult, ExecReturnValue, ExecError, StatusCode}; #[cfg(feature = "std")] @@ -126,7 +129,7 @@ use frame_support::{ weights::DispatchInfo, }; use frame_support::traits::{OnKilledAccount, OnUnbalanced, Currency, Get, Time, Randomness}; -use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root}; +use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root, ensure_verified_contract_call}; use sp_core::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX; use pallet_contracts_primitives::{RentProjection, ContractAccessError}; @@ -369,6 +372,9 @@ pub trait Trait: frame_system::Trait { /// Handler for the unbalanced reduction when making a gas payment. type GasPayment: OnUnbalanced>; + /// Handler for filling and emptying the gas meter for a contract. + type GasHandler: GasHandler; + /// Handler for rent payments. type RentPayment: OnUnbalanced>; @@ -569,15 +575,19 @@ decl_module! { ) -> DispatchResult { let origin = ensure_signed(origin)?; - let (mut gas_meter, imbalance) = gas::buy_gas::(&origin, gas_limit)?; + let mut gas_meter = T::GasHandler::fill_gas(&origin, gas_limit)?; let schedule = >::current_schedule(); let result = wasm::save_code::(code, &mut gas_meter, &schedule); if let Ok(code_hash) = result { Self::deposit_event(RawEvent::CodeStored(code_hash)); } + // Increase total spent gas. + // This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which + // also has Gas type. + GasSpent::mutate(|block_gas_spent| *block_gas_spent += gas_meter.spent()); - gas::refund_unused_gas::(&origin, gas_meter, imbalance); + T::GasHandler::empty_unused_gas(&origin, gas_meter); result.map(|_| ()).map_err(Into::into) } @@ -596,10 +606,10 @@ decl_module! { #[compact] gas_limit: Gas, data: Vec ) -> DispatchResult { - let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; + let (origin, doughnut) = ensure_verified_contract_call::(origin, &dest)?; - Self::bare_call(origin, dest, value, gas_limit, data) + Self::bare_call(origin, dest, value, gas_limit, data, doughnut) .map(|_| ()) .map_err(|e| e.reason.into()) } @@ -623,7 +633,7 @@ decl_module! { ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| { + Self::execute_wasm(origin, gas_limit, None, |ctx, gas_meter| { ctx.instantiate(endowment, gas_meter, &code_hash, data) .map(|(_address, output)| output) }) @@ -681,8 +691,9 @@ impl Module { value: BalanceOf, gas_limit: Gas, input_data: Vec, + doughnut: Option, ) -> ExecResult { - Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| { + Self::execute_wasm(origin, gas_limit, doughnut, |ctx, gas_meter| { ctx.call(dest, value, gas_meter, input_data) }) } @@ -717,23 +728,21 @@ impl Module { fn execute_wasm( origin: T::AccountId, gas_limit: Gas, + doughnut: Option, func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult ) -> ExecResult { - // Pay for the gas upfront. - // - // NOTE: it is very important to avoid any state changes before - // paying for the gas. - let (mut gas_meter, imbalance) = - try_or_exec_error!( - gas::buy_gas::(&origin, gas_limit), - // We don't have a spare buffer here in the first place, so create a new empty one. - Vec::new() - ); + + // Fill up the gas meter upfront. Default behaviour is to pay for the gas upfront. + let mut gas_meter = try_or_exec_error!( + T::GasHandler::fill_gas(&origin, gas_limit), + // We don't have a spare buffer here in the first place, so create a new empty one. + Vec::new() + ); let cfg = Config::preload(); let vm = WasmVm::new(&cfg.schedule); let loader = WasmLoader::new(&cfg.schedule); - let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader); + let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader, doughnut.as_ref()); let result = func(&mut ctx, &mut gas_meter); @@ -742,11 +751,16 @@ impl Module { DirectAccountDb.commit(ctx.overlay.into_change_set()); } - // Refund cost of the unused gas. + // Increase total spent gas. + // This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which + // also has Gas type. + GasSpent::mutate(|block_gas_spent| *block_gas_spent += gas_meter.spent()); + + // Handle unused gas of the gas meter. Default behaviour is to refund cost of the unused gas. // // NOTE: This should go after the commit to the storage, since the storage changes // can alter the balance of the caller. - gas::refund_unused_gas::(&origin, gas_meter, imbalance); + T::GasHandler::empty_unused_gas(&origin, gas_meter); // Execute deferred actions. ctx.deferred.into_iter().for_each(|deferred| { @@ -759,6 +773,13 @@ impl Module { &*topics, ::Event::from(event).into(), ), + DelegatedRuntimeCall { + doughnut, + call, + } => { + let result = call.dispatch(RawOrigin::Delegated(origin.clone(), doughnut.clone()).into()); + Self::deposit_event(RawEvent::DelegatedDispatched(origin.clone(), doughnut, result.is_ok())); + }, DispatchRuntimeCall { origin: who, call, @@ -880,7 +901,8 @@ decl_event! { where Balance = BalanceOf, ::AccountId, - ::Hash + ::Hash, + ::Doughnut, { /// Transfer happened `from` to `to` with given `value` as part of a `call` or `instantiate`. Transfer(AccountId, AccountId, Balance), @@ -913,6 +935,10 @@ decl_event! { /// Triggered when the current schedule is updated. ScheduleUpdated(u32), + /// A call was dispatched from the given account with a doughnut. The bool signals whether it was + /// successful execution or not. + DelegatedDispatched(AccountId, Doughnut, bool), + /// A call was dispatched from the given account. The bool signals whether it was /// successful execution or not. Dispatched(AccountId, bool), diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index e775998a3a..b1cdcd4e03 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -115,6 +115,8 @@ impl frame_system::Trait for Test { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = Contracts; @@ -159,6 +161,7 @@ impl Trait for Test { type ComputeDispatchFee = DummyComputeDispatchFee; type TrieIdGenerator = DummyTrieIdGenerator; type GasPayment = (); + type GasHandler = (); type RentPayment = (); type SignedClaimHandicap = SignedClaimHandicap; type TombstoneDeposit = TombstoneDeposit; @@ -297,13 +300,30 @@ fn compile_module(wabt_module: &str) // Then we check that the all unused gas is refunded. #[test] fn refunds_unused_gas() { - ExtBuilder::default().gas_price(2).build().execute_with(|| { + const GAS_PRICE:u64 = 2; + ExtBuilder::default().gas_price(GAS_PRICE).build().execute_with(|| { Balances::deposit_creating(&ALICE, 100_000_000); assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, Vec::new())); // 2 * 135 - gas price multiplied by the call base fee. - assert_eq!(Balances::free_balance(ALICE), 100_000_000 - (2 * 135)); + assert_eq!(Balances::free_balance(ALICE), 100_000_000 - (GAS_PRICE * CallBaseFee::get())); + }); +} + +/// Test that if the user does not provide enough gas to run the contract, the contract execution should +/// fail, and the gas will be spent and not refunded to the user +#[test] +fn gas_limit_below_base_fee_supplied() { + ExtBuilder::default().gas_price(1).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 100_000_000); + let gas_limit = CallBaseFee::get() - 1; + assert_err!( + Contracts::call(Origin::signed(ALICE), BOB, 0, gas_limit, Vec::new()), + "not enough gas to pay base call fee" + ); + // Contract caller still gets charged their gas limit + assert_eq!(Balances::free_balance(ALICE), 100_000_000 - gas_limit); }); } @@ -1460,8 +1480,8 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: assert_ok!(Contracts::call( Origin::signed(ALICE), BOB, 0, 100_000, - call::set_storage_4_byte()) - ); + call::set_storage_4_byte(), + )); } // Advance 4 blocks, to the 5th. diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 1ea2067a8d..265ac30569 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -153,9 +153,9 @@ mod tests { use std::collections::HashMap; use std::cell::RefCell; use sp_core::H256; + use crate::doughnut_integration::{MockDoughnut, Test, Call}; use crate::exec::{Ext, StorageKey, ExecError, ExecReturnValue, STATUS_SUCCESS}; use crate::gas::{Gas, GasMeter}; - use crate::tests::{Test, Call}; use crate::wasm::prepare::prepare_contract; use crate::CodeHash; use wabt; @@ -166,6 +166,9 @@ mod tests { #[derive(Debug, PartialEq, Eq)] struct DispatchEntry(Call); + #[derive(Debug, PartialEq, Eq)] + struct DelegatedDispatchEntry(MockDoughnut, Call); + #[derive(Debug, PartialEq, Eq)] struct RestoreEntry { dest: u64, @@ -212,6 +215,18 @@ mod tests { /// This behavior is used to prevent mixing up an access to unexpected location and empty /// cell. runtime_storage_keys: RefCell, Option>>>, + + // Mock fields to test doughnut dispatch. + delegated_dispatches: Vec, + doughnut: Option, + } + + impl MockExt { + fn default_with_doughnut(doughnut: Option) -> Self { + let mut mock_ext = Self::default(); + let _ = mock_ext.doughnut = doughnut; + mock_ext + } } impl Ext for MockExt { @@ -261,6 +276,9 @@ mod tests { // TODO: Add tests for different call outcomes. Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) } + fn note_delegated_dispatch_call(&mut self, doughnut: MockDoughnut, call: Call) { + self.delegated_dispatches.push(DelegatedDispatchEntry(doughnut, call)); + } fn note_dispatch_call(&mut self, call: Call) { self.dispatches.push(DispatchEntry(call)); } @@ -281,9 +299,15 @@ mod tests { fn caller(&self) -> &u64 { &42 } + fn origin(&self) -> &u64 { + &24 + } fn address(&self) -> &u64 { &69 } + fn doughnut(&self) -> Option<&MockDoughnut> { + self.doughnut.as_ref() + } fn balance(&self) -> u64 { 228 } @@ -366,6 +390,9 @@ mod tests { ) -> ExecResult { (**self).call(to, value, gas_meter, input_data) } + fn note_delegated_dispatch_call(&mut self, doughnut: MockDoughnut, call: Call) { + (**self).note_delegated_dispatch_call(doughnut, call) + } fn note_dispatch_call(&mut self, call: Call) { (**self).note_dispatch_call(call) } @@ -386,9 +413,15 @@ mod tests { fn caller(&self) -> &u64 { (**self).caller() } + fn origin(&self) -> &u64 { + (**self).origin() + } fn address(&self) -> &u64 { (**self).address() } + fn doughnut(&self) -> Option<&MockDoughnut> { + (**self).doughnut() + } fn balance(&self) -> u64 { (**self).balance() } @@ -781,6 +814,68 @@ mod tests { ).unwrap(); } + /// calls `ext_origin`, loads the address from the scratch buffer and + /// compares it with the constant 24. + const CODE_ORIGIN: &str = r#" +(module + (import "env" "ext_origin" (func $ext_origin)) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the scratch buffer with the caller. + (call $ext_origin) + + ;; assert $ext_scratch_size == 8 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 8) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to the i64 value of 24. + (call $assert + (i64.eq + (i64.load + (i32.const 8) + ) + (i64.const 24) + ) + ) + ) + + (func (export "deploy")) +) +"#; + + #[test] + fn origin() { + let _ = execute( + CODE_ORIGIN, + vec![], + MockExt::default(), + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + } + /// calls `ext_address`, loads the address from the scratch buffer and /// compares it with the constant 69. const CODE_ADDRESS: &str = r#" @@ -1124,6 +1219,60 @@ mod tests { ); } + const CODE_DELEGATED_DISPATCH_CALL: &str = r#" +(module + (import "env" "ext_delegated_dispatch_call" (func $ext_delegated_dispatch_call (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_delegated_dispatch_call + (i32.const 8) ;; Pointer to the start of encoded call buffer + (i32.const 13) ;; Length of the buffer + ) + ) + (func (export "deploy")) + + (data (i32.const 8) "\00\01\2B\00\00\00\00\00\00\00\E5\14\00") +) +"#; + + #[test] + fn delegated_dispatch_call_works_with_doughnut() { + // This test is derived from the unit test, dispatch_call(). + let verifiable_doughnut = MockDoughnut::default() + .set_runtime_verifiable(true) + .set_contract_verifiable(true); + let mut mock_ext = MockExt::default_with_doughnut(Some(verifiable_doughnut.clone())); + let _ = execute( + CODE_DELEGATED_DISPATCH_CALL, + vec![], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!( + &mock_ext.delegated_dispatches, + &[DelegatedDispatchEntry( + verifiable_doughnut, + Call::Balances(pallet_balances::Call::set_balance(43, 1337, 0)), + )] + ); + } + + #[test] + fn delegated_dispatch_call_fails_without_doughnut() { + let mut mock_ext = MockExt::default_with_doughnut(None); + assert_matches!( + execute( + CODE_DELEGATED_DISPATCH_CALL, + vec![], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ), + Err(ExecError { reason: DispatchError::Other("contract trapped during execution"), buffer: _ }) + ); + } + const CODE_RETURN_FROM_START_FN: &str = r#" (module (import "env" "ext_return" (func $ext_return (param i32 i32))) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index a84556d884..ee53fc37c1 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -22,11 +22,14 @@ use crate::exec::{ }; use crate::gas::{Gas, GasMeter, Token, GasMeterResult, approx_gas_for_balance}; use sp_sandbox; +use frame_support::additional_traits::DelegatedDispatchVerifier; use frame_system; use sp_std::{prelude::*, mem, convert::TryInto}; use codec::{Decode, Encode}; use sp_runtime::traits::{Bounded, SaturatedConversion}; +type DelegatedDispatchVerifierOf = <::T as frame_system::Trait>::DelegatedDispatchVerifier; + /// The value returned from ext_call and ext_instantiate contract external functions if the call or /// instantiation traps. This value is chosen as if the execution does not trap, the return value /// will always be an 8-bit integer, so 0x0100 is the smallest value that could not be returned. @@ -429,6 +432,14 @@ define_env!(Env, , let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; + if let Some(doughnut) = ctx.ext.doughnut() { + DelegatedDispatchVerifierOf::::verify_contract_to_contract_call( + &ctx.ext.origin(), + doughnut, + &callee, + ).map_err(|_| sp_sandbox::HostError)?; + } + // Read input data into the scratch buffer, then take ownership of it. read_sandbox_memory_into_scratch(ctx, input_data_ptr, input_data_len)?; let input_data = mem::replace(&mut ctx.scratch_buf, Vec::new()); @@ -593,6 +604,13 @@ define_env!(Env, , Ok(()) }, + // Stores the the origin address of the extrinsic into the scratch buffer. + ext_origin(ctx) => { + ctx.scratch_buf.clear(); + ctx.ext.origin().encode_to(&mut ctx.scratch_buf); + Ok(()) + }, + // Stores the address of the current contract into the scratch buffer. ext_address(ctx) => { ctx.scratch_buf.clear(); @@ -713,6 +731,35 @@ define_env!(Env, , Ok(()) }, + // A method to dispatch delegated calls, similar to ext_dispatch_call, whose purpose is to provide + // users a way to make a delegated contract call from the users' account with doughnut. + ext_delegated_dispatch_call(ctx, call_ptr: u32, call_len: u32) => { + let call: <::T as Trait>::Call = + read_sandbox_memory_as(ctx, call_ptr, call_len)?; + + // Charge gas for dispatching this call. + let fee = { + let balance_fee = <::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call); + approx_gas_for_balance(ctx.gas_meter.gas_price(), balance_fee) + }; + charge_gas( + &mut ctx.gas_meter, + ctx.schedule, + &mut ctx.special_trap, + RuntimeToken::ComputedDispatchFee(fee) + )?; + + if let Some(doughnut) = ctx.ext.doughnut() { + // Use of intermediate variable to avoid `#[warn(mutable_borrow_reservation_conflict)]` + let d = (*doughnut).clone(); + ctx.ext.note_delegated_dispatch_call(d, call); + } else { + return Err(sp_sandbox::HostError) + } + + Ok(()) + }, + // Record a request to restore the caller contract to the specified contract. // // At the finalization stage, i.e. when all changes from the extrinsic that invoked this diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 6755fabd20..bf0e872d26 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1298,6 +1298,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -1349,12 +1351,12 @@ mod tests { type VotingPeriod = VotingPeriod; type EmergencyVotingPeriod = EmergencyVotingPeriod; type MinimumDeposit = MinimumDeposit; - type ExternalOrigin = EnsureSignedBy; - type ExternalMajorityOrigin = EnsureSignedBy; - type ExternalDefaultOrigin = EnsureSignedBy; - type FastTrackOrigin = EnsureSignedBy; - type CancellationOrigin = EnsureSignedBy; - type VetoOrigin = EnsureSignedBy; + type ExternalOrigin = EnsureSignedBy; + type ExternalMajorityOrigin = EnsureSignedBy; + type ExternalDefaultOrigin = EnsureSignedBy; + type FastTrackOrigin = EnsureSignedBy; + type CancellationOrigin = EnsureSignedBy; + type VetoOrigin = EnsureSignedBy; type CooloffPeriod = CooloffPeriod; type PreimageByteDeposit = PreimageByteDeposit; type Slash = (); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index a9474ae844..118109a59c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -814,6 +814,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/elections/src/mock.rs b/frame/elections/src/mock.rs index b82e73d512..f346963f6f 100644 --- a/frame/elections/src/mock.rs +++ b/frame/elections/src/mock.rs @@ -54,6 +54,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/example-offchain-worker/src/tests.rs b/frame/example-offchain-worker/src/tests.rs index 9b6a567a17..bd3fd15a1a 100644 --- a/frame/example-offchain-worker/src/tests.rs +++ b/frame/example-offchain-worker/src/tests.rs @@ -20,6 +20,7 @@ use codec::Decode; use frame_support::{ assert_ok, impl_outer_origin, parameter_types, weights::{GetDispatchInfo, Weight}, + additional_traits::DummyDispatchVerifier, }; use sp_core::{ H256, @@ -65,6 +66,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = DummyDispatchVerifier; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/example/src/lib.rs b/frame/example/src/lib.rs index f4139a310a..15d9ee8836 100644 --- a/frame/example/src/lib.rs +++ b/frame/example/src/lib.rs @@ -688,6 +688,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 0becc170a3..664f19c852 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -22,6 +22,8 @@ sp-core = { version = "2.0.0-dev", path = "../../primitives/core" } pallet-indices = { version = "2.0.0-dev", path = "../indices" } pallet-balances = { version = "2.0.0-dev", path = "../balances" } pallet-transaction-payment = { version = "2.0.0-dev", path = "../transaction-payment" } +sp-keyring = { path = "../../primitives/keyring" } +prml-doughnut = { path = "../../prml/doughnut" } [features] default = ["std"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index d7fecf4590..c1c334e0f2 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -121,7 +121,7 @@ where Applyable + GetDispatchInfo, CallOf: Dispatchable, - OriginOf: From>, + OriginOf: From<(Option, Option)>, UnsignedValidator: ValidateUnsigned>, { fn execute_block(block: Block) { @@ -146,7 +146,7 @@ where Applyable + GetDispatchInfo, CallOf: Dispatchable, - OriginOf: From>, + OriginOf: From<(Option, Option)>, UnsignedValidator: ValidateUnsigned>, { /// Start the execution of a particular block. @@ -370,15 +370,17 @@ where mod tests { use super::*; use sp_core::H256; + use prml_doughnut::{DoughnutRuntime, PlugDoughnut}; use sp_runtime::{ - generic::Era, Perbill, DispatchError, testing::{Digest, Header, Block}, + generic::Era, Perbill, DispatchError, testing::{Digest, Header, Block, doughnut::{TestAccountId}}, traits::{Header as HeaderT, BlakeTwo256, IdentityLookup, ConvertInto}, transaction_validity::{InvalidTransaction, UnknownTransaction, TransactionValidityError}, }; use frame_support::{ impl_outer_event, impl_outer_origin, parameter_types, impl_outer_dispatch, + additional_traits::{DelegatedDispatchVerifier}, + traits::{Currency, LockIdentifier, LockableCurrency, Time, WithdrawReasons, WithdrawReason}, weights::Weight, - traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason}, }; use frame_system::{self as system, Call as SystemCall, ChainContext}; use pallet_balances::Call as BalancesCall; @@ -390,7 +392,7 @@ mod tests { pub trait Trait: frame_system::Trait {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, system = frame_system { #[weight = SimpleDispatchInfo::FixedNormal(100)] fn some_function(origin) { // NOTE: does not make any different. @@ -442,6 +444,22 @@ mod tests { } } + // We aren't testing doughnut verification here just return `Ok(())` + pub struct MockDelegatedDispatchVerifier(sp_std::marker::PhantomData); + impl DelegatedDispatchVerifier for MockDelegatedDispatchVerifier { + type Doughnut = T::Doughnut; + type AccountId = T::AccountId; + const DOMAIN: &'static str = ""; + fn verify_dispatch( + _doughnut: &T::Doughnut, + _module: &str, + _method: &str, + ) -> Result<(), &'static str> { + Ok(()) + } + } + + // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, Eq, PartialEq)] pub struct Runtime; parameter_types! { @@ -457,8 +475,8 @@ mod tests { type BlockNumber = u64; type Hash = sp_core::H256; type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; + type AccountId = TestAccountId; + type Lookup = IdentityLookup; type Header = Header; type Event = MetaEvent; type BlockHashCount = BlockHashCount; @@ -467,10 +485,23 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = PlugDoughnut; + type DelegatedDispatchVerifier = MockDelegatedDispatchVerifier; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } + pub struct TimestampProvider; + impl Time for TimestampProvider { + type Moment = u64; + fn now() -> Self::Moment { 0 } + } + impl DoughnutRuntime for Runtime { + type AccountId = ::AccountId; + type Call = ::Call; + type Doughnut = ::Doughnut; + type TimestampProvider = TimestampProvider; + } parameter_types! { pub const ExistentialDeposit: u64 = 1; } @@ -512,17 +543,18 @@ mod tests { } type SignedExtra = ( + Option>, frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment ); type AllModules = (System, Balances, Custom); - type TestXt = sp_runtime::testing::TestXt; + type TestXt = sp_runtime::testing::DoughnutTestXt; type Executive = super::Executive, ChainContext, Runtime, AllModules>; - - fn extra(nonce: u64, fee: u64) -> SignedExtra { + fn extra(nonce: u64, fee: u64, doughnut: Option>) -> SignedExtra { ( + doughnut, frame_system::CheckEra::from(Era::Immortal), frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), @@ -530,17 +562,17 @@ mod tests { ) } - fn sign_extra(who: u64, nonce: u64, fee: u64) -> (u64, SignedExtra) { - (who, extra(nonce, fee)) + fn sign_extra(who: TestAccountId, nonce: u64, fee: u64, doughnut: Option>) -> (TestAccountId, SignedExtra) { + (who.into(), extra(nonce, fee, doughnut)) } #[test] fn balance_transfer_dispatch_works() { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 211)], + balances: vec![(1.into(), 211)], }.assimilate_storage(&mut t).unwrap(); - let xt = TestXt::new_signed(sign_extra(1, 0, 0), Call::Balances(BalancesCall::transfer(2, 69))); + let xt = TestXt::new_signed(sign_extra(1.into(), 0, 0, None), Call::Balances(BalancesCall::transfer(2.into(), 69))); let weight = xt.get_dispatch_info().weight as u64; let mut t = sp_io::TestExternalities::new(t); t.execute_with(|| { @@ -553,15 +585,15 @@ mod tests { )); let r = Executive::apply_extrinsic(xt); assert!(r.is_ok()); - assert_eq!(>::total_balance(&1), 142 - 10 - weight); - assert_eq!(>::total_balance(&2), 69); + assert_eq!(>::total_balance(&1.into()), 142 - 10 - weight); + assert_eq!(>::total_balance(&2.into()), 69); }); } fn new_test_ext(balance_factor: u64) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 111 * balance_factor)], + balances: vec![(1.into(), 111 * balance_factor)], }.assimilate_storage(&mut t).unwrap(); t.into() } @@ -620,7 +652,7 @@ mod tests { fn bad_extrinsic_not_inserted() { let mut t = new_test_ext(1); // bad nonce check! - let xt = TestXt::new_signed(sign_extra(1, 30, 0), Call::Balances(BalancesCall::transfer(33, 69))); + let xt = TestXt::new_signed(sign_extra(1.into(), 30, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 69))); t.execute_with(|| { Executive::initialize_block(&Header::new( 1, @@ -638,7 +670,7 @@ mod tests { fn block_weight_limit_enforced() { let mut t = new_test_ext(10000); // given: TestXt uses the encoded len as fixed Len: - let xt = TestXt::new_signed(sign_extra(1, 0, 0), Call::Balances(BalancesCall::transfer(33, 0))); + let xt = TestXt::new_signed(sign_extra(1.into(), 0, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))); let encoded = xt.encode(); let encoded_len = encoded.len() as Weight; let limit = AvailableBlockRatio::get() * MaximumBlockWeight::get() - 175; @@ -656,7 +688,7 @@ mod tests { for nonce in 0..=num_to_exhaust_block { let xt = TestXt::new_signed( - sign_extra(1, nonce.into(), 0), Call::Balances(BalancesCall::transfer(33, 0)), + sign_extra(1.into(), nonce.into(), 0, None), Call::Balances(BalancesCall::transfer(3.into(), 0)), ); let res = Executive::apply_extrinsic(xt); if nonce != num_to_exhaust_block { @@ -675,9 +707,9 @@ mod tests { #[test] fn block_weight_and_size_is_stored_per_tx() { - let xt = TestXt::new_signed(sign_extra(1, 0, 0), Call::Balances(BalancesCall::transfer(33, 0))); - let x1 = TestXt::new_signed(sign_extra(1, 1, 0), Call::Balances(BalancesCall::transfer(33, 0))); - let x2 = TestXt::new_signed(sign_extra(1, 2, 0), Call::Balances(BalancesCall::transfer(33, 0))); + let xt = TestXt::new_signed(sign_extra(1.into(), 0, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))); + let x1 = TestXt::new_signed(sign_extra(1.into(), 1, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))); + let x2 = TestXt::new_signed(sign_extra(1.into(), 2, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))); let len = xt.clone().encode().len() as u32; let mut t = new_test_ext(1); t.execute_with(|| { @@ -701,7 +733,7 @@ mod tests { #[test] fn validate_unsigned() { - let xt = TestXt::new_unsigned(Call::Balances(BalancesCall::set_balance(33, 69, 69))); + let xt = TestXt::new_unsigned(Call::Balances(BalancesCall::set_balance(33.into(), 69, 69))); let mut t = new_test_ext(1); t.execute_with(|| { @@ -712,7 +744,7 @@ mod tests { #[test] fn unsigned_weight_is_noted_when_applied() { - let xt = TestXt::new_unsigned(Call::Balances(BalancesCall::set_balance(33, 69, 69))); + let xt = TestXt::new_unsigned(Call::Balances(BalancesCall::set_balance(33.into(), 69, 69))); let len = xt.clone().encode().len() as u32; let mut t = new_test_ext(1); t.execute_with(|| { @@ -729,7 +761,7 @@ mod tests { #[test] fn apply_trusted_skips_signature_check_but_not_others() { - let xt1 = TestXt::new_signed(sign_extra(1, 0, 0), Call::Balances(BalancesCall::transfer(33, 0))) + let xt1 = TestXt::new_signed(sign_extra(1.into(), 0, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))) .badly_signed(); let mut t = new_test_ext(1); @@ -738,7 +770,7 @@ mod tests { assert_eq!(Executive::apply_trusted_extrinsic(xt1), Ok(Ok(()))); }); - let xt2 = TestXt::new_signed(sign_extra(1, 0, 0), Call::Balances(BalancesCall::transfer(33, 0))) + let xt2 = TestXt::new_signed(sign_extra(1.into(), 0, 0, None), Call::Balances(BalancesCall::transfer(33.into(), 0))) .invalid(TransactionValidityError::Invalid(InvalidTransaction::Call)); t.execute_with(|| { @@ -755,14 +787,14 @@ mod tests { let execute_with_lock = |lock: WithdrawReasons| { let mut t = new_test_ext(1); t.execute_with(|| { - as LockableCurrency>::set_lock( + as LockableCurrency>::set_lock( id, - &1, + &1.into(), 110, lock, ); let xt = TestXt::new_signed( - sign_extra(1, 0, 0), + sign_extra(1.into(), 0, 0, None), Call::System(SystemCall::remark(vec![1u8])), ); let weight = xt.get_dispatch_info().weight as u64; @@ -777,13 +809,13 @@ mod tests { if lock == WithdrawReasons::except(WithdrawReason::TransactionPayment) { assert!(Executive::apply_extrinsic(xt).unwrap().is_ok()); // tx fee has been deducted. - assert_eq!(>::total_balance(&1), 111 - 10 - weight); + assert_eq!(>::total_balance(&1.into()), 111 - 10 - weight); } else { assert_eq!( Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()), ); - assert_eq!(>::total_balance(&1), 111); + assert_eq!(>::total_balance(&1.into()), 111); } }); }; diff --git a/frame/executive/tests/doughnut_integration.rs b/frame/executive/tests/doughnut_integration.rs new file mode 100644 index 0000000000..f413be9bef --- /dev/null +++ b/frame/executive/tests/doughnut_integration.rs @@ -0,0 +1,476 @@ +// Copyright 2019 Plug New Zealand Limited +// +// 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. + +//! +//! Doughnut + Extrinsic + Executive integration tests +//! +#![cfg(test)] +use pallet_balances::Call as BalancesCall; +use codec::Encode; +use prml_doughnut::{DoughnutRuntime, PlugDoughnut}; +use sp_core::{crypto::UncheckedFrom, H256}; +use sp_keyring::AccountKeyring; +use sp_runtime::{ + DispatchError, Doughnut, DoughnutV0, MultiSignature, + generic::{self, Era}, Perbill, testing::{Block, Digest, Header}, + traits::{IdentifyAccount, IdentityLookup, Header as HeaderT, BlakeTwo256, Verify, ConvertInto, PlugDoughnutApi, DoughnutApi}, + transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError, UnknownTransaction}, +}; +#[allow(deprecated)] +use sp_runtime::traits::ValidateUnsigned; +use frame_support::{ + impl_outer_event, impl_outer_origin, parameter_types, impl_outer_dispatch, + additional_traits::{DelegatedDispatchVerifier}, + traits::{Currency, Time}, +}; +use frame_system as system; + +type AccountId = <::Signer as IdentifyAccount>::AccountId; +type Address = AccountId; +type Index = u32; +type Signature = MultiSignature; +type System = frame_system::Module; +type Balances = pallet_balances::Module; + +impl_outer_origin! { + pub enum Origin for Runtime {} +} + +impl_outer_event!{ + pub enum MetaEvent for Runtime { + frame_system, + pallet_balances, + } +} +impl_outer_dispatch! { + pub enum Call for Runtime where origin: Origin { + frame_system::System, + pallet_balances::Balances, + } +} + +pub struct MockDelegatedDispatchVerifier(sp_std::marker::PhantomData); +impl DelegatedDispatchVerifier for MockDelegatedDispatchVerifier { + type Doughnut = T::Doughnut; + type AccountId = T::AccountId; + const DOMAIN: &'static str = "test"; + fn verify_dispatch( + doughnut: &T::Doughnut, + _module: &str, + _method: &str, + ) -> Result<(), &'static str> { + // Check the "test" domain has a byte set to `1` for Ok, fail otherwise + let verify = doughnut.get_domain(Self::DOMAIN).unwrap()[0]; + if verify == 1 { + Ok(()) + } else { + Err("dispatch unverified") + } + } +} + +// Create a minimal runtime to verify doughnut's are properly integrated +// This means we are using full-blown types i.e sr25519 Public Keys for AccountId and CheckedExtrinsic +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, Eq, PartialEq)] +pub struct Runtime; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Runtime { + type Origin = Origin; + type Index = Index; + type Call = Call; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = MetaEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type Doughnut = PlugDoughnut; + type DelegatedDispatchVerifier = MockDelegatedDispatchVerifier; + type AccountData = pallet_balances::AccountData; + type OnKilledAccount = (); + type OnNewAccount = (); +} +pub struct TimestampProvider; +impl Time for TimestampProvider { + type Moment = u64; + fn now() -> Self::Moment { + // Return a fixed timestamp (ms) + // It should be > 0 to allow doughnut timestamp validation checks to pass + 10_000 + } +} +impl DoughnutRuntime for Runtime { + type AccountId = ::AccountId; + type Call = ::Call; + type Doughnut = ::Doughnut; + type TimestampProvider = TimestampProvider; +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const TransferFee: u64 = 0; + pub const CreationFee: u64 = 0; +} +impl pallet_balances::Trait for Runtime { + type Balance = u64; + type Event = MetaEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +parameter_types! { + pub const TransactionBaseFee: u64 = 10; + pub const TransactionByteFee: u64 = 0; +} +impl pallet_transaction_payment::Trait for Runtime { + type Currency = Balances; + type OnTransactionPayment = (); + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type WeightToFee = ConvertInto; + type FeeMultiplierUpdate = (); +} + +#[allow(deprecated)] // Allow ValidateUnsigned +impl ValidateUnsigned for Runtime { + type Call = Call; + + fn validate_unsigned(call: &Self::Call) -> TransactionValidity { + match call { + Call::Balances(BalancesCall::set_balance(_, _, _)) => Ok(Default::default()), + _ => UnknownTransaction::NoUnsignedValidator.into(), + } + } +} +type SignedExtra = ( + Option>, + frame_system::CheckVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment +); + +type CheckedExtrinsic = generic::CheckedExtrinsic; // Just a `CheckedExtrinsic` with type parameters set +type UncheckedExtrinsic = generic::UncheckedExtrinsic; // Just an `UnheckedExtrinsic` with type parameters set +type Executive = frame_executive::Executive, frame_system::ChainContext, Runtime, ()>; + +/// Returns transaction extra. +fn signed_extra(nonce: Index, fee: u64, doughnut: Option>) -> SignedExtra { + ( + doughnut, + frame_system::CheckVersion::new(), + frame_system::CheckGenesis::new(), + frame_system::CheckEra::from(Era::mortal(256, 0)), + frame_system::CheckNonce::from(nonce), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(fee), + ) +} + +/// Sign a given `CheckedExtrinsic` (lifted from `node/keyring`) +fn sign_extrinsic(xt: CheckedExtrinsic) -> UncheckedExtrinsic { + match xt.signed { + Some((signed, extra)) => { + let raw_payload = generic::SignedPayload::new(xt.function, extra.clone()).expect("signed payload is valid"); + let signed_ = UncheckedFrom::<[u8; 32]>::unchecked_from(signed.clone().into()); // `AccountId32` => `sr25519::Public` + let key = AccountKeyring::from_public(&signed_).unwrap(); + let signature = raw_payload.using_encoded(|payload| { + if payload.len() > 256 { + key.sign(&sp_io::hashing::blake2_256(payload)) + } else { + key.sign(payload) + } + }).into(); + let (function, extra, _) = raw_payload.deconstruct(); + UncheckedExtrinsic { + signature: Some((signed, signature, extra)), + function, + } + } + None => UncheckedExtrinsic { + signature: None, + function: xt.function, + }, + } +} + +/// Create a valid `DoughnutV0` given an `issuer` and `holder` +fn make_doughnut(issuer: AccountId, holder: AccountId, not_before: Option, expiry: Option, permission_domain_verify: bool) -> Doughnut { + let issuer_pk = UncheckedFrom::<[u8; 32]>::unchecked_from(issuer.clone().into()); // `AccountId32` => `sr25519::Public` + let issuer_key = AccountKeyring::from_public(&issuer_pk).unwrap(); + let mut doughnut = DoughnutV0 { + issuer: issuer.into(), + holder: holder.into(), + expiry: expiry.unwrap_or(u32::max_value()), + not_before: not_before.unwrap_or(0), + payload_version: 0, + signature_version: 0, // sr25519 + signature: [0u8; 64].into(), + domains: vec![("test".to_string(), vec![permission_domain_verify as u8])], + }; + doughnut.signature = issuer_key.sign(&doughnut.payload()).into(); + Doughnut::V0(doughnut) +} + +// TODO: These tests are very repitious, could be DRYed up with macros +#[test] +fn delegated_dispatch_works() { + // Submit an extrinsic with attached doughnut proof to the test Runtime. + // The extrinsic is instigated by Bob (doughnut holder) on Alice's authority (doughnut issuer) + // funds are transferred from Alice to Charlie (receiver) + // Note: We do not verify the contents of the doughnut's permission `domains` section + let issuer_alice: AccountId = AccountKeyring::Alice.into(); + let holder_bob: AccountId = AccountKeyring::Bob.into(); + let receiver_charlie: AccountId = AccountKeyring::Charlie.into(); + + // Setup storage + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(issuer_alice.clone(), 10_011), (holder_bob.clone(), 10_011)], + }.assimilate_storage(&mut t).unwrap(); + + // The doughnut proof is wrapped for embeddeding in extrinsic + let doughnut = PlugDoughnut::::new( + make_doughnut( + issuer_alice.clone(), + holder_bob.clone(), + None, + None, + true, + ) + ); + + let mut t = sp_io::TestExternalities::new(t); + t.execute_with(|| { + // Setup extrinsic + let xt = CheckedExtrinsic { + signed: Some(( + holder_bob.clone(), + signed_extra(0, 0, Some(doughnut)), + )), + function: Call::Balances(BalancesCall::transfer(receiver_charlie.clone().into(), 69)), + }; + let uxt = sign_extrinsic(xt); + + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + + // Submit an extrinsic with attached doughnut proof to the test Runtime. + let r = Executive::apply_extrinsic(uxt); + assert!(r.is_ok()); + assert_eq!(>::total_balance(&issuer_alice), 10_011 - 69); // 69 transferred + assert_eq!(>::total_balance(&holder_bob), 10_011 - 10 - 1024); // fees deducted + assert_eq!(>::total_balance(&receiver_charlie), 69); // Received 69 + }); +} + +#[test] +fn delegated_dispatch_fails_when_extrinsic_signer_is_not_doughnut_holder() { + // Submit an extrinsic with attached doughnut proof to the test Runtime. + // The extrinsic is instigated by Charlie, however the doughnut proof is to Bob (doughnut holder) on Alice's authority (doughnut issuer) + // Funds are not transferred + let issuer_alice: AccountId = AccountKeyring::Alice.into(); + let holder_bob: AccountId = AccountKeyring::Bob.into(); + let receiver_charlie: AccountId = AccountKeyring::Charlie.into(); + + // Setup storage + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(issuer_alice.clone(), 10_011), (holder_bob.clone(), 10_011)], + }.assimilate_storage(&mut t).unwrap(); + + let doughnut = PlugDoughnut::::new( + make_doughnut( + issuer_alice.clone(), + holder_bob.clone(), + None, + None, + true, + ) + ); + + let mut t = sp_io::TestExternalities::new(t); + t.execute_with(|| { + let xt = CheckedExtrinsic { + signed: Some(( + receiver_charlie.clone(), + signed_extra(0, 0, Some(doughnut)), + )), + function: Call::Balances(BalancesCall::transfer(receiver_charlie.clone().into(), 69)), + }; + let uxt = sign_extrinsic(xt); + + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + + let r = Executive::apply_extrinsic(uxt); + assert_eq!(r, Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(180)))); + }); +} + +#[test] +fn delegated_dispatch_fails_when_doughnut_is_expired() { + let issuer_alice: AccountId = AccountKeyring::Alice.into(); + let holder_bob: AccountId = AccountKeyring::Bob.into(); + let receiver_charlie: AccountId = AccountKeyring::Charlie.into(); + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(issuer_alice.clone(), 10_011), (holder_bob.clone(), 10_011)], + }.assimilate_storage(&mut t).unwrap(); + + let doughnut = PlugDoughnut::::new( + make_doughnut( + issuer_alice.clone(), + holder_bob.clone(), + None, + Some(9), // some expired timestamp (s) + true, + ) + ); + + let mut t = sp_io::TestExternalities::new(t); + t.execute_with(|| { + let xt = CheckedExtrinsic { + signed: Some(( + holder_bob.clone(), + signed_extra(0, 0, Some(doughnut)), + )), + function: Call::Balances(BalancesCall::transfer(receiver_charlie.clone().into(), 69)), + }; + let uxt = sign_extrinsic(xt); + + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + + let r = Executive::apply_extrinsic(uxt); + assert_eq!(r, Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(181)))); + }); +} + +#[test] +fn delegated_dispatch_fails_when_doughnut_is_premature() { + let issuer_alice: AccountId = AccountKeyring::Alice.into(); + let holder_bob: AccountId = AccountKeyring::Bob.into(); + let receiver_charlie: AccountId = AccountKeyring::Charlie.into(); + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(issuer_alice.clone(), 10_011), (holder_bob.clone(), 10_011)], + }.assimilate_storage(&mut t).unwrap(); + + let doughnut = PlugDoughnut::::new( + make_doughnut( + issuer_alice.clone(), + holder_bob.clone(), + Some(11), // Some future timestamp (s) + None, + true, + ) + ); + + let mut t = sp_io::TestExternalities::new(t); + t.execute_with(|| { + let xt = CheckedExtrinsic { + signed: Some(( + holder_bob.clone(), + signed_extra(0, 0, Some(doughnut)), + )), + function: Call::Balances(BalancesCall::transfer(receiver_charlie.clone().into(), 69)), + }; + let uxt = sign_extrinsic(xt); + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + let r = Executive::apply_extrinsic(uxt); + assert_eq!(r, Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(182)))); + }); +} + +#[test] +fn delegated_dispatch_fails_when_doughnut_domain_permission_is_unverified() { + let issuer_alice: AccountId = AccountKeyring::Alice.into(); + let holder_bob: AccountId = AccountKeyring::Bob.into(); + let receiver_charlie: AccountId = AccountKeyring::Charlie.into(); + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(issuer_alice.clone(), 10_011), (holder_bob.clone(), 10_011)], + }.assimilate_storage(&mut t).unwrap(); + + let doughnut = PlugDoughnut::::new( + make_doughnut( + issuer_alice.clone(), + holder_bob.clone(), + None, + None, + false, + ) + ); + + let mut t = sp_io::TestExternalities::new(t); + t.execute_with(|| { + let xt = CheckedExtrinsic { + signed: Some(( + holder_bob.clone(), + signed_extra(0, 0, Some(doughnut)), + )), + function: Call::Balances(BalancesCall::transfer(receiver_charlie.clone().into(), 69)), + }; + let uxt = sign_extrinsic(xt); + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + let r = Executive::apply_extrinsic(uxt); + assert_eq!(r, Ok(Err(DispatchError::Other("dispatch unverified")))); + }); +} diff --git a/frame/finality-tracker/Cargo.toml b/frame/finality-tracker/Cargo.toml index e848c02a42..86a451865a 100644 --- a/frame/finality-tracker/Cargo.toml +++ b/frame/finality-tracker/Cargo.toml @@ -30,7 +30,6 @@ std = [ "sp-std/std", "frame-support/std", "sp-runtime/std", - "frame-system/std", "sp-finality-tracker/std", "sp-inherents/std", ] diff --git a/frame/finality-tracker/src/lib.rs b/frame/finality-tracker/src/lib.rs index 08056a34ab..25674e17ed 100644 --- a/frame/finality-tracker/src/lib.rs +++ b/frame/finality-tracker/src/lib.rs @@ -66,7 +66,7 @@ decl_error! { } decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, system = frame_system { type Error = Error; /// The number of recent samples to keep from this chain. Default is 101. const WindowSize: T::BlockNumber = T::WindowSize::get(); @@ -261,6 +261,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/generic-asset/src/impls.rs b/frame/generic-asset/src/impls.rs new file mode 100644 index 0000000000..d6ac1f20b8 --- /dev/null +++ b/frame/generic-asset/src/impls.rs @@ -0,0 +1,569 @@ +// Copyright 2019 Plug New Zealand Ltd. +// This file is part of Plug. + +// Plug 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. + +// Plug 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 Plug. If not, see . + +//! Extra trait implementations for the `GenericAsset` module + +use crate::{Error, Module, NegativeImbalance, PositiveImbalance, SpendingAssetIdAuthority, Trait}; +use sp_std::result; +use sp_runtime::{traits::CheckedSub, DispatchError, DispatchResult}; +use frame_support::{ + additional_traits::{AssetIdAuthority, MultiCurrencyAccounting}, + traits::{ExistenceRequirement, Imbalance, SignedImbalance, WithdrawReasons}, +}; + +impl MultiCurrencyAccounting for Module { + type AccountId = T::AccountId; + type CurrencyId = T::AssetId; + type Balance = T::Balance; + type DefaultCurrencyId = SpendingAssetIdAuthority; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; + + fn total_balance(who: &T::AccountId, currency: Option) -> Self::Balance { + >::total_balance(¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()), who) + } + + fn free_balance(who: &T::AccountId, currency: Option) -> Self::Balance { + >::free_balance(¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()), who) + } + + fn deposit_creating( + who: &T::AccountId, + currency: Option, + value: Self::Balance, + ) -> Self::PositiveImbalance { + let asset_id = ¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()); + let imbalance= Self::make_free_balance_be(who, currency, >::free_balance(asset_id, who) + value); + if let SignedImbalance::Positive(p) = imbalance { + p + } else { + // Impossible, but be defensive. + Self::PositiveImbalance::zero() + } + } + + fn deposit_into_existing( + who: &T::AccountId, + currency: Option, + value: Self::Balance, + ) -> result::Result { + // No existential deposit rule and creation fee in GA. `deposit_into_existing` is same with `deposit_creating`. + Ok(Self::deposit_creating(who, currency, value)) + } + + fn ensure_can_withdraw( + who: &T::AccountId, + currency: Option, + amount: Self::Balance, + reasons: WithdrawReasons, + new_balance: Self::Balance, + ) -> DispatchResult { + >::ensure_can_withdraw( + ¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()), + who, + amount, + reasons, + new_balance, + ) + } + + fn make_free_balance_be( + who: &T::AccountId, + currency: Option, + balance: Self::Balance, + ) -> SignedImbalance { + let asset_id = ¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()); + let original = >::free_balance(asset_id, who); + let imbalance = if original <= balance { + SignedImbalance::Positive(Self::PositiveImbalance::new(balance - original, *asset_id)) + } else { + SignedImbalance::Negative(Self::NegativeImbalance::new(original - balance, *asset_id)) + }; + >::set_free_balance(&asset_id, who, balance); + imbalance + } + + fn transfer( + transactor: &T::AccountId, + dest: &T::AccountId, + currency: Option, + value: Self::Balance, + _ex: ExistenceRequirement, // no existential deposit policy for generic asset + ) -> DispatchResult { + >::make_transfer( + ¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()), + transactor, + dest, + value, + ) + } + + fn withdraw( + who: &T::AccountId, + currency: Option, + value: Self::Balance, + reasons: WithdrawReasons, + _ex: ExistenceRequirement, // no existential deposit policy for generic asset + ) -> result::Result { + let asset_id = ¤cy.unwrap_or_else(|| Self::DefaultCurrencyId::asset_id()); + let new_balance = >::free_balance(asset_id, who) + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + >::ensure_can_withdraw(asset_id, who, value, reasons, new_balance)?; + >::set_free_balance(asset_id, who, new_balance); + + Ok(Self::NegativeImbalance::new(value, *asset_id)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ExtBuilder, GenericAsset, Test}; + use sp_runtime::traits::Zero; + use frame_support::assert_noop; + + #[test] + fn multi_accounting_minimum_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + ::minimum_balance(), + Zero::zero() + ); + }); + } + + #[test] + fn multi_accounting_total_balance() { + let (alice, asset_id, amount) = (&1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, *alice, amount)) + .build() + .execute_with(|| { + assert_eq!( + ::total_balance(alice, Some(asset_id)), + amount + ); + + GenericAsset::reserve(&asset_id, alice, amount / 2).ok(); + // total balance should include reserved balance + assert_eq!( + ::total_balance(alice, Some(asset_id)), + amount + ); + }); + } + + #[test] + fn multi_accounting_free_balance() { + let (alice, asset_id, amount) = (&1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, *alice, amount)) + .build() + .execute_with(|| { + assert_eq!( + ::free_balance(alice, Some(asset_id)), + amount + ); + + GenericAsset::reserve(&asset_id, alice, amount / 2).ok(); + // free balance should not include reserved balance + assert_eq!( + ::free_balance(alice, Some(asset_id)), + amount / 2 + ); + }); + } + + #[test] + fn multi_accounting_deposit_creating() { + let (alice, asset_id, amount) = (&1, 16000, 100); + ExtBuilder::default().build().execute_with(|| { + let imbalance = ::deposit_creating(alice, Some(asset_id), amount); + // Check a positive imbalance of `amount` was created + assert_eq!(imbalance.peek(), amount); + // check free balance of asset has increased with `make_free_balance_be + assert_eq!(GenericAsset::free_balance(&asset_id, &alice), amount); + // explitically drop `imbalance` so issuance is managed + drop(imbalance); + // check issuance of asset has increased with `make_free_balance_be` + assert_eq!(GenericAsset::total_issuance(&asset_id), amount); + }); + } + + #[test] + fn multi_accounting_deposit_into_existing() { + let (alice, asset_id, amount) = (&1, 16000, 100); + ExtBuilder::default().build().execute_with(|| { + let result = + ::deposit_into_existing(alice, Some(asset_id), amount); + // Check a positive imbalance of `amount` was created + assert_eq!(result.unwrap().peek(), amount); + // check free balance of asset has increased with `make_free_balance_be + assert_eq!(GenericAsset::free_balance(&asset_id, &alice), amount); + // check issuance of asset has increased with `make_free_balance_be` + assert_eq!(GenericAsset::total_issuance(&asset_id), amount); + }); + } + + #[test] + fn multi_accounting_ensure_can_withdraw() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + assert_eq!( + ::ensure_can_withdraw( + &alice, + Some(asset_id), + amount / 2, + WithdrawReasons::none(), + amount / 2, + ), + Ok(()) + ); + + // check free balance has not decreased + assert_eq!(GenericAsset::free_balance(&asset_id, &alice), amount); + // check issuance has not decreased + assert_eq!(GenericAsset::total_issuance(&asset_id), amount); + }); + } + + #[test] + fn multi_accounting_make_free_balance_be() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default().build().execute_with(|| { + // Issuance should be `0` initially + assert_eq!(GenericAsset::total_issuance(&asset_id), 0); + + let result = + ::make_free_balance_be(&alice, Some(asset_id), amount); + // Check a positive imbalance of `amount` was created + if let SignedImbalance::Positive(imb) = result { + assert_eq!(imb.peek(), amount); + } else { + assert!(false); + } + // check free balance of asset has increased with `make_free_balance_be + assert_eq!(GenericAsset::free_balance(&asset_id, &alice), amount); + // check issuance of asset has increased with `make_free_balance_be` + assert_eq!(GenericAsset::total_issuance(&asset_id), amount); + }); + } + + #[test] + fn multi_accounting_transfer() { + let (alice, dest_id, asset_id, amount) = (1, 2, 16000, 100); + + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + assert_eq!( + ::transfer( + &alice, + &dest_id, + Some(asset_id), + amount, + ExistenceRequirement::KeepAlive + ), + Ok(()) + ); + assert_eq!(GenericAsset::free_balance(&asset_id, &dest_id), amount); + }); + } + + #[test] + fn multi_accounting_withdraw() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + assert_eq!(GenericAsset::total_issuance(&asset_id), amount); + let result = ::withdraw( + &alice, + Some(asset_id), + amount / 2, + WithdrawReasons::none(), + ExistenceRequirement::KeepAlive, + ); + assert_eq!(result.unwrap().peek(), amount / 2); + + // check free balance of asset has decreased for the account + assert_eq!(GenericAsset::free_balance(&asset_id, &alice), amount / 2); + // check global issuance has decreased for the asset + assert_eq!(GenericAsset::total_issuance(&asset_id), amount / 2); + }); + } + + #[test] + fn it_uses_default_currency_when_unspecified() { + // Run through all the `MultiAccounting` functions checking that the default currency is + // used when the Asset ID is left unspecified (`None`) + let (alice, bob, amount) = (&1, &2, 100); + ExtBuilder::default() + .free_balance((16001, *alice, amount)) // `160001` is the spending asset id from genesis config + .build() + .execute_with(|| { + assert_eq!( + ::total_balance(alice, None), + amount + ); + + assert_eq!( + ::free_balance(alice, None), + amount + ); + + // Mint `amount` of default currency into `alice`s account + let _ = ::deposit_creating(alice, None, amount); + // Check balance updated + assert_eq!( + ::total_balance(alice, None), + amount + amount + ); + assert_eq!(GenericAsset::total_issuance(16001), amount + amount); + + // Make free balance be equal to `amount` again + let _ = ::make_free_balance_be(alice, None, amount); + assert_eq!( + ::free_balance(alice, None), + amount + ); + assert_eq!(GenericAsset::total_issuance(16001), amount); + + // Mint `amount` of `asset_id` into `alice`s account. Similar to `deposit_creating` above + let _ = ::deposit_into_existing(alice, None, amount); + // Check balance updated + assert_eq!( + ::total_balance(alice, None), + amount + amount + ); + assert_eq!(GenericAsset::total_issuance(16001), amount + amount); + + // transfer + let _ = ::transfer( + alice, + bob, + None, + amount, + ExistenceRequirement::KeepAlive, + ); + assert_eq!( + ::free_balance(alice, None), + amount + ); + assert_eq!( + ::free_balance(bob, None), + amount + ); + assert_eq!(GenericAsset::total_issuance(16001), amount + amount); + + // ensure can withdraw + assert!(::ensure_can_withdraw( + alice, + None, + amount, + WithdrawReasons::none(), + amount, + ) + .is_ok()); + + // withdraw + let _ = ::withdraw( + alice, + None, + amount / 2, + WithdrawReasons::none(), + ExistenceRequirement::KeepAlive, + ); + assert_eq!( + ::free_balance(alice, None), + amount / 2 + ); + }); + } + #[test] + fn multi_accounting_transfer_more_than_free_balance_should_fail() { + let (alice, dest_id, asset_id, amount) = (1, 2, 16000, 100); + + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + assert_noop!( + ::transfer( + &alice, + &dest_id, + Some(asset_id), + amount * 2, + ExistenceRequirement::KeepAlive + ), + Error::::InsufficientBalance, + ); + }); + } + + #[test] + fn multi_accounting_transfer_locked_funds_should_fail() { + let (alice, dest_id, asset_id, amount) = (1, 2, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + // Lock alice's funds + GenericAsset::set_lock(1u64.to_be_bytes(), &alice, amount, WithdrawReasons::all()); + + assert_noop!( + ::transfer( + &alice, + &dest_id, + Some(asset_id), + amount, + ExistenceRequirement::KeepAlive + ), + Error::::LiquidityRestrictions, + ); + }); + } + + + #[test] + fn multi_accounting_transfer_reserved_funds_should_fail() { + let (alice, dest_id, asset_id, amount) = (1, 2, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + GenericAsset::reserve(&asset_id, &alice, amount).ok(); + assert_noop!( + ::transfer( + &alice, + &dest_id, + Some(asset_id), + amount, + ExistenceRequirement::KeepAlive + ), + Error::::InsufficientBalance, + ); + }); + } + + #[test] + fn multi_accounting_withdraw_more_than_free_balance_should_fail() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + assert_noop!( + ::withdraw( + &alice, + Some(asset_id), + amount * 2, + WithdrawReasons::none(), + ExistenceRequirement::KeepAlive + ), + Error::::InsufficientBalance, + ); + }); + } + + #[test] + fn multi_accounting_withdraw_locked_funds_should_fail() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + // Lock alice's funds + GenericAsset::set_lock(1u64.to_be_bytes(), &alice, amount, WithdrawReasons::all()); + + assert_noop!( + ::withdraw( + &alice, + Some(asset_id), + amount, + WithdrawReasons::all(), + ExistenceRequirement::KeepAlive + ), + Error::::LiquidityRestrictions, + ); + }); + } + + #[test] + fn multi_accounting_withdraw_reserved_funds_should_fail() { + let (alice, asset_id, amount) = (1, 16000, 100); + ExtBuilder::default() + .free_balance((asset_id, alice, amount)) + .build() + .execute_with(|| { + // Reserve alice's funds + GenericAsset::reserve(&asset_id, &alice, amount).ok(); + + assert_noop!( + ::withdraw( + &alice, + Some(asset_id), + amount, + WithdrawReasons::all(), + ExistenceRequirement::KeepAlive + ), + Error::::InsufficientBalance, + ); + }); + } + + #[test] + fn multi_accounting_make_free_balance_edge_cases() { + let (alice, asset_id) = (&1, 16000); + ExtBuilder::default() + .build() + .execute_with(|| { + let max_value = u64::max_value(); + let min_value = Zero::zero(); + + let _ = ::make_free_balance_be( + alice, + Some(asset_id), + max_value, + ); + // Check balance updated + assert_eq!(GenericAsset::total_issuance(asset_id), max_value); + assert_eq!( + ::free_balance(alice, Some(asset_id)), + max_value + ); + + let _ = ::make_free_balance_be( + alice, + Some(asset_id), + min_value, + ); + // Check balance updated + assert_eq!(GenericAsset::total_issuance(asset_id), min_value); + assert_eq!( + ::free_balance(alice, Some(asset_id)), + min_value + ); + }) + } +} diff --git a/frame/generic-asset/src/lib.rs b/frame/generic-asset/src/lib.rs index f1713dd586..62279d9d9d 100644 --- a/frame/generic-asset/src/lib.rs +++ b/frame/generic-asset/src/lib.rs @@ -75,7 +75,8 @@ //! //! ### Dispatchable Functions //! -//! - `create`: Create a new kind of asset. +//! - `create`: Create a new kind of asset and nominates the owner of this asset. The origin of this call must +//! be root. //! - `transfer`: Transfer some liquid free balance to another account. //! - `update_permission`: Updates permission for a given `asset_id` and an account. The origin of this call //! must have update permissions. @@ -168,10 +169,12 @@ use frame_support::{ Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, ReservableCurrency, SignedImbalance, WithdrawReason, WithdrawReasons, TryDrop, BalanceStatus, }, + additional_traits::{AssetIdAuthority, DummyDispatchVerifier, InherentAssetIdProvider}, Parameter, StorageMap, }; use frame_system::{self as system, ensure_signed, ensure_root}; +mod impls; mod mock; mod tests; @@ -278,7 +281,7 @@ impl Encode for PermissionVersions { PermissionVersions::V1(payload) => { dest.push(&PermissionVersionNumber::V1); dest.push(payload); - }, + } } } } @@ -288,11 +291,9 @@ impl codec::EncodeLike for PermissionVersions {} impl Decode for PermissionVersions { fn decode(input: &mut I) -> core::result::Result { let version = PermissionVersionNumber::decode(input)?; - Ok( - match version { - PermissionVersionNumber::V1 => PermissionVersions::V1(Decode::decode(input)?) - } - ) + Ok(match version { + PermissionVersionNumber::V1 => PermissionVersions::V1(Decode::decode(input)?), + }) } } @@ -359,10 +360,15 @@ decl_module! { fn deposit_event() = default; - /// Create a new kind of asset. - fn create(origin, options: AssetOptions) -> DispatchResult { - let origin = ensure_signed(origin)?; - Self::create_asset(None, Some(origin), options) + /// Create a new kind of asset and nominates the owner of this asset. The + /// origin of this call must be root. + fn create( + origin, + owner: T::AccountId, + options: AssetOptions + ) -> DispatchResult { + ensure_root(origin)?; + Self::create_asset(None, Some(owner), options) } /// Transfer some liquid free balance to another account. @@ -893,72 +899,49 @@ impl Module { } } -pub trait AssetIdProvider { - type AssetId; - fn asset_id() -> Self::AssetId; -} - // wrapping these imbalances in a private module is necessary to ensure absolute privacy // of the inner member. mod imbalances { use super::{ - result, AssetIdProvider, Imbalance, Saturating, StorageMap, Subtrait, Zero, TryDrop + result, Imbalance, InherentAssetIdProvider, Saturating, StorageMap, Subtrait, Trait, Zero, TryDrop, }; use sp_std::mem; /// Opaque, move-only struct with private fields that serves as a token denoting that /// funds have been created without any equal and opposite accounting. #[must_use] - pub struct PositiveImbalance>( - T::Balance, - sp_std::marker::PhantomData, - ); - impl PositiveImbalance - where - T: Subtrait, - U: AssetIdProvider, - { - pub fn new(amount: T::Balance) -> Self { - PositiveImbalance(amount, Default::default()) + #[cfg_attr(test, derive(PartialEq, Debug))] + pub struct PositiveImbalance(T::Balance, T::AssetId); + impl PositiveImbalance { + pub fn new(amount: T::Balance, asset_id: T::AssetId) -> Self { + PositiveImbalance(amount, asset_id) } } /// Opaque, move-only struct with private fields that serves as a token denoting that /// funds have been destroyed without any equal and opposite accounting. #[must_use] - pub struct NegativeImbalance>( - T::Balance, - sp_std::marker::PhantomData, - ); - impl NegativeImbalance - where - T: Subtrait, - U: AssetIdProvider, - { - pub fn new(amount: T::Balance) -> Self { - NegativeImbalance(amount, Default::default()) + #[cfg_attr(test, derive(PartialEq, Debug))] + pub struct NegativeImbalance(T::Balance, T::AssetId); + impl NegativeImbalance { + pub fn new(amount: T::Balance, asset_id: T::AssetId) -> Self { + NegativeImbalance(amount, asset_id) } } - impl TryDrop for PositiveImbalance - where - T: Subtrait, - U: AssetIdProvider, - { + impl TryDrop for PositiveImbalance { fn try_drop(self) -> result::Result<(), Self> { self.drop_zero() } } - impl Imbalance for PositiveImbalance - where - T: Subtrait, - U: AssetIdProvider, - { - type Opposite = NegativeImbalance; + impl Imbalance for PositiveImbalance { + type Opposite = NegativeImbalance; fn zero() -> Self { - Self::new(Zero::zero()) + // We lose asset /currency ID information here + // It should be fine unless we are trying to reconstruct that information from a zeroed Imbalance + Self::new(Zero::zero(), Zero::zero()) } fn drop_zero(self) -> result::Result<(), Self> { if self.0.is_zero() { @@ -970,9 +953,10 @@ mod imbalances { fn split(self, amount: T::Balance) -> (Self, Self) { let first = self.0.min(amount); let second = self.0 - first; + let asset_id = self.1; mem::forget(self); - (Self::new(first), Self::new(second)) + (Self::new(first, asset_id), Self::new(second, asset_id)) } fn merge(mut self, other: Self) -> Self { self.0 = self.0.saturating_add(other.0); @@ -985,13 +969,13 @@ mod imbalances { mem::forget(other); } fn offset(self, other: Self::Opposite) -> result::Result { - let (a, b) = (self.0, other.0); + let (a, b, asset_id) = (self.0, other.0, self.1); mem::forget((self, other)); if a >= b { - Ok(Self::new(a - b)) + Ok(Self::new(a - b, asset_id)) } else { - Err(NegativeImbalance::new(b - a)) + Err(NegativeImbalance::new(b - a, asset_id)) } } fn peek(&self) -> T::Balance { @@ -999,25 +983,17 @@ mod imbalances { } } - impl TryDrop for NegativeImbalance - where - T: Subtrait, - U: AssetIdProvider, - { + impl TryDrop for NegativeImbalance { fn try_drop(self) -> result::Result<(), Self> { self.drop_zero() } } - impl Imbalance for NegativeImbalance - where - T: Subtrait, - U: AssetIdProvider, - { - type Opposite = PositiveImbalance; + impl Imbalance for NegativeImbalance { + type Opposite = PositiveImbalance; fn zero() -> Self { - Self::new(Zero::zero()) + Self::new(Zero::zero(), Zero::zero()) } fn drop_zero(self) -> result::Result<(), Self> { if self.0.is_zero() { @@ -1029,9 +1005,10 @@ mod imbalances { fn split(self, amount: T::Balance) -> (Self, Self) { let first = self.0.min(amount); let second = self.0 - first; + let asset_id = self.1; mem::forget(self); - (Self::new(first), Self::new(second)) + (Self::new(first, asset_id), Self::new(second, asset_id)) } fn merge(mut self, other: Self) -> Self { self.0 = self.0.saturating_add(other.0); @@ -1044,13 +1021,13 @@ mod imbalances { mem::forget(other); } fn offset(self, other: Self::Opposite) -> result::Result { - let (a, b) = (self.0, other.0); + let (a, b, asset_id) = (self.0, other.0, self.1); mem::forget((self, other)); if a >= b { - Ok(Self::new(a - b)) + Ok(Self::new(a - b, asset_id)) } else { - Err(PositiveImbalance::new(b - a)) + Err(PositiveImbalance::new(b - a, asset_id)) } } fn peek(&self) -> T::Balance { @@ -1058,25 +1035,31 @@ mod imbalances { } } - impl Drop for PositiveImbalance - where - T: Subtrait, - U: AssetIdProvider, - { + impl Drop for PositiveImbalance { /// Basic drop handler will just square up the total issuance. fn drop(&mut self) { - >>::mutate(&U::asset_id(), |v| *v = v.saturating_add(self.0)); + >>::mutate(&self.1, |v| *v = v.saturating_add(self.0)); } } - impl Drop for NegativeImbalance - where - T: Subtrait, - U: AssetIdProvider, - { + impl Drop for NegativeImbalance { /// Basic drop handler will just square up the total issuance. fn drop(&mut self) { - >>::mutate(&U::asset_id(), |v| *v = v.saturating_sub(self.0)); + >>::mutate(&self.1, |v| *v = v.saturating_sub(self.0)); + } + } + + impl InherentAssetIdProvider for PositiveImbalance { + type AssetId = T::AssetId; + fn asset_id(&self) -> Self::AssetId { + self.1 + } + } + + impl InherentAssetIdProvider for NegativeImbalance { + type AssetId = T::AssetId; + fn asset_id(&self) -> Self::AssetId { + self.1 } } } @@ -1122,6 +1105,8 @@ impl frame_system::Trait for ElevatedTrait { type AvailableBlockRatio = T::AvailableBlockRatio; type Version = T::Version; type ModuleToIndex = (); + type Doughnut = T::Doughnut; + type DelegatedDispatchVerifier = DummyDispatchVerifier; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); @@ -1138,11 +1123,11 @@ pub struct AssetCurrency(sp_std::marker::PhantomData, sp_std::marker::P impl Currency for AssetCurrency where T: Trait, - U: AssetIdProvider, + U: AssetIdAuthority, { type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; fn total_balance(who: &T::AccountId) -> Self::Balance { Self::free_balance(&who) + Self::reserved_balance(&who) @@ -1190,7 +1175,7 @@ where .ok_or(Error::::InsufficientBalance)?; Self::ensure_can_withdraw(who, value, reasons, new_balance)?; >::set_free_balance(&U::asset_id(), who, new_balance); - Ok(NegativeImbalance::new(value)) + Ok(NegativeImbalance::new(value, U::asset_id())) } fn deposit_into_existing( @@ -1217,9 +1202,9 @@ where ) -> SignedImbalance { let original = >::free_balance(&U::asset_id(), who); let imbalance = if original <= balance { - SignedImbalance::Positive(PositiveImbalance::new(balance - original)) + SignedImbalance::Positive(PositiveImbalance::new(balance - original, U::asset_id())) } else { - SignedImbalance::Negative(NegativeImbalance::new(original - balance)) + SignedImbalance::Negative(NegativeImbalance::new(original - balance, U::asset_id())) }; >::set_free_balance(&U::asset_id(), who, balance); imbalance @@ -1232,46 +1217,51 @@ where fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { let remaining = >::slash(&U::asset_id(), who, value); if let Some(r) = remaining { - (NegativeImbalance::new(value - r), r) + (NegativeImbalance::new(value - r, U::asset_id()), r) } else { - (NegativeImbalance::new(value), Zero::zero()) + (NegativeImbalance::new(value, U::asset_id()), Zero::zero()) } } fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { - >::mutate(&U::asset_id(), |issued| + >::mutate(&U::asset_id(), |issued| { issued.checked_sub(&amount).unwrap_or_else(|| { amount = *issued; Zero::zero() }) - ); - PositiveImbalance::new(amount) + }); + PositiveImbalance::new(amount, U::asset_id()) } fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { - >::mutate(&U::asset_id(), |issued| + >::mutate(&U::asset_id(), |issued| { *issued = issued.checked_add(&amount).unwrap_or_else(|| { amount = Self::Balance::max_value() - *issued; Self::Balance::max_value() }) - ); - NegativeImbalance::new(amount) + }); + NegativeImbalance::new(amount, U::asset_id()) } } impl ReservableCurrency for AssetCurrency where T: Trait, - U: AssetIdProvider, + U: AssetIdAuthority, { fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { Self::free_balance(who) .checked_sub(&value) - .map_or(false, |new_balance| + .map_or(false, |new_balance| { >::ensure_can_withdraw( - &U::asset_id(), who, value, WithdrawReason::Reserve.into(), new_balance - ).is_ok() - ) + &U::asset_id(), + who, + value, + WithdrawReason::Reserve.into(), + new_balance, + ) + .is_ok() + }) } fn reserved_balance(who: &T::AccountId) -> Self::Balance { @@ -1291,7 +1281,7 @@ where let slash = cmp::min(b, value); >::set_reserved_balance(&U::asset_id(), who, b - slash); - (NegativeImbalance::new(slash), value - slash) + (NegativeImbalance::new(slash, U::asset_id()), value - slash) } fn repatriate_reserved( @@ -1304,25 +1294,25 @@ where } } -pub struct StakingAssetIdProvider(sp_std::marker::PhantomData); +pub struct StakingAssetIdAuthority(sp_std::marker::PhantomData); -impl AssetIdProvider for StakingAssetIdProvider { +impl AssetIdAuthority for StakingAssetIdAuthority { type AssetId = T::AssetId; fn asset_id() -> Self::AssetId { >::staking_asset_id() } } -pub struct SpendingAssetIdProvider(sp_std::marker::PhantomData); +pub struct SpendingAssetIdAuthority(sp_std::marker::PhantomData); -impl AssetIdProvider for SpendingAssetIdProvider { +impl AssetIdAuthority for SpendingAssetIdAuthority { type AssetId = T::AssetId; fn asset_id() -> Self::AssetId { >::spending_asset_id() } } -impl LockableCurrency for AssetCurrency> +impl LockableCurrency for AssetCurrency> where T: Trait, T::Balance: MaybeSerializeDeserialize + Debug, @@ -1352,5 +1342,5 @@ where } } -pub type StakingAssetCurrency = AssetCurrency>; -pub type SpendingAssetCurrency = AssetCurrency>; +pub type StakingAssetCurrency = AssetCurrency>; +pub type SpendingAssetCurrency = AssetCurrency>; diff --git a/frame/generic-asset/src/mock.rs b/frame/generic-asset/src/mock.rs index 8db140d90c..c782ff48be 100644 --- a/frame/generic-asset/src/mock.rs +++ b/frame/generic-asset/src/mock.rs @@ -37,7 +37,7 @@ impl_outer_origin! { // For testing the pallet, we construct most of a mock runtime. This means // first constructing a configuration type (`Test`) which `impl`s each of the // configuration traits of pallets we want to use. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq, Debug)] pub struct Test; parameter_types! { pub const BlockHashCount: u64 = 250; @@ -62,6 +62,8 @@ impl frame_system::Trait for Test { type BlockHashCount = BlockHashCount; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/generic-asset/src/tests.rs b/frame/generic-asset/src/tests.rs index d5c0a877df..5992783a5c 100644 --- a/frame/generic-asset/src/tests.rs +++ b/frame/generic-asset/src/tests.rs @@ -38,7 +38,8 @@ fn issuing_asset_units_to_issuer_should_work() { let expected_balance = balance; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: balance, permissions: default_permission @@ -59,7 +60,8 @@ fn issuing_with_next_asset_id_overflow_should_not_work() { }; assert_noop!( GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 1, permissions: default_permission @@ -81,8 +83,9 @@ fn querying_total_supply_should_work() { mint: Owner::Address(1), burn: Owner::Address(1), }; - assert_ok!(GenericAsset::create( - Origin::signed(1), + assert_ok!(GenericAsset::create( + Origin::ROOT, + 1, AssetOptions { initial_issuance: 100, permissions: default_permission @@ -127,7 +130,8 @@ fn transferring_amount_should_work() { burn: Owner::Address(1), }; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: free_balance, permissions: default_permission @@ -165,7 +169,8 @@ fn transferring_amount_should_fail_when_transferring_more_than_free_balance() { burn: Owner::Address(1), }; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 100, permissions: default_permission @@ -189,7 +194,8 @@ fn transferring_less_than_one_unit_should_not_work() { burn: Owner::Address(1), }; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 100, permissions: default_permission @@ -224,7 +230,8 @@ fn self_transfer_should_fail() { burn: Owner::Address(1), }; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: balance, permissions: default_permission @@ -247,7 +254,8 @@ fn transferring_more_units_than_total_supply_should_not_work() { burn: Owner::Address(1), }; assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 100, permissions: default_permission @@ -303,7 +311,8 @@ fn total_balance_should_be_equal_to_account_balance() { }; ExtBuilder::default().free_balance((16000, 1, 100000)).build().execute_with(|| { assert_ok!(GenericAsset::create( - Origin::signed(1), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 100, permissions: default_permission @@ -655,7 +664,8 @@ fn mint_should_increase_asset() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -718,7 +728,8 @@ fn burn_should_burn_an_asset() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -757,7 +768,8 @@ fn check_permission_should_return_correct_permission() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -791,7 +803,8 @@ fn check_permission_should_return_false_for_no_permission() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -831,7 +844,8 @@ fn update_permission_should_change_permission() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -874,7 +888,8 @@ fn update_permission_should_throw_error_when_lack_of_permissions() { }; assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: initial_issuance, permissions: default_permission @@ -1114,7 +1129,8 @@ fn update_permission_should_raise_event() { .build() .execute_with(|| { assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 0, permissions: permissions.clone(), @@ -1158,7 +1174,8 @@ fn mint_should_raise_event() { .build() .execute_with(|| { assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: 0, permissions: permissions.clone(), @@ -1196,7 +1213,8 @@ fn burn_should_raise_event() { .build() .execute_with(|| { assert_ok!(GenericAsset::create( - Origin::signed(origin), + Origin::ROOT, + 1, AssetOptions { initial_issuance: amount, permissions: permissions.clone(), diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 8b94becd5a..4bb49ded11 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -65,6 +65,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 68b7905961..f4fbf48766 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -934,6 +934,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -968,8 +970,8 @@ mod tests { type SubAccountDeposit = SubAccountDeposit; type MaxSubAccounts = MaxSubAccounts; type MaxAdditionalFields = MaxAdditionalFields; - type RegistrarOrigin = EnsureSignedBy; - type ForceOrigin = EnsureSignedBy; + type RegistrarOrigin = EnsureSignedBy; + type ForceOrigin = EnsureSignedBy; } type System = frame_system::Module; type Balances = pallet_balances::Module; diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index a703b24629..062704ce8a 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -23,7 +23,7 @@ use std::cell::RefCell; use crate::{Module, Trait}; use sp_runtime::Perbill; use sp_staking::{SessionIndex, offence::ReportOffence}; -use sp_runtime::testing::{Header, UintAuthorityId, TestXt}; +use sp_runtime::testing::{Header, UintAuthorityId, DoughnutTestXt}; use sp_runtime::traits::{IdentityLookup, BlakeTwo256, ConvertInto}; use sp_core::H256; use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types, weights::Weight}; @@ -65,7 +65,7 @@ impl pallet_session::historical::SessionManager for TestSessionManager } /// An extrinsic type used for tests. -pub type Extrinsic = TestXt; +pub type Extrinsic = DoughnutTestXt; type SubmitTransaction = frame_system::offchain::TransactionSubmitter<(), Call, Extrinsic>; type IdentificationTuple = (u64, u64); type Offence = crate::UnresponsivenessOffence; @@ -115,6 +115,8 @@ impl frame_system::Trait for Runtime { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/indices/src/mock.rs b/frame/indices/src/mock.rs index 355b3cc792..c05f25468b 100644 --- a/frame/indices/src/mock.rs +++ b/frame/indices/src/mock.rs @@ -65,6 +65,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/indices/src/tests.rs b/frame/indices/src/tests.rs index 9e434cfbe2..e1659f96d1 100644 --- a/frame/indices/src/tests.rs +++ b/frame/indices/src/tests.rs @@ -26,9 +26,9 @@ use pallet_balances::Error as BalancesError; #[test] fn claiming_should_work() { new_test_ext().execute_with(|| { - assert_noop!(Indices::claim(Some(0).into(), 0), BalancesError::::InsufficientBalance); - assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_noop!(Indices::claim(Some(2).into(), 0), Error::::InUse); + assert_noop!(Indices::claim((Some(0), None).into(), 0), BalancesError::::InsufficientBalance); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); + assert_noop!(Indices::claim((Some(2), None).into(), 0), Error::::InUse); assert_eq!(Balances::reserved_balance(1), 1); }); } @@ -36,22 +36,22 @@ fn claiming_should_work() { #[test] fn freeing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_ok!(Indices::claim(Some(2).into(), 1)); - assert_noop!(Indices::free(Some(0).into(), 0), Error::::NotOwner); - assert_noop!(Indices::free(Some(1).into(), 1), Error::::NotOwner); - assert_noop!(Indices::free(Some(1).into(), 2), Error::::NotAssigned); - assert_ok!(Indices::free(Some(1).into(), 0)); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); + assert_ok!(Indices::claim((Some(2), None).into(), 1)); + assert_noop!(Indices::free((Some(0), None).into(), 0), Error::::NotOwner); + assert_noop!(Indices::free((Some(1), None).into(), 1), Error::::NotOwner); + assert_noop!(Indices::free((Some(1), None).into(), 2), Error::::NotAssigned); + assert_ok!(Indices::free((Some(1), None).into(), 0)); assert_eq!(Balances::reserved_balance(1), 0); - assert_noop!(Indices::free(Some(1).into(), 0), Error::::NotAssigned); + assert_noop!(Indices::free((Some(1), None).into(), 0), Error::::NotAssigned); }); } #[test] fn indexing_lookup_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_ok!(Indices::claim(Some(2).into(), 1)); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); + assert_ok!(Indices::claim((Some(2), None).into(), 1)); assert_eq!(Indices::lookup_index(0), Some(1)); assert_eq!(Indices::lookup_index(1), Some(2)); assert_eq!(Indices::lookup_index(2), None); @@ -61,9 +61,9 @@ fn indexing_lookup_should_work() { #[test] fn reclaim_index_on_accounts_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_ok!(Indices::free(Some(1).into(), 0)); - assert_ok!(Indices::claim(Some(2).into(), 0)); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); + assert_ok!(Indices::free((Some(1), None).into(), 0)); + assert_ok!(Indices::claim((Some(2), None).into(), 0)); assert_eq!(Indices::lookup_index(0), Some(2)); assert_eq!(Balances::reserved_balance(2), 1); }); @@ -72,10 +72,10 @@ fn reclaim_index_on_accounts_should_work() { #[test] fn transfer_index_on_accounts_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_noop!(Indices::transfer(Some(1).into(), 2, 1), Error::::NotAssigned); - assert_noop!(Indices::transfer(Some(2).into(), 3, 0), Error::::NotOwner); - assert_ok!(Indices::transfer(Some(1).into(), 3, 0)); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); + assert_noop!(Indices::transfer((Some(1), None).into(), 2, 1), Error::::NotAssigned); + assert_noop!(Indices::transfer((Some(2), None).into(), 3, 0), Error::::NotOwner); + assert_ok!(Indices::transfer((Some(1), None).into(), 3, 0)); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::reserved_balance(3), 1); assert_eq!(Indices::lookup_index(0), Some(3)); @@ -85,7 +85,7 @@ fn transfer_index_on_accounts_should_work() { #[test] fn force_transfer_index_on_preowned_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::claim(Some(1).into(), 0)); + assert_ok!(Indices::claim((Some(1), None).into(), 0)); assert_ok!(Indices::force_transfer(Origin::ROOT, 3, 0)); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::reserved_balance(3), 0); diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index c39055c1bc..a0f7b63883 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -269,6 +269,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); @@ -307,10 +309,10 @@ mod tests { impl Trait for Test { type Event = (); - type AddOrigin = EnsureSignedBy; - type RemoveOrigin = EnsureSignedBy; - type SwapOrigin = EnsureSignedBy; - type ResetOrigin = EnsureSignedBy; + type AddOrigin = EnsureSignedBy; + type RemoveOrigin = EnsureSignedBy; + type SwapOrigin = EnsureSignedBy; + type ResetOrigin = EnsureSignedBy; type MembershipInitialized = TestChangeMembers; type MembershipChanged = TestChangeMembers; } diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index caed6e40ac..ead4d4f36c 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -283,6 +283,8 @@ mod tests { type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; + type Doughnut = (); + type DelegatedDispatchVerifier = (); type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; @@ -312,7 +314,7 @@ mod tests { type Currency = Balances; type ReservationFee = ReservationFee; type Slashed = (); - type ForceOrigin = EnsureSignedBy; + type ForceOrigin = EnsureSignedBy; type MinLength = MinLength; type MaxLength = MaxLength; } diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index a003ad6915..f449c36662 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -89,6 +89,8 @@ impl frame_system::Trait for Runtime { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index 0ded7dd6b0..6466740763 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -40,7 +40,7 @@ //! pub trait Trait: frame_system::Trait {} //! //! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { +//! pub struct Module for enum Call where origin: T::Origin, system = frame_system { //! pub fn random_module_example(origin) -> dispatch::DispatchResult { //! let _random_seed = >::random_seed(); //! Ok(()) @@ -191,6 +191,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index a5b7731c22..6728512aec 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -78,6 +78,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index e3854c7524..d1145e3751 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -65,7 +65,7 @@ //! let who = ensure_signed(origin)?; //! //! let _ = >::submit_candidacy( -//! T::Origin::from(Some(who.clone()).into()) +//! T::Origin::from((Some(who.clone()), None).into()) //! ); //! Ok(()) //! } diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index a28b789137..48d5f46724 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -70,6 +70,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -112,14 +114,14 @@ impl InitializeMembers for TestChangeMembers { impl Trait for Test { type Event = (); - type KickOrigin = EnsureSignedBy; + type KickOrigin = EnsureSignedBy; type MembershipInitialized = TestChangeMembers; type MembershipChanged = TestChangeMembers; type Currency = Balances; type CandidateDeposit = CandidateDeposit; type Period = Period; type Score = u64; - type ScoreOrigin = EnsureSignedBy; + type ScoreOrigin = EnsureSignedBy; } type System = frame_system::Module; diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index df858c8ed5..9782efa4fc 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -176,6 +176,8 @@ impl frame_system::Trait for Test { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 158f139df5..2b1f6861c4 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -18,6 +18,7 @@ use super::*; +use frame_support::additional_traits::DummyDispatchVerifier; use frame_support::{impl_outer_origin, parameter_types, ord_parameter_types}; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures @@ -77,6 +78,8 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = DummyDispatchVerifier; type OnNewAccount = (); type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; @@ -101,8 +104,8 @@ impl Trait for Test { type MembershipChanged = (); type RotationPeriod = RotationPeriod; type MaxLockDuration = MaxLockDuration; - type FounderSetOrigin = EnsureSignedBy; - type SuspensionJudgementOrigin = EnsureSignedBy; + type FounderSetOrigin = EnsureSignedBy; + type SuspensionJudgementOrigin = EnsureSignedBy; type ChallengePeriod = ChallengePeriod; } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 7eb7772054..45115c2ea5 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -27,6 +27,7 @@ pallet-balances = { version = "2.0.0-dev", path = "../balances" } pallet-timestamp = { version = "2.0.0-dev", path = "../timestamp" } pallet-staking-reward-curve = { version = "2.0.0-dev", path = "../staking/reward-curve" } substrate-test-utils = { version = "2.0.0-dev", path = "../../test-utils" } +pallet-generic-asset = { version = "2.0.0-dev", path = "../generic-asset" } [features] migrate = [] diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index ee708dabd3..5d357c3b1d 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -250,12 +250,16 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(test)] +mod multi_token_economy_tests; + mod slashing; pub mod inflation; use sp_std::{prelude::*, result}; use codec::{HasCompact, Encode, Decode}; +use core::any::TypeId; use frame_support::{ decl_module, decl_event, decl_storage, ensure, decl_error, weights::SimpleDispatchInfo, @@ -544,9 +548,13 @@ pub struct UnappliedSlash { } pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type PositiveImbalanceOf = - <::Currency as Currency<::AccountId>>::PositiveImbalance; + <::Currency as Currency<::AccountId>>::Balance; +pub type RewardBalanceOf = + <::RewardCurrency as Currency<::AccountId>>::Balance; +type RewardPositiveImbalanceOf = + <::RewardCurrency as Currency<::AccountId>>::PositiveImbalance; +type RewardNegativeImbalanceOf = + <::RewardCurrency as Currency<::AccountId>>::NegativeImbalance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; type MomentOf = <::Time as Time>::Moment; @@ -594,6 +602,13 @@ pub trait Trait: frame_system::Trait { /// The staking balance. type Currency: LockableCurrency; + /// The reward currency system (total issuance, account balance, etc.) + /// It could be the same as `Self::Currency` or not, dependent on the economic model + type RewardCurrency: Currency; + + /// Conversion shim type for converting staking `Currency` balances into `RewardCurrency` balances + type CurrencyToReward: From> + Into>; + /// Time used for computing era duration. type Time: Time; @@ -605,7 +620,7 @@ pub trait Trait: frame_system::Trait { type CurrencyToVote: Convert, u64> + Convert>; /// Tokens have been minted and are unused for validator-reward. - type RewardRemainder: OnUnbalanced>; + type RewardRemainder: OnUnbalanced>; /// The overarching event type. type Event: From> + Into<::Event>; @@ -614,7 +629,7 @@ pub trait Trait: frame_system::Trait { type Slash: OnUnbalanced>; /// Handler for the unbalanced increment when rewarding a staker. - type Reward: OnUnbalanced>; + type Reward: OnUnbalanced>; /// Number of sessions per era. type SessionsPerEra: Get; @@ -771,7 +786,7 @@ decl_storage! { "Stash does not have enough balance to bond." ); let _ = >::bond( - T::Origin::from(Some(stash.clone()).into()), + T::Origin::from((Some(stash.clone()), None).into()), T::Lookup::unlookup(controller.clone()), balance, RewardDestination::Staked, @@ -779,13 +794,13 @@ decl_storage! { let _ = match status { StakerStatus::Validator => { >::validate( - T::Origin::from(Some(controller.clone()).into()), + T::Origin::from((Some(controller.clone()), None).into()), Default::default(), ) }, StakerStatus::Nominator(votes) => { >::nominate( - T::Origin::from(Some(controller.clone()).into()), + T::Origin::from((Some(controller.clone()), None).into()), votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), ) }, _ => Ok(()) @@ -796,10 +811,10 @@ decl_storage! { } decl_event!( - pub enum Event where Balance = BalanceOf, ::AccountId { + pub enum Event where Balance = BalanceOf, ::AccountId, RewardBalance = RewardBalanceOf { /// All validators have been rewarded by the first balance; the second is the remainder /// from the maximum amount of reward. - Reward(Balance, Balance), + Reward(RewardBalance, RewardBalance), /// One validator (and its nominators) has been slashed by the given amount. Slash(AccountId, Balance), /// An old slashing report from a prior era was discarded because it could @@ -1317,34 +1332,45 @@ impl Module { /// Actually make a payment to a staker. This uses the currency's reward function /// to pay the right payee for the given staker account. - fn make_payout(stash: &T::AccountId, amount: BalanceOf) -> Option> { + fn make_payout(stash: &T::AccountId, amount: BalanceOf) -> Option> { let dest = Self::payee(stash); + let reward_amount = T::CurrencyToReward::from(amount).into(); match dest { RewardDestination::Controller => Self::bonded(stash) .and_then(|controller| - T::Currency::deposit_into_existing(&controller, amount).ok() + T::RewardCurrency::deposit_into_existing(&controller, reward_amount).ok() ), RewardDestination::Stash => - T::Currency::deposit_into_existing(stash, amount).ok(), - RewardDestination::Staked => Self::bonded(stash) - .and_then(|c| Self::ledger(&c).map(|l| (c, l))) - .and_then(|(controller, mut l)| { - l.active += amount; - l.total += amount; - let r = T::Currency::deposit_into_existing(stash, amount).ok(); - Self::update_ledger(&controller, &l); - r - }), + T::RewardCurrency::deposit_into_existing(stash, reward_amount).ok(), + RewardDestination::Staked => { + if TypeId::of::() != TypeId::of::() { + // The staking currency is not the same as the reward currency. + // Pay out the reward currency to the stash account (no change to active stake) + T::RewardCurrency::deposit_into_existing(stash, reward_amount).ok() + } else { + // The staking currency _is_ the reward currency, pay reward to stash account and + // increase the active stake in kind + Self::bonded(stash) + .and_then(|c| Self::ledger(&c).map(|l| (c, l))) + .and_then(|(controller, mut l)| { + l.active += amount; + l.total += amount; + let r = T::RewardCurrency::deposit_into_existing(stash, reward_amount).ok(); + Self::update_ledger(&controller, &l); + r + }) + } + } } } /// Reward a given validator by a specific amount. Add the reward to the validator's, and its /// nominators' balance, pro-rata based on their exposure, after having removed the validator's /// pre-payout cut. - fn reward_validator(stash: &T::AccountId, reward: BalanceOf) -> PositiveImbalanceOf { + fn reward_validator(stash: &T::AccountId, reward: BalanceOf) -> RewardPositiveImbalanceOf { let off_the_table = Self::validators(stash).commission * reward; let reward = reward.saturating_sub(off_the_table); - let mut imbalance = >::zero(); + let mut imbalance = >::zero(); let validator_cut = if reward.is_zero() { Zero::zero() } else { @@ -1413,7 +1439,7 @@ impl Module { era_duration.saturated_into::(), ); - let mut total_imbalance = >::zero(); + let mut total_imbalance = >::zero(); for (v, p) in validators.iter().zip(points.individual.into_iter()) { if p != 0 { @@ -1425,11 +1451,13 @@ impl Module { // assert!(total_imbalance.peek() == total_payout) let total_payout = total_imbalance.peek(); - let rest = max_payout.saturating_sub(total_payout); + // Convert `max_payout` into reward currency + let _max_payout: RewardBalanceOf = T::CurrencyToReward::from(max_payout).into(); + let rest = _max_payout.saturating_sub(total_payout); Self::deposit_event(RawEvent::Reward(total_payout, rest)); T::Reward::on_unbalanced(total_imbalance); - T::RewardRemainder::on_unbalanced(T::Currency::issue(rest)); + T::RewardRemainder::on_unbalanced(T::RewardCurrency::issue(rest)); } // Increment current era. diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 22d6628463..8594e86329 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -49,7 +49,7 @@ impl Convert for CurrencyToVoteHandler { } thread_local! { - static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); + pub(crate) static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); static SLASH_DEFER_DURATION: RefCell = RefCell::new(0); } @@ -138,6 +138,8 @@ impl frame_system::Trait for Test { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -201,6 +203,8 @@ parameter_types! { } impl Trait for Test { type Currency = pallet_balances::Module; + type RewardCurrency = pallet_balances::Module; + type CurrencyToReward = Balance; type Time = pallet_timestamp::Module; type CurrencyToVote = CurrencyToVoteHandler; type RewardRemainder = (); @@ -209,7 +213,7 @@ impl Trait for Test { type Reward = (); type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; - type SlashCancelOrigin = frame_system::EnsureRoot; + type SlashCancelOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; type SessionInterface = Self; type RewardCurve = RewardCurve; diff --git a/frame/staking/src/multi_token_economy_tests.rs b/frame/staking/src/multi_token_economy_tests.rs new file mode 100644 index 0000000000..35ae40c537 --- /dev/null +++ b/frame/staking/src/multi_token_economy_tests.rs @@ -0,0 +1,298 @@ +// Copyright 2019 Plug New Zealand Limited +// +// 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. + +// Test for staking rewards in a multi-token economic model +// i.e. The token at stake is not necessarily the token that is rewarded to validators +// Sadly we need to re-mock everything here just to alter the `RewardCurrency`, +// apart from that this file is simplified copy of `mock.rs` + +use sp_core::H256; +use std::collections::HashSet; +use sp_runtime::{ + curve::PiecewiseLinear, + testing::{Header, UintAuthorityId}, + traits::{IdentityLookup, OnInitialize}, + Perbill, +}; +use sp_staking::SessionIndex; +use frame_support::{impl_outer_origin, parameter_types}; + +use crate::{ + EraIndex, GenesisConfig, Module, Trait, StakingLedger, StakerStatus, RewardDestination, + inflation +}; +use crate::mock::{Author11, CurrencyToVoteHandler, TestSessionHandler, ExistentialDeposit, SlashDeferDuration, SESSION}; + +const REWARD_ASSET_ID: u32 = 101; +const STAKING_ASSET_ID: u32 = 100; + +/// The AccountId alias in this test module. +pub type AccountId = u64; +pub type BlockNumber = u64; +pub type Balance = u64; + +use frame_system as system; +impl_outer_origin!{ + pub enum Origin for Test {} +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = BlockNumber; + type Call = (); + type Hash = H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const TransferFee: Balance = 0; + pub const CreationFee: Balance = 0; +} +impl pallet_balances::Trait for Test { + type Balance = Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +impl pallet_generic_asset::Trait for Test { + type Balance = u64; + type AssetId = u32; + type Event = (); +} +parameter_types! { + pub const Period: BlockNumber = 1; + pub const Offset: BlockNumber = 0; + pub const UncleGenerations: u64 = 0; + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25); +} +impl pallet_session::Trait for Test { + type SessionManager = Staking; + type Keys = UintAuthorityId; + type ShouldEndSession = pallet_session::PeriodicSessions; + type SessionHandler = TestSessionHandler; + type Event = (); + type ValidatorId = AccountId; + type ValidatorIdOf = crate::StashOf; + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; +} + +impl pallet_session::historical::Trait for Test { + type FullIdentification = crate::Exposure; + type FullIdentificationOf = crate::ExposureOf; +} +impl pallet_authorship::Trait for Test { + type FindAuthor = Author11; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = Module; +} +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +pallet_staking_reward_curve::build! { + const I_NPOS: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} +parameter_types! { + pub const SessionsPerEra: SessionIndex = 3; + pub const BondingDuration: EraIndex = 3; + pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; +} +impl Trait for Test { + type Currency = pallet_generic_asset::StakingAssetCurrency; + type RewardCurrency = pallet_generic_asset::SpendingAssetCurrency; + type CurrencyToReward = Balance; + type Time = pallet_timestamp::Module; + type CurrencyToVote = CurrencyToVoteHandler; + type RewardRemainder = (); + type Event = (); + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type SlashDeferDuration = SlashDeferDuration; + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = Self; + type RewardCurve = RewardCurve; +} + +pub struct ExtBuilder { + validator_count: u32, + minimum_validator_count: u32, + num_validators: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + validator_count: 2, + minimum_validator_count: 0, + num_validators: None, + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let num_validators = self.num_validators.unwrap_or(self.validator_count); + let validators = (0..num_validators) + .map(|x| ((x + 1) * 10 + 1) as u64) + .collect::>(); + + let _ = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = pallet_generic_asset::GenesisConfig::{ + endowed_accounts: vec![10, 11], + initial_balance: 1_000_000_000, + staking_asset_id: STAKING_ASSET_ID, + spending_asset_id: REWARD_ASSET_ID, + assets: vec![STAKING_ASSET_ID, REWARD_ASSET_ID], + next_asset_id: 102, + }.assimilate_storage(&mut storage); + + let _ = GenesisConfig::{ + current_era: 0, + stakers: vec![ + // (stash, controller, staked_amount, status) + (11, 10, 500_000, StakerStatus::::Validator), + ], + validator_count: self.validator_count, + minimum_validator_count: self.minimum_validator_count, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }.assimilate_storage(&mut storage); + + let _ = pallet_session::GenesisConfig:: { + keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(), + }.assimilate_storage(&mut storage); + + let mut t = sp_io::TestExternalities::new(storage); + t.execute_with(|| { + let validators = Session::validators(); + SESSION.with(|x| + *x.borrow_mut() = (validators.clone(), HashSet::new()) + ); + }); + t + } +} + +pub type System = frame_system::Module; +pub type GenericAsset = pallet_generic_asset::Module; +pub type Session = pallet_session::Module; +pub type Timestamp = pallet_timestamp::Module; +pub type Staking = Module; + +pub fn start_session(session_index: SessionIndex) { + // Compensate for session delay + let session_index = session_index + 1; + for i in Session::current_index()..session_index { + System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number() * 1000); + Session::on_initialize(System::block_number()); + } + + assert_eq!(Session::current_index(), session_index); +} + +pub fn start_era(era_index: EraIndex) { + start_session((era_index * 3).into()); + assert_eq!(Staking::current_era(), era_index); +} + +pub fn current_total_payout_for_duration(duration: u64) -> u64 { + inflation::compute_total_payout( + ::RewardCurve::get(), + >::slot_stake() * 2, + GenericAsset::total_issuance(&STAKING_ASSET_ID), + duration, + ).0 +} + +#[test] +fn validator_reward_is_not_added_to_staked_amount_in_dual_currency_model() { + // Rewards go to the correct destination as determined in Payee + ExtBuilder::default().build().execute_with(|| { + // Check that account 11 is a validator + assert!(Staking::current_elected().contains(&11)); + // Check the balance of the validator account + assert_eq!(GenericAsset::free_balance(&STAKING_ASSET_ID, &10), 1_000_000_000); + // Check the balance of the stash account + assert_eq!(GenericAsset::free_balance(&REWARD_ASSET_ID, &11), 1_000_000_000); + // Check how much is at stake + assert_eq!(Staking::ledger(&10), Some(StakingLedger { + stash: 11, + total: 500_000, + active: 500_000, + unlocking: vec![], + })); + + // Compute total payout now for whole duration as other parameter won't change + let total_payout_0 = current_total_payout_for_duration(3000); + assert!(total_payout_0 > 1); // Test is meaningfull if reward something + >::reward_by_ids(vec![(11, 1)]); + + start_era(1); + + // Check that RewardDestination is Staked (default) + assert_eq!(Staking::payee(&11), RewardDestination::Staked); + // Check that reward went to the stash account of validator + assert_eq!(GenericAsset::free_balance(&REWARD_ASSET_ID, &11), 1_000_000_000 + total_payout_0); + // Check that amount at stake has NOT changed + assert_eq!(Staking::ledger(&10), Some(StakingLedger { + stash: 11, + total: 500_000, + active: 500_000, + unlocking: vec![], + })); + }); +} diff --git a/frame/support/src/additional_traits.rs b/frame/support/src/additional_traits.rs new file mode 100644 index 0000000000..2ba46d1c9f --- /dev/null +++ b/frame/support/src/additional_traits.rs @@ -0,0 +1,304 @@ +//! Additional traits to srml original traits. These traits are generally used +//! to decouple `srml` modules from `prml` modules. + +use crate::dispatch::{Parameter, DispatchError, DispatchResult}; + +use crate::traits::{ + ExistenceRequirement, Imbalance, SignedImbalance, WithdrawReasons, +}; +use codec::FullCodec; +use sp_std::{fmt::Debug, marker::PhantomData, result}; +use sp_runtime::traits::{ + PlugDoughnutApi, MaybeSerializeDeserialize, AtLeast32Bit, Zero, +}; + +/// Perform fee payment for an extrinsic +pub trait ChargeExtrinsicFee { + /// Calculate and charge a fee for the given `extrinsic` + /// How the fee is calculated is an implementation detail. + fn charge_extrinsic_fee<'a>( + transactor: &AccountId, + encoded_len: usize, + extrinsic: &'a Extrinsic, + ) -> Result<(), &'static str>; +} + +/// Charge fee trait +pub trait ChargeFee { + /// The type of fee amount. + type Amount; + + /// Charge `amount` of fees from `transactor`. Return Ok iff the payment was successful. + fn charge_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>; + + /// Refund `amount` of previous charged fees from `transactor`. Return Ok iff the refund was successful. + fn refund_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>; +} + +/// Dummy `ChargeFee` implementation, mainly for testing purpose. +pub struct DummyChargeFee(PhantomData<(T, U)>); + +impl ChargeExtrinsicFee for DummyChargeFee { + fn charge_extrinsic_fee<'a>( + _: &T, + _: usize, + _: &'a U, + ) -> Result<(), &'static str> { + Ok(()) + } +} + +impl ChargeFee for DummyChargeFee { + type Amount = U; + + fn charge_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) } + fn refund_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) } +} + +/// A type which can verify a doughnut delegation proof in order to dispatch a module/method call +/// into the runtime +/// The `verify()` hook is injected into every module/method on the runtime. +/// When a doughnut proof is included along with a transaction, `verify` will be invoked just before executing method logic. +pub trait DelegatedDispatchVerifier { + type Doughnut: PlugDoughnutApi; + type AccountId: Parameter; + + /// The doughnut permission domain it verifies + const DOMAIN: &'static str; + + /// Check the doughnut authorizes a dispatched call to `module` and `method` for this domain + fn verify_dispatch( + _doughnut: &Self::Doughnut, + _module: &str, + _method: &str, + ) -> Result<(), &'static str> { + Err("Doughnut call to module and method verification not implemented for this domain") + } + + /// Check the doughnut authorizes a dispatched call from runtime to the specified contract address for this domain. + fn verify_runtime_to_contract_call( + _caller: &Self::AccountId, + _doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + Err("Doughnut runtime to contract call verification is not implemented for this domain") + } + + /// Check the doughnut authorizes a dispatched call from a contract to another contract with the specified addresses for this domain. + fn verify_contract_to_contract_call( + _caller: &Self::AccountId, + _doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + Err("Doughnut contract to contract call verification is not implemented for this domain") + } +} + +pub struct DummyDispatchVerifier(PhantomData<(D, A)>); + +/// A dummy implementation for when dispatch verifiaction is not needed +impl DelegatedDispatchVerifier for DummyDispatchVerifier { + type Doughnut = D; + type AccountId = A; + const DOMAIN: &'static str = ""; + fn verify_dispatch(_: &Self::Doughnut, _: &str, _: &str) -> Result<(), &'static str> { + Ok(()) + } + fn verify_runtime_to_contract_call( + _caller: &Self::AccountId, + _doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + Ok(()) + } + + fn verify_contract_to_contract_call( + _caller: &Self::AccountId, + _doughnut: &Self::Doughnut, + _contract_addr: &Self::AccountId, + ) -> Result<(), &'static str> { + Ok(()) + } +} + +impl DelegatedDispatchVerifier for () { + type Doughnut = (); + type AccountId = u64; + const DOMAIN: &'static str = ""; + fn verify_dispatch( + doughnut: &Self::Doughnut, + module: &str, + method: &str, + ) -> Result<(), &'static str> { + DummyDispatchVerifier::::verify_dispatch(doughnut, module, method) + } + fn verify_runtime_to_contract_call( + caller: &Self::AccountId, + doughnut: &Self::Doughnut, + addr: &Self::AccountId, + ) -> Result<(), &'static str> { + DummyDispatchVerifier::::verify_runtime_to_contract_call(caller, doughnut, addr) + } + + fn verify_contract_to_contract_call( + caller: &Self::AccountId, + doughnut: &Self::Doughnut, + addr: &Self::AccountId, + ) -> Result<(), &'static str> { + DummyDispatchVerifier::::verify_contract_to_contract_call(caller, doughnut, addr) + } +} + +/// Something which may have doughnut. Returns a ref to the doughnut, if any. +/// It's main purpose is to allow checking if an `OuterOrigin` contains a doughnut (i.e. it is delegated). +pub trait MaybeDoughnutRef { + /// The doughnut type + type Doughnut: PlugDoughnutApi; + /// Return a `&Doughnut`, if any + fn doughnut(&self) -> Option<&Self::Doughnut>; +} + +impl MaybeDoughnutRef for () { + type Doughnut = (); + fn doughnut(&self) -> Option<&Self::Doughnut> { None } +} + +// Note: in the following traits the terms: +// - 'token' / 'asset' / 'currency' and +// - 'balance' / 'value' / 'amount' +// are used interchangeably as they make more sense in certain contexts. + +/// An abstraction over the accounting behaviour of a fungible, multi-currency system +/// Currencies in the system are identifiable by a unique `CurrencyId` +pub trait MultiCurrencyAccounting { + /// The ID type for an account in the system + type AccountId: FullCodec + Debug + Default; + /// The balance of an account for a particular currency + type Balance: AtLeast32Bit + FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default; + /// The ID type of a currency in the system + type CurrencyId: FullCodec + Debug + Default; + /// A type the is aware of the default network currency ID + /// When the currency ID is not specified for a `MultiCurrencyAccounting` method, it will be used + /// by default + type DefaultCurrencyId: AssetIdAuthority; + /// The opaque token type for an imbalance of a particular currency. This is returned by unbalanced operations + /// and must be dealt with. It may be dropped but cannot be cloned. + type NegativeImbalance: Imbalance; + /// The opaque token type for an imbalance of a particular currency. This is returned by unbalanced operations + /// and must be dealt with. It may be dropped but cannot be cloned. + type PositiveImbalance: Imbalance; + + // PUBLIC IMMUTABLES + + /// The minimum balance any single account may have. This is equivalent to the `Balances` module's + /// `ExistentialDeposit`. + fn minimum_balance() -> Self::Balance { + Zero::zero() + } + + /// The combined balance (free + reserved) of `who` for the given `currency`. + fn total_balance(who: &Self::AccountId, currency: Option) -> Self::Balance; + + /// The 'free' balance of a given account. + /// + /// This is the only balance that matters in terms of most operations on tokens. It alone + /// is used to determine the balance when in the contract execution environment. When this + /// balance falls below the value of `ExistentialDeposit`, then the 'current account' is + /// deleted: specifically `FreeBalance`. Further, the `OnFreeBalanceZero` callback + /// is invoked, giving a chance to external modules to clean up data associated with + /// the deleted account. + /// + /// `system::AccountNonce` is also deleted if `ReservedBalance` is also zero (it also gets + /// collapsed to zero if it ever becomes less than `ExistentialDeposit`. + fn free_balance(who: &Self::AccountId, currency: Option) -> Self::Balance; + + /// Returns `Ok` iff the account is able to make a withdrawal of the given amount + /// for the given reason. Basically, it's just a dry-run of `withdraw`. + /// + /// `Err(...)` with the reason why not otherwise. + fn ensure_can_withdraw( + who: &Self::AccountId, + currency: Option, + _amount: Self::Balance, + reasons: WithdrawReasons, + new_balance: Self::Balance, + ) -> DispatchResult; + + // PUBLIC MUTABLES (DANGEROUS) + + /// Adds up to `value` to the free balance of `who`. If `who` doesn't exist, it is created. + /// + /// Infallible. + fn deposit_creating( + who: &Self::AccountId, + currency: Option, + value: Self::Balance, + ) -> Self::PositiveImbalance; + + /// Mints `value` to the free balance of `who`. + /// + /// If `who` doesn't exist, nothing is done and an Err returned. + fn deposit_into_existing( + who: &Self::AccountId, + currency: Option, + value: Self::Balance + ) -> result::Result; + + /// Ensure an account's free balance equals some value; this will create the account + /// if needed. + /// + /// Returns a signed imbalance and status to indicate if the account was successfully updated or update + /// has led to killing of the account. + fn make_free_balance_be( + who: &Self::AccountId, + currency: Option, + balance: Self::Balance, + ) -> SignedImbalance; + + /// Transfer some liquid free balance to another staker. + /// + /// This is a very high-level function. It will ensure all appropriate fees are paid + /// and no imbalance in the system remains. + fn transfer( + source: &Self::AccountId, + dest: &Self::AccountId, + currency: Option, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult; + + /// Removes some free balance from `who` account for `reason` if possible. If `liveness` is + /// `KeepAlive`, then no less than `ExistentialDeposit` must be left remaining. + /// + /// This checks any locks, vesting, and liquidity requirements. If the removal is not possible, + /// then it returns `Err`. + /// + /// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value + /// is `value`. + fn withdraw( + who: &Self::AccountId, + currency: Option, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result; + +} + +/// A type which provides an ID with authority from chain storage +pub trait AssetIdAuthority { + /// The asset ID type e.g a `u32` + type AssetId; + /// Return the authoritative asset ID + fn asset_id() -> Self::AssetId; +} + +/// A type which can provide it's inherent asset ID +/// It is useful in the context of an asset/currency aware balance type +/// It differs from `AssetIdAuthority` in that it is not statically defined +pub trait InherentAssetIdProvider { + /// The asset ID type e.g. a `u32` + type AssetId; + /// Return the inherent asset ID + fn asset_id(&self) -> Self::AssetId; +} diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 084ea285af..9d52747d21 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -18,6 +18,7 @@ //! generating values representing lazy module function calls. pub use crate::sp_std::{result, fmt, prelude::{Vec, Clone, Eq, PartialEq}, marker}; +pub use crate::additional_traits::DelegatedDispatchVerifier; pub use crate::codec::{Codec, EncodeLike, Decode, Encode, Input, Output, HasCompact, EncodeAsRef}; pub use frame_metadata::{ FunctionMetadata, DecodeDifferent, DecodeDifferentArray, FunctionArgumentMetadata, @@ -1276,7 +1277,21 @@ macro_rules! decl_module { $(#[doc = $doc_attr])* $fn_vis fn $fn_name ( $from $(, $param_name : $param )* - ) $( -> $result )* { $( $impl )* } + ) $( -> $result )* { + // Trait imports for doughnut dispatch verification + use $crate::additional_traits::MaybeDoughnutRef; + use $crate::dispatch::DelegatedDispatchVerifier; + // Check whether `origin` is acting with delegated authority (i.e. doughnut attached). + if let Some(doughnut) = &$from.doughnut() { + // Ensure the doughnut authorizes the current call + let _ = ::DelegatedDispatchVerifier::verify_dispatch( + doughnut, + env!("CARGO_PKG_NAME"), // module + stringify!($fn_name) // method + )?; + } + $( $impl )* + } } )* } @@ -1914,12 +1929,14 @@ macro_rules! __check_reserved_fn_name { #[allow(dead_code)] mod tests { use super::*; + // TODO: Why isn't dispatch macro expansion importing this? + use crate::additional_traits::{DelegatedDispatchVerifier, MaybeDoughnutRef}; use crate::sp_runtime::traits::{OnInitialize, OnFinalize}; use crate::weights::{DispatchInfo, DispatchClass}; use crate::traits::{CallMetadata, GetCallMetadata, GetCallName}; pub trait Trait: system::Trait + Sized where Self::AccountId: From { - type Origin; + type Origin: MaybeDoughnutRef; type BlockNumber: Into; type Call: From>; } @@ -1929,6 +1946,8 @@ mod tests { pub trait Trait { type AccountId; + type Doughnut; + type DelegatedDispatchVerifier: DelegatedDispatchVerifier; } pub fn ensure_root(_: R) -> DispatchResult { @@ -2036,8 +2055,17 @@ mod tests { pub struct TraitImpl {} + pub struct MockOrigin(pub u32); + + impl MaybeDoughnutRef for MockOrigin { + type Doughnut = (); + fn doughnut(&self) -> Option<&Self::Doughnut> { + None + } + } + impl Trait for TraitImpl { - type Origin = u32; + type Origin = MockOrigin; type BlockNumber = u32; type Call = OuterCall; } @@ -2045,13 +2073,15 @@ mod tests { type Test = Module; impl_outer_dispatch! { - pub enum OuterCall for TraitImpl where origin: u32 { + pub enum OuterCall for TraitImpl where origin: MockOrigin { self::Test, } } impl system::Trait for TraitImpl { type AccountId = u32; + type DelegatedDispatchVerifier = (); + type Doughnut = (); } #[test] diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 531f255714..e0225b5dcb 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -63,6 +63,7 @@ pub mod inherent; pub mod unsigned; #[macro_use] pub mod error; +pub mod additional_traits; pub mod traits; pub mod weights; diff --git a/frame/support/src/metadata.rs b/frame/support/src/metadata.rs index 46662e5354..93e2c68517 100644 --- a/frame/support/src/metadata.rs +++ b/frame/support/src/metadata.rs @@ -250,6 +250,7 @@ mod tests { }; use codec::{Encode, Decode}; use crate::traits::Get; + use crate::additional_traits::{DelegatedDispatchVerifier, MaybeDoughnutRef}; use sp_runtime::transaction_validity::TransactionValidityError; #[derive(Clone, Eq, Debug, PartialEq, Encode, Decode)] @@ -292,12 +293,14 @@ mod tests { pub trait Trait: 'static { const ASSOCIATED_CONST: u64 = 500; - type Origin: Into, Self::Origin>> - + From>; + type Origin: Into, Self::Origin>> + + From> + MaybeDoughnutRef; type AccountId: From + Encode; type BlockNumber: From + Encode; type SomeValue: Get; type ModuleToIndex: crate::traits::ModuleToIndex; + type Doughnut; + type DelegatedDispatchVerifier: DelegatedDispatchVerifier; } decl_module! { @@ -316,26 +319,29 @@ mod tests { ); #[derive(Clone, PartialEq, Eq, Debug)] - pub enum RawOrigin { + pub enum RawOrigin { Root, Signed(AccountId), + Delegated(AccountId, Doughnut), None, } - impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { + impl From<(Option, Option)> for RawOrigin { + fn from(s: (Option, Option)) -> RawOrigin { match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, + (Some(who), None) => RawOrigin::Signed(who), + (Some(who), Some(doughnut)) => RawOrigin::Delegated(who, doughnut), + _ => RawOrigin::None, } } } - pub type Origin = RawOrigin<::AccountId>; + pub type Origin = RawOrigin<::AccountId, ::Doughnut>; } mod event_module { use crate::dispatch::DispatchResult; + use super::system; pub trait Trait: super::system::Trait { type Balance; @@ -369,8 +375,9 @@ mod tests { } mod event_module2 { + use crate::additional_traits::MaybeDoughnutRef; pub trait Trait { - type Origin; + type Origin: MaybeDoughnutRef; type Balance; type BlockNumber; } @@ -441,6 +448,8 @@ mod tests { type BlockNumber = u32; type SomeValue = SystemValue; type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); } impl_runtime_metadata!( diff --git a/frame/support/src/origin.rs b/frame/support/src/origin.rs index 43d2e70953..b6bfbf8913 100644 --- a/frame/support/src/origin.rs +++ b/frame/support/src/origin.rs @@ -187,11 +187,23 @@ macro_rules! impl_outer_origin { } } } - impl From::AccountId>> for $name { - fn from(x: Option<<$runtime as $system::Trait>::AccountId>) -> Self { + impl From<(Option<<$runtime as $system::Trait>::AccountId>,Option<<$runtime as $system::Trait>::Doughnut>)> for $name { + fn from(x: (Option<<$runtime as $system::Trait>::AccountId>, Option<<$runtime as $system::Trait>::Doughnut>)) -> Self { <$system::Origin<$runtime>>::from(x).into() } } + impl $crate::additional_traits::MaybeDoughnutRef for $name { + type Doughnut = <$runtime as $system::Trait>::Doughnut; + /// Return a ref to the `OuterOrigin`'s attached doughnut, if any + fn doughnut(&self) -> Option<&Self::Doughnut> { + if let $name::system(ref inner) = self { + if let $system::RawOrigin::Delegated(_who, doughnut) = inner { + return Some(doughnut) + } + } + return None + } + } $( $crate::paste::item! { impl From<$module::Origin < $( $generic )? $(, $module::$generic_instance )? > > for $name { @@ -226,25 +238,28 @@ mod tests { mod system { pub trait Trait { type AccountId; + type Doughnut; } #[derive(Clone, PartialEq, Eq, Debug)] - pub enum RawOrigin { + pub enum RawOrigin { Root, Signed(AccountId), + Delegated(AccountId, Doughnut), None, } - impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { + impl From<(Option, Option)> for RawOrigin { + fn from(s: (Option, Option)) -> RawOrigin { match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, + (Some(who), None) => RawOrigin::Signed(who), + (Some(who), Some(doughnut)) => RawOrigin::Delegated(who, doughnut), + _ => RawOrigin::None, } } } - pub type Origin = RawOrigin<::AccountId>; + pub type Origin = RawOrigin<::AccountId, ::Doughnut>; } mod origin_without_generic { @@ -264,6 +279,7 @@ mod tests { impl system::Trait for TestRuntime { type AccountId = u32; + type Doughnut = (); } impl_outer_origin!( diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index c46cca683b..818910ef99 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -326,28 +326,43 @@ impl GetDispatchInfo for sp_runtime::testing::TestX } } +/// Implementation for test extrinsic. +#[cfg(feature = "std")] +impl GetDispatchInfo for sp_runtime::testing::DoughnutTestXt { + fn get_dispatch_info(&self) -> DispatchInfo { + // for testing: weight == size. + DispatchInfo { + weight: self.encode().len() as _, + pays_fee: true, + ..Default::default() + } + } +} + + #[cfg(test)] #[allow(dead_code)] mod tests { + use crate::additional_traits::{DelegatedDispatchVerifier, MaybeDoughnutRef}; use crate::decl_module; use super::*; - pub trait Trait { - type Origin; - type Balance; - type BlockNumber; + pub trait Trait: system::Trait + Sized where Self::AccountId: From { + type Origin: MaybeDoughnutRef; + type BlockNumber: Into; + type Call: From>; } pub struct TraitImpl {} impl Trait for TraitImpl { - type Origin = u32; + type Origin = MockOrigin; type BlockNumber = u32; - type Balance = u32; + type Call = OuterCall; } decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, T::AccountId: From { // no arguments, fixed weight #[weight = SimpleDispatchInfo::FixedNormal(1000)] fn f0(_origin) { unimplemented!(); } @@ -361,6 +376,40 @@ mod tests { } } + mod system { + use super::*; + pub trait Trait { + type AccountId; + type Balance; + type Doughnut; + type DelegatedDispatchVerifier: DelegatedDispatchVerifier; + } + } + + pub struct MockOrigin(pub u32); + + impl MaybeDoughnutRef for MockOrigin { + type Doughnut = (); + fn doughnut(&self) -> Option<&Self::Doughnut> { + None + } + } + + type Test = Module; + + impl_outer_dispatch! { + pub enum OuterCall for TraitImpl where origin: MockOrigin { + self::Test, + } + } + + impl system::Trait for TraitImpl { + type AccountId = u32; + type Balance = u32; + type Doughnut = (); + type DelegatedDispatchVerifier = (); + } + #[test] fn weights_are_correct() { assert_eq!(Call::::f11(10, 20).get_dispatch_info().weight, 120); diff --git a/frame/support/test/tests/decl_error.rs b/frame/support/test/tests/decl_error.rs index 39d7dbdb96..ed838cafa8 100644 --- a/frame/support/test/tests/decl_error.rs +++ b/frame/support/test/tests/decl_error.rs @@ -91,6 +91,8 @@ impl system::Trait for Runtime { type AccountId = AccountId; type Event = Event; type ModuleToIndex = ModuleToIndex; + type Doughnut = (); + type DelegatedDispatchVerifier = (); } frame_support::construct_runtime!( diff --git a/frame/support/test/tests/instance.rs b/frame/support/test/tests/instance.rs index 6fa2806dd3..c6149c524c 100644 --- a/frame/support/test/tests/instance.rs +++ b/frame/support/test/tests/instance.rs @@ -239,6 +239,8 @@ impl system::Trait for Runtime { type AccountId = AccountId; type Event = Event; type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); } frame_support::construct_runtime!( diff --git a/frame/support/test/tests/issue2219.rs b/frame/support/test/tests/issue2219.rs index 4537766981..7cf6ae5b5a 100644 --- a/frame/support/test/tests/issue2219.rs +++ b/frame/support/test/tests/issue2219.rs @@ -163,6 +163,8 @@ impl system::Trait for Runtime { type AccountId = AccountId; type Event = Event; type ModuleToIndex = (); + type DelegatedDispatchVerifier = (); + type Doughnut = (); } impl module::Trait for Runtime {} diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.rs b/frame/support/test/tests/reserved_keyword/on_initialize.rs index e389529bca..619c5f9de1 100644 --- a/frame/support/test/tests/reserved_keyword/on_initialize.rs +++ b/frame/support/test/tests/reserved_keyword/on_initialize.rs @@ -2,16 +2,25 @@ macro_rules! reserved { ($($reserved:ident)*) => { $( mod $reserved { + use frame_support::additional_traits::MaybeDoughnutRef; pub use frame_support::dispatch; - pub trait Trait { - type Origin; + // `decl_module` expansion has added doughnut logic which requires system trait is implemented + pub trait Trait: system::Trait { + type Origin: MaybeDoughnutRef; type BlockNumber: Into; } pub mod system { + use sp_runtime::traits::PlugDoughnutApi; + use frame_support::additional_traits::DelegatedDispatchVerifier; use frame_support::dispatch; + pub trait Trait { + type Doughnut: PlugDoughnutApi; + type DelegatedDispatchVerifier: DelegatedDispatchVerifier; + } + pub fn ensure_root(_: R) -> dispatch::DispatchResult { Ok(()) } diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.stderr b/frame/support/test/tests/reserved_keyword/on_initialize.stderr index 13c2ef8d2c..fa0f71df48 100644 --- a/frame/support/test/tests/reserved_keyword/on_initialize.stderr +++ b/frame/support/test/tests/reserved_keyword/on_initialize.stderr @@ -1,47 +1,47 @@ error: Invalid call fn name: `on_finalize`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: Invalid call fn name: `on_initialize`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: `on_finalise` was renamed to `on_finalize`. Please rename your function accordingly. - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: `on_initialise` was renamed to `on_initialize`. Please rename your function accordingly. - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: Invalid call fn name: `offchain_worker`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: `deposit_event` function is reserved and must follow the syntax: `$vis:vis fn deposit_event() = default;` - --> $DIR/on_initialize.rs:30:1 + --> $DIR/on_initialize.rs:39:1 | -30 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); +39 | reserved!(on_finalize on_initialize on_finalise on_initialise offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/frame/support/test/tests/system.rs b/frame/support/test/tests/system.rs index c7f60117bc..ceeaa04ec4 100644 --- a/frame/support/test/tests/system.rs +++ b/frame/support/test/tests/system.rs @@ -14,17 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +use frame_support::additional_traits::{DelegatedDispatchVerifier as DelegatedDispatchVerifierT, MaybeDoughnutRef}; use frame_support::codec::{Encode, Decode, EncodeLike}; pub trait Trait: 'static + Eq + Clone { - type Origin: Into, Self::Origin>> - + From>; - + type Origin: Into, Self::Origin>> + + From> + MaybeDoughnutRef; type BlockNumber: Decode + Encode + EncodeLike + Clone + Default; type Hash; type AccountId: Encode + EncodeLike + Decode; type Event: From>; type ModuleToIndex: frame_support::traits::ModuleToIndex; + type DelegatedDispatchVerifier: DelegatedDispatchVerifierT; + type Doughnut; } frame_support::decl_module! { @@ -55,26 +57,28 @@ frame_support::decl_error! { /// Origin for the system module. #[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug)] -pub enum RawOrigin { +pub enum RawOrigin { Root, Signed(AccountId), + Delegated(AccountId, Doughnut), None, } -impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { +impl From<(Option,Option)> for RawOrigin { + fn from(s: (Option, Option)) -> RawOrigin { match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, + (Some(who), None) => RawOrigin::Signed(who), + (Some(who), Some(doughnut)) => RawOrigin::Delegated(who, doughnut), + _ => RawOrigin::None, } } } -pub type Origin = RawOrigin<::AccountId>; +pub type Origin = RawOrigin<::AccountId, ::Doughnut>; #[allow(dead_code)] -pub fn ensure_root(o: OuterOrigin) -> Result<(), &'static str> - where OuterOrigin: Into, OuterOrigin>> +pub fn ensure_root(o: OuterOrigin) -> Result<(), &'static str> + where OuterOrigin: Into, OuterOrigin>> { o.into().map(|_| ()).map_err(|_| "bad origin: expected to be a root origin") } diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index f89c9efbd2..607078fe59 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -108,6 +108,7 @@ use sp_runtime::{ self, CheckEqual, AtLeast32Bit, Zero, SignedExtension, Lookup, LookupError, SimpleBitOps, Hash, Member, MaybeDisplay, EnsureOrigin, BadOrigin, SaturatedConversion, MaybeSerialize, MaybeSerializeDeserialize, MaybeMallocSizeOf, StaticLookup, One, Bounded, + PlugDoughnutApi, }, }; @@ -119,6 +120,7 @@ use frame_support::{ StoredMap }, weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo, FunctionOf}, + additional_traits::{DelegatedDispatchVerifier as DelegatedDispatchVerifierT, MaybeDoughnutRef}, }; use codec::{Encode, Decode, FullCodec, EncodeLike}; @@ -140,8 +142,8 @@ pub fn extrinsics_data_root(xts: Vec>) -> H::Output { pub trait Trait: 'static + Eq + Clone { /// The aggregated `Origin` type used by dispatchable calls. type Origin: - Into, Self::Origin>> - + From> + Into, Self::Origin>> + + From> + MaybeDoughnutRef + Clone; /// The aggregated `Call` type. @@ -190,6 +192,12 @@ pub trait Trait: 'static + Eq + Clone { /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount: Get; + /// The runtime proof of delegation AKA doughnut type + type Doughnut: Parameter + Member + PlugDoughnutApi; + + /// A type which verifies a doughnut in order to dispatch a `Call` with delegated authority + type DelegatedDispatchVerifier: DelegatedDispatchVerifierT; + /// The maximum weight of a block. type MaximumBlockWeight: Get; @@ -253,28 +261,36 @@ pub struct EventRecord { /// Origin for the System module. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -pub enum RawOrigin { +pub enum RawOrigin { /// The system itself ordained this dispatch to happen: this is the highest privilege level. Root, /// It is signed by some public key and we provide the `AccountId`. Signed(AccountId), + /// This dispatch uses a doughnut delegation proof. + /// The runtime will replace the extrinsic signer's `AccountId` with the Doughnut issuer's PublicKey after validating it. + /// i.e. a transformation like: `RawOrigin::Signed(signer) => RawOrigin::Delegated(doughnut.issuer(), doughnut)` occurs. + /// The `origin` keeps the doughnut proof so that the runtime is aware of the delegation having taken place at all times + /// during the dispatch execution. + Delegated(AccountId, Doughnut), /// It is signed by nobody, can be either: /// * included and agreed upon by the validators anyway, /// * or unsigned transaction validated by a module. None, } -impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, +impl From<(Option, Option)> for RawOrigin { + fn from(val: (Option, Option)) -> RawOrigin { + match (val.0, val.1) { + (Some(who), None) => RawOrigin::Signed(who), + (Some(who), Some(doughnut)) => RawOrigin::Delegated(who, doughnut), + // Disallow delegation from unsigned extrinsics for now + _ => RawOrigin::None, } } } /// Exposed trait-generic origin type. -pub type Origin = RawOrigin<::AccountId>; +pub type Origin = RawOrigin<::AccountId, ::Doughnut>; // Create a Hash with 69 for each byte, // only used to build genesis config. @@ -433,7 +449,7 @@ decl_error! { } decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, system = self{ type Error = Error; /// A dispatch that will fill the block weight up to the given ratio. @@ -553,11 +569,12 @@ decl_module! { } } -pub struct EnsureRoot(sp_std::marker::PhantomData); +pub struct EnsureRoot(sp_std::marker::PhantomData<(AccountId, Doughnut)>); impl< - O: Into, O>> + From>, + O: Into, O>> + From>, AccountId, -> EnsureOrigin for EnsureRoot { + Doughnut, +> EnsureOrigin for EnsureRoot { type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { @@ -567,11 +584,12 @@ impl< } } -pub struct EnsureSigned(sp_std::marker::PhantomData); +pub struct EnsureSigned(sp_std::marker::PhantomData<(AccountId, Doughnut)>); impl< - O: Into, O>> + From>, + O: Into, O>> + From>, AccountId, -> EnsureOrigin for EnsureSigned { + Doughnut, +> EnsureOrigin for EnsureSigned { type Success = AccountId; fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { @@ -581,12 +599,13 @@ impl< } } -pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId)>); +pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId, Doughnut)>); impl< - O: Into, O>> + From>, + O: Into, O>> + From>, Who: Contains, AccountId: PartialEq + Clone + Ord, -> EnsureOrigin for EnsureSignedBy { + Doughnut, +> EnsureOrigin for EnsureSignedBy { type Success = AccountId; fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { @@ -596,11 +615,12 @@ impl< } } -pub struct EnsureNone(sp_std::marker::PhantomData); +pub struct EnsureNone(sp_std::marker::PhantomData<(AccountId, Doughnut)>); impl< - O: Into, O>> + From>, + O: Into, O>> + From>, AccountId, -> EnsureOrigin for EnsureNone { + Doughnut, +> EnsureOrigin for EnsureNone { type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { @@ -620,18 +640,41 @@ impl EnsureOrigin for EnsureNever { /// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). /// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise. -pub fn ensure_signed(o: OuterOrigin) -> Result - where OuterOrigin: Into, OuterOrigin>> +pub fn ensure_signed(o: OuterOrigin) -> Result + where OuterOrigin: Into, OuterOrigin>> { match o.into() { - Ok(RawOrigin::Signed(t)) => Ok(t), + // Assuming the delegation proof has been validated, a `RawOrigin::Delegated` should also be considered a valid `RawOrigin::Signed` + Ok(RawOrigin::Signed(t)) | Ok(RawOrigin::Delegated(t, _)) => Ok(t), _ => Err(BadOrigin), } } +/// Ensure that 'origin' represents a signed or delegated extrinsic. If 'origin' is a delegated one, ensure that doughnut verifies +/// the issuers's privilage to call dest (destination contract). Return `Ok` with the account id of the issuer who signed the extrinsic +/// or delegated it, otherwise `Err`. +pub fn ensure_verified_contract_call( + origin: T::Origin, + contract: &T::AccountId, +) -> Result<(T::AccountId, Option), &'static str> { + match origin.into() { + Ok(RawOrigin::Signed(caller)) => Ok((caller, None)), + Ok(RawOrigin::Delegated(caller, doughnut)) => { + T::DelegatedDispatchVerifier::verify_runtime_to_contract_call( + &caller, + &doughnut, + contract, + ) + .map(|_| (caller, Some(doughnut))) + .map_err(|msg| msg) + } + _ => Err("bad origin: expected to be a signed origin"), + } +} + /// Ensure that the origin `o` represents the root. Returns `Ok` or an `Err` otherwise. -pub fn ensure_root(o: OuterOrigin) -> Result<(), BadOrigin> - where OuterOrigin: Into, OuterOrigin>> +pub fn ensure_root(o: OuterOrigin) -> Result<(), BadOrigin> + where OuterOrigin: Into, OuterOrigin>> { match o.into() { Ok(RawOrigin::Root) => Ok(()), @@ -640,8 +683,8 @@ pub fn ensure_root(o: OuterOrigin) -> Result<(), BadOrig } /// Ensure that the origin `o` represents an unsigned extrinsic. Returns `Ok` or an `Err` otherwise. -pub fn ensure_none(o: OuterOrigin) -> Result<(), BadOrigin> - where OuterOrigin: Into, OuterOrigin>> +pub fn ensure_none(o: OuterOrigin) -> Result<(), BadOrigin> + where OuterOrigin: Into, OuterOrigin>> { match o.into() { Ok(RawOrigin::None) => Ok(()), @@ -657,7 +700,6 @@ pub enum InitKind { /// Should only be used for off-chain calls, /// regular block execution should clear those. Inspection, - /// Reset also inspectable storage entries. /// /// This should be used for regular block execution. @@ -1187,7 +1229,7 @@ impl SignedExtension for CheckWeight { fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } fn pre_dispatch( - self, + &self, _who: &Self::AccountId, _call: &Self::Call, info: Self::DispatchInfo, @@ -1269,7 +1311,7 @@ impl SignedExtension for CheckNonce { fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } fn pre_dispatch( - self, + &self, who: &Self::AccountId, _call: &Self::Call, _info: Self::DispatchInfo, @@ -1527,6 +1569,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = Version; type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = u32; type OnNewAccount = (); type OnKilledAccount = RecordKilled; @@ -1561,9 +1605,9 @@ mod tests { #[test] fn origin_works() { - let o = Origin::from(RawOrigin::::Signed(1u64)); - let x: Result, Origin> = o.into(); - assert_eq!(x, Ok(RawOrigin::::Signed(1u64))); + let o = Origin::from(RawOrigin::::Signed(1u64)); + let x: Result, Origin> = o.into(); + assert_eq!(x, Ok(RawOrigin::::Signed(1u64))); } #[test] @@ -1908,11 +1952,9 @@ mod tests { }) } - #[test] fn set_code_checks_works() { struct CallInWasm(Vec); - impl sp_core::traits::CallInWasm for CallInWasm { fn call_in_wasm( &self, @@ -1924,7 +1966,6 @@ mod tests { Ok(self.0.clone()) } } - let test_data = vec![ ("test", 1, 2, Ok(())), ("test", 1, 1, Err(Error::::SpecOrImplVersionNeedToIncrease)), @@ -1933,7 +1974,6 @@ mod tests { ("test", 0, 1, Err(Error::::SpecVersionNotAllowedToDecrease)), ("test", 1, 0, Err(Error::::ImplVersionNotAllowedToDecrease)), ]; - for (spec_name, spec_version, impl_version, expected) in test_data.into_iter() { let version = RuntimeVersion { spec_name: spec_name.into(), @@ -1942,7 +1982,6 @@ mod tests { ..Default::default() }; let call_in_wasm = CallInWasm(version.encode()); - let mut ext = new_test_ext(); ext.register_extension(sp_core::traits::CallInWasmExt::new(call_in_wasm)); ext.execute_with(|| { @@ -1950,12 +1989,10 @@ mod tests { RawOrigin::Root.into(), vec![1, 2, 3, 4], ); - assert_eq!(expected.map_err(DispatchError::from), res); }); } } - #[test] fn set_code_with_real_wasm_blob() { let executor = substrate_test_runtime_client::new_native_executor(); diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 1c3fec2112..d177cd6590 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -126,7 +126,7 @@ pub trait Trait: frame_system::Trait { } decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::Origin, system = frame_system { /// The minimum period between blocks. Beware that this is different to the *expected* period /// that the block production apparatus provides. Your chosen consensus system will generally /// work with this to determine a sensible block time. e.g. For Aura, it will be double this @@ -276,6 +276,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 67b3fa63ac..ad15387aed 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -302,6 +302,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index bbf31cc599..6119ea719f 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -758,6 +758,8 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -793,8 +795,8 @@ mod tests { } impl Trait for Test { type Currency = pallet_balances::Module; - type ApproveOrigin = frame_system::EnsureRoot; - type RejectOrigin = frame_system::EnsureRoot; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; type Tippers = TenToFourteen; type TipCountdown = TipCountdown; type TipFindersFee = TipFindersFee; diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 76f3ca6bf6..fa7bb91e21 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -622,6 +622,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index fdc480ddd0..1697790b94 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -337,6 +337,8 @@ mod tests { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); @@ -522,7 +524,7 @@ mod tests { // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_noop!( - Balances::transfer(Some(1).into(), 2, 56), + Balances::transfer((Some(1), None).into(), 2, 56), pallet_balances::Error::::LiquidityRestrictions, ); // Account 1 cannot send more than vested amount }); @@ -539,8 +541,8 @@ mod tests { assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); - assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); + assert_ok!(Vesting::vest((Some(1), None).into())); + assert_ok!(Balances::transfer((Some(1), None).into(), 2, 55)); }); } @@ -555,8 +557,8 @@ mod tests { assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); - assert_ok!(Vesting::vest_other(Some(2).into(), 1)); - assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); + assert_ok!(Vesting::vest_other((Some(2), None).into(), 1)); + assert_ok!(Balances::transfer((Some(1), None).into(), 2, 55)); }); } @@ -567,8 +569,8 @@ mod tests { .build() .execute_with(|| { assert_eq!(System::block_number(), 1); - assert_ok!(Balances::transfer(Some(3).into(), 1, 100)); - assert_ok!(Balances::transfer(Some(3).into(), 2, 100)); + assert_ok!(Balances::transfer((Some(3), None).into(), 1, 100)); + assert_ok!(Balances::transfer((Some(3), None).into(), 2, 100)); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal @@ -578,13 +580,13 @@ mod tests { // Account 1 has only 5 units vested at block 1 (plus 150 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); - assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer(Some(1).into(), 3, 155)); // Account 1 can send extra units gained + assert_ok!(Vesting::vest((Some(1), None).into())); + assert_ok!(Balances::transfer((Some(1), None).into(), 3, 155)); // Account 1 can send extra units gained // Account 2 has no units vested at block 1, but gained 100 assert_eq!(Vesting::vesting_balance(&2), Some(200)); - assert_ok!(Vesting::vest(Some(2).into())); - assert_ok!(Balances::transfer(Some(2).into(), 3, 100)); // Account 2 can send extra units gained + assert_ok!(Vesting::vest((Some(2), None).into())); + assert_ok!(Balances::transfer((Some(2), None).into(), 3, 100)); // Account 2 can send extra units gained }); } @@ -610,7 +612,7 @@ mod tests { assert_eq!(Vesting::vesting(&12), Some(user12_vesting_schedule)); // Account 12 can still send liquid funds - assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5)); + assert_ok!(Balances::transfer((Some(12), None).into(), 3, 256 * 5)); }); } } diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 4b520a240a..7a524f7d06 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -777,6 +777,30 @@ pub trait Logging { } } +/// Interface that provides functions for benchmarking the runtime. +#[runtime_interface] +pub trait Benchmarking { + /// Get the number of nanoseconds passed since the UNIX epoch + /// + /// WARNING! This is a non-deterministic call. Do not use this within + /// consensus critical logic. + fn current_time() -> u128 { + std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Unix time doesn't go backwards; qed") + .as_nanos() + } + + /// Reset the trie database to the genesis state. + fn wipe_db(&mut self) { + self.wipe() + } + + /// Commit pending storage changes to the trie database and clear the database cache. + fn commit_db(&mut self) { + self.commit() + } +} + /// Wasm-only interface that provides functions for interacting with the sandbox. #[runtime_interface(wasm_only)] pub trait Sandbox { diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 29736af6e5..4f19f56adf 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -19,6 +19,7 @@ log = { version = "0.4.8", optional = true } paste = "0.1.6" rand = { version = "0.7.2", optional = true } impl-trait-for-tuples = "0.1.3" +doughnut = { package = "doughnut_rs", branch = "0.3.0", git = "https://github.com/cennznet/doughnut-rs", default-features = false } sp-inherents = { version = "2.0.0-dev", default-features = false, path = "../inherents" } parity-util-mem = { version = "0.5.1", default-features = false, features = ["primitive-types"] } @@ -36,6 +37,7 @@ std = [ "log", "sp-core/std", "rand", + "doughnut/std", "sp-std/std", "sp-io/std", "serde", diff --git a/primitives/runtime/src/generic/checked_extrinsic.rs b/primitives/runtime/src/generic/checked_extrinsic.rs index 20aefbdf99..9a857f00a0 100644 --- a/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/primitives/runtime/src/generic/checked_extrinsic.rs @@ -18,7 +18,7 @@ //! stage. use crate::traits::{ - self, Member, MaybeDisplay, SignedExtension, Dispatchable, + self, Dispatchable, PlugDoughnutApi, MaybeDisplay, MaybeDoughnut, Member, SignedExtension, }; use crate::traits::ValidateUnsigned; use crate::transaction_validity::TransactionValidity; @@ -36,14 +36,15 @@ pub struct CheckedExtrinsic { pub function: Call, } -impl traits::Applyable for +impl traits::Applyable for CheckedExtrinsic where - AccountId: Member + MaybeDisplay, + AccountId: Member + MaybeDisplay + AsRef<[u8]>, Call: Member + Dispatchable, - Extra: SignedExtension, - Origin: From>, + Extra: SignedExtension + MaybeDoughnut, + Origin: From<(Option, Option)>, Info: Clone, + Doughnut: Member + PlugDoughnutApi, { type AccountId = AccountId; type Call = Call; @@ -72,16 +73,22 @@ where info: Self::DispatchInfo, len: usize, ) -> crate::ApplyExtrinsicResult { - let (maybe_who, pre) = if let Some((id, extra)) = self.signed { - let pre = Extra::pre_dispatch(extra, &id, &self.function, info.clone(), len)?; - (Some(id), pre) + let (pre, res) = if let Some((id, extra)) = self.signed { + let pre = Extra::pre_dispatch(&extra, &id, &self.function, info.clone(), len)?; + if let Some(doughnut) = extra.doughnut() { + // A delegated transaction + (pre, self.function.dispatch(Origin::from((Some(doughnut.issuer()), Some(doughnut))))) + } else { + // An ordinary signed transaction + (pre, self.function.dispatch(Origin::from((Some(id), None)))) + } } else { + // An inherent unsiged transaction let pre = Extra::pre_dispatch_unsigned(&self.function, info.clone(), len)?; U::pre_dispatch(&self.function)?; - (None, pre) + (pre, self.function.dispatch(Origin::from((None, None)))) }; - let res = self.function.dispatch(Origin::from(maybe_who)); - Extra::post_dispatch(pre, info.clone(), len); + Extra::post_dispatch(pre, info, len); Ok(res.map_err(Into::into)) } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 6501dafc0e..a2e6eda508 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -74,6 +74,10 @@ pub use sp_arithmetic::helpers_128bit; /// Re-export big_uint stuff. pub use sp_arithmetic::biguint; +/// Re-export official v0 Doughnut type +pub use doughnut::v0::parity::DoughnutV0; +pub use doughnut::Doughnut; + pub use random_number_generator::RandomNumberGenerator; /// An abstraction over justification for a block's validity under a consensus algorithm. @@ -685,6 +689,18 @@ pub fn print(print: impl traits::Printable) { print.print(); } +/// An alphabet of possible parameters to use for benchmarking. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum BenchmarkParameter { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, +} + +/// Results from running benchmarks on a FRAME pallet. +/// Contains duration of the function call in nanoseconds along with the benchmark parameters +/// used for that benchmark result. +pub type BenchmarkResults = (Vec<(BenchmarkParameter, u32)>, u128); + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 6f6ea3dd16..a350e2f8ab 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -21,7 +21,7 @@ use std::{fmt::Debug, ops::Deref, fmt, cell::RefCell}; use crate::codec::{Codec, Encode, Decode}; use crate::traits::{ self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, - SignedExtension, Dispatchable, + SignedExtension, Dispatchable, PlugDoughnutApi, MaybeDisplay, MaybeDoughnut, }; use crate::traits::ValidateUnsigned; use crate::{generic::{self, CheckSignature}, KeyTypeId, ApplyExtrinsicResult}; @@ -222,7 +222,7 @@ impl<'a> Deserialize<'a> for Header { pub struct ExtrinsicWrapper(Xt); impl traits::Extrinsic for ExtrinsicWrapper -where Xt: parity_util_mem::MallocSizeOf + where Xt: parity_util_mem::MallocSizeOf { type Call = (); type SignaturePayload = (); @@ -262,7 +262,7 @@ pub struct Block { } impl traits::Block - for Block +for Block { type Extrinsic = Xt; type Header = Header; @@ -355,7 +355,7 @@ impl TestXt { self.validity = TestValidity::SignatureInvalid(TransactionValidityError::Invalid(InvalidTransaction::BadProof)); self } - } +} // Non-opaque extrinsics always 0. parity_util_mem::malloc_size_of_is_0!(any: TestXt); @@ -369,8 +369,8 @@ impl Serialize for TestXt where TestXt: E impl Debug for TestXt { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "TestXt({:?}, {}, ...)", - self.signature.as_ref().map(|x| &x.0), - if let TestValidity::Valid = self.validity { "valid" } else { "invalid" } + self.signature.as_ref().map(|x| &x.0), + if let TestValidity::Valid = self.validity { "valid" } else { "invalid" } ) } } @@ -388,7 +388,7 @@ impl Checkable for TestXt Err(e), } - } + } } impl traits::Extrinsic for TestXt { @@ -433,7 +433,7 @@ impl Applyable for TestXt where len: usize, ) -> ApplyExtrinsicResult { let maybe_who = if let Some((who, extra)) = self.signature { - Extra::pre_dispatch(extra, &who, &self.call, info, len)?; + Extra::pre_dispatch(&extra, &who, &self.call, info, len)?; Some(who) } else { Extra::pre_dispatch_unsigned(&self.call, info, len)?; @@ -443,3 +443,238 @@ impl Applyable for TestXt where Ok(self.call.dispatch(maybe_who.into()).map_err(Into::into)) } } + + +/// Test XT for doughnut testing. This test class is the same as above, but has an +/// extra "AccontID" parameter type used for signing doughnuts. +/// +/// Used to mock actual transaction. +#[derive(PartialEq, Eq, Clone, Encode, Decode)] +pub struct DoughnutTestXt { + /// Signature with extra. + /// + /// if some, then the transaction is signed. Transaction is unsigned otherwise. + pub signature: Option<(AccountId, Extra)>, + /// Validity. + /// + /// Instantiate invalid variant and transaction will fail correpsonding checks. + pub validity: TestValidity, + /// Call. + pub call: Call, +} + +impl DoughnutTestXt { + /// New signed test `TextXt`. + pub fn new_signed(signature: (AccountId, Extra), call: Call) -> Self { + DoughnutTestXt { + signature: Some(signature), + validity: TestValidity::Valid, + call, + } + } + + /// New unsigned test `TextXt`. + pub fn new_unsigned(call: Call) -> Self { + DoughnutTestXt { + signature: None, + validity: TestValidity::Valid, + call, + } + } + + /// Build invalid variant of `TestXt`. + pub fn invalid(mut self, err: TransactionValidityError) -> Self { + self.validity = TestValidity::OtherInvalid(err); + self + } + + /// Build badly signed variant of `TestXt`. + pub fn badly_signed(mut self) -> Self { + self.validity = TestValidity::SignatureInvalid(TransactionValidityError::Invalid(InvalidTransaction::BadProof)); + self + } +} + +parity_util_mem::malloc_size_of_is_0!(any: DoughnutTestXt); + +impl Serialize for DoughnutTestXt where DoughnutTestXt: Encode { + fn serialize(&self, seq: S) -> Result where S: Serializer { + self.using_encoded(|bytes| seq.serialize_bytes(bytes)) + } +} + +impl Debug for DoughnutTestXt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TestXt({:?}, {}, ...)", + self.signature.as_ref().map(|x| &x.0), + if let TestValidity::Valid = self.validity { "valid" } else { "invalid" } + ) + } +} + +impl Checkable for DoughnutTestXt { + type Checked = Self; + fn check(self, signature: CheckSignature, _: &Context) -> Result { + match self.validity { + TestValidity::Valid => Ok(self), + TestValidity::SignatureInvalid(e) => + if let CheckSignature::No = signature { + Ok(self) + } else { + Err(e) + }, + TestValidity::OtherInvalid(e) => Err(e), + } + } +} + +impl traits::Extrinsic for DoughnutTestXt { + type Call = Call; + type SignaturePayload = (AccountId, Extra); + + fn is_signed(&self) -> Option { + Some(self.signature.is_some()) + } + + fn new(call: Call, signature: Option) -> Option { + Some(DoughnutTestXt { signature, call, validity: TestValidity::Valid }) + } +} + +impl Applyable for DoughnutTestXt where + AccountId: 'static + Send + Sync + Clone + Eq + Codec + Debug + MaybeDisplay + AsRef<[u8]>, + Call: 'static + Sized + Send + Sync + Clone + Eq + Codec + Debug + Dispatchable, + Doughnut: 'static + Sized + Send + Sync + Clone + Eq + Codec + Debug + PlugDoughnutApi, + Extra: SignedExtension + MaybeDoughnut, + Origin: From<(Option,Option)>, + Info: Clone, +{ + type AccountId = AccountId; + type Call = Call; + type DispatchInfo = Info; + + fn sender(&self) -> Option<&Self::AccountId> { self.signature.as_ref().map(|x| &x.0) } + + /// Checks to see if this is a valid *transaction*. It returns information on it if so. + fn validate>( + &self, + _info: Self::DispatchInfo, + _len: usize, + ) -> TransactionValidity { + Ok(Default::default()) + } + + /// Executes all necessary logic needed prior to dispatch and deconstructs into function call, + /// index and sender. + fn apply>( + self, + info: Self::DispatchInfo, + len: usize, + ) -> ApplyExtrinsicResult { + // NOTE: This is lifted directly from the implemenation for `CheckedExtrinsic::apply()`, it handles + // switching origin for delegated calls + let (pre, res) = if let Some((id, extra)) = self.signature { + let pre = Extra::pre_dispatch(&extra, &id, &self.call, info.clone(), len)?; + if let Some(doughnut) = extra.doughnut() { + // A delegated transaction + (pre, self.call.dispatch(Origin::from((Some(doughnut.issuer()), Some(doughnut))))) + } else { + // An ordinary signed transaction + (pre, self.call.dispatch(Origin::from((Some(id), None)))) + } + } else { + // An inherent unsiged transaction + let pre = Extra::pre_dispatch_unsigned(&self.call, info.clone(), len)?; + U::pre_dispatch(&self.call)?; + (pre, self.call.dispatch(Origin::from((None, None)))) + }; + + Extra::post_dispatch(pre, info, len); + Ok(res.map_err(Into::into)) + } +} + +pub mod doughnut { + //! + //! Doughnut aware types for extrinsic tests + //! + use super::*; + use crate::traits::PlugDoughnutApi; + + /// A test account ID. Stores a `u64` as a byte array + /// Gives more functionality than a raw `u64` for testing with Doughnuts + #[derive(PartialEq, Eq, Clone, Debug, Decode, Encode, PartialOrd, Serialize, Deserialize, Default, Ord)] + pub struct TestAccountId(pub [u8; 8]); + + impl TestAccountId { + /// Create a new TestAccountId + pub fn new(id: u64) -> Self { + TestAccountId(id.to_le_bytes()) + } + } + + impl From for TestAccountId { + fn from(val: u64) -> Self { + TestAccountId::new(val) + } + } + + impl From<[u8; 32]> for TestAccountId { + fn from(val: [u8; 32]) -> Self { + let mut buf: [u8; 8] = Default::default(); + buf.copy_from_slice(&val[0..8]); + TestAccountId(buf) + } + } + + impl AsRef<[u8]> for TestAccountId { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } + } + + impl Into<[u8; 32]> for TestAccountId { + fn into(self) -> [u8; 32] { + let mut buf: [u8; 32] = Default::default(); + for (i, b) in self.0.iter().enumerate() { + buf[i] = *b + } + buf + } + } + + impl fmt::Display for TestAccountId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TestAccountId({:?})", self.0) + } + } + + /// A test doughnut + #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] + pub struct TestDoughnut { + /// The issuer ID + pub issuer: TestAccountId, + /// The holder ID + pub holder: TestAccountId, + } + + impl fmt::Display for TestDoughnut { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TestDoughnut(issuer: {:?}, holder: {:?})", self.issuer, self.holder) + } + } + + impl PlugDoughnutApi for TestDoughnut { + type PublicKey = TestAccountId; + type Signature = [u8; 64]; + type Timestamp = u32; + fn holder(&self) -> Self::PublicKey { self.holder.clone() } + fn issuer(&self) -> Self::PublicKey { self.issuer.clone() } + fn expiry(&self) -> Self::Timestamp { u32::max_value() } + fn not_before(&self) -> Self::Timestamp { 0 } + fn payload(&self) -> Vec { Default::default() } + fn signature(&self) -> Self::Signature { [0u8; 64] } + fn signature_version(&self) -> u8 { 0 } + fn get_domain(&self, _domain: &str) -> Option<&[u8]> { None } + } +} diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index fef20c826a..8f1420e714 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -26,6 +26,7 @@ use std::str::FromStr; #[cfg(feature = "std")] use serde::{Serialize, Deserialize, de::DeserializeOwned}; use sp_core::{self, Hasher, Blake2Hasher, TypeId, RuntimeDebug}; +use crate::BenchmarkParameter; use crate::codec::{Codec, Encode, Decode}; use crate::transaction_validity::{ ValidTransaction, TransactionValidity, TransactionValidityError, UnknownTransaction, @@ -38,6 +39,98 @@ pub use sp_arithmetic::traits::{ }; use sp_application_crypto::AppKey; use impl_trait_for_tuples::impl_for_tuples; +pub use doughnut::{ + error::{VerifyError, ValidationError}, + traits::{DoughnutApi, DoughnutVerify}, +}; + +/// A version agnostic API trait to expose a doughnut's underlying data. +/// It requires that associated types implement certain conversion traits in order +/// to provide a default validation implementation. +pub trait PlugDoughnutApi { + /// The holder and issuer public key type + type PublicKey: PartialEq + AsRef<[u8]>; + /// The expiry timestamp type + type Timestamp: PartialOrd + TryInto; + /// The signature type + type Signature; + /// Return the doughnut holder + fn holder(&self) -> Self::PublicKey; + /// Return the doughnut issuer + fn issuer(&self) -> Self::PublicKey; + /// Return the doughnut expiry timestamp + fn expiry(&self) -> Self::Timestamp; + /// Return the doughnut 'not before' timestamp + fn not_before(&self) -> Self::Timestamp; + /// Return the doughnut payload bytes + fn payload(&self) -> Vec; + /// Return the doughnut signature + fn signature(&self) -> Self::Signature; + /// Return the doughnut signature version + fn signature_version(&self) -> u8; + /// Return the payload for domain, if it exists in the doughnut + fn get_domain(&self, domain: &str) -> Option<&[u8]>; + /// Validate the doughnut is usable by a public key (`who`) at the current timestamp (`not_before` <= `now` <= `expiry`) + fn validate(&self, who: Q, now: R) -> Result<(), ValidationError> + where + Q: AsRef<[u8]>, + R: TryInto, + { + if who.as_ref() != self.holder().as_ref() { + return Err(ValidationError::HolderIdentityMismatched); + } + let now_ = now.try_into().map_err(|_| ValidationError::Conversion)?; + if now_ + < self + .not_before() + .try_into() + .map_err(|_| ValidationError::Conversion)? + { + return Err(ValidationError::Premature); + } + if now_ + >= self + .expiry() + .try_into() + .map_err(|_| ValidationError::Conversion)? + { + return Err(ValidationError::Expired); + } + Ok(()) + } +} + +// Dummy implementation for unit type +impl PlugDoughnutApi for () { + type PublicKey = [u8; 32]; + type Timestamp = u32; + type Signature = (); + fn holder(&self) -> Self::PublicKey { + Default::default() + } + fn issuer(&self) -> Self::PublicKey { + Default::default() + } + fn expiry(&self) -> Self::Timestamp { + 0 + } + fn not_before(&self) -> Self::Timestamp { + 0 + } + fn payload(&self) -> Vec { + Vec::default() + } + fn signature(&self) -> Self::Signature {} + fn signature_version(&self) -> u8 { + 255 + } + fn get_domain(&self, _domain: &str) -> Option<&[u8]> { + None + } + // fn validate, R: TryInto>(&self, who: Q, now: R) -> Result<(), ValidationError> { + // Ok(()) + // } +} /// A lazy value. pub trait Lazy { @@ -732,7 +825,7 @@ pub trait SignedExtension: Codec + Debug + Sync + Send + Clone + Eq + PartialEq /// If you ever override this function, you need to make sure to always /// perform the same validation as in `validate`. fn pre_dispatch( - self, + &self, who: &Self::AccountId, call: &Self::Call, info: Self::DispatchInfo, @@ -818,7 +911,7 @@ impl SignedExtension for Tuple { Ok(valid) } - fn pre_dispatch(self, who: &Self::AccountId, call: &Self::Call, info: Self::DispatchInfo, len: usize) + fn pre_dispatch(&self, who: &Self::AccountId, call: &Self::Call, info: Self::DispatchInfo, len: usize) -> Result { Ok(for_tuples!( ( #( Tuple.pre_dispatch(who, call, info.clone(), len)? ),* ) )) @@ -857,6 +950,39 @@ impl SignedExtension for Tuple { } } +/// This trait allows a doughnut value to be deconstructed from an extrinsic's `SignedExtension` payload. +/// This is not possible with the `SignedExtension` trait alone, since the fields are indistinguishable +/// from each other and are only decoded in pre-set hooks (`pre_dispatch`, `validate`, etc.), where as the doughnut is +/// required outside these hooks, such as `Applyable::dispatch`. +pub trait MaybeDoughnut { + /// The extension doughnut type + type Doughnut: Send + Sync + PlugDoughnutApi; + /// Return the doughnut from the `SignedExtension` payload, if any + fn doughnut(self) -> Option; +} + +// Blanket impl for `Option` +impl SignedExtension for Option +where + T: SignedExtension, + Info: Clone, +{ + type AccountId = AccountId; + type AdditionalSigned = (); + type Call = T::Call; + type DispatchInfo = Info; + type Pre = T::Pre; + const IDENTIFIER: &'static str = "OptionSignedExtension"; + + fn additional_signed(&self) -> Result { Ok(()) } + fn validate(&self, who: &Self::AccountId, call: &Self::Call, info: Self::DispatchInfo, len: usize) -> Result { + if let Some(inner) = self { + return inner.validate(who, call, info, len) + } + Ok(ValidTransaction::default()) + } +} + /// Only for bare bone testing when you don't care about signed extensions at all. #[cfg(feature = "std")] impl SignedExtension for () { @@ -1295,6 +1421,39 @@ impl Printable for Tuple { } } +// Implement `MaybeDoughnut` for every tuple that starts with an `(Option, ...)` +// This is targeted at the `SignecExtra` extrinsic tuple, allowing it's doughnut to be extracted. +macro_rules! tuple_impl_indexed { + ($first:ident, $($rest:ident,)+ ; $first_index:tt, $($rest_index:tt,)+) => { + tuple_impl_indexed!([$first] [$($rest)+] ; [$first_index,] [$($rest_index,)+]); + }; + ([$($direct:ident)+] ; [$($index:tt,)+]) => { + impl< + AccountId, + Doughnut: SignedExtension + PlugDoughnutApi, + $($direct: SignedExtension),+ + > MaybeDoughnut for (Option, $($direct),+,) { + type Doughnut = Doughnut; + fn doughnut(self) -> Option { + self.0 + } + } + }; + ([$($direct:ident)+] [] ; [$($index:tt,)+] []) => { + tuple_impl_indexed!([$($direct)+] ; [$($index,)+]); + }; + ( + [$($direct:ident)+] [$first:ident $($rest:ident)*] + ; + [$($index:tt,)+] [$first_index:tt, $($rest_index:tt,)*] + ) => { + tuple_impl_indexed!([$($direct)+] ; [$($index,)+]); + tuple_impl_indexed!([$($direct)+ $first] [$($rest)*] ; [$($index,)+ $first_index,] [$($rest_index,)*]); + }; +} + +#[allow(non_snake_case)] +tuple_impl_indexed!(A, B, C, D, E, F, G, H, I, J, ; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,); /// Something that can convert a [`BlockId`] to a number or a hash. #[cfg(feature = "std")] pub trait BlockIdTo { @@ -1314,6 +1473,75 @@ pub trait BlockIdTo { ) -> Result>, Self::Error>; } +/// The pallet benchmarking trait. +pub trait Benchmarking { + /// Run the benchmarks for this pallet. + /// + /// Parameters + /// - `extrinsic`: The name of extrinsic function you want to benchmark encoded as bytes. + /// - `steps`: The number of sample points you want to take across the range of parameters. + /// - `repeat`: The number of times you want to repeat a benchmark. + fn run_benchmark(extrinsic: Vec, steps: u32, repeat: u32) -> Result, &'static str>; +} + +/// The required setup for creating a benchmark. +pub trait BenchmarkingSetup { + /// Return the components and their ranges which should be tested in this benchmark. + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>; + + /// Set up the storage, and prepare a call and caller to test in a single run of the benchmark. + fn instance(&self, components: &[(BenchmarkParameter, u32)]) -> Result<(Call, RawOrigin), &'static str>; +} + +/// Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. +/// +/// Every variant must implement [`BenchmarkingSetup`](crate::traits::BenchmarkingSetup). +/// +/// ```nocompile +/// +/// struct Transfer; +/// impl BenchmarkingSetup for Transfer { ... } +/// +/// struct SetBalance; +/// impl BenchmarkingSetup for SetBalance { ... } +/// +/// selected_benchmark!(Transfer, SetBalance); +/// ``` +#[macro_export] +macro_rules! selected_benchmark { + ($($bench:ident),*) => { + // The list of available benchmarks for this pallet. + enum SelectedBenchmark { + $( $bench, )* + } + + // Allow us to select a benchmark from the list of available benchmarks. + impl $crate::traits::BenchmarkingSetup, RawOrigin> for SelectedBenchmark { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + match self { + $( Self::$bench => <$bench as $crate::traits::BenchmarkingSetup< + T, + Call, + RawOrigin, + >>::components(&$bench), )* + } + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(Call, RawOrigin), &'static str> + { + match self { + $( Self::$bench => <$bench as $crate::traits::BenchmarkingSetup< + T, + Call, + RawOrigin, + >>::instance(&$bench, components), )* + } + } + } + }; +} + #[cfg(test)] mod tests { use super::*; diff --git a/prml/attestation/Cargo.toml b/prml/attestation/Cargo.toml new file mode 100644 index 0000000000..8fd8f21f28 --- /dev/null +++ b/prml/attestation/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "prml-attestation" +version = "2.0.0" +authors = ["Centrality Developers "] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +sp-core = { default-features = false, path = "../../primitives/core" } +frame-support = { default-features = false, path = "../../frame/support" } +frame-system = { default-features = false, path = "../../frame/system" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-core/std", + "frame-support/std", + "frame-system/std", +] diff --git a/prml/attestation/src/lib.rs b/prml/attestation/src/lib.rs new file mode 100644 index 0000000000..f07d318d5b --- /dev/null +++ b/prml/attestation/src/lib.rs @@ -0,0 +1,136 @@ +// Copyright 2019 Plug New Zealand Limited +// +// 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. + +//! # Attestation Module +//! +//! The Attestation module provides functionality for entities to create attestation claims about one another. +//! +//! This module borrows heavily from ERC 780 https://github.com/ethereum/EIPs/issues/780 +//! +//! ## Terminology +//! +//! Issuer: the entity creating the claim +//! Holder: the entity that the claim is about +//! Topic: the topic which the claim is about ie isOver18 +//! Value: any value pertaining to the claim +//! +//! ## Usage +//! +//! Topic and Value are U256 integers. This means that Topic and Value can technically store any value that can be represented in 256 bits. +//! +//! The user of the module must convert whatever value that they would like to store into a value that can be stored as a U256. +//! +//! It is recommended that Topic be a string value converted to hex and stored on the blockchain as a U256. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_core::uint::U256; +use frame_support::sp_std::prelude::*; +use frame_support::{decl_event, decl_module, decl_storage, dispatch::DispatchResult}; +use frame_system::ensure_signed; + +pub trait Trait: frame_system::Trait { + type Event: From> + Into<::Event>; +} + +type AttestationTopic = U256; +type AttestationValue = U256; + +decl_module! { + pub struct Module for enum Call where origin: T::Origin, system = frame_system { + fn deposit_event() = default; + + /// Create a new claim + pub fn set_claim(origin, holder: T::AccountId, topic: AttestationTopic, value: AttestationValue) -> DispatchResult { + let issuer = ensure_signed(origin)?; + + Self::create_claim(holder, issuer, topic, value)?; + Ok(()) + } + + /// Create a new claim where the holder and issuer are the same person + pub fn set_self_claim(origin, topic: AttestationTopic, value: AttestationValue) -> DispatchResult { + let holder_and_issuer = ensure_signed(origin)?; + + Self::create_claim(holder_and_issuer.clone(), holder_and_issuer, topic, value)?; + Ok(()) + } + + /// Remove a claim, only the original issuer can remove a claim + pub fn remove_claim(origin, holder: T::AccountId, topic: AttestationTopic) -> DispatchResult { + let issuer = ensure_signed(origin)?; + >::mutate(&holder,|issuers| issuers.retain(|vec_issuer| *vec_issuer != issuer)); + >::mutate((holder.clone(), issuer.clone()),|topics| topics.retain(|vec_topic| *vec_topic != topic)); + >::remove((holder.clone(), issuer.clone(), topic)); + + Self::deposit_event(RawEvent::ClaimRemoved(holder, issuer, topic)); + + Ok(()) + } + } +} + +decl_event!( + pub enum Event where ::AccountId { + ClaimSet(AccountId, AccountId, AttestationTopic, AttestationValue), + ClaimRemoved(AccountId, AccountId, AttestationTopic), + } +); + +decl_storage! { + trait Store for Module as Attestation { + /// The maps are layed out to support the nested structure shown below in JSON, will look to optimise later. + /// + /// { + /// holder: { + /// issuer: { + /// topic: + /// } + /// } + /// } + /// + + /// A map of HolderId => Vec + Issuers: map hasher(blake2_256) T::AccountId => Vec; + /// A map of (HolderId, IssuerId) => Vec + Topics: map hasher(blake2_256) (T::AccountId, T::AccountId) => Vec; + /// A map of (HolderId, IssuerId, AttestationTopic) => AttestationValue + Values: map hasher(blake2_256) (T::AccountId, T::AccountId, AttestationTopic) => AttestationValue; + } +} + +impl Module { + fn create_claim( + holder: T::AccountId, + issuer: T::AccountId, + topic: AttestationTopic, + value: AttestationValue, + ) -> DispatchResult { + >::mutate(&holder, |issuers| { + if !issuers.contains(&issuer) { + issuers.push(issuer.clone()) + } + }); + + >::mutate((holder.clone(), issuer.clone()), |topics| { + if !topics.contains(&topic) { + topics.push(topic) + } + }); + + >::insert((holder.clone(), issuer.clone(), topic), value); + Self::deposit_event(RawEvent::ClaimSet(holder, issuer, topic, value)); + Ok(()) + } +} diff --git a/prml/doughnut/Cargo.toml b/prml/doughnut/Cargo.toml new file mode 100644 index 0000000000..db0dbd16c0 --- /dev/null +++ b/prml/doughnut/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "prml-doughnut" +version = "2.0.0" +authors = ["Centrality Developers "] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true } +sp-core = { default-features = false, path = "../../primitives/core" } +sp-std = { default-features = false, path = "../../primitives/std" } +sp-runtime = { path = "../../primitives/runtime", default-features = false } +frame-support = { default-features = false, path = "../../frame/support" } + +[dev-dependencies] +sp-io ={ default-features = false, path = "../../primitives/io" } +sp-keyring = { default-features = false, path = "../../primitives/keyring" } + +[features] +default = ["std"] +std = [ + "codec/std", + "serde", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "frame-support/std", +] diff --git a/prml/doughnut/src/constants.rs b/prml/doughnut/src/constants.rs new file mode 100644 index 0000000000..cfab217ac3 --- /dev/null +++ b/prml/doughnut/src/constants.rs @@ -0,0 +1,29 @@ +// Copyright 2020 Plug New Zealand Limited +// This file is part of Plug. + +// Plug 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. + +// Plug 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 Plug. If not, see . + +//! Plug Doughnut Constants + +pub (crate) mod error_code { + //! Plug Doughnut Error Code Constants + pub const VERIFY_INVALID: u8 = 170; + pub const VERIFY_UNSUPPORTED_VERSION: u8 = 171; + pub const VERIFY_BAD_SIGNATURE_FORMAT: u8 = 172; + pub const VERIFY_BAD_PUBLIC_KEY_FORMAT: u8 = 173; + pub const VALIDATION_HOLDER_SIGNER_IDENTITY_MISMATCH: u8 = 180; + pub const VALIDATION_EXPIRED: u8 = 181; + pub const VALIDATION_PREMATURE: u8 = 182; + pub const VALIDATION_CONVERSION: u8 = 183; +} \ No newline at end of file diff --git a/prml/doughnut/src/impls.rs b/prml/doughnut/src/impls.rs new file mode 100644 index 0000000000..15893731ac --- /dev/null +++ b/prml/doughnut/src/impls.rs @@ -0,0 +1,481 @@ +// Copyright 2019-2020 Plug New Zealand Limited +// This file is part of Plug. + +// Plug 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. + +// Plug 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 Plug. If not, see . + +use crate::{DoughnutRuntime, PlugDoughnut, constants::error_code}; +use sp_core::{ + ed25519::{self}, + sr25519::{self}, +}; +use sp_std::{self, convert::{TryFrom}, prelude::*}; +use sp_runtime::{Doughnut}; +use sp_runtime::traits::{PlugDoughnutApi, DoughnutApi, DoughnutVerify, SignedExtension, ValidationError, Verify, VerifyError}; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransaction}; +use frame_support::{ + dispatch::DispatchInfo, + traits::Time, +}; + +// Proxy calls to the inner Doughnut type and provide Runtime type conversions where required. +impl PlugDoughnutApi for PlugDoughnut +where + Runtime: DoughnutRuntime, + Runtime::AccountId: AsRef<[u8]> + From<[u8; 32]>, +{ + type PublicKey = Runtime::AccountId; + type Signature = [u8; 64]; + type Timestamp = u32; + + fn holder(&self) -> Self::PublicKey { + match &self.0 { + Doughnut::V0(v0) => v0.holder().into() + } + } + fn issuer(&self) -> Self::PublicKey { + match &self.0 { + Doughnut::V0(v0) => v0.issuer().into() + } + } + fn not_before(&self) -> Self::Timestamp { + match &self.0 { + Doughnut::V0(v0) => v0.not_before().into() + } + } + fn expiry(&self) -> Self::Timestamp { + match &self.0 { + Doughnut::V0(v0) => v0.expiry().into() + } + } + fn signature(&self) -> Self::Signature { + match &self.0 { + Doughnut::V0(v0) => v0.signature().into() + } + } + fn signature_version(&self) -> u8 { + match &self.0 { + Doughnut::V0(v0) => v0.signature_version() + } + } + fn payload(&self) -> Vec { + match &self.0 { + Doughnut::V0(v0) => v0.payload() + } + } + fn get_domain(&self, domain: &str) -> Option<&[u8]> { + match &self.0 { + Doughnut::V0(v0) => v0.get_domain(domain) + } + } +} + +// Re-implemented here due to sr25519 verification requiring an external +// wasm VM call when using `no std` +impl DoughnutVerify for PlugDoughnut +where + Runtime: DoughnutRuntime, + Runtime::AccountId: AsRef<[u8]> + From<[u8; 32]>, +{ + /// Verify the doughnut signature. Returns `true` on success, false otherwise + fn verify(&self) -> Result<(), VerifyError> { + // TODO: This is starting to look like `MultiSignature`, maybe worth refactoring + match self.signature_version() { + // sr25519 + 0 => { + let signature = sr25519::Signature(self.signature()); + let issuer = sr25519::Public::try_from(self.issuer().as_ref()) + .map_err(|_| VerifyError::Invalid)?; + match signature.verify(&self.payload()[..], &issuer) { + true => Ok(()), + false => Err(VerifyError::Invalid), + } + }, + // ed25519 + 1 => { + let signature = ed25519::Signature(self.signature()); + let issuer = ed25519::Public::try_from(self.issuer().as_ref()) + .map_err(|_| VerifyError::Invalid)?; + match signature.verify(&self.payload()[..], &issuer) { + true => Ok(()), + false => Err(VerifyError::Invalid), + } + }, + // signature version unsupported. + _ => Err(VerifyError::UnsupportedVersion), + } + } +} + +impl SignedExtension for PlugDoughnut +where + Runtime: DoughnutRuntime + Eq + Clone + Send + Sync, + Runtime::AccountId: AsRef<[u8]> + From<[u8; 32]>, +{ + type AccountId = Runtime::AccountId; + type AdditionalSigned = (); + type Call = Runtime::Call; + type DispatchInfo = DispatchInfo; + type Pre = (); + const IDENTIFIER: &'static str = "PlugDoughnutSignedExtension"; + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } + fn validate(&self, who: &Self::AccountId, _call: &Self::Call, _info: Self::DispatchInfo, _len: usize) -> Result + { + // Check doughnut signature verifies + if let Err(err) = self.verify() { + let code = match err { + VerifyError::Invalid => error_code::VERIFY_INVALID, + VerifyError::UnsupportedVersion => error_code::VERIFY_UNSUPPORTED_VERSION, + VerifyError::BadSignatureFormat => error_code::VERIFY_BAD_SIGNATURE_FORMAT, + VerifyError::BadPublicKeyFormat => error_code::VERIFY_BAD_PUBLIC_KEY_FORMAT, + }; + return Err(InvalidTransaction::Custom(code).into()) + } + // Convert chain reported timestamp from milliseconds into seconds as per doughnut timestamp spec. + let now = Runtime::TimestampProvider::now() / 1000_u32.into(); + // Check doughnut is valid for use by `who` at the current timestamp + if let Err(err) = PlugDoughnutApi::validate(self, who, now) { + let code = match err { + ValidationError::HolderIdentityMismatched => error_code::VALIDATION_HOLDER_SIGNER_IDENTITY_MISMATCH, + ValidationError::Expired => error_code::VALIDATION_EXPIRED, + ValidationError::Premature => error_code::VALIDATION_PREMATURE, + ValidationError::Conversion => error_code::VALIDATION_CONVERSION, + }; + return Err(InvalidTransaction::Custom(code).into()) + } + Ok(ValidTransaction::default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::crypto::Pair; + use sp_io::TestExternalities; + use sp_keyring::{AccountKeyring, Ed25519Keyring}; + use sp_runtime::{DoughnutV0, Doughnut, MultiSignature, traits::IdentifyAccount}; + + type Signature = MultiSignature; + type AccountId = <::Signer as IdentifyAccount>::AccountId; + + #[derive(Clone, Eq, PartialEq)] + pub struct Runtime; + + pub struct FixedTimestampProvider; + impl Time for FixedTimestampProvider { + type Moment = u64; + // Return a constant timestamp (ms) + fn now() -> Self::Moment { + 50_000 + } + } + + impl DoughnutRuntime for Runtime { + type AccountId = AccountId; + type Call = (); + type Doughnut = PlugDoughnut; + type TimestampProvider = FixedTimestampProvider; + } + + #[test] + fn plug_doughnut_validates() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 3000, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut.signature = issuer.pair().sign(&doughnut.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut)); + assert!( + as SignedExtension>::validate( + &plug_doughnut, + &holder.to_account_id(), // who + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ).is_ok() + ); + } + + #[test] + fn plug_doughnut_does_not_validate_premature() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 3000, + not_before: 51, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut.signature = issuer.pair().sign(&doughnut.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut)); + // premature + assert_eq!( + as SignedExtension>::validate( + &plug_doughnut, + &holder.to_account_id(), // who + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ), + Err(InvalidTransaction::Custom(error_code::VALIDATION_PREMATURE).into()) + ); + } + + #[test] + fn plug_doughnut_does_not_validate_expired() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 49, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut.signature = issuer.pair().sign(&doughnut.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut)); + // expired + assert_eq!( + as SignedExtension>::validate( + &plug_doughnut, + &holder.to_account_id(), + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ), + Err(InvalidTransaction::Custom(error_code::VALIDATION_EXPIRED).into()) + ); + } + + #[test] + fn plug_doughnut_does_not_validate_bad_holder() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 3000, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut.signature = issuer.pair().sign(&doughnut.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut)); + // Charlie is not the holder + assert_eq!( + as SignedExtension>::validate( + &plug_doughnut, + &AccountKeyring::Charlie.to_account_id(), // who + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ), + Err(InvalidTransaction::Custom(error_code::VALIDATION_HOLDER_SIGNER_IDENTITY_MISMATCH).into()) + ); + } + + #[test] + fn plug_doughnut_verifies_sr25519_signature() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut_v0 = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut_v0.signature = issuer.pair().sign(&doughnut_v0.payload()).into(); + + let doughnut = Doughnut::V0(doughnut_v0); + let plug_doughnut = PlugDoughnut::::new(doughnut); + assert!(plug_doughnut.verify().is_ok()); + } + + #[test] + fn plug_doughnut_does_not_verify_sr25519_signature() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut_v0 = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut_v0.signature = holder.sign(&doughnut_v0.payload()).into(); + + let doughnut = Doughnut::V0(doughnut_v0); + let plug_doughnut = PlugDoughnut::::new(doughnut); + assert_eq!(plug_doughnut.verify(), Err(VerifyError::Invalid)); + } + + #[test] + fn plug_doughnut_verifies_ed25519_signature() { + let (issuer, holder) = (Ed25519Keyring::Alice, Ed25519Keyring::Bob); + let mut doughnut_v0 = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 1, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut_v0.signature = issuer.pair().sign(&doughnut_v0.payload()).into(); + + let doughnut = Doughnut::V0(doughnut_v0); + let plug_doughnut = PlugDoughnut::::new(doughnut); + + // Externalities is required for ed25519 signature verification + TestExternalities::default().execute_with(|| { + assert!(plug_doughnut.verify().is_ok()); + }); + } + + #[test] + fn plug_doughnut_does_not_verify_ed25519_signature() { + let (issuer, holder) = (Ed25519Keyring::Alice, Ed25519Keyring::Bob); + let mut doughnut_v0 = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 1, + domains: vec![("test".to_string(), vec![0u8])], + }; + // !holder signs the doughnuts + doughnut_v0.signature = holder.sign(&doughnut_v0.payload()).into(); + + let doughnut = Doughnut::V0(doughnut_v0); + let plug_doughnut = PlugDoughnut::::new(doughnut); + + // Externalities is required for ed25519 signature verification + TestExternalities::default().execute_with(|| { + assert_eq!(plug_doughnut.verify(), Err(VerifyError::Invalid)); + }); + } + + #[test] + fn plug_doughnut_does_not_verify_unknown_signature_version() { + let (issuer, holder) = (Ed25519Keyring::Alice, Ed25519Keyring::Bob); + let mut doughnut_v0 = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 200, + domains: vec![("test".to_string(), vec![0u8])], + }; + doughnut_v0.signature = issuer.pair().sign(&doughnut_v0.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut_v0)); + assert_eq!( + as SignedExtension>::validate( + &plug_doughnut, + &holder.to_account_id(), // who + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ), + Err(InvalidTransaction::Custom(error_code::VERIFY_UNSUPPORTED_VERSION).into()) + ); + } + + #[test] + fn plug_doughnut_proxies_to_inner_doughnut() { + let issuer = [0u8; 32]; + let holder = [1u8; 32]; + let expiry = 55555; + let not_before = 123; + let signature = [1u8; 64]; + let signature_version = 1; + + let doughnut_v0 = DoughnutV0 { + issuer, + holder, + expiry, + not_before, + payload_version: 0, + signature: signature.into(), + signature_version, + domains: vec![("test".to_string(), vec![0u8])], + }; + + let doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut_v0.clone())); + + assert_eq!(Into::<[u8; 32]>::into(doughnut.issuer()), issuer); + assert_eq!(Into::<[u8; 32]>::into(doughnut.holder()), holder); + assert_eq!(doughnut.expiry(), expiry); + assert_eq!(doughnut.not_before(), not_before); + assert_eq!(doughnut.signature_version(), signature_version); + assert_eq!(&doughnut.signature()[..], &signature[..]); + assert_eq!(doughnut.payload(), doughnut_v0.payload()); + } + + #[test] + fn plug_doughnut_does_not_verify_invalid_signature() { + let (issuer, holder) = (AccountKeyring::Alice, AccountKeyring::Bob); + let mut doughnut = DoughnutV0 { + issuer: issuer.to_raw_public(), + holder: holder.to_raw_public(), + expiry: 0, + not_before: 0, + payload_version: 0, + signature: [1u8; 64].into(), + signature_version: 0, + domains: vec![("test".to_string(), vec![0u8])], + }; + // Signed by holder! + doughnut.signature = holder.pair().sign(&doughnut.payload()).into(); + + let plug_doughnut = PlugDoughnut::::new(Doughnut::V0(doughnut)); + + assert_eq!( + as SignedExtension>::validate( + &plug_doughnut, + &holder.to_account_id(), // who + &(), // Call + Default::default(), // DispatchInfo + 0usize // len + ), + Err(InvalidTransaction::Custom(error_code::VERIFY_INVALID).into()) + ); + } +} diff --git a/prml/doughnut/src/lib.rs b/prml/doughnut/src/lib.rs new file mode 100644 index 0000000000..3812927da6 --- /dev/null +++ b/prml/doughnut/src/lib.rs @@ -0,0 +1,81 @@ +// Copyright 2019-2020 Plug New Zealand Limited +// This file is part of Plug. + +// Plug 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. + +// Plug 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 Plug. If not, see . + + +//! A collection of doughnut traits and srtucts which provide doughnut integartion for a plug runtime. +//! This includes validation and signature verification and type conversions. +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Encode, Decode}; +use sp_std::{self}; +use sp_runtime::{Doughnut}; +use sp_runtime::traits::{PlugDoughnutApi, Member}; +use frame_support::Parameter; +use frame_support::additional_traits::DelegatedDispatchVerifier; +use frame_support::traits::Time; + +mod constants; +mod impls; + +// TODO: This should eventually become a super trait for `system::Trait` so that all doughnut functionality may be moved here +/// A runtime which supports doughnut verification and validation +pub trait DoughnutRuntime { + type AccountId: Member + Parameter; + type Call; + type Doughnut: Member + Parameter + PlugDoughnutApi; + type TimestampProvider: Time; +} + +/// A doughnut wrapped for compatibility with the extrinsic transport layer and the plug runtime types. +/// It can be passed to the runtime as a `SignedExtension` in an extrinsic. +#[derive(Encode, Decode, Clone, Eq, PartialEq)] +pub struct PlugDoughnut(Doughnut, sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for PlugDoughnut +where + Runtime: DoughnutRuntime + Send + Sync, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + self.0.encode().fmt(f) + } +} + +impl PlugDoughnut +where + Runtime: DoughnutRuntime, +{ + /// Create a new PlugDoughnut + pub fn new(doughnut: Doughnut) -> Self { + Self(doughnut, sp_std::marker::PhantomData) + } +} + +/// It verifies that a doughnut allows execution of a module+method combination +pub struct PlugDoughnutDispatcher(sp_std::marker::PhantomData); + +impl DelegatedDispatchVerifier for PlugDoughnutDispatcher { + type Doughnut = Runtime::Doughnut; + type AccountId = Runtime::AccountId; + const DOMAIN: &'static str = "plug"; + /// Verify a Doughnut proof authorizes method dispatch given some input parameters + fn verify_dispatch( + _doughnut: &Runtime::Doughnut, + _module: &str, + _method: &str, + ) -> Result<(), &'static str> { + Err("Doughnut dispatch verification is not implemented for this domain") + } +} diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 55e153103d..c222de5362 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -372,6 +372,8 @@ impl frame_system::Trait for Runtime { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); + type Doughnut = (); + type DelegatedDispatchVerifier = (); type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); diff --git a/utils/grafana-data-source/test/Cargo.toml b/utils/grafana-data-source/test/Cargo.toml new file mode 100644 index 0000000000..18c080c8d1 --- /dev/null +++ b/utils/grafana-data-source/test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +description = "Grafana data source server test" +name = "grafana-data-source-test" +version = "2.0.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +grafana-data-source = { version = "0.8", path = ".." } +futures = "0.3" +futures-timer = "3.0.1" +rand = "0.7"