diff --git a/Cargo.toml b/Cargo.toml index b29becb5d..b1993804f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,44 +1,57 @@ cargo-features = ["resolver"] [workspace] -resolver = "2" - members = [ - # Primary modules - 'node', - 'runtime', - "runtime/common", - 'primitives', + "blockchain/node", + "blockchain/node/cli", + "blockchain/node/service", + + "blockchain/modules/*", + "blockchain/modules/currencies/runtime-api", + "blockchain/modules/evm-utility/macro", + "blockchain/primitives", + "blockchain/rpc", - # SERML Modules - "modules/airdrop", - "modules/currencies", - "modules//evm", - "modules//evm/rpc", - "modules//evm/rpc/runtime_api", - "modules//evm-accounts", - "modules//evm-bridge", - "modules//evm-manager", - "modules/idle-scheduler", - "modules/nft", - "modules/prices", - "modules/transaction-pause", - "modules/transaction-payment", - "modules/vesting", + "blockchainruntime/common", + "blockchainruntime/qingdao", + "blockchainruntime/setheum", - # ORML modules - "submodules/orml/authority", - "submodules/orml/benchmarking", - "submodules/orml/currencies", - "submodules/orml/nft", - "submodules/orml/oracle", - "submodules/orml/oracle/rpc", - "submodules/orml/oracle/rpc/runtime-api", - "submodules/orml/tokens", - "submodules/orml/traits", - "submodules/orml/utilities", + "orml/asset-registry", + "orml/auction", + "orml/authority", + "orml/benchmarking", + "orml/currencies", + "orml/gradually-update", + "orml/nft", + "orml/oracle", + "orml/oracle/runtime-api", + "orml/parameters", + "orml/payments", + "orml/rewards", + "orml/tokens", + "orml/tokens/runtime-api", + "orml/traits", + "orml/unknown-tokens", + "orml/utilities", + "orml/vesting", + "orml/xcm-support", + "orml/xcm", + "orml/xtokens", ] -exclude = ['rpc'] + +resolver = "2" + +[profile.release] +# Substrate runtime requires unwinding. +panic = 'unwind' + +[profile.dev] +split-debuginfo = "unpacked" + +[profile.production] +inherits = "release" +lto = true +codegen-units = 1 # The list of dependencies below (which can be both direct and indirect dependencies) are crates # that are suspected to be CPU-intensive, and that are unlikely to require debugging (as some of @@ -57,7 +70,6 @@ exclude = ['rpc'] # This list is ordered alphabetically. [profile.dev.package] blake2 = { opt-level = 3 } -blake2-rfc = { opt-level = 3 } blake2b_simd = { opt-level = 3 } chacha20poly1305 = { opt-level = 3 } cranelift-codegen = { opt-level = 3 } @@ -66,7 +78,7 @@ crc32fast = { opt-level = 3 } crossbeam-deque = { opt-level = 3 } crypto-mac = { opt-level = 3 } curve25519-dalek = { opt-level = 3 } -ed25519-dalek = { opt-level = 3 } +ed25519-zebra = { opt-level = 3 } flate2 = { opt-level = 3 } futures-channel = { opt-level = 3 } hashbrown = { opt-level = 3 } @@ -75,7 +87,6 @@ hmac = { opt-level = 3 } httparse = { opt-level = 3 } integer-sqrt = { opt-level = 3 } keccak = { opt-level = 3 } -libm = { opt-level = 3 } librocksdb-sys = { opt-level = 3 } libsecp256k1 = { opt-level = 3 } libz-sys = { opt-level = 3 } @@ -94,14 +105,185 @@ smallvec = { opt-level = 3 } snow = { opt-level = 3 } twox-hash = { opt-level = 3 } uint = { opt-level = 3 } -wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } +insta.opt-level = 3 +similar.opt-level = 3 -[profile.release] -# Substrate runtime requires unwinding. -panic = 'unwind' +[workspace.dependencies] +log = { version = "0.4.20", default-features = false } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.145", default-features = false } +parity-scale-codec = { version = "3.6.5", default-features = false } +serde_json = { version = "1.0.81", default-features = false } +hex = { version = "0.4", default-features = false } +hex-literal = { version = "0.4.1" } +rand_chacha = { version = "0.2", default-features = false } +env_logger = { version = "0.10.0" } +smallvec = { version = "1.4.1" } +ripemd = { version = "0.1.3", default-features = false } +rlp = { version = "0.5.2", default-features = false } +sha3 = { version = "0.10.8", default-features = false } +tiny-keccak = { version = "2.0" } +num = { version = "0.4", default-features = false } +bn = { package = "substrate-bn", version = "0.6", default-features = false } +libsecp256k1 = { version = "0.7", default-features = false } +impl-trait-for-tuples = { version = "0.2.2" } +ethereum-types = { version = "0.14.0", default-features = false } +num_enum = { version = "0.5.1", default-features = false } +quote = { version = "1.0.20" } +syn = { version = "1.0.98" } +proc-macro2 = { version = "1.0.40" } +clap = { version = "4.0.9" } +derive_more = { version = "0.99" } +bstringify = { version = "0.1.2" } +enumflags2 = { version = "0.7.7" } +paste = { version = "1.0" } +futures = { version = "0.3.28" } +jsonrpsee = { version = "0.16.2" } +static_assertions = { version = "1.1.0" } +ethabi = { version = "18.0.0", default-features = false } +async-trait = { version = "0.1.71" } +coins-bip32 = { version = "0.7.0" } +coins-bip39 = { version = "0.7.0" } +k256 = { version = "0.11.5", default-features = false } -[profile.dev] -split-debuginfo = "unpacked" +# Dependencies are split into 2 groups: WASM and Client. +# - "WASM" dependencies requires to be no_std compatible, which often requires +# `default-features = false`. When used in a client-side crate the "std" feature should be enabled +# there if it exists. +# - "Client" dependencies are only used in the client, and thus don't need to be no_std compatible. + +# ORML (WASM) +wasm-bencher = { git = "https://github.com/open-web3-stack/wasm-bencher", branch = "polkadot-v1.3.0", default-features = false } +orml-auction = { path = "orml/auction", default-features = false } +orml-authority = { path = "orml/authority", default-features = false } +orml-benchmarking = { path = "orml/benchmarking", default-features = false } +orml-currencies = { path = "orml/currencies", default-features = false } +orml-nft = { path = "orml/nft", default-features = false } +orml-oracle = { path = "orml/oracle", default-features = false } +orml-oracle-runtime-api = { path = "orml/oracle/runtime-api", default-features = false } +orml-parameters = { path = "orml/parameters", default-features = false } +orml-payments = { path = "orml/payments", default-features = false } +orml-rewards = { path = "orml/rewards", default-features = false } +orml-tokens = { path = "orml/tokens", default-features = false } +orml-tokens-runtime-api = { path = "orml/tokens/runtime-api", default-features = false } +orml-traits = { path = "orml/traits", default-features = false } +orml-unknown-tokens = { path = "orml/unknown-tokens", default-features = false } +orml-utilities = { path = "orml/utilities", default-features = false } +orml-vesting = { path = "orml/vesting", default-features = false } +orml-xcm = { path = "orml/xcm", default-features = false } +orml-xcm-support = { path = "orml/xcm-support", default-features = false } +orml-xtokens = { path = "orml/xtokens", default-features = false } + +# Setheum (WASM) +primitives = { package = "setheum-primitives", path = "blockchain/primitives", default-features = false } +runtime-common = { path = "blockchain/runtime/common", default-features = false } +qingdao-runtime = { path = "blockchain/runtime/qingdao", default-features = false } +setheum-runtime = { path = "blockchain/runtime/setheum", default-features = false } + +# Setheum & ORML (client) +setheum-cli = { path = "blockchain/node/cli" } +setheum-rpc = { path = "blockchain/rpc" } +setheum-service = { path = "blockchain/node/service", default-features = false } +module-evm-utility-macro = { path = "blockchain/modules/evm-utility/macro" } +orml-build-script-utils = { path = "orml/build-script-utils" } + +# Substrate (WASM) +frame-benchmarking = { version = "25.0.0", default-features = false } +frame-executive = { version = "25.0.0", default-features = false } +frame-support = { version = "25.0.0", default-features = false } +frame-system = { version = "25.0.0", default-features = false } +frame-system-rpc-runtime-api = { version = "23.0.0", default-features = false } +frame-try-runtime = { version = "0.31.0", default-features = false } +pallet-aura = { version = "24.0.0", default-features = false } +pallet-authority-discovery = { version = "25.0.0", default-features = false } +pallet-authorship = { version = "25.0.0", default-features = false } +pallet-balances = { version = "25.0.0", default-features = false } +pallet-bounties = { version = "24.0.0", default-features = false } +pallet-collective = { version = "25.0.0", default-features = false } +pallet-democracy = { version = "25.0.0", default-features = false } +pallet-elections-phragmen = { version = "26.0.0", default-features = false } +pallet-indices = { version = "25.0.0", default-features = false } +pallet-membership = { version = "25.0.0", default-features = false } +pallet-message-queue = { version = "28.0.0", default-features = false } +pallet-multisig = { version = "25.0.0", default-features = false } +pallet-preimage = { version = "25.0.0", default-features = false } +pallet-proxy = { version = "25.0.0", default-features = false } +pallet-recovery = { version = "25.0.0", default-features = false } +pallet-root-testing = { version = "1.0.0", default-features = false } +pallet-scheduler = { version = "26.0.0", default-features = false } +pallet-session = { version = "25.0.0", default-features = false } +pallet-sudo = { version = "25.0.0", default-features = false } +pallet-timestamp = { version = "24.0.0", default-features = false } +pallet-tips = { version = "24.0.0", default-features = false } +pallet-transaction-payment = { version = "25.0.0", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { version = "25.0.0", default-features = false } +pallet-treasury = { version = "24.0.0", default-features = false } +pallet-utility = { version = "25.0.0", default-features = false } +pallet-xcm = { version = "4.0.0", default-features = false } +sp-api = { version = "23.0.0", default-features = false } +sp-application-crypto = { version = "27.0.0", default-features = false } +sp-arithmetic = { version = "20.0.0", default-features = false } +sp-block-builder = { version = "23.0.0", default-features = false } +sp-blockchain = { version = "25.0.0", default-features = false } +sp-consensus = { version = "0.29.0", default-features = false } +sp-consensus-aura = { version = "0.29.0", default-features = false } +sp-consensus-slots = { version = "0.29.0", default-features = false } +sp-core = { version = "25.0.0", default-features = false } +sp-debug-derive = { version = "12.0.0", default-features = false } +sp-externalities = { version = "0.23.0", default-features = false } +sp-inherents = { version = "23.0.0", default-features = false } +sp-io = { version = "27.0.0", default-features = false } +sp-keyring = { version = "28.0.0", default-features = false } +sp-keystore = { version = "0.31.0", default-features = false } +sp-offchain = { version = "23.0.0", default-features = false } +sp-runtime = { version = "28.0.0", default-features = false } +sp-runtime-interface = { version = "21.0.0", default-features = false } +sp-session = { version = "24.0.0", default-features = false } +sp-staking = { version = "23.0.0", default-features = false } +sp-state-machine = { version = "0.32.0", default-features = false } +sp-std = { version = "12.0.0", default-features = false } +sp-storage = { version = "17.0.0", default-features = false } +sp-timestamp = { version = "23.0.0", default-features = false } +sp-tracing = { version = "14.0.0", default-features = false } +sp-transaction-pool = { version = "23.0.0", default-features = false } +sp-trie = { version = "26.0.0", default-features = false } +sp-version = { version = "26.0.0", default-features = false } +sp-wasm-interface = { version = "18.0.0", default-features = false } +sp-weights = { version = "24.0.0", default-features = false } +xcm = { package = "staging-xcm", version = "4.0.0", default-features = false } +xcm-builder = { package = "staging-xcm-builder", version = "4.0.0", default-features = false } +xcm-executor = { package = "staging-xcm-executor", version = "4.0.0", default-features = false } +# Substrate (WASM) +frame-benchmarking-cli = { version = "29.0.0" } +pallet-transaction-payment-rpc = { version = "27.0.0" } +sc-basic-authorship = { version = "0.31.0" } +sc-chain-spec = { version = "24.0.0" } +sc-cli = { version = "0.33.0" } +sc-client-api = { version = "25.0.0" } +sc-consensus = { version = "0.30.0" } +sc-consensus-aura = { version = "0.31.0" } +sc-consensus-grandpa = { version = "0.16.0" } +sc-consensus-manual-seal = { version = "0.32.0" } +sc-consensus-slots = { version = "0.30.0" } +sc-executor = { version = "0.29.0" } +sc-network = { version = "0.31.0" } +sc-network-common = { version = "0.30.0" } +sc-network-sync = { version = "0.30.0" } +sc-offchain = { version = "26.0.0" } +sc-rpc = { version = "26.0.0" } +sc-rpc-api = { version = "0.30.0" } +sc-rpc-server = { version = "10.0.0" } +sc-service = { version = "0.32.0" } +sc-telemetry = { version = "12.0.0" } +sc-tracing = { version = "25.0.0" } +sc-transaction-pool = { version = "25.0.0" } +sc-transaction-pool-api = { version = "25.0.0" } +substrate-build-script-utils = { version = "9.0.0" } +substrate-frame-rpc-system = { version = "25.0.0" } +substrate-prometheus-endpoint = { version = "0.16.0" } +substrate-wasm-builder = { version = "14.0.0" } +try-runtime-cli = { version = "0.35.0" } +xcm-simulator = { version = "4.0.0" } diff --git a/README.md b/README.md index 3549be96a..a7db034a1 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ Setheum's Blockchain Network node Implementation in Rust, ready for hacking :roc
-[![Setheum version](https://img.shields.io/badge/Setheum-0.9.80-blue?logo=Parity%20Substrate)](https://setheum.xyz/) -[![License](https://img.shields.io/github/license/Setheum-Labs/Setheum?color=blue)](https://github.com/Setheum-Labs/Setheum/blob/master/LICENSE) +[![Setheum version](https://img.shields.io/badge/Setheum-0.9.81-yellow?logo=Parity%20Substrate)](https://setheum.xyz/) +[![License](https://img.shields.io/github/license/Setheum-Labs/Setheum?color=blue)](https://github.com/Setheum-Labs/Setheum/blob/master/LICENSE.md) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/contributor/CONTRIBUTING.md) -
+[![Rust](https://github.com/Setheum-Labs/Setheum/actions/workflows/rust.yml/badge.svg)](https://github.com/Setheum-Labs/Setheum/actions/workflows/rust.yml) +[![CodeQL](https://github.com/Setheum-Labs/Setheum/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Setheum-Labs/Setheum/actions/workflows/github-code-scanning/codeql) [![Website](https://img.shields.io/badge/web-gray?logo=web)](https://setheum.xyz) [![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2FSetheum)](https://twitter.com/Setheum) @@ -82,7 +83,7 @@ Setheum’s consensus system works to achieve high scalability and high security ### 1.2. EthicalDeFi -EthicalDeFi Suite is the DeFi powerhouse of the Setheum Network, providing all kinds of top notch DeFi protocols including a cutting-edge AMM DEX, modules, +EthicalDeFi Suite is the DeFi powerhouse of the Setheum Network, providing all kinds of top notch DeFi protocols including AMM, Orderbook & Aggregated DEX modules, Decentralised Liquid Staking for Setheum SE and ethical zero-interest halal stablecoins that gives us the properties of both Fiat and Crypto with SlickUSD (USSD) and the Setter (SETR) using an Ethical Collateralized Debt Position (ECDP) mechanism that is over-Collateralized and multi-Collateralised and stable without compromising decentralisation or economic stability, offering zero-interest loans of stable cryptocurrencies that has scalable value and trust, @@ -304,7 +305,7 @@ Run the module benchmarks and generate the weights file: --execution=wasm \ --wasm-execution=compiled \ --heap-pages=4096 \ - --output=./modules/currencies/src/weights.rs + --output=./blockchain/modules/currencies/src/weights.rs ``` ### 6.4. Bench Bot diff --git a/blockchain/modules/airdrop/Cargo.toml b/blockchain/modules/airdrop/Cargo.toml index fb6dfed2b..96b88a180 100644 --- a/blockchain/modules/airdrop/Cargo.toml +++ b/blockchain/modules/airdrop/Cargo.toml @@ -1,35 +1,41 @@ [package] name = "module-airdrop" -version = "1.0.0" +version = "0.1.0" authors = ["Setheum Labs"] -edition = "2018" +edition = "2021" [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } -orml-traits = { package = "orml-traits", path = "../submodules/orml/traits", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +orml-traits = { workspace = true } +primitives = { workspace = true } [dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -orml-tokens = { path = "../submodules/orml/tokens" } +orml-tokens = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "codec/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", - "sp-core/std", - "sp-std/std", + "orml-tokens/std", "orml-traits/std", - "support/std", + "parity-scale-codec/std", "primitives/std", + "sp-runtime/std", + "sp-core/std", + "sp-std/std", + "scale-info/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "module-dex/try-runtime", ] diff --git a/blockchain/modules/airdrop/README.md b/blockchain/modules/airdrop/README.md new file mode 100644 index 000000000..ed74f2982 --- /dev/null +++ b/blockchain/modules/airdrop/README.md @@ -0,0 +1,5 @@ +# Airdrop Module + +## Overview + +This module creates airdrops and distributes airdrops to the - acccounts in the airdrop list from a drop origin. The module for distributing Setheum Airdrops. diff --git a/blockchain/modules/airdrop/src/lib.rs b/blockchain/modules/airdrop/src/lib.rs index 6dcf82fff..edd45e2cc 100644 --- a/blockchain/modules/airdrop/src/lib.rs +++ b/blockchain/modules/airdrop/src/lib.rs @@ -29,6 +29,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] +#![allow(clippy::type_complexity)] use frame_support::{pallet_prelude::*, transactional, PalletId, traits::Get}; use frame_system::pallet_prelude::*; @@ -38,6 +39,7 @@ use sp_std::vec::Vec; use sp_runtime::traits::AccountIdConversion; mod mock; +mod tests; pub use module::*; diff --git a/blockchain/modules/asset-registry/Cargo.toml b/blockchain/modules/asset-registry/Cargo.toml new file mode 100644 index 000000000..a19259901 --- /dev/null +++ b/blockchain/modules/asset-registry/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "module-asset-registry" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +log = { workspace = true } +scale-info = { workspace = true } +parity-scale-codec = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +primitives = { workspace = true } + +xcm = { workspace = true } + +module-support = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } +hex = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } + +module-evm = { workspace = true, features = ["std"] } +module-evm-bridge = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "log/std", + "parity-scale-codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "xcm/std", + "module-support/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/asset-registry/README.md b/blockchain/modules/asset-registry/README.md new file mode 100644 index 000000000..3d8727b7e --- /dev/null +++ b/blockchain/modules/asset-registry/README.md @@ -0,0 +1,7 @@ +بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +# Asset Registry Module + +## Overview + +Local and foreign assets management. The foreign assets can be updated without runtime upgrade. diff --git a/blockchain/modules/asset-registry/src/lib.rs b/blockchain/modules/asset-registry/src/lib.rs new file mode 100644 index 000000000..b8b2e59b6 --- /dev/null +++ b/blockchain/modules/asset-registry/src/lib.rs @@ -0,0 +1,706 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Asset Registry Module +//! +//! Local and foreign assets management. The foreign assets can be updated without runtime upgrade. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + traits::{Currency, EnsureOrigin}, +}; +use frame_system::pallet_prelude::*; +use module_support::{AssetIdMapping, BuyWeightRate, EVMBridge, Erc20InfoMapping, InvokeContext, Ratio}; +use primitives::{ + currency::{ + AssetIds, AssetMetadata, CurrencyIdType, DexShare, DexShareType, Erc20Id, ForeignAssetId, TokenInfo, + }, + evm::{ + is_system_contract, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD, + H160_POSITION_DEXSHARE_LEFT_TYPE, H160_POSITION_DEXSHARE_RIGHT_FIELD, H160_POSITION_DEXSHARE_RIGHT_TYPE, + H160_POSITION_FOREIGN_ASSET, H160_POSITION_TOKEN, + }, + CurrencyId, +}; +use scale_info::prelude::format; +use sp_runtime::{traits::One, ArithmeticError, FixedPointNumber, FixedU128}; +use sp_std::{boxed::Box, vec::Vec}; + +use xcm::{v3::prelude::*, VersionedMultiLocation}; + +mod mock; +mod tests; +mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +/// Type alias for currency balance. +pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency type for withdraw and balance storage. + type Currency: Currency; + + /// Evm Bridge for getting info of contracts from the EVM. + type EVMBridge: EVMBridge>; + + /// Required origin for registering asset. + type RegisterOrigin: EnsureOrigin; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The given location could not be used (e.g. because it cannot be expressed in the + /// desired version of XCM). + BadLocation, + /// MultiLocation existed + MultiLocationExisted, + /// AssetId not exists + AssetIdNotExists, + /// AssetId exists + AssetIdExisted, + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + /// The foreign asset registered. + ForeignAssetRegistered { + asset_id: ForeignAssetId, + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, + /// The foreign asset updated. + ForeignAssetUpdated { + asset_id: ForeignAssetId, + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, + /// The asset registered. + AssetRegistered { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, + /// The asset updated. + AssetUpdated { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, + } + + /// Next available Foreign AssetId ID. + /// + /// NextForeignAssetId: ForeignAssetId + #[pallet::storage] + #[pallet::getter(fn next_foreign_asset_id)] + pub type NextForeignAssetId = StorageValue<_, ForeignAssetId, ValueQuery>; + + /// The storages for MultiLocations. + /// + /// ForeignAssetLocations: map ForeignAssetId => Option + #[pallet::storage] + #[pallet::getter(fn foreign_asset_locations)] + pub type ForeignAssetLocations = StorageMap<_, Twox64Concat, ForeignAssetId, MultiLocation, OptionQuery>; + + /// The storages for CurrencyIds. + /// + /// LocationToCurrencyIds: map MultiLocation => Option + #[pallet::storage] + #[pallet::getter(fn location_to_currency_ids)] + pub type LocationToCurrencyIds = StorageMap<_, Twox64Concat, MultiLocation, CurrencyId, OptionQuery>; + + /// The storages for EvmAddress. + /// + /// Erc20IdToAddress: map Erc20Id => Option + #[pallet::storage] + #[pallet::getter(fn erc20_id_to_address)] + pub type Erc20IdToAddress = StorageMap<_, Twox64Concat, Erc20Id, EvmAddress, OptionQuery>; + + /// The storages for AssetMetadatas. + /// + /// AssetMetadatas: map AssetIds => Option + #[pallet::storage] + #[pallet::getter(fn asset_metadatas)] + pub type AssetMetadatas = + StorageMap<_, Twox64Concat, AssetIds, AssetMetadata>, OptionQuery>; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub assets: Vec<(CurrencyId, BalanceOf)>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.assets.iter().for_each(|(asset, ed)| { + frame_support::assert_ok!(Pallet::::do_register_native_asset( + *asset, + &AssetMetadata { + name: asset.name().unwrap().as_bytes().to_vec(), + symbol: asset.symbol().unwrap().as_bytes().to_vec(), + decimals: asset.decimals().unwrap(), + minimal_balance: *ed, + } + )); + }); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::register_foreign_asset())] + pub fn register_foreign_asset( + origin: OriginFor, + location: Box, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let location: MultiLocation = (*location).try_into().map_err(|()| Error::::BadLocation)?; + let foreign_asset_id = Self::do_register_foreign_asset(&location, &metadata)?; + + Self::deposit_event(Event::::ForeignAssetRegistered { + asset_id: foreign_asset_id, + asset_address: location, + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::update_foreign_asset())] + pub fn update_foreign_asset( + origin: OriginFor, + foreign_asset_id: ForeignAssetId, + location: Box, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let location: MultiLocation = (*location).try_into().map_err(|()| Error::::BadLocation)?; + Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?; + + Self::deposit_event(Event::::ForeignAssetUpdated { + asset_id: foreign_asset_id, + asset_address: location, + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::register_erc20_asset())] + pub fn register_erc20_asset( + origin: OriginFor, + contract: EvmAddress, + minimal_balance: BalanceOf, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let metadata = Self::do_register_erc20_asset(contract, minimal_balance)?; + + Self::deposit_event(Event::::AssetRegistered { + asset_id: AssetIds::Erc20(contract), + metadata, + }); + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::update_erc20_asset())] + pub fn update_erc20_asset( + origin: OriginFor, + contract: EvmAddress, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + Self::do_update_erc20_asset(contract, &metadata)?; + + Self::deposit_event(Event::::AssetUpdated { asset_id: AssetIds::Erc20(contract), + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::register_native_asset())] + pub fn register_native_asset( + origin: OriginFor, + currency_id: CurrencyId, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + Self::do_register_native_asset(currency_id, &metadata)?; + + Self::deposit_event(Event::::AssetRegistered { + asset_id: AssetIds::NativeAssetId(currency_id), + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::update_native_asset())] + pub fn update_native_asset( + origin: OriginFor, + currency_id: CurrencyId, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + Self::do_update_native_asset(currency_id, &metadata)?; + + Self::deposit_event(Event::::AssetUpdated { + asset_id: AssetIds::NativeAssetId(currency_id), + metadata: *metadata, + }); + Ok(()) + } + } +} + +impl Pallet { + fn get_next_foreign_asset_id() -> Result { + NextForeignAssetId::::try_mutate(|current| -> Result { + let id = *current; + *current = current.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + Ok(id) + }) + } + + fn do_register_foreign_asset( + location: &MultiLocation, + metadata: &AssetMetadata>, + ) -> Result { + let foreign_asset_id = Self::get_next_foreign_asset_id()?; + LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { + ensure!(maybe_currency_ids.is_none(), Error::::MultiLocationExisted); + *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + + ForeignAssetLocations::::try_mutate(foreign_asset_id, |maybe_location| -> DispatchResult { + ensure!(maybe_location.is_none(), Error::::MultiLocationExisted); + *maybe_location = Some(*location); + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + ) + }) + })?; + + Ok(foreign_asset_id) + } + + fn do_update_foreign_asset( + foreign_asset_id: ForeignAssetId, + location: &MultiLocation, + metadata: &AssetMetadata>, + ) -> DispatchResult { + ForeignAssetLocations::::try_mutate(foreign_asset_id, |maybe_multi_locations| -> DispatchResult { + let old_multi_locations = maybe_multi_locations.as_mut().ok_or(Error::::AssetIdNotExists)?; + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + // modify location + if location != old_multi_locations { + LocationToCurrencyIds::::remove(*old_multi_locations); + LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { + ensure!(maybe_currency_ids.is_none(), Error::::MultiLocationExisted); + *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + Ok(()) + })?; + } + *maybe_asset_metadatas = Some(metadata.clone()); + *old_multi_locations = *location; + Ok(()) + }, + ) + }) + } + + fn do_register_erc20_asset( + contract: EvmAddress, + minimal_balance: BalanceOf, + ) -> Result>, DispatchError> { + let invoke_context = InvokeContext { + contract, + sender: Default::default(), + origin: Default::default(), + }; + + let metadata = AssetMetadata { + name: T::EVMBridge::name(invoke_context)?, + symbol: T::EVMBridge::symbol(invoke_context)?, + decimals: T::EVMBridge::decimals(invoke_context)?, + minimal_balance, + }; + + let erc20_id = Into::::into(DexShare::Erc20(contract)); + + AssetMetadatas::::try_mutate(AssetIds::Erc20(contract), |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + + Erc20IdToAddress::::try_mutate(erc20_id, |maybe_address| -> DispatchResult { + ensure!(maybe_address.is_none(), Error::::AssetIdExisted); + *maybe_address = Some(contract); + + Ok(()) + })?; + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + })?; + + Ok(metadata) + } + + fn do_update_erc20_asset(contract: EvmAddress, metadata: &AssetMetadata>) -> DispatchResult { + AssetMetadatas::::try_mutate(AssetIds::Erc20(contract), |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }) + } + + fn do_register_native_asset(asset: CurrencyId, metadata: &AssetMetadata>) -> DispatchResult { + AssetMetadatas::::try_mutate( + AssetIds::NativeAssetId(asset), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + )?; + + Ok(()) + } + + fn do_update_native_asset(currency_id: CurrencyId, metadata: &AssetMetadata>) -> DispatchResult { + AssetMetadatas::::try_mutate( + AssetIds::NativeAssetId(currency_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + ) + } +} + +pub struct AssetIdMaps(sp_std::marker::PhantomData); + +impl AssetIdMapping>> for AssetIdMaps { + fn get_asset_metadata(asset_ids: AssetIds) -> Option>> { + Pallet::::asset_metadatas(asset_ids) + } + + fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option { + Pallet::::foreign_asset_locations(foreign_asset_id) + } + + fn get_currency_id(multi_location: MultiLocation) -> Option { + Pallet::::location_to_currency_ids(multi_location) + } +} + +fn key_to_currency(location: MultiLocation) -> Option { + match location { + MultiLocation { + parents: 0, + interior: X1(Junction::GeneralKey { data, length }), + } => { + let key = &data[..data.len().min(length as usize)]; + CurrencyId::decode(&mut &*key).ok() + } + _ => None, + } +} + +pub struct BuyWeightRateOfForeignAsset(sp_std::marker::PhantomData); + +impl BuyWeightRate for BuyWeightRateOfForeignAsset +where + BalanceOf: Into, +{ + fn calculate_rate(location: MultiLocation) -> Option { + if let Some(CurrencyId::ForeignAsset(foreign_asset_id)) = Pallet::::location_to_currency_ids(location) { + if let Some(asset_metadata) = Pallet::::asset_metadatas(AssetIds::ForeignAssetId(foreign_asset_id)) { + let minimum_balance = asset_metadata.minimal_balance.into(); + let rate = FixedU128::saturating_from_rational(minimum_balance, T::Currency::minimum_balance().into()); + log::debug!(target: "asset-registry::weight", "ForeignAsset: {}, MinimumBalance: {}, rate:{:?}", foreign_asset_id, minimum_balance, rate); + return Some(rate); + } + } + None + } +} + +pub struct BuyWeightRateOfErc20(sp_std::marker::PhantomData); + +impl BuyWeightRate for BuyWeightRateOfErc20 +where + BalanceOf: Into, +{ + fn calculate_rate(location: MultiLocation) -> Option { + let currency = key_to_currency(location); + match currency { + Some(CurrencyId::Erc20(address)) if !is_system_contract(&address) => { + if let Some(asset_metadata) = Pallet::::asset_metadatas(AssetIds::Erc20(address)) { + let minimum_balance = asset_metadata.minimal_balance.into(); + let rate = + FixedU128::saturating_from_rational(minimum_balance, T::Currency::minimum_balance().into()); + log::debug!(target: "asset-registry::weight", "Erc20: {}, MinimumBalance: {}, rate:{:?}", address, minimum_balance, rate); + Some(rate) + } else { + None + } + } + _ => None, + } + } +} + +pub struct EvmErc20InfoMapping(sp_std::marker::PhantomData); + +impl EvmErc20InfoMapping { + fn name_for_dex_share(symbol: DexShare) -> Option> { + match symbol { + DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.name), + DexShare::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.name) + } + } + } + + fn symbol_for_dex_share(symbol: DexShare) -> Option> { + match symbol { + DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.symbol), + DexShare::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.symbol) + } + } + } + + fn decimal_for_dex_share(symbol: DexShare) -> Option { + match symbol { + DexShare::Token(symbol) => CurrencyId::Token(symbol).decimals(), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.decimals), + DexShare::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.decimals) + } + } + } + + fn decode_evm_address_for_dex_share(address: &[u8], left: bool) -> Option { + let (dex_share_type, dex_share_field) = if left { + (H160_POSITION_DEXSHARE_LEFT_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD) + } else { + (H160_POSITION_DEXSHARE_RIGHT_TYPE, H160_POSITION_DEXSHARE_RIGHT_FIELD) + }; + match DexShareType::try_from(address[dex_share_type]).ok()? { + DexShareType::Token => address[dex_share_field][3].try_into().map(DexShare::Token).ok(), + DexShareType::Erc20 => { + let id = u32::from_be_bytes(address[dex_share_field].try_into().ok()?); + Erc20IdToAddress::::get(id).map(DexShare::Erc20) + } + DexShareType::ForeignAsset => { + let id = ForeignAssetId::from_be_bytes(address[dex_share_field][2..].try_into().ok()?); + Some(DexShare::ForeignAsset(id)) + } + } + } +} + +impl Erc20InfoMapping for EvmErc20InfoMapping { + // Returns the name associated with a given CurrencyId. + // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + // the EvmAddress must have been mapped. + fn name(currency_id: CurrencyId) -> Option> { + let name = match currency_id { + CurrencyId::Token(_) => AssetMetadatas::::get(AssetIds::NativeAssetId(currency_id)).map(|v| v.name), + CurrencyId::DexShare(symbol_0, symbol_1) => { + let name_0 = EvmErc20InfoMapping::::name_for_dex_share(symbol_0)?; + let name_1 = EvmErc20InfoMapping::::name_for_dex_share(symbol_1)?; + + let mut vec = Vec::new(); + vec.extend_from_slice(&b"LP "[..]); + vec.extend_from_slice(&name_0); + vec.extend_from_slice(&b" - "[..]); + vec.extend_from_slice(&name_1); + Some(vec) + } + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.name), + CurrencyId::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.name) + } + }?; + + // More than 32 bytes will be truncated. + if name.len() > 32 { + Some(name[..32].to_vec()) + } else { + Some(name) + } + } + + // Returns the symbol associated with a given CurrencyId. + // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + // the EvmAddress must have been mapped. + fn symbol(currency_id: CurrencyId) -> Option> { + let symbol = match currency_id { + CurrencyId::Token(_) => AssetMetadatas::::get(AssetIds::NativeAssetId(currency_id)).map(|v| v.symbol), + CurrencyId::DexShare(symbol_0, symbol_1) => { + let token_symbol_0 = EvmErc20InfoMapping::::symbol_for_dex_share(symbol_0)?; + let token_symbol_1 = EvmErc20InfoMapping::::symbol_for_dex_share(symbol_1)?; + + let mut vec = Vec::new(); + vec.extend_from_slice(&b"LP_"[..]); + vec.extend_from_slice(&token_symbol_0); + vec.extend_from_slice(&b"_"[..]); + vec.extend_from_slice(&token_symbol_1); + Some(vec) + } + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.symbol), + CurrencyId::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.symbol) + } + }?; + + // More than 32 bytes will be truncated. + if symbol.len() > 32 { + Some(symbol[..32].to_vec()) + } else { + Some(symbol) + } + } + + // Returns the decimals associated with a given CurrencyId. + // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + // the EvmAddress must have been mapped. + fn decimals(currency_id: CurrencyId) -> Option { + match currency_id { + CurrencyId::Token(_) => AssetMetadatas::::get(AssetIds::NativeAssetId(currency_id)).map(|v| v.decimals), + CurrencyId::DexShare(symbol_0, _) => { + // initial dex share amount is calculated based on currency_id_0, + // use the decimals of currency_id_0 as the decimals of lp token. + EvmErc20InfoMapping::::decimal_for_dex_share(symbol_0) + } + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.decimals), + CurrencyId::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.decimals) + } + } + } + + // Encode the CurrencyId to EvmAddress. + // If is CurrencyId::DexShare and contain DexShare::Erc20, + // will use the u32 to get the DexShare::Erc20 from the mapping. + fn encode_evm_address(v: CurrencyId) -> Option { + match v { + CurrencyId::DexShare(left, right) => { + match left { + DexShare::Erc20(address) => { + // ensure erc20 is mapped + AssetMetadatas::::get(AssetIds::Erc20(address)).map(|_| ())?; + } + DexShare::Token(_) + | DexShare::ForeignAsset(_) => {} + }; + match right { + DexShare::Erc20(address) => { + // ensure erc20 is mapped + AssetMetadatas::::get(AssetIds::Erc20(address)).map(|_| ())?; + } + DexShare::Token(_) + | DexShare::ForeignAsset(_) => {} + }; + } + CurrencyId::Token(_) + | CurrencyId::Erc20(_) + | CurrencyId::ForeignAsset(_) => {} + }; + + EvmAddress::try_from(v).ok() + } + + // Decode the CurrencyId from EvmAddress. + // If is CurrencyId::DexShare and contain DexShare::Erc20, + // will use the u32 to get the DexShare::Erc20 from the mapping. + fn decode_evm_address(addr: EvmAddress) -> Option { + if !is_system_contract(&addr) { + return Some(CurrencyId::Erc20(addr)); + } + + let address = addr.as_bytes(); + let currency_id = match CurrencyIdType::try_from(address[H160_POSITION_CURRENCY_ID_TYPE]).ok()? { + CurrencyIdType::Token => address[H160_POSITION_TOKEN].try_into().map(CurrencyId::Token).ok(), + CurrencyIdType::DexShare => { + let left = EvmErc20InfoMapping::::decode_evm_address_for_dex_share(address, true)?; + let right = EvmErc20InfoMapping::::decode_evm_address_for_dex_share(address, false)?; + Some(CurrencyId::DexShare(left, right)) + } + CurrencyIdType::ForeignAsset => { + let id = ForeignAssetId::from_be_bytes(address[H160_POSITION_FOREIGN_ASSET].try_into().ok()?); + Some(CurrencyId::ForeignAsset(id)) + } + }; + + // Make sure that every bit of the address is the same + Self::encode_evm_address(currency_id?).and_then(|encoded| if encoded == addr { currency_id } else { None }) + } +} diff --git a/blockchain/modules/asset-registry/src/mock.rs b/blockchain/modules/asset-registry/src/mock.rs new file mode 100644 index 000000000..050cb7a3e --- /dev/null +++ b/blockchain/modules/asset-registry/src/mock.rs @@ -0,0 +1,275 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Mocks for asset registry module. + +#![cfg(test)] + +use crate as asset_registry; +use frame_support::{ + assert_ok, construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64}, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockAddressMapping, AddressMapping}; +use primitives::{ + evm::convert_decimals_to_evm, evm::EvmAddress, AccountId, Balance, CurrencyId, ReserveIdentifier, TokenSymbol, +}; +use sp_core::{H160, H256, U256}; +use sp_runtime::BuildStorage; +use std::str::FromStr; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = module_support::SystemAccountStore; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +parameter_types! { + pub NetworkContractSource: EvmAddress = alice_evm_addr(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId = AccountId::from([1u8; 32]); + pub const TreasuryAccount: AccountId = AccountId::from([2u8; 32]); + pub const NetworkContractAccount: AccountId = AccountId::from([0u8; 32]); + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); +} + +impl module_evm::Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = (); + type NewContractExtraBytes = ConstU32<1>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<10>; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = (); + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + + type DeveloperDeposit = ConstU128<1000>; + type PublicationFee = ConstU128<200>; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = (); + type Task = (); + type IdleScheduler = (); + type WeightInfo = (); +} + +impl module_evm_bridge::Config for Runtime { + type EVM = EVM; +} + +impl asset_registry::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EVMBridge = module_evm_bridge::EVMBridge; + type RegisterOrigin = EnsureSignedBy; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + AssetRegistry: asset_registry, + EVM: module_evm, + EVMBridge: module_evm_bridge, + } +); + +pub fn erc20_address() -> EvmAddress { + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() +} + +pub fn erc20_address_same_prefix() -> EvmAddress { + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba644").unwrap() +} + +pub fn erc20_address_not_exists() -> EvmAddress { + EvmAddress::from_str("0000000000000000000100000000000002000001").unwrap() +} + +pub fn alice() -> AccountId { + ::AddressMapping::get_account_id(&alice_evm_addr()) +} + +pub fn alice_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() +} + +pub const ALICE_BALANCE: u128 = 100_000_000_000_000_000_000_000u128; + +pub fn deploy_contracts() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), + ], + data: { + let mut buf = [0u8; 32]; + U256::from(ALICE_BALANCE).to_big_endian(&mut buf); + H256::from_slice(&buf).as_bytes().to_vec() + }, + }], + used_gas: 1235455, + used_storage: 5131, + })); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); +} + +// Specify contract address +pub fn deploy_contracts_same_prefix() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create_predeploy_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + erc20_address_same_prefix(), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + System::assert_has_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address_same_prefix(), + logs: vec![module_evm::Log { + address: erc20_address_same_prefix(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), + ], + data: { + let mut buf = [0u8; 32]; + U256::from(ALICE_BALANCE).to_big_endian(&mut buf); + H256::from_slice(&buf).as_bytes().to_vec() + }, + }], + used_gas: 1235455, + used_storage: 5131, + })); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::ContractPublished { + contract: erc20_address_same_prefix(), + })); +} + +pub struct ExtBuilder { + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + asset_registry::GenesisConfig:: { + assets: vec![(CurrencyId::Token(TokenSymbol::SEE), 1)], + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self.balances.into_iter().collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + module_evm::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/blockchain/modules/asset-registry/src/tests.rs b/blockchain/modules/asset-registry/src/tests.rs new file mode 100644 index 000000000..910370186 --- /dev/null +++ b/blockchain/modules/asset-registry/src/tests.rs @@ -0,0 +1,1051 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Unit tests for asset registry module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{ + alice, deploy_contracts, deploy_contracts_same_prefix, erc20_address, erc20_address_not_exists, + erc20_address_same_prefix, AssetRegistry, CouncilAccount, ExtBuilder, Runtime, RuntimeEvent, RuntimeOrigin, System, +}; +use primitives::TokenSymbol; +use sp_core::H160; +use std::str::{from_utf8, FromStr}; + +#[test] +fn key_to_currency_work() { + let erc20 = CurrencyId::Erc20(EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba644").unwrap()); + let v2_location = xcm::v2::MultiLocation::new( + 0, + xcm::v2::Junctions::X1(xcm::v2::Junction::GeneralKey(erc20.encode().try_into().unwrap())), + ); + let v3_location_from_v2 = MultiLocation::try_from(v2_location.clone()).unwrap(); + let v3_location = MultiLocation::new( + 0, + Junctions::X1(Junction::from(BoundedVec::try_from(erc20.encode()).unwrap())), + ); + assert_eq!(v3_location_from_v2, v3_location); + assert_eq!(crate::key_to_currency(v3_location), Some(erc20)); +} + +#[test] +fn test_v2_to_v3_incompatible_multilocation() { + let v2_location = xcm::v2::MultiLocation::new( + 0, + xcm::v2::Junctions::X1(xcm::v2::Junction::GeneralKey(vec![0].try_into().unwrap())), + ); + + let v3_location = MultiLocation::new(0, X1(Junction::from(BoundedVec::try_from(vec![0]).unwrap()))); + + // Assert that V2 and V3 Multilocation both are encoded differently + assert!(v2_location.encode() != v3_location.encode()); +} + +#[test] +fn versioned_multi_location_convert_work() { + ExtBuilder::default().build().execute_with(|| { + // v2 + let v2_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(1000)), + }); + let location: MultiLocation = v2_location.try_into().unwrap(); + assert_eq!( + location, + MultiLocation { + parents: 0, + interior: xcm::v3::Junctions::X1(xcm::v3::Junction::Parachain(1000)) + } + ); + + // v3 + let v3_location = VersionedMultiLocation::V3(MultiLocation { + parents: 0, + interior: xcm::v3::Junctions::X1(xcm::v3::Junction::Parachain(1000)), + }); + let location: MultiLocation = v3_location.try_into().unwrap(); + assert_eq!( + location, + MultiLocation { + parents: 0, + interior: xcm::v3::Junctions::X1(xcm::v3::Junction::Parachain(1000)) + } + ); + + // handle all of VersionedMultiLocation + assert!(match location.into() { + VersionedMultiLocation::V2 { .. } | VersionedMultiLocation::V3 { .. } => true, + }); + }); +} + +#[test] +fn register_foreign_asset_work() { + ExtBuilder::default().build().execute_with(|| { + // v2 + let v2_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(1000)), + }); + + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v2_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + let location: MultiLocation = v2_location.try_into().unwrap(); + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::ForeignAssetRegistered { + asset_id: 0, + asset_address: location.clone(), + metadata: AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }, + })); + + assert_eq!(ForeignAssetLocations::::get(0), Some(location.clone())); + assert_eq!( + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), + Some(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ); + assert_eq!( + LocationToCurrencyIds::::get(location), + Some(CurrencyId::ForeignAsset(0)) + ); + + // v3 + let v3_location = VersionedMultiLocation::V3(xcm::v3::MultiLocation { + parents: 0, + interior: xcm::v3::Junctions::X1(xcm::v3::Junction::GeneralKey { + length: 32, + data: [0u8; 32], + }), + }); + + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v3_location.clone()), + Box::new(AssetMetadata { + name: b"Another Token Name".to_vec(), + symbol: b"ATN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + let location: MultiLocation = v3_location.try_into().unwrap(); + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::ForeignAssetRegistered { + asset_id: 1, + asset_address: location.clone(), + metadata: AssetMetadata { + name: b"Another Token Name".to_vec(), + symbol: b"ATN".to_vec(), + decimals: 12, + minimal_balance: 1, + }, + })); + + assert_eq!(ForeignAssetLocations::::get(1), Some(location.clone())); + assert_eq!( + AssetMetadatas::::get(AssetIds::ForeignAssetId(1)), + Some(AssetMetadata { + name: b"Another Token Name".to_vec(), + symbol: b"ATN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ); + assert_eq!( + LocationToCurrencyIds::::get(location), + Some(CurrencyId::ForeignAsset(1)) + ); + }); +} + +#[test] +fn register_foreign_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + let v3_location = VersionedMultiLocation::V3(xcm::v3::MultiLocation { + parents: 0, + interior: xcm::v3::Junctions::X1(xcm::v3::Junction::Parachain(1000)), + }); + + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v3_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + assert_noop!( + AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v3_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ), + Error::::MultiLocationExisted + ); + + NextForeignAssetId::::set(u16::MAX); + assert_noop!( + AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v3_location), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn update_foreign_asset_work() { + ExtBuilder::default().build().execute_with(|| { + let v2_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(1000)), + }); + + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v2_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + assert_ok!(AssetRegistry::update_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + 0, + Box::new(v2_location.clone()), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + )); + + let location: MultiLocation = v2_location.try_into().unwrap(); + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::ForeignAssetUpdated { + asset_id: 0, + asset_address: location.clone(), + metadata: AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ); + assert_eq!(ForeignAssetLocations::::get(0), Some(location.clone())); + assert_eq!( + LocationToCurrencyIds::::get(location.clone()), + Some(CurrencyId::ForeignAsset(0)) + ); + + // modify location + let new_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(2000)), + }); + + assert_ok!(AssetRegistry::update_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + 0, + Box::new(new_location.clone()), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + )); + assert_eq!( + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ); + let new_location: MultiLocation = new_location.try_into().unwrap(); + assert_eq!(ForeignAssetLocations::::get(0), Some(new_location.clone())); + assert_eq!(LocationToCurrencyIds::::get(location), None); + assert_eq!( + LocationToCurrencyIds::::get(new_location), + Some(CurrencyId::ForeignAsset(0)) + ); + }); +} + +#[test] +fn update_foreign_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + let v2_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(1000)), + }); + + assert_noop!( + AssetRegistry::update_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + 0, + Box::new(v2_location.clone()), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ), + Error::::AssetIdNotExists + ); + + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(v2_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + assert_ok!(AssetRegistry::update_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + 0, + Box::new(v2_location), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + )); + + // existed location + let new_location = VersionedMultiLocation::V2(xcm::v2::MultiLocation { + parents: 0, + interior: xcm::v2::Junctions::X1(xcm::v2::Junction::Parachain(2000)), + }); + assert_ok!(AssetRegistry::register_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + Box::new(new_location.clone()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + assert_noop!( + AssetRegistry::update_foreign_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + 0, + Box::new(new_location), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ), + Error::::MultiLocationExisted + ); + }); +} + +#[test] +fn register_erc20_asset_work() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::AssetRegistered { + asset_id: AssetIds::Erc20(erc20_address()), + metadata: AssetMetadata { + name: b"long string name, long string name, long string name, long string name, long string name" + .to_vec(), + symbol: b"TestToken".to_vec(), + decimals: 17, + minimal_balance: 1, + }, + })); + + assert_eq!(Erc20IdToAddress::::get(0x5dddfce5), Some(erc20_address())); + + assert_eq!( + AssetMetadatas::::get(AssetIds::Erc20(erc20_address())), + Some(AssetMetadata { + name: b"long string name, long string name, long string name, long string name, long string name" + .to_vec(), + symbol: b"TestToken".to_vec(), + decimals: 17, + minimal_balance: 1, + }) + ); + }); +} + +#[test] +fn register_erc20_asset_should_not_work() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + deploy_contracts_same_prefix(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + assert_noop!( + AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address_same_prefix(), + 1 + ), + Error::::AssetIdExisted + ); + + assert_noop!( + AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address_not_exists(), + 1 + ), + module_evm_bridge::Error::::InvalidReturnValue, + ); + }); +} + +#[test] +fn update_erc20_asset_work() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + assert_ok!(AssetRegistry::update_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + )); + + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::AssetUpdated { + asset_id: AssetIds::Erc20(erc20_address()), + metadata: AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::Erc20(erc20_address())), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ); + }); +} + +#[test] +fn register_native_asset_works() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRegistry::register_native_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + CurrencyId::Token(TokenSymbol::EDF), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::AssetRegistered { + asset_id: AssetIds::NativeAssetId(CurrencyId::Token(TokenSymbol::EDF)), + metadata: AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::NativeAssetId(CurrencyId::Token(TokenSymbol::EDF))), + Some(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ); + // Can't duplicate + assert_noop!( + AssetRegistry::register_native_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + CurrencyId::Token(TokenSymbol::EDF), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ), + Error::::AssetIdExisted + ); + }); +} + +#[test] +fn update_native_asset_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + AssetRegistry::update_native_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + CurrencyId::Token(TokenSymbol::EDF), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ), + Error::::AssetIdNotExists + ); + + assert_ok!(AssetRegistry::register_native_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + CurrencyId::Token(TokenSymbol::EDF), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + assert_ok!(AssetRegistry::update_native_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + CurrencyId::Token(TokenSymbol::EDF), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + )); + + System::assert_last_event(RuntimeEvent::AssetRegistry(crate::Event::AssetUpdated { + asset_id: AssetIds::NativeAssetId(CurrencyId::Token(TokenSymbol::EDF)), + metadata: AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::NativeAssetId(CurrencyId::Token(TokenSymbol::EDF))), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ); + }); +} + +#[test] +fn update_erc20_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + + assert_noop!( + AssetRegistry::update_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"TOKEN".to_vec(), + decimals: 12, + minimal_balance: 2, + }) + ), + Error::::AssetIdNotExists + ); + }); +} + +#[test] +fn name_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::Token(TokenSymbol::SEE)), + Some(b"Setheum".to_vec()) + ); + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::Erc20(erc20_address())), + Some(b"long string name, long string name, long string name, long string name, long string name"[..32].to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::Erc20(erc20_address_not_exists())), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::USSD))), + Some(b"LP Setheum - Slick USD".to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Token(TokenSymbol::USSD))), + Some(b"LP long string name, long string name, long string name, long string name, long string name - Slick USD"[..32].to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Erc20(erc20_address()))), + Some(b"LP long string name, long string name, long string name, long string name, long string name - long string name, long string name, long string name, long string name, long string name"[..32].to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Erc20(erc20_address_not_exists()))), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Erc20(erc20_address_not_exists()))), + None + ); + }); +} + +#[test] +fn symbol_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::Token(TokenSymbol::SEE)), + Some(b"SEE".to_vec()) + ); + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::Erc20(erc20_address())), + Some(b"TestToken".to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::Erc20(erc20_address_not_exists())), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::USSD) + )), + Some(b"LP_SEE_USSD".to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Token(TokenSymbol::USSD) + )), + Some(b"LP_TestToken_USSD".to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address()) + )), + Some(b"LP_TestToken_TestToken".to_vec()) + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Erc20(erc20_address_not_exists()) + )), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::symbol(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address_not_exists()) + )), + None + ); + }); +} + +#[test] +fn decimals_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::Token(TokenSymbol::SEE)), + Some(12) + ); + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::Erc20(erc20_address())), + Some(17) + ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::Erc20(erc20_address_not_exists())), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::USSD) + )), + Some(12) + ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Token(TokenSymbol::USSD) + )), + Some(17) + ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address()) + )), + Some(17) + ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address_not_exists()) + )), + Some(17) + ); +} + +#[test] +fn encode_evm_address_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + // Token + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Token(TokenSymbol::SEE)), + H160::from_str("0x0000000000000000000100000000000000000000").ok() + ); + + // Erc20 + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address())), + Some(erc20_address()) + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address_not_exists())), + Some(erc20_address_not_exists()) + ); + + // DexShare + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::USSD) + )), + H160::from_str("0x0000000000000000000200000000000000000001").ok() + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Token(TokenSymbol::USSD) + )), + H160::from_str("0x00000000000000000002015dddfce50000000001").ok() + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::USSD), + DexShare::Erc20(erc20_address()) + )), + H160::from_str("0x000000000000000000020000000001015dddfce5").ok() + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address()) + )), + H160::from_str("0x00000000000000000002015dddfce5015dddfce5").ok() + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Erc20(erc20_address_not_exists()) + )), + None + ); + + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address_not_exists()) + )), + None + ); + + // ForeignAsset + assert_eq!( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::ForeignAsset(1)), + H160::from_str("0x0000000000000000000500000000000000000001").ok() + ); + }); +} + +#[test] +fn decode_evm_address_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_ok!(AssetRegistry::register_erc20_asset( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + // Token + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Token(TokenSymbol::SEE)).unwrap() + ), + Some(CurrencyId::Token(TokenSymbol::SEE)) + ); + + // Erc20 + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address())).unwrap() + ), + Some(CurrencyId::Erc20(erc20_address())) + ); + + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address_not_exists())) + .unwrap() + ), + None, + ); + + // DexShare + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::USSD) + )) + .unwrap(), + ), + Some(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::USSD) + )) + ); + + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Token(TokenSymbol::USSD) + )) + .unwrap() + ), + Some(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Token(TokenSymbol::USSD) + )) + ); + + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address()) + )) + .unwrap() + ), + Some(CurrencyId::DexShare( + DexShare::Erc20(erc20_address()), + DexShare::Erc20(erc20_address()) + )) + ); + + // decode invalid evm address + // CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), + // DexShare::Erc20(erc20_address_not_exists())) + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + H160::from_str("0x0000000000000000000000010000000002000001").unwrap() + ), + None + ); + + // decode invalid evm address + // CurrencyId::DexShare(DexShare::Erc20(erc20_address()), + // DexShare::Erc20(erc20_address_not_exists())) + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + H160::from_str("0x0000000000000000000000010200000002000001").unwrap() + ), + None + ); + + // Allow non-system contracts + let non_system_contracts = H160::from_str("0x1000000000000000000000000000000000000000").unwrap(); + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address(non_system_contracts), + Some(CurrencyId::Erc20(non_system_contracts)) + ); + + // ForeignAsset + assert_eq!( + EvmErc20InfoMapping::::decode_evm_address( + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::ForeignAsset(1)).unwrap() + ), + Some(CurrencyId::ForeignAsset(1)) + ); + }); +} diff --git a/blockchain/modules/asset-registry/src/weights.rs b/blockchain/modules/asset-registry/src/weights.rs new file mode 100644 index 000000000..c926354b9 --- /dev/null +++ b/blockchain/modules/asset-registry/src/weights.rs @@ -0,0 +1,165 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for module_asset_registry +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_asset_registry +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./modules/asset-registry/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_asset_registry. +pub trait WeightInfo { + fn register_foreign_asset() -> Weight; + fn update_foreign_asset() -> Weight; + fn register_stable_asset() -> Weight; + fn update_stable_asset() -> Weight; + fn register_erc20_asset() -> Weight; + fn update_erc20_asset() -> Weight; + fn register_native_asset() -> Weight; + fn update_native_asset() -> Weight; +} + +/// Weights for module_asset_registry using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + // Storage: AssetRegistry NextForeignAssetId (r:1 w:1) + // Storage: AssetRegistry LocationToCurrencyIds (r:1 w:1) + // Storage: AssetRegistry ForeignAssetLocations (r:1 w:1) + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn register_foreign_asset() -> Weight { + Weight::from_parts(21_475_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: AssetRegistry ForeignAssetLocations (r:1 w:1) + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn update_foreign_asset() -> Weight { + Weight::from_parts(19_334_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: AssetRegistry NextStableAssetId (r:1 w:1) + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn register_stable_asset() -> Weight { + Weight::from_parts(15_830_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn update_stable_asset() -> Weight { + Weight::from_parts(14_342_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: EVM Accounts (r:2 w:0) + // Storage: EVM Codes (r:1 w:0) + // Storage: EVM AccountStorages (r:5 w:0) + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + // Storage: AssetRegistry Erc20IdToAddress (r:1 w:1) + fn register_erc20_asset() -> Weight { + Weight::from_parts(187_828_000, 0) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn update_erc20_asset() -> Weight { + Weight::from_parts(19_773_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn register_native_asset() -> Weight { + Weight::from_parts(13_140_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: AssetRegistry AssetMetadatas (r:1 w:1) + fn update_native_asset() -> Weight { + Weight::from_parts(13_815_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn register_foreign_asset() -> Weight { + Weight::from_parts(21_475_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn update_foreign_asset() -> Weight { + Weight::from_parts(19_334_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn register_stable_asset() -> Weight { + Weight::from_parts(15_830_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn update_stable_asset() -> Weight { + Weight::from_parts(14_342_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn register_erc20_asset() -> Weight { + Weight::from_parts(187_828_000, 0) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn update_erc20_asset() -> Weight { + Weight::from_parts(19_773_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn register_native_asset() -> Weight { + Weight::from_parts(13_140_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn update_native_asset() -> Weight { + Weight::from_parts(13_815_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/blockchain/modules/currencies/Cargo.toml b/blockchain/modules/currencies/Cargo.toml index 8e176e8ca..0c87e3841 100644 --- a/blockchain/modules/currencies/Cargo.toml +++ b/blockchain/modules/currencies/Cargo.toml @@ -1,47 +1,52 @@ -[package] -name = "module-currencies" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-traits = { path = "../submodules/orml/traits", default-features = false } -orml-utilities = { path = "../submodules/orml/utilities", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -tokens = { package = "orml-tokens", path = "../submodules/orml/tokens" } -module-evm = { path = "../evm" } -module-evm-bridge = { path = "../evm-bridge" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", - "frame-support/std", - "frame-system/std", - "orml-traits/std", - "orml-utilities/std", - "primitives/std", - "support/std", -] +[package] +name = "module-currencies" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } + +orml-traits = { workspace = true } + +primitives = { workspace = true } +module-support = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } +hex = { workspace = true, features = ["std"] } +hex-literal = { workspace = true } +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +module-evm = { workspace = true, features = ["std"] } +module-evm-bridge = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "primitives/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/currencies/runtime-api/Cargo.toml b/blockchain/modules/currencies/runtime-api/Cargo.toml new file mode 100644 index 000000000..8896618d0 --- /dev/null +++ b/blockchain/modules/currencies/runtime-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "module-currencies-runtime-api" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +sp-runtime = { workspace = true } +sp-api = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "sp-runtime/std", + "sp-api/std", + "sp-std/std", + "sp-core/std", +] diff --git a/blockchain/modules/example/src/tests.rs b/blockchain/modules/currencies/runtime-api/src/lib.rs similarity index 61% rename from blockchain/modules/example/src/tests.rs rename to blockchain/modules/currencies/runtime-api/src/lib.rs index ddca1c29e..4873e5540 100644 --- a/blockchain/modules/example/src/tests.rs +++ b/blockchain/modules/currencies/runtime-api/src/lib.rs @@ -18,28 +18,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Unit tests for example module. - -#![cfg(test)] - -use crate::mock::*; -use frame_support::assert_ok; - -#[test] -fn set_dummy_work() { - new_test_ext().execute_with(|| { - assert_eq!(Example::dummy(), None); - assert_ok!(Example::set_dummy(Origin::root(), 20)); - assert_eq!(Example::dummy(), Some(20)); - System::assert_last_event(Event::Example(crate::Event::Dummy(20))); - }); -} - -#[test] -fn do_set_bar_work() { - new_test_ext().execute_with(|| { - assert_eq!(Example::bar(2), 200); - Example::do_set_bar(&2, 10); - assert_eq!(Example::bar(2), 10); - }); +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::all)] + +use sp_runtime::codec::Codec; + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait CurrenciesApi where + CurrencyId: Codec, + AccountId: Codec, + Balance: Codec, + { + fn query_free_balance(currency_id: CurrencyId, who: AccountId) -> Balance; + } } diff --git a/blockchain/modules/currencies/src/lib.rs b/blockchain/modules/currencies/src/lib.rs index 81b2e57c6..5fb62f0d0 100644 --- a/blockchain/modules/currencies/src/lib.rs +++ b/blockchain/modules/currencies/src/lib.rs @@ -24,35 +24,34 @@ #![allow(clippy::unused_unit)] #![allow(clippy::upper_case_acronyms)] -use codec::Codec; use frame_support::{ pallet_prelude::*, traits::{ - Currency as PalletCurrency, ExistenceRequirement, Get, LockableCurrency as PalletLockableCurrency, - ReservableCurrency as PalletReservableCurrency, WithdrawReasons, + tokens::{ + fungible, fungibles, DepositConsequence, Fortitude, Precision, Preservation, Provenance, Restriction, + WithdrawConsequence, + }, + BalanceStatus as Status, Currency as PalletCurrency, ExistenceRequirement, Get, Imbalance, + LockableCurrency as PalletLockableCurrency, ReservableCurrency as PalletReservableCurrency, WithdrawReasons, }, transactional, }; use frame_system::pallet_prelude::*; +use module_support::{evm::limits::erc20, AddressMapping, EVMBridge, InvokeContext}; use orml_traits::{ arithmetic::{Signed, SimpleArithmetic}, - currency::TransferAll, + currency::{OnDust, TransferAll}, BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency, - LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, OnDust, + LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, }; +use parity_scale_codec::Codec; use primitives::{evm::EvmAddress, CurrencyId}; use sp_io::hashing::blake2_256; use sp_runtime::{ - traits::{CheckedSub, MaybeSerializeDeserialize, Saturating, StaticLookup, Zero}, + traits::{CheckedAdd, CheckedSub, Convert, MaybeSerializeDeserialize, Saturating, StaticLookup, Zero}, DispatchError, DispatchResult, }; -use sp_std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - marker, result, - vec::Vec, -}; -use support::{AddressMapping, EVMBridge, InvokeContext}; +use sp_std::{fmt::Debug, marker, result, vec::Vec}; mod mock; mod tests; @@ -61,12 +60,9 @@ pub mod weights; pub use module::*; pub use weights::WeightInfo; -type BalanceOf = <::MultiCurrency as MultiCurrency<::AccountId>>::Balance; -type CurrencyIdOf = - <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; - type AmountOf = <::MultiCurrency as MultiCurrencyExtended<::AccountId>>::Amount; +type BalanceOf = <::MultiCurrency as MultiCurrency<::AccountId>>::Balance; #[frame_support::pallet] pub mod module { @@ -74,28 +70,50 @@ pub mod module { #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; type MultiCurrency: TransferAll + MultiCurrencyExtended + MultiLockableCurrency - + MultiReservableCurrency; + + MultiReservableCurrency + + fungibles::Inspect> + + fungibles::Mutate> + + fungibles::Unbalanced> + + fungibles::InspectHold, Reason = ()> + + fungibles::MutateHold> + + fungibles::UnbalancedHold>; type NativeCurrency: BasicCurrencyExtended, Amount = AmountOf> + BasicLockableCurrency> - + BasicReservableCurrency>; + + BasicReservableCurrency> + + fungible::Inspect> + + fungible::Mutate> + + fungible::Unbalanced> + + fungible::InspectHold> + + fungible::MutateHold> + + fungible::UnbalancedHold>; /// The native currency id #[pallet::constant] type GetNativeCurrencyId: Get; + /// Used as temporary account for ERC20 token `withdraw` and `deposit`. + // TODO: See how to update this when we include the SialBridge; + #[pallet::constant] + type Erc20HoldingAccount: Get; + /// Weight information for extrinsics in this module. type WeightInfo: WeightInfo; /// Mapping from address to account id. type AddressMapping: AddressMapping; + + type EVMBridge: EVMBridge>; + /// Convert gas to weight. + type GasToWeight: Convert; + /// The AccountId that can perform a sweep dust. - type SweepOrigin: EnsureOrigin; + type SweepOrigin: EnsureOrigin; /// Handler to burn or transfer account's dust type OnDust: OnDust>; @@ -113,29 +131,45 @@ pub mod module { EvmAccountNotFound, /// Real origin not found RealOriginNotFound, + /// Deposit result is not expected + DepositFailed, } #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance", CurrencyIdOf = "CurrencyId")] + #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Currency transfer success. \[currency_id, from, to, amount\] - Transferred(CurrencyIdOf, T::AccountId, T::AccountId, BalanceOf), - /// Update balance success. \[currency_id, who, amount\] - BalanceUpdated(CurrencyIdOf, T::AccountId, AmountOf), - /// Deposit success. \[currency_id, who, amount\] - Deposited(CurrencyIdOf, T::AccountId, BalanceOf), - /// Withdraw success. \[currency_id, who, amount\] - Withdrawn(CurrencyIdOf, T::AccountId, BalanceOf), - /// Dust swept. \[currency_id, who, amount\] - DustSwept(CurrencyIdOf, T::AccountId, BalanceOf), + /// Currency transfer success. + Transferred { + currency_id: CurrencyId, + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + /// Withdrawn some balances from an account + Withdrawn { + currency_id: CurrencyId, + who: T::AccountId, + amount: BalanceOf, + }, + /// Deposited some balance into an account + Deposited { + currency_id: CurrencyId, + who: T::AccountId, + amount: BalanceOf, + }, + /// Dust swept. + DustSwept { + currency_id: CurrencyId, + who: T::AccountId, + amount: BalanceOf, + }, } #[pallet::pallet] pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks> for Pallet {} #[pallet::call] impl Pallet { @@ -143,25 +177,26 @@ pub mod module { /// /// The dispatch origin for this call must be `Signed` by the /// transactor. - #[pallet::weight(T::WeightInfo::transfer_non_native_currency())] + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::transfer_non_native_currency() + .saturating_add(if currency_id.is_erc20_currency_id() { T::GasToWeight::convert(erc20::TRANSFER.gas) } else { Weight::zero() }) + )] pub fn transfer( origin: OriginFor, dest: ::Source, - currency_id: CurrencyIdOf, + currency_id: CurrencyId, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; - >::transfer(currency_id, &from, &to, amount)?; - - Self::deposit_event(Event::Transferred(currency_id, from, to, amount)); - Ok(()) + >::transfer(currency_id, &from, &to, amount) } /// Transfer some native currency to another account. /// /// The dispatch origin for this call must be `Signed` by the /// transactor. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::transfer_native_currency())] pub fn transfer_native_currency( origin: OriginFor, @@ -170,32 +205,30 @@ pub mod module { ) -> DispatchResult { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; - T::NativeCurrency::transfer(&from, &to, amount)?; - - Self::deposit_event(Event::Transferred(T::GetNativeCurrencyId::get(), from, to, amount)); - Ok(()) + >::transfer(&from, &to, amount) } - /// update amount of account `who` under `currency_id`. + /// Update amount of account `who` under `currency_id`. /// /// The dispatch origin of this call must be _Root_. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::update_balance_non_native_currency())] pub fn update_balance( origin: OriginFor, who: ::Source, - currency_id: CurrencyIdOf, + currency_id: CurrencyId, amount: AmountOf, ) -> DispatchResult { ensure_root(origin)?; let dest = T::Lookup::lookup(who)?; - >::update_balance(currency_id, &dest, amount)?; - Ok(()) + >::update_balance(currency_id, &dest, amount) } + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::sweep_dust(accounts.len() as u32))] pub fn sweep_dust( origin: OriginFor, - currency_id: CurrencyIdOf, + currency_id: CurrencyId, accounts: Vec, ) -> DispatchResult { T::SweepOrigin::ensure_origin(origin)?; @@ -203,33 +236,78 @@ pub mod module { return Err(Error::::Erc20InvalidOperation.into()); } for account in accounts { - let free_balance = Self::free_balance(currency_id, &account); + let free_balance = >::free_balance(currency_id, &account); if free_balance.is_zero() { continue; } - let total_balance = Self::total_balance(currency_id, &account); + let total_balance = >::total_balance(currency_id, &account); if free_balance != total_balance { continue; } - if free_balance < Self::minimum_balance(currency_id) { + if free_balance < >::minimum_balance(currency_id) { T::OnDust::on_dust(&account, currency_id, free_balance); - Self::deposit_event(Event::DustSwept(currency_id, account, free_balance)); + Self::deposit_event(Event::::DustSwept { + currency_id, + who: account, + amount: free_balance, + }); } } Ok(()) } + + /// Set lock by lock_id + /// + /// The dispatch origin of this call must be _Root_. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::force_set_lock())] + pub fn force_set_lock( + origin: OriginFor, + who: ::Source, + currency_id: CurrencyId, + #[pallet::compact] amount: BalanceOf, + lock_id: LockIdentifier, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + >::set_lock(lock_id, currency_id, &who, amount) + } + + /// Remove lock by lock_id + /// + /// The dispatch origin of this call must be _Root_. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::force_remove_lock())] + pub fn force_remove_lock( + origin: OriginFor, + who: ::Source, + currency_id: CurrencyId, + lock_id: LockIdentifier, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + >::remove_lock(lock_id, currency_id, &who) + } + } +} + +// TODO: See how to update this when we include the SialBridge; +impl Pallet { + fn get_evm_origin() -> Result { + let origin = T::EVMBridge::get_real_or_xcm_origin().ok_or(Error::::RealOriginNotFound)?; + Ok(T::AddressMapping::get_or_create_evm_address(&origin)) } } impl MultiCurrency for Pallet { - type CurrencyId = CurrencyIdOf; + type CurrencyId = CurrencyId; type Balance = BalanceOf; fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { match currency_id { - CurrencyId::Erc20(_) => Default::default(), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::minimum_balance(), - _ => T::MultiCurrency::minimum_balance(currency_id), + CurrencyId::Erc20(_) => Zero::zero(), + id if id == T::GetNativeCurrencyId::get() => >::minimum_balance(), + _ => >::minimum_balance(currency_id), } } @@ -241,26 +319,20 @@ impl MultiCurrency for Pallet { origin: Default::default(), }) .unwrap_or_default(), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::total_issuance(), - _ => T::MultiCurrency::total_issuance(currency_id), + id if id == T::GetNativeCurrencyId::get() => >::total_issuance(), + _ => >::total_issuance(currency_id), } } fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { match currency_id { - CurrencyId::Erc20(contract) => { - if let Some(address) = T::AddressMapping::get_evm_address(who) { - let context = InvokeContext { - contract, - sender: Default::default(), - origin: Default::default(), - }; - return T::EVMBridge::balance_of(context, address).unwrap_or_default(); - } - Default::default() + CurrencyId::Erc20(_) => { + let free_balance = Self::free_balance(currency_id, who); + let reserved_balance = >::reserved_balance(currency_id, who); + free_balance.saturating_add(reserved_balance) } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::total_balance(who), - _ => T::MultiCurrency::total_balance(currency_id, who), + id if id == T::GetNativeCurrencyId::get() => >::total_balance(who), + _ => >::total_balance(currency_id, who), } } @@ -277,16 +349,20 @@ impl MultiCurrency for Pallet { } Default::default() } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::free_balance(who), - _ => T::MultiCurrency::free_balance(currency_id, who), + id if id == T::GetNativeCurrencyId::get() => >::free_balance(who), + _ => >::free_balance(currency_id, who), } } fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { match currency_id { CurrencyId::Erc20(contract) => { + if amount.is_zero() { + return Ok(()); + } + let address = T::AddressMapping::get_evm_address(who).ok_or(Error::::EvmAccountNotFound)?; - let balance = T::EVMBridge::balance_of( + let free_balance = T::EVMBridge::balance_of( InvokeContext { contract, sender: Default::default(), @@ -295,11 +371,13 @@ impl MultiCurrency for Pallet { address, ) .unwrap_or_default(); - ensure!(balance >= amount, Error::::BalanceTooLow); + ensure!(free_balance >= amount, Error::::BalanceTooLow); Ok(()) } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::ensure_can_withdraw(who, amount), - _ => T::MultiCurrency::ensure_can_withdraw(currency_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => { + >::ensure_can_withdraw(who, amount) + } + _ => >::ensure_can_withdraw(currency_id, who, amount), } } @@ -316,24 +394,29 @@ impl MultiCurrency for Pallet { match currency_id { CurrencyId::Erc20(contract) => { let sender = T::AddressMapping::get_evm_address(from).ok_or(Error::::EvmAccountNotFound)?; - let origin = T::EVMBridge::get_origin().ok_or(Error::::RealOriginNotFound)?; - let origin_address = T::AddressMapping::get_or_create_evm_address(&origin); let address = T::AddressMapping::get_or_create_evm_address(to); T::EVMBridge::transfer( InvokeContext { contract, sender, - origin: origin_address, + origin: Self::get_evm_origin()?, }, address, amount, )?; } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::transfer(from, to, amount)?, - _ => T::MultiCurrency::transfer(currency_id, from, to, amount)?, + id if id == T::GetNativeCurrencyId::get() => { + >::transfer(from, to, amount)? + } + _ => >::transfer(currency_id, from, to, amount)?, } - Self::deposit_event(Event::Transferred(currency_id, from.clone(), to.clone(), amount)); + Self::deposit_event(Event::Transferred { + currency_id, + from: from.clone(), + to: to.clone(), + amount, + }); Ok(()) } @@ -341,41 +424,100 @@ impl MultiCurrency for Pallet { if amount.is_zero() { return Ok(()); } + match currency_id { - CurrencyId::Erc20(_) => return Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::deposit(who, amount)?, - _ => T::MultiCurrency::deposit(currency_id, who, amount)?, + CurrencyId::Erc20(contract) => { + // TODO: See how to update this when we include the SialBridge; + // deposit from erc20 holding account to receiver(who). in xcm case which receive erc20 from another chain, + // we choose other chain's sovereign account to charge storage fee. we must make sure + // another chain sovereign account has enough native token to charge storage fee. + let sender = T::Erc20HoldingAccount::get(); + let from = T::AddressMapping::get_account_id(&sender); + ensure!( + !Self::free_balance(currency_id, &from).is_zero(), + Error::::DepositFailed + ); + let receiver = T::AddressMapping::get_or_create_evm_address(who); + T::EVMBridge::transfer( + InvokeContext { + contract, + sender, + origin: Self::get_evm_origin().unwrap_or(receiver), + }, + receiver, + amount, + )?; + Self::deposit_event(Event::Withdrawn { + currency_id, + who: from, + amount, + }); + Self::deposit_event(Event::Deposited { + currency_id, + who: who.clone(), + amount, + }); + Ok(()) + } + id if id == T::GetNativeCurrencyId::get() => >::deposit(who, amount), + _ => >::deposit(currency_id, who, amount), } - Self::deposit_event(Event::Deposited(currency_id, who.clone(), amount)); - Ok(()) } fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } + match currency_id { - CurrencyId::Erc20(_) => return Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::withdraw(who, amount)?, - _ => T::MultiCurrency::withdraw(currency_id, who, amount)?, + CurrencyId::Erc20(contract) => { + // TODO: See how to update this when we include the SialBridge; + // withdraw from sender(who) to erc20 holding account. in xcm case which receive erc20 from another chain, + // sender is other chain's sovereign account. As the origin here is used to charge storage fee, + // we must make sure the other chain's sovereign account has enough native token to charge storage fee. + let receiver = T::Erc20HoldingAccount::get(); + let sender = T::AddressMapping::get_evm_address(who).ok_or(Error::::EvmAccountNotFound)?; + T::EVMBridge::transfer( + InvokeContext { + contract, + sender, + origin: Self::get_evm_origin().unwrap_or(sender), + }, + receiver, + amount, + )?; + Self::deposit_event(Event::Withdrawn { + currency_id, + who: who.clone(), + amount, + }); + Self::deposit_event(Event::Deposited { + currency_id, + who: T::AddressMapping::get_account_id(&receiver), + amount, + }); + Ok(()) + } + id if id == T::GetNativeCurrencyId::get() => >::withdraw(who, amount), + _ => >::withdraw(currency_id, who, amount), } - Self::deposit_event(Event::Withdrawn(currency_id, who.clone(), amount)); - Ok(()) } fn can_slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> bool { match currency_id { - CurrencyId::Erc20(_) => false, - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::can_slash(who, amount), - _ => T::MultiCurrency::can_slash(currency_id, who, amount), + CurrencyId::Erc20(_) => amount.is_zero(), + id if id == T::GetNativeCurrencyId::get() => { + >::can_slash(who, amount) + } + _ => >::can_slash(currency_id, who, amount), } } fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance { match currency_id { CurrencyId::Erc20(_) => Default::default(), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::slash(who, amount), - _ => T::MultiCurrency::slash(currency_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => >::slash(who, amount), + _ => >::slash(currency_id, who, amount), } } } @@ -385,17 +527,23 @@ impl MultiCurrencyExtended for Pallet { fn update_balance(currency_id: Self::CurrencyId, who: &T::AccountId, by_amount: Self::Amount) -> DispatchResult { match currency_id { - CurrencyId::Erc20(_) => return Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::update_balance(who, by_amount)?, - _ => T::MultiCurrency::update_balance(currency_id, who, by_amount)?, + CurrencyId::Erc20(_) => { + if by_amount.is_zero() { + Ok(()) + } else { + Err(Error::::Erc20InvalidOperation.into()) + } + } + id if id == T::GetNativeCurrencyId::get() => { + >::update_balance(who, by_amount) + } + _ => >::update_balance(currency_id, who, by_amount), } - Self::deposit_event(Event::BalanceUpdated(currency_id, who.clone(), by_amount)); - Ok(()) } } impl MultiLockableCurrency for Pallet { - type Moment = T::BlockNumber; + type Moment = BlockNumberFor; fn set_lock( lock_id: LockIdentifier, @@ -405,8 +553,10 @@ impl MultiLockableCurrency for Pallet { ) -> DispatchResult { match currency_id { CurrencyId::Erc20(_) => Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::set_lock(lock_id, who, amount), - _ => T::MultiCurrency::set_lock(lock_id, currency_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => { + >::set_lock(lock_id, who, amount) + } + _ => >::set_lock(lock_id, currency_id, who, amount), } } @@ -418,16 +568,20 @@ impl MultiLockableCurrency for Pallet { ) -> DispatchResult { match currency_id { CurrencyId::Erc20(_) => Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::extend_lock(lock_id, who, amount), - _ => T::MultiCurrency::extend_lock(lock_id, currency_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => { + >::extend_lock(lock_id, who, amount) + } + _ => >::extend_lock(lock_id, currency_id, who, amount), } } fn remove_lock(lock_id: LockIdentifier, currency_id: Self::CurrencyId, who: &T::AccountId) -> DispatchResult { match currency_id { CurrencyId::Erc20(_) => Err(Error::::Erc20InvalidOperation.into()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::remove_lock(lock_id, who), - _ => T::MultiCurrency::remove_lock(lock_id, currency_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::remove_lock(lock_id, who) + } + _ => >::remove_lock(lock_id, currency_id, who), } } } @@ -436,16 +590,20 @@ impl MultiReservableCurrency for Pallet { fn can_reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool { match currency_id { CurrencyId::Erc20(_) => Self::ensure_can_withdraw(currency_id, who, value).is_ok(), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::can_reserve(who, value), - _ => T::MultiCurrency::can_reserve(currency_id, who, value), + id if id == T::GetNativeCurrencyId::get() => { + >::can_reserve(who, value) + } + _ => >::can_reserve(currency_id, who, value), } } fn slash_reserved(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance { match currency_id { CurrencyId::Erc20(_) => value, - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::slash_reserved(who, value), - _ => T::MultiCurrency::slash_reserved(currency_id, who, value), + id if id == T::GetNativeCurrencyId::get() => { + >::slash_reserved(who, value) + } + _ => >::slash_reserved(currency_id, who, value), } } @@ -465,8 +623,10 @@ impl MultiReservableCurrency for Pallet { } Default::default() } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::reserved_balance(who), - _ => T::MultiCurrency::reserved_balance(currency_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::reserved_balance(who) + } + _ => >::reserved_balance(currency_id, who), } } @@ -481,14 +641,16 @@ impl MultiReservableCurrency for Pallet { InvokeContext { contract, sender: address, - origin: address, + origin: Self::get_evm_origin().unwrap_or(address), }, reserve_address(address), value, ) } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::reserve(who, value), - _ => T::MultiCurrency::reserve(currency_id, who, value), + id if id == T::GetNativeCurrencyId::get() => { + >::reserve(who, value) + } + _ => >::reserve(currency_id, who, value), } } @@ -510,23 +672,26 @@ impl MultiReservableCurrency for Pallet { ) .unwrap_or_default(); let actual = reserved_balance.min(value); - return match T::EVMBridge::transfer( + match T::EVMBridge::transfer( InvokeContext { contract, sender, - origin: address, + origin: Self::get_evm_origin().unwrap_or(address), }, address, actual, ) { Ok(_) => value - actual, Err(_) => value, - }; + } + } else { + value } - value } - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::unreserve(who, value), - _ => T::MultiCurrency::unreserve(currency_id, who, value), + id if id == T::GetNativeCurrencyId::get() => { + >::unreserve(who, value) + } + _ => >::unreserve(currency_id, who, value), } } @@ -573,7 +738,7 @@ impl MultiReservableCurrency for Pallet { InvokeContext { contract, sender: slashed_reserve_address, - origin: slashed_address, + origin: Self::get_evm_origin().unwrap_or(slashed_address), }, beneficiary_address, actual, @@ -582,18 +747,420 @@ impl MultiReservableCurrency for Pallet { InvokeContext { contract, sender: slashed_reserve_address, - origin: slashed_address, + origin: Self::get_evm_origin().unwrap_or(slashed_address), }, beneficiary_reserve_address, actual, ), + }?; + Ok(value - actual) + } + id if id == T::GetNativeCurrencyId::get() => { + >::repatriate_reserved( + slashed, + beneficiary, + value, + status, + ) + } + _ => >::repatriate_reserved( + currency_id, + slashed, + beneficiary, + value, + status, + ), + } + } +} + +/// impl fungiles for Pallet +impl fungibles::Inspect for Pallet { + type AssetId = CurrencyId; + type Balance = BalanceOf; + + fn total_issuance(asset_id: Self::AssetId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::total_issuance(asset_id), + id if id == T::GetNativeCurrencyId::get() => >::total_issuance(), + _ => >::total_issuance(asset_id), + } + } + + fn minimum_balance(asset_id: Self::AssetId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::minimum_balance(asset_id), + id if id == T::GetNativeCurrencyId::get() => >::minimum_balance(), + _ => >::minimum_balance(asset_id), + } + } + + fn balance(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::free_balance(asset_id, who), + id if id == T::GetNativeCurrencyId::get() => >::balance(who), + _ => >::balance(asset_id, who), + } + } + + fn total_balance(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::total_balance(asset_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::total_balance(who) + } + _ => >::total_balance(asset_id, who), + } + } + + fn reducible_balance( + asset_id: Self::AssetId, + who: &T::AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::free_balance(asset_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::reducible_balance(who, preservation, force) + } + _ => >::reducible_balance(asset_id, who, preservation, force), + } + } + + fn can_deposit( + asset_id: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + match asset_id { + CurrencyId::Erc20(_) => { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if >::total_issuance(asset_id) + .checked_add(&amount) + .is_none() + { + return DepositConsequence::Overflow; + } + + if >::balance(asset_id, who).saturating_add(amount) + < >::minimum_balance(asset_id) + { + return DepositConsequence::BelowMinimum; + } + + DepositConsequence::Success + } + id if id == T::GetNativeCurrencyId::get() => { + >::can_deposit(who, amount, provenance) + } + _ => >::can_deposit(asset_id, who, amount, provenance), + } + } + + fn can_withdraw( + asset_id: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + match asset_id { + CurrencyId::Erc20(_) => match >::ensure_can_withdraw(asset_id, who, amount) { + Ok(()) => WithdrawConsequence::Success, + _ => WithdrawConsequence::BalanceLow, + }, + id if id == T::GetNativeCurrencyId::get() => { + >::can_withdraw(who, amount) + } + _ => >::can_withdraw(asset_id, who, amount), + } + } + + fn asset_exists(asset_id: Self::AssetId) -> bool { + match asset_id { + CurrencyId::Erc20(contract) => T::EVMBridge::symbol(InvokeContext { + contract, + sender: Default::default(), + origin: Default::default(), + }) + .is_ok(), + id if id == T::GetNativeCurrencyId::get() => true, + _ => >::asset_exists(asset_id), + } + } +} + +impl fungibles::Unbalanced for Pallet { + fn handle_dust(_dust: fungibles::Dust) { + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/support/src/traits/tokens/fungibles/regular.rs#L124 + // Note: currently the field of Dust type is private and there is no constructor for it, so + // we can't construct a Dust value and pass it. Do nothing here. + // `Pallet` overwrites these functions which can be called as user-level operation of + // fungibles traits when calling these functions, it will not actually reach + // `Unbalanced::handle_dust`. + } + + fn write_balance( + asset_id: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + match asset_id { + CurrencyId::Erc20(_) => Err(Error::::Erc20InvalidOperation.into()), + id if id == T::GetNativeCurrencyId::get() => { + >::write_balance(who, amount) + } + _ => >::write_balance(asset_id, who, amount), + } + } + + fn set_total_issuance(asset_id: Self::AssetId, amount: Self::Balance) { + match asset_id { + CurrencyId::Erc20(_) => {} + id if id == T::GetNativeCurrencyId::get() => { + >::set_total_issuance(amount) + } + _ => >::set_total_issuance(asset_id, amount), + } + } +} + +impl fungibles::Mutate for Pallet { + fn mint_into( + asset_id: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Result { + match asset_id { + CurrencyId::Erc20(_) => >::deposit(asset_id, who, amount).map(|_| amount), + id if id == T::GetNativeCurrencyId::get() => { + >::mint_into(who, amount) + } + _ => >::mint_into(asset_id, who, amount), + } + } + + fn burn_from( + asset_id: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + fortitude: Fortitude, + ) -> Result { + match asset_id { + CurrencyId::Erc20(_) => >::withdraw(asset_id, who, amount).map(|_| amount), + id if id == T::GetNativeCurrencyId::get() => { + >::burn_from(who, amount, precision, fortitude) + } + _ => >::burn_from(asset_id, who, amount, precision, fortitude), + } + } + + fn transfer( + asset_id: Self::AssetId, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + match asset_id { + CurrencyId::Erc20(_) => { + // Event is deposited in `fn transfer` + >::transfer(asset_id, source, dest, amount).map(|_| amount) + } + id if id == T::GetNativeCurrencyId::get() => { + >::transfer(source, dest, amount, preservation).map(|actual| { + Self::deposit_event(Event::Transferred { + currency_id: asset_id, + from: source.clone(), + to: dest.clone(), + amount: actual, + }); + actual + }) + } + _ => >::transfer(asset_id, source, dest, amount, preservation) + .map(|actual| { + Self::deposit_event(Event::Transferred { + currency_id: asset_id, + from: source.clone(), + to: dest.clone(), + amount: actual, + }); + actual + }), + } + } +} + +type ReasonOf =

::AccountId>>::Reason; +impl fungibles::InspectHold for Pallet { + type Reason = >::Reason; + + fn balance_on_hold(asset_id: Self::AssetId, reason: &Self::Reason, who: &T::AccountId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::reserved_balance(asset_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::balance_on_hold(reason, who) + } + _ => >::balance_on_hold(asset_id, &(), who), + } + } + + fn total_balance_on_hold(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => >::reserved_balance(asset_id, who), + id if id == T::GetNativeCurrencyId::get() => { + >::total_balance_on_hold(who) + } + _ => >::total_balance_on_hold(asset_id, who), + } + } + + fn reducible_total_balance_on_hold(asset_id: Self::AssetId, who: &T::AccountId, force: Fortitude) -> Self::Balance { + match asset_id { + CurrencyId::Erc20(_) => Zero::zero(), + id if id == T::GetNativeCurrencyId::get() => { + >::reducible_total_balance_on_hold(who, force) + } + _ => >::reducible_total_balance_on_hold(asset_id, who, force), + } + } + + fn hold_available(asset_id: Self::AssetId, reason: &Self::Reason, who: &T::AccountId) -> bool { + match asset_id { + CurrencyId::Erc20(_) => true, + id if id == T::GetNativeCurrencyId::get() => { + >::hold_available(reason, who) + } + _ => >::hold_available(asset_id, &(), who), + } + } + + fn can_hold(asset_id: Self::AssetId, reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) -> bool { + match asset_id { + CurrencyId::Erc20(_) => >::can_reserve(asset_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => { + >::can_hold(reason, who, amount) + } + _ => >::can_hold(asset_id, &(), who, amount), + } + } +} + +impl fungibles::UnbalancedHold for Pallet { + fn set_balance_on_hold( + asset_id: Self::AssetId, + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match asset_id { + CurrencyId::Erc20(_) => Err(Error::::Erc20InvalidOperation.into()), + id if id == T::GetNativeCurrencyId::get() => { + >::set_balance_on_hold(reason, who, amount) + } + _ => >::set_balance_on_hold(asset_id, &(), who, amount), + } + } +} + +impl fungibles::MutateHold for Pallet { + fn hold( + asset_id: Self::AssetId, + reason: &ReasonOf, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match asset_id { + CurrencyId::Erc20(_) => >::reserve(asset_id, who, amount), + id if id == T::GetNativeCurrencyId::get() => { + >::hold(reason, who, amount) + } + _ => >::hold(asset_id, &(), who, amount), + } + } + + fn release( + asset_id: Self::AssetId, + reason: &ReasonOf, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match asset_id { + CurrencyId::Erc20(_) => { + if amount.is_zero() { + return Ok(amount); + } + ensure!( + precision == Precision::BestEffort + || amount <= >::reserved_balance(asset_id, who), + Error::::BalanceTooLow + ); + let gap = >::unreserve(asset_id, who, amount); + Ok(amount.saturating_sub(gap)) + } + id if id == T::GetNativeCurrencyId::get() => { + >::release(reason, who, amount, precision) + } + _ => >::release(asset_id, &(), who, amount, precision), + } + } + + fn transfer_on_hold( + asset_id: Self::AssetId, + reason: &ReasonOf, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + precision: Precision, + restriction: Restriction, + fortitude: Fortitude, + ) -> Result { + match asset_id { + CurrencyId::Erc20(_) => { + if amount.is_zero() { + return Ok(amount); } - .map(|_| value - actual) + ensure!( + precision == Precision::BestEffort + || amount <= >::balance_on_hold(asset_id, reason, source), + Error::::BalanceTooLow + ); + + let status = match restriction { + Restriction::Free => Status::Free, + Restriction::OnHold => Status::Reserved, + }; + let gap = + >::repatriate_reserved(asset_id, source, dest, amount, status)?; + Ok(amount.saturating_sub(gap)) } id if id == T::GetNativeCurrencyId::get() => { - T::NativeCurrency::repatriate_reserved(slashed, beneficiary, value, status) + >::transfer_on_hold( + reason, + source, + dest, + amount, + precision, + restriction, + fortitude, + ) } - _ => T::MultiCurrency::repatriate_reserved(currency_id, slashed, beneficiary, value, status), + _ => >::transfer_on_hold( + asset_id, + &(), + source, + dest, + amount, + precision, + restriction, + fortitude, + ), } } } @@ -603,28 +1170,28 @@ pub struct Currency(marker::PhantomData, marker::PhantomDat impl BasicCurrency for Currency where T: Config, - GetCurrencyId: Get>, + GetCurrencyId: Get, { type Balance = BalanceOf; fn minimum_balance() -> Self::Balance { - >::minimum_balance(GetCurrencyId::get()) + as MultiCurrency>::minimum_balance(GetCurrencyId::get()) } fn total_issuance() -> Self::Balance { - >::total_issuance(GetCurrencyId::get()) + as MultiCurrency>::total_issuance(GetCurrencyId::get()) } fn total_balance(who: &T::AccountId) -> Self::Balance { - >::total_balance(GetCurrencyId::get(), who) + as MultiCurrency>::total_balance(GetCurrencyId::get(), who) } fn free_balance(who: &T::AccountId) -> Self::Balance { - >::free_balance(GetCurrencyId::get(), who) + as MultiCurrency>::free_balance(GetCurrencyId::get(), who) } fn ensure_can_withdraw(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - >::ensure_can_withdraw(GetCurrencyId::get(), who, amount) + as MultiCurrency>::ensure_can_withdraw(GetCurrencyId::get(), who, amount) } fn transfer(from: &T::AccountId, to: &T::AccountId, amount: Self::Balance) -> DispatchResult { @@ -632,26 +1199,26 @@ where } fn deposit(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - >::deposit(GetCurrencyId::get(), who, amount) + as MultiCurrency>::deposit(GetCurrencyId::get(), who, amount) } fn withdraw(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - >::withdraw(GetCurrencyId::get(), who, amount) + as MultiCurrency>::withdraw(GetCurrencyId::get(), who, amount) } fn can_slash(who: &T::AccountId, amount: Self::Balance) -> bool { - >::can_slash(GetCurrencyId::get(), who, amount) + as MultiCurrency>::can_slash(GetCurrencyId::get(), who, amount) } fn slash(who: &T::AccountId, amount: Self::Balance) -> Self::Balance { - >::slash(GetCurrencyId::get(), who, amount) + as MultiCurrency>::slash(GetCurrencyId::get(), who, amount) } } impl BasicCurrencyExtended for Currency where T: Config, - GetCurrencyId: Get>, + GetCurrencyId: Get, { type Amount = AmountOf; @@ -663,9 +1230,9 @@ where impl BasicLockableCurrency for Currency where T: Config, - GetCurrencyId: Get>, + GetCurrencyId: Get, { - type Moment = T::BlockNumber; + type Moment = BlockNumberFor; fn set_lock(lock_id: LockIdentifier, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { as MultiLockableCurrency>::set_lock(lock_id, GetCurrencyId::get(), who, amount) @@ -683,7 +1250,7 @@ where impl BasicReservableCurrency for Currency where T: Config, - GetCurrencyId: Get>, + GetCurrencyId: Get, { fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { as MultiReservableCurrency>::can_reserve(GetCurrencyId::get(), who, value) @@ -721,6 +1288,171 @@ where } } +/// impl fungile for Currency +impl fungible::Inspect for Currency +where + T: Config, + GetCurrencyId: Get, +{ + type Balance = BalanceOf; + + fn total_issuance() -> Self::Balance { + as fungibles::Inspect<_>>::total_issuance(GetCurrencyId::get()) + } + + fn minimum_balance() -> Self::Balance { + as fungibles::Inspect<_>>::minimum_balance(GetCurrencyId::get()) + } + + fn balance(who: &T::AccountId) -> Self::Balance { + as fungibles::Inspect<_>>::balance(GetCurrencyId::get(), who) + } + + fn total_balance(who: &T::AccountId) -> Self::Balance { + as fungibles::Inspect<_>>::total_balance(GetCurrencyId::get(), who) + } + + fn reducible_balance(who: &T::AccountId, preservation: Preservation, force: Fortitude) -> Self::Balance { + as fungibles::Inspect<_>>::reducible_balance(GetCurrencyId::get(), who, preservation, force) + } + + fn can_deposit(who: &T::AccountId, amount: Self::Balance, provenance: Provenance) -> DepositConsequence { + as fungibles::Inspect<_>>::can_deposit(GetCurrencyId::get(), who, amount, provenance) + } + + fn can_withdraw(who: &T::AccountId, amount: Self::Balance) -> WithdrawConsequence { + as fungibles::Inspect<_>>::can_withdraw(GetCurrencyId::get(), who, amount) + } +} + +impl fungible::Unbalanced for Currency +where + T: Config, + GetCurrencyId: Get, +{ + fn handle_dust(_dust: fungible::Dust) { + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/support/src/traits/tokens/fungibles/regular.rs#L124 + // Note: currently the field of Dust type is private and there is no constructor for it, so + // we can't construct a Dust value and pass it. Do nothing here. + // `Pallet` overwrites these functions which can be called as user-level operation of + // fungibles traits when calling these functions, it will not actually reach + // `Unbalanced::handle_dust`. + } + + fn write_balance(who: &T::AccountId, amount: Self::Balance) -> Result, DispatchError> { + as fungibles::Unbalanced<_>>::write_balance(GetCurrencyId::get(), who, amount) + } + + fn set_total_issuance(amount: Self::Balance) { + as fungibles::Unbalanced<_>>::set_total_issuance(GetCurrencyId::get(), amount) + } +} + +impl fungible::Mutate for Currency +where + T: Config, + GetCurrencyId: Get, +{ + fn mint_into(who: &T::AccountId, amount: Self::Balance) -> Result { + as fungibles::Mutate<_>>::mint_into(GetCurrencyId::get(), who, amount) + } + + fn burn_from( + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + fortitude: Fortitude, + ) -> Result { + as fungibles::Mutate<_>>::burn_from(GetCurrencyId::get(), who, amount, precision, fortitude) + } + + fn transfer( + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + as fungibles::Mutate<_>>::transfer(GetCurrencyId::get(), source, dest, amount, preservation) + } +} + +impl fungible::InspectHold for Currency +where + T: Config, + GetCurrencyId: Get, +{ + type Reason = ReasonOf, T>; + + fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> Self::Balance { + as fungibles::InspectHold<_>>::balance_on_hold(GetCurrencyId::get(), reason, who) + } + fn total_balance_on_hold(who: &T::AccountId) -> Self::Balance { + as fungibles::InspectHold<_>>::total_balance_on_hold(GetCurrencyId::get(), who) + } + fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { + as fungibles::InspectHold<_>>::reducible_total_balance_on_hold(GetCurrencyId::get(), who, force) + } + fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { + as fungibles::InspectHold<_>>::hold_available(GetCurrencyId::get(), reason, who) + } + fn can_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) -> bool { + as fungibles::InspectHold<_>>::can_hold(GetCurrencyId::get(), reason, who, amount) + } +} + +type ReasonOfFungible =

::AccountId>>::Reason; +impl fungible::UnbalancedHold for Currency +where + T: Config, + GetCurrencyId: Get, +{ + fn set_balance_on_hold( + reason: &ReasonOfFungible, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + as fungibles::UnbalancedHold<_>>::set_balance_on_hold(GetCurrencyId::get(), reason, who, amount) + } +} + +impl fungible::MutateHold for Currency +where + T: Config, + GetCurrencyId: Get, +{ + fn hold(reason: &ReasonOfFungible, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + as fungibles::MutateHold<_>>::hold(GetCurrencyId::get(), reason, who, amount) + } + fn release( + reason: &ReasonOfFungible, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + as fungibles::MutateHold<_>>::release(GetCurrencyId::get(), reason, who, amount, precision) + } + fn transfer_on_hold( + reason: &ReasonOfFungible, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + precision: Precision, + restriction: Restriction, + fortitude: Fortitude, + ) -> Result { + as fungibles::MutateHold<_>>::transfer_on_hold( + GetCurrencyId::get(), + reason, + source, + dest, + amount, + precision, + restriction, + fortitude, + ) + } +} + /// Adapt other currency traits implementation to `BasicCurrency`. pub struct BasicCurrencyAdapter(marker::PhantomData<(T, Currency, Amount, Moment)>); @@ -736,19 +1468,19 @@ where type Balance = PalletBalanceOf; fn minimum_balance() -> Self::Balance { - Currency::minimum_balance() + >::minimum_balance() } fn total_issuance() -> Self::Balance { - Currency::total_issuance() + >::total_issuance() } fn total_balance(who: &AccountId) -> Self::Balance { - Currency::total_balance(who) + >::total_balance(who) } fn free_balance(who: &AccountId) -> Self::Balance { - Currency::free_balance(who) + >::free_balance(who) } fn ensure_can_withdraw(who: &AccountId, amount: Self::Balance) -> DispatchResult { @@ -756,28 +1488,34 @@ where .checked_sub(&amount) .ok_or(Error::::BalanceTooLow)?; - Currency::ensure_can_withdraw(who, amount, WithdrawReasons::all(), new_balance) + >::ensure_can_withdraw(who, amount, WithdrawReasons::all(), new_balance) } fn transfer(from: &AccountId, to: &AccountId, amount: Self::Balance) -> DispatchResult { - Currency::transfer(from, to, amount, ExistenceRequirement::AllowDeath) + >::transfer(from, to, amount, ExistenceRequirement::AllowDeath) } fn deposit(who: &AccountId, amount: Self::Balance) -> DispatchResult { - let _ = Currency::deposit_creating(who, amount); + if !amount.is_zero() { + let deposit_result = >::deposit_creating(who, amount); + let actual_deposit = deposit_result.peek(); + ensure!(actual_deposit == amount, Error::::DepositFailed); + } + Ok(()) } fn withdraw(who: &AccountId, amount: Self::Balance) -> DispatchResult { - Currency::withdraw(who, amount, WithdrawReasons::all(), ExistenceRequirement::AllowDeath).map(|_| ()) + >::withdraw(who, amount, WithdrawReasons::all(), ExistenceRequirement::AllowDeath) + .map(|_| ()) } fn can_slash(who: &AccountId, amount: Self::Balance) -> bool { - Currency::can_slash(who, amount) + >::can_slash(who, amount) } fn slash(who: &AccountId, amount: Self::Balance) -> Self::Balance { - let (_, gap) = Currency::slash(who, amount); + let (_, gap) = >::slash(who, amount); gap } } @@ -794,7 +1532,8 @@ where + Copy + MaybeSerializeDeserialize + Debug - + Default, + + Default + + MaxEncodedLen, Currency: PalletCurrency, T: Config, { @@ -823,17 +1562,17 @@ where type Moment = Moment; fn set_lock(lock_id: LockIdentifier, who: &AccountId, amount: Self::Balance) -> DispatchResult { - Currency::set_lock(lock_id, who, amount, WithdrawReasons::all()); + >::set_lock(lock_id, who, amount, WithdrawReasons::all()); Ok(()) } fn extend_lock(lock_id: LockIdentifier, who: &AccountId, amount: Self::Balance) -> DispatchResult { - Currency::extend_lock(lock_id, who, amount, WithdrawReasons::all()); + >::extend_lock(lock_id, who, amount, WithdrawReasons::all()); Ok(()) } fn remove_lock(lock_id: LockIdentifier, who: &AccountId) -> DispatchResult { - Currency::remove_lock(lock_id, who); + >::remove_lock(lock_id, who); Ok(()) } } @@ -846,24 +1585,24 @@ where T: Config, { fn can_reserve(who: &AccountId, value: Self::Balance) -> bool { - Currency::can_reserve(who, value) + >::can_reserve(who, value) } fn slash_reserved(who: &AccountId, value: Self::Balance) -> Self::Balance { - let (_, gap) = Currency::slash_reserved(who, value); + let (_, gap) = >::slash_reserved(who, value); gap } fn reserved_balance(who: &AccountId) -> Self::Balance { - Currency::reserved_balance(who) + >::reserved_balance(who) } fn reserve(who: &AccountId, value: Self::Balance) -> DispatchResult { - Currency::reserve(who, value) + >::reserve(who, value) } fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance { - Currency::unreserve(who, value) + >::unreserve(who, value) } fn repatriate_reserved( @@ -872,7 +1611,171 @@ where value: Self::Balance, status: BalanceStatus, ) -> result::Result { - Currency::repatriate_reserved(slashed, beneficiary, value, status) + >::repatriate_reserved(slashed, beneficiary, value, status) + } +} + +/// impl fungile for Currency +type FungibleBalanceOf = >::Balance; +impl fungible::Inspect for BasicCurrencyAdapter +where + Currency: fungible::Inspect, + T: Config, +{ + type Balance = FungibleBalanceOf; + + fn total_issuance() -> Self::Balance { + >::total_issuance() + } + fn minimum_balance() -> Self::Balance { + >::minimum_balance() + } + fn balance(who: &T::AccountId) -> Self::Balance { + >::balance(who) + } + fn total_balance(who: &T::AccountId) -> Self::Balance { + >::total_balance(who) + } + fn reducible_balance(who: &T::AccountId, preservation: Preservation, force: Fortitude) -> Self::Balance { + >::reducible_balance(who, preservation, force) + } + fn can_deposit(who: &T::AccountId, amount: Self::Balance, provenance: Provenance) -> DepositConsequence { + >::can_deposit(who, amount, provenance) + } + fn can_withdraw(who: &T::AccountId, amount: Self::Balance) -> WithdrawConsequence { + >::can_withdraw(who, amount) + } +} + +impl fungible::Unbalanced + for BasicCurrencyAdapter +where + Currency: fungible::Unbalanced, + T: Config, +{ + fn handle_dust(_dust: fungible::Dust) { + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/support/src/traits/tokens/fungibles/regular.rs#L124 + // Note: currently the field of Dust type is private and there is no constructor for it, so + // we can't construct a Dust value and pass it. + // `BasicCurrencyAdapter` overwrites these functions which can be called as user-level + // operation of fungible traits when calling these functions, it will not actually reach + // `Unbalanced::handle_dust`. + } + + fn write_balance(who: &T::AccountId, amount: Self::Balance) -> Result, DispatchError> { + >::write_balance(who, amount) + } + + fn set_total_issuance(amount: Self::Balance) { + >::set_total_issuance(amount) + } +} + +impl fungible::Mutate for BasicCurrencyAdapter +where + Currency: fungible::Mutate, + T: Config, +{ + fn mint_into(who: &T::AccountId, amount: Self::Balance) -> Result { + >::mint_into(who, amount) + } + + fn burn_from( + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + fortitude: Fortitude, + ) -> Result { + >::burn_from(who, amount, precision, fortitude) + } + + fn transfer( + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + >::transfer(source, dest, amount, preservation) + } +} + +impl fungible::InspectHold + for BasicCurrencyAdapter +where + Currency: fungible::InspectHold, + T: Config, +{ + type Reason = >::Reason; + + fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> Self::Balance { + >::balance_on_hold(reason, who) + } + fn total_balance_on_hold(who: &T::AccountId) -> Self::Balance { + >::total_balance_on_hold(who) + } + fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { + >::reducible_total_balance_on_hold(who, force) + } + fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { + >::hold_available(reason, who) + } + fn can_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) -> bool { + >::can_hold(reason, who, amount) + } +} + +impl fungible::UnbalancedHold + for BasicCurrencyAdapter +where + Currency: fungible::UnbalancedHold, + T: Config, +{ + fn set_balance_on_hold( + reason: &ReasonOfFungible, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + >::set_balance_on_hold(reason, who, amount) + } +} + +impl fungible::MutateHold + for BasicCurrencyAdapter +where + Currency: fungible::MutateHold, + T: Config, +{ + fn hold(reason: &ReasonOfFungible, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + >::hold(reason, who, amount) + } + + fn release( + reason: &ReasonOfFungible, + who: &T::AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + >::release(reason, who, amount, precision) + } + + fn transfer_on_hold( + reason: &ReasonOfFungible, + source: &T::AccountId, + dest: &T::AccountId, + amount: Self::Balance, + precision: Precision, + restriction: Restriction, + fortitude: Fortitude, + ) -> Result { + >::transfer_on_hold( + reason, + source, + dest, + amount, + precision, + restriction, + fortitude, + ) } } @@ -880,10 +1783,14 @@ impl TransferAll for Pallet { #[transactional] fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { // transfer non-native free to dest - T::MultiCurrency::transfer_all(source, dest)?; + >::transfer_all(source, dest)?; // transfer all free to dest - T::NativeCurrency::transfer(source, dest, T::NativeCurrency::free_balance(source)) + >::transfer( + source, + dest, + >::free_balance(source), + ) } } @@ -893,18 +1800,20 @@ fn reserve_address(address: EvmAddress) -> EvmAddress { } pub struct TransferDust(marker::PhantomData<(T, GetAccountId)>); -impl OnDust, BalanceOf> for TransferDust +impl OnDust> for TransferDust where T: Config, GetAccountId: Get, { - fn on_dust(who: &T::AccountId, currency_id: CurrencyIdOf, amount: BalanceOf) { + fn on_dust(who: &T::AccountId, currency_id: CurrencyId, amount: BalanceOf) { // transfer the dust to treasury account, ignore the result, // if failed will leave some dust which still could be recycled. let _ = match currency_id { CurrencyId::Erc20(_) => Ok(()), - id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::transfer(who, &GetAccountId::get(), amount), - _ => T::MultiCurrency::transfer(currency_id, who, &GetAccountId::get(), amount), + id if id == T::GetNativeCurrencyId::get() => { + >::transfer(who, &GetAccountId::get(), amount) + } + _ => >::transfer(currency_id, who, &GetAccountId::get(), amount), }; } } diff --git a/blockchain/modules/currencies/src/mock.rs b/blockchain/modules/currencies/src/mock.rs index 0d29082ea..c76da11a8 100644 --- a/blockchain/modules/currencies/src/mock.rs +++ b/blockchain/modules/currencies/src/mock.rs @@ -22,61 +22,44 @@ #![cfg(test)] -use frame_support::{assert_ok, ord_parameter_types, parameter_types, traits::GenesisBuild, PalletId}; -use orml_traits::parameter_type_with_key; -use primitives::{CurrencyId, ReserveIdentifier, TokenSymbol}; +use super::*; +pub use crate as currencies; + +use frame_support::{ + assert_ok, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockAddressMapping, AddressMapping}; +use orml_traits::{currency::MutationHooks, parameter_type_with_key}; +use primitives::{evm::convert_decimals_to_evm, CurrencyId, ReserveIdentifier, TokenSymbol}; use sp_core::H256; +use sp_core::{H160, U256}; use sp_runtime::{ testing::Header, - traits::{AccountIdConversion, IdentityLookup, One}, - AccountId32, Perbill, FixedPointNumber, + traits::{AccountIdConversion, IdentityLookup}, + AccountId32, BuildStorage, }; -use sp_std::cell::RefCell; -use support::{ - mocks::MockAddressMapping, - AddressMapping, DEXManager, - Ratio, Price, PriceProvider, - SwapLimit -}; - -use super::*; -use frame_system::EnsureSignedBy; -use sp_core::{bytes::from_hex, H160}; use sp_std::str::FromStr; -pub use crate as currencies; - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: u32 = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} +pub const CHARLIE: AccountId = AccountId32::new([6u8; 32]); +pub const DAVE: AccountId = AccountId32::new([7u8; 32]); +pub const EVE: AccountId = AccountId32::new([8u8; 32]); +pub const FERDIE: AccountId = AccountId32::new([9u8; 32]); pub type AccountId = AccountId32; -pub type BlockNumber = u64; - -// Currencies constants - CurrencyId/TokenSymbol -pub const DNAR: CurrencyId = CurrencyId::Token(TokenSymbol::DNAR); -pub const HELP: CurrencyId = CurrencyId::Token(TokenSymbol::HELP); -pub const SETR: CurrencyId = CurrencyId::Token(TokenSymbol::SETR); -pub const SETUSD: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); - -pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); -pub const X_TOKEN_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); - impl frame_system::Config for Runtime { - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = BlockNumber; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; type Hash = H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type BlockWeights = (); type BlockLength = (); type Version = (); @@ -85,73 +68,90 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = (); + type BaseCallFilter = Everything; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } type Balance = u128; parameter_type_with_key! { pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { - if *currency_id == DNAR { return 2; } + if *currency_id == EDF { return 2; } Default::default() }; } parameter_types! { - pub DustAccount: AccountId = PalletId(*b"srml/dst").into_account(); - pub const MaxLocks: u32 = 100; -} - -impl tokens::Config for Runtime { - type Event = Event; + pub DustAccount: AccountId = PalletId(*b"orml/dst").into_account_truncating(); +} + +pub struct CurrencyHooks(marker::PhantomData); +impl MutationHooks for CurrencyHooks +where + T::AccountId: From, +{ + type OnDust = orml_tokens::TransferDust; + type OnSlash = (); + type PreDeposit = (); + type PostDeposit = (); + type PreTransfer = (); + type PostTransfer = (); + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type Balance = Balance; type Amount = i64; type CurrencyId = CurrencyId; type ExistentialDeposits = ExistentialDeposits; - type OnDust = tokens::TransferDust; + type CurrencyHooks = CurrencyHooks; type WeightInfo = (); - type MaxLocks = MaxLocks; - type DustRemovalWhitelist = (); + type MaxLocks = ConstU32<100>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; } -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; -} +pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const X_TOKEN_ID: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); parameter_types! { - pub const ExistentialDeposit: u64 = 2; - pub const MaxReserves: u32 = 50; + pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; } impl pallet_balances::Config for Runtime { type Balance = Balance; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<2>; + type AccountStore = module_support::SystemAccountStore; type MaxLocks = (); - type MaxReserves = MaxReserves; + type MaxReserves = ConstU32<50>; type ReserveIdentifier = ReserveIdentifier; type WeightInfo = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<1>; + type MaxFreezes = (); } pub type PalletBalances = pallet_balances::Pallet; -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<1000>; type WeightInfo = (); } parameter_types! { - pub const NewContractExtraBytes: u32 = 1; pub NetworkContractSource: H160 = alice_evm_addr(); } @@ -159,32 +159,43 @@ ord_parameter_types! { pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); - pub const StorageDepositPerByte: u128 = 10; + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); + pub const TxFeePerGas: u128 = 10; pub const DeveloperDeposit: u64 = 1000; - pub const DeploymentFee: u64 = 200; + pub const PublicationFee: u64 = 200; +} + +pub struct GasToWeight; +impl Convert for GasToWeight { + fn convert(a: u64) -> Weight { + Weight::from_parts(a, 0) + } } impl module_evm::Config for Runtime { type AddressMapping = MockAddressMapping; type Currency = PalletBalances; type TransferAll = (); - type NewContractExtraBytes = NewContractExtraBytes; + type NewContractExtraBytes = ConstU32<1>; type StorageDepositPerByte = StorageDepositPerByte; - type Event = Event; - type Precompiles = (); - type ChainId = (); - type GasToWeight = (); - type ChargeTransactionPayment = (); + type TxFeePerGas = TxFeePerGas; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; type NetworkContractOrigin = EnsureSignedBy; type NetworkContractSource = NetworkContractSource; type DeveloperDeposit = DeveloperDeposit; - type DeploymentFee = DeploymentFee; + type PublicationFee = PublicationFee; type TreasuryAccount = TreasuryAccount; - type FreeDeploymentOrigin = EnsureSignedBy; + type FreePublicationOrigin = EnsureSignedBy; type Runner = module_evm::runner::stack::Runner; type FindAuthor = (); + type Task = (); + type IdleScheduler = (); type WeightInfo = (); } @@ -192,141 +203,20 @@ impl module_evm_bridge::Config for Runtime { type EVM = EVM; } -pub struct MockDEX; -impl DEXManager for MockDEX { - fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { - match (currency_id_a, currency_id_b) { - (SETUSD, DNAR) => (10000, 200), - _ => (0, 0), - } - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - unimplemented!() - } - - fn get_swap_amount(_: &[CurrencyId], _: SwapLimit) -> Option<(Balance, Balance)> { - unimplemented!() - } - - fn get_best_price_swap_path( - _: CurrencyId, - _: CurrencyId, - _: SwapLimit, - _: Vec>, - ) -> Option> { - unimplemented!() - } - - fn swap_with_specific_path( - _: &AccountId, - _: &[CurrencyId], - _: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } - - fn buyback_swap_with_specific_path( - _: &AccountId, - _: &[CurrencyId], - _: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } - - fn swap_with_exact_target( - _who: &AccountId, - _path: &[CurrencyId], - _exact_target_amount: Balance, - _max_supply_amount: Balance, - ) -> DispatchResult { - unimplemented!() - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { - unimplemented!() - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } -} - -thread_local! { - static RELATIVE_PRICE: RefCell> = RefCell::new(Some(Price::one())); -} - -pub struct MockPriceSource; -impl MockPriceSource { - pub fn _set_relative_price(price: Option) { - RELATIVE_PRICE.with(|v| *v.borrow_mut() = price); - } -} -impl PriceProvider for MockPriceSource { - - fn get_relative_price(_base: CurrencyId, _quota: CurrencyId) -> Option { - RELATIVE_PRICE.with(|v| *v.borrow_mut()) - } - - fn get_price(_currency_id: CurrencyId) -> Option { - None - } - -} - -parameter_type_with_key! { - pub GetStableCurrencyMinimumSupply: |currency_id: CurrencyId| -> Balance { - match currency_id { - &SETR => 10_000, - &SETUSD => 10_000, - _ => 0, - } - }; -} - parameter_types! { - pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(1, 2); - pub AlternativeSwapPathJointList: Vec> = vec![ - vec![DNAR], - ]; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![SETR, DNAR], - vec![SETUSD, SETR, DNAR] - ]; - pub const TradingPathLimit: u32 = 4; - pub StableCurrencyInflationPeriod: u64 = 5; - pub SetterMinimumClaimableTransferAmounts: Balance = 2; - pub SetterMaximumClaimableTransferAmounts: Balance = 200; - pub SetDollarMinimumClaimableTransferAmounts: Balance = 2; - pub SetDollarMaximumClaimableTransferAmounts: Balance = 200; -} - -ord_parameter_types! { - pub const Root: AccountId = alice(); + pub Erc20HoldingAccount: H160 = primitives::evm::ERC20_HOLDING_ACCOUNT; } impl Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type MultiCurrency = Tokens; type NativeCurrency = AdaptedBasicCurrency; type GetNativeCurrencyId = GetNativeCurrencyId; + type Erc20HoldingAccount = Erc20HoldingAccount; type WeightInfo = (); type AddressMapping = MockAddressMapping; - type EVMBridge = EVMBridge; + type EVMBridge = module_evm_bridge::EVMBridge; + type GasToWeight = GasToWeight; type SweepOrigin = EnsureSignedBy; type OnDust = crate::TransferDust; } @@ -337,20 +227,16 @@ pub type AdaptedBasicCurrency = BasicCurrencyAdapter; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Tokens: tokens::{Pallet, Storage, Event, Config}, - Currencies: currencies::{Pallet, Call, Event}, - EVM: module_evm::{Pallet, Config, Call, Storage, Event}, - EVMBridge: module_evm_bridge::{Pallet}, + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + Tokens: orml_tokens, + Currencies: currencies, + EVM: module_evm, + EVMBridge: module_evm_bridge, } ); @@ -381,34 +267,52 @@ pub fn eva_evm_addr() -> EvmAddress { pub const ID_1: LockIdentifier = *b"1 "; pub fn erc20_address() -> EvmAddress { - EvmAddress::from_str("0000000000000000000000000000000002000000").unwrap() + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() } +pub fn erc20_address_not_exist() -> EvmAddress { + EvmAddress::from_str("0x00ddfce53ee040d9eb21afbc0ae1bb4dbb0ba600").unwrap() +} + +pub const ALICE_BALANCE: u128 = 100_000_000_000_000_000_000_000u128; + pub fn deploy_contracts() { - let code = from_hex(include!("../../../..//evm-bridge/src/erc20_demo_contract")).unwrap(); - assert_ok!(EVM::create_network_contract( - Origin::signed(NetworkContractAccount::get()), + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), code, 0, 2_100_000, - 10000 + 10_000, + vec![] )); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { - address: H160::from_str("0x0000000000000000000000000000000002000000").unwrap(), + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), ], - data: H256::from_low_u64_be(10000).as_bytes().to_vec(), + data: { + let mut buf = [0u8; 32]; + U256::from(ALICE_BALANCE).to_big_endian(&mut buf); + H256::from_slice(&buf).as_bytes().to_vec() + }, }], - ))); + used_gas: 1235455, + used_storage: 5131, + })); - assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); } pub struct ExtBuilder { @@ -437,8 +341,8 @@ impl ExtBuilder { } pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() + let mut t = frame_system::GenesisConfig::::default() + .build_storage() .unwrap(); pallet_balances::GenesisConfig:: { @@ -453,7 +357,7 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - tokens::GenesisConfig:: { + orml_tokens::GenesisConfig:: { balances: self .balances .into_iter() diff --git a/blockchain/modules/currencies/src/tests.rs b/blockchain/modules/currencies/src/tests.rs index e681207f6..813b48437 100644 --- a/blockchain/modules/currencies/src/tests.rs +++ b/blockchain/modules/currencies/src/tests.rs @@ -23,15 +23,338 @@ #![cfg(test)] use super::*; -use frame_support::{assert_noop, assert_ok}; +use crate::mock::Erc20HoldingAccount; +use frame_support::{assert_noop, assert_ok, dispatch::GetDispatchInfo, traits::WithdrawReasons}; use mock::{ - alice, bob, deploy_contracts, erc20_address, eva, AccountId, AdaptedBasicCurrency, CouncilAccount, Currencies, - DustAccount, Event, ExtBuilder, NativeCurrency, Origin, PalletBalances, Runtime, System, Tokens, DNAR, EVM, ID_1, + alice, bob, deploy_contracts, erc20_address, erc20_address_not_exist, eva, AccountId, AdaptedBasicCurrency, + Balances, CouncilAccount, Currencies, DustAccount, ExtBuilder, NativeCurrency, PalletBalances, Runtime, + RuntimeEvent, RuntimeOrigin, System, Tokens, ALICE_BALANCE, CHARLIE, DAVE, EDF, EVE, EVM, FERDIE, ID_1, NATIVE_CURRENCY_ID, X_TOKEN_ID, }; +use module_support::mocks::MockAddressMapping; +use module_support::EVM as EVMTrait; use sp_core::H160; -use sp_runtime::traits::BadOrigin; -use support::EVM as EVMTrait; +use sp_runtime::{ + traits::{BadOrigin, Bounded}, + ModuleError, TokenError, +}; + +// this test displays the ED and provider/consumer behavior of current pallet-balances +#[test] +fn test_balances_provider() { + ExtBuilder::default().build().execute_with(|| { + // inc_providers to initialize a account directly (it occurs create contract) + assert_eq!(System::account_exists(&DAVE), false); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (0, 0)); + assert_eq!(System::inc_providers(&DAVE), frame_system::IncRefStatus::Created); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (1, 0)); + assert_eq!(System::account_exists(&DAVE), true); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (1, 0)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (0, 0) + ); + + // creat CHARLIE by creating + let _ = >::deposit_creating(&CHARLIE, 10000); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 0)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (10000, 0) + ); + + // transfer to already existed DAVE but receive amount + free_balance < ED + assert_noop!( + >::transfer(&CHARLIE, &DAVE, 1, ExistenceRequirement::AllowDeath), + TokenError::BelowMinimum + ); + + // transfer to already existed DAVE but receive amount + free_balance >= ED + assert_ok!(>::transfer( + &CHARLIE, + &DAVE, + 100, + ExistenceRequirement::AllowDeath + )); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (2, 0)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (100, 0) + ); + + // reserve and after reserved_amount below ED for CHARLIE + assert_ok!(>::reserve(&CHARLIE, 1)); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (9899, 1) + ); + assert_ok!(>::reserve(&CHARLIE, 899)); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (9000, 900) + ); + + // reserve and after free_balance below ED for CHARLIE + assert_noop!( + >::reserve(&CHARLIE, 8999), + DispatchError::ConsumerRemaining + ); + + // reserve and after reserved_amount below ED for DAVE + assert_ok!(>::reserve(&DAVE, 1)); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (2, 1)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (99, 1) + ); + + // reserve and after free_balance is below ED for DAVE, will dec provider + // but not dust. + assert_ok!(>::reserve(&DAVE, 98)); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (1, 99) + ); + + // reserve and after free_balance is zero for DAVE + assert_ok!(>::reserve(&DAVE, 1)); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (0, 100) + ); + + // transfer to DAVE but receive amount + free_balance < ED + assert_noop!( + >::transfer(&CHARLIE, &DAVE, 1, ExistenceRequirement::AllowDeath), + TokenError::BelowMinimum + ); + + // can use repatriate_reserved to transfer reserved balance to receiver's free, even if + // free_balance + repatriate amount < ED, it will succeed! + assert_eq!( + >::repatriate_reserved(&CHARLIE, &DAVE, 1, BalanceStatus::Free), + Ok(0) + ); + assert_eq!((System::providers(&DAVE), System::consumers(&DAVE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&DAVE), + >::reserved_balance(&DAVE) + ), + (1, 100) + ); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (9000, 899) + ); + + assert_eq!(System::account_exists(&EVE), false); + assert_eq!((System::providers(&EVE), System::consumers(&EVE)), (0, 0)); + assert_eq!( + ( + >::free_balance(&EVE), + >::reserved_balance(&EVE) + ), + (0, 0) + ); + + // inc_provider to initialize EVE + assert_eq!(System::inc_providers(&EVE), frame_system::IncRefStatus::Created); + assert_eq!(System::account_exists(&EVE), true); + assert_eq!((System::providers(&EVE), System::consumers(&EVE)), (1, 0)); + assert_eq!( + ( + >::free_balance(&EVE), + >::reserved_balance(&EVE) + ), + (0, 0) + ); + + // repatriate_reserved try to transfer amount reserved balance to EVE's reserved balance + // will succeed, even if reserved_balance + amount < ED. the benificiary will not be dust + // for its non-zero reserved balance + assert_eq!( + >::repatriate_reserved(&CHARLIE, &EVE, 1, BalanceStatus::Reserved), + Ok(0) + ); + assert_eq!((System::providers(&EVE), System::consumers(&EVE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&EVE), + >::reserved_balance(&EVE) + ), + (0, 1) + ); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (9000, 898) + ); + + assert_eq!(System::inc_providers(&FERDIE), frame_system::IncRefStatus::Created); + assert_eq!(System::account_exists(&FERDIE), true); + assert_eq!((System::providers(&FERDIE), System::consumers(&FERDIE)), (1, 0)); + assert_eq!( + ( + >::free_balance(&FERDIE), + >::reserved_balance(&FERDIE) + ), + (0, 0) + ); + + // repatriate_reserved try to transfer amount reserved balance to FERDIE's free balance + // will succeed, but if free_balance + amount < ED. the benificiary will be act as dust. + assert_eq!( + >::repatriate_reserved(&CHARLIE, &FERDIE, 1, BalanceStatus::Free), + Ok(0) + ); + assert_eq!((System::providers(&FERDIE), System::consumers(&FERDIE)), (1, 0)); + assert_eq!( + ( + >::free_balance(&FERDIE), + >::reserved_balance(&FERDIE) + ), + (0, 0) + ); + assert_eq!((System::providers(&CHARLIE), System::consumers(&CHARLIE)), (1, 1)); + assert_eq!( + ( + >::free_balance(&CHARLIE), + >::reserved_balance(&CHARLIE) + ), + (9000, 897) + ); + }); +} + +#[test] +fn force_set_lock_and_force_remove_lock_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_noop!( + Currencies::force_set_lock(Some(bob()).into(), alice(), EDF, 100, ID_1,), + BadOrigin + ); + + assert_eq!(Tokens::locks(&alice(), EDF).len(), 0); + assert_eq!(PalletBalances::locks(&alice()).len(), 0); + + assert_ok!(Currencies::force_set_lock( + RuntimeOrigin::root(), + alice(), + EDF, + 100, + ID_1, + )); + assert_ok!(Currencies::force_set_lock( + RuntimeOrigin::root(), + alice(), + NATIVE_CURRENCY_ID, + 1000, + ID_1, + )); + + assert_eq!( + Tokens::locks(&alice(), EDF)[0], + orml_tokens::BalanceLock { id: ID_1, amount: 100 } + ); + assert_eq!( + PalletBalances::locks(&alice())[0], + pallet_balances::BalanceLock { + id: ID_1, + amount: 1000, + reasons: WithdrawReasons::all().into(), + } + ); + + assert_ok!(Currencies::force_set_lock( + RuntimeOrigin::root(), + alice(), + EDF, + 10, + ID_1, + )); + assert_ok!(Currencies::force_set_lock( + RuntimeOrigin::root(), + alice(), + NATIVE_CURRENCY_ID, + 100, + ID_1, + )); + assert_eq!( + Tokens::locks(&alice(), EDF)[0], + orml_tokens::BalanceLock { id: ID_1, amount: 10 } + ); + assert_eq!( + PalletBalances::locks(&alice())[0], + pallet_balances::BalanceLock { + id: ID_1, + amount: 100, + reasons: WithdrawReasons::all().into(), + } + ); + + // do nothing + assert_ok!(Currencies::force_set_lock(RuntimeOrigin::root(), alice(), EDF, 0, ID_1,)); + assert_eq!( + Tokens::locks(&alice(), EDF)[0], + orml_tokens::BalanceLock { id: ID_1, amount: 10 } + ); + + // remove lock + assert_noop!( + Currencies::force_remove_lock(Some(bob()).into(), alice(), EDF, ID_1,), + BadOrigin + ); + + assert_ok!(Currencies::force_remove_lock(RuntimeOrigin::root(), alice(), EDF, ID_1,)); + assert_ok!(Currencies::force_remove_lock( + RuntimeOrigin::root(), + alice(), + NATIVE_CURRENCY_ID, + ID_1, + )); + assert_eq!(Tokens::locks(&alice(), EDF).len(), 0); + assert_eq!(PalletBalances::locks(&alice()).len(), 0); + }); +} #[test] fn multi_lockable_currency_should_work() { @@ -119,9 +442,9 @@ fn multi_currency_should_work() { .build() .execute_with(|| { >::set_origin(alice()); - assert_ok!(Currencies::transfer(Some(alice()).into(), bob(), X_TOKEN_ID, 50, false)); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 0); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 200); + assert_ok!(Currencies::transfer(Some(alice()).into(), bob(), X_TOKEN_ID, 50)); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 50); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 150); }); } @@ -207,6 +530,26 @@ fn basic_currency_adapting_pallet_balances_deposit() { }); } +#[test] +fn basic_currency_adapting_pallet_balances_deposit_throw_error_when_actual_deposit_is_not_expected() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(PalletBalances::total_balance(&eva()), 0); + assert_eq!(PalletBalances::total_issuance(), 200); + assert_noop!( + AdaptedBasicCurrency::deposit(&eva(), 1), + Error::::DepositFailed + ); + assert_eq!(PalletBalances::total_balance(&eva()), 0); + assert_eq!(PalletBalances::total_issuance(), 200); + assert_ok!(AdaptedBasicCurrency::deposit(&eva(), 2)); + assert_eq!(PalletBalances::total_balance(&eva()), 2); + assert_eq!(PalletBalances::total_issuance(), 202); + }); +} + #[test] fn basic_currency_adapting_pallet_balances_withdraw() { ExtBuilder::default() @@ -250,14 +593,19 @@ fn update_balance_call_should_work() { .build() .execute_with(|| { assert_ok!(Currencies::update_balance( - Origin::root(), + RuntimeOrigin::root(), alice(), NATIVE_CURRENCY_ID, -10 )); assert_eq!(NativeCurrency::free_balance(&alice()), 90); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 100); - assert_ok!(Currencies::update_balance(Origin::root(), alice(), X_TOKEN_ID, 10)); + assert_ok!(Currencies::update_balance( + RuntimeOrigin::root(), + alice(), + X_TOKEN_ID, + 10 + )); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 110); }); } @@ -278,58 +626,88 @@ fn call_event_should_work() { .one_hundred_for_alice_n_bob() .build() .execute_with(|| { - assert_ok!(Currencies::transfer(Some(alice()).into(), bob(), X_TOKEN_ID, 50, false)); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 0); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 200); - System::assert_last_event(Event::Currencies(crate::Event::Transferred( - X_TOKEN_ID, - alice(), - bob(), - 50, - ))); - - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 0); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 200); - System::assert_last_event(Event::Currencies(crate::Event::Transferred( + assert_ok!(Currencies::transfer(Some(alice()).into(), bob(), X_TOKEN_ID, 50)); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 50); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 150); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Currencies(crate::Event::Transferred { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 50, + })); + + System::reset_events(); + assert_ok!(>::transfer( X_TOKEN_ID, - alice(), - bob(), - 50, - ))); + &alice(), + &bob(), + 10 + )); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 40); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 160); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 10, + })); + System::assert_has_event(RuntimeEvent::Currencies(crate::Event::Transferred { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 10, + })); assert_ok!(>::deposit( X_TOKEN_ID, &alice(), 100 )); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 100); - System::assert_last_event(Event::Currencies(crate::Event::Deposited(X_TOKEN_ID, alice(), 100))); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 140); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Deposited { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 100, + })); assert_ok!(>::withdraw( X_TOKEN_ID, &alice(), 20 )); - assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 80); - System::assert_last_event(Event::Currencies(crate::Event::Withdrawn(X_TOKEN_ID, alice(), 20))); + assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 120); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Withdrawn { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 20, + })); }); } #[test] fn erc20_total_issuance_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); - assert_eq!(Currencies::total_issuance(CurrencyId::Erc20(erc20_address())), 10000); + assert_eq!( + Currencies::total_issuance(CurrencyId::Erc20(erc20_address())), + ALICE_BALANCE + ); }); } #[test] fn erc20_free_balance_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); @@ -342,7 +720,7 @@ fn erc20_free_balance_should_work() { assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - 10000 + ALICE_BALANCE ); assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 0); }); @@ -351,7 +729,7 @@ fn erc20_free_balance_should_work() { #[test] fn erc20_total_balance_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); @@ -364,7 +742,7 @@ fn erc20_total_balance_should_work() { assert_eq!( Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &alice()), - 10000 + ALICE_BALANCE ); assert_eq!(Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &bob()), 0); }); @@ -373,7 +751,7 @@ fn erc20_total_balance_should_work() { #[test] fn erc20_ensure_withdraw_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); @@ -383,22 +761,25 @@ fn erc20_ensure_withdraw_should_work() { &alice(), 100 )); - assert_eq!( + assert_noop!( Currencies::ensure_can_withdraw(CurrencyId::Erc20(erc20_address()), &bob(), 100), - Err(Error::::BalanceTooLow.into()), + Error::::BalanceTooLow, ); assert_ok!(Currencies::transfer( - Origin::signed(alice()), + RuntimeOrigin::signed(alice()), bob(), CurrencyId::Erc20(erc20_address()), - 100, - false + 100 )); assert_ok!(Currencies::ensure_can_withdraw( CurrencyId::Erc20(erc20_address()), &bob(), 100 )); + assert_noop!( + Currencies::ensure_can_withdraw(CurrencyId::Erc20(erc20_address()), &bob(), 101), + Error::::BalanceTooLow, + ); }); } @@ -406,62 +787,60 @@ fn erc20_ensure_withdraw_should_work() { fn erc20_transfer_should_work() { ExtBuilder::default() .balances(vec![ - (alice(), NATIVE_CURRENCY_ID, 100000), + (alice(), NATIVE_CURRENCY_ID, 200000), (bob(), NATIVE_CURRENCY_ID, 100000), + (eva(), NATIVE_CURRENCY_ID, 100000), ]) .build() .execute_with(|| { deploy_contracts(); - let alice_balance = 10000; - >::set_origin(alice()); - >::set_origin(bob()); + >::set_origin(eva()); + assert_ok!(Currencies::transfer( - Origin::signed(alice()), + RuntimeOrigin::signed(alice()), bob(), CurrencyId::Erc20(erc20_address()), - 100, - false + 100 )); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), - 200 + 100 ); assert_eq!( Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &bob()), - 200 + 100 ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 200 + ALICE_BALANCE - 100 ); assert_eq!( Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 200 + ALICE_BALANCE - 100 ); assert_ok!(Currencies::transfer( - Origin::signed(bob()), + RuntimeOrigin::signed(bob()), alice(), CurrencyId::Erc20(erc20_address()), - 10, - false + 10 )); - assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 180); + assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 90); assert_eq!( Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &bob()), - 180 + 90 ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 180 + ALICE_BALANCE - 90 ); assert_eq!( Currencies::total_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 180 + ALICE_BALANCE - 90 ); }); } @@ -470,7 +849,7 @@ fn erc20_transfer_should_work() { fn erc20_transfer_should_fail() { ExtBuilder::default() .balances(vec![ - (alice(), NATIVE_CURRENCY_ID, 100000), + (alice(), NATIVE_CURRENCY_ID, 200000), (bob(), NATIVE_CURRENCY_ID, 100000), ]) .build() @@ -479,7 +858,12 @@ fn erc20_transfer_should_fail() { // Real origin not found assert_noop!( - Currencies::transfer(Origin::signed(alice()), bob(), CurrencyId::Erc20(erc20_address()), 100, false), + Currencies::transfer( + RuntimeOrigin::signed(alice()), + bob(), + CurrencyId::Erc20(erc20_address()), + 100 + ), Error::::RealOriginNotFound ); @@ -487,32 +871,40 @@ fn erc20_transfer_should_fail() { >::set_origin(bob()); // empty address - assert!( - Currencies::transfer(Origin::signed(alice()), bob(), CurrencyId::Erc20(H160::default()), 100, false).is_err() - ); + assert!(Currencies::transfer( + RuntimeOrigin::signed(alice()), + bob(), + CurrencyId::Erc20(H160::default()), + 100 + ) + .is_err()); // bob can't transfer. bob balance 0 - assert!( - Currencies::transfer(Origin::signed(bob()), alice(), CurrencyId::Erc20(erc20_address()), 1, false).is_err() - ); + assert!(Currencies::transfer( + RuntimeOrigin::signed(bob()), + alice(), + CurrencyId::Erc20(erc20_address()), + 1 + ) + .is_err()); }); } #[test] fn erc20_can_reserve_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); - assert!(Currencies::can_reserve(CurrencyId::Erc20(erc20_address()), &alice(), 1),); + assert!(Currencies::can_reserve(CurrencyId::Erc20(erc20_address()), &alice(), 1)); }); } #[test] fn erc20_slash_reserve_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); @@ -531,18 +923,18 @@ fn erc20_slash_reserve_should_work() { #[test] fn erc20_reserve_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); - let alice_balance = 10000; + assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), 0 ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance + ALICE_BALANCE ); assert_ok!(Currencies::reserve(CurrencyId::Erc20(erc20_address()), &alice(), 100)); @@ -553,7 +945,7 @@ fn erc20_reserve_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 100 + ALICE_BALANCE - 100 ); }); } @@ -561,14 +953,13 @@ fn erc20_reserve_should_work() { #[test] fn erc20_unreserve_should_work() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); - let alice_balance = 10000; assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance + ALICE_BALANCE ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -585,7 +976,7 @@ fn erc20_unreserve_should_work() { assert_ok!(Currencies::reserve(CurrencyId::Erc20(erc20_address()), &alice(), 30)); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 30 + ALICE_BALANCE - 30 ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -597,7 +988,7 @@ fn erc20_unreserve_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 15 + ALICE_BALANCE - 15 ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -609,7 +1000,7 @@ fn erc20_unreserve_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance + ALICE_BALANCE ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -621,11 +1012,11 @@ fn erc20_unreserve_should_work() { #[test] fn erc20_should_not_slash() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); - assert!(!Currencies::can_slash(CurrencyId::Erc20(erc20_address()), &alice(), 1),); + assert!(!Currencies::can_slash(CurrencyId::Erc20(erc20_address()), &alice(), 1)); // calling slash will return 0 assert_eq!(Currencies::slash(CurrencyId::Erc20(erc20_address()), &alice(), 1), 0); }); @@ -634,7 +1025,7 @@ fn erc20_should_not_slash() { #[test] fn erc20_should_not_be_lockable() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); @@ -657,26 +1048,24 @@ fn erc20_should_not_be_lockable() { fn erc20_repatriate_reserved_should_work() { ExtBuilder::default() .balances(vec![ - (alice(), NATIVE_CURRENCY_ID, 100000), + (alice(), NATIVE_CURRENCY_ID, 200000), (bob(), NATIVE_CURRENCY_ID, 100000), ]) .build() .execute_with(|| { deploy_contracts(); - let bob_balance = 200; - let alice_balance = 10000 - bob_balance; + let bob_balance = 100; >::set_origin(alice()); assert_ok!(Currencies::transfer( - Origin::signed(alice()), + RuntimeOrigin::signed(alice()), bob(), CurrencyId::Erc20(erc20_address()), - bob_balance, - false + bob_balance )); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 200 + ALICE_BALANCE - bob_balance ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -704,7 +1093,7 @@ fn erc20_repatriate_reserved_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 200 + ALICE_BALANCE - bob_balance ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), @@ -713,14 +1102,14 @@ fn erc20_repatriate_reserved_should_work() { assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), - bob_balance + 200 + bob_balance ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &bob()), 0 ); assert_ok!(Currencies::reserve(CurrencyId::Erc20(erc20_address()), &bob(), 50)); - assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 350); + assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50 @@ -735,7 +1124,7 @@ fn erc20_repatriate_reserved_should_work() { ), Ok(10) ); - assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 350); + assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50 @@ -753,13 +1142,13 @@ fn erc20_repatriate_reserved_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 200 + ALICE_BALANCE - bob_balance ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), 30 ); - assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 350); + assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &bob()), 20 @@ -777,13 +1166,13 @@ fn erc20_repatriate_reserved_should_work() { ); assert_eq!( Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &alice()), - alice_balance - 180 + ALICE_BALANCE - bob_balance + 20 ); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &alice()), 30 ); - assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 350); + assert_eq!(Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()), 50); assert_eq!( Currencies::reserved_balance(CurrencyId::Erc20(erc20_address()), &bob()), 0 @@ -794,166 +1183,1799 @@ fn erc20_repatriate_reserved_should_work() { #[test] fn erc20_invalid_operation() { ExtBuilder::default() - .balances(vec![(alice(), NATIVE_CURRENCY_ID, 100000)]) + .balances(vec![(alice(), NATIVE_CURRENCY_ID, 200000)]) .build() .execute_with(|| { deploy_contracts(); + >::set_origin(alice()); + assert_noop!( - Currencies::deposit(CurrencyId::Erc20(erc20_address()), &alice(), 1), - Error::::Erc20InvalidOperation - ); - assert_noop!( - Currencies::withdraw(CurrencyId::Erc20(erc20_address()), &alice(), 1), - Error::::Erc20InvalidOperation - ); - assert_noop!( - Currencies::update_balance(Origin::root(), alice(), CurrencyId::Erc20(erc20_address()), 1), + Currencies::update_balance(RuntimeOrigin::root(), alice(), CurrencyId::Erc20(erc20_address()), 1), Error::::Erc20InvalidOperation, ); }); } #[test] -fn sweep_dust_tokens_works() { - ExtBuilder::default().build().execute_with(|| { - tokens::Accounts::::insert( - bob(), - DNAR, - tokens::AccountData { - free: 1, - frozen: 0, - reserved: 0, - }, - ); - tokens::Accounts::::insert( - eva(), - DNAR, - tokens::AccountData { - free: 2, - frozen: 0, - reserved: 0, - }, - ); - tokens::Accounts::::insert( - alice(), - DNAR, - tokens::AccountData { - free: 0, - frozen: 1, - reserved: 0, - }, - ); - tokens::Accounts::::insert( - DustAccount::get(), - DNAR, - tokens::AccountData { - free: 100, - frozen: 0, - reserved: 0, - }, - ); - tokens::TotalIssuance::::insert(DNAR, 104); - - let accounts = vec![bob(), eva(), alice()]; +fn erc20_withdraw_deposit_works() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 200000), + (bob(), NATIVE_CURRENCY_ID, 100000), + ]) + .build() + .execute_with(|| { + deploy_contracts(); + >::set_origin(alice()); - assert_noop!( - Currencies::sweep_dust(Origin::signed(bob()), DNAR, accounts.clone()), - DispatchError::BadOrigin - ); + let erc20_holding_account = MockAddressMapping::get_account_id(&Erc20HoldingAccount::get()); - assert_ok!(Currencies::sweep_dust( - Origin::signed(CouncilAccount::get()), - DNAR, - accounts.clone() - )); - System::assert_last_event(Event::Currencies(crate::Event::DustSwept(DNAR, bob(), 1))); + // transfer to all-zero account failed. + assert_noop!( + Currencies::transfer( + RuntimeOrigin::signed(alice()), + MockAddressMapping::get_account_id(&H160::from_low_u64_be(0)), + CurrencyId::Erc20(erc20_address()), + 100 + ), + module_evm_bridge::Error::::ExecutionRevert + ); + // transfer to non-all-zero account ok. + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(alice()), + erc20_holding_account.clone(), + CurrencyId::Erc20(erc20_address()), + 100 + )); + assert_eq!( + 100, + Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &erc20_holding_account) + ); - // bob's account is gone - assert_eq!(tokens::Accounts::::contains_key(bob(), DNAR), false); - assert_eq!(Currencies::free_balance(DNAR, &bob()), 0); + // withdraw: sender to erc20 holding account + assert_ok!(Currencies::withdraw(CurrencyId::Erc20(erc20_address()), &alice(), 100)); + assert_eq!( + 200, + Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &erc20_holding_account) + ); - // eva's account remains, not below ED - assert_eq!(Currencies::free_balance(DNAR, &eva()), 2); + // deposit: erc20 holding account to receiver + assert_ok!(Currencies::deposit(CurrencyId::Erc20(erc20_address()), &bob(), 100)); + assert_eq!( + 100, + Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &erc20_holding_account) + ); + assert_eq!( + 100, + Currencies::free_balance(CurrencyId::Erc20(erc20_address()), &bob()) + ); - // Dust transferred to dust receiver - assert_eq!(Currencies::free_balance(DNAR, &DustAccount::get()), 101); - // Total issuance remains the same - assert_eq!(Currencies::total_issuance(DNAR), 104); - }); + // deposit failed, because erc20 holding account balance not enough + assert_noop!( + Currencies::deposit(CurrencyId::Erc20(erc20_address()), &bob(), 101), + module_evm_bridge::Error::::ExecutionRevert + ); + }); } #[test] -fn sweep_dust_native_currency_works() { - use frame_support::traits::StoredMap; - ExtBuilder::default().build().execute_with(|| { - assert_ok!(::AccountStore::insert( - &bob(), - pallet_balances::AccountData { +fn fungible_inspect_trait_should_work() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 200000), + (alice(), X_TOKEN_ID, 200000), + ]) + .build() + .execute_with(|| { + deploy_contracts(); + + // Test for Inspect::total_issuance + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 200000 + ); + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + assert_eq!( + >::total_issuance(CurrencyId::Erc20(erc20_address())), + ALICE_BALANCE + ); + assert_eq!(>::total_issuance(), 200000); + assert_eq!(>::total_issuance(), 200000); + + // Test for Inspect::minimum_balance + assert_eq!( + >::minimum_balance(NATIVE_CURRENCY_ID), + 2 + ); + assert_eq!(>::minimum_balance(X_TOKEN_ID), 0); + assert_eq!( + >::minimum_balance(CurrencyId::Erc20(erc20_address())), + 0 + ); + assert_eq!(>::minimum_balance(), 2); + assert_eq!(>::minimum_balance(), 2); + + // Test for Inspect::balance and Inspect::total_balance + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 148690 + ); + assert_eq!( + >::total_balance(NATIVE_CURRENCY_ID, &alice()), + 148690 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE + ); + assert_eq!(>::balance(&alice()), 148690); + assert_eq!( + >::balance(&alice()), + 148690 + ); + + // Test for Inspect::reducible_balance. No locks or reserves + // With Keep alive + assert_eq!( + >::reducible_balance( + NATIVE_CURRENCY_ID, + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 148688 + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 148688 + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 148688 + ); + assert_eq!( + >::reducible_balance( + X_TOKEN_ID, + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 200000 + ); + assert_eq!( + >::reducible_balance( + CurrencyId::Erc20(erc20_address()), + &alice(), + Preservation::Preserve, + Fortitude::Polite, + ), + ALICE_BALANCE + ); + + // Test for Inspect::reducible_balance. No locks or reserves + // without Keep alive. + assert_eq!( + >::reducible_balance( + NATIVE_CURRENCY_ID, + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 148690 + ); + assert_eq!( + >::reducible_balance( + X_TOKEN_ID, + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 200000 + ); + assert_eq!( + >::reducible_balance( + CurrencyId::Erc20(erc20_address()), + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + ALICE_BALANCE + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 148690 + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 148690 + ); + + // Set some locks + assert_ok!(Currencies::set_lock(ID_1, NATIVE_CURRENCY_ID, &alice(), 1000)); + assert_ok!(Currencies::set_lock(ID_1, X_TOKEN_ID, &alice(), 1000)); + + // Test Inspect::reducible_balance with locks + assert_eq!( + >::reducible_balance( + NATIVE_CURRENCY_ID, + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 147690 + ); + assert_eq!( + >::reducible_balance( + X_TOKEN_ID, + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 199000 + ); + assert_eq!( + >::reducible_balance( + CurrencyId::Erc20(erc20_address()), + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + ALICE_BALANCE + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 147690 + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Preserve, + Fortitude::Polite + ), + 147690 + ); + + assert_eq!( + >::reducible_balance( + NATIVE_CURRENCY_ID, + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 147690 + ); + assert_eq!( + >::reducible_balance( + X_TOKEN_ID, + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 199000 + ); + assert_eq!( + >::reducible_balance( + CurrencyId::Erc20(erc20_address()), + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + ALICE_BALANCE + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 147690 + ); + assert_eq!( + >::reducible_balance( + &alice(), + Preservation::Expendable, + Fortitude::Polite + ), + 147690 + ); + + // Test for Inspect::can_deposit + assert_eq!( + >::can_deposit( + NATIVE_CURRENCY_ID, + &alice(), + Bounded::max_value(), + Provenance::Minted + ), + DepositConsequence::Overflow + ); + assert_eq!( + >::can_deposit( + &alice(), + Bounded::max_value(), + Provenance::Minted + ), + DepositConsequence::Overflow + ); + assert_eq!( + >::can_deposit(NATIVE_CURRENCY_ID, &bob(), 1, Provenance::Minted), + DepositConsequence::BelowMinimum + ); + assert_eq!( + >::can_deposit(&bob(), 1, Provenance::Minted), + DepositConsequence::BelowMinimum + ); + assert_eq!( + >::can_deposit( + NATIVE_CURRENCY_ID, + &alice(), + 100, + Provenance::Minted + ), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(&alice(), 100, Provenance::Minted), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(NATIVE_CURRENCY_ID, &alice(), 0, Provenance::Minted), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(&alice(), 0, Provenance::Minted), + DepositConsequence::Success + ); + + assert_eq!( + >::can_deposit( + X_TOKEN_ID, + &alice(), + Bounded::max_value(), + Provenance::Minted + ), + DepositConsequence::Overflow + ); + assert_eq!( + >::can_deposit( + X_TOKEN_ID, + &alice(), + Bounded::max_value(), + Provenance::Minted + ), + DepositConsequence::Overflow + ); + assert_eq!( + >::can_deposit(X_TOKEN_ID, &alice(), 100, Provenance::Minted), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(X_TOKEN_ID, &alice(), 100, Provenance::Minted), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(X_TOKEN_ID, &alice(), 0, Provenance::Minted), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit(X_TOKEN_ID, &alice(), 0, Provenance::Minted), + DepositConsequence::Success + ); + + assert_eq!( + >::can_deposit( + CurrencyId::Erc20(erc20_address()), + &alice(), + Bounded::max_value(), + Provenance::Minted + ), + DepositConsequence::Overflow + ); + assert_eq!( + >::can_deposit( + CurrencyId::Erc20(erc20_address()), + &alice(), + 100, + Provenance::Minted + ), + DepositConsequence::Success + ); + assert_eq!( + >::can_deposit( + CurrencyId::Erc20(erc20_address()), + &alice(), + 0, + Provenance::Minted + ), + DepositConsequence::Success + ); + + // Test Inspect::can_withdraw + assert_eq!( + >::can_withdraw(NATIVE_CURRENCY_ID, &alice(), Bounded::max_value()), + WithdrawConsequence::Underflow + ); + assert_eq!( + >::can_withdraw(&alice(), Bounded::max_value()), + WithdrawConsequence::Underflow + ); + + assert_eq!( + >::can_withdraw(NATIVE_CURRENCY_ID, &alice(), 147690 + 1), + WithdrawConsequence::Frozen + ); + assert_eq!( + >::can_withdraw(&alice(), 147690 + 1), + WithdrawConsequence::Frozen + ); + assert_eq!( + >::can_withdraw(NATIVE_CURRENCY_ID, &alice(), 100), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(&alice(), 100), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(NATIVE_CURRENCY_ID, &alice(), 0), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(&alice(), 0), + WithdrawConsequence::Success + ); + + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), Bounded::max_value()), + WithdrawConsequence::Underflow + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), Bounded::max_value()), + WithdrawConsequence::Underflow + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 200001), + WithdrawConsequence::Underflow + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 200001), + WithdrawConsequence::Underflow + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 100), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 100), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 0), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(X_TOKEN_ID, &alice(), 0), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw( + CurrencyId::Erc20(erc20_address()), + &alice(), + Bounded::max_value() + ), + WithdrawConsequence::BalanceLow + ); + assert_eq!( + >::can_withdraw(CurrencyId::Erc20(erc20_address()), &alice(), 100), + WithdrawConsequence::Success + ); + assert_eq!( + >::can_withdraw(CurrencyId::Erc20(erc20_address()), &alice(), 0), + WithdrawConsequence::Success + ); + + // Test Inspect::asset_exists + assert_eq!( + >::asset_exists(NATIVE_CURRENCY_ID), + true + ); + assert_eq!(>::asset_exists(X_TOKEN_ID), true); + assert_eq!(>::asset_exists(EDF), false); + assert_eq!( + >::asset_exists(CurrencyId::Erc20(erc20_address())), + true + ); + assert_eq!( + >::asset_exists(CurrencyId::Erc20(erc20_address_not_exist())), + false + ); + }); +} + +#[test] +fn fungible_mutate_trait_should_work() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 100000), + (alice(), X_TOKEN_ID, 200000), + ]) + .build() + .execute_with(|| { + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 100000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 100000 + ); + assert_ok!(>::mint_into( + NATIVE_CURRENCY_ID, + &alice(), + 1000 + )); + System::assert_last_event(RuntimeEvent::Balances(pallet_balances::Event::Minted { + who: alice(), + amount: 1000, + })); + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 101000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 101000 + ); + + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + assert_ok!(>::mint_into( + X_TOKEN_ID, + &alice(), + 1000 + )); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Deposited { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 1000, + })); + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 201000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 201000 + ); + + assert_ok!(>::mint_into( + CurrencyId::Erc20(erc20_address()), + &alice(), + 0 + )); + // mint_into will deposit erc20 holding account to recipient. + // but here erc20 holding account don't have enough balance. + assert_noop!( + >::mint_into(CurrencyId::Erc20(erc20_address()), &alice(), 1), + Error::::DepositFailed + ); + + assert_eq!(>::total_issuance(), 101000); + assert_eq!( + >::balance(&alice()), + 101000 + ); + assert_ok!(>::mint_into(&alice(), 1000)); + assert_eq!(>::total_issuance(), 102000); + assert_eq!( + >::balance(&alice()), + 102000 + ); + + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 102000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 102000 + ); + assert_ok!(>::burn_from( + NATIVE_CURRENCY_ID, + &alice(), + 1000, + Precision::Exact, + Fortitude::Force, + )); + System::assert_last_event(RuntimeEvent::Balances(pallet_balances::Event::Burned { + who: alice(), + amount: 1000, + })); + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 101000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 101000 + ); + + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 201000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 201000 + ); + assert_ok!(>::burn_from( + X_TOKEN_ID, + &alice(), + 1000, + Precision::Exact, + Fortitude::Force, + )); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Withdrawn { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 1000, + })); + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + + assert_ok!(>::burn_from( + CurrencyId::Erc20(erc20_address()), + &alice(), + 0, + Precision::Exact, + Fortitude::Force, + )); + + assert_eq!( + >::burn_from( + CurrencyId::Erc20(erc20_address()), + &alice(), + 1, + Precision::Exact, + Fortitude::Force, + ), + Err(module_evm_bridge::Error::::InvalidReturnValue.into()) + ); + + assert_eq!(>::total_issuance(), 101000); + assert_eq!( + >::balance(&alice()), + 101000 + ); + assert_ok!(>::burn_from( + &alice(), + 1000, + Precision::Exact, + Fortitude::Force, + )); + assert_eq!(>::total_issuance(), 100000); + assert_eq!( + >::balance(&alice()), + 100000 + ); + + // Burn dust if remaining is less than ED. + assert_eq!( + >::burn_from( + NATIVE_CURRENCY_ID, + &alice(), + 99_999, + Precision::Exact, + Fortitude::Force, + ), + Ok(99_999) + ); + assert_eq!(>::total_issuance(), 0); + }); +} + +#[test] +fn fungible_mutate_trait_transfer_should_work() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 500000), + (alice(), X_TOKEN_ID, 200000), + ]) + .build() + .execute_with(|| { + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 500000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &bob()), + 0 + ); + + System::reset_events(); + assert_ok!(>::transfer( + NATIVE_CURRENCY_ID, + &alice(), + &bob(), + 10000, + Preservation::Preserve, + )); + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: alice(), + to: bob(), + amount: 10000, + })); + System::assert_has_event(RuntimeEvent::Currencies(crate::Event::Transferred { + currency_id: NATIVE_CURRENCY_ID, + from: alice(), + to: bob(), + amount: 10000, + })); + + assert_noop!( + >::transfer( + NATIVE_CURRENCY_ID, + &alice(), + &bob(), + 489_999, + Preservation::Preserve, + ), + TokenError::NotExpendable, + ); + + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 490000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &bob()), + 10000 + ); + + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + assert_eq!(>::balance(X_TOKEN_ID, &bob()), 0); + System::reset_events(); + assert_ok!(>::transfer( + X_TOKEN_ID, + &alice(), + &bob(), + 10000, + Preservation::Preserve, + )); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 10000, + })); + System::assert_has_event(RuntimeEvent::Currencies(crate::Event::Transferred { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 10000, + })); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 190000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &bob()), + 10000 + ); + + assert_eq!( + >::balance(&alice()), + 490000 + ); + assert_eq!(>::balance(&bob()), 10000); + assert_ok!(>::transfer( + &alice(), + &bob(), + 10000, + Preservation::Preserve, + )); + assert_eq!( + >::balance(&alice()), + 480000 + ); + assert_eq!(>::balance(&bob()), 20000); + + deploy_contracts(); + >::set_origin(alice()); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &bob()), + 0 + ); + assert_ok!(>::transfer( + CurrencyId::Erc20(erc20_address()), + &alice(), + &bob(), + 2000, + Preservation::Preserve + )); + System::assert_last_event(RuntimeEvent::Currencies(crate::Event::Transferred { + currency_id: CurrencyId::Erc20(erc20_address()), + from: alice(), + to: bob(), + amount: 2000, + })); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE - 2000 + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &bob()), + 2000 + ); + }); +} + +#[test] +fn fungible_unbalanced_trait_should_work() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 100000), + (alice(), X_TOKEN_ID, 200000), + ]) + .build() + .execute_with(|| { + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 100000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 100000 + ); + assert_ok!(>::write_balance( + NATIVE_CURRENCY_ID, + &alice(), + 80000 + )); + + // now, fungible::Unbalanced::write_balance as low-level function, does not use BalanceSet event + + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 100000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 80000 + ); + + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + assert_ok!(>::write_balance( + X_TOKEN_ID, + &alice(), + 80000 + )); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::BalanceSet { + currency_id: X_TOKEN_ID, + who: alice(), + free: 80000, + reserved: 0, + })); + + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 80000 + ); + + assert_eq!(>::total_issuance(), 100000); + assert_eq!(>::balance(&alice()), 80000); + assert_ok!(>::write_balance( + &alice(), + 60000 + )); + assert_eq!(>::total_issuance(), 100000); + assert_eq!(>::balance(&alice()), 60000); + + assert_noop!( + >::write_balance( + CurrencyId::Erc20(erc20_address()), + &alice(), + 0 + ), + Error::::Erc20InvalidOperation + ); + + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 100000 + ); + >::set_total_issuance(NATIVE_CURRENCY_ID, 60000); + assert_eq!( + >::total_issuance(NATIVE_CURRENCY_ID), + 60000 + ); + + assert_eq!( + >::total_issuance(X_TOKEN_ID), + 200000 + ); + >::set_total_issuance(X_TOKEN_ID, 80000); + assert_eq!(>::total_issuance(X_TOKEN_ID), 80000); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::TotalIssuanceSet { + currency_id: X_TOKEN_ID, + amount: 80000, + })); + + assert_eq!(>::total_issuance(), 60000); + >::set_total_issuance(0); + assert_eq!(>::total_issuance(), 0); + + assert_eq!( + >::total_issuance(CurrencyId::Erc20(erc20_address())), + 0 + ); + >::set_total_issuance(CurrencyId::Erc20(erc20_address()), 80000); + assert_eq!( + >::total_issuance(CurrencyId::Erc20(erc20_address())), + 0 + ); + }); +} + +#[test] +fn fungible_inspect_hold_and_hold_trait_should_work() { + ExtBuilder::default() + .balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 500000), + (alice(), X_TOKEN_ID, 200000), + (bob(), NATIVE_CURRENCY_ID, 10000), + (bob(), X_TOKEN_ID, 10000), + ]) + .build() + .execute_with(|| { + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 500000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 0 + ); + + assert_eq!( + >::can_hold(NATIVE_CURRENCY_ID, &(), &alice(), 499998), + true, + ); + assert_eq!( + >::can_hold(NATIVE_CURRENCY_ID, &(), &alice(), 500001), + false + ); + + assert_ok!(>::hold( + NATIVE_CURRENCY_ID, + &(), + &alice(), + 20000 + )); + assert_noop!( + >::hold(NATIVE_CURRENCY_ID, &(), &alice(), 500000), + TokenError::FundsUnavailable, + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 20000 + ); + + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 200000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 0 + ); + assert_eq!( + >::can_hold(X_TOKEN_ID, &(), &alice(), 200000), + true + ); + assert_eq!( + >::can_hold(X_TOKEN_ID, &(), &alice(), 200001), + false + ); + assert_ok!(>::hold( + X_TOKEN_ID, + &(), + &alice(), + 20000 + )); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Reserved { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 20000, + })); + + assert_noop!( + >::hold(X_TOKEN_ID, &(), &alice(), 200000), + DispatchError::Module(ModuleError { + index: 2, + error: [0, 0, 0, 0], + message: Some("BalanceTooLow",), + },) + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 180000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 20000 + ); + + assert_eq!( + >::balance(&alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 20000 + ); + assert_eq!( + >::can_hold(&(), &alice(), 20000), + true + ); + assert_ok!(>::hold( + &(), + &alice(), + 20000 + )); + assert_eq!( + >::balance(&alice()), + 460000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 40000 + ); + + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 460000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 40000 + ); + assert_eq!( + >::release( + NATIVE_CURRENCY_ID, + &(), + &alice(), + 10000, + Precision::BestEffort, + ), + Ok(10000) + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 470000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 30000 + ); + assert_noop!( + >::release( + NATIVE_CURRENCY_ID, + &(), + &alice(), + 50000, + Precision::Exact, + ), + TokenError::FundsUnavailable, + ); + assert_eq!( + >::release( + NATIVE_CURRENCY_ID, + &(), + &alice(), + 50000, + Precision::BestEffort, + ), + Ok(30000) + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 0 + ); + assert_ok!(>::hold( + NATIVE_CURRENCY_ID, + &(), + &alice(), + 30000 + )); + + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 180000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 20000 + ); + assert_eq!( + >::release( + X_TOKEN_ID, + &(), + &alice(), + 10000, + Precision::BestEffort, + ), + Ok(10000) + ); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::Unreserved { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 10000, + })); + + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 190000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 10000 + ); + assert_noop!( + >::release(X_TOKEN_ID, &(), &alice(), 100000, Precision::Exact,), + DispatchError::Module(ModuleError { + index: 2, + error: [0, 0, 0, 0], + message: Some("BalanceTooLow") + }) + ); + assert_eq!( + >::release( + X_TOKEN_ID, + &(), + &alice(), + 100000, + Precision::BestEffort, + ), + Ok(10000) + ); + assert_ok!(>::hold( + X_TOKEN_ID, + &(), + &alice(), + 10000 + )); + + assert_eq!( + >::balance(&alice()), + 470000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 30000 + ); + assert_eq!( + >::release(&(), &alice(), 10000, Precision::BestEffort,), + Ok(10000) + ); + assert_eq!( + >::balance(&alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 20000 + ); + + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 20000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &bob()), + 10000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &bob()), + 0 + ); + assert_eq!( + >::transfer_on_hold( + NATIVE_CURRENCY_ID, + &(), + &alice(), + &bob(), + 2000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + Ok(2000) + ); + assert_noop!( + >::transfer_on_hold( + NATIVE_CURRENCY_ID, + &(), + &alice(), + &bob(), + 200000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + TokenError::Frozen, + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &alice()), + 18000 + ); + assert_eq!( + >::balance(NATIVE_CURRENCY_ID, &bob()), + 10000 + ); + assert_eq!( + >::balance_on_hold(NATIVE_CURRENCY_ID, &(), &bob()), + 2000 + ); + + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 190000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 10000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &bob()), + 10000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &bob()), + 0 + ); + assert_eq!( + >::transfer_on_hold( + X_TOKEN_ID, + &(), + &alice(), + &bob(), + 2000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + Ok(2000) + ); + System::assert_last_event(RuntimeEvent::Tokens(orml_tokens::Event::ReserveRepatriated { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 2000, + status: BalanceStatus::Reserved, + })); + + assert_noop!( + >::transfer_on_hold( + X_TOKEN_ID, + &(), + &alice(), + &bob(), + 200000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + DispatchError::Module(ModuleError { + index: 2, + error: [0, 0, 0, 0], + message: Some("BalanceTooLow") + }) + ); + assert_eq!( + >::balance(X_TOKEN_ID, &alice()), + 190000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &alice()), + 8000 + ); + assert_eq!( + >::balance(X_TOKEN_ID, &bob()), + 10000 + ); + assert_eq!( + >::balance_on_hold(X_TOKEN_ID, &(), &bob()), + 2000 + ); + + assert_eq!( + >::balance(&alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 18000 + ); + assert_eq!(>::balance(&bob()), 10000); + assert_eq!( + >::balance_on_hold(&(), &bob()), + 2000 + ); + assert_eq!( + >::transfer_on_hold( + &(), + &alice(), + &bob(), + 2000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + Ok(2000) + ); + assert_eq!( + >::balance(&alice()), + 480000 + ); + assert_eq!( + >::balance_on_hold(&(), &alice()), + 16000 + ); + assert_eq!(>::balance(&bob()), 10000); + assert_eq!( + >::balance_on_hold(&(), &bob()), + 4000 + ); + + deploy_contracts(); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 0 + ); + assert_eq!( + >::can_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 8000 + ), + true + ); + assert_eq!( + >::can_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + ALICE_BALANCE + 1 + ), + false + ); + assert_ok!(>::hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 8000 + )); + + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE - 8000 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 8000 + ); + + assert_eq!( + >::release( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 0, + Precision::BestEffort, + ), + Ok(0) + ); + + assert_noop!( + >::release( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 8001, + Precision::Exact, + ), + Error::::BalanceTooLow + ); + assert_eq!( + >::release( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 8001, + Precision::BestEffort, + ), + Ok(8000) + ); + + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 0 + ); + + assert_ok!(>::hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + 8000 + )); + + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE - 8000 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 8000 + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &bob()), + 0 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &bob() + ), + 0 + ); + + assert_noop!( + >::transfer_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + &bob(), + 8001, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + Error::::BalanceTooLow + ); + + assert_eq!( + >::transfer_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + &bob(), + 2000, + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + ), + Ok(2000) + ); + + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE - 8000 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 6000 + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &bob()), + 0 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &bob() + ), + 2000 + ); + + assert_eq!( + >::transfer_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice(), + &bob(), + 6001, + Precision::BestEffort, + Restriction::OnHold, + Fortitude::Polite, + ), + Ok(6000) + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &alice()), + ALICE_BALANCE - 8000 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &alice() + ), + 0 + ); + assert_eq!( + >::balance(CurrencyId::Erc20(erc20_address()), &bob()), + 0 + ); + assert_eq!( + >::balance_on_hold( + CurrencyId::Erc20(erc20_address()), + &(), + &bob() + ), + 8000 + ); + }); +} + +#[test] +fn sweep_dust_tokens_works() { + ExtBuilder::default().build().execute_with(|| { + orml_tokens::Accounts::::insert( + bob(), + EDF, + orml_tokens::AccountData { free: 1, + frozen: 0, reserved: 0, - misc_frozen: 0, - fee_frozen: 0, }, - )); - assert_ok!(::AccountStore::insert( - &eva(), - pallet_balances::AccountData { + ); + orml_tokens::Accounts::::insert( + eva(), + EDF, + orml_tokens::AccountData { free: 2, + frozen: 0, reserved: 0, - misc_frozen: 0, - fee_frozen: 0, }, - )); - assert_ok!(::AccountStore::insert( - &alice(), - pallet_balances::AccountData { + ); + orml_tokens::Accounts::::insert( + alice(), + EDF, + orml_tokens::AccountData { free: 0, + frozen: 1, reserved: 0, - misc_frozen: 2, - fee_frozen: 2, }, - )); - assert_ok!(::AccountStore::insert( - &DustAccount::get(), - pallet_balances::AccountData { + ); + orml_tokens::Accounts::::insert( + DustAccount::get(), + EDF, + orml_tokens::AccountData { free: 100, + frozen: 0, reserved: 0, - misc_frozen: 0, - fee_frozen: 0, }, - )); - pallet_balances::TotalIssuance::::put(104); - - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &bob()), 1); - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &eva()), 2); - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &alice()), 0); - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &DustAccount::get()), 100); + ); + orml_tokens::TotalIssuance::::insert(EDF, 104); let accounts = vec![bob(), eva(), alice()]; assert_noop!( - Currencies::sweep_dust(Origin::signed(bob()), NATIVE_CURRENCY_ID, accounts.clone()), + Currencies::sweep_dust(RuntimeOrigin::signed(bob()), EDF, accounts.clone()), DispatchError::BadOrigin ); assert_ok!(Currencies::sweep_dust( - Origin::signed(CouncilAccount::get()), - NATIVE_CURRENCY_ID, - accounts.clone() + RuntimeOrigin::signed(CouncilAccount::get()), + EDF, + accounts )); - System::assert_last_event(Event::Currencies(crate::Event::DustSwept(NATIVE_CURRENCY_ID, bob(), 1))); + System::assert_last_event(RuntimeEvent::Currencies(crate::Event::DustSwept { + currency_id: EDF, + who: bob(), + amount: 1, + })); // bob's account is gone - assert_eq!(System::account_exists(&bob()), false); - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &bob()), 0); + assert_eq!(orml_tokens::Accounts::::contains_key(bob(), EDF), false); + assert_eq!(Currencies::free_balance(EDF, &bob()), 0); // eva's account remains, not below ED - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &eva()), 2); + assert_eq!(Currencies::free_balance(EDF, &eva()), 2); // Dust transferred to dust receiver - assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &DustAccount::get()), 101); + assert_eq!(Currencies::free_balance(EDF, &DustAccount::get()), 101); // Total issuance remains the same - assert_eq!(Currencies::total_issuance(NATIVE_CURRENCY_ID), 104); + assert_eq!(Currencies::total_issuance(EDF), 104); + }); +} + +#[test] +fn sweep_dust_native_currency_works() { + use frame_support::traits::StoredMap; + ExtBuilder::default().build().execute_with(|| { + assert_ok!(::AccountStore::insert( + &bob(), + pallet_balances::AccountData { + free: 1, + reserved: 0, + frozen: 0, + flags: Default::default(), + }, + )); + + // TODO: seems the insert directly does not work now, it's probably because of the new machanism of + // provider and consumer: https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/system/src/lib.rs#L1692 + // consider deposit_creating alive account, then decrease the ED to fix this test! + assert_eq!( + ::AccountStore::get(&bob()), + Default::default() + ); + + // assert_ok!(::AccountStore::insert( + // &eva(), + // pallet_balances::AccountData { + // free: 2, + // reserved: 0, + // frozen: 0, + // flags: Default::default(), + // }, + // )); + // assert_ok!(::AccountStore::insert( + // &alice(), + // pallet_balances::AccountData { + // free: 0, + // reserved: 0, + // frozen: 2, + // flags: Default::default(), + // }, + // )); + // assert_ok!(::AccountStore::insert( + // &DustAccount::get(), + // pallet_balances::AccountData { + // free: 100, + // reserved: 0, + // frozen: 0, + // flags: Default::default(), + // }, + // )); + // pallet_balances::TotalIssuance::::put(104); + + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &bob()), 1); + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &eva()), 2); + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &alice()), 0); + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &DustAccount::get()), 100); + + // let accounts = vec![bob(), eva(), alice()]; + + // assert_noop!( + // Currencies::sweep_dust(RuntimeOrigin::signed(bob()), NATIVE_CURRENCY_ID, + // accounts.clone()), DispatchError::BadOrigin + // ); + + // assert_ok!(Currencies::sweep_dust( + // RuntimeOrigin::signed(CouncilAccount::get()), + // NATIVE_CURRENCY_ID, + // accounts + // )); + // System::assert_last_event(RuntimeEvent::Currencies(crate::Event::DustSwept { + // currency_id: NATIVE_CURRENCY_ID, + // who: bob(), + // amount: 1, + // })); + + // // bob's account is gone + // assert_eq!(System::account_exists(&bob()), false); + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &bob()), 0); + + // // eva's account remains, not below ED + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &eva()), 2); + + // // Dust transferred to dust receiver + // assert_eq!(Currencies::free_balance(NATIVE_CURRENCY_ID, &DustAccount::get()), 101); + // // Total issuance remains the same + // assert_eq!(Currencies::total_issuance(NATIVE_CURRENCY_ID), 104); }); } @@ -962,7 +2984,7 @@ fn sweep_dust_erc20_not_allowed() { ExtBuilder::default().build().execute_with(|| { assert_noop!( Currencies::sweep_dust( - Origin::signed(CouncilAccount::get()), + RuntimeOrigin::signed(CouncilAccount::get()), CurrencyId::Erc20(erc20_address()), vec![] ), @@ -970,3 +2992,31 @@ fn sweep_dust_erc20_not_allowed() { ); }); } + +#[test] +fn transfer_erc20_will_charge_gas() { + ExtBuilder::default().build().execute_with(|| { + let dispatch_info = module::Call::::transfer { + dest: alice(), + currency_id: CurrencyId::Erc20(erc20_address()), + amount: 1, + } + .get_dispatch_info(); + assert_eq!( + dispatch_info.weight, + ::WeightInfo::transfer_non_native_currency() + + Weight::from_parts(module_support::evm::limits::erc20::TRANSFER.gas, 0) // mock GasToWeight is 1:1 + ); + + let dispatch_info = module::Call::::transfer { + dest: alice(), + currency_id: EDF, + amount: 1, + } + .get_dispatch_info(); + assert_eq!( + dispatch_info.weight, + ::WeightInfo::transfer_non_native_currency() + ); + }); +} diff --git a/blockchain/modules/currencies/src/weights.rs b/blockchain/modules/currencies/src/weights.rs index f2e868f3a..eb3eab69c 100644 --- a/blockchain/modules/currencies/src/weights.rs +++ b/blockchain/modules/currencies/src/weights.rs @@ -1,135 +1,301 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Autogenerated weights for module_currencies -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-09-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_currencies -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --template=.maintain/module-weight-template.hbs -// --output=./blockchain/modules/currencies/src/weights.rs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_currencies. -pub trait WeightInfo { - fn transfer_non_native_currency() -> Weight; - fn transfer_native_currency() -> Weight; - fn update_balance_non_native_currency() -> Weight; - fn update_balance_native_currency_creating() -> Weight; - fn update_balance_native_currency_killing() -> Weight; - fn sweep_dust(c: u32, ) -> Weight; -} - -/// Weights for module_currencies using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn transfer_non_native_currency() -> Weight { - (65_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } - fn transfer_native_currency() -> Weight { - (59_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn update_balance_non_native_currency() -> Weight { - (36_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn update_balance_native_currency_creating() -> Weight { - (34_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn update_balance_native_currency_killing() -> Weight { - (35_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn sweep_dust(c: u32, ) -> Weight { - (7_733_000 as Weight) - // Standard Error: 93_000 - .saturating_add((25_350_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn transfer_non_native_currency() -> Weight { - (65_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } - fn transfer_native_currency() -> Weight { - (59_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn update_balance_non_native_currency() -> Weight { - (36_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - fn update_balance_native_currency_creating() -> Weight { - (34_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn update_balance_native_currency_killing() -> Weight { - (35_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn sweep_dust(c: u32, ) -> Weight { - (7_733_000 as Weight) - // Standard Error: 93_000 - .saturating_add((25_350_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for module_currencies +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-05-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `ip-172-31-22-123`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/setheum +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_currencies +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/currencies/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_currencies. +pub trait WeightInfo { + fn transfer_non_native_currency() -> Weight; + fn transfer_native_currency() -> Weight; + fn update_balance_non_native_currency() -> Weight; + fn update_balance_native_currency_creating() -> Weight; + fn update_balance_native_currency_killing() -> Weight; + fn sweep_dust(c: u32, ) -> Weight; + fn force_set_lock() -> Weight; + fn force_remove_lock() -> Weight; +} + +/// Weights for module_currencies using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + // Storage: Tokens Accounts (r:2 w:2) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_non_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `2493` + // Estimated: `13352` + // Minimum execution time: 86_216 nanoseconds. + Weight::from_parts(88_106_000, 13352) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn transfer_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `1977` + // Estimated: `7118` + // Minimum execution time: 68_140 nanoseconds. + Weight::from_parts(69_315_000, 7118) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Proof: Tokens TotalIssuance (max_values: None, max_size: Some(67), added: 2542, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn update_balance_non_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `2008` + // Estimated: `10737` + // Minimum execution time: 54_990 nanoseconds. + Weight::from_parts(55_756_000, 10737) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn update_balance_native_currency_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `1707` + // Estimated: `3593` + // Minimum execution time: 50_095 nanoseconds. + Weight::from_parts(51_020_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn update_balance_native_currency_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `1846` + // Estimated: `7118` + // Minimum execution time: 49_296 nanoseconds. + Weight::from_parts(50_228_000, 7118) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Tokens Accounts (r:4 w:4) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:3 w:3) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[1, 3]`. + fn sweep_dust(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1880 + c * (339 ±0)` + // Estimated: `4602 + c * (5225 ±0)` + // Minimum execution time: 63_930 nanoseconds. + Weight::from_parts(28_195_038, 4602) + // Standard Error: 55_030 + .saturating_add(Weight::from_parts(37_716_994, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 5225).saturating_mul(c.into())) + } + // Storage: Tokens Locks (r:1 w:1) + // Proof: Tokens Locks (max_values: None, max_size: Some(1300), added: 3775, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_lock() -> Weight { + // Proof Size summary in bytes: + // Measured: `2209` + // Estimated: `11970` + // Minimum execution time: 56_749 nanoseconds. + Weight::from_parts(57_522_000, 11970) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Tokens Locks (r:1 w:1) + // Proof: Tokens Locks (max_values: None, max_size: Some(1300), added: 3775, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_remove_lock() -> Weight { + // Proof Size summary in bytes: + // Measured: `2314` + // Estimated: `11970` + // Minimum execution time: 57_795 nanoseconds. + Weight::from_parts(58_743_000, 11970) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Tokens Accounts (r:2 w:2) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_non_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `2493` + // Estimated: `13352` + // Minimum execution time: 86_216 nanoseconds. + Weight::from_parts(88_106_000, 13352) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn transfer_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `1977` + // Estimated: `7118` + // Minimum execution time: 68_140 nanoseconds. + Weight::from_parts(69_315_000, 7118) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Proof: Tokens TotalIssuance (max_values: None, max_size: Some(67), added: 2542, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn update_balance_non_native_currency() -> Weight { + // Proof Size summary in bytes: + // Measured: `2008` + // Estimated: `10737` + // Minimum execution time: 54_990 nanoseconds. + Weight::from_parts(55_756_000, 10737) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn update_balance_native_currency_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `1707` + // Estimated: `3593` + // Minimum execution time: 50_095 nanoseconds. + Weight::from_parts(51_020_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn update_balance_native_currency_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `1846` + // Estimated: `7118` + // Minimum execution time: 49_296 nanoseconds. + Weight::from_parts(50_228_000, 7118) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Tokens Accounts (r:4 w:4) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:3 w:3) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[1, 3]`. + fn sweep_dust(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1880 + c * (339 ±0)` + // Estimated: `4602 + c * (5225 ±0)` + // Minimum execution time: 63_930 nanoseconds. + Weight::from_parts(28_195_038, 4602) + // Standard Error: 55_030 + .saturating_add(Weight::from_parts(37_716_994, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 5225).saturating_mul(c.into())) + } + // Storage: Tokens Locks (r:1 w:1) + // Proof: Tokens Locks (max_values: None, max_size: Some(1300), added: 3775, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_lock() -> Weight { + // Proof Size summary in bytes: + // Measured: `2209` + // Estimated: `11970` + // Minimum execution time: 56_749 nanoseconds. + Weight::from_parts(57_522_000, 11970) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Tokens Locks (r:1 w:1) + // Proof: Tokens Locks (max_values: None, max_size: Some(1300), added: 3775, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:1) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_remove_lock() -> Weight { + // Proof Size summary in bytes: + // Measured: `2314` + // Estimated: `11970` + // Minimum execution time: 57_795 nanoseconds. + Weight::from_parts(58_743_000, 11970) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } +} diff --git a/blockchain/modules/edfis-swap-dex/Cargo.toml b/blockchain/modules/edfis-swap-dex/Cargo.toml new file mode 100644 index 000000000..e5cabb291 --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "module-edfis-swap-dex" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-std = { workspace = true } +orml-traits = { workspace = true } + +module-support = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +orml-tokens = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/edfis-swap-dex/README.md b/blockchain/modules/edfis-swap-dex/README.md new file mode 100644 index 000000000..f6352f747 --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/README.md @@ -0,0 +1,7 @@ +بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +# Edfis Swap DEX Module + +## Overview + +Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap mechanism refers to the design of `Uniswap V2`. In addition to being used for trading, DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction when the liquidity is sufficient. The `Swap Exchange` is one of the DEXs on `Edfis` (Ethical DeFi's Exchange) providing market making liquidity for Edfis Exchange. diff --git a/blockchain/modules/edfis-swap-dex/src/lib.rs b/blockchain/modules/edfis-swap-dex/src/lib.rs new file mode 100644 index 000000000..29ba13519 --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/src/lib.rs @@ -0,0 +1,1547 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # DEX Module +//! +//! ## Overview +//! +//! Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap +//! mechanism refers to the design of `Uniswap V2`. In addition to being used for trading, +//! DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction +//! when the liquidity is sufficient. The `Swap Exchange` is one of the DEXs on +//! `Edfis` (Ethical DeFi's Exchange) providing market making liquidity for `Edfis Exchange`. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] +#![allow(clippy::unused_unit)] +#![allow(clippy::collapsible_if)] + +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use frame_system::pallet_prelude::*; +use module_support::{SwapDexIncentives, SwapDexManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; +use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; +use parity_scale_codec::MaxEncodedLen; +use primitives::{Balance, CurrencyId, TradingPair}; +use scale_info::TypeInfo; +use sp_core::{H160, U256}; +use sp_runtime::{ + traits::{AccountIdConversion, One, Saturating, Zero}, + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, +}; +use sp_std::{prelude::*, vec}; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +/// Parameters of TradingPair in Provisioning status +#[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct ProvisioningParameters { + /// limit contribution per time. + min_contribution: (Balance, Balance), + /// target provision that trading pair could to be Enabled. + target_provision: (Balance, Balance), + /// accumulated provision amount for this Provisioning trading pair. + accumulated_provision: (Balance, Balance), + /// The number of block that status can be converted to Enabled. + not_before: BlockNumber, +} + +/// Status for TradingPair +#[derive(Clone, Copy, Encode, Decode, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub enum TradingPairStatus { + /// Default status, + /// can withdraw liquidity, re-enable and list this trading pair. + Disabled, + /// TradingPair is Provisioning, + /// can add provision and disable this trading pair. + Provisioning(ProvisioningParameters), + /// TradingPair is Enabled, + /// can add/remove liquidity, trading and disable this trading pair. + Enabled, +} + +impl Default for TradingPairStatus { + fn default() -> Self { + Self::Disabled + } +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for transfer currencies + type Currency: MultiCurrencyExtended; + + /// Trading fee rate + /// The first item of the tuple is the numerator of the fee rate, second + /// item is the denominator, fee_rate = numerator / denominator, + /// use (u32, u32) over `Rate` type to minimize internal division + /// operation. + #[pallet::constant] + type GetExchangeFee: Get<(u32, u32)>; + + /// The limit for length of trading path + #[pallet::constant] + type TradingPathLimit: Get; + + /// The DEX's module id, keep all assets in DEX. + #[pallet::constant] + type PalletId: Get; + + /// Mapping between CurrencyId and ERC20 address so user can use Erc20 + /// address as LP token. + type Erc20InfoMapping: Erc20InfoMapping; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + + /// DEX incentives + type SwapDexIncentives: SwapDexIncentives; + + /// The origin which may list, enable or disable trading pairs. + type ListingOrigin: EnsureOrigin; + + /// The extended provisioning blocks since the `not_before` of provisioning. + #[pallet::constant] + type ExtendedProvisioningBlocks: Get>; + + /// Event handler which calls when update liquidity pool. + type OnLiquidityPoolUpdated: Happened<(TradingPair, Balance, Balance)>; + } + + #[pallet::error] + pub enum Error { + /// Trading pair is already Enabled + AlreadyEnabled, + /// Trading pair must be in Enabled status + MustBeEnabled, + /// Trading pair must be in Provisioning status + MustBeProvisioning, + /// Trading pair must be in Disabled status + MustBeDisabled, + /// This trading pair is not allowed to be listed + NotAllowedList, + /// The increment of provision is invalid + InvalidContributionIncrement, + /// The increment of liquidity is invalid + InvalidLiquidityIncrement, + /// Invalid currency id + InvalidCurrencyId, + /// Invalid trading path length + InvalidTradingPathLength, + /// Target amount is less to min_target_amount + InsufficientTargetAmount, + /// Supply amount is more than max_supply_amount + ExcessiveSupplyAmount, + /// Liquidity is not enough + InsufficientLiquidity, + /// The supply amount is zero + ZeroSupplyAmount, + /// The target amount is zero + ZeroTargetAmount, + /// The share increment is unacceptable + UnacceptableShareIncrement, + /// The liquidity withdrawn is unacceptable + UnacceptableLiquidityWithdrawn, + /// The swap dosen't meet the invariant check + InvariantCheckFailed, + /// The Provision is unqualified to be converted to `Enabled` + UnqualifiedProvision, + /// Trading pair is still provisioning + StillProvisioning, + /// The Asset unregistered. + AssetUnregistered, + /// The trading path is invalid + InvalidTradingPath, + /// Not allowed to refund provision + NotAllowedRefund, + /// Cannot swap + CannotSwap, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// add provision success + AddProvision { + who: T::AccountId, + currency_0: CurrencyId, + contribution_0: Balance, + currency_1: CurrencyId, + contribution_1: Balance, + }, + /// Add liquidity success. + AddLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_increment: Balance, + }, + /// Remove liquidity from the trading pool success. + RemoveLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_decrement: Balance, + }, + /// Use supply currency to swap target currency. + Swap { + trader: T::AccountId, + path: Vec, + liquidity_changes: Vec, + }, + /// Enable trading pair. + EnableTradingPair { trading_pair: TradingPair }, + /// List provisioning trading pair. + ListProvisioning { trading_pair: TradingPair }, + /// Disable trading pair. + DisableTradingPair { trading_pair: TradingPair }, + /// Provisioning trading pair convert to Enabled. + ProvisioningToEnabled { + trading_pair: TradingPair, + pool_0: Balance, + pool_1: Balance, + share_amount: Balance, + }, + /// refund provision success + RefundProvision { + who: T::AccountId, + currency_0: CurrencyId, + contribution_0: Balance, + currency_1: CurrencyId, + contribution_1: Balance, + }, + /// Provisioning trading pair aborted. + ProvisioningAborted { + trading_pair: TradingPair, + accumulated_provision_0: Balance, + accumulated_provision_1: Balance, + }, + } + + /// Liquidity pool for TradingPair. + /// + /// LiquidityPool: map TradingPair => (Balance, Balance) + #[pallet::storage] + #[pallet::getter(fn liquidity_pool)] + pub type LiquidityPool = StorageMap<_, Twox64Concat, TradingPair, (Balance, Balance), ValueQuery>; + + /// Status for TradingPair. + /// + /// TradingPairStatuses: map TradingPair => TradingPairStatus + #[pallet::storage] + #[pallet::getter(fn trading_pair_statuses)] + pub type TradingPairStatuses = + StorageMap<_, Twox64Concat, TradingPair, TradingPairStatus>, ValueQuery>; + + /// Provision of TradingPair by AccountId. + /// + /// ProvisioningPool: double_map TradingPair, AccountId => (Balance, + /// Balance) + #[pallet::storage] + #[pallet::getter(fn provisioning_pool)] + pub type ProvisioningPool = + StorageDoubleMap<_, Twox64Concat, TradingPair, Twox64Concat, T::AccountId, (Balance, Balance), ValueQuery>; + + /// Initial exchange rate, used to calculate the dex share amount for founders of provisioning + /// + /// InitialShareExchangeRates: map TradingPair => (ExchangeRate, ExchangeRate) + #[pallet::storage] + #[pallet::getter(fn initial_share_exchange_rates)] + pub type InitialShareExchangeRates = + StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub initial_listing_trading_pairs: + Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumberFor)>, + pub initial_enabled_trading_pairs: Vec, + pub initial_added_liquidity_pools: Vec<(T::AccountId, Vec<(TradingPair, (Balance, Balance))>)>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.initial_listing_trading_pairs.iter().for_each( + |(trading_pair, min_contribution, target_provision, not_before)| { + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution: *min_contribution, + target_provision: *target_provision, + accumulated_provision: Default::default(), + not_before: *not_before, + }), + ); + }, + ); + + self.initial_enabled_trading_pairs.iter().for_each(|trading_pair| { + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Enabled); + }); + + self.initial_added_liquidity_pools + .iter() + .for_each(|(who, trading_pairs_data)| { + trading_pairs_data + .iter() + .for_each(|(trading_pair, (deposit_amount_0, deposit_amount_1))| { + let result = match >::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Enabled => >::do_add_liquidity( + who, + trading_pair.first(), + trading_pair.second(), + *deposit_amount_0, + *deposit_amount_1, + Default::default(), + false, + ), + _ => Err(Error::::MustBeEnabled.into()), + }; + + assert!(result.is_ok(), "genesis add lidquidity pool failed."); + }); + }); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Trading with DEX, swap with exact supply amount + /// + /// - `path`: trading path. + /// - `supply_amount`: exact supply amount. + /// - `min_target_amount`: acceptable minimum target amount. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::swap_with_exact_supply(path.len() as u32))] + pub fn swap_with_exact_supply( + origin: OriginFor, + path: Vec, + #[pallet::compact] supply_amount: Balance, + #[pallet::compact] min_target_amount: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_swap_with_exact_supply(&who, &path, supply_amount, min_target_amount)?; + Ok(()) + } + + /// Trading with DEX, swap with exact target amount + /// + /// - `path`: trading path. + /// - `target_amount`: exact target amount. + /// - `max_supply_amount`: acceptable maximum supply amount. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::swap_with_exact_target(path.len() as u32))] + pub fn swap_with_exact_target( + origin: OriginFor, + path: Vec, + #[pallet::compact] target_amount: Balance, + #[pallet::compact] max_supply_amount: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_swap_with_exact_target(&who, &path, target_amount, max_supply_amount)?; + Ok(()) + } + + /// Add liquidity to Enabled trading pair. + /// - Add provision success will record the provision, issue shares to caller in the initial + /// exchange rate when trading pair convert to Enabled. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `max_amount_a`: maximum amount of currency_id_a is allowed to inject to liquidity + /// pool. + /// - `max_amount_b`: maximum amount of currency_id_b is allowed to inject to liquidity + /// pool. + /// - `min_share_increment`: minimum acceptable share amount. + /// - `stake_increment_share`: indicates whether to stake increased dex share to earn + /// incentives + #[pallet::call_index(2)] + #[pallet::weight(if *stake_increment_share { + ::WeightInfo::add_liquidity_and_stake() + } else { + ::WeightInfo::add_liquidity() + })] + pub fn add_liquidity( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] max_amount_a: Balance, + #[pallet::compact] max_amount_b: Balance, + #[pallet::compact] min_share_increment: Balance, + stake_increment_share: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_add_liquidity( + &who, + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + min_share_increment, + stake_increment_share, + )?; + Ok(()) + } + + /// Add provision to Provisioning trading pair. + /// If succeed, will record the provision, but shares issuing will happen after the + /// trading pair convert to Enabled status. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `amount_a`: provision amount for currency_id_a. + /// - `amount_b`: provision amount for currency_id_b. + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::add_provision())] + pub fn add_provision( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] amount_a: Balance, + #[pallet::compact] amount_b: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_add_provision(&who, currency_id_a, currency_id_b, amount_a, amount_b)?; + Ok(()) + } + + /// Claim dex share for founders who have participated in trading pair provision. + /// + /// - `owner`: founder account. + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::claim_dex_share())] + pub fn claim_dex_share( + origin: OriginFor, + owner: T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_claim_dex_share(&owner, currency_id_a, currency_id_b)?; + Ok(()) + } + + /// Remove liquidity from specific liquidity pool in the form of burning + /// shares, and withdrawing currencies in trading pairs from liquidity + /// pool in proportion, and withdraw liquidity incentive interest. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `remove_share`: liquidity amount to remove. + /// - `min_withdrawn_a`: minimum acceptable withrawn for currency_id_a. + /// - `min_withdrawn_b`: minimum acceptable withrawn for currency_id_b. + /// - `by_unstake`: this flag indicates whether to withdraw share which is on incentives. + #[pallet::call_index(5)] + #[pallet::weight(if *by_unstake { + ::WeightInfo::remove_liquidity_by_unstake() + } else { + ::WeightInfo::remove_liquidity() + })] + pub fn remove_liquidity( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] remove_share: Balance, + #[pallet::compact] min_withdrawn_a: Balance, + #[pallet::compact] min_withdrawn_b: Balance, + by_unstake: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_remove_liquidity( + &who, + currency_id_a, + currency_id_b, + remove_share, + min_withdrawn_a, + min_withdrawn_b, + by_unstake, + )?; + Ok(()) + } + + /// List a new provisioning trading pair. + #[pallet::call_index(6)] + #[pallet::weight((::WeightInfo::list_provisioning(), DispatchClass::Operational))] + pub fn list_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] min_contribution_a: Balance, + #[pallet::compact] min_contribution_b: Balance, + #[pallet::compact] target_provision_a: Balance, + #[pallet::compact] target_provision_b: Balance, + #[pallet::compact] not_before: BlockNumberFor, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Disabled + ), + Error::::MustBeDisabled + ); + ensure!( + T::Currency::total_issuance(trading_pair.dex_share_currency_id()).is_zero() + && ProvisioningPool::::iter_prefix(trading_pair).next().is_none(), + Error::::NotAllowedList + ); + + let check_asset_registry = |currency_id: CurrencyId| match currency_id { + CurrencyId::Erc20(_) | CurrencyId::ForeignAsset(_) => { + T::Erc20InfoMapping::name(currency_id) + .map(|_| ()) + .ok_or(Error::::AssetUnregistered) + } + CurrencyId::Token(_) | CurrencyId::DexShare(_, _) => Ok(()), /* No registration required */ + }; + check_asset_registry(currency_id_a)?; + check_asset_registry(currency_id_b)?; + + let (min_contribution, target_provision) = if currency_id_a == trading_pair.first() { + ( + (min_contribution_a, min_contribution_b), + (target_provision_a, target_provision_b), + ) + } else { + ( + (min_contribution_b, min_contribution_a), + (target_provision_b, target_provision_a), + ) + }; + + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution, + target_provision, + accumulated_provision: Default::default(), + not_before, + }), + ); + Self::deposit_event(Event::ListProvisioning { trading_pair }); + Ok(()) + } + + /// List a new trading pair, trading pair will become Enabled status + /// after provision process. + #[pallet::call_index(7)] + #[pallet::weight((::WeightInfo::update_provisioning_parameters(), DispatchClass::Operational))] + pub fn update_provisioning_parameters( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] min_contribution_a: Balance, + #[pallet::compact] min_contribution_b: Balance, + #[pallet::compact] target_provision_a: Balance, + #[pallet::compact] target_provision_b: Balance, + #[pallet::compact] not_before: BlockNumberFor, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::Provisioning(provisioning_parameters) => { + let (min_contribution, target_provision) = if currency_id_a == trading_pair.first() { + ( + (min_contribution_a, min_contribution_b), + (target_provision_a, target_provision_b), + ) + } else { + ( + (min_contribution_b, min_contribution_a), + (target_provision_b, target_provision_a), + ) + }; + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution, + target_provision, + accumulated_provision: provisioning_parameters.accumulated_provision, + not_before, + }), + ); + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + + /// Enable a Provisioning trading pair if meet the condition. + #[pallet::call_index(8)] + #[pallet::weight((::WeightInfo::end_provisioning(), DispatchClass::Operational))] + pub fn end_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + let (total_provision_0, total_provision_1) = provisioning_parameters.accumulated_provision; + ensure!( + frame_system::Pallet::::block_number() >= provisioning_parameters.not_before + && !total_provision_0.is_zero() + && !total_provision_1.is_zero() + && (total_provision_0 >= provisioning_parameters.target_provision.0 + || total_provision_1 >= provisioning_parameters.target_provision.1), + Error::::UnqualifiedProvision + ); + + // directly use token_0 as base to calculate initial dex share amount. + let (share_exchange_rate_0, share_exchange_rate_1) = ( + ExchangeRate::one(), + ExchangeRate::checked_from_rational(total_provision_0, total_provision_1) + .ok_or(ArithmeticError::Overflow)?, + ); + let shares_from_provision_0 = share_exchange_rate_0 + .checked_mul_int(total_provision_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_provision_1 = share_exchange_rate_1 + .checked_mul_int(total_provision_1) + .ok_or(ArithmeticError::Overflow)?; + let total_shares_to_issue = shares_from_provision_0 + .checked_add(shares_from_provision_1) + .ok_or(ArithmeticError::Overflow)?; + + // issue total shares to module account + T::Currency::deposit( + trading_pair.dex_share_currency_id(), + &Self::account_id(), + total_shares_to_issue, + )?; + + // inject provision to liquidity pool + Self::try_mutate_liquidity_pool(&trading_pair, |(pool_0, pool_1)| -> DispatchResult { + *pool_0 = pool_0.checked_add(total_provision_0).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_add(total_provision_1).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + + // update trading_pair to Enabled status + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Enabled); + + // record initial exchange rate so that founders can use it to calculate their own shares + InitialShareExchangeRates::::insert( + trading_pair, + (share_exchange_rate_0, share_exchange_rate_1), + ); + + Self::deposit_event(Event::ProvisioningToEnabled { + trading_pair, + pool_0: total_provision_0, + pool_1: total_provision_1, + share_amount: total_shares_to_issue, + }); + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + + /// Enable a trading pair + /// if the status of trading pair is `Disabled`, or `Provisioning` without any accumulated + /// provision, enable it directly. + #[pallet::call_index(9)] + #[pallet::weight((::WeightInfo::enable_trading_pair(), DispatchClass::Operational))] + pub fn enable_trading_pair( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Disabled => {} + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + ensure!( + provisioning_parameters.accumulated_provision.0.is_zero() + && provisioning_parameters.accumulated_provision.1.is_zero(), + Error::::StillProvisioning + ); + } + TradingPairStatus::<_, _>::Enabled => return Err(Error::::AlreadyEnabled.into()), + } + + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Enabled); + Self::deposit_event(Event::EnableTradingPair { trading_pair }); + Ok(()) + } + + /// Disable an `Enabled` trading pair. + #[pallet::call_index(10)] + #[pallet::weight((::WeightInfo::disable_trading_pair(), DispatchClass::Operational))] + pub fn disable_trading_pair( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Disabled); + Self::deposit_event(Event::DisableTradingPair { trading_pair }); + Ok(()) + } + + /// Refund provision if the provision has already aborted. + /// + /// - `owner`: founder account. + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::refund_provision())] + pub fn refund_provision( + origin: OriginFor, + owner: T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Disabled + ), + Error::::MustBeDisabled + ); + + // Make sure the trading pair has not been successfully ended provisioning. + ensure!( + InitialShareExchangeRates::::get(trading_pair) == Default::default(), + Error::::NotAllowedRefund + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, &owner, |maybe_contribution| -> DispatchResult { + if let Some((contribution_0, contribution_1)) = maybe_contribution.take() { + T::Currency::transfer(trading_pair.first(), &Self::account_id(), &owner, contribution_0)?; + T::Currency::transfer(trading_pair.second(), &Self::account_id(), &owner, contribution_1)?; + + // decrease ref count + frame_system::Pallet::::dec_consumers(&owner); + + Self::deposit_event(Event::RefundProvision { + who: owner.clone(), + currency_0: trading_pair.first(), + contribution_0, + currency_1: trading_pair.second(), + contribution_1, + }); + } + Ok(()) + }) + } + + /// Abort provision when it doesn't meet the target and expires. + #[pallet::call_index(12)] + #[pallet::weight((::WeightInfo::abort_provisioning(), DispatchClass::Operational))] + pub fn abort_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + let (total_provision_0, total_provision_1) = provisioning_parameters.accumulated_provision; + let met_target = !total_provision_0.is_zero() + && !total_provision_1.is_zero() + && (total_provision_0 >= provisioning_parameters.target_provision.0 + || total_provision_1 >= provisioning_parameters.target_provision.1); + let expired = frame_system::Pallet::::block_number() + > provisioning_parameters + .not_before + .saturating_add(T::ExtendedProvisioningBlocks::get()); + + if !met_target && expired { + // update trading_pair to disabled status + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Disabled); + + Self::deposit_event(Event::ProvisioningAborted { + trading_pair, + accumulated_provision_0: total_provision_0, + accumulated_provision_1: total_provision_1, + }); + } + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + } +} + +impl Pallet { + fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + fn try_mutate_liquidity_pool( + trading_pair: &TradingPair, + f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, + ) -> sp_std::result::Result { + LiquidityPool::::try_mutate(trading_pair, |(pool_0, pool_1)| -> sp_std::result::Result { + let old_pool_0 = *pool_0; + let old_pool_1 = *pool_1; + f((pool_0, pool_1)).map(move |result| { + if *pool_0 != old_pool_0 || *pool_1 != old_pool_1 { + T::OnLiquidityPoolUpdated::happened(&(*trading_pair, *pool_0, *pool_1)); + } + + result + }) + }) + } + + fn do_claim_dex_share(who: &T::AccountId, currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> DispatchResult { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + !matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Provisioning(_) + ), + Error::::StillProvisioning + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, who, |maybe_contribution| -> DispatchResult { + if let Some((contribution_0, contribution_1)) = maybe_contribution.take() { + let (exchange_rate_0, exchange_rate_1) = Self::initial_share_exchange_rates(trading_pair); + let shares_from_provision_0 = exchange_rate_0 + .checked_mul_int(contribution_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_provision_1 = exchange_rate_1 + .checked_mul_int(contribution_1) + .ok_or(ArithmeticError::Overflow)?; + let shares_to_claim = shares_from_provision_0 + .checked_add(shares_from_provision_1) + .ok_or(ArithmeticError::Overflow)?; + + T::Currency::transfer( + trading_pair.dex_share_currency_id(), + &Self::account_id(), + who, + shares_to_claim, + )?; + + // decrease ref count + frame_system::Pallet::::dec_consumers(who); + } + Ok(()) + })?; + + // clear InitialShareExchangeRates once it is all claimed + if ProvisioningPool::::iter_prefix(trading_pair).next().is_none() { + InitialShareExchangeRates::::remove(trading_pair); + } + + Ok(()) + } + + fn do_add_provision( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + contribution_a: Balance, + contribution_b: Balance, + ) -> DispatchResult { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + let mut provision_parameters = match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provision_parameters) => provision_parameters, + _ => return Err(Error::::MustBeProvisioning.into()), + }; + let (contribution_0, contribution_1) = if currency_id_a == trading_pair.first() { + (contribution_a, contribution_b) + } else { + (contribution_b, contribution_a) + }; + + ensure!( + contribution_0 >= provision_parameters.min_contribution.0 + || contribution_1 >= provision_parameters.min_contribution.1, + Error::::InvalidContributionIncrement + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, who, |maybe_pool| -> DispatchResult { + let existed = maybe_pool.is_some(); + let mut pool = maybe_pool.unwrap_or_default(); + pool.0 = pool.0.checked_add(contribution_0).ok_or(ArithmeticError::Overflow)?; + pool.1 = pool.1.checked_add(contribution_1).ok_or(ArithmeticError::Overflow)?; + + let module_account_id = Self::account_id(); + T::Currency::transfer(trading_pair.first(), who, &module_account_id, contribution_0)?; + T::Currency::transfer(trading_pair.second(), who, &module_account_id, contribution_1)?; + + *maybe_pool = Some(pool); + + if !existed && maybe_pool.is_some() { + if frame_system::Pallet::::inc_consumers(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); + } + } + + provision_parameters.accumulated_provision.0 = provision_parameters + .accumulated_provision + .0 + .checked_add(contribution_0) + .ok_or(ArithmeticError::Overflow)?; + provision_parameters.accumulated_provision.1 = provision_parameters + .accumulated_provision + .1 + .checked_add(contribution_1) + .ok_or(ArithmeticError::Overflow)?; + + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::<_, _>::Provisioning(provision_parameters), + ); + + Self::deposit_event(Event::AddProvision { + who: who.clone(), + currency_0: trading_pair.first(), + contribution_0, + currency_1: trading_pair.second(), + contribution_1, + }); + Ok(()) + }) + } + + fn do_add_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled, + ); + + ensure!( + !max_amount_a.is_zero() && !max_amount_b.is_zero(), + Error::::InvalidLiquidityIncrement + ); + + Self::try_mutate_liquidity_pool( + &trading_pair, + |(pool_0, pool_1)| -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + let dex_share_currency_id = trading_pair.dex_share_currency_id(); + let total_shares = T::Currency::total_issuance(dex_share_currency_id); + let (max_amount_0, max_amount_1) = if currency_id_a == trading_pair.first() { + (max_amount_a, max_amount_b) + } else { + (max_amount_b, max_amount_a) + }; + let (pool_0_increment, pool_1_increment, share_increment): (Balance, Balance, Balance) = + if total_shares.is_zero() { + // directly use token_0 as base to calculate initial dex share amount. + let (exchange_rate_0, exchange_rate_1) = ( + ExchangeRate::one(), + ExchangeRate::checked_from_rational(max_amount_0, max_amount_1) + .ok_or(ArithmeticError::Overflow)?, + ); + + let shares_from_token_0 = exchange_rate_0 + .checked_mul_int(max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_token_1 = exchange_rate_1 + .checked_mul_int(max_amount_1) + .ok_or(ArithmeticError::Overflow)?; + let initial_shares = shares_from_token_0 + .checked_add(shares_from_token_1) + .ok_or(ArithmeticError::Overflow)?; + + (max_amount_0, max_amount_1, initial_shares) + } else { + let exchange_rate_0_1 = + ExchangeRate::checked_from_rational(*pool_1, *pool_0).ok_or(ArithmeticError::Overflow)?; + let input_exchange_rate_0_1 = ExchangeRate::checked_from_rational(max_amount_1, max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + + if input_exchange_rate_0_1 <= exchange_rate_0_1 { + // max_amount_0 may be too much, calculate the actual amount_0 + let exchange_rate_1_0 = ExchangeRate::checked_from_rational(*pool_0, *pool_1) + .ok_or(ArithmeticError::Overflow)?; + let amount_0 = exchange_rate_1_0 + .checked_mul_int(max_amount_1) + .ok_or(ArithmeticError::Overflow)?; + let share_increment = Ratio::checked_from_rational(amount_0, *pool_0) + .and_then(|n| n.checked_mul_int(total_shares)) + .ok_or(ArithmeticError::Overflow)?; + (amount_0, max_amount_1, share_increment) + } else { + // max_amount_1 is too much, calculate the actual amount_1 + let amount_1 = exchange_rate_0_1 + .checked_mul_int(max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + let share_increment = Ratio::checked_from_rational(amount_1, *pool_1) + .and_then(|n| n.checked_mul_int(total_shares)) + .ok_or(ArithmeticError::Overflow)?; + (max_amount_0, amount_1, share_increment) + } + }; + + ensure!( + !share_increment.is_zero() && !pool_0_increment.is_zero() && !pool_1_increment.is_zero(), + Error::::InvalidLiquidityIncrement, + ); + ensure!( + share_increment >= min_share_increment, + Error::::UnacceptableShareIncrement + ); + + let module_account_id = Self::account_id(); + T::Currency::transfer(trading_pair.first(), who, &module_account_id, pool_0_increment)?; + T::Currency::transfer(trading_pair.second(), who, &module_account_id, pool_1_increment)?; + T::Currency::deposit(dex_share_currency_id, who, share_increment)?; + + *pool_0 = pool_0.checked_add(pool_0_increment).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_add(pool_1_increment).ok_or(ArithmeticError::Overflow)?; + + if stake_increment_share { + T::SwapDexIncentives::do_deposit_dex_share(who, dex_share_currency_id, share_increment)?; + } + + Self::deposit_event(Event::AddLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_increment, + currency_1: trading_pair.second(), + pool_1: pool_1_increment, + share_increment, + }); + + if currency_id_a == trading_pair.first() { + Ok((pool_0_increment, pool_1_increment, share_increment)) + } else { + Ok((pool_1_increment, pool_0_increment, share_increment)) + } + }, + ) + } + + #[transactional] + fn do_remove_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + if remove_share.is_zero() { + return Ok((Zero::zero(), Zero::zero())); + } + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + let dex_share_currency_id = trading_pair.dex_share_currency_id(); + + Self::try_mutate_liquidity_pool( + &trading_pair, + |(pool_0, pool_1)| -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let (min_withdrawn_0, min_withdrawn_1) = if currency_id_a == trading_pair.first() { + (min_withdrawn_a, min_withdrawn_b) + } else { + (min_withdrawn_b, min_withdrawn_a) + }; + let total_shares = T::Currency::total_issuance(dex_share_currency_id); + let proportion = + Ratio::checked_from_rational(remove_share, total_shares).ok_or(ArithmeticError::Overflow)?; + let pool_0_decrement = proportion.checked_mul_int(*pool_0).ok_or(ArithmeticError::Overflow)?; + let pool_1_decrement = proportion.checked_mul_int(*pool_1).ok_or(ArithmeticError::Overflow)?; + let module_account_id = Self::account_id(); + + ensure!( + pool_0_decrement >= min_withdrawn_0 && pool_1_decrement >= min_withdrawn_1, + Error::::UnacceptableLiquidityWithdrawn, + ); + + if by_unstake { + T::SwapDexIncentives::do_withdraw_dex_share(who, dex_share_currency_id, remove_share)?; + } + T::Currency::withdraw(dex_share_currency_id, who, remove_share)?; + T::Currency::transfer(trading_pair.first(), &module_account_id, who, pool_0_decrement)?; + T::Currency::transfer(trading_pair.second(), &module_account_id, who, pool_1_decrement)?; + + *pool_0 = pool_0.checked_sub(pool_0_decrement).ok_or(ArithmeticError::Underflow)?; + *pool_1 = pool_1.checked_sub(pool_1_decrement).ok_or(ArithmeticError::Underflow)?; + + Self::deposit_event(Event::RemoveLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_decrement, + currency_1: trading_pair.second(), + pool_1: pool_1_decrement, + share_decrement: remove_share, + }); + + if currency_id_a == trading_pair.first() { + Ok((pool_0_decrement, pool_1_decrement)) + } else { + Ok((pool_1_decrement, pool_0_decrement)) + } + }, + ) + } + + fn get_liquidity(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { + if let Some(trading_pair) = TradingPair::from_currency_ids(currency_id_a, currency_id_b) { + let (pool_0, pool_1) = Self::liquidity_pool(trading_pair); + if currency_id_a == trading_pair.first() { + (pool_0, pool_1) + } else { + (pool_1, pool_0) + } + } else { + (Zero::zero(), Zero::zero()) + } + } + + /// Get how much target amount will be got for specific supply amount. + fn get_target_amount(supply_pool: Balance, target_pool: Balance, supply_amount: Balance) -> Balance { + if supply_amount.is_zero() || supply_pool.is_zero() || target_pool.is_zero() { + Zero::zero() + } else { + let (fee_numerator, fee_denominator) = T::GetExchangeFee::get(); + let supply_amount_with_fee: U256 = + U256::from(supply_amount).saturating_mul(U256::from(fee_denominator.saturating_sub(fee_numerator))); + let numerator: U256 = supply_amount_with_fee.saturating_mul(U256::from(target_pool)); + let denominator: U256 = U256::from(supply_pool) + .saturating_mul(U256::from(fee_denominator)) + .saturating_add(supply_amount_with_fee); + + numerator + .checked_div(denominator) + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + } + + /// Get how much supply amount will be paid for specific target amount. + fn get_supply_amount(supply_pool: Balance, target_pool: Balance, target_amount: Balance) -> Balance { + if target_amount.is_zero() || supply_pool.is_zero() || target_pool.is_zero() { + Zero::zero() + } else { + let (fee_numerator, fee_denominator) = T::GetExchangeFee::get(); + let numerator: U256 = U256::from(supply_pool) + .saturating_mul(U256::from(target_amount)) + .saturating_mul(U256::from(fee_denominator)); + let denominator: U256 = U256::from(target_pool) + .saturating_sub(U256::from(target_amount)) + .saturating_mul(U256::from(fee_denominator.saturating_sub(fee_numerator))); + + numerator + .checked_div(denominator) + .and_then(|r| r.checked_add(U256::one())) // add 1 to result so that correct the possible losses caused by remainder discarding in + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + } + + fn get_target_amounts( + path: &[CurrencyId], + supply_amount: Balance, + ) -> sp_std::result::Result, DispatchError> { + Self::validate_path(path)?; + + let path_length = path.len(); + let mut target_amounts: Vec = vec![Zero::zero(); path_length]; + target_amounts[0] = supply_amount; + + let mut i: usize = 0; + while i + 1 < path_length { + let trading_pair = + TradingPair::from_currency_ids(path[i], path[i + 1]).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + let (supply_pool, target_pool) = Self::get_liquidity(path[i], path[i + 1]); + ensure!( + !supply_pool.is_zero() && !target_pool.is_zero(), + Error::::InsufficientLiquidity + ); + let target_amount = Self::get_target_amount(supply_pool, target_pool, target_amounts[i]); + ensure!(!target_amount.is_zero(), Error::::ZeroTargetAmount); + + target_amounts[i + 1] = target_amount; + i += 1; + } + + Ok(target_amounts) + } + + fn get_supply_amounts( + path: &[CurrencyId], + target_amount: Balance, + ) -> sp_std::result::Result, DispatchError> { + Self::validate_path(path)?; + + let path_length = path.len(); + let mut supply_amounts: Vec = vec![Zero::zero(); path_length]; + supply_amounts[path_length - 1] = target_amount; + + let mut i: usize = path_length - 1; + while i > 0 { + let trading_pair = + TradingPair::from_currency_ids(path[i - 1], path[i]).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + let (supply_pool, target_pool) = Self::get_liquidity(path[i - 1], path[i]); + ensure!( + !supply_pool.is_zero() && !target_pool.is_zero(), + Error::::InsufficientLiquidity + ); + let supply_amount = Self::get_supply_amount(supply_pool, target_pool, supply_amounts[i]); + ensure!(!supply_amount.is_zero(), Error::::ZeroSupplyAmount); + + supply_amounts[i - 1] = supply_amount; + i -= 1; + } + + Ok(supply_amounts) + } + + fn validate_path(path: &[CurrencyId]) -> DispatchResult { + let path_length = path.len(); + ensure!( + path_length >= 2 && path_length <= T::TradingPathLimit::get().saturated_into(), + Error::::InvalidTradingPathLength + ); + ensure!(path.get(0) != path.get(path_length - 1), Error::::InvalidTradingPath); + + Ok(()) + } + + fn _swap( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + supply_increment: Balance, + target_decrement: Balance, + ) -> DispatchResult { + if let Some(trading_pair) = TradingPair::from_currency_ids(supply_currency_id, target_currency_id) { + Self::try_mutate_liquidity_pool(&trading_pair, |(pool_0, pool_1)| -> DispatchResult { + let invariant_before_swap: U256 = U256::from(*pool_0).saturating_mul(U256::from(*pool_1)); + + if supply_currency_id == trading_pair.first() { + *pool_0 = pool_0.checked_add(supply_increment).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_sub(target_decrement).ok_or(ArithmeticError::Underflow)?; + } else { + *pool_0 = pool_0.checked_sub(target_decrement).ok_or(ArithmeticError::Underflow)?; + *pool_1 = pool_1.checked_add(supply_increment).ok_or(ArithmeticError::Overflow)?; + } + + // invariant check to ensure the constant product formulas (k = x * y) + let invariant_after_swap: U256 = U256::from(*pool_0).saturating_mul(U256::from(*pool_1)); + ensure!( + invariant_after_swap >= invariant_before_swap, + Error::::InvariantCheckFailed, + ); + Ok(()) + })?; + } + Ok(()) + } + + fn _swap_by_path(path: &[CurrencyId], amounts: &[Balance]) -> DispatchResult { + let mut i: usize = 0; + while i + 1 < path.len() { + let (supply_currency_id, target_currency_id) = (path[i], path[i + 1]); + let (supply_increment, target_decrement) = (amounts[i], amounts[i + 1]); + Self::_swap( + supply_currency_id, + target_currency_id, + supply_increment, + target_decrement, + )?; + i += 1; + } + Ok(()) + } + + #[transactional] + fn do_swap_with_exact_supply( + who: &T::AccountId, + path: &[CurrencyId], + supply_amount: Balance, + min_target_amount: Balance, + ) -> sp_std::result::Result { + let amounts = Self::get_target_amounts(path, supply_amount)?; + ensure!( + amounts[amounts.len() - 1] >= min_target_amount, + Error::::InsufficientTargetAmount + ); + let module_account_id = Self::account_id(); + let actual_target_amount = amounts[amounts.len() - 1]; + + T::Currency::transfer(path[0], who, &module_account_id, supply_amount)?; + Self::_swap_by_path(path, &amounts)?; + T::Currency::transfer(path[path.len() - 1], &module_account_id, who, actual_target_amount)?; + + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); + Ok(actual_target_amount) + } + + #[transactional] + fn do_swap_with_exact_target( + who: &T::AccountId, + path: &[CurrencyId], + target_amount: Balance, + max_supply_amount: Balance, + ) -> sp_std::result::Result { + let amounts = Self::get_supply_amounts(path, target_amount)?; + ensure!(amounts[0] <= max_supply_amount, Error::::ExcessiveSupplyAmount); + let module_account_id = Self::account_id(); + let actual_supply_amount = amounts[0]; + + T::Currency::transfer(path[0], who, &module_account_id, actual_supply_amount)?; + Self::_swap_by_path(path, &amounts)?; + T::Currency::transfer(path[path.len() - 1], &module_account_id, who, target_amount)?; + + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); + Ok(actual_supply_amount) + } +} + +impl SwapDexManager for Pallet { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { + Self::get_liquidity(currency_id_a, currency_id_b) + } + + fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option { + let trading_pair = TradingPair::from_currency_ids(currency_id_a, currency_id_b)?; + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Disabled => None, + TradingPairStatus::<_, _>::Provisioning(_) | TradingPairStatus::<_, _>::Enabled => { + T::Erc20InfoMapping::encode_evm_address(trading_pair.dex_share_currency_id()) + } + } + } + + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::get_target_amounts(path, exact_supply_amount) + .ok() + .and_then(|amounts| { + if amounts[amounts.len() - 1] >= minimum_target_amount { + Some((exact_supply_amount, amounts[amounts.len() - 1])) + } else { + None + } + }) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::get_supply_amounts(path, exact_target_amount) + .ok() + .and_then(|amounts| { + if amounts[0] <= maximum_supply_amount { + Some((amounts[0], exact_target_amount)) + } else { + None + } + }) + } + } + } + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + let default_swap_path = vec![supply_currency_id, target_currency_id]; + let mut maybe_best = Self::get_swap_amount(&default_swap_path, limit) + .map(|(supply_amout, target_amount)| (default_swap_path, supply_amout, target_amount)); + + for path_joint in alternative_path_joint_list { + if !path_joint.is_empty() { + let mut swap_path = vec![]; + + if supply_currency_id != path_joint[0] { + swap_path.push(supply_currency_id); + } + + swap_path.extend(path_joint.clone()); + + if target_currency_id != path_joint[path_joint.len() - 1] { + swap_path.push(target_currency_id); + } + + if let Some((supply_amount, target_amount)) = Self::get_swap_amount(&swap_path, limit) { + if let Some((_, previous_supply, previous_target)) = maybe_best { + if supply_amount > previous_supply || target_amount < previous_target { + continue; + } + } + + maybe_best = Some((swap_path, supply_amount, target_amount)); + } + } + } + + maybe_best + } + + fn swap_with_specific_path( + who: &T::AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::do_swap_with_exact_supply(who, path, exact_supply_amount, minimum_target_amount) + .map(|actual_target_amount| (exact_supply_amount, actual_target_amount)) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::do_swap_with_exact_target(who, path, exact_target_amount, maximum_supply_amount) + .map(|actual_supply_amount| (actual_supply_amount, exact_target_amount)) + } + } + } + + // `do_add_liquidity` is used in genesis_build, + // but transactions are not supported by BasicExternalities, + // put `transactional` here + #[transactional] + fn add_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + Self::do_add_liquidity( + who, + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + min_share_increment, + stake_increment_share, + ) + } + + fn remove_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + Self::do_remove_liquidity( + who, + currency_id_a, + currency_id_b, + remove_share, + min_withdrawn_a, + min_withdrawn_b, + by_unstake, + ) + } +} diff --git a/blockchain/modules/edfis-swap-dex/src/mock.rs b/blockchain/modules/edfis-swap-dex/src/mock.rs new file mode 100644 index 000000000..375facd01 --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/src/mock.rs @@ -0,0 +1,222 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Mocks for the dex module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, Nothing}, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockErc20InfoMapping, SpecificJointsSwap}; +use orml_traits::{parameter_type_with_key, MultiReservableCurrency}; +use primitives::{Amount, TokenSymbol}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_std::cell::RefCell; + +pub type BlockNumber = u64; +pub type AccountId = u128; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; +pub const CAROL: AccountId = 3; +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const WBTC: CurrencyId = CurrencyId::Token(TokenSymbol::FA_WBTC); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); + +parameter_types! { + pub static USSDWBTCPair: TradingPair = TradingPair::from_currency_ids(USSD, WBTC).unwrap(); + pub static USSDEDFPair: TradingPair = TradingPair::from_currency_ids(USSD, EDF).unwrap(); + pub static EDFWBTCPair: TradingPair = TradingPair::from_currency_ids(EDF, WBTC).unwrap(); +} + +mod dex { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +pub struct MockSwapDexIncentives; +impl SwapDexIncentives for MockSwapDexIncentives { + fn do_deposit_dex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + Tokens::reserve(lp_currency_id, who, amount) + } + + fn do_withdraw_dex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + let _ = Tokens::unreserve(lp_currency_id, who, amount); + Ok(()) + } +} + +ord_parameter_types! { + pub const ListingOrigin: AccountId = 3; +} + +parameter_types! { + pub const GetExchangeFee: (u32, u32) = (1, 100); + pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![EDF], + ]; +} + +thread_local! { + pub static USSD_EDF_POOL_RECORD: RefCell<(Balance, Balance)> = RefCell::new((0, 0)); +} + +pub struct MockOnLiquidityPoolUpdated; +impl Happened<(TradingPair, Balance, Balance)> for MockOnLiquidityPoolUpdated { + fn happened(info: &(TradingPair, Balance, Balance)) { + let (trading_pair, new_pool_0, new_pool_1) = info; + if *trading_pair == USSDEDFPair::get() { + USSD_EDF_POOL_RECORD.with(|v| *v.borrow_mut() = (*new_pool_0, *new_pool_1)); + } + } +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<3>; + type PalletId = DEXPalletId; + type Erc20InfoMapping = MockErc20InfoMapping; + type WeightInfo = (); + type SwapDexIncentives = MockSwapDexIncentives; + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<2000>; + type OnLiquidityPoolUpdated = MockOnLiquidityPoolUpdated; +} + +parameter_types! { + pub USSDJoint: Vec> = vec![vec![USSD]]; + pub SEEJoint: Vec> = vec![vec![SEE]]; +} + +pub type USSDJointSwap = SpecificJointsSwap; +pub type SEEJointSwap = SpecificJointsSwap; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + DexModule: dex, + Tokens: orml_tokens, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, + initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumber)>, + initial_enabled_trading_pairs: Vec, + initial_added_liquidity_pools: Vec<(AccountId, Vec<(TradingPair, (Balance, Balance))>)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, SEE, 1_000_000_000_000_000_000u128), + (BOB, SEE, 1_000_000_000_000_000_000u128), + (ALICE, USSD, 1_000_000_000_000_000_000u128), + (BOB, USSD, 1_000_000_000_000_000_000u128), + (ALICE, WBTC, 1_000_000_000_000_000_000u128), + (BOB, WBTC, 1_000_000_000_000_000_000u128), + (ALICE, EDF, 1_000_000_000_000_000_000u128), + (BOB, EDF, 1_000_000_000_000_000_000u128), + ], + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: vec![], + initial_added_liquidity_pools: vec![], + } + } +} + +impl ExtBuilder { + pub fn initialize_enabled_trading_pairs(mut self) -> Self { + self.initial_enabled_trading_pairs = vec![USSDEDFPair::get(), USSDWBTCPair::get(), EDFWBTCPair::get()]; + self + } + + pub fn initialize_added_liquidity_pools(mut self, who: AccountId) -> Self { + self.initial_added_liquidity_pools = vec![( + who, + vec![ + (USSDEDFPair::get(), (1_000_000u128, 2_000_000u128)), + (USSDWBTCPair::get(), (1_000_000u128, 2_000_000u128)), + (EDFWBTCPair::get(), (1_000_000u128, 2_000_000u128)), + ], + )]; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + dex::GenesisConfig:: { + initial_listing_trading_pairs: self.initial_listing_trading_pairs, + initial_enabled_trading_pairs: self.initial_enabled_trading_pairs, + initial_added_liquidity_pools: self.initial_added_liquidity_pools, + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/edfis-swap-dex/src/tests.rs b/blockchain/modules/edfis-swap-dex/src/tests.rs new file mode 100644 index 000000000..975645b8a --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/src/tests.rs @@ -0,0 +1,1997 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Unit tests for the dex module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{ + SEEJointSwap, USSDWBTCPair, USSDEDFPair, USSDJointSwap, EDFWBTCPair, DexModule, ExtBuilder, ListingOrigin, Runtime, + RuntimeEvent, RuntimeOrigin, System, Tokens, SEE, ALICE, USSD, USSD_EDF_POOL_RECORD, BOB, WBTC, CAROL, EDF, +}; +use module_support::{Swap, SwapError}; +use orml_traits::MultiReservableCurrency; +use sp_core::H160; +use sp_runtime::traits::BadOrigin; +use std::str::FromStr; + +#[test] +fn list_provisioning_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::list_provisioning( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + BadOrigin + ); + + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ListProvisioning { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + USSD, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::InvalidCurrencyId + ); + + assert_noop!( + DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::MustBeDisabled + ); + + assert_noop!( + DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + CurrencyId::ForeignAsset(0), + USSD, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); + assert_noop!( + DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + CurrencyId::ForeignAsset(0), + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); + }); +} + +#[test] +fn update_provisioning_parameters_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::update_provisioning_parameters( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + BadOrigin + ); + + assert_noop!( + DexModule::update_provisioning_parameters( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::MustBeProvisioning + ); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + + assert_ok!(DexModule::update_provisioning_parameters( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 2_000_000_000_000u128, + 0, + 3_000_000_000_000u128, + 2_000_000_000_000u128, + 50, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (2_000_000_000_000u128, 0), + target_provision: (3_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 50, + }) + ); + }); +} + +#[test] +fn enable_diabled_trading_pair_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::enable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + BadOrigin + ); + + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_ok!(DexModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::EnableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + DexModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), EDF, USSD), + Error::::AlreadyEnabled + ); + }); +} + +#[test] +fn enable_provisioning_without_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128 + )); + + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_ok!(DexModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::EnableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + DexModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::StillProvisioning + ); + }); +} + +#[test] +fn end_provisioning_trading_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 1_000_000_000_000u128, + 2_000_000_000_000u128 + )); + + assert_noop!( + DexModule::end_provisioning(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::UnqualifiedProvision + ); + System::set_block_number(10); + + assert_eq!( + DexModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (1_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 10, + }) + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + assert_eq!(DexModule::liquidity_pool(USSDWBTCPair::get()), (0, 0)); + assert_eq!(Tokens::total_issuance(USSDWBTCPair::get().dex_share_currency_id()), 0); + assert_eq!( + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &DexModule::account_id()), + 0 + ); + + assert_ok!(DexModule::end_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ProvisioningToEnabled { + trading_pair: USSDWBTCPair::get(), + pool_0: 1_000_000_000_000u128, + pool_1: 2_000_000_000_000u128, + share_amount: 2_000_000_000_000u128, + })); + assert_eq!( + DexModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + (ExchangeRate::one(), ExchangeRate::checked_from_rational(1, 2).unwrap()) + ); + assert_eq!( + DexModule::liquidity_pool(USSDWBTCPair::get()), + (1_000_000_000_000u128, 2_000_000_000_000u128) + ); + assert_eq!( + Tokens::total_issuance(USSDWBTCPair::get().dex_share_currency_id()), + 2_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &DexModule::account_id()), + 2_000_000_000_000u128 + ); + }); +} + +#[test] +fn abort_provisioning_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF), + Error::::MustBeProvisioning + ); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 1000, + )); + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 1000, + )); + + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128 + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + WBTC, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + )); + + // not expired, nothing happened. + System::set_block_number(2000); + assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (1_000_000_000_000u128, 1_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + assert_eq!( + DexModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + + // both expired, the provision for USSD-EDF could be aborted, the provision for USSD-WBTC + // couldn't be aborted because it's already met the target. + System::set_block_number(3001); + assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ProvisioningAborted { + trading_pair: USSDEDFPair::get(), + accumulated_provision_0: 1_000_000_000_000u128, + accumulated_provision_1: 1_000_000_000_000u128, + })); + + assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + assert_eq!( + DexModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + }); +} + +#[test] +fn refund_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 5_000_000_000_000_000_000u128, + 4_000_000_000_000_000_000u128, + 1000, + )); + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + 1000, + )); + + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000_000_000u128, + 1_000_000_000_000_000_000u128 + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 0, + 600_000_000_000_000_000u128, + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + WBTC, + 100_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + )); + + assert_noop!( + DexModule::refund_provision(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + Error::::MustBeDisabled + ); + + // abort provisioning of USSD-EDF + System::set_block_number(3001); + assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + + assert_eq!( + DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (1_000_000_000_000_000_000u128, 1_000_000_000_000_000_000u128) + ); + assert_eq!( + DexModule::provisioning_pool(USSDEDFPair::get(), BOB), + (0, 600_000_000_000_000_000u128) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 1_100_000_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(EDF, &DexModule::account_id()), + 1_600_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 0); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 900_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &BOB), 400_000_000_000_000_000u128); + + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + assert_ok!(DexModule::refund_provision( + RuntimeOrigin::signed(ALICE), + ALICE, + USSD, + EDF + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RefundProvision { + who: ALICE, + currency_0: USSD, + contribution_0: 1_000_000_000_000_000_000u128, + currency_1: EDF, + contribution_1: 1_000_000_000_000_000_000u128, + })); + + assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 100_000_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(EDF, &DexModule::account_id()), + 600_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); + + assert_ok!(DexModule::refund_provision( + RuntimeOrigin::signed(ALICE), + BOB, + USSD, + EDF + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RefundProvision { + who: BOB, + currency_0: USSD, + contribution_0: 0, + currency_1: EDF, + contribution_1: 600_000_000_000_000_000u128, + })); + + assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 100_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 900_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000u128); + assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); + + // not allow refund if the provisioning has been ended before. + assert_ok!(DexModule::end_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_ok!(DexModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + DexModule::provisioning_pool(USSDWBTCPair::get(), BOB), + (100_000_000_000_000_000u128, 100_000_000_000_000_000u128) + ); + assert_noop!( + DexModule::refund_provision(RuntimeOrigin::signed(BOB), BOB, USSD, WBTC), + Error::::NotAllowedRefund + ); + }); +} + +#[test] +fn disable_trading_pair_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + + assert_noop!( + DexModule::disable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + BadOrigin + ); + + assert_ok!(DexModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::DisableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + DexModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF), + Error::::MustBeEnabled + ); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_noop!( + DexModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::MustBeEnabled + ); + }); +} + +#[test] +fn on_liquidity_pool_updated_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_eq!(USSD_EDF_POOL_RECORD.with(|v| *v.borrow()), (0, 0)); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_eq!( + USSD_EDF_POOL_RECORD.with(|v| *v.borrow()), + (5000000000000, 1000000000000) + ); + }); +} + +#[test] +fn add_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + ), + Error::::MustBeProvisioning + ); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 10, + )); + + assert_noop!( + DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 4_999_999_999_999u128, + 999_999_999_999u128, + ), + Error::::InvalidContributionIncrement + ); + + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + let alice_ref_count_0 = System::consumers(&ALICE); + + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000u128, + 0, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 0), + not_before: 10, + }) + ); + assert_eq!( + DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (5_000_000_000_000u128, 0) + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 5_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddProvision { + who: ALICE, + currency_0: USSD, + contribution_0: 5_000_000_000_000u128, + currency_1: EDF, + contribution_1: 0, + })); + }); +} + +#[test] +fn claim_dex_share_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 0, + )); + + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000_000u128, + 200_000_000_000_000u128, + )); + assert_ok!(DexModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 4_000_000_000_000_000u128, + 800_000_000_000_000u128, + )); + + assert_noop!( + DexModule::claim_dex_share(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + Error::::StillProvisioning + ); + + assert_ok!(DexModule::end_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + + let lp_currency_id = USSDEDFPair::get().dex_share_currency_id(); + + assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + assert_eq!( + DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + (ExchangeRate::one(), ExchangeRate::saturating_from_rational(5, 1)) + ); + assert_eq!( + Tokens::free_balance(lp_currency_id, &DexModule::account_id()), + 10_000_000_000_000_000u128 + ); + assert_eq!( + DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (1_000_000_000_000_000u128, 200_000_000_000_000u128) + ); + assert_eq!( + DexModule::provisioning_pool(USSDEDFPair::get(), BOB), + (4_000_000_000_000_000u128, 800_000_000_000_000u128) + ); + assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 0); + assert_eq!(Tokens::free_balance(lp_currency_id, &BOB), 0); + + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + assert_ok!(DexModule::claim_dex_share( + RuntimeOrigin::signed(ALICE), + ALICE, + USSD, + EDF + )); + assert_eq!( + Tokens::free_balance(lp_currency_id, &DexModule::account_id()), + 8_000_000_000_000_000u128 + ); + assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 2_000_000_000_000_000u128); + assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); + assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + + assert_ok!(DexModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_ok!(DexModule::claim_dex_share(RuntimeOrigin::signed(BOB), BOB, USSD, EDF)); + assert_eq!(Tokens::free_balance(lp_currency_id, &DexModule::account_id()), 0); + assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_eq!(Tokens::free_balance(lp_currency_id, &BOB), 8_000_000_000_000_000u128); + assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); + assert!(!InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + }); +} + +#[test] +fn get_liquidity_work() { + ExtBuilder::default().build().execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (1000, 20)); + assert_eq!(DexModule::liquidity_pool(USSDEDFPair::get()), (1000, 20)); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (1000, 20)); + assert_eq!(DexModule::get_liquidity(EDF, USSD), (20, 1000)); + }); +} + +#[test] +fn get_target_amount_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(DexModule::get_target_amount(10000, 0, 1000), 0); + assert_eq!(DexModule::get_target_amount(0, 20000, 1000), 0); + assert_eq!(DexModule::get_target_amount(10000, 20000, 0), 0); + assert_eq!(DexModule::get_target_amount(10000, 1, 1000000), 0); + assert_eq!(DexModule::get_target_amount(10000, 20000, 10000), 9949); + assert_eq!(DexModule::get_target_amount(10000, 20000, 1000), 1801); + }); +} + +#[test] +fn get_supply_amount_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(DexModule::get_supply_amount(10000, 0, 1000), 0); + assert_eq!(DexModule::get_supply_amount(0, 20000, 1000), 0); + assert_eq!(DexModule::get_supply_amount(10000, 20000, 0), 0); + assert_eq!(DexModule::get_supply_amount(10000, 1, 1), 0); + assert_eq!(DexModule::get_supply_amount(10000, 20000, 9949), 9999); + assert_eq!(DexModule::get_target_amount(10000, 20000, 9999), 9949); + assert_eq!(DexModule::get_supply_amount(10000, 20000, 1801), 1000); + assert_eq!(DexModule::get_target_amount(10000, 20000, 1000), 1801); + }); +} + +#[test] +fn get_target_amounts_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + assert_noop!( + DexModule::get_target_amounts(&[EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, USSD, WBTC, EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, USSD, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, USSD, SEE], 10000), + Error::::MustBeEnabled, + ); + assert_eq!( + DexModule::get_target_amounts(&[EDF, USSD], 10000), + Ok(vec![10000, 24874]) + ); + assert_eq!( + DexModule::get_target_amounts(&[EDF, USSD, WBTC], 10000), + Ok(vec![10000, 24874, 1]) + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, USSD, WBTC], 100), + Error::::ZeroTargetAmount, + ); + assert_noop!( + DexModule::get_target_amounts(&[EDF, WBTC], 100), + Error::::InsufficientLiquidity, + ); + }); +} + +#[test] +fn calculate_amount_for_big_number_work() { + ExtBuilder::default().build().execute_with(|| { + LiquidityPool::::insert( + USSDEDFPair::get(), + (171_000_000_000_000_000_000_000, 56_000_000_000_000_000_000_000), + ); + assert_eq!( + DexModule::get_supply_amount( + 171_000_000_000_000_000_000_000, + 56_000_000_000_000_000_000_000, + 1_000_000_000_000_000_000_000 + ), + 3_140_495_867_768_595_041_323 + ); + assert_eq!( + DexModule::get_target_amount( + 171_000_000_000_000_000_000_000, + 56_000_000_000_000_000_000_000, + 3_140_495_867_768_595_041_323 + ), + 1_000_000_000_000_000_000_000 + ); + }); +} + +#[test] +fn get_supply_amounts_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + assert_noop!( + DexModule::get_supply_amounts(&[EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, USSD, WBTC, EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, USSD, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, USSD, SEE], 10000), + Error::::MustBeEnabled, + ); + assert_eq!( + DexModule::get_supply_amounts(&[EDF, USSD], 24874), + Ok(vec![10000, 24874]) + ); + assert_eq!( + DexModule::get_supply_amounts(&[EDF, USSD], 25000), + Ok(vec![10102, 25000]) + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, USSD, WBTC], 10000), + Error::::ZeroSupplyAmount, + ); + assert_noop!( + DexModule::get_supply_amounts(&[EDF, WBTC], 10000), + Error::::InsufficientLiquidity, + ); + }); +} + +#[test] +fn _swap_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + + assert_eq!(DexModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_noop!( + DexModule::_swap(USSD, EDF, 50000, 5001), + Error::::InvariantCheckFailed + ); + assert_ok!(DexModule::_swap(USSD, EDF, 50000, 5000)); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (100000, 5000)); + assert_ok!(DexModule::_swap(EDF, USSD, 100, 800)); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (99200, 5100)); + }); +} + +#[test] +fn _swap_by_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + + assert_eq!(DexModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_eq!(DexModule::get_liquidity(USSD, WBTC), (100000, 10)); + assert_ok!(DexModule::_swap_by_path(&[EDF, USSD], &[10000, 25000])); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (25000, 20000)); + assert_ok!(DexModule::_swap_by_path(&[EDF, USSD, WBTC], &[100000, 20000, 1])); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (5000, 120000)); + assert_eq!(DexModule::get_liquidity(USSD, WBTC), (120000, 9)); + }); +} + +#[test] +fn add_liquidity_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_noop!( + DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + SEE, + USSD, + 100_000_000, + 100_000_000, + 0, + false + ), + Error::::MustBeEnabled + ); + assert_noop!( + DexModule::add_liquidity(RuntimeOrigin::signed(ALICE), USSD, EDF, 0, 100_000_000, 0, false), + Error::::InvalidLiquidityIncrement + ); + + assert_eq!(DexModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 5_000_000_000_000, + currency_1: EDF, + pool_1: 1_000_000_000_000, + share_increment: 10_000_000_000_000, + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (5_000_000_000_000, 1_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 1_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 10_000_000_000_000 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + DexModule::add_liquidity(RuntimeOrigin::signed(BOB), USSD, EDF, 4, 1, 0, true,), + Error::::InvalidLiquidityIncrement, + ); + + assert_noop!( + DexModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 50_000_000_000_000, + 8_000_000_000_000, + 80_000_000_000_001, + true, + ), + Error::::UnacceptableShareIncrement + ); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 50_000_000_000_000, + 8_000_000_000_000, + 80_000_000_000_000, + true, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddLiquidity { + who: BOB, + currency_0: USSD, + pool_0: 40_000_000_000_000, + currency_1: EDF, + pool_1: 8_000_000_000_000, + share_increment: 80_000_000_000_000, + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (45_000_000_000_000, 9_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 45_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 9_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 80_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &BOB), 999_960_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_992_000_000_000_000); + }); +} + +#[test] +fn remove_liquidity_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false + )); + assert_noop!( + DexModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSDEDFPair::get().dex_share_currency_id(), + EDF, + 100_000_000, + 0, + 0, + false, + ), + Error::::InvalidCurrencyId + ); + + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (5_000_000_000_000, 1_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 1_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 10_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_000_000_000_000); + + assert_noop!( + DexModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_001, + 800_000_000_000, + false, + ), + Error::::UnacceptableLiquidityWithdrawn + ); + assert_noop!( + DexModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_000, + 800_000_000_001, + false, + ), + Error::::UnacceptableLiquidityWithdrawn + ); + assert_ok!(DexModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_000, + 800_000_000_000, + false, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 4_000_000_000_000, + currency_1: EDF, + pool_1: 800_000_000_000, + share_decrement: 8_000_000_000_000, + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (1_000_000_000_000, 200_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 1_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 200_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 2_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_999_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_800_000_000_000); + + assert_ok!(DexModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 2_000_000_000_000, + 0, + 0, + false, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 1_000_000_000_000, + currency_1: EDF, + pool_1: 200_000_000_000, + share_decrement: 2_000_000_000_000, + })); + assert_eq!(DexModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + true + )); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 10_000_000_000_000 + ); + assert_ok!(DexModule::remove_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 2_000_000_000_000, + 0, + 0, + true, + )); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 8_000_000_000_000 + ); + }); +} + +#[test] +fn do_swap_with_exact_supply_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 100_000_000_000_000, + 10_000_000_000, + 0, + false, + )); + + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (500_000_000_000_000, 100_000_000_000_000) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 600_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD], 100_000_000_000_000, 250_000_000_000_000,), + Error::::InsufficientTargetAmount + ); + assert_noop!( + DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, WBTC, EDF], 100_000_000_000_000, 0), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, EDF], 100_000_000_000_000, 0), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::do_swap_with_exact_supply(&BOB, &[EDF, SEE], 100_000_000_000_000, 0), + Error::::MustBeEnabled, + ); + + assert_ok!(DexModule::do_swap_with_exact_supply( + &BOB, + &[EDF, USSD], + 100_000_000_000_000, + 200_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (251_256_281_407_036, 200_000_000_000_000) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 351_256_281_407_036 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 200_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_900_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_ok!(DexModule::do_swap_with_exact_supply( + &BOB, + &[EDF, USSD, WBTC], + 200_000_000_000_000, + 1, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD, WBTC], + liquidity_changes: vec![200_000_000_000_000, 124_996_843_514_053, 5_530_663_837], + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (126_259_437_892_983, 400_000_000_000_000) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (224_996_843_514_053, 4_469_336_163) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 351_256_281_407_036 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 400_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 4_469_336_163); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_700_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_530_663_837); + }); +} + +#[test] +fn do_swap_with_exact_target_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 100_000_000_000_000, + 10_000_000_000, + 0, + false, + )); + + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (500_000_000_000_000, 100_000_000_000_000) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 600_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + DexModule::do_swap_with_exact_target(&BOB, &[EDF, USSD], 250_000_000_000_000, 100_000_000_000_000,), + Error::::ExcessiveSupplyAmount + ); + assert_noop!( + DexModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD, WBTC, EDF], + 250_000_000_000_000, + 200_000_000_000_000, + ), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + DexModule::do_swap_with_exact_target(&BOB, &[EDF, USSD, EDF], 250_000_000_000_000, 200_000_000_000_000,), + Error::::InvalidTradingPath, + ); + assert_noop!( + DexModule::do_swap_with_exact_target(&BOB, &[EDF, SEE], 250_000_000_000_000, 200_000_000_000_000), + Error::::MustBeEnabled, + ); + + assert_ok!(DexModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD], + 250_000_000_000_000, + 200_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![101_010_101_010_102, 250_000_000_000_000], + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (250_000_000_000_000, 201_010_101_010_102) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 350_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 201_010_101_010_102); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_898_989_898_989_898); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_ok!(DexModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD, WBTC], + 5_000_000_000, + 2_000_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD, WBTC], + liquidity_changes: vec![137_654_580_386_993, 101_010_101_010_102, 5_000_000_000], + })); + assert_eq!( + DexModule::get_liquidity(USSD, EDF), + (148_989_898_989_898, 338_664_681_397_095) + ); + assert_eq!( + DexModule::get_liquidity(USSD, WBTC), + (201_010_101_010_102, 5_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &DexModule::account_id()), + 350_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 338_664_681_397_095); + assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 5_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_761_335_318_602_905); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_000_000_000); + }); +} + +#[test] +fn initialize_added_liquidity_pools_genesis_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .initialize_added_liquidity_pools(ALICE) + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_eq!(DexModule::get_liquidity(USSD, EDF), (1000000, 2000000)); + assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 2000000); + assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 3000000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 2000000 + ); + }); +} + +#[test] +fn get_swap_amount_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + assert_eq!( + DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 0)), + Some((10000, 24874)) + ); + assert_eq!( + DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 24875)), + None + ); + assert_eq!( + DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(Balance::max_value(), 24874)), + Some((10000, 24874)) + ); + assert_eq!( + DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(9999, 24874)), + None + ); + }); +} + +#[test] +fn get_best_price_swap_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (300000, 100000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (50000, 10000)); + LiquidityPool::::insert(EDFWBTCPair::get(), (10000, 10000)); + + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 30), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(0, 0), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![SEE]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![EDF]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![USSD]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![WBTC]]), + Some((vec![EDF, WBTC, USSD], 10, 44)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10000, 0), vec![vec![WBTC]]), + Some((vec![EDF, USSD], 10000, 27024)) + ); + + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(10, 30), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(0, 0), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![SEE]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![EDF]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![USSD]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![WBTC]]), + Some((vec![EDF, WBTC, USSD], 8, 30)) + ); + assert_eq!( + DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(100000, 20000), vec![vec![WBTC]]), + Some((vec![EDF, USSD], 7216, 20000)) + ); + }); +} + +#[test] +fn swap_with_specific_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + + assert_noop!( + DexModule::swap_with_specific_path( + &BOB, + &[EDF, USSD], + SwapLimit::ExactSupply(100_000_000_000_000, 248_743_718_592_965) + ), + Error::::InsufficientTargetAmount + ); + + assert_ok!(DexModule::swap_with_specific_path( + &BOB, + &[EDF, USSD], + SwapLimit::ExactSupply(100_000_000_000_000, 200_000_000_000_000) + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); + + assert_noop!( + DexModule::swap_with_specific_path( + &BOB, + &[USSD, EDF], + SwapLimit::ExactTarget(253_794_223_643_470, 100_000_000_000_000) + ), + Error::::ExcessiveSupplyAmount + ); + + assert_ok!(DexModule::swap_with_specific_path( + &BOB, + &[USSD, EDF], + SwapLimit::ExactTarget(300_000_000_000_000, 100_000_000_000_000) + )); + System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![USSD, EDF], + liquidity_changes: vec![253_794_223_643_471, 100_000_000_000_000], + })); + }); +} + +#[test] +fn get_liquidity_token_address_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!(DexModule::get_liquidity_token_address(USSD, EDF), None); + + assert_ok!(DexModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_eq!( + DexModule::get_liquidity_token_address(USSD, EDF), + Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) + ); + + assert_ok!(DexModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + DexModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + assert_eq!( + DexModule::get_liquidity_token_address(USSD, EDF), + Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) + ); + }); +} + +#[test] +fn specific_joint_swap_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_ok!(DexModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + + assert_eq!( + USSDJointSwap::get_swap_amount(WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + Some((10000, 9800)) + ); + assert_eq!( + SEEJointSwap::get_swap_amount(WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + None + ); + + assert_noop!( + USSDJointSwap::swap(&CAROL, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + orml_tokens::Error::::BalanceTooLow, + ); + assert_noop!( + USSDJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 9801)), + SwapError::CannotSwap, + ); + assert_noop!( + SEEJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + SwapError::CannotSwap, + ); + + assert_eq!( + USSDJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + Ok((10000, 9800)), + ); + + assert_eq!( + USSDJointSwap::swap(&BOB, EDF, WBTC, SwapLimit::ExactTarget(20000, 10000)), + Ok((10204, 10000)), + ); + }); +} diff --git a/blockchain/modules/edfis-swap-dex/src/weights.rs b/blockchain/modules/edfis-swap-dex/src/weights.rs new file mode 100644 index 000000000..43c492e63 --- /dev/null +++ b/blockchain/modules/edfis-swap-dex/src/weights.rs @@ -0,0 +1,241 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for module_dex +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_dex +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./modules/dex/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_dex. +pub trait WeightInfo { + fn enable_trading_pair() -> Weight; + fn disable_trading_pair() -> Weight; + fn list_provisioning() -> Weight; + fn update_provisioning_parameters() -> Weight; + fn end_provisioning() -> Weight; + fn add_provision() -> Weight; + fn claim_dex_share() -> Weight; + fn add_liquidity() -> Weight; + fn add_liquidity_and_stake() -> Weight; + fn remove_liquidity() -> Weight; + fn remove_liquidity_by_unstake() -> Weight; + fn swap_with_exact_supply(u: u32, ) -> Weight; + fn swap_with_exact_target(u: u32, ) -> Weight; + fn refund_provision() -> Weight; + fn abort_provisioning() -> Weight; +} + +/// Weights for module_dex using the Acala node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn enable_trading_pair() -> Weight { + Weight::from_parts(24_728_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn disable_trading_pair() -> Weight { + Weight::from_parts(24_891_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn list_provisioning() -> Weight { + Weight::from_parts(37_619_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn update_provisioning_parameters() -> Weight { + Weight::from_parts(11_808_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn end_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn add_provision() -> Weight { + Weight::from_parts(127_543_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn claim_dex_share() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn add_liquidity() -> Weight { + Weight::from_parts(184_975_000, 0) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + fn add_liquidity_and_stake() -> Weight { + Weight::from_parts(258_276_000, 0) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + fn remove_liquidity() -> Weight { + Weight::from_parts(158_440_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn remove_liquidity_by_unstake() -> Weight { + Weight::from_parts(277_297_000, 0) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + fn swap_with_exact_supply(u: u32, ) -> Weight { + Weight::from_parts(93_799_000, 0) + // Standard Error: 117_000 + .saturating_add(Weight::from_parts(16_008_000, 0).saturating_mul(u as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn swap_with_exact_target(u: u32, ) -> Weight { + Weight::from_parts(93_966_000, 0) + // Standard Error: 226_000 + .saturating_add(Weight::from_parts(16_058_000, 0).saturating_mul(u as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn refund_provision() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn abort_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn enable_trading_pair() -> Weight { + Weight::from_parts(24_728_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn disable_trading_pair() -> Weight { + Weight::from_parts(24_891_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn list_provisioning() -> Weight { + Weight::from_parts(37_619_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn update_provisioning_parameters() -> Weight { + Weight::from_parts(11_808_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn end_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn add_provision() -> Weight { + Weight::from_parts(127_543_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn claim_dex_share() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn add_liquidity() -> Weight { + Weight::from_parts(184_975_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + fn add_liquidity_and_stake() -> Weight { + Weight::from_parts(258_276_000, 0) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + fn remove_liquidity() -> Weight { + Weight::from_parts(158_440_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn remove_liquidity_by_unstake() -> Weight { + Weight::from_parts(277_297_000, 0) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + fn swap_with_exact_supply(u: u32, ) -> Weight { + Weight::from_parts(93_799_000, 0) + // Standard Error: 117_000 + .saturating_add(Weight::from_parts(16_008_000, 0).saturating_mul(u as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn swap_with_exact_target(u: u32, ) -> Weight { + Weight::from_parts(93_966_000, 0) + // Standard Error: 226_000 + .saturating_add(Weight::from_parts(16_058_000, 0).saturating_mul(u as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn refund_provision() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn abort_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } +} diff --git a/blockchain/modules/evm-accounts/Cargo.toml b/blockchain/modules/evm-accounts/Cargo.toml index a0bb8c9f4..3627436f8 100644 --- a/blockchain/modules/evm-accounts/Cargo.toml +++ b/blockchain/modules/evm-accounts/Cargo.toml @@ -1,48 +1,55 @@ -[package] -name = "module-evm-accounts" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -module-support = { path = "../support", default-features = false } - -[dev-dependencies] -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-currencies = { path = "../submodules/orml/currencies" } -orml-tokens = { path = "../submodules/orml/tokens" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "libsecp256k1/std", - "sp-core/std", - "sp-runtime/std", - "sp-io/std", - "sp-std/std", - "frame-support/std", - "frame-system/std", - "primitives/std", - "orml-traits/std", - "module-support/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] +[package] +name = "module-evm-accounts" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true } +libsecp256k1 = { workspace = true, features = ["hmac", "static-context"], optional = true } +scale-info = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +orml-traits = { workspace = true } +primitives = { workspace = true } +module-support = { workspace = true } +module-evm-utility-macro = { workspace = true } + +[dev-dependencies] +pallet-balances = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "libsecp256k1", + "libsecp256k1/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-io/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "orml-traits/std", + "module-support/std", +] +runtime-benchmarks = [ + "libsecp256k1/hmac", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/evm-accounts/src/lib.rs b/blockchain/modules/evm-accounts/src/lib.rs index 84d6b8062..dc541047f 100644 --- a/blockchain/modules/evm-accounts/src/lib.rs +++ b/blockchain/modules/evm-accounts/src/lib.rs @@ -23,29 +23,30 @@ //! ## Overview //! //! Evm Accounts module provide a two way mapping between Substrate accounts and -//! EVM accounts so user only have deal with one account / private key. +//! EVM accounts so user only has to deal with one account / private key. #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] -use codec::Encode; use frame_support::{ ensure, pallet_prelude::*, traits::{Currency, IsType, OnKilledAccount}, - transactional, }; use frame_system::{ensure_signed, pallet_prelude::*}; -use module_support::AddressMapping; +use module_evm_utility_macro::keccak256; +use module_support::{AddressMapping, EVMAccountsManager}; use orml_traits::currency::TransferAll; -use primitives::{evm::EvmAddress, AccountIndex}; -use sp_core::{crypto::AccountId32, ecdsa}; +use parity_scale_codec::Encode; +use primitives::{evm::EvmAddress, to_bytes, AccountIndex}; +use sp_core::crypto::AccountId32; +use sp_core::{H160, H256}; use sp_io::{ crypto::secp256k1_ecdsa_recover, hashing::{blake2_256, keccak_256}, }; use sp_runtime::{ - traits::{LookupError, StaticLookup}, + traits::{LookupError, StaticLookup, Zero}, MultiAddress, }; use sp_std::{marker::PhantomData, vec::Vec}; @@ -57,7 +58,8 @@ pub mod weights; pub use module::*; pub use weights::WeightInfo; -pub type EcdsaSignature = ecdsa::Signature; +/// A signature (a 512-bit value, plus 8 bits for recovery ID). +pub type Eip712Signature = [u8; 65]; #[frame_support::pallet] pub mod module { @@ -65,7 +67,7 @@ pub mod module { #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The Currency for managing Evm account assets. type Currency: Currency; @@ -73,6 +75,10 @@ pub mod module { /// Mapping from address to account id. type AddressMapping: AddressMapping; + /// Chain ID of EVM. + #[pallet::constant] + type ChainId: Get; + /// Merge free balance from source to dest. type TransferAll: TransferAll; @@ -81,12 +87,14 @@ pub mod module { } #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId")] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { /// Mapping between Substrate accounts and EVM accounts - /// claim account. \[account_id, evm_address\] - ClaimAccount(T::AccountId, EvmAddress), + /// claim account. + ClaimAccount { + account_id: T::AccountId, + evm_address: EvmAddress, + }, } /// Error for evm accounts module. @@ -122,7 +130,7 @@ pub mod module { pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks> for Pallet {} #[pallet::call] impl Pallet { @@ -131,12 +139,12 @@ pub mod module { /// /// - `eth_address`: The address to bind to the caller's account /// - `eth_signature`: A signature generated by the address to prove ownership + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim_account())] - #[transactional] pub fn claim_account( origin: OriginFor, eth_address: EvmAddress, - eth_signature: EcdsaSignature, + eth_signature: Eip712Signature, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -148,8 +156,7 @@ pub mod module { ); // recover evm address from signature - let address = Self::eth_recover(ð_signature, &who.using_encoded(to_ascii_hex), &[][..]) - .ok_or(Error::::BadSignature)?; + let address = Self::verify_eip712_signature(&who, ð_signature).ok_or(Error::::BadSignature)?; ensure!(eth_address == address, Error::::InvalidSignature); // check if the evm padded address already exists @@ -162,7 +169,10 @@ pub mod module { Accounts::::insert(eth_address, &who); EvmAddresses::::insert(&who, eth_address); - Self::deposit_event(Event::ClaimAccount(who, eth_address)); + Self::deposit_event(Event::ClaimAccount { + account_id: who, + evm_address: eth_address, + }); Ok(()) } @@ -170,75 +180,94 @@ pub mod module { /// Claim account mapping between Substrate accounts and a generated EVM /// address based off of those accounts. /// Ensure eth_address has not been mapped + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::claim_default_account())] pub fn claim_default_account(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - - // ensure account_id has not been mapped - ensure!(!EvmAddresses::::contains_key(&who), Error::::AccountIdHasMapped); - - let eth_address = T::AddressMapping::get_or_create_evm_address(&who); - - Self::deposit_event(Event::ClaimAccount(who, eth_address)); - + let _ = Self::do_claim_default_evm_address(who)?; Ok(()) } } } impl Pallet { - // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` - // would sign. - pub fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec { - let prefix = b"setheum evm:"; - let mut l = prefix.len() + what.len() + extra.len(); - let mut rev = Vec::new(); - while l > 0 { - rev.push(b'0' + (l % 10) as u8); - l /= 10; - } - let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - v.extend(rev.into_iter().rev()); - v.extend_from_slice(&prefix[..]); - v.extend_from_slice(what); - v.extend_from_slice(extra); - v - } - - // Attempts to recover the Ethereum address from a message signature signed by - // using the Ethereum RPC's `personal_sign` and `eth_sign`. - pub fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { - let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); - let mut res = EvmAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) - } - - // Returns an Etherum public key derived from an Ethereum secret key. - pub fn eth_public(secret: &secp256k1::SecretKey) -> secp256k1::PublicKey { - secp256k1::PublicKey::from_secret_key(secret) + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] + // Returns an Ethereum public key derived from an Ethereum secret key. + pub fn eth_public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { + libsecp256k1::PublicKey::from_secret_key(secret) } #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] - // Returns an Etherum address derived from an Ethereum secret key. + // Returns an Ethereum address derived from an Ethereum secret key. // Only for tests - pub fn eth_address(secret: &secp256k1::SecretKey) -> EvmAddress { + pub fn eth_address(secret: &libsecp256k1::SecretKey) -> EvmAddress { EvmAddress::from_slice(&keccak_256(&Self::eth_public(secret).serialize()[1..65])[12..]) } + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] // Constructs a message and signs it. - pub fn eth_sign(secret: &secp256k1::SecretKey, what: &[u8], extra: &[u8]) -> EcdsaSignature { - let msg = keccak_256(&Self::ethereum_signable_message(&to_ascii_hex(what)[..], extra)); - let (sig, recovery_id) = secp256k1::sign(&secp256k1::Message::parse(&msg), secret); + pub fn eth_sign(secret: &libsecp256k1::SecretKey, who: &T::AccountId) -> Eip712Signature { + let msg = keccak_256(&Self::eip712_signable_message(who)); + let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); let mut r = [0u8; 65]; r[0..64].copy_from_slice(&sig.serialize()[..]); r[64] = recovery_id.serialize(); - EcdsaSignature::from_slice(&r) + r + } + + fn verify_eip712_signature(who: &T::AccountId, sig: &[u8; 65]) -> Option { + let msg = Self::eip712_signable_message(who); + let msg_hash = keccak_256(msg.as_slice()); + + recover_signer(sig, &msg_hash) + } + + // Eip-712 message to be signed + fn eip712_signable_message(who: &T::AccountId) -> Vec { + let domain_separator = Self::evm_account_domain_separator(); + let payload_hash = Self::evm_account_payload_hash(who); + + let mut msg = b"\x19\x01".to_vec(); + msg.extend_from_slice(&domain_separator); + msg.extend_from_slice(&payload_hash); + msg } + + fn evm_account_payload_hash(who: &T::AccountId) -> [u8; 32] { + let tx_type_hash = keccak256!("Transaction(bytes substrateAddress)"); + let mut tx_msg = tx_type_hash.to_vec(); + tx_msg.extend_from_slice(&keccak_256(&who.encode())); + keccak_256(tx_msg.as_slice()) + } + + fn evm_account_domain_separator() -> [u8; 32] { + let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)"); + let mut domain_seperator_msg = domain_hash.to_vec(); + domain_seperator_msg.extend_from_slice(keccak256!("Setheum EVM claim")); // name + domain_seperator_msg.extend_from_slice(keccak256!("1")); // version + domain_seperator_msg.extend_from_slice(&to_bytes(T::ChainId::get())); // chain id + domain_seperator_msg + .extend_from_slice(frame_system::Pallet::::block_hash(BlockNumberFor::::zero()).as_ref()); // genesis block hash + keccak_256(domain_seperator_msg.as_slice()) + } + + fn do_claim_default_evm_address(who: T::AccountId) -> Result { + // ensure account_id has not been mapped + ensure!(!EvmAddresses::::contains_key(&who), Error::::AccountIdHasMapped); + + let eth_address = T::AddressMapping::get_or_create_evm_address(&who); + + Ok(eth_address) + } +} + +fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option { + secp256k1_ecdsa_recover(sig, msg_hash) + .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) + .ok() } -// Creates a an EvmAddress from an AccountId by appending the bytes "evm:" to +// Creates an EvmAddress from an AccountId by appending the bytes "evm:" to // the account_id and hashing it. fn account_to_default_evm_address(account_id: &impl Encode) -> EvmAddress { let payload = (b"evm:", account_id); @@ -272,7 +301,9 @@ where EvmAddresses::::get(account_id).or_else(|| { let data: &[u8] = account_id.into_ref().as_ref(); // Return the underlying EVM address if it exists otherwise return None - if data.starts_with(b"evm:") { + // account_id must start with "evm:" and ends with 8 bytes of zeros + // the range [4..24] contains the EVM address + if data.starts_with(b"evm:") && data.ends_with(&[0u8; 8]) { Some(EvmAddress::from_slice(&data[4..24])) } else { None @@ -287,8 +318,13 @@ where let addr = account_to_default_evm_address(account_id); // create reverse mapping - Accounts::::insert(&addr, &account_id); - EvmAddresses::::insert(&account_id, &addr); + Accounts::::insert(addr, account_id); + EvmAddresses::::insert(account_id, addr); + + Pallet::::deposit_event(Event::ClaimAccount { + account_id: account_id.clone(), + evm_address: addr, + }); addr }) @@ -310,11 +346,7 @@ where pub struct CallKillAccount(PhantomData); impl OnKilledAccount for CallKillAccount { fn on_killed_account(who: &T::AccountId) { - // remove the reserve mapping that could be created by - // `get_or_create_evm_address` - Accounts::::remove(account_to_default_evm_address(who.into_ref())); - - // remove mapping created by `claim_account` + // remove mapping created by `claim_account` or `get_or_create_evm_address` if let Some(evm_addr) = Pallet::::evm_addresses(who) { Accounts::::remove(evm_addr); EvmAddresses::::remove(who); @@ -338,14 +370,21 @@ impl StaticLookup for Pallet { } } -/// Converts the given binary data into ASCII-encoded hex. It will be twice -/// the length. -pub fn to_ascii_hex(data: &[u8]) -> Vec { - let mut r = Vec::with_capacity(data.len() * 2); - let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); - for &b in data.iter() { - push_nibble(b / 16); - push_nibble(b % 16); +impl EVMAccountsManager for Pallet { + /// Returns the AccountId used to generate the given EvmAddress. + fn get_account_id(address: &EvmAddress) -> T::AccountId { + T::AddressMapping::get_account_id(address) + } + + /// Returns the EvmAddress associated with a given AccountId or the underlying EvmAddress of the + /// AccountId. + fn get_evm_address(account_id: &T::AccountId) -> Option { + T::AddressMapping::get_evm_address(account_id) + } + + /// Claim account mapping between AccountId and a generated EvmAddress based off of the + /// AccountId. + fn claim_default_evm_address(account_id: &T::AccountId) -> Result { + Self::do_claim_default_evm_address(account_id.clone()) } - r } diff --git a/blockchain/modules/evm-accounts/src/mock.rs b/blockchain/modules/evm-accounts/src/mock.rs index a7123fc24..5fd7f811b 100644 --- a/blockchain/modules/evm-accounts/src/mock.rs +++ b/blockchain/modules/evm-accounts/src/mock.rs @@ -23,12 +23,15 @@ #![cfg(test)] use super::*; -use frame_support::{construct_runtime, parameter_types}; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU128, ConstU64, Everything, Nothing}, +}; use orml_traits::parameter_type_with_key; use primitives::{Amount, Balance, CurrencyId, TokenSymbol}; use sp_core::{crypto::AccountId32, H256}; use sp_io::hashing::keccak_256; -use sp_runtime::{testing::Header, traits::IdentityLookup}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; pub type AccountId = AccountId32; pub type BlockNumber = u64; @@ -39,23 +42,17 @@ pub const BOB: AccountId = AccountId32::new([1u8; 32]); mod evm_accounts { pub use super::super::*; } - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - impl frame_system::Config for Runtime { - type Origin = Origin; - type Index = u64; - type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type BlockWeights = (); type BlockLength = (); type Version = (); @@ -64,25 +61,28 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = (); + type BaseCallFilter = Everything; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} impl pallet_balances::Config for Runtime { type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU128<1>; type AccountStore = frame_system::Pallet; type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); } parameter_type_with_key! { @@ -92,15 +92,17 @@ parameter_type_with_key! { } impl orml_tokens::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Balance = Balance; type Amount = Amount; type CurrencyId = CurrencyId; type WeightInfo = (); type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); + type CurrencyHooks = (); type MaxLocks = (); - type DustRemovalWhitelist = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; } parameter_types! { @@ -108,7 +110,6 @@ parameter_types! { } impl orml_currencies::Config for Runtime { - type Event = Event; type MultiCurrency = Tokens; type NativeCurrency = AdaptedBasicCurrency; type GetNativeCurrencyId = GetNativeCurrencyId; @@ -117,27 +118,23 @@ impl orml_currencies::Config for Runtime { pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; impl Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type ChainId = (); type AddressMapping = EvmAddressMapping; type TransferAll = Currencies; type WeightInfo = (); } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - EvmAccountsModule: evm_accounts::{Pallet, Call, Storage, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Currencies: orml_currencies::{Pallet, Call, Event}, + pub enum Runtime { + System: frame_system, + EvmAccountsModule: evm_accounts, + Tokens: orml_tokens, + Balances: pallet_balances, + Currencies: orml_currencies, } ); @@ -151,8 +148,8 @@ impl Default for ExtBuilder { impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() + let mut t = frame_system::GenesisConfig::::default() + .build_storage() .unwrap(); pallet_balances::GenesisConfig:: { @@ -167,12 +164,12 @@ impl ExtBuilder { } } -pub fn alice() -> secp256k1::SecretKey { - secp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +pub fn alice() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() } -pub fn bob() -> secp256k1::SecretKey { - secp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() +pub fn bob() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() } pub fn bob_account_id() -> AccountId { diff --git a/blockchain/modules/evm-accounts/src/tests.rs b/blockchain/modules/evm-accounts/src/tests.rs index 26a575521..0c4b9e78d 100644 --- a/blockchain/modules/evm-accounts/src/tests.rs +++ b/blockchain/modules/evm-accounts/src/tests.rs @@ -24,21 +24,21 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use mock::{alice, bob, Event, EvmAccountsModule, ExtBuilder, Origin, Runtime, System, ALICE, BOB}; +use mock::{alice, bob, EvmAccountsModule, ExtBuilder, Runtime, RuntimeEvent, RuntimeOrigin, System, ALICE, BOB}; use std::str::FromStr; #[test] fn claim_account_work() { ExtBuilder::default().build().execute_with(|| { assert_ok!(EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); - System::assert_last_event(Event::EvmAccountsModule(crate::Event::ClaimAccount( - ALICE, - EvmAccountsModule::eth_address(&alice()), - ))); + System::assert_last_event(RuntimeEvent::EvmAccountsModule(crate::Event::ClaimAccount { + account_id: ALICE, + evm_address: EvmAccountsModule::eth_address(&alice()), + })); assert!( Accounts::::contains_key(EvmAccountsModule::eth_address(&alice())) && EvmAddresses::::contains_key(ALICE) @@ -51,46 +51,38 @@ fn claim_account_should_not_work() { ExtBuilder::default().build().execute_with(|| { assert_noop!( EvmAccountsModule::claim_account( - Origin::signed(ALICE), - EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&bob(), &ALICE.encode(), &vec![1][..]) - ), - Error::::InvalidSignature - ); - assert_noop!( - EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&bob(), &BOB.encode(), &[][..]) + EvmAccountsModule::eth_sign(&bob(), &BOB) ), Error::::InvalidSignature ); assert_noop!( EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::InvalidSignature ); assert_ok!(EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_noop!( EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::AccountIdHasMapped ); assert_noop!( EvmAccountsModule::claim_account( - Origin::signed(BOB), + RuntimeOrigin::signed(BOB), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &BOB.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &BOB) ), Error::::EthAddressHasMapped ); @@ -112,9 +104,9 @@ fn evm_get_account_id() { ); assert_ok!(EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_eq!(EvmAddressMapping::::get_account_id(&evm_account), ALICE); @@ -131,6 +123,22 @@ fn evm_get_account_id() { }); } +#[test] +fn validate_evm_account_id() { + ExtBuilder::default().build().execute_with(|| { + assert!(EvmAddressMapping::::get_evm_address(&ALICE).is_none()); + + let no_zero_padding = AccountId32::new(*b"evm:aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert!(EvmAddressMapping::::get_evm_address(&no_zero_padding).is_none()); + + let valid_account_id = AccountId32::new(*b"evm:aaaaaaaaaaaaaaaaaaaa\0\0\0\0\0\0\0\0"); + assert_eq!( + EvmAddressMapping::::get_evm_address(&valid_account_id).unwrap(), + EvmAddress::from(b"aaaaaaaaaaaaaaaaaaaa") + ); + }); +} + #[test] fn account_to_evm() { ExtBuilder::default().build().execute_with(|| { @@ -140,9 +148,9 @@ fn account_to_evm() { let alice_evm_account = EvmAccountsModule::eth_address(&alice()); assert_ok!(EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), alice_evm_account, - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_eq!(EvmAddressMapping::::get_account_id(&alice_evm_account), ALICE); @@ -169,6 +177,10 @@ fn account_to_evm_with_create_default() { EvmAddressMapping::::get_or_create_evm_address(&ALICE), default_evm_account ); + System::assert_last_event(RuntimeEvent::EvmAccountsModule(crate::Event::ClaimAccount { + account_id: ALICE, + evm_address: default_evm_account, + })); assert_eq!( EvmAddressMapping::::get_evm_address(&ALICE), Some(default_evm_account) @@ -185,9 +197,9 @@ fn account_to_evm_with_create_default() { assert_noop!( EvmAccountsModule::claim_account( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), alice_evm_account, - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::AccountIdHasMapped ); diff --git a/blockchain/modules/evm-accounts/src/weights.rs b/blockchain/modules/evm-accounts/src/weights.rs index 119bf1a3b..14b6f5030 100644 --- a/blockchain/modules/evm-accounts/src/weights.rs +++ b/blockchain/modules/evm-accounts/src/weights.rs @@ -1,84 +1,84 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - - -//! Autogenerated weights for module_evm_accounts -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_evm_accounts -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./blockchain/modules/evm-accounts/src/weights.rs -// --template=.maintain/module-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_evm_accounts. -pub trait WeightInfo { - fn claim_account() -> Weight; - fn claim_default_account() -> Weight; -} - -/// Weights for module_evm_accounts using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn claim_account() -> Weight { - (340_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn claim_default_account() -> Weight { - (19_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn claim_account() -> Weight { - (340_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn claim_default_account() -> Weight { - (19_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + + +//! Autogenerated weights for module_evm_accounts +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_evm_accounts +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/evm-accounts/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_evm_accounts. +pub trait WeightInfo { + fn claim_account() -> Weight; + fn claim_default_account() -> Weight; +} + +/// Weights for module_evm_accounts using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn claim_account() -> Weight { + Weight::from_parts(340_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn claim_default_account() -> Weight { + Weight::from_parts(19_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn claim_account() -> Weight { + Weight::from_parts(340_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn claim_default_account() -> Weight { + Weight::from_parts(19_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/blockchain/modules/evm-manager/Cargo.toml b/blockchain/modules/evm-manager/Cargo.toml deleted file mode 100644 index ad3707d11..000000000 --- a/blockchain/modules/evm-manager/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "module-evm-manager" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -module-support = { path = "../support", default-features = false } - -[dev-dependencies] -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-currencies = { path = "../submodules/orml/currencies" } -orml-tokens = { path = "../submodules/orml/tokens" } -orml-traits = { path = "../submodules/orml/traits" } -orml-utilities = { path = "../submodules/orml/utilities" } -module-evm = { path = "../evm" } -module-evm-bridge = { path = "../evm-bridge" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-core/std", - "sp-runtime/std", - "sp-io/std", - "sp-std/std", - "frame-support/std", - "frame-system/std", - "primitives/std", - "module-support/std", -] diff --git a/blockchain/modules/evm-manager/src/lib.rs b/blockchain/modules/evm-manager/src/lib.rs deleted file mode 100644 index d1a35279e..000000000 --- a/blockchain/modules/evm-manager/src/lib.rs +++ /dev/null @@ -1,306 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! # Evm Manager Module -//! -//! ## Overview -//! -//! Evm manager module provides common support features for Evm, including: -//! - A two way mapping between `u32` and `Erc20 address` so user can use Erc20 address as LP token. - -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use frame_support::{ensure, pallet_prelude::*, require_transactional, traits::Currency}; -use module_support::{CurrencyIdMapping, EVMBridge, InvokeContext}; -use primitives::{ - currency::TokenInfo, - evm::{Erc20Info, EvmAddress}, - *, -}; -use sp_std::{ - convert::{TryFrom, TryInto}, - vec::Vec, -}; - -mod mock; -mod tests; - -pub use module::*; - -pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; - -#[frame_support::pallet] -pub mod module { - use super::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Currency: Currency; - type EVMBridge: EVMBridge>; - } - - /// Error for evm accounts module. - #[pallet::error] - pub enum Error { - /// CurrencyId existed - CurrencyIdExisted, - } - - /// Mapping between u32 and Erc20 address. - /// Erc20 address is 20 byte, take the first 4 non-zero bytes, if it is less - /// than 4, add 0 to the left. - /// - /// map u32 => Option - #[pallet::storage] - #[pallet::getter(fn currency_id_map)] - pub type CurrencyIdMap = StorageMap<_, Twox64Concat, u32, Erc20Info, OptionQuery>; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::call] - impl Pallet {} -} - -impl Pallet {} - -pub struct EvmCurrencyIdMapping(sp_std::marker::PhantomData); - -impl CurrencyIdMapping for EvmCurrencyIdMapping { - // Use first 4 non-zero bytes as u32 to the mapping between u32 and evm address. - // Take the first 4 non-zero bytes, if it is less than 4, add 0 to the left. - #[require_transactional] - fn set_erc20_mapping(address: EvmAddress) -> DispatchResult { - CurrencyIdMap::::mutate( - Into::::into(DexShare::Erc20(address)), - |maybe_erc20_info| -> DispatchResult { - if let Some(erc20_info) = maybe_erc20_info.as_mut() { - ensure!(erc20_info.address == address, Error::::CurrencyIdExisted); - } else { - let invoke_context = InvokeContext { - contract: address, - sender: Default::default(), - origin: Default::default(), - }; - - let info = Erc20Info { - address, - name: T::EVMBridge::name(invoke_context)?, - symbol: T::EVMBridge::symbol(invoke_context)?, - decimals: T::EVMBridge::decimals(invoke_context)?, - }; - - *maybe_erc20_info = Some(info); - } - Ok(()) - }, - ) - } - - // Returns the EvmAddress associated with a given u32. - fn get_evm_address(currency_id: u32) -> Option { - CurrencyIdMap::::get(currency_id).map(|v| v.address) - } - - // Returns the name associated with a given CurrencyId. - // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - // the EvmAddress must have been mapped. - fn name(currency_id: CurrencyId) -> Option> { - let name = match currency_id { - CurrencyId::Token(_) => currency_id.name().map(|v| v.as_bytes().to_vec()), - CurrencyId::DexShare(symbol_0, symbol_1) => { - let name_0 = match symbol_0 { - DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => CurrencyIdMap::::get(Into::::into(symbol_0)) - .filter(|v| v.address == address) - .map(|v| v.name), - }?; - let name_1 = match symbol_1 { - DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => CurrencyIdMap::::get(Into::::into(symbol_1)) - .filter(|v| v.address == address) - .map(|v| v.name), - }?; - - let mut vec = Vec::new(); - vec.extend_from_slice(&b"LP "[..]); - vec.extend_from_slice(&name_0); - vec.extend_from_slice(&b" - ".to_vec()); - vec.extend_from_slice(&name_1); - Some(vec) - } - CurrencyId::Erc20(address) => CurrencyIdMap::::get(Into::::into(DexShare::Erc20(address))) - .filter(|v| v.address == address) - .map(|v| v.name), - }?; - - // More than 32 bytes will be truncated. - if name.len() > 32 { - Some(name[..32].to_vec()) - } else { - Some(name) - } - } - - // Returns the symbol associated with a given CurrencyId. - // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - // the EvmAddress must have been mapped. - fn symbol(currency_id: CurrencyId) -> Option> { - let symbol = match currency_id { - CurrencyId::Token(_) => currency_id.symbol().map(|v| v.as_bytes().to_vec()), - CurrencyId::DexShare(symbol_0, symbol_1) => { - let token_symbol_0 = match symbol_0 { - DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => CurrencyIdMap::::get(Into::::into(symbol_0)) - .filter(|v| v.address == address) - .map(|v| v.symbol), - }?; - let token_symbol_1 = match symbol_1 { - DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => CurrencyIdMap::::get(Into::::into(symbol_1)) - .filter(|v| v.address == address) - .map(|v| v.symbol), - }?; - - let mut vec = Vec::new(); - vec.extend_from_slice(&b"LP_"[..]); - vec.extend_from_slice(&token_symbol_0); - vec.extend_from_slice(&b"_".to_vec()); - vec.extend_from_slice(&token_symbol_1); - Some(vec) - } - CurrencyId::Erc20(address) => CurrencyIdMap::::get(Into::::into(DexShare::Erc20(address))) - .filter(|v| v.address == address) - .map(|v| v.symbol), - }?; - - // More than 32 bytes will be truncated. - if symbol.len() > 32 { - Some(symbol[..32].to_vec()) - } else { - Some(symbol) - } - } - - // Returns the decimals associated with a given CurrencyId. - // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - // the EvmAddress must have been mapped. - fn decimals(currency_id: CurrencyId) -> Option { - match currency_id { - CurrencyId::Token(_) => currency_id.decimals(), - CurrencyId::DexShare(symbol_0, _) => { - // initial dex share amount is calculated based on currency_id_0, - // use the decimals of currency_id_0 as the decimals of lp token. - match symbol_0 { - DexShare::Token(symbol) => CurrencyId::Token(symbol).decimals(), - DexShare::Erc20(address) => CurrencyIdMap::::get(Into::::into(symbol_0)) - .filter(|v| v.address == address) - .map(|v| v.decimals), - } - } - CurrencyId::Erc20(address) => CurrencyIdMap::::get(Into::::into(DexShare::Erc20(address))) - .filter(|v| v.address == address) - .map(|v| v.decimals), - } - } - - // Encode the CurrencyId to EvmAddress. - // If is CurrencyId::DexShare and contain DexShare::Erc20, - // will use the u32 to get the DexShare::Erc20 from the mapping. - fn encode_evm_address(v: CurrencyId) -> Option { - match v { - CurrencyId::DexShare(left, right) => { - let symbol_0 = match left { - DexShare::Token(_) => Some(left.into()), - DexShare::Erc20(address) => { - let id: u32 = left.into(); - CurrencyIdMap::::get(id).filter(|v| v.address == address).map(|_| id) - } - }?; - let symbol_1 = match right { - DexShare::Token(_) => Some(right.into()), - DexShare::Erc20(address) => { - let id: u32 = right.into(); - CurrencyIdMap::::get(id).filter(|v| v.address == address).map(|_| id) - } - }?; - - let mut prefix = EvmAddress::default(); - prefix[0..H160_PREFIX_DEXSHARE.len()].copy_from_slice(&H160_PREFIX_DEXSHARE); - Some(prefix | EvmAddress::from_low_u64_be(u64::from(symbol_0) << 32 | u64::from(symbol_1))) - } - - // Token or Erc20 - _ => EvmAddress::try_from(v).ok(), - } - } - - // Decode the CurrencyId from EvmAddress. - // If is CurrencyId::DexShare and contain DexShare::Erc20, - // will use the u32 to get the DexShare::Erc20 from the mapping. - fn decode_evm_address(addr: EvmAddress) -> Option { - let address = addr.as_bytes(); - - // Token - if address.starts_with(&H160_PREFIX_TOKEN) { - return address[H160_POSITION_TOKEN].try_into().map(CurrencyId::Token).ok(); - } - - // DexShare - if address.starts_with(&H160_PREFIX_DEXSHARE) { - let left = { - if address[H160_POSITION_DEXSHARE_LEFT].starts_with(&[0u8; 3]) { - // Token - address[H160_POSITION_DEXSHARE_LEFT][3] - .try_into() - .map(DexShare::Token) - .ok() - } else { - // Erc20 - let id = u32::from_be_bytes(address[H160_POSITION_DEXSHARE_LEFT].try_into().ok()?); - CurrencyIdMap::::get(id).map(|v| DexShare::Erc20(v.address)) - } - }?; - let right = { - if address[H160_POSITION_DEXSHARE_RIGHT].starts_with(&[0u8; 3]) { - // Token - address[H160_POSITION_DEXSHARE_RIGHT][3] - .try_into() - .map(DexShare::Token) - .ok() - } else { - // Erc20 - let id = u32::from_be_bytes(address[H160_POSITION_DEXSHARE_RIGHT].try_into().ok()?); - CurrencyIdMap::::get(id).map(|v| DexShare::Erc20(v.address)) - } - }?; - - return Some(CurrencyId::DexShare(left, right)); - } - - // Erc20 - let id = Into::::into(DexShare::Erc20(addr)); - CurrencyIdMap::::get(id).map(|v| CurrencyId::Erc20(v.address)) - } -} diff --git a/blockchain/modules/evm-manager/src/mock.rs b/blockchain/modules/evm-manager/src/mock.rs deleted file mode 100644 index fadfea125..000000000 --- a/blockchain/modules/evm-manager/src/mock.rs +++ /dev/null @@ -1,274 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Mocks for the evm-manager module. - -#![cfg(test)] - -use super::*; -use frame_support::{assert_ok, construct_runtime, ord_parameter_types, parameter_types}; -use frame_system::EnsureSignedBy; -use module_support::{mocks::MockAddressMapping, AddressMapping}; -use orml_traits::parameter_type_with_key; -use primitives::{Amount, Balance, CurrencyId, ReserveIdentifier, TokenSymbol}; -use sp_core::{bytes::from_hex, crypto::AccountId32, H160, H256}; -use sp_runtime::{testing::Header, traits::IdentityLookup}; -use std::str::FromStr; - -pub type AccountId = AccountId32; -pub type BlockNumber = u64; - -mod evm_manager { - pub use super::super::*; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type Origin = Origin; - type Index = u64; - type BlockNumber = BlockNumber; - type Call = Call; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; -} -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type Event = Event; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); -} - -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() - }; -} - -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = Balance; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = (); - type DustRemovalWhitelist = (); -} - -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); -} - -impl orml_currencies::Config for Runtime { - type Event = Event; - type MultiCurrency = Tokens; - type NativeCurrency = AdaptedBasicCurrency; - type GetNativeCurrencyId = GetNativeCurrencyId; - type WeightInfo = (); -} -pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; - -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - pub const NewContractExtraBytes: u32 = 1; - pub NetworkContractSource: EvmAddress = alice_evm_addr(); -} - -ord_parameter_types! { - pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); - pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); - pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); - pub const StorageDepositPerByte: u128 = 10; - pub const DeveloperDeposit: u64 = 1000; - pub const DeploymentFee: u64 = 200; -} - -impl module_evm::Config for Runtime { - type AddressMapping = MockAddressMapping; - type Currency = Balances; - type TransferAll = (); - type NewContractExtraBytes = NewContractExtraBytes; - type StorageDepositPerByte = StorageDepositPerByte; - type Event = Event; - type Precompiles = (); - type ChainId = (); - type GasToWeight = (); - type ChargeTransactionPayment = (); - type NetworkContractOrigin = EnsureSignedBy; - type NetworkContractSource = NetworkContractSource; - - type DeveloperDeposit = DeveloperDeposit; - type DeploymentFee = DeploymentFee; - type TreasuryAccount = TreasuryAccount; - type FreeDeploymentOrigin = EnsureSignedBy; - - type Runner = module_evm::runner::stack::Runner; - type FindAuthor = (); - type WeightInfo = (); -} - -impl module_evm_bridge::Config for Runtime { - type EVM = EVM; -} - -impl Config for Runtime { - type Currency = Balances; - type EVMBridge = EVMBridge; -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -pub fn erc20_address() -> EvmAddress { - EvmAddress::from_str("0000000000000000000000000000000002000000").unwrap() -} - -pub fn erc20_address_not_exists() -> EvmAddress { - EvmAddress::from_str("0000000000000000000000000000000002000001").unwrap() -} - -pub fn alice() -> AccountId { - ::AddressMapping::get_account_id(&alice_evm_addr()) -} - -pub fn alice_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() -} - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - EvmManager: evm_manager::{Pallet, Storage}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Currencies: orml_currencies::{Pallet, Call, Event}, - EVM: module_evm::{Pallet, Config, Call, Storage, Event}, - EVMBridge: module_evm_bridge::{Pallet}, - } -); - -pub fn deploy_contracts() { - let code = from_hex(include!("../../evm-bridge/src/erc20_demo_contract")).unwrap(); - assert_ok!(EVM::create_network_contract( - Origin::signed(NetworkContractAccount::get()), - code, - 0, - 2_100_000, - 10000 - )); - - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { - address: H160::from_str("0x0000000000000000000000000000000002000000").unwrap(), - topics: vec![ - H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), - ], - data: H256::from_low_u64_be(10000).as_bytes().to_vec(), - }], - ))); - - assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); -} - -pub struct ExtBuilder { - balances: Vec<(AccountId, Balance)>, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { balances: vec![] } - } -} - -impl ExtBuilder { - pub fn balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: self.balances.into_iter().collect::>(), - } - .assimilate_storage(&mut t) - .unwrap(); - - module_evm::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} diff --git a/blockchain/modules/evm-manager/src/tests.rs b/blockchain/modules/evm-manager/src/tests.rs deleted file mode 100644 index 1b623b40f..000000000 --- a/blockchain/modules/evm-manager/src/tests.rs +++ /dev/null @@ -1,460 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Unit tests for the evm-manager module. - -#![cfg(test)] - -use super::*; -use frame_support::{assert_noop, assert_ok}; -use mock::{alice, deploy_contracts, erc20_address, erc20_address_not_exists, ExtBuilder, Runtime}; -use orml_utilities::with_transaction_result; -use primitives::TokenSymbol; -use sp_core::H160; -use std::str::FromStr; - -#[test] -fn set_erc20_mapping_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - - assert_noop!( - with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping( - EvmAddress::from_str("0000000000000000000000000000000200000000").unwrap(), - ) - }), - Error::::CurrencyIdExisted, - ); - - assert_noop!( - with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping( - EvmAddress::from_str("0000000000000000000000000000000200000001").unwrap(), - ) - }), - Error::::CurrencyIdExisted, - ); - - assert_noop!( - with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address_not_exists()) - }), - module_evm_bridge::Error::::InvalidReturnValue, - ); - }); -} - -#[test] -fn get_evm_address_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::get_evm_address(DexShare::Erc20(erc20_address()).into()), - Some(erc20_address()) - ); - - assert_eq!(EvmCurrencyIdMapping::::get_evm_address(u32::default()), None); - }); -} - -#[test] -fn name_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::Token(TokenSymbol::SEE)), - Some(b"Setheum".to_vec()) - ); - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::Erc20(erc20_address())), - Some(b"long string name, long string name, long string name, long string name, long string name"[..32].to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::Erc20(erc20_address_not_exists())), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::SETUSD))), - Some(b"LP Setheum - SetDollar".to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Token(TokenSymbol::SETUSD))), - Some(b"LP long string name, long string name, long string name, long string name, long string name - SetDollar"[..32].to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Erc20(erc20_address()))), - Some(b"LP long string name, long string name, long string name, long string name, long string name - long string name, long string name, long string name, long string name, long string name"[..32].to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Erc20(erc20_address_not_exists()))), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Erc20(erc20_address_not_exists()))), - None - ); - }); -} - -#[test] -fn symbol_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::Token(TokenSymbol::SEE)), - Some(b"SEE".to_vec()) - ); - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::Erc20(erc20_address())), - Some(b"TestToken".to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::Erc20(erc20_address_not_exists())), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD) - )), - Some(b"LP_SETM_SETUSD".to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Token(TokenSymbol::SETUSD) - )), - Some(b"LP_TestToken_SETUSD".to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address()) - )), - Some(b"LP_TestToken_TestToken".to_vec()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Erc20(erc20_address_not_exists()) - )), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::symbol(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address_not_exists()) - )), - None - ); - }); -} - -#[test] -fn decimals_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::Token(TokenSymbol::SEE)), - Some(18) - ); - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::Erc20(erc20_address())), - Some(17) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::Erc20(erc20_address_not_exists())), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD) - )), - Some(18) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Token(TokenSymbol::SETUSD) - )), - Some(17) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address()) - )), - Some(17) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decimals(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address_not_exists()) - )), - Some(17) - ); - }); -} - -#[test] -fn encode_evm_address_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Token(TokenSymbol::SEE)), - H160::from_str("0x0000000000000000000000000000000001000000").ok() - ); - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address())), - Some(erc20_address()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address_not_exists())), - Some(erc20_address_not_exists()) - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD) - )), - H160::from_str("0x0000000000000000000000010000000000000005").ok() - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Token(TokenSymbol::SETUSD) - )), - H160::from_str("0x0000000000000000000000010200000000000005").ok() - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SETUSD), - DexShare::Erc20(erc20_address()) - )), - H160::from_str("0x0000000000000000000000010000000502000000").ok() - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address()) - )), - H160::from_str("0x0000000000000000000000010200000002000000").ok() - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Erc20(erc20_address_not_exists()) - )), - None - ); - - assert_eq!( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address_not_exists()) - )), - None - ); - }); -} - -#[test] -fn decode_evm_address_works() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmCurrencyIdMapping::::set_erc20_mapping(erc20_address()) - })); - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Token(TokenSymbol::SEE)).unwrap() - ), - Some(CurrencyId::Token(TokenSymbol::SEE)) - ); - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address())).unwrap() - ), - Some(CurrencyId::Erc20(erc20_address())) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::Erc20(erc20_address_not_exists())) - .unwrap() - ), - None, - ); - - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD) - )) - .unwrap(), - ), - Some(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD) - )) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Token(TokenSymbol::SETUSD) - )) - .unwrap() - ), - Some(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Token(TokenSymbol::SETUSD) - )) - ); - - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - EvmCurrencyIdMapping::::encode_evm_address(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address()) - )) - .unwrap() - ), - Some(CurrencyId::DexShare( - DexShare::Erc20(erc20_address()), - DexShare::Erc20(erc20_address()) - )) - ); - - // decode invalid evm address - // CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), - // DexShare::Erc20(erc20_address_not_exists())) - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - H160::from_str("0x0000000000000000000000010000000002000001").unwrap() - ), - None - ); - - // decode invalid evm address - // CurrencyId::DexShare(DexShare::Erc20(erc20_address()), - // DexShare::Erc20(erc20_address_not_exists())) - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address( - H160::from_str("0x0000000000000000000000010200000002000001").unwrap() - ), - None - ); - - // decode invalid evm address - // Allow non-system contracts - let non_system_contracts = H160::from_str("0x1000000000000000000000000000000000000000").unwrap(); - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address(non_system_contracts), - None - ); - - let id = Into::::into(DexShare::Erc20(non_system_contracts)); - CurrencyIdMap::::mutate(id, |maybe_erc20_info| { - let info = Erc20Info { - address: non_system_contracts, - name: b"Test".to_vec(), - symbol: b"T".to_vec(), - decimals: 17, - }; - - *maybe_erc20_info = Some(info); - }); - assert_eq!( - EvmCurrencyIdMapping::::decode_evm_address(non_system_contracts), - Some(CurrencyId::Erc20(non_system_contracts)) - ); - }); -} diff --git a/blockchain/modules/evm-utility/Cargo.toml b/blockchain/modules/evm-utility/Cargo.toml new file mode 100644 index 000000000..8aa7bc7be --- /dev/null +++ b/blockchain/modules/evm-utility/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "module-evm-utility" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +sha3 = { workspace = true } + +sp-std = { workspace = true } + +evm = { version = "0.41.1", default-features = false, features = ["with-codec"] } +evm-gasometer = { version = "0.41.0", default-features = false } +evm-runtime = { version = "0.41.0", default-features = false } +ethereum = { version = "0.15.0", default-features = false, features = ["with-codec"] } + +[features] +default = ["std"] +std = [ + "sha3/std", + "sp-std/std", + "evm/std", + "evm/with-serde", + "evm-runtime/std", + "evm-gasometer/std", + "ethereum/with-serde", +] +tracing = [ + "evm/tracing", + "evm-gasometer/tracing", + "evm-runtime/tracing", +] diff --git a/blockchain/modules/evm-utility/macro/Cargo.toml b/blockchain/modules/evm-utility/macro/Cargo.toml new file mode 100644 index 000000000..21ceb6c02 --- /dev/null +++ b/blockchain/modules/evm-utility/macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "module-evm-utility-macro" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = { workspace = true } +syn = { workspace = true, features = ["full", "fold", "extra-traits", "visit"] } +proc-macro2 = { workspace = true } +module-evm-utility = { workspace = true, features = ["std"] } diff --git a/blockchain/primitives/proc-macro/src/lib.rs b/blockchain/modules/evm-utility/macro/src/lib.rs similarity index 83% rename from blockchain/primitives/proc-macro/src/lib.rs rename to blockchain/modules/evm-utility/macro/src/lib.rs index fe47c07eb..c5a4759c5 100644 --- a/blockchain/primitives/proc-macro/src/lib.rs +++ b/blockchain/modules/evm-utility/macro/src/lib.rs @@ -21,9 +21,7 @@ use proc_macro::TokenStream; use proc_macro2::Literal; use quote::quote; -use sha3::{Digest, Keccak256}; -use std::convert::TryInto; -use syn::{parse_macro_input, Expr, ExprLit, Ident, ItemEnum, Lit}; +use syn::{parse_macro_input, Expr, ExprLit, Ident, ItemEnum, Lit, LitByteStr, LitStr}; #[proc_macro_attribute] pub fn generate_function_selector(_: TokenStream, input: TokenStream) -> TokenStream { @@ -43,7 +41,7 @@ pub fn generate_function_selector(_: TokenStream, input: TokenStream) -> TokenSt for variant in variants { if let Some((_, Expr::Lit(ExprLit { lit, .. }))) = variant.discriminant { if let Lit::Str(token) = lit { - let selector = get_function_selector(&token.value()); + let selector = module_evm_utility::get_function_selector(&token.value()); // println!("method: {:?}, selector: {:?}", token.value(), selector); ident_expressions.push(variant.ident); variant_expressions.push(Expr::Lit(ExprLit { @@ -69,12 +67,13 @@ pub fn generate_function_selector(_: TokenStream, input: TokenStream) -> TokenSt .into() } -fn get_function_selector(s: &str) -> u32 { - // create a SHA3-256 object - let mut hasher = Keccak256::new(); - // write input message - hasher.update(s); - // read hash digest - let result = hasher.finalize(); - u32::from_be_bytes(result[..4].try_into().unwrap()) +#[proc_macro] +pub fn keccak256(input: TokenStream) -> TokenStream { + let lit_str = parse_macro_input!(input as LitStr); + + let result = module_evm_utility::sha3_256(&lit_str.value()); + + let eval = Lit::ByteStr(LitByteStr::new(result.as_ref(), proc_macro2::Span::call_site())); + + quote!(#eval).into() } diff --git a/blockchain/modules/evm-utility/macro/tests/test.rs b/blockchain/modules/evm-utility/macro/tests/test.rs new file mode 100644 index 000000000..f79ca9011 --- /dev/null +++ b/blockchain/modules/evm-utility/macro/tests/test.rs @@ -0,0 +1,56 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#[cfg(test)] +mod tests { + #[test] + fn generate_function_selector_works() { + #[module_evm_utility_macro::generate_function_selector] + #[derive(Debug, Eq, PartialEq)] + #[repr(u32)] + pub enum Action { + Name = "name()", + Symbol = "symbol()", + Decimals = "decimals()", + TotalSupply = "totalSupply()", + BalanceOf = "balanceOf(address)", + Transfer = "transfer(address,uint256)", + } + + assert_eq!(Action::Name as u32, 0x06fdde03_u32); + assert_eq!(Action::Symbol as u32, 0x95d89b41_u32); + assert_eq!(Action::Decimals as u32, 0x313ce567_u32); + assert_eq!(Action::TotalSupply as u32, 0x18160ddd_u32); + assert_eq!(Action::BalanceOf as u32, 0x70a08231_u32); + assert_eq!(Action::Transfer as u32, 0xa9059cbb_u32); + } + + #[test] + fn keccak256_works() { + assert_eq!( + module_evm_utility_macro::keccak256!(""), + &module_evm_utility::sha3_256("") + ); + assert_eq!( + module_evm_utility_macro::keccak256!("keccak256"), + &module_evm_utility::sha3_256("keccak256") + ); + } +} diff --git a/blockchain/modules/evm-utility/src/lib.rs b/blockchain/modules/evm-utility/src/lib.rs new file mode 100644 index 000000000..b720e662b --- /dev/null +++ b/blockchain/modules/evm-utility/src/lib.rs @@ -0,0 +1,50 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Evm utiltity Module +//! +//! A pallet provides some utility methods. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sha3::{Digest, Keccak256}; + +pub use ethereum; +pub use evm::{self, backend::Basic as Account}; +pub use evm_gasometer; +pub use evm_runtime; + +pub fn sha3_256(s: &str) -> [u8; 32] { + let mut result = [0u8; 32]; + + // create a SHA3-256 object + let mut hasher = Keccak256::new(); + // write input message + hasher.update(s); + // read hash digest + result.copy_from_slice(&hasher.finalize()[..32]); + + result +} + +pub fn get_function_selector(s: &str) -> u32 { + let result = sha3_256(s); + u32::from_be_bytes(result[..4].try_into().unwrap()) +} diff --git a/blockchain/modules/example/Cargo.toml b/blockchain/modules/example/Cargo.toml deleted file mode 100644 index 454c5eaca..000000000 --- a/blockchain/modules/example/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "nft-mart" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", -] diff --git a/blockchain/modules/example/src/lib.rs b/blockchain/modules/example/src/lib.rs deleted file mode 100644 index 3ccc927de..000000000 --- a/blockchain/modules/example/src/lib.rs +++ /dev/null @@ -1,146 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! # Example Module -//! -//! A simple example of a FRAME pallet demonstrating -//! concepts, APIs and structures common to most FRAME runtimes. - -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use frame_support::pallet_prelude::*; -use frame_system::pallet_prelude::*; - -mod mock; -mod tests; - -pub use module::*; - -#[frame_support::pallet] -pub mod module { - use super::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Balance: Parameter + codec::HasCompact + From + Into + Default + MaybeSerializeDeserialize; - #[pallet::constant] - type SomeConst: Get; - type Event: From> + IsType<::Event>; - } - - #[pallet::error] - pub enum Error { - /// Some wrong behavior - Wrong, - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - #[pallet::metadata(T::Balance = "Balance")] - pub enum Event { - /// Dummy event, just here so there's a generic type that's used. - Dummy(T::Balance), - } - - #[pallet::type_value] - pub fn OnFooEmpty() -> T::Balance { - 3.into() - } - - /// Some documentation - #[pallet::storage] - #[pallet::getter(fn dummy)] - type Dummy = StorageValue<_, T::Balance, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn bar)] - pub(crate) type Bar = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance, ValueQuery>; - - #[pallet::storage] - type Foo = StorageValue<_, T::Balance, ValueQuery, OnFooEmpty>; - - #[pallet::storage] - type Double = StorageDoubleMap<_, Blake2_128Concat, u32, Twox64Concat, u64, T::Balance, ValueQuery>; - - #[pallet::genesis_config] - pub struct GenesisConfig { - pub dummy: Option, - pub bar: Vec<(T::AccountId, T::Balance)>, - pub foo: T::Balance, - } - - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - dummy: Default::default(), - bar: Default::default(), - foo: OnFooEmpty::::get(), - } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - if let Some(dummy) = self.dummy.as_ref() { - Dummy::::put(dummy); - } - for (k, v) in &self.bar { - Bar::::insert(k, v); - } - Foo::::put(&self.foo); - } - } - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_initialize(_n: T::BlockNumber) -> Weight { - Dummy::::put(T::Balance::from(10)); - 10 - } - - fn on_finalize(_n: T::BlockNumber) { - Dummy::::put(T::Balance::from(11)); - } - } - - #[pallet::call] - impl Pallet { - #[pallet::weight(>::into(new_value.clone()))] - pub fn set_dummy(origin: OriginFor, #[pallet::compact] new_value: T::Balance) -> DispatchResult { - ensure_root(origin)?; - - Dummy::::put(&new_value); - Self::deposit_event(Event::Dummy(new_value)); - - Ok(()) - } - } -} - -impl Pallet { - pub fn do_set_bar(who: &T::AccountId, amount: T::Balance) { - Bar::::insert(who, amount); - } -} diff --git a/blockchain/modules/example/src/mock.rs b/blockchain/modules/example/src/mock.rs deleted file mode 100644 index a7bedd60d..000000000 --- a/blockchain/modules/example/src/mock.rs +++ /dev/null @@ -1,94 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Mocks for example module. - -#![cfg(test)] - -use crate as example; -use frame_support::pallet_prelude::GenesisBuild; -use frame_support::{construct_runtime, parameter_types}; - -parameter_types!( - pub const SomeConst: u64 = 10; - pub const BlockHashCount: u32 = 250; -); - -impl frame_system::Config for Runtime { - type BaseCallFilter = (); - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = Call; - type Hash = sp_runtime::testing::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = sp_runtime::traits::IdentityLookup; - type Header = sp_runtime::testing::Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -impl example::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; - type Balance = u64; -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Event}, - // NOTE: name Example here is needed in order to have same module prefix - Example: example::{Pallet, Call, Event, Config, Storage}, - } -); - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - example::GenesisConfig:: { - bar: vec![(1, 100), (2, 200)], - ..Default::default() - } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} diff --git a/blockchain/modules/nft/Cargo.toml b/blockchain/modules/nft/Cargo.toml index b241fd320..40e230bcd 100644 --- a/blockchain/modules/nft/Cargo.toml +++ b/blockchain/modules/nft/Cargo.toml @@ -1,54 +1,61 @@ -[package] -name = "module-nft" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["max-encoded-len"] } -enumflags2 = { version = "0.6.3" } - -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false, optional = true} -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-nft = { path = "../submodules/orml/nft", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } - -orml-tokens = { path = "../submodules/orml/tokens" } -module-currencies = { path = "../currencies" } -support = { package = "module-support", path = "../support" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "enumflags2/serde", - "sp-std/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "pallet-proxy/std", - "primitives/std", - "orml-traits/std", - "orml-nft/std", - "enumflags2/serde", -] -runtime-benchmarks = [ - "frame-benchmarking", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] +[package] +name = "module-nft" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +serde = { workspace = true, features = ["alloc", "derive"] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true } + +frame-benchmarking = { workspace = true, optional = true} +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-proxy = { workspace = true } +primitives = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +orml-nft = { workspace = true } +orml-traits = { workspace = true } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-utility = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +module-currencies = { workspace = true, features = ["std"] } +module-support = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "serde/std", + + "parity-scale-codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "orml-nft/std", + "orml-traits/std", + "pallet-proxy/std", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "orml-nft/try-runtime", + "pallet-proxy/try-runtime", +] diff --git a/blockchain/modules/nft/src/benchmarking.rs b/blockchain/modules/nft/src/benchmarking.rs index 355c641cc..f10f5b2c3 100644 --- a/blockchain/modules/nft/src/benchmarking.rs +++ b/blockchain/modules/nft/src/benchmarking.rs @@ -1,354 +1,330 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Benchmarks for the nft module. - -#![cfg(feature = "runtime-benchmarks")] - -use sp_std::vec; - -use frame_benchmarking::{account, benchmarks}; -use frame_support::{dispatch::DispatchErrorWithPostInfo, traits::Get, weights::DispatchClass}; -use frame_system::RawOrigin; -use sp_runtime::traits::{AccountIdConversion, StaticLookup, UniqueSaturatedInto}; - -pub use crate::*; -use primitives::Balance; - -pub struct Module(crate::Pallet); - -const SEED: u32 = 0; - -fn dollar(d: u32) -> Balance { - let d: Balance = d.into(); - d.saturating_mul(1_000_000_000_000_000_000) -} - -fn test_attr() -> Attributes { - let mut attr: Attributes = BTreeMap::new(); - for i in 0..30 { - attr.insert(vec![i], vec![0; 64]); - } - attr -} - -fn create_token_class(caller: T::AccountId) -> Result { - let base_currency_amount = dollar(10000); - ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); - - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class( - RawOrigin::Signed(caller).into(), - vec![1], - Properties( - ClassProperty::Transferable - | ClassProperty::Burnable - | ClassProperty::Mintable - | ClassProperty::ClassPropertiesMutable, - ), - test_attr(), - )?; - - ::Currency::make_free_balance_be( - &module_account, - base_currency_amount.unique_saturated_into(), - ); - - Ok(module_account) -} - -benchmarks! { - // create NFT class - create_class { - let caller: T::AccountId = account("caller", 0, SEED); - let base_currency_amount = dollar(10000); - - ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); - }: _(RawOrigin::Signed(caller), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), test_attr()) - - // mint NFT token - mint { - let i in 1 .. 1000; - - let caller: T::AccountId = account("caller", 0, SEED); - let to: T::AccountId = account("to", 0, SEED); - let to_lookup = T::Lookup::unlookup(to); - - let module_account = create_token_class::(caller)?; - }: _(RawOrigin::Signed(module_account), to_lookup, 0u32.into(), vec![1], test_attr(), i) - - // transfer NFT token to another account - transfer { - let caller: T::AccountId = account("caller", 0, SEED); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - let to: T::AccountId = account("to", 0, SEED); - let to_lookup = T::Lookup::unlookup(to.clone()); - - let module_account = create_token_class::(caller)?; - - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; - }: _(RawOrigin::Signed(to), caller_lookup, (0u32.into(), 0u32.into())) - - // burn NFT token - burn { - let caller: T::AccountId = account("caller", 0, SEED); - let to: T::AccountId = account("to", 0, SEED); - let to_lookup = T::Lookup::unlookup(to.clone()); - - let module_account = create_token_class::(caller)?; - - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; - }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into())) - - // burn NFT token with remark - burn_with_remark { - let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32; - let remark_message = vec![1; b as usize]; - let caller: T::AccountId = account("caller", 0, SEED); - let to: T::AccountId = account("to", 0, SEED); - let to_lookup = T::Lookup::unlookup(to.clone()); - - let module_account = create_token_class::(caller)?; - - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; - }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into()), remark_message) - - // destroy NFT class - destroy_class { - let caller: T::AccountId = account("caller", 0, SEED); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - - let base_currency_amount = dollar(1000); - - let module_account = create_token_class::(caller)?; - - }: _(RawOrigin::Signed(module_account), 0u32.into(), caller_lookup) - - update_class_properties { - let caller: T::AccountId = account("caller", 0, SEED); - let to: T::AccountId = account("to", 0, SEED); - let to_lookup = T::Lookup::unlookup(to); - - let module_account = create_token_class::(caller)?; - }: _(RawOrigin::Signed(module_account), 0u32.into(), Properties(ClassProperty::Transferable.into())) -} - -#[cfg(test)] -mod mock { - use super::*; - use crate as nft; - - use codec::{Decode, Encode}; - use frame_support::{ - parameter_types, - traits::{Contains, InstanceFilter}, - weights::Weight, - PalletId, RuntimeDebug, - }; - use sp_core::{crypto::AccountId32, H256}; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, - }; - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - } - - pub type AccountId = AccountId32; - - impl frame_system::Config for Runtime { - type BaseCallFilter = BaseFilter; - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Call = Call; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; - } - impl pallet_balances::Config for Runtime { - type Balance = Balance; - type Event = (); - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); - } - impl pallet_utility::Config for Runtime { - type Event = (); - type Call = Call; - type WeightInfo = (); - } - parameter_types! { - pub const ProxyDepositBase: u64 = 1; - pub const ProxyDepositFactor: u64 = 1; - pub const MaxProxies: u16 = 4; - pub const MaxPending: u32 = 2; - pub const AnnouncementDepositBase: u64 = 1; - pub const AnnouncementDepositFactor: u64 = 1; - } - #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, MaxEncodedLen)] - pub enum ProxyType { - Any, - JustTransfer, - JustUtility, - } - impl Default for ProxyType { - fn default() -> Self { - Self::Any - } - } - impl InstanceFilter for ProxyType { - fn filter(&self, c: &Call) -> bool { - match self { - ProxyType::Any => true, - ProxyType::JustTransfer => matches!(c, Call::Balances(pallet_balances::Call::transfer(..))), - ProxyType::JustUtility => matches!(c, Call::Utility(..)), - } - } - fn is_superset(&self, o: &Self) -> bool { - self == &ProxyType::Any || self == o - } - } - pub struct BaseFilter; - impl Contains for BaseFilter { - fn contains(c: &Call) -> bool { - match *c { - // Remark is used as a no-op call in the benchmarking - Call::System(SystemCall::remark(_)) => true, - Call::System(_) => false, - _ => true, - } - } - } - impl pallet_proxy::Config for Runtime { - type Event = (); - type Call = Call; - type Currency = Balances; - type ProxyType = ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; - type WeightInfo = (); - type CallHasher = BlakeTwo256; - type MaxPending = MaxPending; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; - } - - parameter_types! { - pub const CreateClassDeposit: Balance = 200; - pub const CreateTokenDeposit: Balance = 100; - pub const DataDepositPerByte: Balance = 10; - pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); - pub MaxAttributesBytes: u32 = 2048; - } - - impl crate::Config for Runtime { - type Event = (); - type Currency = Balances; - type CreateClassDeposit = CreateClassDeposit; - type CreateTokenDeposit = CreateTokenDeposit; - type DataDepositPerByte = DataDepositPerByte; - type PalletId = NftPalletId; - type MaxAttributesBytes = MaxAttributesBytes; - type WeightInfo = (); - } - - parameter_types! { - pub const MaxClassMetadata: u32 = 1024; - pub const MaxTokenMetadata: u32 = 1024; - } - - impl orml_nft::Config for Runtime { - type ClassId = u32; - type TokenId = u64; - type ClassData = ClassData; - type TokenData = TokenData; - type MaxClassMetadata = MaxClassMetadata; - type MaxTokenMetadata = MaxTokenMetadata; - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Utility: pallet_utility::{Pallet, Call, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, - OrmlNFT: orml_nft::{Pallet, Storage, Config}, - NFT: nft::{Pallet, Call, Event}, - } - ); - - use frame_system::Call as SystemCall; - - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -#[cfg(test)] -mod tests { - use super::mock::*; - use super::*; - use frame_benchmarking::impl_benchmark_test_suite; - - impl_benchmark_test_suite!(Pallet, super::new_test_ext(), super::Runtime,); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Benchmarks for the nft module. + +#![cfg(feature = "runtime-benchmarks")] + +use sp_std::vec; + +use frame_benchmarking::{account, benchmarks}; +use frame_support::{dispatch::DispatchClass, dispatch::DispatchErrorWithPostInfo, traits::Get}; +use frame_system::RawOrigin; +use sp_runtime::traits::{AccountIdConversion, StaticLookup, UniqueSaturatedInto}; +use sp_std::collections::btree_map::BTreeMap; + +pub use crate::*; +use primitives::Balance; + +pub struct Module(crate::Pallet); + +const SEED: u32 = 0; + +fn dollar(d: u32) -> Balance { + let d: Balance = d.into(); + d.saturating_mul(1_000_000_000_000_000_000) +} + +fn test_attr() -> Attributes { + let mut attr: Attributes = BTreeMap::new(); + for i in 0..30 { + attr.insert(vec![i], vec![0; 64]); + } + attr +} + +fn create_token_class(caller: T::AccountId) -> Result { + let base_currency_amount = dollar(1000); + ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + + let module_account: T::AccountId = + T::PalletId::get().into_sub_account_truncating(orml_nft::Pallet::::next_class_id()); + crate::Pallet::::create_class( + RawOrigin::Signed(caller).into(), + vec![1], + Properties( + ClassProperty::Transferable + | ClassProperty::Burnable + | ClassProperty::Mintable + | ClassProperty::ClassPropertiesMutable, + ), + test_attr(), + )?; + + ::Currency::make_free_balance_be( + &module_account, + base_currency_amount.unique_saturated_into(), + ); + + Ok(module_account) +} + +benchmarks! { + // create NFT class + create_class { + let caller: T::AccountId = account("caller", 0, SEED); + let base_currency_amount = dollar(1000); + + ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + }: _(RawOrigin::Signed(caller), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), test_attr()) + + // mint NFT token + mint { + let i in 1 .. 1000; + + let caller: T::AccountId = account("caller", 0, SEED); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to); + + let module_account = create_token_class::(caller)?; + }: _(RawOrigin::Signed(module_account), to_lookup, 0u32.into(), vec![1], test_attr(), i) + + // transfer NFT token to another account + transfer { + let caller: T::AccountId = account("caller", 0, SEED); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to.clone()); + + let module_account = create_token_class::(caller)?; + + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; + }: _(RawOrigin::Signed(to), caller_lookup, (0u32.into(), 0u32.into())) + + // burn NFT token + burn { + let caller: T::AccountId = account("caller", 0, SEED); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to.clone()); + + let module_account = create_token_class::(caller)?; + + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; + }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into())) + + // burn NFT token with remark + burn_with_remark { + let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32; + let remark_message = vec![1; b as usize]; + let caller: T::AccountId = account("caller", 0, SEED); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to.clone()); + + let module_account = create_token_class::(caller)?; + + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; + }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into()), remark_message) + + // destroy NFT class + destroy_class { + let caller: T::AccountId = account("caller", 0, SEED); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + + let base_currency_amount = dollar(1000); + + let module_account = create_token_class::(caller)?; + + }: _(RawOrigin::Signed(module_account), 0u32.into(), caller_lookup) + + update_class_properties { + let caller: T::AccountId = account("caller", 0, SEED); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to); + + let module_account = create_token_class::(caller)?; + }: _(RawOrigin::Signed(module_account), 0u32.into(), Properties(ClassProperty::Transferable.into())) +} + +#[cfg(test)] +mod mock { + use super::*; + use crate as nft; + + use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Contains, InstanceFilter}, + PalletId, + }; + use parity_scale_codec::{Decode, Encode}; + use sp_core::{crypto::AccountId32, H256}; + use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, RuntimeDebug, + }; + + pub type AccountId = AccountId32; + + impl frame_system::Config for Runtime { + type BaseCallFilter = BaseFilter; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = (); + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + } + impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = (); + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); + } + impl pallet_utility::Config for Runtime { + type RuntimeEvent = (); + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); + } + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] + pub enum ProxyType { + Any, + JustTransfer, + JustUtility, + } + impl Default for ProxyType { + fn default() -> Self { + Self::Any + } + } + impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::JustTransfer => matches!( + c, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) + ), + ProxyType::JustUtility => matches!(c, RuntimeCall::Utility(..)), + } + } + fn is_superset(&self, o: &Self) -> bool { + self == &ProxyType::Any || self == o + } + } + pub struct BaseFilter; + impl Contains for BaseFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + // Remark is used as a no-op call in the benchmarking + RuntimeCall::System(SystemCall::remark { .. }) => true, + RuntimeCall::System(_) => false, + _ => true, + } + } + } + impl pallet_proxy::Config for Runtime { + type RuntimeEvent = (); + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ConstU128<1>; + type ProxyDepositFactor = ConstU128<1>; + type MaxProxies = ConstU32<4>; + type WeightInfo = (); + type CallHasher = BlakeTwo256; + type MaxPending = ConstU32<2>; + type AnnouncementDepositBase = ConstU128<1>; + type AnnouncementDepositFactor = ConstU128<1>; + } + + parameter_types! { + pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); + } + + impl crate::Config for Runtime { + type RuntimeEvent = (); + type Currency = Balances; + type CreateClassDeposit = ConstU128<200>; + type CreateTokenDeposit = ConstU128<100>; + type DataDepositPerByte = ConstU128<10>; + type PalletId = NftPalletId; + type MaxAttributesBytes = ConstU32<2048>; + type WeightInfo = (); + } + + impl orml_nft::Config for Runtime { + type ClassId = u32; + type TokenId = u64; + type ClassData = ClassData; + type TokenData = TokenData; + type MaxClassMetadata = ConstU32<1024>; + type MaxTokenMetadata = ConstU32<1024>; + } + + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Utility: pallet_utility, + Balances: pallet_balances, + Proxy: pallet_proxy, + OrmlNFT: orml_nft, + NFT: nft, + } + ); + + use frame_system::Call as SystemCall; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +#[cfg(test)] +mod tests { + use super::mock::*; + use super::*; + use frame_benchmarking::impl_benchmark_test_suite; + + impl_benchmark_test_suite!(Pallet, super::new_test_ext(), super::Runtime,); +} diff --git a/blockchain/modules/nft/src/lib.rs b/blockchain/modules/nft/src/lib.rs index 7710ca489..4a1f7e6c3 100644 --- a/blockchain/modules/nft/src/lib.rs +++ b/blockchain/modules/nft/src/lib.rs @@ -23,27 +23,31 @@ #![allow(clippy::unused_unit)] #![allow(clippy::upper_case_acronyms)] -use enumflags2::BitFlags; use frame_support::{ pallet_prelude::*, require_transactional, traits::{ + tokens::nonfungibles::{Inspect, Mutate, Transfer}, Currency, ExistenceRequirement::{AllowDeath, KeepAlive}, NamedReservableCurrency, }, - transactional, PalletId, + PalletId, }; use frame_system::pallet_prelude::*; -use orml_traits::NFT; -use primitives::{NFTBalance, ReserveIdentifier}; -#[cfg(feature = "std")] +use orml_traits::InspectExtended; +use primitives::{ + nft::{Attributes, ClassProperty, NFTBalance, Properties, CID}, + ReserveIdentifier, +}; +use scale_info::TypeInfo; + use serde::{Deserialize, Serialize}; use sp_runtime::{ traits::{AccountIdConversion, Hash, Saturating, StaticLookup, Zero}, DispatchResult, RuntimeDebug, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::prelude::*; pub mod benchmarking; mod mock; @@ -53,43 +57,7 @@ pub mod weights; pub use module::*; pub use weights::WeightInfo; -pub type CID = Vec; -pub type Attributes = BTreeMap, Vec>; - -#[repr(u8)] -#[derive(Encode, Decode, Clone, Copy, BitFlags, RuntimeDebug, PartialEq, Eq)] -pub enum ClassProperty { - /// Is token transferable - Transferable = 0b00000001, - /// Is token burnable - Burnable = 0b00000010, - /// Is minting new tokens allowed - Mintable = 0b00000100, - /// Is class properties mutable - ClassPropertiesMutable = 0b00001000, -} - -#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Properties(pub BitFlags); - -impl Eq for Properties {} -impl Encode for Properties { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl Decode for Properties { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u8::decode(input)?; - Ok(Self( - >::from_bits(field as u8).map_err(|_| "invalid value")?, - )) - } -} - -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, Serialize, Deserialize)] pub struct ClassData { /// Deposit reserved to create token class pub deposit: Balance, @@ -99,8 +67,7 @@ pub struct ClassData { pub attributes: Attributes, } -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, Serialize, Deserialize)] pub struct TokenData { /// Deposit reserved to create token pub deposit: Balance, @@ -125,7 +92,7 @@ pub mod module { + orml_nft::Config>, TokenData = TokenData>> + pallet_proxy::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Currency type for reserve balance. type Currency: NamedReservableCurrency< @@ -181,40 +148,64 @@ pub mod module { Immutable, /// Attributes too large AttributesTooLarge, + /// The given token ID is not correct + IncorrectTokenId, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId", ClassIdOf = "ClassId", TokenIdOf = "TokenId", T::Hash = "Hash")] pub enum Event { - /// Created NFT class. \[owner, class_id\] - CreatedClass(T::AccountId, ClassIdOf), - /// Minted NFT token. \[from, to, class_id, quantity\] - MintedToken(T::AccountId, T::AccountId, ClassIdOf, u32), - /// Transferred NFT token. \[from, to, class_id, token_id\] - TransferredToken(T::AccountId, T::AccountId, ClassIdOf, TokenIdOf), - /// Burned NFT token. \[owner, class_id, token_id\] - BurnedToken(T::AccountId, ClassIdOf, TokenIdOf), - /// Burned NFT token with remark. \[owner, class_id, token_id, remark_hash\] - BurnedTokenWithRemark(T::AccountId, ClassIdOf, TokenIdOf, T::Hash), - /// Destroyed NFT class. \[owner, class_id\] - DestroyedClass(T::AccountId, ClassIdOf), + /// Created NFT class. + CreatedClass { + owner: T::AccountId, + class_id: ClassIdOf, + }, + /// Minted NFT token. + MintedToken { + from: T::AccountId, + to: T::AccountId, + class_id: ClassIdOf, + quantity: u32, + }, + /// Transferred NFT token. + TransferredToken { + from: T::AccountId, + to: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + }, + /// Burned NFT token. + BurnedToken { + owner: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + }, + /// Burned NFT token with remark. + BurnedTokenWithRemark { + owner: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + remark_hash: T::Hash, + }, + /// Destroyed NFT class. + DestroyedClass { + owner: T::AccountId, + class_id: ClassIdOf, + }, } #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(_); - #[pallet::hooks] - impl Hooks for Pallet {} - #[pallet::call] impl Pallet { /// Create NFT class, tokens belong to the class. /// /// - `metadata`: external metadata /// - `properties`: class property, include `Transferable` `Burnable` + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::create_class())] - #[transactional] pub fn create_class( origin: OriginFor, metadata: CID, @@ -223,7 +214,7 @@ pub mod module { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let next_id = orml_nft::Pallet::::next_class_id(); - let owner: T::AccountId = T::PalletId::get().into_sub_account(next_id); + let owner: T::AccountId = T::PalletId::get().into_sub_account_truncating(next_id); let class_deposit = T::CreateClassDeposit::get(); let data_deposit = Self::data_deposit(&metadata, &attributes)?; @@ -231,8 +222,17 @@ pub mod module { let deposit = class_deposit.saturating_add(data_deposit); let total_deposit = proxy_deposit.saturating_add(deposit); - // ensure enough token for proxy deposit + class deposit + data deposit - ::Currency::transfer(&who, &owner, total_deposit, KeepAlive)?; + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/balances/src/lib.rs#L965 + // Now the pallet-balances judges whether provider is based on the `free balance` instead of + // `total balance`. When there's no other providers, error will throw in following reserve + // operation, which want to make `free balance` zero and `reserved balance` not zero. + // If receiver account doesn't have enough ED, transfer an additional ED to make sure of the subsequent + // reserve operation. + let total_transfer_amount = + total_deposit.saturating_add(::Currency::minimum_balance()); + + // ensure enough token for proxy deposit + class deposit + data deposit + ed + ::Currency::transfer(&who, &owner, total_transfer_amount, KeepAlive)?; ::Currency::reserve_named(&RESERVE_ID, &owner, deposit)?; @@ -246,7 +246,10 @@ pub mod module { }; orml_nft::Pallet::::create_class(&owner, metadata, data)?; - Self::deposit_event(Event::CreatedClass(owner, next_id)); + Self::deposit_event(Event::CreatedClass { + owner, + class_id: next_id, + }); Ok(().into()) } @@ -256,8 +259,8 @@ pub mod module { /// - `class_id`: token belong to the class id /// - `metadata`: external metadata /// - `quantity`: token quantity + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::mint(*quantity))] - #[transactional] pub fn mint( origin: OriginFor, to: ::Source, @@ -268,15 +271,16 @@ pub mod module { ) -> DispatchResult { let who = ensure_signed(origin)?; let to = T::Lookup::lookup(to)?; - Self::do_mint(who, to, class_id, metadata, attributes, quantity) + Self::do_mint(&who, &to, class_id, metadata, attributes, quantity)?; + Ok(()) } /// Transfer NFT token to another account /// /// - `to`: the token owner's account /// - `token`: (class_id, token_id) + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::transfer())] - #[transactional] pub fn transfer( origin: OriginFor, to: ::Source, @@ -290,8 +294,8 @@ pub mod module { /// Burn NFT token /// /// - `token`: (class_id, token_id) + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::burn())] - #[transactional] pub fn burn(origin: OriginFor, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_burn(who, token, None) @@ -301,8 +305,8 @@ pub mod module { /// /// - `token`: (class_id, token_id) /// - `remark`: Vec + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::burn_with_remark(remark.len() as u32))] - #[transactional] pub fn burn_with_remark( origin: OriginFor, token: (ClassIdOf, TokenIdOf), @@ -317,8 +321,8 @@ pub mod module { /// /// - `class_id`: The class ID to destroy /// - `dest`: The proxy account that will receive free balance + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::destroy_class())] - #[transactional] pub fn destroy_class( origin: OriginFor, class_id: ClassIdOf, @@ -349,7 +353,7 @@ pub mod module { AllowDeath, )?; - Self::deposit_event(Event::DestroyedClass(who, class_id)); + Self::deposit_event(Event::DestroyedClass { owner: who, class_id }); Ok(().into()) } @@ -358,8 +362,8 @@ pub mod module { /// /// - `class_id`: The class ID to update /// - `properties`: The new properties + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::update_class_properties())] - #[transactional] pub fn update_class_properties( origin: OriginFor, class_id: ClassIdOf, @@ -370,7 +374,7 @@ pub mod module { let class_info = class_info.as_mut().ok_or(Error::::ClassIdNotFound)?; ensure!(who == class_info.owner, Error::::NoPermission); - let mut data = &mut class_info.data; + let data = &mut class_info.data; ensure!( data.properties.0.contains(ClassProperty::ClassPropertiesMutable), Error::::Immutable @@ -386,7 +390,7 @@ pub mod module { impl Pallet { #[require_transactional] - fn do_transfer(from: &T::AccountId, to: &T::AccountId, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { + pub fn do_transfer(from: &T::AccountId, to: &T::AccountId, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { let class_info = orml_nft::Pallet::::classes(token.0).ok_or(Error::::ClassIdNotFound)?; let data = class_info.data; ensure!( @@ -398,26 +402,46 @@ impl Pallet { orml_nft::Pallet::::transfer(from, to, token)?; - ::Currency::unreserve_named(&RESERVE_ID, from, token_info.data.deposit); - ::Currency::transfer(from, to, token_info.data.deposit, AllowDeath)?; - ::Currency::reserve_named(&RESERVE_ID, to, token_info.data.deposit)?; + let reserve_balance = token_info.data.deposit; + + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/balances/src/lib.rs#L965 + // Now the pallet-balances judges whether provider is based on the `free balance` instead of + // `total balance`. When there's no other providers, error will throw in following reserve + // operation, which want to make `free balance` zero and `reserved balance` not zero. + // If receiver account doesn't have enough ED, transfer an additional ED to make sure of the subsequent + // reserve operation. + let transfer_amount = + if ::Currency::free_balance(to) < ::Currency::minimum_balance() { + reserve_balance.saturating_add(::Currency::minimum_balance()) + } else { + reserve_balance + }; + + ::Currency::unreserve_named(&RESERVE_ID, from, reserve_balance); + ::Currency::transfer(from, to, transfer_amount, AllowDeath)?; + ::Currency::reserve_named(&RESERVE_ID, to, reserve_balance)?; - Self::deposit_event(Event::TransferredToken(from.clone(), to.clone(), token.0, token.1)); + Self::deposit_event(Event::TransferredToken { + from: from.clone(), + to: to.clone(), + class_id: token.0, + token_id: token.1, + }); Ok(()) } #[require_transactional] fn do_mint( - who: T::AccountId, - to: T::AccountId, + who: &T::AccountId, + to: &T::AccountId, class_id: ClassIdOf, metadata: CID, attributes: Attributes, quantity: u32, - ) -> DispatchResult { + ) -> Result>, DispatchError> { ensure!(quantity >= 1, Error::::InvalidQuantity); let class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; - ensure!(who == class_info.owner, Error::::NoPermission); + ensure!(*who == class_info.owner, Error::::NoPermission); ensure!( class_info.data.properties.0.contains(ClassProperty::Mintable), @@ -428,18 +452,42 @@ impl Pallet { let deposit = T::CreateTokenDeposit::get().saturating_add(data_deposit); let total_deposit = deposit.saturating_mul(quantity.into()); + // https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/balances/src/lib.rs#L965 + // Now the pallet-balances judges whether does provider is based on the `free balance` instead of + // `total balance`. When there's no other providers, error will throw in following reserve + // operation, which want to make `free balance` is zero and `reserved balance` is not zero. + // If receiver account has not enough ed, transfer an additional ED to make sure the subsequent + // reserve operation. + let total_transfer_amount = + if ::Currency::free_balance(to) < ::Currency::minimum_balance() { + total_deposit.saturating_add(::Currency::minimum_balance()) + } else { + total_deposit + }; + // `repatriate_reserved` will check `to` account exist and may return // `DeadAccount`. - ::Currency::transfer(&who, &to, total_deposit, KeepAlive)?; - ::Currency::reserve_named(&RESERVE_ID, &to, total_deposit)?; + ::Currency::transfer(who, to, total_transfer_amount, KeepAlive)?; + ::Currency::reserve_named(&RESERVE_ID, to, total_deposit)?; + let mut token_ids = Vec::with_capacity(quantity as usize); let data = TokenData { deposit, attributes }; for _ in 0..quantity { - orml_nft::Pallet::::mint(&to, class_id, metadata.clone(), data.clone())?; + token_ids.push(orml_nft::Pallet::::mint( + to, + class_id, + metadata.clone(), + data.clone(), + )?); } - Self::deposit_event(Event::MintedToken(who, to, class_id, quantity)); - Ok(()) + Self::deposit_event(Event::MintedToken { + from: who.clone(), + to: to.clone(), + class_id, + quantity, + }); + Ok(token_ids) } fn do_burn(who: T::AccountId, token: (ClassIdOf, TokenIdOf), remark: Option>) -> DispatchResult { @@ -459,9 +507,18 @@ impl Pallet { if let Some(remark) = remark { let hash = T::Hashing::hash(&remark[..]); - Self::deposit_event(Event::BurnedTokenWithRemark(who, token.0, token.1, hash)); + Self::deposit_event(Event::BurnedTokenWithRemark { + owner: who, + class_id: token.0, + token_id: token.1, + remark_hash: hash, + }); } else { - Self::deposit_event(Event::BurnedToken(who, token.0, token.1)); + Self::deposit_event(Event::BurnedToken { + owner: who, + class_id: token.0, + token_id: token.1, + }); } Ok(()) @@ -483,20 +540,67 @@ impl Pallet { } } -impl NFT for Pallet { - type ClassId = ClassIdOf; - type TokenId = TokenIdOf; +impl InspectExtended for Pallet { type Balance = NFTBalance; fn balance(who: &T::AccountId) -> Self::Balance { orml_nft::TokensByOwner::::iter_prefix((who,)).count() as u128 } - fn owner(token: (Self::ClassId, Self::TokenId)) -> Option { - orml_nft::Pallet::::tokens(token.0, token.1).map(|t| t.owner) + fn next_token_id(class: Self::CollectionId) -> Self::ItemId { + orml_nft::Pallet::::next_token_id(class) + } +} + +impl Inspect for Pallet { + type ItemId = TokenIdOf; + type CollectionId = ClassIdOf; + + fn owner(class: &Self::CollectionId, instance: &Self::ItemId) -> Option { + orml_nft::Pallet::::tokens(class, instance).map(|t| t.owner) + } + + fn collection_owner(class: &Self::CollectionId) -> Option { + orml_nft::Pallet::::classes(class).map(|c| c.owner) + } + + fn can_transfer(class: &Self::CollectionId, _: &Self::ItemId) -> bool { + orml_nft::Pallet::::classes(class).map_or(false, |class_info| { + class_info.data.properties.0.contains(ClassProperty::Transferable) + }) } +} + +impl Mutate for Pallet { + /// Mint some asset `instance` of `class` to be owned by `who`. + fn mint_into(class: &Self::CollectionId, instance: &Self::ItemId, who: &T::AccountId) -> DispatchResult { + // Ensure the next token ID is correct + ensure!( + orml_nft::Pallet::::next_token_id(class) == *instance, + Error::::IncorrectTokenId + ); + + let class_owner = + >::collection_owner(class).ok_or(Error::::ClassIdNotFound)?; + Self::do_mint(&class_owner, who, *class, Default::default(), Default::default(), 1u32)?; + Ok(()) + } + + /// Burn some asset `instance` of `class`. + fn burn( + class: &Self::CollectionId, + instance: &Self::ItemId, + _maybe_check_owner: Option<&T::AccountId>, + ) -> DispatchResult { + let owner = >::owner(class, instance).ok_or(Error::::TokenIdNotFound)?; + Self::do_burn(owner, (*class, *instance), None) + } +} - fn transfer(from: &T::AccountId, to: &T::AccountId, token: (Self::ClassId, Self::TokenId)) -> DispatchResult { - Self::do_transfer(from, to, token) +impl Transfer for Pallet { + /// Transfer asset `instance` of `class` into `destination` account. + fn transfer(class: &Self::CollectionId, instance: &Self::ItemId, destination: &T::AccountId) -> DispatchResult { + let owner = >::owner(class, instance).ok_or(Error::::TokenIdNotFound)?; + Self::do_transfer(&owner, destination, (*class, *instance)) } } diff --git a/blockchain/modules/nft/src/mock.rs b/blockchain/modules/nft/src/mock.rs index 98015da8b..e11d9f14a 100644 --- a/blockchain/modules/nft/src/mock.rs +++ b/blockchain/modules/nft/src/mock.rs @@ -1,441 +1,266 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -#![cfg(test)] - -use super::*; - -use crate as nft; -use codec::{Decode, Encode}; -use frame_support::{ - construct_runtime, ord_parameter_types, parameter_types, - traits::{Contains, InstanceFilter}, - RuntimeDebug, -}; -use frame_system::EnsureSignedBy; -use orml_traits::parameter_type_with_key; -use primitives::{Amount, Balance, BlockNumber, CurrencyId, ReserveIdentifier, TokenSymbol}; -use sp_core::{crypto::AccountId32, H256}; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; -use support::{mocks::MockAddressMapping, SerpTreasury}; - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -pub type AccountId = AccountId32; - -impl frame_system::Config for Runtime { - type BaseCallFilter = BaseFilter; - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Call = Call; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; -} -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type Event = Event; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); -} -impl pallet_utility::Config for Runtime { - type Event = Event; - type Call = Call; - type WeightInfo = (); -} -parameter_types! { - pub const ProxyDepositBase: u64 = 1; - pub const ProxyDepositFactor: u64 = 1; - pub const MaxProxies: u16 = 4; - pub const MaxPending: u32 = 2; - pub const AnnouncementDepositBase: u64 = 1; - pub const AnnouncementDepositFactor: u64 = 1; -} -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, MaxEncodedLen)] -pub enum ProxyType { - Any, - JustTransfer, - JustUtility, -} -impl Default for ProxyType { - fn default() -> Self { - Self::Any - } -} -impl InstanceFilter for ProxyType { - fn filter(&self, c: &Call) -> bool { - match self { - ProxyType::Any => true, - ProxyType::JustTransfer => matches!(c, Call::Balances(pallet_balances::Call::transfer(..))), - ProxyType::JustUtility => matches!(c, Call::Utility(..)), - } - } - fn is_superset(&self, o: &Self) -> bool { - self == &ProxyType::Any || self == o - } -} -pub struct BaseFilter; -impl Contains for BaseFilter { - fn contains(c: &Call) -> bool { - match *c { - // Remark is used as a no-op call in the benchmarking - Call::System(SystemCall::remark(_)) => true, - Call::System(_) => false, - _ => true, - } - } -} -impl pallet_proxy::Config for Runtime { - type Event = Event; - type Call = Call; - type Currency = Balances; - type ProxyType = ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; - type WeightInfo = (); - type CallHasher = BlakeTwo256; - type MaxPending = MaxPending; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; -} - -pub type NativeCurrency = module_currencies::BasicCurrencyAdapter; - -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() - }; -} - -ord_parameter_types! { - pub const One: AccountId = ALICE; -} - -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = Balance; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = (); - type DustRemovalWhitelist = (); -} - -pub struct MockSerpTreasury; -impl SerpTreasury for MockSerpTreasury { - type Balance = Balance; - type CurrencyId = CurrencyId; - - fn calculate_supply_change( - _numerator: Balance, - _denominator: Balance, - _supply: Balance - ) -> Self::Balance{ - unimplemented!() - } - - fn serp_tes_now() -> DispatchResult { - unimplemented!() - } - - /// Deliver System StableCurrency Inflation - fn issue_stablecurrency_inflation() -> DispatchResult { - unimplemented!() - } - - /// SerpUp ratio for BuyBack Swaps to burn Dinar - fn get_buyback_serpup( - _amount: Balance, - _currency_id: CurrencyId, - ) -> DispatchResult { - unimplemented!() - } - - /// Add CashDrop to the pool - fn add_cashdrop_to_pool( - _currency_id: Self::CurrencyId, - _amount: Self::Balance - ) -> DispatchResult { - unimplemented!() - } - - /// Issue CashDrop from the pool to the claimant account - fn issue_cashdrop_from_pool( - _claimant_id: &AccountId, - _currency_id: Self::CurrencyId, - _amount: Self::Balance - ) -> DispatchResult { - unimplemented!() - } - - /// SerpUp ratio for SetPay Cashdrops - fn get_cashdrop_serpup( - _amount: Balance, - _currency_id: CurrencyId - ) -> DispatchResult { - unimplemented!() - } - - /// SerpUp ratio for BuyBack Swaps to burn Dinar - fn get_buyback_serplus( - _amount: Balance, - _currency_id: CurrencyId, - ) -> DispatchResult { - unimplemented!() - } - - fn get_cashdrop_serplus( - _amount: Balance, - _currency_id: CurrencyId - ) -> DispatchResult { - unimplemented!() - } - - /// issue serpup surplus(stable currencies) to their destinations according to the serpup_ratio. - fn on_serplus( - _currency_id: CurrencyId, - _amount: Balance, - ) -> DispatchResult { - unimplemented!() - } - - /// issue serpup surplus(stable currencies) to their destinations according to the serpup_ratio. - fn on_serpup( - _currency_id: CurrencyId, - _amount: Balance, - ) -> DispatchResult { - unimplemented!() - } - - /// buy back and burn surplus(stable currencies) with swap by DEX. - fn on_serpdown( - _currency_id: CurrencyId, - _amount: Balance, - ) -> DispatchResult { - unimplemented!() - } - - /// get the minimum supply of a setcurrency - by key - fn get_minimum_supply( - _currency_id: CurrencyId - ) -> Balance { - unimplemented!() - } - - /// issue standard to `who` - fn issue_standard( - _currency_id: CurrencyId, - _who: &AccountId, - _standard: Balance - ) -> DispatchResult { - unimplemented!() - } - - /// burn standard(stable currency) of `who` - fn burn_standard( - _currency_id: CurrencyId, - _who: &AccountId, - _standard: Balance - ) -> DispatchResult { - unimplemented!() - } - - /// issue setter of amount setter to `who` - fn issue_setter( - _who: &AccountId, - _setter: Balance - ) -> DispatchResult { - unimplemented!() - } - - /// burn setter of `who` - fn burn_setter( - _who: &AccountId, - _setter: Balance - ) -> DispatchResult { - unimplemented!() - } - - /// deposit reserve asset (Setter (SETR)) to serp treasury by `who` - fn deposit_setter( - _from: &AccountId, - _amount: Balance - ) -> DispatchResult { - unimplemented!() - } - - /// claim cashdrop of `currency_id` relative to `transfer_amount` for `who` - fn claim_cashdrop( - _currency_id: CurrencyId, - _who: &AccountId, - _transfer_amount: Balance - ) -> DispatchResult { - unimplemented!() - } -} - -pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); - -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; -} - -pub const SETR: CurrencyId = CurrencyId::Token(TokenSymbol::SETR); -pub const SETUSD: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); - -parameter_types! { - pub StableCurrencyIds: Vec = vec![ - SETR, - SETUSD, - ]; -} - -impl module_currencies::Config for Runtime { - type Event = Event; - type MultiCurrency = Tokens; - type NativeCurrency = NativeCurrency; - type GetNativeCurrencyId = GetNativeCurrencyId; - type StableCurrencyIds = StableCurrencyIds; - type SerpTreasury = MockSerpTreasury; - type WeightInfo = (); - type AddressMapping = MockAddressMapping; - type EVMBridge = (); - type SweepOrigin = EnsureSignedBy; - type OnDust = (); -} - -parameter_types! { - pub const CreateClassDeposit: Balance = 200; - pub const CreateTokenDeposit: Balance = 100; - pub const DataDepositPerByte: Balance = 10; - pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); - pub MaxAttributesBytes: u32 = 10; -} -impl Config for Runtime { - type Event = Event; - type Currency = Balances; - type CreateClassDeposit = CreateClassDeposit; - type CreateTokenDeposit = CreateTokenDeposit; - type DataDepositPerByte = DataDepositPerByte; - type PalletId = NftPalletId; - type MaxAttributesBytes = MaxAttributesBytes; - type WeightInfo = (); -} - -parameter_types! { - pub const MaxClassMetadata: u32 = 1024; - pub const MaxTokenMetadata: u32 = 1024; -} - -impl orml_nft::Config for Runtime { - type ClassId = u32; - type TokenId = u64; - type ClassData = ClassData; - type TokenData = TokenData; - type MaxClassMetadata = MaxClassMetadata; - type MaxTokenMetadata = MaxTokenMetadata; -} - -use frame_system::Call as SystemCall; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - NFTModule: nft::{Pallet, Call, Event}, - OrmlNFT: orml_nft::{Pallet, Storage, Config}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, - Utility: pallet_utility::{Pallet, Call, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - Currency: module_currencies::{Pallet, Call, Event}, - } -); - -pub const ALICE: AccountId = AccountId::new([1u8; 32]); -pub const BOB: AccountId = AccountId::new([2u8; 32]); -pub const CLASS_ID: ::ClassId = 0; -pub const CLASS_ID_NOT_EXIST: ::ClassId = 1; -pub const TOKEN_ID: ::TokenId = 0; -pub const TOKEN_ID_NOT_EXIST: ::TokenId = 1; - -pub struct ExtBuilder; -impl Default for ExtBuilder { - fn default() -> Self { - ExtBuilder - } -} - -impl ExtBuilder { - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 100000)], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![cfg(test)] + +use super::*; + +use crate as nft; +use frame_support::{ + construct_runtime, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Contains, InstanceFilter, Nothing}, +}; +use frame_system::EnsureSignedBy; +use module_support::mocks::MockAddressMapping; +use orml_traits::parameter_type_with_key; +use parity_scale_codec::{Decode, Encode}; +use primitives::{Amount, Balance, CurrencyId, ReserveIdentifier, TokenSymbol}; +use sp_core::{crypto::AccountId32, H160, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, RuntimeDebug, +}; + +pub type AccountId = AccountId32; + +impl frame_system::Config for Runtime { + type BaseCallFilter = BaseFilter; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub enum ProxyType { + Any, + JustTransfer, + JustUtility, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::JustTransfer => matches!( + c, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) + ), + ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }), + } + } + fn is_superset(&self, o: &Self) -> bool { + self == &ProxyType::Any || self == o + } +} +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + // Remark is used as a no-op call in the benchmarking + RuntimeCall::System(SystemCall::remark { .. }) => true, + RuntimeCall::System(_) => false, + _ => true, + } + } +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ConstU128<1>; + type ProxyDepositFactor = ConstU128<1>; + type MaxProxies = ConstU32<4>; + type WeightInfo = (); + type CallHasher = BlakeTwo256; + type MaxPending = ConstU32<2>; + type AnnouncementDepositBase = ConstU128<1>; + type AnnouncementDepositFactor = ConstU128<1>; +} + +pub type NativeCurrency = module_currencies::BasicCurrencyAdapter; + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +ord_parameter_types! { + pub const One: AccountId = ALICE; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; + pub Erc20HoldingAccount: H160 = H160::from_low_u64_be(1); +} + +impl module_currencies::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Tokens; + type NativeCurrency = NativeCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type Erc20HoldingAccount = Erc20HoldingAccount; + type WeightInfo = (); + type AddressMapping = MockAddressMapping; + type EVMBridge = (); + type GasToWeight = (); + type SweepOrigin = EnsureSignedBy; + type OnDust = (); +} + +parameter_types! { + pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); +} +pub const CREATE_CLASS_DEPOSIT: u128 = 200; +pub const CREATE_TOKEN_DEPOSIT: u128 = 100; +pub const DATA_DEPOSIT_PER_BYTE: u128 = 10; +pub const MAX_ATTRIBUTES_BYTES: u32 = 10; +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type CreateClassDeposit = ConstU128; + type CreateTokenDeposit = ConstU128; + type DataDepositPerByte = ConstU128; + type PalletId = NftPalletId; + type MaxAttributesBytes = ConstU32; + type WeightInfo = (); +} + +impl orml_nft::Config for Runtime { + type ClassId = u32; + type TokenId = u64; + type ClassData = ClassData; + type TokenData = TokenData; + type MaxClassMetadata = ConstU32<1024>; + type MaxTokenMetadata = ConstU32<1024>; +} + +use frame_system::Call as SystemCall; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + NFTModule: nft, + OrmlNFT: orml_nft, + Balances: pallet_balances, + Proxy: pallet_proxy, + Utility: pallet_utility, + Tokens: orml_tokens, + Currency: module_currencies, + } +); + +pub const ALICE: AccountId = AccountId::new([1u8; 32]); +pub const BOB: AccountId = AccountId::new([2u8; 32]); +pub const CLASS_ID: ::ClassId = 0; +pub const CLASS_ID_NOT_EXIST: ::ClassId = 1; +pub const TOKEN_ID: ::TokenId = 0; +pub const TOKEN_ID_NOT_EXIST: ::TokenId = 1; + +pub struct ExtBuilder; +impl Default for ExtBuilder { + fn default() -> Self { + ExtBuilder + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, 100000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/blockchain/modules/nft/src/tests.rs b/blockchain/modules/nft/src/tests.rs index 531bf6b80..f4c7efeb9 100644 --- a/blockchain/modules/nft/src/tests.rs +++ b/blockchain/modules/nft/src/tests.rs @@ -25,11 +25,11 @@ use super::*; use frame_support::traits::Currency; use frame_support::{assert_noop, assert_ok}; -use mock::{Event, *}; +use mock::{RuntimeEvent, *}; use orml_nft::TokenInfo; use primitives::Balance; -use sp_runtime::{traits::BlakeTwo256, ArithmeticError}; -use sp_std::convert::TryInto; +use sp_runtime::{traits::BlakeTwo256, ArithmeticError, TokenError}; +use sp_std::collections::btree_map::BTreeMap; fn free_balance(who: &AccountId) -> Balance { ::Currency::free_balance(who) @@ -40,7 +40,7 @@ fn reserved_balance(who: &AccountId) -> Balance { } fn class_id_account() -> AccountId { - ::PalletId::get().into_sub_account(CLASS_ID) + ::PalletId::get().into_sub_account_truncating(CLASS_ID) } fn test_attr(x: u8) -> Attributes { @@ -58,18 +58,17 @@ fn create_class_should_work() { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Default::default(), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( - class_id_account(), - CLASS_ID, - ))); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::CreatedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); - let cls_deposit = - CreateClassDeposit::get() + DataDepositPerByte::get() * ((metadata.len() as u128) + TEST_ATTR_LEN); + let cls_deposit = CREATE_CLASS_DEPOSIT + DATA_DEPOSIT_PER_BYTE * ((metadata.len() as u128) + TEST_ATTR_LEN); assert_eq!( reserved_balance(&class_id_account()), @@ -93,12 +92,12 @@ fn create_class_should_fail() { let metadata = vec![1]; assert_noop!( NFTModule::create_class( - Origin::signed(BOB), + RuntimeOrigin::signed(BOB), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable), Default::default(), ), - pallet_balances::Error::::InsufficientBalance + TokenError::FundsUnavailable ); let mut large_attr: Attributes = BTreeMap::new(); @@ -106,7 +105,7 @@ fn create_class_should_fail() { assert_noop!( NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata, Properties(ClassProperty::Transferable | ClassProperty::Burnable), large_attr, @@ -122,42 +121,45 @@ fn mint_should_work() { let metadata = vec![1]; let metadata_2 = vec![2, 3]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( - class_id_account(), - CLASS_ID, - ))); - assert_ok!(Balances::deposit_into_existing( - &class_id_account(), - 2 * (CreateTokenDeposit::get() + ((metadata_2.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get())) - )); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::CreatedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); + assert_eq!(Balances::free_balance(&class_id_account()), 1); + assert_eq!(Balances::reserved_balance(&class_id_account()), 282); + + let mint_token_deposit = + 2 * (CREATE_TOKEN_DEPOSIT + ((metadata_2.len() as u128 + TEST_ATTR_LEN) * DATA_DEPOSIT_PER_BYTE)); + let transfer_amount = mint_token_deposit + Balances::minimum_balance(); + assert_ok!(Balances::deposit_into_existing(&class_id_account(), transfer_amount)); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata_2.clone(), test_attr(2), 2 )); - System::assert_last_event(Event::NFTModule(crate::Event::MintedToken( - class_id_account(), - BOB, - CLASS_ID, - 2, - ))); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::MintedToken { + from: class_id_account(), + to: BOB, + class_id: CLASS_ID, + quantity: 2, + })); assert_eq!( reserved_balance(&class_id_account()), - CreateClassDeposit::get() + CREATE_CLASS_DEPOSIT + Proxy::deposit(1u32) - + DataDepositPerByte::get() * (metadata.len() as u128 + TEST_ATTR_LEN) + + DATA_DEPOSIT_PER_BYTE * (metadata.len() as u128 + TEST_ATTR_LEN) ); assert_eq!( reserved_balance(&BOB), - 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN)) + 2 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE * (metadata_2.len() as u128 + TEST_ATTR_LEN)) ); assert_eq!( orml_nft::Pallet::::tokens(0, 0).unwrap(), @@ -165,8 +167,7 @@ fn mint_should_work() { metadata: metadata_2.clone().try_into().unwrap(), owner: BOB, data: TokenData { - deposit: CreateTokenDeposit::get() - + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN), + deposit: CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE * (metadata_2.len() as u128 + TEST_ATTR_LEN), attributes: test_attr(2), } } @@ -177,8 +178,7 @@ fn mint_should_work() { metadata: metadata_2.clone().try_into().unwrap(), owner: BOB, data: TokenData { - deposit: CreateTokenDeposit::get() - + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN), + deposit: CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE * (metadata_2.len() as u128 + TEST_ATTR_LEN), attributes: test_attr(2), } } @@ -195,14 +195,14 @@ fn mint_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_noop!( NFTModule::mint( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), BOB, CLASS_ID_NOT_EXIST, metadata.clone(), @@ -214,7 +214,7 @@ fn mint_should_fail() { assert_noop!( NFTModule::mint( - Origin::signed(BOB), + RuntimeOrigin::signed(BOB), BOB, CLASS_ID, metadata.clone(), @@ -226,7 +226,7 @@ fn mint_should_fail() { assert_noop!( NFTModule::mint( - Origin::signed(BOB), + RuntimeOrigin::signed(BOB), BOB, CLASS_ID, metadata.clone(), @@ -241,11 +241,11 @@ fn mint_should_fail() { }); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 2 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) + Balances::minimum_balance() )); assert_noop!( NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -262,7 +262,7 @@ fn mint_should_fail_without_mintable() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Default::default(), Default::default(), @@ -270,7 +270,7 @@ fn mint_should_fail_without_mintable() { assert_noop!( NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -287,17 +287,17 @@ fn transfer_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 2 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -307,29 +307,43 @@ fn transfer_should_work() { assert_eq!( reserved_balance(&BOB), - 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 2 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) ); - assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( - BOB, ALICE, CLASS_ID, TOKEN_ID, - ))); + assert_ok!(NFTModule::transfer( + RuntimeOrigin::signed(BOB), + ALICE, + (CLASS_ID, TOKEN_ID) + )); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::TransferredToken { + from: BOB, + to: ALICE, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&BOB), - 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 1 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) ); assert_eq!( reserved_balance(&ALICE), - 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 1 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) ); - assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( - ALICE, BOB, CLASS_ID, TOKEN_ID, - ))); + assert_ok!(NFTModule::transfer( + RuntimeOrigin::signed(ALICE), + BOB, + (CLASS_ID, TOKEN_ID) + )); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::TransferredToken { + from: ALICE, + to: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&BOB), - 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + 2 * (CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE) ); assert_eq!(reserved_balance(&ALICE), 0); }); @@ -340,17 +354,17 @@ fn transfer_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -358,15 +372,15 @@ fn transfer_should_fail() { 1 )); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), + NFTModule::transfer(RuntimeOrigin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), Error::::ClassIdNotFound ); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), + NFTModule::transfer(RuntimeOrigin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + NFTModule::transfer(RuntimeOrigin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), orml_nft::Error::::NoPermission ); }); @@ -374,17 +388,17 @@ fn transfer_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Mintable.into()), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -392,7 +406,7 @@ fn transfer_should_fail() { 1 )); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), + NFTModule::transfer(RuntimeOrigin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), Error::::NonTransferable ); }); @@ -403,28 +417,32 @@ fn burn_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata.clone(), Default::default(), 1 )); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID))); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::BurnedToken { + owner: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&class_id_account()), - CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) + CREATE_CLASS_DEPOSIT + Proxy::deposit(1u32) + DATA_DEPOSIT_PER_BYTE * (metadata.len() as u128) ); }); } @@ -434,17 +452,17 @@ fn burn_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -452,12 +470,12 @@ fn burn_should_fail() { 1 )); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), + NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - NFTModule::burn(Origin::signed(ALICE), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(RuntimeOrigin::signed(ALICE), (CLASS_ID, TOKEN_ID)), Error::::NoPermission ); @@ -465,7 +483,7 @@ fn burn_should_fail() { class_info.as_mut().unwrap().total_issuance = 0; }); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID)), ArithmeticError::Overflow, ); }); @@ -473,17 +491,17 @@ fn burn_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Mintable.into()), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -491,7 +509,7 @@ fn burn_should_fail() { 1 )); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID)), Error::::NonBurnable ); }); @@ -502,17 +520,17 @@ fn burn_with_remark_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata.clone(), @@ -523,20 +541,20 @@ fn burn_with_remark_should_work() { let remark = "remark info".as_bytes().to_vec(); let remark_hash = BlakeTwo256::hash(&remark[..]); assert_ok!(NFTModule::burn_with_remark( - Origin::signed(BOB), + RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID), remark )); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedTokenWithRemark( - BOB, - CLASS_ID, - TOKEN_ID, + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::BurnedTokenWithRemark { + owner: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, remark_hash, - ))); + })); assert_eq!( reserved_balance(&class_id_account()), - CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) + CREATE_CLASS_DEPOSIT + Proxy::deposit(1u32) + DATA_DEPOSIT_PER_BYTE * (metadata.len() as u128) ); }); } @@ -545,50 +563,51 @@ fn burn_with_remark_should_work() { fn destroy_class_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; + assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); - let deposit = - Proxy::deposit(1u32) + CreateClassDeposit::get() + DataDepositPerByte::get() * (metadata.len() as u128); - assert_eq!(free_balance(&ALICE), 100000 - deposit); + let deposit = Proxy::deposit(1u32) + CREATE_CLASS_DEPOSIT + DATA_DEPOSIT_PER_BYTE * (metadata.len() as u128); + let transfer = deposit + Balances::minimum_balance(); + assert_eq!(free_balance(&ALICE), 100000 - transfer); assert_eq!(reserved_balance(&ALICE), 0); - assert_eq!(free_balance(&class_id_account()), 0); + assert_eq!(free_balance(&class_id_account()), Balances::minimum_balance()); assert_eq!(reserved_balance(&class_id_account()), deposit); assert_eq!(free_balance(&BOB), 0); assert_eq!(reserved_balance(&BOB), 0); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, Default::default(), 1 )); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID))); assert_ok!(NFTModule::destroy_class( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), CLASS_ID, ALICE )); - System::assert_last_event(Event::NFTModule(crate::Event::DestroyedClass( - class_id_account(), - CLASS_ID, - ))); + System::assert_last_event(RuntimeEvent::NFTModule(crate::Event::DestroyedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); assert_eq!(free_balance(&class_id_account()), 0); assert_eq!(reserved_balance(&class_id_account()), 0); assert_eq!(free_balance(&ALICE), 100000); assert_eq!(reserved_balance(&ALICE), 0); assert_eq!( free_balance(&BOB), - CreateTokenDeposit::get() + DataDepositPerByte::get() + CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() ); assert_eq!(reserved_balance(&BOB), 0); }); @@ -599,17 +618,17 @@ fn destroy_class_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + 1 * CREATE_TOKEN_DEPOSIT + DATA_DEPOSIT_PER_BYTE + Balances::minimum_balance() )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, @@ -617,29 +636,29 @@ fn destroy_class_should_fail() { 1 )); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), + NFTModule::destroy_class(RuntimeOrigin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), Error::::ClassIdNotFound ); assert_noop!( - NFTModule::destroy_class(Origin::signed(BOB), CLASS_ID, BOB), + NFTModule::destroy_class(RuntimeOrigin::signed(BOB), CLASS_ID, BOB), Error::::NoPermission ); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + NFTModule::destroy_class(RuntimeOrigin::signed(class_id_account()), CLASS_ID, BOB), Error::::CannotDestroyClass ); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::burn(RuntimeOrigin::signed(BOB), (CLASS_ID, TOKEN_ID))); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + NFTModule::destroy_class(RuntimeOrigin::signed(class_id_account()), CLASS_ID, BOB), pallet_proxy::Error::::NotFound ); assert_ok!(NFTModule::destroy_class( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), CLASS_ID, ALICE )); @@ -652,7 +671,7 @@ fn update_class_properties_should_work() { let metadata = vec![1]; assert_ok!(NFTModule::create_class( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::ClassPropertiesMutable | ClassProperty::Mintable), Default::default(), @@ -660,11 +679,11 @@ fn update_class_properties_should_work() { assert_ok!(Balances::deposit_into_existing( &class_id_account(), - CreateTokenDeposit::get() + ((metadata.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get()) + CREATE_TOKEN_DEPOSIT + ((metadata.len() as u128 + TEST_ATTR_LEN) * DATA_DEPOSIT_PER_BYTE) )); assert_ok!(NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata.clone(), @@ -672,35 +691,43 @@ fn update_class_properties_should_work() { 1 )); - assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::transfer( + RuntimeOrigin::signed(BOB), + ALICE, + (CLASS_ID, TOKEN_ID) + )); assert_ok!(NFTModule::update_class_properties( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), CLASS_ID, Properties(ClassProperty::ClassPropertiesMutable.into()) )); assert_noop!( - NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + NFTModule::transfer(RuntimeOrigin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), Error::::NonTransferable ); assert_ok!(NFTModule::update_class_properties( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), CLASS_ID, Properties(ClassProperty::Transferable.into()) )); - assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::transfer( + RuntimeOrigin::signed(ALICE), + BOB, + (CLASS_ID, TOKEN_ID) + )); assert_noop!( - NFTModule::update_class_properties(Origin::signed(class_id_account()), CLASS_ID, Default::default()), + NFTModule::update_class_properties(RuntimeOrigin::signed(class_id_account()), CLASS_ID, Default::default()), Error::::Immutable ); assert_noop!( NFTModule::mint( - Origin::signed(class_id_account()), + RuntimeOrigin::signed(class_id_account()), BOB, CLASS_ID, metadata, diff --git a/blockchain/modules/nft/src/weights.rs b/blockchain/modules/nft/src/weights.rs index 8893ce582..cb82347fd 100644 --- a/blockchain/modules/nft/src/weights.rs +++ b/blockchain/modules/nft/src/weights.rs @@ -1,148 +1,148 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Autogenerated weights for module_nft -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-07-26, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_nft -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./blockchain/modules/nft/src/weights.rs -// --template=.maintain/module-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_nft. -pub trait WeightInfo { - fn create_class() -> Weight; - fn mint(i: u32, ) -> Weight; - fn transfer() -> Weight; - fn burn() -> Weight; - fn burn_with_remark(b: u32, ) -> Weight; - fn destroy_class() -> Weight; - fn update_class_properties() -> Weight; -} - -/// Weights for module_nft using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn create_class() -> Weight { - (177_661_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn mint(i: u32, ) -> Weight { - (44_387_000 as Weight) - // Standard Error: 46_000 - .saturating_add((72_699_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(i as Weight))) - } - fn transfer() -> Weight { - (266_936_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(7 as Weight)) - } - fn burn() -> Weight { - (189_094_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn burn_with_remark(b: u32, ) -> Weight { - (196_036_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn destroy_class() -> Weight { - (217_091_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - } - fn update_class_properties() -> Weight { - (52_914_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn create_class() -> Weight { - (177_661_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } - fn mint(i: u32, ) -> Weight { - (44_387_000 as Weight) - // Standard Error: 46_000 - .saturating_add((72_699_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(i as Weight))) - } - fn transfer() -> Weight { - (266_936_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(7 as Weight)) - } - fn burn() -> Weight { - (189_094_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } - fn burn_with_remark(b: u32, ) -> Weight { - (196_036_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } - fn destroy_class() -> Weight { - (217_091_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) - } - fn update_class_properties() -> Weight { - (52_914_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for module_nft +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-07-26, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_nft +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/nft/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_nft. +pub trait WeightInfo { + fn create_class() -> Weight; + fn mint(i: u32, ) -> Weight; + fn transfer() -> Weight; + fn burn() -> Weight; + fn burn_with_remark(b: u32, ) -> Weight; + fn destroy_class() -> Weight; + fn update_class_properties() -> Weight; +} + +/// Weights for module_nft using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn create_class() -> Weight { + Weight::from_parts(177_661_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn mint(i: u32, ) -> Weight { + Weight::from_parts(44_387_000, 0) + // Standard Error: 46_000 + .saturating_add(Weight::from_parts(72_699_000, 0).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(i as u64))) + } + fn transfer() -> Weight { + Weight::from_parts(266_936_000, 0) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + fn burn() -> Weight { + Weight::from_parts(189_094_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn burn_with_remark(b: u32, ) -> Weight { + Weight::from_parts(196_036_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(2_000, 0).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn destroy_class() -> Weight { + Weight::from_parts(217_091_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn update_class_properties() -> Weight { + Weight::from_parts(52_914_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn create_class() -> Weight { + Weight::from_parts(177_661_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn mint(i: u32, ) -> Weight { + Weight::from_parts(44_387_000, 0) + // Standard Error: 46_000 + .saturating_add(Weight::from_parts(72_699_000, 0).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(i as u64))) + } + fn transfer() -> Weight { + Weight::from_parts(266_936_000, 0) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + fn burn() -> Weight { + Weight::from_parts(189_094_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn burn_with_remark(b: u32, ) -> Weight { + Weight::from_parts(196_036_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(2_000, 0).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn destroy_class() -> Weight { + Weight::from_parts(217_091_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn update_class_properties() -> Weight { + Weight::from_parts(52_914_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/blockchain/modules/prices/Cargo.toml b/blockchain/modules/prices/Cargo.toml index e737458d8..89d99949b 100644 --- a/blockchain/modules/prices/Cargo.toml +++ b/blockchain/modules/prices/Cargo.toml @@ -1,38 +1,40 @@ -[package] -name = "module-prices" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -integer-sqrt = "0.1.5" -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-traits = { package = "orml-traits", path = "../submodules/orml/traits", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } - -[dev-dependencies] -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-tokens = { path = "../submodules/orml/tokens" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "sp-core/std", - "sp-std/std", - "orml-traits/std", - "support/std", - "primitives/std", -] +[package] +name = "module-prices" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +sp-runtime = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +orml-traits = { workspace = true } +module-support = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "primitives/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/prices/README.md b/blockchain/modules/prices/README.md new file mode 100644 index 000000000..11f307259 --- /dev/null +++ b/blockchain/modules/prices/README.md @@ -0,0 +1,10 @@ +بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +# Prices Module + +## Overview + +The data from Oracle cannot be used in business, prices module will do some process and feed prices for Setheum. Process include: + - specify a fixed price for stable currency; + - feed price in USD or related price bewteen two currencies; + - lock/unlock the price data got from oracle; diff --git a/blockchain/modules/prices/src/lib.rs b/blockchain/modules/prices/src/lib.rs index 4474d8a05..a3c108d69 100644 --- a/blockchain/modules/prices/src/lib.rs +++ b/blockchain/modules/prices/src/lib.rs @@ -1,264 +1,304 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! # Prices Module -//! -//! ## Overview -//! -//! The data from Oracle cannot be used in business, prices module will do some -//! process and feed prices for Setheum. Process include: -//! - specify a fixed price for stable currency -//! - feed price in USD or related price bewteen two currencies -//! - lock/unlock the price data get from oracle - -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use frame_support::{pallet_prelude::*, transactional}; -use frame_system::pallet_prelude::*; -use orml_traits::{DataFeeder, DataProvider, MultiCurrency}; -use primitives::{Balance, CurrencyId}; -use sp_core::U256; -use sp_runtime::FixedPointNumber; -use sp_std::{convert::TryInto, marker::PhantomData}; -use support::{CurrencyIdMapping, DEXManager, LockablePrice, Price, PriceProvider}; -use integer_sqrt::*; - -mod mock; -mod tests; -pub mod weights; - -pub use module::*; -pub use weights::WeightInfo; - -#[frame_support::pallet] -pub mod module { - use super::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - - /// The data source, such as Oracle. - type Source: DataProvider + DataFeeder; - - /// The stable currency id, it should be SETUSD in Setheum. - #[pallet::constant] - type GetSetUSDId: Get; - - /// The stable currency id, it should be SETR in Setheum. - #[pallet::constant] - type SetterCurrencyId: Get; - - /// The fixed prices of stable currency SETUSD, it should be 1 USD in Setheum. - #[pallet::constant] - type SetUSDFixedPrice: Get; - - /// The fixed prices of stable currency SETR, it should be 0.1 USD (10 cents) in Setheum. - #[pallet::constant] - type SetterFixedPrice: Get; - - /// The origin which may lock and unlock prices feed to system. - type LockOrigin: EnsureOrigin; - - /// DEX provide liquidity info. - type DEX: DEXManager; - - /// Currency provide the total insurance of LPToken. - type Currency: MultiCurrency; - - /// Mapping between CurrencyId and ERC20 address so user can use Erc20. - type CurrencyIdMapping: CurrencyIdMapping; - - /// Weight information for the extrinsics in this module. - type WeightInfo: WeightInfo; - } - - #[pallet::error] - pub enum Error { - /// Failed to access price - AccessPriceFailed, - /// There's no locked price - NoLockedPrice, - } - - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { - /// Lock price. \[currency_id, locked_price\] - LockPrice(CurrencyId, Price), - /// Unlock price. \[currency_id\] - UnlockPrice(CurrencyId), - /// Unlock price. \[relative_price\] - FetchPrice(CurrencyId, Option), - /// Unlock price. \[relative_price\] - RelativePrice(CurrencyId, CurrencyId, Option), - } - - /// Mapping from currency id to it's locked price - /// - /// map CurrencyId => Option - #[pallet::storage] - #[pallet::getter(fn locked_price)] - pub type LockedPrice = StorageMap<_, Twox64Concat, CurrencyId, Price, OptionQuery>; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::call] - impl Pallet { - /// Lock the price and feed it to system. - /// - /// The dispatch origin of this call must be `LockOrigin`. - /// - /// - `currency_id`: currency type. - #[pallet::weight((T::WeightInfo::lock_price(), DispatchClass::Operational))] - #[transactional] - pub fn lock_price(origin: OriginFor, currency_id: CurrencyId) -> DispatchResult { - T::LockOrigin::ensure_origin(origin)?; - as LockablePrice>::lock_price(currency_id)?; - Ok(()) - } - - /// Unlock the price and get the price from `PriceProvider` again - /// - /// The dispatch origin of this call must be `LockOrigin`. - /// - /// - `currency_id`: currency type. - #[pallet::weight((T::WeightInfo::unlock_price(), DispatchClass::Operational))] - #[transactional] - pub fn unlock_price(origin: OriginFor, currency_id: CurrencyId) -> DispatchResult { - T::LockOrigin::ensure_origin(origin)?; - as LockablePrice>::unlock_price(currency_id)?; - Ok(()) - } - } -} - -impl Pallet { - /// access the exchange rate of specific currency to USD, - /// it always access the real-time price directly. - /// - /// Note: this returns the price for 1 basic unit - fn access_price(currency_id: CurrencyId) -> Option { - let maybe_price = if currency_id == T::GetSetUSDId::get() { - // if is SETUSD, use fixed price - Some(T::SetUSDFixedPrice::get()) - } else if currency_id == T::SetterCurrencyId::get() { - // if is SETR, return Setter fixed price (currently $0.1) - Some(T::SetterFixedPrice::get()) - } else if let CurrencyId::DexShare(symbol_0, symbol_1) = currency_id { - let token_0: CurrencyId = symbol_0.into(); - let token_1: CurrencyId = symbol_1.into(); - - // directly return the fair price - return { - if let (Some(price_0), Some(price_1)) = (Self::access_price(token_0), Self::access_price(token_1)) { - let (pool_0, pool_1) = T::DEX::get_liquidity_pool(token_0, token_1); - let total_shares = T::Currency::total_issuance(currency_id); - lp_token_fair_price(total_shares, pool_0, pool_1, price_0, price_1) - } else { - None - } - }; - } else { - // get real-time price from oracle - T::Source::get(¤cy_id) - }; - - let maybe_adjustment_multiplier = 10u128.checked_pow(T::CurrencyIdMapping::decimals(currency_id)?.into()); - - if let (Some(price), Some(adjustment_multiplier)) = (maybe_price, maybe_adjustment_multiplier) { - // return the price for 1 basic unit - Price::checked_from_rational(price.into_inner(), adjustment_multiplier) - } else { - None - } - } -} - -impl LockablePrice for Pallet { - /// Record the real-time price from oracle as the locked price - fn lock_price(currency_id: CurrencyId) -> DispatchResult { - let price = Self::access_price(currency_id).ok_or(Error::::AccessPriceFailed)?; - LockedPrice::::insert(currency_id, price); - Pallet::::deposit_event(Event::LockPrice(currency_id, price)); - Ok(()) - } - - /// Unlock the locked price - fn unlock_price(currency_id: CurrencyId) -> DispatchResult { - let _ = LockedPrice::::take(currency_id).ok_or(Error::::NoLockedPrice)?; - Pallet::::deposit_event(Event::UnlockPrice(currency_id)); - Ok(()) - } -} - -/// PriceProvider that always provider real-time prices from oracle -pub struct RealTimePriceProvider(PhantomData); -impl PriceProvider for RealTimePriceProvider { - fn get_price(currency_id: CurrencyId) -> Option { - Pallet::::access_price(currency_id) - } -} - -/// PriceProvider that priority access to the locked price, if it is none, -/// will access to real-time price -pub struct PriorityLockedPriceProvider(PhantomData); -impl PriceProvider for PriorityLockedPriceProvider { - fn get_price(currency_id: CurrencyId) -> Option { - Pallet::::locked_price(currency_id).or_else(|| Pallet::::access_price(currency_id)) - } -} - -/// PriceProvider that always provider locked prices from prices module -pub struct LockedPriceProvider(PhantomData); -impl PriceProvider for LockedPriceProvider { - fn get_price(currency_id: CurrencyId) -> Option { - Pallet::::locked_price(currency_id) - } -} - -/// The fair price is determined by the external feed price and the size of the liquidity pool: -/// https://blog.alphafinance.io/fair-lp-token-pricing/ -/// fair_price = (pool_0 * pool_1)^0.5 * (price_0 * price_1)^0.5 / total_shares * 2 -fn lp_token_fair_price( - total_shares: Balance, - pool_a: Balance, - pool_b: Balance, - price_a: Price, - price_b: Price, -) -> Option { - U256::from(pool_a) - .saturating_mul(U256::from(pool_b)) - .integer_sqrt() - .saturating_mul( - U256::from(price_a.into_inner()) - .saturating_mul(U256::from(price_b.into_inner())) - .integer_sqrt(), - ) - .checked_div(U256::from(total_shares)) - .and_then(|n| n.checked_mul(U256::from(2))) - .and_then(|r| TryInto::::try_into(r).ok()) - .map(Price::from_inner) -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Prices Module +//! +//! ## Overview +//! +//! The data from Oracle cannot be used in business, prices module will do some +//! process and feed prices for Setheum. Process include: +//! - specify a fixed price for stable currency +//! - feed price in USD or related price bewteen two currencies +//! - lock/unlock the price data got from oracle + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use module_support::{SwapDexManager, Erc20InfoMapping, ExchangeRateProvider, LockablePrice, Price, PriceProvider, Rate}; +use orml_traits::{DataFeeder, DataProvider, GetByKey, MultiCurrency}; +use primitives::{Balance, CurrencyId, Lease}; +use sp_core::U256; +use sp_runtime::{ + traits::{BlockNumberProvider, CheckedMul, One, Saturating, UniqueSaturatedInto}, + FixedPointNumber, +}; +use sp_std::marker::PhantomData; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The data source, such as Oracle. + type Source: DataProvider + DataFeeder; + + /// The fixed prices of USSD, it should be 1 USD in Setheum. + #[pallet::constant] + type USSDFixedPrice: Get; + + /// The USSD CURRENCY id, it should be USSD in Setheum. + #[pallet::constant] + type GetUSSDCurrencyId: Get; + + /// The SEE currency id, it should be SEE in Setheum. + #[pallet::constant] + type GetSEECurrencyId: Get; + + /// The Liquid SEE currency id, it should be LSEE in Setheum. + #[pallet::constant] + type GetLiquidSEECurrencyId: Get; + + /// The EDF currency id, it should be EDF in Setheum. + #[pallet::constant] + type GetEDFCurrencyId: Get; + + /// The Liquid EDF currency id, it should be LEDF in Setheum. + #[pallet::constant] + type GetLiquidEDFCurrencyId: Get; + + /// The origin which may lock and unlock prices feed to system. + type LockOrigin: EnsureOrigin; + + /// The provider of the exchange rate between liquid currency and + /// staking currency. + type LiquidStakingExchangeRateProvider: ExchangeRateProvider; + + /// SwapDex provide liquidity info. + type SwapDex: SwapDexManager; + + /// Currency provide the total insurance of LPToken. + type Currency: MultiCurrency; + + /// Mapping between CurrencyId and ERC20 address so user can use Erc20. + type Erc20InfoMapping: Erc20InfoMapping; + + /// Block number provider for the relaychain. + type RelayChainBlockNumber: BlockNumberProvider>; + + /// The staking reward rate per relaychain block for StakingCurrency. + /// In fact, the staking reward is not settled according to the block on relaychain. + #[pallet::constant] + type RewardRatePerRelaychainBlock: Get; + + /// If a currency is pegged to another currency in price, price of this currency is + /// equal to the price of another. + type PricingPegged: GetByKey>; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Failed to access price + AccessPriceFailed, + /// There's no locked price + NoLockedPrice, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Lock price. + LockPrice { + currency_id: CurrencyId, + locked_price: Price, + }, + /// Unlock price. + UnlockPrice { currency_id: CurrencyId }, + } + + /// Mapping from currency id to it's locked price + /// + /// map CurrencyId => Option + #[pallet::storage] + #[pallet::getter(fn locked_price)] + pub type LockedPrice = StorageMap<_, Twox64Concat, CurrencyId, Price, OptionQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Lock the price and feed it to system. + /// + /// The dispatch origin of this call must be `LockOrigin`. + /// + /// - `currency_id`: currency type. + #[pallet::call_index(0)] + #[pallet::weight((T::WeightInfo::lock_price(), DispatchClass::Operational))] + pub fn lock_price(origin: OriginFor, currency_id: CurrencyId) -> DispatchResult { + T::LockOrigin::ensure_origin(origin)?; + as LockablePrice>::lock_price(currency_id)?; + Ok(()) + } + + /// Unlock the price and get the price from `PriceProvider` again + /// + /// The dispatch origin of this call must be `LockOrigin`. + /// + /// - `currency_id`: currency type. + #[pallet::call_index(1)] + #[pallet::weight((T::WeightInfo::unlock_price(), DispatchClass::Operational))] + pub fn unlock_price(origin: OriginFor, currency_id: CurrencyId) -> DispatchResult { + T::LockOrigin::ensure_origin(origin)?; + as LockablePrice>::unlock_price(currency_id)?; + Ok(()) + } + } +} + +impl Pallet { + /// access the exchange rate of specific currency to USD, + /// it always access the real-time price directly. + /// + /// Note: this returns the price for 1 basic unit + fn access_price(currency_id: CurrencyId) -> Option { + // if it's configured pegged to another currency id + let currency_id = if let Some(pegged_currency_id) = T::PricingPegged::get(¤cy_id) { + pegged_currency_id + } else { + currency_id + }; + + let maybe_price = if currency_id == T::GetUSSDCurrencyId::get() { + // if is USSD stablecoin, use fixed price + Some(T::USSDFixedPrice::get()) + } else if currency_id == T::GetLiquidSEECurrencyId::get() { + // directly return real-time the multiple of the price of SEECurrencyId and the exchange rate + return Self::access_price(T::GetSEECurrencyId::get()) + .and_then(|n| n.checked_mul(&T::LiquidStakingExchangeRateProvider::get_exchange_rate())); + } else if currency_id == T::GetLiquidEDFCurrencyId::get() { + // directly return real-time the multiple of the price of EDFCurrencyId and the exchange rate + return Self::access_price(T::GetEDFCurrencyId::get()) + .and_then(|n| n.checked_mul(&T::LiquidStakingExchangeRateProvider::get_exchange_rate())); + } else if let CurrencyId::DexShare(dex_share_0, dex_share_1) = currency_id { + let token_0: CurrencyId = dex_share_0.into(); + let token_1: CurrencyId = dex_share_1.into(); + + // directly return the fair price + return { + if let (Some(price_0), Some(price_1)) = (Self::access_price(token_0), Self::access_price(token_1)) { + let (pool_0, pool_1) = T::SwapDex::get_liquidity_pool(token_0, token_1); + let total_shares = T::Currency::total_issuance(currency_id); + lp_token_fair_price(total_shares, pool_0, pool_1, price_0, price_1) + } else { + None + } + }; + } else { + // get real-time price from oracle + T::Source::get(¤cy_id) + }; + + let maybe_adjustment_multiplier = 10u128.checked_pow(T::Erc20InfoMapping::decimals(currency_id)?.into()); + + if let (Some(price), Some(adjustment_multiplier)) = (maybe_price, maybe_adjustment_multiplier) { + // return the price for 1 basic unit + Price::checked_from_rational(price.into_inner(), adjustment_multiplier) + } else { + None + } + } +} + +impl LockablePrice for Pallet { + /// Record the real-time price from oracle as the locked price + fn lock_price(currency_id: CurrencyId) -> DispatchResult { + let price = Self::access_price(currency_id).ok_or(Error::::AccessPriceFailed)?; + LockedPrice::::insert(currency_id, price); + Pallet::::deposit_event(Event::LockPrice { + currency_id, + locked_price: price, + }); + Ok(()) + } + + /// Unlock the locked price + fn unlock_price(currency_id: CurrencyId) -> DispatchResult { + let _ = LockedPrice::::take(currency_id).ok_or(Error::::NoLockedPrice)?; + Pallet::::deposit_event(Event::UnlockPrice { currency_id }); + Ok(()) + } +} + +/// PriceProvider that always provider real-time prices from oracle +pub struct RealTimePriceProvider(PhantomData); +impl PriceProvider for RealTimePriceProvider { + fn get_price(currency_id: CurrencyId) -> Option { + Pallet::::access_price(currency_id) + } +} + +/// PriceProvider that priority access to the locked price, if it is none, +/// will access to real-time price +pub struct PriorityLockedPriceProvider(PhantomData); +impl PriceProvider for PriorityLockedPriceProvider { + fn get_price(currency_id: CurrencyId) -> Option { + Pallet::::locked_price(currency_id).or_else(|| Pallet::::access_price(currency_id)) + } +} + +/// PriceProvider that always provider locked prices from prices module +pub struct LockedPriceProvider(PhantomData); +impl PriceProvider for LockedPriceProvider { + fn get_price(currency_id: CurrencyId) -> Option { + Pallet::::locked_price(currency_id) + } +} + +/// The fair price is determined by the external feed price and the size of the liquidity pool: +/// https://blog.alphafinance.io/fair-lp-token-pricing/ +/// fair_price = (pool_0 * pool_1)^0.5 * (price_0 * price_1)^0.5 / total_shares * 2 +fn lp_token_fair_price( + total_shares: Balance, + pool_a: Balance, + pool_b: Balance, + price_a: Price, + price_b: Price, +) -> Option { + U256::from(pool_a) + .saturating_mul(U256::from(pool_b)) + .integer_sqrt() + .saturating_mul( + U256::from(price_a.into_inner()) + .saturating_mul(U256::from(price_b.into_inner())) + .integer_sqrt(), + ) + .checked_div(U256::from(total_shares)) + .and_then(|n| n.checked_mul(U256::from(2))) + .and_then(|r| TryInto::::try_into(r).ok()) + .map(Price::from_inner) +} diff --git a/blockchain/modules/prices/src/mock.rs b/blockchain/modules/prices/src/mock.rs index acb1505ac..0c2c9f786 100644 --- a/blockchain/modules/prices/src/mock.rs +++ b/blockchain/modules/prices/src/mock.rs @@ -1,269 +1,277 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Mocks for the prices module. - -#![cfg(test)] - -use super::*; -use frame_support::{construct_runtime, ord_parameter_types, parameter_types}; -use frame_system::EnsureSignedBy; -use orml_traits::{parameter_type_with_key, DataFeeder}; -use primitives::{currency::DexShare, Amount, TokenSymbol}; -use sp_core::{H160, H256}; -use sp_runtime::{ - testing::Header, - traits::{IdentityLookup, One as OneT, Zero}, - DispatchError, FixedPointNumber, -}; -use sp_std::cell::RefCell; -use support::{mocks::MockCurrencyIdMapping, SwapLimit}; - -pub type AccountId = u128; -pub type BlockNumber = u64; - -pub const SETM: CurrencyId = CurrencyId::Token(TokenSymbol::SETM); -pub const SETUSD: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); -pub const SETR: CurrencyId = CurrencyId::Token(TokenSymbol::SETR); -pub const SERP: CurrencyId = CurrencyId::Token(TokenSymbol::SERP); -pub const DNAR: CurrencyId = CurrencyId::Token(TokenSymbol::DNAR); -pub const LP_SETUSD_DNAR: CurrencyId = - CurrencyId::DexShare(DexShare::Token(TokenSymbol::SETUSD), DexShare::Token(TokenSymbol::DNAR)); - -mod prices { - pub use super::super::*; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type Origin = Origin; - type Index = u64; - type BlockNumber = BlockNumber; - type Call = Call; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -thread_local! { - static CHANGED: RefCell = RefCell::new(false); -} - -pub fn mock_oracle_update() { - CHANGED.with(|v| *v.borrow_mut() = true) -} - -pub struct MockDataProvider; -impl DataProvider for MockDataProvider { - fn get(currency_id: &CurrencyId) -> Option { - if CHANGED.with(|v| *v.borrow_mut()) { - match *currency_id { - SETUSD => None, - SERP => Some(Price::saturating_from_integer(40000)), - DNAR => Some(Price::saturating_from_integer(10)), - SETM => Some(Price::saturating_from_integer(30)), - _ => None, - } - } else { - match *currency_id { - SETUSD => Some(Price::saturating_from_rational(99, 100)), - SERP => Some(Price::saturating_from_integer(50000)), - DNAR => Some(Price::saturating_from_integer(100)), - SETM => Some(Price::zero()), - _ => None, - } - } - } -} - -impl DataFeeder for MockDataProvider { - fn feed_value(_: AccountId, _: CurrencyId, _: Price) -> sp_runtime::DispatchResult { - Ok(()) - } -} - -pub struct MockDEX; -impl DEXManager for MockDEX { - fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { - match (currency_id_a, currency_id_b) { - (SETUSD, DNAR) => (10000, 200), - _ => (0, 0), - } - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - unimplemented!() - } - - fn get_swap_amount(_: &[CurrencyId], _: SwapLimit) -> Option<(Balance, Balance)> { - unimplemented!() - } - - fn get_best_price_swap_path( - _: CurrencyId, - _: CurrencyId, - _: SwapLimit, - _: Vec>, - ) -> Option> { - unimplemented!() - } - - fn swap_with_specific_path( - _: &AccountId, - _: &[CurrencyId], - _: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } - - fn buyback_swap_with_specific_path( - _: &AccountId, - _: &[CurrencyId], - _: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } - - fn swap_with_exact_target( - _who: &AccountId, - _path: &[CurrencyId], - _exact_target_amount: Balance, - _max_supply_amount: Balance, - ) -> DispatchResult { - unimplemented!() - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { - unimplemented!() - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - unimplemented!() - } -} - -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() - }; -} - -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = Balance; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = (); - type DustRemovalWhitelist = (); -} - -ord_parameter_types! { - pub const One: AccountId = 1; -} - -parameter_types! { - pub const GetSetUSDId: CurrencyId = SETUSD; - pub const SetterCurrencyId: CurrencyId = SETR; - pub SetUSDFixedPrice: Price = Price::one(); - pub SetterFixedPrice: Price = Price::saturating_from_rational(1, 4); // $0.25 -} - -impl Config for Runtime { - type Event = Event; - type Source = MockDataProvider; - type GetSetUSDId = GetSetUSDId; - type SetterCurrencyId = SetterCurrencyId; - type SetUSDFixedPrice = SetUSDFixedPrice; - type SetterFixedPrice = SetterFixedPrice; - type LockOrigin = EnsureSignedBy; - type DEX = MockDEX; - type Currency = Tokens; - type CurrencyIdMapping = MockCurrencyIdMapping; - type WeightInfo = (); -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - PricesModule: prices::{Pallet, Storage, Call, Event}, - Tokens: orml_tokens::{Pallet, Call, Storage, Event}, - } -); - -pub struct ExtBuilder; - -impl Default for ExtBuilder { - fn default() -> Self { - ExtBuilder - } -} - -impl ExtBuilder { - pub fn build(self) -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - t.into() - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Mocks for the prices module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, ord_parameter_types, parameter_types, + traits::{ConstU64, Everything, Nothing}, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockErc20InfoMapping, ExchangeRate, SwapLimit}; +use orml_traits::{parameter_type_with_key, DataFeeder}; +use primitives::{currency::DexShare, Amount, TokenSymbol}; +use sp_core::{H160, H256}; +use sp_runtime::{ + traits::{IdentityLookup, One as OneT, Zero}, + BuildStorage, DispatchError, FixedPointNumber, +}; +use sp_std::cell::RefCell; + +pub type AccountId = u128; +pub type BlockNumber = u64; + +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const LSEE: CurrencyId = CurrencyId::Token(TokenSymbol::LSEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const TAI: CurrencyId = CurrencyId::Token(TokenSymbol::TAI); +pub const KSM: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); +pub const LP_USSD_SEE: CurrencyId = + CurrencyId::DexShare(DexShare::Token(TokenSymbol::USSD), DexShare::Token(TokenSymbol::SEE)); + + +mod prices { + pub use super::super::*; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +thread_local! { + static CHANGED: RefCell = RefCell::new(false); +} + +pub fn mock_oracle_update() { + CHANGED.with(|v| *v.borrow_mut() = true) +} + +pub struct MockDataProvider; +impl DataProvider for MockDataProvider { + fn get(currency_id: &CurrencyId) -> Option { + if CHANGED.with(|v| *v.borrow_mut()) { + match *currency_id { + USSD => None, + TAI => Some(Price::saturating_from_integer(40000)), + SEE => Some(Price::saturating_from_integer(10)), + SEE => Some(Price::saturating_from_integer(30)), + KSM => Some(Price::saturating_from_integer(200)), + _ => None, + } + } else { + match *currency_id { + USSD => Some(Price::saturating_from_rational(99, 100)), + TAI => Some(Price::saturating_from_integer(50000)), + SEE => Some(Price::saturating_from_integer(100)), + SEE => Some(Price::zero()), + KSM => None, + _ => None, + } + } + } +} + +impl DataFeeder for MockDataProvider { + fn feed_value(_: Option, _: CurrencyId, _: Price) -> sp_runtime::DispatchResult { + Ok(()) + } +} + +pub struct MockLiquidStakingExchangeProvider; +impl ExchangeRateProvider for MockLiquidStakingExchangeProvider { + fn get_exchange_rate() -> ExchangeRate { + if CHANGED.with(|v| *v.borrow_mut()) { + ExchangeRate::saturating_from_rational(3, 5) + } else { + ExchangeRate::saturating_from_rational(1, 2) + } + } +} + +pub struct MockSwapDex; +impl SwapDexManager for MockSwapDex { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { + match (currency_id_a, currency_id_b) { + (USSD, SEE) => (10000, 200), + _ => (0, 0), + } + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + unimplemented!() + } + + fn get_swap_amount(_: &[CurrencyId], _: SwapLimit) -> Option<(Balance, Balance)> { + unimplemented!() + } + + fn get_best_price_swap_path( + _: CurrencyId, + _: CurrencyId, + _: SwapLimit, + _: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + unimplemented!() + } + + fn swap_with_specific_path( + _: &AccountId, + _: &[CurrencyId], + _: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + unimplemented!() + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + _stake_increment_share: bool, + ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + unimplemented!() + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + unimplemented!() + } +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +impl BlockNumberProvider for MockRelayBlockNumberProvider { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + Self::get() + } +} + +ord_parameter_types! { + pub const One: AccountId = 1; +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const GetSEECurrencyId: CurrencyId = SEE; + pub const GetLiquidSEECurrencyId: CurrencyId = LSEE; + pub USSDFixedPrice: Price = Price::one(); + pub static MockRelayBlockNumberProvider: BlockNumber = 0; + pub RewardRatePerRelaychainBlock: Rate = Rate::saturating_from_rational(1, 1000); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Source = MockDataProvider; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type USSDFixedPrice = USSDFixedPrice; + type GetSEECurrencyId = GetSEECurrencyId; + type GetLiquidSEECurrencyId = GetLiquidSEECurrencyId; + type LockOrigin = EnsureSignedBy; + type LiquidStakingExchangeRateProvider = MockLiquidStakingExchangeProvider; + type SwapDex = MockSwapDex; + type Currency = Tokens; + type Erc20InfoMapping = MockErc20InfoMapping; + type RelayChainBlockNumber = MockRelayBlockNumberProvider; + type RewardRatePerRelaychainBlock = RewardRatePerRelaychainBlock; + type PricingPegged = PricingPegged; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PricesModule: prices, + Tokens: orml_tokens, + } +); + +pub struct ExtBuilder; + +impl Default for ExtBuilder { + fn default() -> Self { + ExtBuilder + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/prices/src/tests.rs b/blockchain/modules/prices/src/tests.rs index 4070f12bb..3d7244563 100644 --- a/blockchain/modules/prices/src/tests.rs +++ b/blockchain/modules/prices/src/tests.rs @@ -24,7 +24,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use mock::{Event, *}; +use mock::{RuntimeEvent, *}; use sp_runtime::{ traits::{BadOrigin, Bounded}, FixedPointNumber, @@ -122,71 +122,95 @@ fn lp_token_fair_price_works() { fn access_price_of_stable_currency() { ExtBuilder::default().build().execute_with(|| { assert_eq!( - PricesModule::access_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) - ); + PricesModule::access_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); // 1 USD, right shift the decimal point (18-12) places mock_oracle_update(); assert_eq!( - PricesModule::access_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + PricesModule::access_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) ); }); } +#[test] +fn access_price_of_liquid_currency() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + PricesModule::access_price(SEE), + Some(Price::saturating_from_integer(10000000000u128)) + ); // 100 USD, right shift the decimal point (18-12) places + assert_eq!( + PricesModule::access_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) + ); // dot_price * 1/2 + + mock_oracle_update(); + assert_eq!( + PricesModule::access_price(SEE), + Some(Price::saturating_from_integer(1000000000u128)) + ); // 10 USD, right shift the decimal point (18-12) places + assert_eq!( + PricesModule::access_price(LSEE), + Some(Price::saturating_from_integer(600000000u128)) + ); // dot_price * 3/5 + }); +} + #[test] fn access_price_of_dex_share_currency() { ExtBuilder::default().build().execute_with(|| { assert_eq!( - PricesModule::access_price(DNAR), - Some(Price::saturating_from_integer(100u128)) + PricesModule::access_price(SEE), + Some(Price::saturating_from_integer(10000000000u128)) ); // 100 USD, right shift the decimal point (18-12) places assert_eq!( - PricesModule::access_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + PricesModule::access_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) ); - assert_eq!(Tokens::total_issuance(LP_SETUSD_DNAR), 0); - assert_eq!(MockDEX::get_liquidity_pool(SETUSD, DNAR), (10000, 200)); + assert_eq!(Tokens::total_issuance(LP_USSD_SEE), 0); + assert_eq!(MockDEX::get_liquidity_pool(USSD, SEE), (10000, 200)); // when the total issuance of dex share currency is zero - assert_eq!(PricesModule::access_price(LP_SETUSD_DNAR), None); + assert_eq!(PricesModule::access_price(LP_USSD_SEE), None); // issue LP - assert_ok!(Tokens::deposit(LP_SETUSD_DNAR, &1, 100)); - assert_eq!(Tokens::total_issuance(LP_SETUSD_DNAR), 100); + assert_ok!(Tokens::deposit(LP_USSD_SEE, &1, 100)); + assert_eq!(Tokens::total_issuance(LP_USSD_SEE), 100); let lp_price_1 = lp_token_fair_price( - Tokens::total_issuance(LP_SETUSD_DNAR), - MockDEX::get_liquidity_pool(SETUSD, DNAR).0, - MockDEX::get_liquidity_pool(SETUSD, DNAR).1, - PricesModule::access_price(SETUSD).unwrap(), - PricesModule::access_price(DNAR).unwrap(), + Tokens::total_issuance(LP_USSD_SEE), + MockDEX::get_liquidity_pool(USSD, SEE).0, + MockDEX::get_liquidity_pool(USSD, SEE).1, + PricesModule::access_price(USSD).unwrap(), + PricesModule::access_price(SEE).unwrap(), ); - assert_eq!(PricesModule::access_price(LP_SETUSD_DNAR), lp_price_1); + assert_eq!(PricesModule::access_price(LP_USSD_SEE), lp_price_1); // issue more LP - assert_ok!(Tokens::deposit(LP_SETUSD_DNAR, &1, 100)); - assert_eq!(Tokens::total_issuance(LP_SETUSD_DNAR), 200); + assert_ok!(Tokens::deposit(LP_USSD_SEE, &1, 100)); + assert_eq!(Tokens::total_issuance(LP_USSD_SEE), 200); let lp_price_2 = lp_token_fair_price( - Tokens::total_issuance(LP_SETUSD_DNAR), - MockDEX::get_liquidity_pool(SETUSD, DNAR).0, - MockDEX::get_liquidity_pool(SETUSD, DNAR).1, - PricesModule::access_price(SETUSD).unwrap(), - PricesModule::access_price(DNAR).unwrap(), + Tokens::total_issuance(LP_USSD_SEE), + MockDEX::get_liquidity_pool(USSD, SEE).0, + MockDEX::get_liquidity_pool(USSD, SEE).1, + PricesModule::access_price(USSD).unwrap(), + PricesModule::access_price(SEE).unwrap(), ); - assert_eq!(PricesModule::access_price(LP_SETUSD_DNAR), lp_price_2); + assert_eq!(PricesModule::access_price(LP_USSD_SEE), lp_price_2); mock_oracle_update(); let lp_price_3 = lp_token_fair_price( - Tokens::total_issuance(LP_SETUSD_DNAR), - MockDEX::get_liquidity_pool(SETUSD, DNAR).0, - MockDEX::get_liquidity_pool(SETUSD, DNAR).1, - PricesModule::access_price(SETUSD).unwrap(), - PricesModule::access_price(DNAR).unwrap(), + Tokens::total_issuance(LP_USSD_SEE), + MockDEX::get_liquidity_pool(USSD, SEE).0, + MockDEX::get_liquidity_pool(USSD, SEE).1, + PricesModule::access_price(USSD).unwrap(), + PricesModule::access_price(SEE).unwrap(), ); - assert_eq!(PricesModule::access_price(LP_SETUSD_DNAR), lp_price_3); + assert_eq!(PricesModule::access_price(LP_USSD_SEE), lp_price_3); }); } @@ -194,17 +218,35 @@ fn access_price_of_dex_share_currency() { fn access_price_of_other_currency() { ExtBuilder::default().build().execute_with(|| { assert_eq!(PricesModule::access_price(SEE), Some(Price::saturating_from_integer(0))); - assert_eq!(PricesModule::access_price(SETR), Some(Price::saturating_from_rational(1, 4))); + assert_eq!(PricesModule::access_price(KSM), None); mock_oracle_update(); assert_eq!( PricesModule::access_price(SEE), - Some(Price::saturating_from_integer(30u128)) + Some(Price::saturating_from_integer(30000000u128)) ); // 30 USD, right shift the decimal point (18-12) places assert_eq!( - PricesModule::access_price(SERP), - Some(Price::saturating_from_integer(40000u128)) + PricesModule::access_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) + ); // 200 USD, right shift the decimal point (18-12) places + }); +} + +#[test] +fn access_price_of_pegged_currency() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(PricesModule::access_price(KSM), None); + assert_eq!(PricesModule::access_price(TAIKSM), None); + + mock_oracle_update(); + assert_eq!( + PricesModule::access_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) + ); // 200 USD, right shift the decimal point (18-12) places + assert_eq!( + PricesModule::access_price(TAIKSM), + Some(Price::saturating_from_integer(200000000u128)) ); // 200 USD, right shift the decimal point (18-12) places }); } @@ -214,38 +256,49 @@ fn lock_price_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_noop!(PricesModule::unlock_price(Origin::signed(5), SERP), BadOrigin); + assert_noop!(PricesModule::unlock_price(RuntimeOrigin::signed(5), TAI), BadOrigin); - // lock the price of SERP + // lock the price of TAI assert_eq!( - PricesModule::access_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + PricesModule::access_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); - assert_eq!(PricesModule::locked_price(SERP), None); - assert_ok!(PricesModule::lock_price(Origin::signed(1), SERP)); - System::assert_last_event(Event::PricesModule(crate::Event::LockPrice( - SERP, - Price::saturating_from_integer(50000u128), - ))); + assert_eq!(PricesModule::locked_price(TAI), None); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), TAI)); + System::assert_last_event(RuntimeEvent::PricesModule(crate::Event::LockPrice { + currency_id: TAI, + locked_price: Price::saturating_from_integer(50000000000u128), + })); assert_eq!( - PricesModule::locked_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + PricesModule::locked_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) + ); + + // cannot lock the price of KSM when the price from oracle is None + assert_eq!(PricesModule::access_price(KSM), None); + assert_eq!(PricesModule::locked_price(KSM), None); + assert_noop!( + PricesModule::lock_price(RuntimeOrigin::signed(1), KSM), + Error::::AccessPriceFailed ); + assert_eq!(PricesModule::locked_price(KSM), None); - // lock the price of SETR when the price of SETR from oracle is some + mock_oracle_update(); + + // lock the price of KSM when the price of KSM from oracle is some assert_eq!( - PricesModule::access_price(SETR), - Some(Price::saturating_from_rational(1, 4)) + PricesModule::access_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) ); - assert_eq!(PricesModule::locked_price(SETR), None); - assert_ok!(PricesModule::lock_price(Origin::signed(1), SETR)); - System::assert_last_event(Event::PricesModule(crate::Event::LockPrice( - SETR, - Price::saturating_from_rational(1, 4), - ))); + assert_eq!(PricesModule::locked_price(KSM), None); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), KSM)); + System::assert_last_event(RuntimeEvent::PricesModule(crate::Event::LockPrice { + currency_id: KSM, + locked_price: Price::saturating_from_integer(200000000u128), + })); assert_eq!( - PricesModule::locked_price(SETR), - Some(Price::saturating_from_rational(1, 4)) + PricesModule::locked_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) ); }); } @@ -255,22 +308,24 @@ fn unlock_price_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_noop!(PricesModule::unlock_price(Origin::signed(5), SERP), BadOrigin); + assert_noop!(PricesModule::unlock_price(RuntimeOrigin::signed(5), TAI), BadOrigin); // unlock failed when there's no locked price assert_noop!( - PricesModule::unlock_price(Origin::signed(1), SERP), + PricesModule::unlock_price(RuntimeOrigin::signed(1), TAI), Error::::NoLockedPrice ); - assert_ok!(PricesModule::lock_price(Origin::signed(1), SERP)); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), TAI)); assert_eq!( - PricesModule::locked_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + PricesModule::locked_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); - assert_ok!(PricesModule::unlock_price(Origin::signed(1), SERP)); - System::assert_last_event(Event::PricesModule(crate::Event::UnlockPrice(SERP))); - assert_eq!(PricesModule::locked_price(SERP), None); + assert_ok!(PricesModule::unlock_price(RuntimeOrigin::signed(1), TAI)); + System::assert_last_event(RuntimeEvent::PricesModule(crate::Event::UnlockPrice { + currency_id: TAI, + })); + assert_eq!(PricesModule::locked_price(TAI), None); }); } @@ -278,165 +333,200 @@ fn unlock_price_work() { fn price_providers_work() { ExtBuilder::default().build().execute_with(|| { // issue LP - assert_ok!(Tokens::deposit(LP_SETUSD_DNAR, &1, 100)); - assert_eq!(Tokens::total_issuance(LP_SETUSD_DNAR), 100); + assert_ok!(Tokens::deposit(LP_USSD_SEE, &1, 100)); + assert_eq!(Tokens::total_issuance(LP_USSD_SEE), 100); let lp_price_1 = lp_token_fair_price( - Tokens::total_issuance(LP_SETUSD_DNAR), - MockDEX::get_liquidity_pool(SETUSD, DNAR).0, - MockDEX::get_liquidity_pool(SETUSD, DNAR).1, - PricesModule::access_price(SETUSD).unwrap(), - PricesModule::access_price(DNAR).unwrap(), + Tokens::total_issuance(LP_USSD_SEE), + MockDEX::get_liquidity_pool(USSD, SEE).0, + MockDEX::get_liquidity_pool(USSD, SEE).1, + PricesModule::access_price(USSD).unwrap(), + PricesModule::access_price(SEE).unwrap(), ); assert_eq!( - RealTimePriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + RealTimePriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + RealTimePriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); assert_eq!( - RealTimePriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + RealTimePriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) ); - assert_eq!(RealTimePriceProvider::::get_price(SETR), Some(Price::saturating_from_rational(1, 4))); - assert_eq!(RealTimePriceProvider::::get_price(LP_SETUSD_DNAR), lp_price_1); - assert_eq!(RealTimePriceProvider::::get_relative_price(SERP, SETR), Some(Price::saturating_from_integer(200_000))); + assert_eq!(RealTimePriceProvider::::get_price(KSM), None); + assert_eq!(RealTimePriceProvider::::get_price(LP_USSD_SEE), lp_price_1); + assert_eq!(RealTimePriceProvider::::get_relative_price(TAI, KSM), None); assert_eq!( - PriorityLockedPriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + PriorityLockedPriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + PriorityLockedPriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + PriorityLockedPriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) ); - assert_eq!(PriorityLockedPriceProvider::::get_price(SETR), Some(Price::saturating_from_rational(1, 4))); + assert_eq!(PriorityLockedPriceProvider::::get_price(KSM), None); assert_eq!( - PriorityLockedPriceProvider::::get_price(LP_SETUSD_DNAR), + PriorityLockedPriceProvider::::get_price(LP_USSD_SEE), lp_price_1 ); assert_eq!( - PriorityLockedPriceProvider::::get_relative_price(SERP, SETR), - Some(Price::saturating_from_integer(200_000)) + PriorityLockedPriceProvider::::get_relative_price(TAI, KSM), + None ); - assert_eq!(LockedPriceProvider::::get_price(SETUSD), None); - assert_eq!(LockedPriceProvider::::get_price(SERP), None); - assert_eq!(LockedPriceProvider::::get_price(SETR), None); - assert_eq!(LockedPriceProvider::::get_price(LP_SETUSD_DNAR), None); - assert_eq!(LockedPriceProvider::::get_relative_price(SERP, SETR), None); + assert_eq!(LockedPriceProvider::::get_price(USSD), None); + assert_eq!(LockedPriceProvider::::get_price(TAI), None); + assert_eq!(LockedPriceProvider::::get_price(LSEE), None); + assert_eq!(LockedPriceProvider::::get_price(KSM), None); + assert_eq!(LockedPriceProvider::::get_price(LP_USSD_SEE), None); + assert_eq!(LockedPriceProvider::::get_relative_price(TAI, KSM), None); // lock price - assert_ok!(PricesModule::lock_price(Origin::signed(1), SETUSD)); - assert_ok!(PricesModule::lock_price(Origin::signed(1), SERP)); - - assert_ok!(PricesModule::lock_price(Origin::signed(1), LP_SETUSD_DNAR)); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), USSD)); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), TAI)); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), LSEE)); + assert_noop!( + PricesModule::lock_price(RuntimeOrigin::signed(1), KSM), + Error::::AccessPriceFailed + ); + assert_ok!(PricesModule::lock_price(RuntimeOrigin::signed(1), LP_USSD_SEE)); assert_eq!( - LockedPriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + LockedPriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + LockedPriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); assert_eq!( - LockedPriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + LockedPriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) ); - assert_eq!(LockedPriceProvider::::get_price(SETR), None); - assert_eq!(LockedPriceProvider::::get_price(LP_SETUSD_DNAR), lp_price_1); - assert_eq!(LockedPriceProvider::::get_relative_price(SERP, SETR), None); + assert_eq!(LockedPriceProvider::::get_price(KSM), None); + assert_eq!(LockedPriceProvider::::get_price(LP_USSD_SEE), lp_price_1); + assert_eq!(LockedPriceProvider::::get_relative_price(TAI, KSM), None); // mock oracle update mock_oracle_update(); let lp_price_2 = lp_token_fair_price( - Tokens::total_issuance(LP_SETUSD_DNAR), - MockDEX::get_liquidity_pool(SETUSD, DNAR).0, - MockDEX::get_liquidity_pool(SETUSD, DNAR).1, - PricesModule::access_price(SETUSD).unwrap(), - PricesModule::access_price(DNAR).unwrap(), + Tokens::total_issuance(LP_USSD_SEE), + MockDEX::get_liquidity_pool(USSD, SEE).0, + MockDEX::get_liquidity_pool(USSD, SEE).1, + PricesModule::access_price(USSD).unwrap(), + PricesModule::access_price(SEE).unwrap(), ); assert_eq!( - RealTimePriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + RealTimePriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + RealTimePriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(40000000000u128)) ); assert_eq!( - RealTimePriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(40000u128)) + RealTimePriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(600000000u128)) ); assert_eq!( - RealTimePriceProvider::::get_price(SETR), - Some(Price::saturating_from_rational(1, 4)) + RealTimePriceProvider::::get_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) ); - assert_eq!(RealTimePriceProvider::::get_price(LP_SETUSD_DNAR), lp_price_2); + assert_eq!(RealTimePriceProvider::::get_price(LP_USSD_SEE), lp_price_2); assert_eq!( - RealTimePriceProvider::::get_relative_price(SERP, SETR), - Some(Price::saturating_from_integer(160_000u128)) + RealTimePriceProvider::::get_relative_price(TAI, KSM), + Some(Price::saturating_from_integer(200u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + PriorityLockedPriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + PriorityLockedPriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SETR), - Some(Price::saturating_from_rational(1, 4)) + PriorityLockedPriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(LP_SETUSD_DNAR), + PriorityLockedPriceProvider::::get_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) + ); + assert_eq!( + PriorityLockedPriceProvider::::get_price(LP_USSD_SEE), lp_price_1 ); assert_eq!( - PriorityLockedPriceProvider::::get_relative_price(SERP, SETR), - Some(Price::saturating_from_integer(200_000u128)) + PriorityLockedPriceProvider::::get_relative_price(TAI, KSM), + Some(Price::saturating_from_integer(250u128)) ); assert_eq!( - LockedPriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + LockedPriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + LockedPriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(50000000000u128)) ); assert_eq!( - LockedPriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(50000u128)) + LockedPriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(5000000000u128)) ); - assert_eq!(LockedPriceProvider::::get_price(SETR), None); - assert_eq!(LockedPriceProvider::::get_price(LP_SETUSD_DNAR), lp_price_1); - assert_eq!(LockedPriceProvider::::get_relative_price(SERP, SETR), None); + assert_eq!(LockedPriceProvider::::get_price(KSM), None); + assert_eq!(LockedPriceProvider::::get_price(LP_USSD_SEE), lp_price_1); + assert_eq!(LockedPriceProvider::::get_relative_price(TAI, KSM), None); // unlock price - assert_ok!(PricesModule::unlock_price(Origin::signed(1), SETUSD)); - assert_ok!(PricesModule::unlock_price(Origin::signed(1), SERP)); + assert_ok!(PricesModule::unlock_price(RuntimeOrigin::signed(1), USSD)); + assert_ok!(PricesModule::unlock_price(RuntimeOrigin::signed(1), TAI)); + assert_ok!(PricesModule::unlock_price(RuntimeOrigin::signed(1), LSEE)); assert_noop!( - PricesModule::unlock_price(Origin::signed(1), SETR), + PricesModule::unlock_price(RuntimeOrigin::signed(1), KSM), Error::::NoLockedPrice ); - assert_ok!(PricesModule::unlock_price(Origin::signed(1), LP_SETUSD_DNAR)); + assert_ok!(PricesModule::unlock_price(RuntimeOrigin::signed(1), LP_USSD_SEE)); assert_eq!( - PriorityLockedPriceProvider::::get_price(SETUSD), - Some(Price::saturating_from_integer(1u128)) + PriorityLockedPriceProvider::::get_price(USSD), + Some(Price::saturating_from_integer(1000000u128)) + ); + assert_eq!( + PriorityLockedPriceProvider::::get_price(TAI), + Some(Price::saturating_from_integer(40000000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SERP), - Some(Price::saturating_from_integer(40000u128)) + PriorityLockedPriceProvider::::get_price(LSEE), + Some(Price::saturating_from_integer(600000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(SETR), - Some(Price::saturating_from_rational(1, 4)) + PriorityLockedPriceProvider::::get_price(KSM), + Some(Price::saturating_from_integer(200000000u128)) ); assert_eq!( - PriorityLockedPriceProvider::::get_price(LP_SETUSD_DNAR), + PriorityLockedPriceProvider::::get_price(LP_USSD_SEE), lp_price_2 ); assert_eq!( - PriorityLockedPriceProvider::::get_relative_price(SERP, SETR), - Some(Price::saturating_from_integer(160_000u128)) + PriorityLockedPriceProvider::::get_relative_price(TAI, KSM), + Some(Price::saturating_from_integer(200u128)) ); - assert_eq!(LockedPriceProvider::::get_price(SETUSD), None); - assert_eq!(LockedPriceProvider::::get_price(SERP), None); - assert_eq!(LockedPriceProvider::::get_price(SETR), None); - assert_eq!(LockedPriceProvider::::get_price(LP_SETUSD_DNAR), None); - assert_eq!(LockedPriceProvider::::get_relative_price(SERP, SETR), None); + assert_eq!(LockedPriceProvider::::get_price(USSD), None); + assert_eq!(LockedPriceProvider::::get_price(TAI), None); + assert_eq!(LockedPriceProvider::::get_price(LSEE), None); + assert_eq!(LockedPriceProvider::::get_price(KSM), None); + assert_eq!(LockedPriceProvider::::get_price(LP_USSD_SEE), None); + assert_eq!(LockedPriceProvider::::get_relative_price(TAI, KSM), None); }); } diff --git a/blockchain/modules/prices/src/weights.rs b/blockchain/modules/prices/src/weights.rs index 18537e7ef..c02f9f622 100644 --- a/blockchain/modules/prices/src/weights.rs +++ b/blockchain/modules/prices/src/weights.rs @@ -1,82 +1,82 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - - -//! Autogenerated weights for module_prices -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-02-27, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_prices -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./blockchain/modules/prices/src/weights.rs -// --template=.maintain/module-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_prices. -pub trait WeightInfo { - fn lock_price() -> Weight; - fn unlock_price() -> Weight; -} - -/// Weights for module_prices using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn lock_price() -> Weight { - (53_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(11 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn unlock_price() -> Weight { - (12_000_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn lock_price() -> Weight { - (53_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(11 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - fn unlock_price() -> Weight { - (12_000_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + + +//! Autogenerated weights for module_prices +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-27, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_prices +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/prices/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_prices. +pub trait WeightInfo { + fn lock_price() -> Weight; + fn unlock_price() -> Weight; +} + +/// Weights for module_prices using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn lock_price() -> Weight { + Weight::from_parts(53_000_000, 0) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + fn unlock_price() -> Weight { + Weight::from_parts(12_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn lock_price() -> Weight { + Weight::from_parts(53_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn unlock_price() -> Weight { + Weight::from_parts(12_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/blockchain/modules/support/Cargo.toml b/blockchain/modules/support/Cargo.toml index 27404a087..6fea841c6 100644 --- a/blockchain/modules/support/Cargo.toml +++ b/blockchain/modules/support/Cargo.toml @@ -1,29 +1,42 @@ -[package] -name = "module-support" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -impl-trait-for-tuples = "0.1.3" -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "sp-runtime/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "frame-support/std", - "primitives/std", -] +[package] +name = "module-support" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +impl-trait-for-tuples = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +serde = { workspace = true, features = ["std"], optional = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +xcm = { workspace = true } + +orml-tokens = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "orml-tokens/std", + "primitives/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", +] diff --git a/blockchain/modules/support/src/bounded.rs b/blockchain/modules/support/src/bounded.rs new file mode 100644 index 000000000..949cd5b2d --- /dev/null +++ b/blockchain/modules/support/src/bounded.rs @@ -0,0 +1,241 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::Rate; + +use frame_support::traits::Get; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use primitives::{Balance, BlockNumber}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{CheckedSub, One, Zero}, + FixedPointNumber, RuntimeDebug, +}; +use sp_std::{marker::PhantomData, prelude::*, result::Result}; + +#[cfg(feature = "std")] +use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize}; + +/// The bounded type errors. +#[derive(RuntimeDebug, PartialEq, Eq)] +pub enum Error { + /// The value is out of bound. + OutOfBounds, + /// The change diff exceeds the max absolute value. + ExceedMaxChangeAbs, +} + +/// An abstract definition of bounded type. The type is within the range of `Range` +/// and while update the inner value, the max absolute value of the diff is `MaxChangeAbs`. +/// The `Default` value is minimum value of the range. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] +#[derive(Encode, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +#[scale_info(skip_type_params(Range, MaxChangeAbs))] +pub struct BoundedType( + T, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData<(Range, MaxChangeAbs)>, +); + +impl, MaxChangeAbs: Get> Decode + for BoundedType +{ + fn decode(input: &mut I) -> Result { + let inner = T::decode(input)?; + Self::try_from(inner).map_err(|_| "BoundedType: value out of bounds".into()) + } +} + +#[cfg(feature = "std")] +impl<'de, T, Range, MaxChangeAbs> Deserialize<'de> for BoundedType +where + T: Encode + Decode + CheckedSub + PartialOrd + Deserialize<'de>, + Range: Get<(T, T)>, + MaxChangeAbs: Get, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: T = T::deserialize(deserializer)?; + Self::try_from(value).map_err(|_| SerdeError::custom("out of bounds")) + } +} + +impl, MaxChangeAbs: Get> Default + for BoundedType +{ + fn default() -> Self { + let (min, _) = Range::get(); + Self(min, PhantomData) + } +} + +impl BoundedType +where + T: Encode + Decode + CheckedSub + PartialOrd, + Range: Get<(T, T)>, + MaxChangeAbs: Get, +{ + /// Try to create a new instance of `BoundedType`. Returns `Err` if out of bound. + pub fn try_from(value: T) -> Result { + let (min, max) = Range::get(); + if value < min || value > max { + return Err(Error::OutOfBounds); + } + Ok(Self(value, PhantomData)) + } + + /// Set the inner value. Returns `Err` if out of bound or the diff with current value exceeds + /// the max absolute value. + pub fn try_set(&mut self, value: T) -> Result<(), Error> { + let (min, max) = Range::get(); + let max_change_abs = MaxChangeAbs::get(); + let old_value = &self.0; + if value < min || value > max { + return Err(Error::OutOfBounds); + } + + let abs = if value > *old_value { + value + .checked_sub(old_value) + .expect("greater number subtracting smaller one can't underflow; qed") + } else { + old_value + .checked_sub(&value) + .expect("greater number subtracting smaller one can't underflow; qed") + }; + if abs > max_change_abs { + return Err(Error::ExceedMaxChangeAbs); + } + + self.0 = value; + Ok(()) + } + + pub fn into_inner(self) -> T { + self.0 + } + + pub fn inner(&self) -> &T { + &self.0 + } +} + +/// Fractional range between `Rate::zero()` and `Rate::one()`. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub struct Fractional; +impl Get<(Rate, Rate)> for Fractional { + fn get() -> (Rate, Rate) { + (Rate::zero(), Rate::one()) + } +} + +/// Maximum absolute change is 1/5. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub struct OneFifth; +impl Get for OneFifth { + fn get() -> Rate { + Rate::saturating_from_rational(1, 5) + } +} + +pub type BoundedRate = BoundedType; + +/// Fractional rate. +/// +/// The range is between 0 to 1, and max absolute value of change diff is 1/5. +pub type FractionalRate = BoundedRate; + +pub type BoundedBalance = BoundedType; + +pub type BoundedBlockNumber = BoundedType; + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{assert_err, assert_ok}; + + #[test] + fn fractional_rate_works() { + assert_err!( + FractionalRate::try_from(Rate::from_rational(11, 10)), + Error::OutOfBounds + ); + + let mut rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + assert_ok!(rate.try_set(Rate::from_rational(10, 10))); + assert_err!(rate.try_set(Rate::from_rational(11, 10)), Error::OutOfBounds); + assert_err!(rate.try_set(Rate::from_rational(79, 100)), Error::ExceedMaxChangeAbs); + + assert_eq!(FractionalRate::default().into_inner(), Rate::zero()); + } + + #[test] + fn encode_decode_works() { + let rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + let encoded = rate.encode(); + assert_eq!(FractionalRate::decode(&mut &encoded[..]).unwrap(), rate); + + assert_eq!(encoded, Rate::from_rational(8, 10).encode()); + } + + #[test] + fn decode_fails_if_out_of_bounds() { + let bad_rate = BoundedType::(Rate::from_rational(11, 10), PhantomData); + let bad_rate_encoded = bad_rate.encode(); + assert_err!( + FractionalRate::decode(&mut &bad_rate_encoded[..]), + "BoundedType: value out of bounds" + ); + } + + #[test] + fn ser_de_works() { + let rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + assert_eq!(serde_json::json!(&rate).to_string(), r#""800000000000000000""#); + + let deserialized: FractionalRate = serde_json::from_str(r#""800000000000000000""#).unwrap(); + assert_eq!(deserialized, rate); + } + + #[test] + fn deserialize_fails_if_out_of_bounds() { + let failed: Result = serde_json::from_str(r#""1100000000000000000""#); + match failed { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds"), + _ => panic!("should fail"), + } + } + + #[test] + fn bounded_type_default_is_range_min() { + #[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] + pub struct OneToTwo; + impl Get<(Rate, Rate)> for OneToTwo { + fn get() -> (Rate, Rate) { + (Rate::one(), Rate::from_rational(2, 1)) + } + } + + type BoundedRateOneToTwo = BoundedRate; + + assert_eq!(BoundedRateOneToTwo::default().into_inner(), Rate::one()); + } +} diff --git a/blockchain/modules/support/src/dex.rs b/blockchain/modules/support/src/dex.rs new file mode 100644 index 000000000..86d43f4e5 --- /dev/null +++ b/blockchain/modules/support/src/dex.rs @@ -0,0 +1,253 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::H160; +use sp_runtime::{DispatchError, RuntimeDebug}; +use sp_std::{cmp::PartialEq, prelude::*, result::Result}; + +#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum AggregatedSwapPath { + SwapDex(Vec), + // OrderBookDex(Vec), +} + +pub trait SwapDexManager { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); + + fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; + + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)>; + + fn swap_with_specific_path( + who: &AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn add_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError>; + + fn remove_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError>; +} + +pub trait Swap +where + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)>; + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + let aggregated_swap_path = AggregatedSwapPath::Dex(swap_path.to_vec()); + Self::swap_by_aggregated_path(who, &[aggregated_swap_path], limit) + } + + fn swap_by_aggregated_path( + who: &AccountId, + swap_path: &[AggregatedSwapPath], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; +} + +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum SwapError { + CannotSwap, +} + +impl Into for SwapError { + fn into(self) -> DispatchError { + DispatchError::Other("Cannot swap") + } +} + +// Dex wrapper of Swap implementation +pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); + +impl Swap + for SpecificJointsSwap +where + Dex: SwapDexManager, + Joints: Get>>, + Balance: Clone, + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)> { + >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit, + Joints::get(), + ) + .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) + } + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let path = >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit.clone(), + Joints::get(), + ) + .ok_or_else(|| Into::::into(SwapError::CannotSwap))? + .0; + + >::swap_with_specific_path(who, &path, limit) + } + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + >::swap_with_specific_path(who, swap_path, limit) + } + + fn swap_by_aggregated_path( + who: &AccountId, + swap_path: &[AggregatedSwapPath], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + ensure!(swap_path.len() == 1, Into::::into(SwapError::CannotSwap)); + match swap_path.last() { + Some(AggregatedSwapPath::::Dex(path)) => { + >::swap_with_specific_path(who, path, limit) + } + _ => Err(Into::::into(SwapError::CannotSwap)), + } + } +} + +#[cfg(feature = "std")] +impl SwapDexManager for () +where + Balance: Default, +{ + fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { + Default::default() + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + Some(Default::default()) + } + + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { + Some(Default::default()) + } + + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + Some(Default::default()) + } + + fn swap_with_specific_path( + _who: &AccountId, + _path: &[CurrencyId], + _limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + _stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } +} diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs new file mode 100644 index 000000000..b54a6b1c8 --- /dev/null +++ b/blockchain/modules/support/src/ecdp.rs @@ -0,0 +1,209 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_scale_codec::FullCodec; +use primitives::Position; +use sp_core::U256; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::{ + cmp::{Eq, PartialEq}, + fmt::Debug, + prelude::*, +}; + +use crate::{dex::*, ExchangeRate, Ratio}; + +pub trait EmergencyShutdown { + fn is_shutdown() -> bool; +} + +pub trait AuctionManager { + type CurrencyId; + type Balance; + type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; + + fn new_collateral_auction( + refund_recipient: &AccountId, + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + ) -> DispatchResult; + fn cancel_auction(id: Self::AuctionId) -> DispatchResult; + fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; + fn get_total_target_in_auction() -> Self::Balance; +} + +pub trait PeggedRiskManager { + fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; + + fn check_position_valid( + currency_id: CurrencyId, + collateral_balance: Balance, + debit_balance: DebitBalance, + check_required_ratio: bool, + ) -> DispatchResult; + + fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; +} + +#[cfg(feature = "std")] +impl PeggedRiskManager + for () +{ + fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { + Default::default() + } + + fn check_position_valid( + _currency_id: CurrencyId, + _collateral_balance: Balance, + _debit_balance: DebitBalance, + _check_required_ratio: bool, + ) -> DispatchResult { + Ok(()) + } + + fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { + Ok(()) + } +} + +// pub trait UnpeggedRiskManager { +// fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; + +// fn check_position_valid( +// currency_id: CurrencyId, +// collateral_balance: Balance, +// debit_balance: DebitBalance, +// check_required_ratio: bool, +// ) -> DispatchResult; + +// fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; +// } + +// #[cfg(feature = "std")] +// impl UnpeggedRiskManager +// for () +// { +// fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { +// Default::default() +// } + +// fn check_position_valid( +// _currency_id: CurrencyId, +// _collateral_balance: Balance, +// _debit_balance: DebitBalance, +// _check_required_ratio: bool, +// ) -> DispatchResult { +// Ok(()) +// } + +// fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { +// Ok(()) +// } +// } + +/// An abstraction of cdp treasury for SlickUSD ECDP Protocol. +pub trait SlickUsdEcdpTreasury { + type Balance; + type CurrencyId; + + /// get surplus amount of cdp treasury + fn get_surplus_pool() -> Self::Balance; + + /// get debit amount of cdp treasury + fn get_debit_pool() -> Self::Balance; + + /// get collateral assets amount of cdp treasury + fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; + + /// calculate the proportion of specific debit amount for the whole system + fn get_debit_proportion(amount: Self::Balance) -> Ratio; + + /// issue debit for cdp treasury + fn on_system_debit(amount: Self::Balance) -> DispatchResult; + + /// issue surplus(stable currency) for cdp treasury + fn on_system_surplus(amount: Self::Balance) -> DispatchResult; + + /// issue debit to `who` + /// if backed flag is true, means the debit to issue is backed on some + /// assets, otherwise will increase same amount of debit to system debit. + fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; + + /// burn debit(stable currency) of `who` + fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; + + /// deposit surplus(stable currency) to cdp treasury by `from` + fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// withdraw surplus(stable currency) from cdp treasury to `to` + fn withdraw_surplus(to: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// deposit collateral assets to cdp treasury by `who` + fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; + + /// withdraw collateral assets of cdp treasury to `who` + fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; +} + +pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { + fn swap_collateral_to_stable( + currency_id: Self::CurrencyId, + limit: SwapLimit, + collateral_in_auction: bool, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn create_collateral_auctions( + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + refund_receiver: AccountId, + splited: bool, + ) -> sp_std::result::Result; + + fn remove_liquidity_for_lp_collateral( + currency_id: Self::CurrencyId, + amount: Self::Balance, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn max_auction() -> u32; +} + +/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. +pub trait SlickUsdEcdpManager { + /// Adjust ECDP loan + fn adjust_loan( + who: &AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult; + /// Close ECDP loan using DEX + fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; + /// Get open ECDP corresponding to an account and collateral `CurrencyId` + fn get_position(who: &AccountId, currency_id: CurrencyId) -> Position; + /// Get liquidation ratio for collateral `CurrencyId` + fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; + /// Get current ratio of collateral to debit of open ECDP + fn get_current_collateral_ratio(who: &AccountId, currency_id: CurrencyId) -> Option; + /// Get exchange rate of debit units to debit value for a currency_id + fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; +} diff --git a/blockchain/modules/support/src/evm.rs b/blockchain/modules/support/src/evm.rs new file mode 100644 index 000000000..4ab9b945d --- /dev/null +++ b/blockchain/modules/support/src/evm.rs @@ -0,0 +1,331 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_scale_codec::{Decode, Encode}; +use primitives::currency::AssetIds; +use primitives::{ + evm::{CallInfo, EvmAddress}, + Balance, CurrencyId, +}; +use sp_core::H160; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize}, + DispatchError, DispatchResult, RuntimeDebug, +}; +use sp_std::{ + cmp::{Eq, PartialEq}, + prelude::*, +}; + +/// Return true if the call of EVM precompile contract is allowed. +pub trait PrecompileCallerFilter { + fn is_allowed(caller: H160) -> bool; +} + +/// Return true if the EVM precompile is paused. +pub trait PrecompilePauseFilter { + fn is_paused(address: H160) -> bool; +} + +/// An abstraction of EVM for EVMBridge +pub trait EVM { + type Balance: AtLeast32BitUnsigned + Copy + MaybeSerializeDeserialize + Default; + + fn execute( + context: InvokeContext, + input: Vec, + value: Self::Balance, + gas_limit: u64, + storage_limit: u32, + mode: ExecutionMode, + ) -> Result; + + /// Get the real origin account and charge storage rent from the origin. + fn get_origin() -> Option; + /// Set the EVM origin + fn set_origin(origin: AccountId); + /// Kill the EVM origin + fn kill_origin(); + /// Push new EVM origin in xcm + fn push_xcm_origin(origin: AccountId); + /// Pop EVM origin in xcm + fn pop_xcm_origin(); + /// Kill the EVM origin in xcm + fn kill_xcm_origin(); + /// Get the real origin account or xcm origin and charge storage rent from the origin. + fn get_real_or_xcm_origin() -> Option; +} + +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug)] +pub enum ExecutionMode { + Execute, + /// Discard any state changes + View, + /// Also discard any state changes and use estimate gas mode for evm config + EstimateGas, +} + +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug)] +pub struct InvokeContext { + pub contract: EvmAddress, + /// similar to msg.sender + pub sender: EvmAddress, + /// similar to tx.origin + pub origin: EvmAddress, +} + +/// An abstraction of EVMBridge +pub trait EVMBridge { + /// Execute ERC20.name() to read token name from ERC20 contract + fn name(context: InvokeContext) -> Result, DispatchError>; + /// Execute ERC20.symbol() to read token symbol from ERC20 contract + fn symbol(context: InvokeContext) -> Result, DispatchError>; + /// Execute ERC20.decimals() to read token decimals from ERC20 contract + fn decimals(context: InvokeContext) -> Result; + /// Execute ERC20.totalSupply() to read total supply from ERC20 contract + fn total_supply(context: InvokeContext) -> Result; + /// Execute ERC20.balanceOf(address) to read balance of address from ERC20 + /// contract + fn balance_of(context: InvokeContext, address: EvmAddress) -> Result; + /// Execute ERC20.transfer(address, uint256) to transfer value to `to` + fn transfer(context: InvokeContext, to: EvmAddress, value: Balance) -> DispatchResult; + /// Get the real origin account and charge storage rent from the origin. + fn get_origin() -> Option; + /// Set the EVM origin + fn set_origin(origin: AccountId); + /// Kill the EVM origin + fn kill_origin(); + /// Push new EVM origin in xcm + fn push_xcm_origin(origin: AccountId); + /// Pop EVM origin in xcm + fn pop_xcm_origin(); + /// Kill the EVM origin in xcm + fn kill_xcm_origin(); + /// Get the real origin account or xcm origin and charge storage rent from the origin. + fn get_real_or_xcm_origin() -> Option; +} + +#[cfg(feature = "std")] +impl EVMBridge for () { + fn name(_context: InvokeContext) -> Result, DispatchError> { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn symbol(_context: InvokeContext) -> Result, DispatchError> { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn decimals(_context: InvokeContext) -> Result { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn total_supply(_context: InvokeContext) -> Result { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn balance_of(_context: InvokeContext, _address: EvmAddress) -> Result { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn transfer(_context: InvokeContext, _to: EvmAddress, _value: Balance) -> DispatchResult { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn get_origin() -> Option { + None + } + fn set_origin(_origin: AccountId) {} + fn kill_origin() {} + fn push_xcm_origin(_origin: AccountId) {} + fn pop_xcm_origin() {} + fn kill_xcm_origin() {} + fn get_real_or_xcm_origin() -> Option { + None + } +} + +/// EVM bridge for collateral liquidation. +pub trait LiquidationEvmBridge { + /// Execute liquidation. Sufficient repayment is expected to be transferred to `repay_dest`, + /// if not received or below `min_repayment`, the liquidation would be seen as failed. + fn liquidate( + context: InvokeContext, + collateral: EvmAddress, + repay_dest: EvmAddress, + amount: Balance, + min_repayment: Balance, + ) -> DispatchResult; + /// Called on sufficient repayment received and collateral transferred to liquidation contract. + fn on_collateral_transfer(context: InvokeContext, collateral: EvmAddress, amount: Balance); + /// Called on insufficient repayment received and repayment refunded to liquidation contract. + fn on_repayment_refund(context: InvokeContext, collateral: EvmAddress, repayment: Balance); +} +impl LiquidationEvmBridge for () { + fn liquidate( + _context: InvokeContext, + _collateral: EvmAddress, + _repay_dest: EvmAddress, + _amount: Balance, + _min_repayment: Balance, + ) -> DispatchResult { + Err(DispatchError::Other("unimplemented evm bridge")) + } + fn on_collateral_transfer(_context: InvokeContext, _collateral: EvmAddress, _amount: Balance) {} + fn on_repayment_refund(_context: InvokeContext, _collateral: EvmAddress, _repayment: Balance) {} +} + +/// An abstraction of EVMManager +pub trait EVMManager { + /// Query the constants `NewContractExtraBytes` value from evm module. + fn query_new_contract_extra_bytes() -> u32; + /// Query the constants `StorageDepositPerByte` value from evm module. + fn query_storage_deposit_per_byte() -> Balance; + /// Query the maintainer address from the ERC20 contract. + fn query_maintainer(contract: H160) -> Result; + /// Query the constants `DeveloperDeposit` value from evm module. + fn query_developer_deposit() -> Balance; + /// Query the constants `PublicationFee` value from evm module. + fn query_publication_fee() -> Balance; + /// Transfer the maintainer of the contract address. + fn transfer_maintainer(from: AccountId, contract: H160, new_maintainer: H160) -> DispatchResult; + /// Publish contract + fn publish_contract_precompile(who: AccountId, contract: H160) -> DispatchResult; + /// Query the developer status of an account + fn query_developer_status(who: AccountId) -> bool; + /// Enable developer mode + fn enable_account_contract_development(who: AccountId) -> DispatchResult; + /// Disable developer mode + fn disable_account_contract_development(who: AccountId) -> DispatchResult; +} + +/// An abstraction of EVMAccountsManager +pub trait EVMAccountsManager { + /// Returns the AccountId used to generate the given EvmAddress. + fn get_account_id(address: &EvmAddress) -> AccountId; + /// Returns the EvmAddress associated with a given AccountId or the underlying EvmAddress of the + /// AccountId. + fn get_evm_address(account_id: &AccountId) -> Option; + /// Claim account mapping between AccountId and a generated EvmAddress based off of the + /// AccountId. + fn claim_default_evm_address(account_id: &AccountId) -> Result; +} + +/// A mapping between `AccountId` and `EvmAddress`. +pub trait AddressMapping { + /// Returns the AccountId used go generate the given EvmAddress. + fn get_account_id(evm: &EvmAddress) -> AccountId; + /// Returns the EvmAddress associated with a given AccountId or the + /// underlying EvmAddress of the AccountId. + /// Returns None if there is no EvmAddress associated with the AccountId + /// and there is no underlying EvmAddress in the AccountId. + fn get_evm_address(account_id: &AccountId) -> Option; + /// Returns the EVM address associated with an account ID and generates an + /// account mapping if no association exists. + fn get_or_create_evm_address(account_id: &AccountId) -> EvmAddress; + /// Returns the default EVM address associated with an account ID. + fn get_default_evm_address(account_id: &AccountId) -> EvmAddress; + /// Returns true if a given AccountId is associated with a given EvmAddress + /// and false if is not. + fn is_linked(account_id: &AccountId, evm: &EvmAddress) -> bool; +} + +/// A mapping between AssetId and AssetMetadata. +pub trait AssetIdMapping { + /// Returns the AssetMetadata associated with a given `AssetIds`. + fn get_asset_metadata(asset_ids: AssetIds) -> Option; + /// Returns the MultiLocation associated with a given ForeignAssetId. + fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option; + /// Returns the CurrencyId associated with a given MultiLocation. + fn get_currency_id(multi_location: MultiLocation) -> Option; +} + +/// A mapping between u32 and Erc20 address. +/// provide a way to encode/decode for CurrencyId; +pub trait Erc20InfoMapping { + /// Returns the name associated with a given CurrencyId. + /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + /// the EvmAddress must have been mapped. + fn name(currency_id: CurrencyId) -> Option>; + /// Returns the symbol associated with a given CurrencyId. + /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + /// the EvmAddress must have been mapped. + fn symbol(currency_id: CurrencyId) -> Option>; + /// Returns the decimals associated with a given CurrencyId. + /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, + /// the EvmAddress must have been mapped. + fn decimals(currency_id: CurrencyId) -> Option; + /// Encode the CurrencyId to EvmAddress. + /// If is CurrencyId::DexShare and contain DexShare::Erc20, + /// will use the u32 to get the DexShare::Erc20 from the mapping. + fn encode_evm_address(v: CurrencyId) -> Option; + /// Decode the CurrencyId from EvmAddress. + /// If is CurrencyId::DexShare and contain DexShare::Erc20, + /// will use the u32 to get the DexShare::Erc20 from the mapping. + fn decode_evm_address(v: EvmAddress) -> Option; +} + +#[cfg(feature = "std")] +impl Erc20InfoMapping for () { + fn name(_currency_id: CurrencyId) -> Option> { + None + } + + fn symbol(_currency_id: CurrencyId) -> Option> { + None + } + + fn decimals(_currency_id: CurrencyId) -> Option { + None + } + + fn encode_evm_address(_v: CurrencyId) -> Option { + None + } + + fn decode_evm_address(_v: EvmAddress) -> Option { + None + } +} + +pub mod limits { + pub struct Limit { + pub gas: u64, + pub storage: u32, + } + + impl Limit { + pub const fn new(gas: u64, storage: u32) -> Self { + Self { gas, storage } + } + } + + pub mod erc20 { + use super::*; + + pub const NAME: Limit = Limit::new(100_000, 0); + pub const SYMBOL: Limit = Limit::new(100_000, 0); + pub const DECIMALS: Limit = Limit::new(100_000, 0); + pub const TOTAL_SUPPLY: Limit = Limit::new(100_000, 0); + pub const BALANCE_OF: Limit = Limit::new(100_000, 0); + pub const TRANSFER: Limit = Limit::new(200_000, 960); + } + + pub mod liquidation { + use super::*; + + pub const LIQUIDATE: Limit = Limit::new(200_000, 1_000); + pub const ON_COLLATERAL_TRANSFER: Limit = Limit::new(200_000, 1_000); + pub const ON_REPAYMENT_REFUND: Limit = Limit::new(200_000, 1_000); + } +} diff --git a/blockchain/modules/support/src/incentives.rs b/blockchain/modules/support/src/incentives.rs new file mode 100644 index 000000000..06bf8da5d --- /dev/null +++ b/blockchain/modules/support/src/incentives.rs @@ -0,0 +1,67 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::Rate; +use parity_scale_codec::{Decode, Encode}; +use primitives::CurrencyId; +use scale_info::TypeInfo; +use sp_runtime::{DispatchResult, RuntimeDebug}; +use sp_std::prelude::*; + +/// PoolId for various rewards pools +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum PoolId { + /// Rewards and shares pool for Swap market makers who stake LP token(LPCurrencyId) + SwapDex(CurrencyId), + + /// Rewards and shares pool for Orderbook market makers + OrderBookDex(CurrencyId), +} + +pub trait IncentivesManager { + /// Gets reward amount for the given reward currency added per period + fn get_incentive_reward_amount(pool_id: PoolId, currency_id: CurrencyId) -> Balance; + /// Stake LP token to add shares to pool + fn deposit_swapdex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult; + /// Unstake LP token to remove shares from pool + fn withdraw_swapdex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult; + /// Claim all available rewards for specific `PoolId` + fn claim_rewards(who: AccountId, pool_id: PoolId) -> DispatchResult; + /// Gets deduction reate for claiming reward early + fn get_claim_reward_deduction_rate(pool_id: PoolId) -> Rate; + /// Gets the pending rewards for a pool, for an account + fn get_pending_rewards(pool_id: PoolId, who: AccountId, reward_currency: Vec) -> Vec; +} + +pub trait SwapDexIncentives { + fn do_deposit_swapdex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult; + fn do_withdraw_swapdex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult; +} + +#[cfg(feature = "std")] +impl SwapDexIncentives for () { + fn do_deposit_swapdex_share(_: &AccountId, _: CurrencyId, _: Balance) -> DispatchResult { + Ok(()) + } + + fn do_withdraw_swapdex_share(_: &AccountId, _: CurrencyId, _: Balance) -> DispatchResult { + Ok(()) + } +} diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index f120e31c5..6c592c7c7 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -20,84 +20,184 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::upper_case_acronyms)] +#![allow(clippy::from_over_into)] +#![allow(clippy::type_complexity)] -use codec::{Decode, Encode, FullCodec}; use frame_support::pallet_prelude::{DispatchClass, Pays, Weight}; -use primitives::{ - Balance as AsBalance, - CampaignId, CurrencyId, - evm::{CallInfo, EvmAddress}, - task::TaskResult -}; -use scale_info::TypeInfo; -use sp_core::H160; +use primitives::{task::TaskResult, Balance, CurrencyId, Multiplier, Nonce, ReserveIdentifier}; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedDiv, MaybeSerializeDeserialize}, - transaction_validity::TransactionValidityError, - DispatchError, DispatchResult, FixedU128, RuntimeDebug, -}; -use sp_std::{ - cmp::{Eq, PartialEq}, - fmt::Debug, - prelude::*, + traits::CheckedDiv, transaction_validity::TransactionValidityError, DispatchError, DispatchResult, FixedU128, }; - +use sp_std::{prelude::*, result::Result}; +use xcm::prelude::*; + +pub mod bounded; +pub mod dex; +pub mod evm; +pub mod liquid_staking; +pub mod ecdp; +pub mod incentives; pub mod mocks; +pub use crate::bounded::*; +pub use crate::dex::*; +pub use crate::evm::*; +pub use crate::liquid_staking::*; +pub use crate::ecdp::*; +pub use crate::incentives::*; + pub type Price = FixedU128; pub type ExchangeRate = FixedU128; pub type Ratio = FixedU128; pub type Rate = FixedU128; -pub trait RiskManager { - fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; +/// Implement this StoredMap to replace https://github.com/paritytech/substrate/blob/569aae5341ea0c1d10426fa1ec13a36c0b64393b/frame/system/src/lib.rs#L1679 +/// NOTE: If use module-evm, need regards existed `frame_system::Account` also exists +/// `pallet_balances::Account`, even if it's AccountData is default. (This kind of account is +/// usually created by inc_provider), so that `repatriate_reserved` can transfer reserved balance to +/// contract account, which is created by `inc_provider`. +pub struct SystemAccountStore(sp_std::marker::PhantomData); +impl frame_support::traits::StoredMap for SystemAccountStore { + fn get(k: &T::AccountId) -> T::AccountData { + frame_system::Account::::get(k).data + } + + fn try_mutate_exists>( + k: &T::AccountId, + f: impl FnOnce(&mut Option) -> Result, + ) -> Result { + let account = frame_system::Account::::get(k); + let is_default = account.data == T::AccountData::default(); + + // if System Account exists, act its Balances Account also exists. + let mut some_data = if is_default && !frame_system::Pallet::::account_exists(k) { + None + } else { + Some(account.data) + }; - fn check_position_valid( - currency_id: CurrencyId, - collateral_balance: Balance, - debit_balance: DebitBalance, - check_required_ratio: bool, - ) -> DispatchResult; + let result = f(&mut some_data)?; + if frame_system::Pallet::::providers(k) > 0 || frame_system::Pallet::::sufficients(k) > 0 { + frame_system::Account::::mutate(k, |a| a.data = some_data.unwrap_or_default()); + } else { + frame_system::Account::::remove(k) + } + Ok(result) + } +} + +pub trait PriceProvider { + fn get_price(currency_id: CurrencyId) -> Option; + fn get_relative_price(base: CurrencyId, quote: CurrencyId) -> Option { + if let (Some(base_price), Some(quote_price)) = (Self::get_price(base), Self::get_price(quote)) { + base_price.checked_div("e_price) + } else { + None + } + } +} + +pub trait SwapDexPriceProvider { + fn get_relative_price(base: CurrencyId, quote: CurrencyId) -> Option; +} + +pub trait LockablePrice { + fn lock_price(currency_id: CurrencyId) -> DispatchResult; + fn unlock_price(currency_id: CurrencyId) -> DispatchResult; +} + +pub trait ExchangeRateProvider { + fn get_exchange_rate() -> ExchangeRate; +} + +pub trait TransactionPayment { + fn reserve_fee(who: &AccountId, fee: Balance, named: Option) -> Result; + fn unreserve_fee(who: &AccountId, fee: Balance, named: Option) -> Balance; + fn unreserve_and_charge_fee( + who: &AccountId, + weight: Weight, + ) -> Result<(Balance, NegativeImbalance), TransactionValidityError>; + fn refund_fee(who: &AccountId, weight: Weight, payed: NegativeImbalance) -> Result<(), TransactionValidityError>; + fn charge_fee( + who: &AccountId, + len: u32, + weight: Weight, + tip: Balance, + pays_fee: Pays, + class: DispatchClass, + ) -> Result<(), TransactionValidityError>; + fn weight_to_fee(weight: Weight) -> Balance; + fn apply_multiplier_to_fee(fee: Balance, multiplier: Option) -> Balance; +} + +/// Dispatchable tasks +pub trait DispatchableTask { + fn dispatch(self, weight: Weight) -> TaskResult; +} - fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; +/// Idle scheduler trait +pub trait IdleScheduler { + fn schedule(task: Task) -> Result; + fn dispatch(id: Nonce, weight: Weight) -> Weight; } #[cfg(feature = "std")] -impl RiskManager - for () -{ - fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { - Default::default() +impl DispatchableTask for () { + fn dispatch(self, _weight: Weight) -> TaskResult { + unimplemented!() } +} - fn check_position_valid( - _currency_id: CurrencyId, - _collateral_balance: Balance, - _debit_balance: DebitBalance, - _check_required_ratio: bool, - ) -> DispatchResult { - Ok(()) +#[cfg(feature = "std")] +impl IdleScheduler for () { + fn schedule(_task: Task) -> Result { + unimplemented!() } - - fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { - Ok(()) + fn dispatch(_id: Nonce, _weight: Weight) -> Weight { + unimplemented!() } } -pub trait AuctionManager { - type CurrencyId; - type Balance; - type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; +#[impl_trait_for_tuples::impl_for_tuples(30)] +pub trait OnNewEra { + fn on_new_era(era: EraIndex); +} + +pub trait NomineesProvider { + fn nominees() -> Vec; +} - fn new_collateral_auction( - refund_recipient: &AccountId, - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, +pub trait LiquidateCollateral { + fn liquidate( + who: &AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, ) -> DispatchResult; - fn cancel_auction(id: Self::AuctionId) -> DispatchResult; - fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; - fn get_total_target_in_auction() -> Self::Balance; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl LiquidateCollateral for Tuple { + fn liquidate( + who: &AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, + ) -> DispatchResult { + let mut last_error = None; + for_tuples!( #( + match Tuple::liquidate(who, currency_id, amount, target_stable_amount) { + Ok(_) => return Ok(()), + Err(e) => { last_error = Some(e) } + } + )* ); + let last_error = last_error.unwrap_or(DispatchError::Other("No liquidation impl.")); + Err(last_error) + } +} + +pub trait BuyWeightRate { + fn calculate_rate(location: MultiLocation) -> Option; } /// The Structure of a Campaign info. @@ -235,586 +335,3 @@ pub trait CampaignManager { /// Get the total amounts raised in protocol fn get_total_amounts_raised() -> Vec<(CurrencyId, AsBalance)>; } - -#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, TypeInfo)] -pub enum SwapLimit { - /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) - ExactSupply(Balance, Balance), - /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) - ExactTarget(Balance, Balance), -} - -// #[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, TypeInfo)] -// pub enum SerpingStatus { -// /// Enable/Activate serping of setcurrencies (period). -// Active(BlockNumber), -// /// Disable/Deactivate serping of setcurrencies. -// Inactive, -// } - -pub trait DEXManager { - fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); - - fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; - - fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; - - fn get_best_price_swap_path( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - alternative_path_joint_list: Vec>, - ) -> Option>; - - fn swap_with_specific_path( - who: &AccountId, - path: &[CurrencyId], - limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError>; - - fn buyback_swap_with_specific_path( - who: &AccountId, - path: &[CurrencyId], - limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError>; - - fn swap_with_exact_target( - who: &AccountId, - path: &[CurrencyId], - exact_target_amount: Balance, - max_supply_amount: Balance, - ) -> DispatchResult; - - fn add_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, - min_share_increment: Balance, - ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError>; - - fn remove_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - remove_share: Balance, - min_withdrawn_a: Balance, - min_withdrawn_b: Balance, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError>; -} - -impl DEXManager for () -where - Balance: Default, -{ - fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { - Default::default() - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - Some(Default::default()) - } - - fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { - Some(Default::default()) - } - - fn get_best_price_swap_path( - _supply_currency_id: CurrencyId, - _target_currency_id: CurrencyId, - _limit: SwapLimit, - _alternative_path_joint_list: Vec>, - ) -> Option> { - Some(Default::default()) - } - - fn swap_with_specific_path( - _who: &AccountId, - _path: &[CurrencyId], - _limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn buyback_swap_with_specific_path( - _who: &AccountId, - _path: &[CurrencyId], - _limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn swap_with_exact_target( - _who: &AccountId, - _path: &[CurrencyId], - _exact_target_amount: Balance, - _max_supply_amount: Balance, - ) -> DispatchResult { - Ok(Default::default()) - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } -} - -/// An abstraction of serp treasury for the SERP (Setheum Elastic Reserve Protocol). -pub trait SerpTreasury { - type Balance; - type CurrencyId; - - fn calculate_supply_change(numerator: Self::Balance, denominator: Self::Balance, supply: Self::Balance) -> Self::Balance; - - fn serp_tes_now() -> DispatchResult; - - /// Deliver System StableCurrency Inflation - fn issue_stablecurrency_inflation() -> DispatchResult; - - /// SerpUp ratio for BuyBack Swaps to burn Dinar or Setter - fn get_buyback_serpup(amount: Self::Balance, currency_id: Self::CurrencyId) -> DispatchResult; - - /// Add CashDrop to the pool - fn add_cashdrop_to_pool(currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// Issue CashDrop from the pool to the claimant account - fn issue_cashdrop_from_pool( - claimant_id: &AccountId, - currency_id: Self::CurrencyId, - amount: Self::Balance - ) -> DispatchResult; - - /// SerpUp ratio for SetPay Cashdrops - fn get_cashdrop_serpup(amount: Self::Balance, currency_id: Self::CurrencyId) -> DispatchResult; - - /// Serplus ratio for BuyBack Swaps to burn Setter - fn get_buyback_serplus(amount: Self::Balance, currency_id: Self::CurrencyId) -> DispatchResult; - - fn get_cashdrop_serplus(amount: Self::Balance, currency_id: Self::CurrencyId) -> DispatchResult; - - /// issue system surplus(stable currencies) to their destinations according to the serpup_ratio. - fn on_serplus(currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// issue serpup surplus(stable currencies) to their destinations according to the serpup_ratio. - fn on_serpup(currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// buy back and burn surplus(stable currencies) with swap by DEX. - fn on_serpdown(currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// get the minimum supply of a setcurrency - by key - fn get_minimum_supply(currency_id: Self::CurrencyId) -> Self::Balance; - - /// issue standard to `who` - fn issue_standard(currency_id: Self::CurrencyId, who: &AccountId, standard: Self::Balance) -> DispatchResult; - - /// burn standard(stable currency) of `who` - fn burn_standard(currency_id: Self::CurrencyId, who: &AccountId, standard: Self::Balance) -> DispatchResult; - - /// issue setter of amount setter to `who` - fn issue_setter(who: &AccountId, setter: Self::Balance) -> DispatchResult; - - /// burn setter of `who` - fn burn_setter(who: &AccountId, setter: Self::Balance) -> DispatchResult; - - /// deposit reserve asset (Setter (SETR)) to serp treasury by `who` - fn deposit_setter(from: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// claim cashdrop of `currency_id` relative to `transfer_amount` for `who` - fn claim_cashdrop(currency_id: Self::CurrencyId, who: &AccountId, transfer_amount: Self::Balance) -> DispatchResult; -} - -pub trait SerpTreasuryExtended: SerpTreasury { - /// When SetCurrency needs SerpDown - fn buyback_swap_with_exact_supply( - from_currency_id: Self::CurrencyId, - to_currency_id: Self::CurrencyId, - swap_limit: SwapLimit, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - /// When SetCurrency needs SerpDown - fn buyback_swap_with_exact_target( - from_currency_id: Self::CurrencyId, - to_currency_id: Self::CurrencyId, - swap_limit: SwapLimit, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; -} - -/// An abstraction of cdp treasury for Setmint Protocol. -pub trait CDPTreasury { - type Balance; - type CurrencyId; - - /// get surplus amount of cdp treasury - fn get_surplus_pool() -> Self::Balance; - - /// get debit amount of cdp treasury - fn get_debit_pool() -> Self::Balance; - - /// get collateral assets amount of cdp treasury - fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; - - /// calculate the proportion of specific debit amount for the whole system - fn get_debit_proportion(amount: Self::Balance) -> Ratio; - - /// issue debit for cdp treasury - fn on_system_debit(amount: Self::Balance) -> DispatchResult; - - /// issue surplus(stable currency) for cdp treasury - fn on_system_surplus(amount: Self::Balance) -> DispatchResult; - - /// issue debit to `who` - /// if backed flag is true, means the debit to issue is backed on some - /// assets, otherwise will increase same amount of debit to system debit. - fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; - - /// burn debit(stable currency) of `who` - fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; - - /// deposit surplus(stable currency) to cdp treasury by `from` - fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; - - /// deposit collateral assets to cdp treasury by `who` - fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// withdraw collateral assets of cdp treasury to `who` - fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; -} - -pub trait CDPTreasuryExtended: CDPTreasury { - fn swap_collateral_to_stable( - currency_id: Self::CurrencyId, - limit: SwapLimit, - collateral_in_auction: bool, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn create_collateral_auctions( - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, - refund_receiver: AccountId, - splited: bool, - ) -> DispatchResult; - - fn remove_liquidity_for_lp_collateral( - currency_id: Self::CurrencyId, - amount: Self::Balance, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn max_auction() -> u32; -} - -pub trait PriceProvider { - fn get_price(currency_id: CurrencyId) -> Option; - fn get_relative_price(base: CurrencyId, quote: CurrencyId) -> Option { - if let (Some(base_price), Some(quote_price)) = (Self::get_price(base), Self::get_price(quote)) { - base_price.checked_div("e_price) - } else { - None - } - } -} - -pub trait DEXPriceProvider { - fn get_relative_price(base: CurrencyId, quote: CurrencyId) -> Option; -} - -pub trait LockablePrice { - fn lock_price(currency_id: CurrencyId) -> DispatchResult; - fn unlock_price(currency_id: CurrencyId) -> DispatchResult; -} - -pub trait ExchangeRateProvider { - fn get_exchange_rate() -> ExchangeRate; -} - -/// Dispatchable tasks -pub trait DispatchableTask { - fn dispatch(self, weight: Weight) -> TaskResult; -} - -/// Idle scheduler trait -pub trait IdleScheduler { - fn schedule(task: Task) -> DispatchResult; -} - -#[cfg(feature = "std")] -impl DispatchableTask for () { - fn dispatch(self, _weight: Weight) -> TaskResult { - unimplemented!() - } -} - -#[cfg(feature = "std")] -impl IdleScheduler for () { - fn schedule(_task: Task) -> DispatchResult { - unimplemented!() - } -} - -pub trait EmergencyShutdown { - fn is_shutdown() -> bool; -} - -/// Return true if the call of EVM precompile contract is allowed. -pub trait PrecompileCallerFilter { - fn is_allowed(caller: H160) -> bool; -} - -/// An abstraction of EVM for EVMBridge -pub trait EVM { - type Balance: AtLeast32BitUnsigned + Copy + MaybeSerializeDeserialize + Default; - - fn execute( - context: InvokeContext, - input: Vec, - value: Self::Balance, - gas_limit: u64, - storage_limit: u32, - mode: ExecutionMode, - ) -> Result; - - /// Get the real origin account and charge storage rent from the origin. - fn get_origin() -> Option; - /// Provide a method to set origin for `on_initialize` - fn set_origin(origin: AccountId); -} - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug)] -pub enum ExecutionMode { - Execute, - /// Discard any state changes - View, - /// Also discard any state changes and use estimate gas mode for evm config - EstimateGas, -} - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug)] -pub struct InvokeContext { - pub contract: EvmAddress, - /// similar to msg.sender - pub sender: EvmAddress, - /// similar to tx.origin - pub origin: EvmAddress, -} - -/// An abstraction of EVMBridge -pub trait EVMBridge { - /// Execute ERC20.name() to read token name from ERC20 contract - fn name(context: InvokeContext) -> Result, DispatchError>; - /// Execute ERC20.symbol() to read token symbol from ERC20 contract - fn symbol(context: InvokeContext) -> Result, DispatchError>; - /// Execute ERC20.decimals() to read token decimals from ERC20 contract - fn decimals(context: InvokeContext) -> Result; - /// Execute ERC20.totalSupply() to read total supply from ERC20 contract - fn total_supply(context: InvokeContext) -> Result; - /// Execute ERC20.balanceOf(address) to read balance of address from ERC20 - /// contract - fn balance_of(context: InvokeContext, address: EvmAddress) -> Result; - /// Execute ERC20.transfer(address, uint256) to transfer value to `to` - fn transfer(context: InvokeContext, to: EvmAddress, value: Balance) -> DispatchResult; - /// Get the real origin account and charge storage rent from the origin. - fn get_origin() -> Option; - /// Provide a method to set origin for `on_initialize` - fn set_origin(origin: AccountId); -} - -#[cfg(feature = "std")] -impl EVMBridge for () { - fn name(_context: InvokeContext) -> Result, DispatchError> { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn symbol(_context: InvokeContext) -> Result, DispatchError> { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn decimals(_context: InvokeContext) -> Result { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn total_supply(_context: InvokeContext) -> Result { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn balance_of(_context: InvokeContext, _address: EvmAddress) -> Result { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn transfer(_context: InvokeContext, _to: EvmAddress, _value: Balance) -> DispatchResult { - Err(DispatchError::Other("unimplemented evm bridge")) - } - fn get_origin() -> Option { - None - } - fn set_origin(_origin: AccountId) {} -} - -/// An abstraction of EVMStateRentTrait -pub trait EVMStateRentTrait { - /// Query the constants `NewContractExtraBytes` value from evm module. - fn query_new_contract_extra_bytes() -> u32; - /// Query the constants `StorageDepositPerByte` value from evm module. - fn query_storage_deposit_per_byte() -> Balance; - /// Query the maintainer address from the ERC20 contract. - fn query_maintainer(contract: H160) -> Result; - /// Query the constants `DeveloperDeposit` value from evm module. - fn query_developer_deposit() -> Balance; - /// Query the constants `DeploymentFee` value from evm module. - fn query_deployment_fee() -> Balance; - /// Transfer the maintainer of the contract address. - fn transfer_maintainer(from: AccountId, contract: H160, new_maintainer: H160) -> DispatchResult; -} - -pub trait TransactionPayment { - fn reserve_fee(who: &AccountId, weight: Weight) -> Result; - fn unreserve_fee(who: &AccountId, fee: Balance); - fn unreserve_and_charge_fee( - who: &AccountId, - weight: Weight, - ) -> Result<(Balance, NegativeImbalance), TransactionValidityError>; - fn refund_fee(who: &AccountId, weight: Weight, payed: NegativeImbalance) -> Result<(), TransactionValidityError>; - fn charge_fee( - who: &AccountId, - len: u32, - weight: Weight, - tip: Balance, - pays_fee: Pays, - class: DispatchClass, - ) -> Result<(), TransactionValidityError>; -} - -#[cfg(feature = "std")] -use frame_support::traits::Imbalance; -#[cfg(feature = "std")] -impl> - TransactionPayment for () -{ - fn reserve_fee(_who: &AccountId, _weight: Weight) -> Result { - Ok(Default::default()) - } - - fn unreserve_fee(_who: &AccountId, _fee: Balance) {} - - fn unreserve_and_charge_fee( - _who: &AccountId, - _weight: Weight, - ) -> Result<(Balance, NegativeImbalance), TransactionValidityError> { - Ok((Default::default(), Imbalance::zero())) - } - - fn refund_fee( - _who: &AccountId, - _weight: Weight, - _payed: NegativeImbalance, - ) -> Result<(), TransactionValidityError> { - Ok(()) - } - - fn charge_fee( - _who: &AccountId, - _len: u32, - _weight: Weight, - _tip: Balance, - _pays_fee: Pays, - _class: DispatchClass, - ) -> Result<(), TransactionValidityError> { - Ok(()) - } -} - -pub trait Contains { - fn contains(t: &T) -> bool; -} - -/// A mapping between `AccountId` and `EvmAddress`. -pub trait AddressMapping { - /// Returns the AccountId used go generate the given EvmAddress. - fn get_account_id(evm: &EvmAddress) -> AccountId; - /// Returns the EvmAddress associated with a given AccountId or the - /// underlying EvmAddress of the AccountId. - /// Returns None if there is no EvmAddress associated with the AccountId - /// and there is no underlying EvmAddress in the AccountId. - fn get_evm_address(account_id: &AccountId) -> Option; - /// Returns the EVM address associated with an account ID and generates an - /// account mapping if no association exists. - fn get_or_create_evm_address(account_id: &AccountId) -> EvmAddress; - /// Returns the default EVM address associated with an account ID. - fn get_default_evm_address(account_id: &AccountId) -> EvmAddress; - /// Returns true if a given AccountId is associated with a given EvmAddress - /// and false if is not. - fn is_linked(account_id: &AccountId, evm: &EvmAddress) -> bool; -} - -/// A mapping between u32 and Erc20 address. -/// provide a way to encode/decode for CurrencyId; -pub trait CurrencyIdMapping { - /// Use first 4 non-zero bytes as u32 to the mapping between u32 and evm - /// address. - fn set_erc20_mapping(address: EvmAddress) -> DispatchResult; - /// Returns the EvmAddress associated with a given u32. - fn get_evm_address(currency_id: u32) -> Option; - /// Returns the name associated with a given CurrencyId. - /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - /// the EvmAddress must have been mapped. - fn name(currency_id: CurrencyId) -> Option>; - /// Returns the symbol associated with a given CurrencyId. - /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - /// the EvmAddress must have been mapped. - fn symbol(currency_id: CurrencyId) -> Option>; - /// Returns the decimals associated with a given CurrencyId. - /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, - /// the EvmAddress must have been mapped. - fn decimals(currency_id: CurrencyId) -> Option; - /// Encode the CurrencyId to EvmAddress. - /// If is CurrencyId::DexShare and contain DexShare::Erc20, - /// will use the u32 to get the DexShare::Erc20 from the mapping. - fn encode_evm_address(v: CurrencyId) -> Option; - /// Decode the CurrencyId from EvmAddress. - /// If is CurrencyId::DexShare and contain DexShare::Erc20, - /// will use the u32 to get the DexShare::Erc20 from the mapping. - fn decode_evm_address(v: EvmAddress) -> Option; -} - -#[cfg(feature = "std")] -impl CurrencyIdMapping for () { - fn set_erc20_mapping(_address: EvmAddress) -> DispatchResult { - Err(DispatchError::Other("unimplemented CurrencyIdMapping")) - } - - fn get_evm_address(_currency_id: u32) -> Option { - None - } - - fn name(_currency_id: CurrencyId) -> Option> { - None - } - - fn symbol(_currency_id: CurrencyId) -> Option> { - None - } - - fn decimals(_currency_id: CurrencyId) -> Option { - None - } - - fn encode_evm_address(_v: CurrencyId) -> Option { - None - } - - fn decode_evm_address(_v: EvmAddress) -> Option { - None - } -} diff --git a/blockchain/modules/support/src/liquid_staking.rs b/blockchain/modules/support/src/liquid_staking.rs new file mode 100644 index 000000000..2054417bd --- /dev/null +++ b/blockchain/modules/support/src/liquid_staking.rs @@ -0,0 +1,38 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ExchangeRate, Rate}; +use sp_runtime::DispatchResult; +use xcm::v3::prelude::*; + +pub trait LiquidStakingManager { + /// Mint liquid currency by locking up staking currency + fn mint(who: AccountId, amount: Balance) -> DispatchResult; + /// Request for protocol to redeem liquid currency for staking currency + fn request_redeem(who: AccountId, amount: Balance, fast_match: bool) -> DispatchResult; + /// Calculates current exchange rate between staking and liquid currencies (staking : liquid) + fn get_exchange_rate() -> ExchangeRate; + /// Estimated return rate per era from liquid staking + fn get_estimated_reward_rate() -> Rate; + /// Gets commission rate of the Liquid Staking protocol + fn get_commission_rate() -> Rate; + /// Fee for fast matching redeem request + fn get_fast_match_fee() -> Rate; +} diff --git a/blockchain/modules/support/src/mocks.rs b/blockchain/modules/support/src/mocks.rs index a099fe557..4b2dc5365 100644 --- a/blockchain/modules/support/src/mocks.rs +++ b/blockchain/modules/support/src/mocks.rs @@ -18,16 +18,22 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{AddressMapping, CurrencyId, CurrencyIdMapping}; -use codec::Encode; -use frame_support::pallet_prelude::DispatchResult; -use primitives::{currency::TokenInfo, evm::EvmAddress, H160_POSITION_TOKEN, H160_PREFIX_TOKEN}; +#![allow(clippy::type_complexity)] +use crate::{AddressMapping, CurrencyId, Erc20InfoMapping, TransactionPayment}; +use frame_support::pallet_prelude::{DispatchClass, Pays, Weight}; +use parity_scale_codec::Encode; +use primitives::{ + currency::TokenInfo, + evm::{EvmAddress, H160_POSITION_TOKEN}, + Multiplier, ReserveIdentifier, +}; use sp_core::{crypto::AccountId32, H160}; use sp_io::hashing::blake2_256; -use sp_std::{ - convert::{TryFrom, TryInto}, - vec::Vec, -}; +use sp_runtime::{transaction_validity::TransactionValidityError, DispatchError, DispatchResult}; +use sp_std::{marker::PhantomData, vec::Vec}; + +#[cfg(feature = "std")] +use frame_support::traits::Imbalance; pub struct MockAddressMapping; @@ -41,7 +47,7 @@ impl AddressMapping for MockAddressMapping { fn get_evm_address(account_id: &AccountId32) -> Option { let data: [u8; 32] = account_id.clone().into(); - if data.starts_with(b"evm:") { + if data.starts_with(b"evm:") && data.ends_with(&[0u8; 8]) { Some(H160::from_slice(&data[4..24])) } else { None @@ -65,17 +71,9 @@ impl AddressMapping for MockAddressMapping { } } -pub struct MockCurrencyIdMapping; - -impl CurrencyIdMapping for MockCurrencyIdMapping { - fn set_erc20_mapping(_address: EvmAddress) -> DispatchResult { - Ok(()) - } - - fn get_evm_address(_currency_id: u32) -> Option { - Some(EvmAddress::default()) - } +pub struct MockErc20InfoMapping; +impl Erc20InfoMapping for MockErc20InfoMapping { fn name(currency_id: CurrencyId) -> Option> { currency_id.name().map(|v| v.as_bytes().to_vec()) } @@ -93,11 +91,122 @@ impl CurrencyIdMapping for MockCurrencyIdMapping { } fn decode_evm_address(v: EvmAddress) -> Option { - let address = v.as_bytes(); - if address.starts_with(&H160_PREFIX_TOKEN) { - address[H160_POSITION_TOKEN].try_into().map(CurrencyId::Token).ok() - } else { - None - } + let token = v.as_bytes()[H160_POSITION_TOKEN] + .try_into() + .map(CurrencyId::Token) + .ok()?; + EvmAddress::try_from(token) + .map(|addr| if addr == v { Some(token) } else { None }) + .ok()? + } +} + +#[cfg(feature = "std")] +impl> + TransactionPayment for () +{ + fn reserve_fee( + _who: &AccountId, + _fee: Balance, + _named: Option, + ) -> Result { + Ok(Default::default()) + } + + fn unreserve_fee(_who: &AccountId, _fee: Balance, _named: Option) -> Balance { + Default::default() + } + + fn unreserve_and_charge_fee( + _who: &AccountId, + _weight: Weight, + ) -> Result<(Balance, NegativeImbalance), TransactionValidityError> { + Ok((Default::default(), Imbalance::zero())) + } + + fn refund_fee( + _who: &AccountId, + _weight: Weight, + _payed: NegativeImbalance, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn charge_fee( + _who: &AccountId, + _len: u32, + _weight: Weight, + _tip: Balance, + _pays_fee: Pays, + _class: DispatchClass, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn weight_to_fee(_weight: Weight) -> Balance { + Default::default() + } + + fn apply_multiplier_to_fee(_fee: Balance, _multiplier: Option) -> Balance { + Default::default() + } +} + +/// Given provided `Currency`, implements default reserve behavior +pub struct MockReservedTransactionPayment(PhantomData); + +#[cfg(feature = "std")] +impl< + AccountId, + Balance: Default + Copy, + NegativeImbalance: Imbalance, + Currency: frame_support::traits::NamedReservableCurrency< + AccountId, + ReserveIdentifier = ReserveIdentifier, + Balance = Balance, + >, + > TransactionPayment for MockReservedTransactionPayment +{ + fn reserve_fee(who: &AccountId, fee: Balance, named: Option) -> Result { + Currency::reserve_named(&named.unwrap(), who, fee)?; + Ok(fee) + } + + fn unreserve_fee(who: &AccountId, fee: Balance, named: Option) -> Balance { + Currency::unreserve_named(&named.unwrap(), who, fee) + } + + fn unreserve_and_charge_fee( + _who: &AccountId, + _weight: Weight, + ) -> Result<(Balance, NegativeImbalance), TransactionValidityError> { + Ok((Default::default(), Imbalance::zero())) + } + + fn refund_fee( + _who: &AccountId, + _weight: Weight, + _payed: NegativeImbalance, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn charge_fee( + _who: &AccountId, + _len: u32, + _weight: Weight, + _tip: Balance, + _pays_fee: Pays, + _class: DispatchClass, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn weight_to_fee(_weight: Weight) -> Balance { + Default::default() + } + + fn apply_multiplier_to_fee(_fee: Balance, _multiplier: Option) -> Balance { + Default::default() } } diff --git a/blockchain/modules/transaction-pause/Cargo.toml b/blockchain/modules/transaction-pause/Cargo.toml index ceb9eacff..ad22f4760 100644 --- a/blockchain/modules/transaction-pause/Cargo.toml +++ b/blockchain/modules/transaction-pause/Cargo.toml @@ -1,40 +1,42 @@ -[package] -name = "module-transaction-pause" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io= { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } - -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-tokens = { path = "../submodules/orml/tokens" } -smallvec = "1.4.1" - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "sp-io/std", - "sp-std/std", - "primitives/std", - "support/std", - "orml-traits/std", -] +[package] +name = "module-transaction-pause" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +log = { workspace = true } +hex-literal = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +module-support = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +orml-traits = { workspace = true, features = ["std"] } +primitives = { workspace = true, features = ["std"] } +smallvec = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "module-support/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/transaction-pause/src/lib.rs b/blockchain/modules/transaction-pause/src/lib.rs index 4773e2f43..824c62704 100644 --- a/blockchain/modules/transaction-pause/src/lib.rs +++ b/blockchain/modules/transaction-pause/src/lib.rs @@ -22,12 +22,11 @@ #![allow(clippy::unused_unit)] use frame_support::{ - dispatch::{CallMetadata, GetCallMetadata}, pallet_prelude::*, - traits::{Contains, PalletInfoAccess}, - transactional, + traits::{CallMetadata, Contains, GetCallMetadata, PalletInfoAccess}, }; use frame_system::pallet_prelude::*; +use sp_core::H160; use sp_runtime::DispatchResult; use sp_std::{prelude::*, vec::Vec}; @@ -44,10 +43,10 @@ pub mod module { #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The origin which may set filter. - type UpdateOrigin: EnsureOrigin; + type UpdateOrigin: EnsureOrigin; /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; @@ -64,10 +63,20 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Paused transaction . \[pallet_name_bytes, function_name_bytes\] - TransactionPaused(Vec, Vec), - /// Unpaused transaction . \[pallet_name_bytes, function_name_bytes\] - TransactionUnpaused(Vec, Vec), + /// Paused transaction + TransactionPaused { + pallet_name_bytes: Vec, + function_name_bytes: Vec, + }, + /// Unpaused transaction + TransactionUnpaused { + pallet_name_bytes: Vec, + function_name_bytes: Vec, + }, + /// Paused EVM precompile + EvmPrecompilePaused { address: H160 }, + /// Unpaused EVM precompile + EvmPrecompileUnpaused { address: H160 }, } /// The paused transaction map @@ -77,16 +86,24 @@ pub mod module { #[pallet::getter(fn paused_transactions)] pub type PausedTransactions = StorageMap<_, Twox64Concat, (Vec, Vec), (), OptionQuery>; + /// The paused EVM precompile map + /// + /// map (PrecompileAddress) => Option<()> + #[pallet::storage] + #[pallet::getter(fn paused_evm_precompiles)] + pub type PausedEvmPrecompiles = StorageMap<_, Blake2_128Concat, H160, (), OptionQuery>; + #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks> for Pallet {} #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::pause_transaction())] - #[transactional] pub fn pause_transaction(origin: OriginFor, pallet_name: Vec, function_name: Vec) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; @@ -100,14 +117,17 @@ pub mod module { PausedTransactions::::mutate_exists((pallet_name.clone(), function_name.clone()), |maybe_paused| { if maybe_paused.is_none() { *maybe_paused = Some(()); - Self::deposit_event(Event::TransactionPaused(pallet_name, function_name)); + Self::deposit_event(Event::TransactionPaused { + pallet_name_bytes: pallet_name, + function_name_bytes: function_name, + }); } }); Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::unpause_transaction())] - #[transactional] pub fn unpause_transaction( origin: OriginFor, pallet_name: Vec, @@ -115,7 +135,33 @@ pub mod module { ) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; if PausedTransactions::::take((&pallet_name, &function_name)).is_some() { - Self::deposit_event(Event::TransactionUnpaused(pallet_name, function_name)); + Self::deposit_event(Event::TransactionUnpaused { + pallet_name_bytes: pallet_name, + function_name_bytes: function_name, + }); + }; + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::pause_evm_precompile())] + pub fn pause_evm_precompile(origin: OriginFor, address: H160) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + PausedEvmPrecompiles::::mutate_exists(address, |maybe_paused| { + if maybe_paused.is_none() { + *maybe_paused = Some(()); + Self::deposit_event(Event::EvmPrecompilePaused { address }); + } + }); + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::unpause_evm_precompile())] + pub fn unpause_evm_precompile(origin: OriginFor, address: H160) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + if PausedEvmPrecompiles::::take(address).is_some() { + Self::deposit_event(Event::EvmPrecompileUnpaused { address }); }; Ok(()) } @@ -123,11 +169,11 @@ pub mod module { } pub struct PausedTransactionFilter(sp_std::marker::PhantomData); -impl Contains for PausedTransactionFilter +impl Contains for PausedTransactionFilter where - ::Call: GetCallMetadata, + ::RuntimeCall: GetCallMetadata, { - fn contains(call: &T::Call) -> bool { + fn contains(call: &T::RuntimeCall) -> bool { let CallMetadata { function_name, pallet_name, @@ -135,3 +181,10 @@ where PausedTransactions::::contains_key((pallet_name.as_bytes(), function_name.as_bytes())) } } + +pub struct PausedPrecompileFilter(sp_std::marker::PhantomData); +impl module_support::PrecompilePauseFilter for PausedPrecompileFilter { + fn is_paused(address: H160) -> bool { + PausedEvmPrecompiles::::contains_key(address) + } +} diff --git a/blockchain/modules/transaction-pause/src/mock.rs b/blockchain/modules/transaction-pause/src/mock.rs index 9f7281a9f..da54c04bb 100644 --- a/blockchain/modules/transaction-pause/src/mock.rs +++ b/blockchain/modules/transaction-pause/src/mock.rs @@ -23,37 +23,35 @@ #![cfg(test)] use super::*; -use frame_support::{construct_runtime, ord_parameter_types, parameter_types}; +use frame_support::{ + construct_runtime, ord_parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, +}; use frame_system::EnsureSignedBy; use orml_traits::parameter_type_with_key; use primitives::{Amount, Balance, CurrencyId, TokenSymbol}; use sp_core::H256; -use sp_runtime::{testing::Header, traits::IdentityLookup}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; pub type AccountId = u128; pub const ALICE: AccountId = 1; -pub const SETUSD: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); mod transaction_pause { pub use super::super::*; } -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - impl frame_system::Config for Runtime { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type BlockWeights = (); type BlockLength = (); type Version = (); @@ -62,27 +60,28 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = (); + type BaseCallFilter = Everything; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const NativeTokenExistentialDeposit: Balance = 10; - pub const MaxReserves: u32 = 50; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Runtime { type Balance = Balance; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = NativeTokenExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<10>; type AccountStore = System; type MaxLocks = (); - type MaxReserves = MaxReserves; + type MaxReserves = ConstU32<50>; type ReserveIdentifier = (); type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); } parameter_type_with_key! { @@ -92,15 +91,17 @@ parameter_type_with_key! { } impl orml_tokens::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Balance = Balance; type Amount = Amount; type CurrencyId = CurrencyId; type WeightInfo = (); type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); + type CurrencyHooks = (); type MaxLocks = (); - type DustRemovalWhitelist = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; } ord_parameter_types! { @@ -108,24 +109,19 @@ ord_parameter_types! { } impl Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type UpdateOrigin = EnsureSignedBy; type WeightInfo = (); } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TransactionPause: transaction_pause::{Pallet, Storage, Call, Event}, - Balances: pallet_balances::{Pallet, Storage, Call, Event}, - Tokens: orml_tokens::{Pallet, Storage, Call, Event}, + pub enum Runtime { + System: frame_system, + TransactionPause: transaction_pause, + Balances: pallet_balances, + Tokens: orml_tokens, } ); @@ -139,8 +135,8 @@ impl Default for ExtBuilder { impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() + let t = frame_system::GenesisConfig::::default() + .build_storage() .unwrap(); t.into() diff --git a/blockchain/modules/transaction-pause/src/tests.rs b/blockchain/modules/transaction-pause/src/tests.rs index ed0b32e92..195e6ccb3 100644 --- a/blockchain/modules/transaction-pause/src/tests.rs +++ b/blockchain/modules/transaction-pause/src/tests.rs @@ -24,13 +24,17 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use mock::{Event, *}; +use mock::{RuntimeEvent, *}; use sp_runtime::traits::BadOrigin; -const BALANCE_TRANSFER: &::Call = - &mock::Call::Balances(pallet_balances::Call::transfer(ALICE, 10)); -const TOKENS_TRANSFER: &::Call = - &mock::Call::Tokens(orml_tokens::Call::transfer(ALICE, SETUSD, 10)); +const BALANCE_TRANSFER: &::RuntimeCall = + &mock::RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: ALICE, value: 10 }); +const TOKENS_TRANSFER: &::RuntimeCall = + &mock::RuntimeCall::Tokens(orml_tokens::Call::transfer { + dest: ALICE, + currency_id: USSD, + amount: 10, + }); #[test] fn pause_transaction_work() { @@ -38,7 +42,7 @@ fn pause_transaction_work() { System::set_block_number(1); assert_noop!( - TransactionPause::pause_transaction(Origin::signed(5), b"Balances".to_vec(), b"transfer".to_vec()), + TransactionPause::pause_transaction(RuntimeOrigin::signed(5), b"Balances".to_vec(), b"transfer".to_vec()), BadOrigin ); @@ -47,14 +51,14 @@ fn pause_transaction_work() { None ); assert_ok!(TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Balances".to_vec(), b"transfer".to_vec() )); - System::assert_last_event(Event::TransactionPause(crate::Event::TransactionPaused( - b"Balances".to_vec(), - b"transfer".to_vec(), - ))); + System::assert_last_event(RuntimeEvent::TransactionPause(crate::Event::TransactionPaused { + pallet_name_bytes: b"Balances".to_vec(), + function_name_bytes: b"transfer".to_vec(), + })); assert_eq!( TransactionPause::paused_transactions((b"Balances".to_vec(), b"transfer".to_vec())), Some(()) @@ -62,7 +66,7 @@ fn pause_transaction_work() { assert_noop!( TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"TransactionPause".to_vec(), b"pause_transaction".to_vec() ), @@ -70,14 +74,14 @@ fn pause_transaction_work() { ); assert_noop!( TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"TransactionPause".to_vec(), b"some_other_call".to_vec() ), Error::::CannotPause ); assert_ok!(TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"OtherPallet".to_vec(), b"pause_transaction".to_vec() )); @@ -90,7 +94,7 @@ fn unpause_transaction_work() { System::set_block_number(1); assert_ok!(TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Balances".to_vec(), b"transfer".to_vec() )); @@ -100,19 +104,19 @@ fn unpause_transaction_work() { ); assert_noop!( - TransactionPause::unpause_transaction(Origin::signed(5), b"Balances".to_vec(), b"transfer".to_vec()), + TransactionPause::unpause_transaction(RuntimeOrigin::signed(5), b"Balances".to_vec(), b"transfer".to_vec()), BadOrigin ); assert_ok!(TransactionPause::unpause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Balances".to_vec(), b"transfer".to_vec() )); - System::assert_last_event(Event::TransactionPause(crate::Event::TransactionUnpaused( - b"Balances".to_vec(), - b"transfer".to_vec(), - ))); + System::assert_last_event(RuntimeEvent::TransactionPause(crate::Event::TransactionUnpaused { + pallet_name_bytes: b"Balances".to_vec(), + function_name_bytes: b"transfer".to_vec(), + })); assert_eq!( TransactionPause::paused_transactions((b"Balances".to_vec(), b"transfer".to_vec())), None @@ -126,24 +130,24 @@ fn paused_transaction_filter_work() { assert!(!PausedTransactionFilter::::contains(BALANCE_TRANSFER)); assert!(!PausedTransactionFilter::::contains(TOKENS_TRANSFER)); assert_ok!(TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Balances".to_vec(), - b"transfer".to_vec() + b"transfer_allow_death".to_vec() )); assert_ok!(TransactionPause::pause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Tokens".to_vec(), b"transfer".to_vec() )); assert!(PausedTransactionFilter::::contains(BALANCE_TRANSFER)); assert!(PausedTransactionFilter::::contains(TOKENS_TRANSFER)); assert_ok!(TransactionPause::unpause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Balances".to_vec(), - b"transfer".to_vec() + b"transfer_allow_death".to_vec() )); assert_ok!(TransactionPause::unpause_transaction( - Origin::signed(1), + RuntimeOrigin::signed(1), b"Tokens".to_vec(), b"transfer".to_vec() )); @@ -151,3 +155,28 @@ fn paused_transaction_filter_work() { assert!(!PausedTransactionFilter::::contains(TOKENS_TRANSFER)); }); } + +#[test] +fn pause_and_unpause_evm_precompile_works() { + use module_support::PrecompilePauseFilter; + ExtBuilder::default().build().execute_with(|| { + let one = H160::from_low_u64_be(1); + + assert_noop!( + TransactionPause::pause_evm_precompile(RuntimeOrigin::signed(2), one), + BadOrigin + ); + + assert!(!PausedPrecompileFilter::::is_paused(one)); + assert_ok!(TransactionPause::pause_evm_precompile(RuntimeOrigin::signed(1), one)); + assert!(PausedPrecompileFilter::::is_paused(one)); + + assert_noop!( + TransactionPause::unpause_evm_precompile(RuntimeOrigin::signed(2), one), + BadOrigin + ); + + assert_ok!(TransactionPause::unpause_evm_precompile(RuntimeOrigin::signed(1), one)); + assert!(!PausedPrecompileFilter::::is_paused(one)); + }); +} diff --git a/blockchain/modules/transaction-pause/src/weights.rs b/blockchain/modules/transaction-pause/src/weights.rs index 64da5db99..fd3262f18 100644 --- a/blockchain/modules/transaction-pause/src/weights.rs +++ b/blockchain/modules/transaction-pause/src/weights.rs @@ -1,83 +1,105 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Autogenerated weights for module_transaction_pause -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-08-16, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_transaction_pause -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./blockchain/modules/transaction-pause/src/weights.rs -// --template=.maintain/module-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_transaction_pause. -pub trait WeightInfo { - fn pause_transaction() -> Weight; - fn unpause_transaction() -> Weight; -} - -/// Weights for module_transaction_pause using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn pause_transaction() -> Weight { - (25_798_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn unpause_transaction() -> Weight { - (25_355_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn pause_transaction() -> Weight { - (25_798_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn unpause_transaction() -> Weight { - (25_355_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for module_transaction_pause +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-08-16, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_transaction_pause +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/transaction-pause/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_transaction_pause. +pub trait WeightInfo { + fn pause_transaction() -> Weight; + fn unpause_transaction() -> Weight; + fn pause_evm_precompile() -> Weight; + fn unpause_evm_precompile() -> Weight; +} + +/// Weights for module_transaction_pause using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn pause_transaction() -> Weight { + Weight::from_parts(25_798_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn unpause_transaction() -> Weight { + Weight::from_parts(25_355_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn pause_evm_precompile() -> Weight { + Weight::from_parts(13_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn unpause_evm_precompile() -> Weight { + Weight::from_parts(14_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn pause_transaction() -> Weight { + Weight::from_parts(25_798_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn unpause_transaction() -> Weight { + Weight::from_parts(25_355_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn pause_evm_precompile() -> Weight { + Weight::from_parts(13_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn unpause_evm_precompile() -> Weight { + Weight::from_parts(14_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/blockchain/modules/transaction-payment/Cargo.toml b/blockchain/modules/transaction-payment/Cargo.toml index 754dd3bd4..cff01107e 100644 --- a/blockchain/modules/transaction-payment/Cargo.toml +++ b/blockchain/modules/transaction-payment/Cargo.toml @@ -1,45 +1,45 @@ -[package] -name = "module-transaction-payment" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io= { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-tokens = { path = "../submodules/orml/tokens" } -module-currencies = { path = "../../tokens/currencies" } -# module-dex = { path = "../submodules/EthicalDeFi/DEX" } -smallvec = "1.4.1" - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "sp-io/std", - "sp-std/std", - "pallet-transaction-payment/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "primitives/std", - "support/std", - "orml-traits/std", -] +[package] +name = "module-transaction-payment" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +serde = { version = "1.0.124", optional = true } +codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +sp-io= { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +orml-tokens = { path = "../submodules/orml/tokens" } +module-currencies = { path = "../../tokens/currencies" } +# module-dex = { path = "../submodules/EthicalDeFi/DEX" } +smallvec = "1.4.1" + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-std/std", + "pallet-transaction-payment/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "primitives/std", + "support/std", + "orml-traits/std", +] diff --git a/blockchain/modules/transaction-payment/src/lib.rs b/blockchain/modules/transaction-payment/src/lib.rs index 5be7c1611..113f331f1 100644 --- a/blockchain/modules/transaction-payment/src/lib.rs +++ b/blockchain/modules/transaction-payment/src/lib.rs @@ -53,7 +53,7 @@ use sp_runtime::{ FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, }; use sp_std::{convert::TryInto, prelude::*, vec}; -use support::{DEXManager, PriceProvider, Ratio, TransactionPayment}; +use support::{SwapDexManager, PriceProvider, Ratio, TransactionPayment}; mod mock; mod tests; @@ -258,7 +258,7 @@ pub mod module { type FeeMultiplierUpdate: MultiplierUpdate; /// DEX to exchange currencies. - type DEX: DEXManager; + type DEX: SwapDexManager; /// When swap with DEX, the acceptable max slippage for the price from oracle. #[pallet::constant] diff --git a/blockchain/modules/transaction-payment/src/mock.rs b/blockchain/modules/transaction-payment/src/mock.rs index 3eaa0f863..dc8779305 100644 --- a/blockchain/modules/transaction-payment/src/mock.rs +++ b/blockchain/modules/transaction-payment/src/mock.rs @@ -317,7 +317,7 @@ ord_parameter_types! { } parameter_types! { - pub const DEXPalletId: PalletId = PalletId(*b"set/sdex"); + pub const DEXPalletId: PalletId = PalletId(*b"edf/swap"); pub GetExchangeFee: (u32, u32) = (1, 100); // 1% pub const TradingPathLimit: u32 = 4; pub GetStableCurrencyExchangeFee: (u32, u32) = (1, 200); // 0.5% diff --git a/blockchain/modules/vesting/Cargo.toml b/blockchain/modules/vesting/Cargo.toml index b893ef256..1272266c8 100644 --- a/blockchain/modules/vesting/Cargo.toml +++ b/blockchain/modules/vesting/Cargo.toml @@ -1,43 +1,51 @@ -[package] -name = "module-vesting" -description = "Provides scheduled balance locking mechanism, in a *graded vesting* way." -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["max-encoded-len"] } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-tokens = { path = "../submodules/orml/tokens" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", - "frame-support/std", - "frame-system/std", - "primitives/std", - "support/std", - "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] \ No newline at end of file +[package] +name = "module-vesting" +description = "Provides scheduled balance locking mechanism, in a *graded vesting* way." +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/vesting/README.md b/blockchain/modules/vesting/README.md index 425eb1d78..7b9d4407c 100644 --- a/blockchain/modules/vesting/README.md +++ b/blockchain/modules/vesting/README.md @@ -2,8 +2,12 @@ ## Overview -Vesting module provides a means of scheduled balance lock on an account. It uses the *graded vesting* way, which unlocks a specific amount of balance every period of time, until all balance unlocked. +Vesting module provides a means of scheduled balance lock on an account. It uses the *graded vesting* way, which unlocks a specific amount of balance every period of time, until all balance unlocked. The `NativeCurrencyId` (SEE) and `EDFCurrencyId` (EDF) are both supported in this module. ### Vesting Schedule The schedule of a vesting is described by data structure `VestingSchedule`: from the block number of `start`, of `currency_id`, for every `period` amount of blocks, `per_period` amount of balance would unlocked, until number of periods `period_count` reached. Note in vesting schedules, *time* is measured by block number. All `VestingSchedule`s under an account could be queried in chain state. + +### Locks + +The implementation uses locks which allow tokens to be locked by other pallets that's also using locks, for example, the conviction-voting pallet. diff --git a/blockchain/modules/vesting/src/lib.rs b/blockchain/modules/vesting/src/lib.rs index 6879df92c..1f7342e6e 100644 --- a/blockchain/modules/vesting/src/lib.rs +++ b/blockchain/modules/vesting/src/lib.rs @@ -47,23 +47,24 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] -use codec::{HasCompact, MaxEncodedLen}; use frame_support::{ ensure, pallet_prelude::*, - traits::{EnsureOrigin, Get}, - transactional, BoundedVec, + traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, WithdrawReasons}, + BoundedVec, }; -use frame_system::{ensure_signed, pallet_prelude::*}; +use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; +use scale_info::TypeInfo; use sp_runtime::{ traits::{AtLeast32Bit, BlockNumberProvider, CheckedAdd, Saturating, StaticLookup, Zero}, ArithmeticError, DispatchResult, RuntimeDebug, }; use sp_std::{ cmp::{Eq, PartialEq}, - convert::TryInto, vec::Vec, -};use orml_traits::{ +}; +use orml_traits::{ LockIdentifier, MultiCurrency, MultiLockableCurrency, }; use primitives::CurrencyId; @@ -75,14 +76,14 @@ mod weights; pub use module::*; pub use weights::WeightInfo; -pub const VESTING_LOCK_ID: LockIdentifier = *b"set/vest"; +pub const VESTING_LOCK_ID: LockIdentifier = *b"setvest"; /// The vesting schedule. /// /// Benefits would be granted gradually, `per_period` amount every `period` /// of blocks after `start`. -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)] -pub struct VestingSchedule { +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct VestingSchedule { /// Vesting starting block pub start: BlockNumber, /// Number of blocks between vest @@ -94,7 +95,9 @@ pub struct VestingSchedule { pub per_period: Balance, } -impl VestingSchedule { +impl + VestingSchedule +{ /// Returns the end of all periods, `None` if calculation overflows. pub fn end(&self) -> Option { // period * period_count + start @@ -135,61 +138,40 @@ pub mod module { <::MultiCurrency as MultiCurrency<::AccountId>>::Balance; pub(crate) type CurrencyIdOf = <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; - pub(crate) type VestingScheduleOf = VestingSchedule<::BlockNumber, BalanceOf>; + pub(crate) type VestingScheduleOf = VestingSchedule, BalanceOf>; pub type ScheduledItem = ( ::AccountId, CurrencyIdOf, - ::BlockNumber, - ::BlockNumber, + BlockNumberFor, + BlockNumberFor, u32, BalanceOf, ); #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - - type MultiCurrency: MultiLockableCurrency; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type MultiCurrency: MultiLockableCurrency>; + #[pallet::constant] - /// Native Setheum (SEE) currency id. [P]Pronounced "set M" or "setem" - /// + /// Native Setheum (SEE) currency id. type GetNativeCurrencyId: Get; #[pallet::constant] - /// Serp (SERP) currency id. - /// - type GetSerpCurrencyId: Get; - - #[pallet::constant] - /// The Dinar (DNAR) currency id. - /// - type GetDinarCurrencyId: Get; - - #[pallet::constant] - /// HighEnd LaunchPad (HELP) currency id. (LaunchPad Token) - /// - type GetHelpCurrencyId: Get; - - #[pallet::constant] - /// Setter (SETR) currency id - /// - type SetterCurrencyId: Get; + /// Ethical DeFi (EDF) currency id. + type GetEDFCurrencyId: Get; #[pallet::constant] - /// The SetDollar (SETUSD) currency id - type GetSetUSDId: Get; + /// The minimum amount of SEE transferred to call `vested_transfer`. + type MinNativeVestedTransfer: Get>; #[pallet::constant] - /// The minimum amount transferred to call `vested_transfer`. - type MinVestedTransfer: Get>; + /// The minimum amount of EDF transferred to call `vested_transfer`. + type MinEDFVestedTransfer: Get>; - /// SetheumTreasury account. For Vested Transfer - #[pallet::constant] - type TreasuryAccount: Get; - - /// The origin which may update inflation related params - type UpdateOrigin: EnsureOrigin; + /// Required origin for vested transfer. + type VestedTransferOrigin: EnsureOrigin; /// Weight information for extrinsics in this module. type WeightInfo: WeightInfo; @@ -197,20 +179,11 @@ pub mod module { /// The maximum vesting schedules for SEE type MaxNativeVestingSchedules: Get; - /// The maximum vesting schedules for SERP - type MaxSerpVestingSchedules: Get; - - /// The maximum vesting schedules for DNAR - type MaxDinarVestingSchedules: Get; - - /// The maximum vesting schedules for HELP - type MaxHelpVestingSchedules: Get; + /// The maximum vesting schedules for EDF + type MaxEDFVestingSchedules: Get; - /// The maximum vesting schedules for SETR - type MaxSetterVestingSchedules: Get; - - /// The maximum vesting schedules for SETUSD - type MaxSetUSDVestingSchedules: Get; + // The block number provider + type BlockNumberProvider: BlockNumberProvider>; } #[pallet::error] @@ -225,23 +198,29 @@ pub mod module { TooManyVestingSchedules, /// The vested transfer amount is too low AmountLow, - /// Failed because the maximum vesting schedules was exceeded - MaxVestingSchedulesExceeded, + /// Failed because the maximum vesting schedules for SEE was exceeded + MaxNativeVestingSchedulesExceeded, + /// Failed because the maximum vesting schedules for EDF was exceeded + MaxEDFVestingSchedulesExceeded, } #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId", VestingScheduleOf = "VestingScheduleOf", BalanceOf = "Balance")] pub enum Event { - /// Added new vesting schedule. \[currency_id, from, to, vesting_schedule\] - VestingScheduleAdded(CurrencyIdOf, T::AccountId, T::AccountId, VestingScheduleOf), - /// Claimed vesting. \[who, currency_id, locked_amount\] - Claimed(T::AccountId, CurrencyIdOf, BalanceOf), - /// Updated vesting schedules. \[currency_id, who\] - VestingSchedulesUpdated(CurrencyIdOf, T::AccountId), + /// Added new vesting schedule. + VestingScheduleAdded { + currency_id: CurrencyIdOf, + from: T::AccountId, + to: T::AccountId, + vesting_schedule: VestingScheduleOf, + }, + /// Claimed vesting. + Claimed { currency_id: CurrencyIdOf, who: T::AccountId, amount: BalanceOf }, + /// Updated vesting schedules. + VestingSchedulesUpdated {currency_id: CurrencyIdOf, who: T::AccountId }, } - /// Vesting schedules of an account under SEE currency. + /// Vesting schedules of an account under Native Currency (SEE). /// /// NativeVestingSchedules: map AccountId => Vec #[pallet::storage] @@ -253,210 +232,98 @@ pub mod module { BoundedVec, T::MaxNativeVestingSchedules>, ValueQuery, >; - - /// Vesting schedules of an account under SERP currency. - /// - /// SerpVestingSchedules: map AccountId => Vec - #[pallet::storage] - #[pallet::getter(fn serp_vesting_schedules)] - pub type SerpVestingSchedules = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxSerpVestingSchedules>, - ValueQuery, - >; - - /// Vesting schedules of an account under DNAR currency. - /// - /// DinarVestingSchedules: map AccountId => Vec - #[pallet::storage] - #[pallet::getter(fn dinar_vesting_schedules)] - pub type DinarVestingSchedules = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxDinarVestingSchedules>, - ValueQuery, - >; - - /// Vesting schedules of an account under HELP currency. - /// - /// HelpVestingSchedules: map AccountId => Vec - #[pallet::storage] - #[pallet::getter(fn help_vesting_schedules)] - pub type HelpVestingSchedules = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxHelpVestingSchedules>, - ValueQuery, - >; - - /// Vesting schedules of an account under SETR currency. - /// - /// SetterVestingSchedules: map AccountId => Vec - #[pallet::storage] - #[pallet::getter(fn setter_vesting_schedules)] - pub type SetterVestingSchedules = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxSetterVestingSchedules>, - ValueQuery, - >; - - /// Vesting schedules of an account under SETUSD currency. + + /// Vesting schedules of an account under EDF Currency. /// - /// SetUSDVestingSchedules: map AccountId => Vec + /// EDFVestingSchedules: map AccountId => Vec #[pallet::storage] - #[pallet::getter(fn setusd_vesting_schedules)] - pub type SetUSDVestingSchedules = StorageMap< + #[pallet::getter(fn edf_vesting_schedules)] + pub type EDFVestingSchedules = StorageMap< _, Blake2_128Concat, T::AccountId, - BoundedVec, T::MaxSetUSDVestingSchedules>, + BoundedVec, T::MaxEDFVestingSchedules>, ValueQuery, >; + #[pallet::genesis_config] pub struct GenesisConfig { pub vesting: Vec>, } - #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - GenesisConfig { vesting: vec![] } + GenesisConfig { + vesting: Default::default(), + } } } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { self.vesting .iter() .for_each(|(who, currency_id, start, period, period_count, per_period)| { if currency_id == &T::GetNativeCurrencyId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxNativeVestingSchedules> = - vec![VestingSchedule { + let mut bounded_schedules = VestingSchedules::::get(who); + bounded_schedules + .try_push(VestingSchedule { start: *start, period: *period, period_count: *period_count, per_period: *per_period, - }] - .try_into() + }) .expect("Max vesting schedules exceeded"); - + let total_amount = bounded_schedules + .iter() + .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { + let amount = ensure_valid_vesting_schedule::(T::GetNativeCurrencyId::get(), schedule)?; + acc_amount + .checked_add(&amount) + .ok_or_else(|| ArithmeticError::Overflow.into()) + }) + .expect("Invalid vesting schedule"); + + } + assert!( - T::MultiCurrency::free_balance(T::GetNativeCurrencyId::get(), who) >= total, - "Account do not have enough balance" + T::MultiCurrency::free_balance(T::GetNativeCurrencyId::get(), who) >= total_amount, + "Account does not have enough balance" ); - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetNativeCurrencyId::get(), who, total).unwrap(); - NativeVestingSchedules::::insert(who, bounded_schedule); - } else if currency_id == &T::GetSerpCurrencyId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxSerpVestingSchedules> = - vec![VestingSchedule { - start: *start, - period: *period, - period_count: *period_count, - per_period: *per_period, - }] - .try_into() - .expect("Max vesting schedules exceeded"); - - assert!( - T::MultiCurrency::free_balance(T::GetSerpCurrencyId::get(), who) >= total, - "Account do not have enough balance" - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetSerpCurrencyId::get(), who, total).unwrap(); - SerpVestingSchedules::::insert(who, bounded_schedule); - } else if currency_id == &T::GetDinarCurrencyId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxDinarVestingSchedules> = - vec![VestingSchedule { + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetNativeCurrencyId::get(), who, total_amount); + NativeVestingSchedules::::insert(who, bounded_schedules); + } else if currency_id == &T::GetEDFCurrencyId::get() { + let mut bounded_schedules = VestingSchedules::::get(who); + bounded_schedules + .try_push(VestingSchedule { start: *start, period: *period, period_count: *period_count, per_period: *per_period, - }] - .try_into() + }) .expect("Max vesting schedules exceeded"); - + let total_amount = bounded_schedules + .iter() + .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { + let amount = ensure_valid_vesting_schedule::(T::GetEDFCurrencyId::get(), schedule)?; + acc_amount + .checked_add(&amount) + .ok_or_else(|| ArithmeticError::Overflow.into()) + }) + .expect("Invalid vesting schedule"); + + } + assert!( - T::MultiCurrency::free_balance(T::GetDinarCurrencyId::get(), who) >= total, - "Account do not have enough balance" + T::MultiCurrency::free_balance(T::GetEDFCurrencyId::get(), who) >= total_amount, + "Account does not have enough balance" ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetDinarCurrencyId::get(), who, total).unwrap(); - DinarVestingSchedules::::insert(who, bounded_schedule); - } else if currency_id == &T::GetHelpCurrencyId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxHelpVestingSchedules> = - vec![VestingSchedule { - start: *start, - period: *period, - period_count: *period_count, - per_period: *per_period, - }] - .try_into() - .expect("Max vesting schedules exceeded"); - - assert!( - T::MultiCurrency::free_balance(T::GetHelpCurrencyId::get(), who) >= total, - "Account do not have enough balance" - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetHelpCurrencyId::get(), who, total).unwrap(); - HelpVestingSchedules::::insert(who, bounded_schedule); - } else if currency_id == &T::SetterCurrencyId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxSetterVestingSchedules> = - vec![VestingSchedule { - start: *start, - period: *period, - period_count: *period_count, - per_period: *per_period, - }] - .try_into() - .expect("Max vesting schedules exceeded"); - - assert!( - T::MultiCurrency::free_balance(T::SetterCurrencyId::get(), who) >= total, - "Account do not have enough balance" - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::SetterCurrencyId::get(), who, total).unwrap(); - SetterVestingSchedules::::insert(who, bounded_schedule); - } else if currency_id == &T::GetSetUSDId::get() { - let total = *per_period * Into::>::into(*period_count); - - let bounded_schedule: BoundedVec, T::MaxSetUSDVestingSchedules> = - vec![VestingSchedule { - start: *start, - period: *period, - period_count: *period_count, - per_period: *per_period, - }] - .try_into() - .expect("Max vesting schedules exceeded"); - - assert!( - T::MultiCurrency::free_balance(T::GetSetUSDId::get(), who) >= total, - "Account do not have enough balance" - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetSetUSDId::get(), who, total).unwrap(); - SetUSDVestingSchedules::::insert(who, bounded_schedule); + + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetEDFCurrencyId::get(), who, total_amount); + EDFVestingSchedules::::insert(who, bounded_schedules); } }); } @@ -466,35 +333,54 @@ pub mod module { pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks> for Pallet {} #[pallet::call] impl Pallet { - #[pallet::weight(T::WeightInfo::claim((::MaxNativeVestingSchedules::get() / 2) as u32))] + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::claim(::MaxVestingSchedules::get() / 2))] pub fn claim(origin: OriginFor, currency_id: CurrencyIdOf) -> DispatchResult { let who = ensure_signed(origin)?; let locked_amount = Self::do_claim(currency_id, &who); - Self::deposit_event(Event::Claimed(who, currency_id, locked_amount)); + Self::deposit_event(Event::Claimed { + currency_id, + who, + amount: locked_amount, + }); Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::vested_transfer())] pub fn vested_transfer( origin: OriginFor, currency_id: CurrencyIdOf, dest: ::Source, - schedule: VestingScheduleOf + schedule: VestingScheduleOf, ) -> DispatchResult { - T::UpdateOrigin::ensure_origin(origin)?; - let from = T::TreasuryAccount::get(); + let from = T::VestedTransferOrigin::ensure_origin(origin)?; let to = T::Lookup::lookup(dest)?; + + if to == from { + ensure!( + T::MultiCurrency::free_balance(currency_id, &from) >= schedule.total_amount().ok_or(ArithmeticError::Overflow)?, + Error::::InsufficientBalanceToLock, + ); + } + Self::do_vested_transfer(currency_id, &from, &to, schedule.clone())?; - Self::deposit_event(Event::VestingScheduleAdded(currency_id, from, to, schedule)); + Self::deposit_event(Event::VestingScheduleAdded { + currency_id, + from, + to, + vesting_schedule: schedule, + }); Ok(()) } + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::update_vesting_schedules(vesting_schedules.len() as u32))] pub fn update_vesting_schedules( origin: OriginFor, @@ -502,16 +388,17 @@ pub mod module { who: ::Source, vesting_schedules: Vec>, ) -> DispatchResult { - T::UpdateOrigin::ensure_origin(origin)?; + ensure_root(origin)?; let account = T::Lookup::lookup(who)?; Self::do_update_vesting_schedules(currency_id, &account, vesting_schedules)?; - Self::deposit_event(Event::VestingSchedulesUpdated(currency_id, account)); + Self::deposit_event(Event::VestingSchedulesUpdated { currency_id, who: account }); Ok(()) } - #[pallet::weight(T::WeightInfo::claim((::MaxNativeVestingSchedules::get() / 2) as u32))] + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::claim(::MaxVestingSchedules::get() / 2))] pub fn claim_for( origin: OriginFor, currency_id: CurrencyIdOf, @@ -521,60 +408,39 @@ pub mod module { let who = T::Lookup::lookup(dest)?; let locked_amount = Self::do_claim(currency_id, &who); - Self::deposit_event(Event::Claimed(who, currency_id, locked_amount)); + Self::deposit_event(Event::Claimed { + currency_id, + who, + amount: locked_amount, + }); Ok(()) } } } -impl BlockNumberProvider for Pallet { - type BlockNumber = ::BlockNumber; - - fn current_block_number() -> Self::BlockNumber { - >::block_number() - } -} - impl Pallet { fn do_claim(currency_id: CurrencyIdOf, who: &T::AccountId) -> BalanceOf { - let locked = Self::locked_balance(currency_id, who); + let locked = Self::locked_balance(who); if locked.is_zero() { if currency_id == T::GetNativeCurrencyId::get() { // cleanup the storage and unlock the fund >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - } else if currency_id == T::GetSerpCurrencyId::get() { - // cleanup the storage and unlock the fund - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - } else if currency_id == T::GetDinarCurrencyId::get() { + T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who); + } else if currency_id == T::GetEDFCurrencyId::get() { // cleanup the storage and unlock the fund - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - } else if currency_id == T::GetHelpCurrencyId::get() { - // cleanup the storage and unlock the fund - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - } else if currency_id == T::SetterCurrencyId::get() { - // cleanup the storage and unlock the fund - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - } else if currency_id == T::GetSetUSDId::get() { - // cleanup the storage and unlock the fund - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); + >::remove(who); + T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who); } } else { - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, locked).unwrap(); + T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, locked); } locked } /// Returns locked balance based on current block number. fn locked_balance(currency_id: CurrencyIdOf, who: &T::AccountId) -> BalanceOf { - let now = ::current_block_number(); + let now = T::BlockNumberProvider::current_block_number(); if currency_id == T::GetNativeCurrencyId::get() { - // cleanup the storage and unlock the fund >::mutate_exists(who, |maybe_schedules| { let total = if let Some(schedules) = maybe_schedules.as_mut() { let mut total: BalanceOf = Zero::zero(); @@ -590,87 +456,10 @@ impl Pallet { if total.is_zero() { *maybe_schedules = None; } - return total - }) - } else if currency_id == T::GetSerpCurrencyId::get() { - // cleanup the storage and unlock the fund - >::mutate_exists(who, |maybe_schedules| { - let total = if let Some(schedules) = maybe_schedules.as_mut() { - let mut total: BalanceOf = Zero::zero(); - schedules.retain(|s| { - let amount = s.locked_amount(now); - total = total.saturating_add(amount); - !amount.is_zero() - }); - total - } else { - Zero::zero() - }; - if total.is_zero() { - *maybe_schedules = None; - } - return total - }) - } else if currency_id == T::GetDinarCurrencyId::get() { - // cleanup the storage and unlock the fund - >::mutate_exists(who, |maybe_schedules| { - let total = if let Some(schedules) = maybe_schedules.as_mut() { - let mut total: BalanceOf = Zero::zero(); - schedules.retain(|s| { - let amount = s.locked_amount(now); - total = total.saturating_add(amount); - !amount.is_zero() - }); - total - } else { - Zero::zero() - }; - if total.is_zero() { - *maybe_schedules = None; - } - return total - }) - } else if currency_id == T::GetHelpCurrencyId::get() { - // cleanup the storage and unlock the fund - >::mutate_exists(who, |maybe_schedules| { - let total = if let Some(schedules) = maybe_schedules.as_mut() { - let mut total: BalanceOf = Zero::zero(); - schedules.retain(|s| { - let amount = s.locked_amount(now); - total = total.saturating_add(amount); - !amount.is_zero() - }); - total - } else { - Zero::zero() - }; - if total.is_zero() { - *maybe_schedules = None; - } - return total - }) - } else if currency_id == T::SetterCurrencyId::get() { - // cleanup the storage and unlock the fund - >::mutate_exists(who, |maybe_schedules| { - let total = if let Some(schedules) = maybe_schedules.as_mut() { - let mut total: BalanceOf = Zero::zero(); - schedules.retain(|s| { - let amount = s.locked_amount(now); - total = total.saturating_add(amount); - !amount.is_zero() - }); - total - } else { - Zero::zero() - }; - if total.is_zero() { - *maybe_schedules = None; - } - return total + total }) - } else if currency_id == T::GetSetUSDId::get() { - // cleanup the storage and unlock the fund - >::mutate_exists(who, |maybe_schedules| { + } else if currency_id == T::GetEDFCurrencyId::get() { + >::mutate_exists(who, |maybe_schedules| { let total = if let Some(schedules) = maybe_schedules.as_mut() { let mut total: BalanceOf = Zero::zero(); schedules.retain(|s| { @@ -685,223 +474,121 @@ impl Pallet { if total.is_zero() { *maybe_schedules = None; } - return total + total }) - } else { - Zero::zero() } } - #[transactional] fn do_vested_transfer( currency_id: CurrencyIdOf, from: &T::AccountId, to: &T::AccountId, schedule: VestingScheduleOf ) -> DispatchResult { - let schedule_amount = Self::ensure_valid_vesting_schedule(&schedule)?; + if currency_id == T::GetNativeCurrencyId::get() { + let schedule_amount = ensure_valid_vesting_schedule::(T::GetNativeCurrencyId::get(), &schedule)?; - let total_amount = Self::locked_balance(currency_id, to) - .checked_add(&schedule_amount) - .ok_or(ArithmeticError::Overflow)?; + let total_amount = Self::locked_balance(T::GetNativeCurrencyId::get(), to) + .checked_add(&schedule_amount) + .ok_or(ArithmeticError::Overflow)?; - if currency_id == T::GetNativeCurrencyId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - } else if currency_id == T::GetSerpCurrencyId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - } else if currency_id == T::GetDinarCurrencyId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - } else if currency_id == T::GetHelpCurrencyId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - } else if currency_id == T::SetterCurrencyId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - } else if currency_id == T::GetSetUSDId::get() { - T::MultiCurrency::transfer(currency_id, from, to, schedule_amount)?; - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, to, total_amount)?; - >::try_append(to, schedule).map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - }; - Ok(()) + T::MultiCurrency::transfer(T::GetNativeCurrencyId::get(), from, to, schedule_amount)?; + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetNativeCurrencyId::get(), to, total_amount)?; + >::try_append(to, schedule).map_err(|_| Error::::MaxNativeVestingSchedulesExceeded)?; + } else if currency_id == T::GetEDFCurrencyId::get() { + let schedule_amount = ensure_valid_vesting_schedule::(T::GetEDFCurrencyId::get(), &schedule)?; + + let total_amount = Self::locked_balance(T::GetEDFCurrencyId::get(), to) + .checked_add(&schedule_amount) + .ok_or(ArithmeticError::Overflow)?; + + T::MultiCurrency::transfer(T::GetEDFCurrencyId::get(), from, to, schedule_amount)?; + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetEDFCurrencyId::get(), to, total_amount)?; + >::try_append(to, schedule).map_err(|_| Error::::MaxEDFVestingSchedulesExceeded)?; + } + Ok(()) } fn do_update_vesting_schedules( currency_id: CurrencyIdOf, - who: &T::AccountId, + who: &T::AccountId, schedules: Vec> ) -> DispatchResult { if currency_id == T::GetNativeCurrencyId::get() { let bounded_schedules: BoundedVec, T::MaxNativeVestingSchedules> = schedules .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - + .map_err(|_| Error::::MaxNativeVestingSchedulesExceeded)?; + // empty vesting schedules cleanup the storage and unlock the fund if bounded_schedules.len().is_zero() { >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); + T::MultiCurrency::remove_lock(VESTING_LOCK_ID,T::GetNativeCurrencyId::get(), who)?; return Ok(()); } - + let total_amount = bounded_schedules .iter() .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) + let amount = ensure_valid_vesting_schedule::(T::GetNativeCurrencyId::get(), schedule)?; + acc_amount + .checked_add(&amount) + .ok_or_else(|| ArithmeticError::Overflow.into()) })?; ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, + T::MultiCurrency::free_balance(T::GetNativeCurrencyId::get(), who) >= total_amount, Error::::InsufficientBalanceToLock, ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; + + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetNativeCurrencyId::get(), who, total_amount); >::insert(who, bounded_schedules); - } else if currency_id == T::GetSerpCurrencyId::get() { - let bounded_schedules: BoundedVec, T::MaxSerpVestingSchedules> = schedules + } else if currency_id == T::GetEDFCurrencyId::get() { + let bounded_schedules: BoundedVec, T::MaxEDFVestingSchedules> = schedules .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - - // empty vesting schedules cleanup the storage and unlock the fund - if bounded_schedules.len().is_zero() { - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - return Ok(()); - } - - let total_amount = bounded_schedules - .iter() - .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) - })?; - ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, - Error::::InsufficientBalanceToLock, - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; - >::insert(who, bounded_schedules); - } else if currency_id == T::GetDinarCurrencyId::get() { - let bounded_schedules: BoundedVec, T::MaxDinarVestingSchedules> = schedules - .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - - // empty vesting schedules cleanup the storage and unlock the fund - if bounded_schedules.len().is_zero() { - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - return Ok(()); - } - - let total_amount = bounded_schedules - .iter() - .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) - })?; - ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, - Error::::InsufficientBalanceToLock, - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; - >::insert(who, bounded_schedules); - } else if currency_id == T::GetHelpCurrencyId::get() { - let bounded_schedules: BoundedVec, T::MaxHelpVestingSchedules> = schedules - .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - - // empty vesting schedules cleanup the storage and unlock the fund - if bounded_schedules.len().is_zero() { - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - return Ok(()); - } - - let total_amount = bounded_schedules - .iter() - .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) - })?; - ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, - Error::::InsufficientBalanceToLock, - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; - >::insert(who, bounded_schedules); - } else if currency_id == T::SetterCurrencyId::get() { - let bounded_schedules: BoundedVec, T::MaxSetterVestingSchedules> = schedules - .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - - // empty vesting schedules cleanup the storage and unlock the fund - if bounded_schedules.len().is_zero() { - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); - return Ok(()); - } - - let total_amount = bounded_schedules - .iter() - .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) - })?; - ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, - Error::::InsufficientBalanceToLock, - ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; - >::insert(who, bounded_schedules); - } else if currency_id == T::GetSetUSDId::get() { - let bounded_schedules: BoundedVec, T::MaxSetUSDVestingSchedules> = schedules - .try_into() - .map_err(|_| Error::::MaxVestingSchedulesExceeded)?; - + .map_err(|_| Error::::MaxEDFVestingSchedulesExceeded)?; + // empty vesting schedules cleanup the storage and unlock the fund if bounded_schedules.len().is_zero() { - >::remove(who); - T::MultiCurrency::remove_lock(VESTING_LOCK_ID, currency_id, who).unwrap(); + >::remove(who); + T::MultiCurrency::remove_lock(VESTING_LOCK_ID,T::GetEDFCurrencyId::get(), who)?; return Ok(()); } - + let total_amount = bounded_schedules .iter() .try_fold::<_, _, Result, DispatchError>>(Zero::zero(), |acc_amount, schedule| { - let amount = Self::ensure_valid_vesting_schedule(schedule)?; - Ok(acc_amount + amount) + let amount = ensure_valid_vesting_schedule::(T::GetEDFCurrencyId::get(), schedule)?; + acc_amount + .checked_add(&amount) + .ok_or_else(|| ArithmeticError::Overflow.into()) })?; ensure!( - T::MultiCurrency::free_balance(currency_id, who) >= total_amount, + T::MultiCurrency::free_balance(T::GetEDFCurrencyId::get(), who) >= total_amount, Error::::InsufficientBalanceToLock, ); - - T::MultiCurrency::set_lock(VESTING_LOCK_ID, currency_id, who, total_amount)?; - >::insert(who, bounded_schedules); - }; + + T::MultiCurrency::set_lock(VESTING_LOCK_ID, T::GetEDFCurrencyId::get(), who, total_amount); + >::insert(who, bounded_schedules); + } Ok(()) } +} - /// Returns `Ok(amount)` if valid schedule, or error. - fn ensure_valid_vesting_schedule(schedule: &VestingScheduleOf) -> Result, DispatchError> { - ensure!(!schedule.period.is_zero(), Error::::ZeroVestingPeriod); - ensure!(!schedule.period_count.is_zero(), Error::::ZeroVestingPeriodCount); - ensure!(schedule.end().is_some(), ArithmeticError::Overflow); - - let total = schedule.total_amount().ok_or(ArithmeticError::Overflow)?; - - ensure!(total >= T::MinVestedTransfer::get(), Error::::AmountLow); - - Ok(total) +/// Returns `Ok(total_total)` if valid schedule, or error. +fn ensure_valid_vesting_schedule( + currency_id: CurrencyIdOf, + schedule: &VestingScheduleOf +) -> Result, DispatchError> { + ensure!(!schedule.period.is_zero(), Error::::ZeroVestingPeriod); + ensure!(!schedule.period_count.is_zero(), Error::::ZeroVestingPeriodCount); + ensure!(schedule.end().is_some(), ArithmeticError::Overflow); + + let total_total = schedule.total_amount().ok_or(ArithmeticError::Overflow)?; + + if currency_id == T::GetNativeCurrencyId::get() { + ensure!(total_total >= T::MinNativeVestedTransfer::get(), Error::::AmountLow); + } else if currency_id == T::GetEDFCurrencyId::get() { + ensure!(total_total >= T::MinEDFVestedTransfer::get(), Error::::AmountLow); } -} \ No newline at end of file + + Ok(total_total) +} diff --git a/blockchain/modules/vesting/src/mock.rs b/blockchain/modules/vesting/src/mock.rs index e47449604..924d6d635 100644 --- a/blockchain/modules/vesting/src/mock.rs +++ b/blockchain/modules/vesting/src/mock.rs @@ -5,46 +5,36 @@ use super::*; use frame_support::{ construct_runtime, parameter_types, - traits::{EnsureOrigin, Everything}, + traits::{ConstU32, ConstU64, EnsureOrigin, Everything, Nothing}, }; use frame_system::RawOrigin; use orml_traits::parameter_type_with_key; use primitives::{Amount, TokenSymbol}; use sp_core::H256; -use sp_runtime::{testing::Header, traits::IdentityLookup}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; -use crate as vesting; - -parameter_types! { - pub const BlockHashCount: u64 = 250; +mod vesting { + pub use super::super::*; } pub type AccountId = u128; -pub const SETR: CurrencyId = CurrencyId::Token(TokenSymbol::SETR); -pub const SETUSD: CurrencyId = CurrencyId::Token(TokenSymbol::SETUSD); -pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); -pub const SERP: CurrencyId = CurrencyId::Token(TokenSymbol::SERP); -pub const DNAR: CurrencyId = CurrencyId::Token(TokenSymbol::DNAR); -pub const HELP: CurrencyId = CurrencyId::Token(TokenSymbol::HELP); - impl frame_system::Config for Runtime { - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type BlockWeights = (); type BlockLength = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); @@ -52,138 +42,106 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } type Balance = u64; -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxLocks: u32 = 100; +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; } -impl pallet_balances::Config for Runtime { +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type Balance = Balance; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type MaxLocks = MaxLocks; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); + type DustRemovalWhitelist = Nothing; +} + +impl BlockNumberProvider for MockBlockNumberProvider { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + Self::get() + } } + pub struct EnsureAliceOrBob; -impl EnsureOrigin for EnsureAliceOrBob { +impl EnsureOrigin for EnsureAliceOrBob { type Success = AccountId; - fn try_origin(o: Origin) -> Result { - Into::, Origin>>::into(o).and_then(|o| match o { + fn try_origin(o: RuntimeOrigin) -> Result { + Into::, RuntimeOrigin>>::into(o).and_then(|o| match o { RawOrigin::Signed(ALICE) => Ok(ALICE), RawOrigin::Signed(BOB) => Ok(BOB), - r => Err(Origin::from(r)), + r => Err(RuntimeOrigin::from(r)), }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> Origin { - Origin::from(RawOrigin::Signed(Default::default())) + fn try_successful_origin() -> Result { + let zero_account_id = AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite length input; no invalid inputs for type; qed"); + Ok(RuntimeOrigin::from(RawOrigin::Signed(zero_account_id))) } } -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() - }; -} - -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = Balance; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = MaxLocks; - type DustRemovalWhitelist = (); -} +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::edf); parameter_types! { - pub StableCurrencyIds: Vec = vec![ - SETR, - SETUSD, - ]; - pub const SetterCurrencyId: CurrencyId = SETR; // Setter currency ticker is SETR/ - pub const GetSetUSDId: CurrencyId = SETUSD; // SetDollar currency ticker is SETUSD/ - pub const GetNativeCurrencyId: CurrencyId = SEE; // Setheum native currency ticker is SEE/ - pub const GetSerpCurrencyId: CurrencyId = SERP; // Serp currency ticker is SERP/ - pub const GetDinarCurrencyId: CurrencyId = DNAR; // The Dinar currency ticker is DNAR/ - pub const GetHelpCurrencyId: CurrencyId = HELP; // HighEnd LaunchPad currency ticker is HELP/ + pub const GetNativeCurrencyId: CurrencyId = SEE; + pub const GetDEFCurrencyId: CurrencyId = EDF; pub static MockBlockNumberProvider: u64 = 0; - pub const TreasuryAccount: AccountId = TREASURY; } impl BlockNumberProvider for MockBlockNumberProvider { type BlockNumber = u64; - fn current_block_number() -> Self::BlockNumber { + fn current_block_number() -> BlockNumberFor { Self::get() } } -parameter_types! { - pub const MaxNativeVestingSchedules: u32 = 2; - pub const MaxSerpVestingSchedules: u32 = 2; - pub const MaxDinarVestingSchedules: u32 = 2; - pub const MaxHelpVestingSchedules: u32 = 2; - pub const MaxSetterVestingSchedules: u32 = 2; - pub const MaxSetUSDVestingSchedules: u32 = 2; - pub const MinVestedTransfer: u64 = 5; -} - impl Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type MultiCurrency = Tokens; type GetNativeCurrencyId = GetNativeCurrencyId; - type GetSerpCurrencyId = GetSerpCurrencyId; - type GetDinarCurrencyId = GetDinarCurrencyId; - type GetHelpCurrencyId = GetHelpCurrencyId; - type SetterCurrencyId = SetterCurrencyId; - type GetSetUSDId = GetSetUSDId; - type MinVestedTransfer = MinVestedTransfer; - type TreasuryAccount = TreasuryAccount; - type UpdateOrigin = EnsureAliceOrBob; + type GetEDFCurrencyId = GetEDFCurrencyId; + type MinNativeVestedTransfer = ConstU64<5>; + type MinEDFVestedTransfer = ConstU64<5>; + type VestedTransferOrigin = EnsureAliceOrBob; type WeightInfo = (); - type MaxNativeVestingSchedules = MaxNativeVestingSchedules; - type MaxSerpVestingSchedules = MaxSerpVestingSchedules; - type MaxDinarVestingSchedules = MaxDinarVestingSchedules; - type MaxHelpVestingSchedules = MaxHelpVestingSchedules; - type MaxSetterVestingSchedules = MaxSetterVestingSchedules; - type MaxSetUSDVestingSchedules = MaxSetUSDVestingSchedules; + type MaxNativeVestingSchedules = ConstU32<2>; + type MaxEDFVestingSchedules = ConstU32<2>; + type BlockNumberProvider = MockBlockNumberProvider; } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Vesting: vesting::{Pallet, Storage, Call, Event, Config}, - PalletBalances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config} + pub enum Runtime { + System: frame_system, + Vesting: vesting, + Tokens: orml_tokens, } ); pub const ALICE: AccountId = 1; pub const BOB: AccountId = 2; pub const CHARLIE: AccountId = 3; -pub const TREASURY: AccountId = 4; +#[derive(Default)] pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, } @@ -192,33 +150,19 @@ impl Default for ExtBuilder { fn default() -> Self { Self { balances: vec![ - (ALICE, SEE, 10000), - (ALICE, SERP, 1000), - (ALICE, DNAR, 1000), - (ALICE, HELP, 1000), - (ALICE, SETR, 1000), - (ALICE, SETUSD, 1000), - (CHARLIE, SEE, 10000), - (CHARLIE, SERP, 1000), - (CHARLIE, DNAR, 1000), - (CHARLIE, HELP, 1000), - (CHARLIE, SETR, 1000), - (CHARLIE, SETUSD, 1000), - (TREASURY, SEE, 10000), - (TREASURY, SERP, 1000), - (TREASURY, DNAR, 1000), - (TREASURY, HELP, 1000), - (TREASURY, SETR, 1000), - (TREASURY, SETUSD, 1000) + (ALICE, SEE, 100), + (ALICE, EDF, 100), + (CHARLIE, SEE, 50) + (CHARLIE, EDF, 50) ], } } } impl ExtBuilder { - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() + pub fn build() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() .unwrap(); orml_tokens::GenesisConfig:: { @@ -228,12 +172,17 @@ impl ExtBuilder { .unwrap(); vesting::GenesisConfig:: { - // who, start, period, period_count, per_period - vesting: vec![(CHARLIE, SEE, 2, 3, 4, 5)], + vesting: vec![ + // who, start, period, period_count, per_period + (CHARLIE, SEE, 2, 3, 1, 5), + (CHARLIE, SEE, 2 + 3, 3, 3, 5), + (CHARLIE, EDF, 2, 3, 1, 5), + (CHARLIE, EDF, 2 + 3, 3, 3, 5), + ], } .assimilate_storage(&mut t) .unwrap(); t.into() } -} \ No newline at end of file +} diff --git a/blockchain/modules/vesting/src/tests.rs b/blockchain/modules/vesting/src/tests.rs index fa0e2d89b..df4ce5764 100644 --- a/blockchain/modules/vesting/src/tests.rs +++ b/blockchain/modules/vesting/src/tests.rs @@ -4,7 +4,9 @@ use super::*; use frame_support::{assert_noop, assert_ok, error::BadOrigin}; -use mock::{Event, SEE, *}; +use mock::*; +use sp_runtime::traits::Dispatchable; +use sp_runtime::TokenError; #[test] fn vesting_from_chain_spec_works() { @@ -14,30 +16,40 @@ fn vesting_from_chain_spec_works() { &CHARLIE, 10 )); + assert!(Tokens::ensure_can_withdraw(SEE, &CHARLIE, 11).is_err()); assert_eq!( Vesting::native_vesting_schedules(&CHARLIE), - vec![VestingSchedule { - start: 2u64, - period: 3u64, - period_count: 4u32, - per_period: 5u64, - }] + vec![ + VestingSchedule { + start: 2u64, + period: 3u64, + period_count: 1u32, + per_period: 5u64, + }, + VestingSchedule { + start: 2u64 + 3u64, + period: 3u64, + period_count: 3u32, + per_period: 5u64, + } + ] ); - System::set_block_number(13); + MockBlockNumberProvider::set(13); - assert_ok!(Vesting::claim(Origin::signed(CHARLIE), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(CHARLIE), SEE)); assert_ok!(Tokens::ensure_can_withdraw( SEE, &CHARLIE, 25 )); + assert!(Tokens::ensure_can_withdraw(SEE, &CHARLIE, 26).is_err()); - System::set_block_number(14); + MockBlockNumberProvider::set(14); - assert_ok!(Vesting::claim(Origin::signed(CHARLIE), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(CHARLIE), SEE)); assert_ok!(Tokens::ensure_can_withdraw( SEE, @@ -58,8 +70,58 @@ fn vested_transfer_works() { period_count: 1u32, per_period: 100u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule.clone())); + assert_ok!(Vesting::vested_transfer( + RuntimeOrigin::signed(ALICE), + SEE, + BOB, + schedule.clone() + )); assert_eq!(Vesting::native_vesting_schedules(&BOB), vec![schedule.clone()]); + System::assert_last_event(RuntimeEvent::Vesting(crate::Event::VestingScheduleAdded { + from: ALICE, + to: BOB, + vesting_schedule: schedule, + })); + }); +} + +#[test] +fn self_vesting() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + let schedule = VestingSchedule { + start: 0u64, + period: 10u64, + period_count: 1u32, + per_period: ALICE_BALANCE, + }; + + let bad_schedule = VestingSchedule { + start: 0u64, + period: 10u64, + period_count: 1u32, + per_period: 10 * ALICE_BALANCE, + }; + + assert_noop!( + Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, ALICE, bad_schedule), + crate::Error::::InsufficientBalanceToLock + ); + + assert_ok!(Vesting::vested_transfer( + RuntimeOrigin::signed(ALICE), + SEE, + ALICE, + schedule.clone() + )); + + assert_eq!(Vesting::native_vesting_schedules(&ALICE), vec![schedule.clone()]); + System::assert_last_event(RuntimeEvent::Vesting(crate::Event::VestingScheduleAdded { + from: ALICE, + to: ALICE, + vesting_schedule: schedule, + })); }); } @@ -72,9 +134,9 @@ fn add_new_vesting_schedule_merges_with_current_locked_balance_and_until() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule)); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule)); - System::set_block_number(12); + MockBlockNumberProvider::set(12); let another_schedule = VestingSchedule { start: 10u64, @@ -82,7 +144,20 @@ fn add_new_vesting_schedule_merges_with_current_locked_balance_and_until() { period_count: 1u32, per_period: 7u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, another_schedule)); + assert_ok!(Vesting::vested_transfer( + RuntimeOrigin::signed(ALICE), + SEE, + BOB, + another_schedule + )); + + assert_eq!( + Tokens::locks(&BOB, SEE).get(0), + Some(&BalanceLock { + id: VESTING_LOCK_ID, + amount: 17u64, + }) + ); }); } @@ -95,7 +170,7 @@ fn cannot_use_fund_if_not_claimed() { period_count: 1u32, per_period: 50u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule)); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule)); assert!(Tokens::ensure_can_withdraw(SEE, &BOB, 1).is_err()); }); } @@ -110,7 +185,7 @@ fn vested_transfer_fails_if_zero_period_or_count() { per_period: 100u64, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule), Error::::ZeroVestingPeriod ); @@ -121,12 +196,28 @@ fn vested_transfer_fails_if_zero_period_or_count() { per_period: 100u64, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule), Error::::ZeroVestingPeriodCount ); }); } +#[test] +fn vested_transfer_fails_if_transfer_err() { + ExtBuilder::default().build().execute_with(|| { + let schedule = VestingSchedule { + start: 1u64, + period: 1u64, + period_count: 1u32, + per_period: 100u64, + }; + assert_noop!( + Vesting::vested_transfer(RuntimeOrigin::signed(BOB), SEE, ALICE, schedule), + TokenError::FundsUnavailable, + ); + }); +} + #[test] fn vested_transfer_fails_if_overflow() { ExtBuilder::default().build().execute_with(|| { @@ -137,7 +228,7 @@ fn vested_transfer_fails_if_overflow() { per_period: u64::MAX, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule), ArithmeticError::Overflow, ); @@ -148,7 +239,7 @@ fn vested_transfer_fails_if_overflow() { per_period: 1u64, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, another_schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, another_schedule), ArithmeticError::Overflow, ); }); @@ -164,7 +255,7 @@ fn vested_transfer_fails_if_bad_origin() { per_period: 100u64, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(CHARLIE), SEE, BOB, schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(CHARLIE), SEE, BOB, schedule), BadOrigin ); }); @@ -179,29 +270,28 @@ fn claim_works() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule)); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule)); - System::set_block_number(11); + MockBlockNumberProvider::set(11); // remain locked if not claimed - assert!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 10).is_err()); + assert!(Tokens::transfer(&BOB, &ALICE, SEE, 10).is_err()); // unlocked after claiming - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); assert!(NativeVestingSchedules::::contains_key(BOB)); - assert_ok!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 10)); + assert_ok!(Tokens::transfer(&BOB, &ALICE, SEE, 10)); // more are still locked - assert!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 1).is_err()); + assert!(Tokens::transfer(&BOB, &ALICE, SEE, 1).is_err()); - System::set_block_number(21); + MockBlockNumberProvider::set(21); // claim more - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); assert!(!NativeVestingSchedules::::contains_key(BOB)); - assert_ok!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 10)); + assert_ok!(Tokens::transfer(&BOB, &ALICE, SEE, 10)); // all used up assert_eq!(Tokens::free_balance(SEE, &BOB), 0); - assert_eq!(PalletBalances::free_balance(BOB), 0); // no locks anymore - assert_eq!(PalletBalances::locks(&BOB), vec![]); + assert_eq!(Tokens::locks(&BOB, SEE), vec![]); }); } @@ -214,18 +304,25 @@ fn claim_for_works() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule)); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule)); - assert_ok!(Vesting::claim_for(Origin::signed(ALICE), SEE, BOB)); + assert_ok!(Vesting::claim_for(RuntimeOrigin::signed(ALICE), SEE, BOB)); + assert_eq!( + Tokens::locks(&BOB, SEE).get(0), + Some(&BalanceLock { + id: VESTING_LOCK_ID, + amount: 20u64, + }) + ); assert!(NativeVestingSchedules::::contains_key(&BOB)); - System::set_block_number(21); + MockBlockNumberProvider::set(21); - assert_ok!(Vesting::claim_for(Origin::signed(ALICE), SEE, BOB)); + assert_ok!(Vesting::claim_for(RuntimeOrigin::signed(ALICE), SEE, BOB)); // no locks anymore - assert_eq!(PalletBalances::locks(&BOB), vec![]); + assert_eq!(Tokens::locks(&BOB, SEE), vec![]); assert!(!NativeVestingSchedules::::contains_key(&BOB)); }); } @@ -239,7 +336,7 @@ fn update_vesting_schedules_works() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule)); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule)); let updated_schedule = VestingSchedule { start: 0u64, @@ -248,33 +345,39 @@ fn update_vesting_schedules_works() { per_period: 10u64, }; assert_ok!(Vesting::update_vesting_schedules( - Origin::signed(ALICE), + RuntimeOrigin::root(), SEE, BOB, vec![updated_schedule] )); - System::set_block_number(11); - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); - assert!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 1).is_err()); + MockBlockNumberProvider::set(11); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); + assert!(Tokens::transfer(&BOB, &ALICE, SEE, 1).is_err()); - System::set_block_number(21); - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); - assert_ok!(Tokens::transfer(Origin::signed(BOB), ALICE, SEE, 10)); + MockBlockNumberProvider::set(21); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); + assert_ok!(Tokens::transfer(&BOB, &ALICE, SEE, 10)); // empty vesting schedules cleanup the storage and unlock the fund assert!(NativeVestingSchedules::::contains_key(BOB)); - - assert_ok!(Vesting::update_vesting_schedules(Origin::signed(ALICE), SEE, BOB, vec![])); + assert_eq!( + Tokens::locks(&BOB, SEE).get(0), + Some(&BalanceLock { + id: VESTING_LOCK_ID, + amount: 10u64, + }) + ); + assert_ok!(Vesting::update_vesting_schedules(RuntimeOrigin::root(), SEE, BOB, vec![])); assert!(!NativeVestingSchedules::::contains_key(BOB)); - assert_eq!(PalletBalances::locks(&BOB), vec![]); + assert_eq!(Tokens::locks(&BOB, SEE), vec![]); }); } #[test] fn update_vesting_schedules_fails_if_unexpected_existing_locks() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(Tokens::transfer(Origin::signed(ALICE), BOB, SEE, 1)); + assert_ok!(Tokens::transfer(&ALICE, &BOB, SEE, 1)); Tokens::set_lock(*b"prelocks", SEE, &BOB, 0u64); }); } @@ -289,7 +392,7 @@ fn vested_transfer_check_for_min() { per_period: 3u64, }; assert_noop!( - Vesting::vested_transfer(Origin::signed(BOB), SEE, ALICE, schedule), + Vesting::vested_transfer(RuntimeOrigin::signed(BOB), SEE, ALICE, schedule), Error::::AmountLow ); }); @@ -304,7 +407,7 @@ fn multiple_vesting_schedule_claim_works() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule.clone())); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule.clone())); let schedule2 = VestingSchedule { start: 0u64, @@ -312,23 +415,23 @@ fn multiple_vesting_schedule_claim_works() { period_count: 3u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule2.clone())); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule2.clone())); assert_eq!(Vesting::native_vesting_schedules(&BOB), vec![schedule, schedule2.clone()]); - System::set_block_number(21); + MockBlockNumberProvider::set(21); - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB, SEE))); assert_eq!(Vesting::native_vesting_schedules(&BOB), vec![schedule2]); - System::set_block_number(31); + MockBlockNumberProvider::set(31); - assert_ok!(Vesting::claim(Origin::signed(BOB), SEE)); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB, SEE))); assert!(!NativeVestingSchedules::::contains_key(&BOB)); - assert_eq!(PalletBalances::locks(&BOB), vec![]); + assert_eq!(Tokens::locks(&BOB, SEE), vec![]); }); } @@ -341,18 +444,75 @@ fn exceeding_maximum_schedules_should_fail() { period_count: 2u32, per_period: 10u64, }; - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule.clone())); - assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule.clone())); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule.clone())); + assert_ok!(Vesting::vested_transfer(RuntimeOrigin::signed(ALICE), SEE, BOB, schedule.clone())); + + let create = RuntimeCall::Vesting(crate::Call::::vested_transfer { + currency_id: SEE, + dest: BOB, + schedule: schedule.clone(), + }); assert_noop!( - Vesting::vested_transfer(Origin::signed(ALICE), SEE, BOB, schedule.clone()), - Error::::MaxVestingSchedulesExceeded + create.dispatch(RuntimeOrigin::signed(ALICE)), + Error::::MaxNativeVestingSchedulesExceeded ); let schedules = vec![schedule.clone(), schedule.clone(), schedule]; assert_noop!( - Vesting::update_vesting_schedules(Origin::signed(ALICE), SEE, BOB, schedules), - Error::::MaxVestingSchedulesExceeded + Vesting::update_vesting_schedules(RuntimeOrigin::root(), SEE, BOB, schedules), + Error::::MaxNativeVestingSchedulesExceeded ); }); -} \ No newline at end of file +} + +#[test] +fn cliff_vesting_works() { + const VESTING_AMOUNT: Balance = 12; + const VESTING_PERIOD: Balance = 20; + + ExtBuilder::default().build().execute_with(|| { + let cliff_schedule = VestingSchedule { + start: VESTING_PERIOD - 1, + period: 1, + period_count: 1, + per_period: VESTING_AMOUNT, + }; + + let balance_lock = BalanceLock { + id: VESTING_LOCK_ID, + amount: VESTING_AMOUNT, + }; + + assert_eq!(Tokens::free_balance(SEE, BOB), 0); + assert_ok!(Vesting::vested_transfer( + RuntimeOrigin::signed(ALICE), + SEE, + BOB, + cliff_schedule + )); + assert_eq!(Tokens::free_balance(SEE, BOB), VESTING_AMOUNT); + assert_eq!(Tokens::locks(&BOB, SEE), vec![balance_lock.clone()]); + + for i in 1..VESTING_PERIOD { + MockBlockNumberProvider::set(i); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); + assert_eq!(Tokens::free_balance(SEE, BOB), VESTING_AMOUNT); + assert_eq!(Tokens::locks(&BOB, SEE), vec![balance_lock.clone()]); + assert_noop!( + Tokens::transfer(&BOB, &CHARLIE, SEE, VESTING_AMOUNT), + TokenError::Frozen, + ); + } + + MockBlockNumberProvider::set(VESTING_PERIOD); + assert_ok!(Vesting::claim(RuntimeOrigin::signed(BOB), SEE)); + assert!(Tokens::locks(&BOB, SEE).is_empty()); + assert_ok!(Tokens::transfer( + &BOB, + &CHARLIE, + SEE, + VESTING_AMOUNT + )); + }); +} diff --git a/blockchain/modules/vesting/src/weights.rs b/blockchain/modules/vesting/src/weights.rs index db35b4f8a..251132c67 100644 --- a/blockchain/modules/vesting/src/weights.rs +++ b/blockchain/modules/vesting/src/weights.rs @@ -10,7 +10,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=orlm_vesting +// --pallet=module_vesting // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -20,6 +20,7 @@ // ..maintain/orml-weight-template.hbs + #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -28,7 +29,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for module_vesting. +/// Weight functions needed for orml_vesting. pub trait WeightInfo { fn vested_transfer() -> Weight; fn claim(i: u32, ) -> Weight; @@ -38,22 +39,22 @@ pub trait WeightInfo { /// Default weights. impl WeightInfo for () { fn vested_transfer() -> Weight { - (69_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + Weight::from_parts(69_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } fn claim(i: u32, ) -> Weight { - (31_747_000 as Weight) + Weight::from_parts(31_747_000, 0) // Standard Error: 4_000 - .saturating_add((63_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(Weight::from_parts(63_000, 0).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } fn update_vesting_schedules(i: u32, ) -> Weight { - (29_457_000 as Weight) + Weight::from_parts(29_457_000, 0) // Standard Error: 4_000 - .saturating_add((117_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(Weight::from_parts(117_000, 0).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } } diff --git a/blockchain/chains/qingdao/node/Cargo.toml b/blockchain/node/Cargo.toml similarity index 100% rename from blockchain/chains/qingdao/node/Cargo.toml rename to blockchain/node/Cargo.toml diff --git a/blockchain/chains/qingdao/node/build.rs b/blockchain/node/build.rs similarity index 100% rename from blockchain/chains/qingdao/node/build.rs rename to blockchain/node/build.rs diff --git a/blockchain/chains/qingdao/node/src/chain_spec.rs b/blockchain/node/src/chain_spec.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/chain_spec.rs rename to blockchain/node/src/chain_spec.rs diff --git a/blockchain/chains/qingdao/node/src/cli.rs b/blockchain/node/src/cli.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/cli.rs rename to blockchain/node/src/cli.rs diff --git a/blockchain/chains/qingdao/node/src/command.rs b/blockchain/node/src/command.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/command.rs rename to blockchain/node/src/command.rs diff --git a/blockchain/chains/qingdao/node/src/lib.rs b/blockchain/node/src/lib.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/lib.rs rename to blockchain/node/src/lib.rs diff --git a/blockchain/chains/qingdao/node/src/main.rs b/blockchain/node/src/main.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/main.rs rename to blockchain/node/src/main.rs diff --git a/blockchain/chains/qingdao/node/src/rpc.rs b/blockchain/node/src/rpc.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/rpc.rs rename to blockchain/node/src/rpc.rs diff --git a/blockchain/chains/qingdao/node/src/service.rs b/blockchain/node/src/service.rs similarity index 100% rename from blockchain/chains/qingdao/node/src/service.rs rename to blockchain/node/src/service.rs diff --git a/blockchain/primitives/Cargo.toml b/blockchain/primitives/Cargo.toml index 15b1de519..3ef2c526b 100644 --- a/blockchain/primitives/Cargo.toml +++ b/blockchain/primitives/Cargo.toml @@ -1,50 +1,55 @@ -[package] -name = "setheum-primitives" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -bstringify = "0.1.2" -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["max-encoded-len"] } -num_enum = { version = "0.5.1", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } - -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -ethereum-types = { version = "0.12.0", default-features = false } -evm = { version = "0.30.1", default-features = false, features = ["with-codec"] } -ethereum = { version = "0.9.0", default-features = false, features = ["with-codec"] } - -[dev-dependencies] -serde_json = { version = "1.0.64" } -hex-literal = "0.3.1" -primitives-proc-macro = { path = "./proc-macro" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "num_enum/std", - - "scale-info/std", - "sp-runtime/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "sp-api/std", - - "frame-support/std", - - "evm/std", - "evm/with-serde", - "ethereum/std", - "ethereum/with-serde", -] \ No newline at end of file +[package] +name = "setheum-primitives" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +log = { workspace = true } +hex-literal = { workspace = true } +bstringify = { workspace = true } +serde = { workspace = true, features = ["alloc", "derive"] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +num_enum = { workspace = true } +scale-info = { workspace = true } +enumflags2 = { workspace = true, features = ["serde"] } +paste = { workspace = true } + +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +sp-api = { workspace = true } + +frame-support = { workspace = true } + +orml-traits = { workspace = true } + +module-evm-utility = { workspace = true } +module-evm-utility-macro = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } + + +[features] +default = ["std"] +std = [ + "serde/std", + "parity-scale-codec/std", + "num_enum/std", + "scale-info/std", + "enumflags2/serde", + "sp-runtime/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "sp-api/std", + + "frame-support/std", + + "orml-traits/std", + "module-evm-utility/std", +] +evm-tests = [] +try-runtime = [] diff --git a/blockchain/primitives/proc-macro/Cargo.toml b/blockchain/primitives/proc-macro/Cargo.toml deleted file mode 100644 index b0eef384c..000000000 --- a/blockchain/primitives/proc-macro/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "primitives-proc-macro" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[lib] -proc-macro = true - -[dependencies] -sha3 = { version = "0.9.1" } -quote = "1.0.3" -syn = { version = "1.0.58", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.6" -proc-macro-crate = "1.0.0" diff --git a/blockchain/primitives/src/bonding/controller.rs b/blockchain/primitives/src/bonding/controller.rs new file mode 100644 index 000000000..ba9b064cd --- /dev/null +++ b/blockchain/primitives/src/bonding/controller.rs @@ -0,0 +1,172 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use frame_support::{dispatch::DispatchResult, pallet_prelude::Member, traits::Get, Parameter, StorageMap}; +use parity_scale_codec::Codec; +use sp_runtime::DispatchError; +use sp_std::prelude::*; + +use super::error::Error; +use super::ledger::{BondingLedger, UnlockChunk}; +use crate::Balance; + +pub type BondingLedgerOf = BondingLedger< + ::Moment, + ::MaxUnbondingChunks, + ::MinBond, +>; + +pub struct BondChange { + pub new: Balance, + pub old: Balance, + pub change: Balance, +} +pub trait BondingController +where + BondingLedgerOf: Codec + Default, + frame_support::BoundedVec< + UnlockChunk<::Moment>, + ::MaxUnbondingChunks, + >: Codec, +{ + type MinBond: Get; + type MaxUnbondingChunks: Get; + type Moment: Ord + Eq + Copy; + type AccountId: Parameter + Member; + + type Ledger: StorageMap, Query = Option>>; + + fn available_balance(who: &Self::AccountId, ledger: &BondingLedgerOf) -> Balance; + fn apply_ledger(who: &Self::AccountId, ledger: &BondingLedgerOf) -> DispatchResult; + fn convert_error(err: Error) -> DispatchError; + + fn bond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).unwrap_or_default(); + + let available = Self::available_balance(who, &ledger); + let bond_amount = amount.min(available); + + if bond_amount == 0 { + return Ok(None); + } + + let old_active = ledger.active(); + + let ledger = ledger.bond(bond_amount).map_err(Self::convert_error)?; + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: bond_amount, + })) + } + + fn unbond(who: &Self::AccountId, amount: Balance, at: Self::Moment) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, unbond_amount) = ledger.unbond(amount, at).map_err(Self::convert_error)?; + + if unbond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: unbond_amount, + })) + } + + fn unbond_instant(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, unbond_amount) = ledger.unbond_instant(amount).map_err(Self::convert_error)?; + + if unbond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: unbond_amount, + })) + } + + fn rebond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, rebond_amount) = ledger.rebond(amount).map_err(Self::convert_error)?; + + if rebond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: rebond_amount, + })) + } + + fn withdraw_unbonded(who: &Self::AccountId, now: Self::Moment) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_total = ledger.total(); + + let ledger = ledger.consolidate_unlocked(now); + + let new_total = ledger.total(); + + let diff = old_total.saturating_sub(new_total); + + if diff == 0 { + return Ok(None); + } + + if new_total == 0 { + Self::Ledger::remove(who); + } else { + Self::Ledger::insert(who, &ledger); + } + + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_total, + new: new_total, + change: diff, + })) + } +} diff --git a/blockchain/primitives/src/bonding/error.rs b/blockchain/primitives/src/bonding/error.rs new file mode 100644 index 000000000..5ed6fbbd8 --- /dev/null +++ b/blockchain/primitives/src/bonding/error.rs @@ -0,0 +1,28 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use sp_runtime::RuntimeDebug; + +#[derive(PartialEq, Eq, RuntimeDebug)] +pub enum Error { + BelowMinBondThreshold, + MaxUnlockChunksExceeded, + NotBonded, +} diff --git a/blockchain/primitives/src/bonding/ledger.rs b/blockchain/primitives/src/bonding/ledger.rs new file mode 100644 index 000000000..8999ac939 --- /dev/null +++ b/blockchain/primitives/src/bonding/ledger.rs @@ -0,0 +1,445 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::error::Error; +use crate::Balance; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, RuntimeDebug}; + +use frame_support::pallet_prelude::*; + +/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be +/// unlocked. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct UnlockChunk { + /// Amount of funds to be unlocked. + value: Balance, + /// Era number at which point it'll be unlocked. + unlock_at: Moment, +} + +/// The ledger of a (bonded) account. +#[derive(PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(MaxUnlockingChunks, MinBond))] +pub struct BondingLedger +where + Moment: Eq + Clone, + MaxUnlockingChunks: Get, +{ + /// The total amount of the account's balance that we are currently + /// accounting for. It's just `active` plus all the `unlocking` + /// balances. + total: Balance, + /// The total amount of the account's balance that will be at stake in + /// any forthcoming rounds. + active: Balance, + /// Any balance that is becoming free, which may eventually be + /// transferred out of the account. + unlocking: BoundedVec, MaxUnlockingChunks>, + + _phantom: PhantomData, +} + +impl BondingLedger +where + Moment: Ord + Eq + Copy, + MaxUnlockingChunks: Get, + MinBond: Get, +{ + pub fn new() -> Self { + Default::default() + } + + pub fn active(&self) -> Balance { + self.active + } + + pub fn total(&self) -> Balance { + self.total + } + + pub fn unlocking_len(&self) -> usize { + self.unlocking.len() + } + + /// Bond more funds. + pub fn bond(mut self, amount: Balance) -> Result { + self.active = self.active.saturating_add(amount); + self.total = self.total.saturating_add(amount); + self.check_min_bond()?; + Ok(self) + } + + /// Start unbonding and create new UnlockChunk. + /// Note that if the `unlock_at` is same as the last UnlockChunk, they will be merged. + pub fn unbond(mut self, amount: Balance, unlock_at: Moment) -> Result<(Self, Balance), Error> { + let amount = amount.min(self.active); + self.active = self.active.saturating_sub(amount); + self.check_min_bond()?; + self.unlocking = self + .unlocking + .try_mutate(|unlocking| { + // try merge if the last chunk unlock time is same + if let Some(last) = unlocking.last_mut() { + if last.unlock_at == unlock_at { + last.value = last.value.saturating_add(amount); + return; + } + } + // or make a new one + unlocking.push(UnlockChunk { + value: amount, + unlock_at, + }); + }) + .ok_or(Error::MaxUnlockChunksExceeded)?; + Ok((self, amount)) + } + + pub fn unbond_instant(mut self, amount: Balance) -> Result<(Self, Balance), Error> { + let amount = amount.min(self.active); + self.active = self.active.saturating_sub(amount); + self.total = self.total.saturating_sub(amount); + self.check_min_bond()?; + Ok((self, amount)) + } + + /// Remove entries from `unlocking` that are sufficiently old and reduce + /// the total by the sum of their balances. + #[must_use] + pub fn consolidate_unlocked(mut self, now: Moment) -> Self { + let mut total = self.total; + self.unlocking.retain(|chunk| { + if chunk.unlock_at > now { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }); + + self.total = total; + + self + } + + /// Re-bond funds that were scheduled for unlocking. + pub fn rebond(mut self, value: Balance) -> Result<(Self, Balance), Error> { + let mut unlocking_balance: Balance = Zero::zero(); + + self.unlocking = self + .unlocking + .try_mutate(|unlocking| { + while let Some(last) = unlocking.last_mut() { + if unlocking_balance + last.value <= value { + unlocking_balance += last.value; + self.active += last.value; + unlocking.pop(); + } else { + let diff = value - unlocking_balance; + + unlocking_balance += diff; + self.active += diff; + last.value -= diff; + } + + if unlocking_balance >= value { + break; + } + } + }) + .expect("Only popped elements from inner_vec"); + + self.check_min_bond()?; + + Ok((self, unlocking_balance)) + } + + pub fn is_empty(&self) -> bool { + self.total.is_zero() + } + + fn check_min_bond(&self) -> Result<(), Error> { + if self.active > 0 && self.active < MinBond::get() { + return Err(Error::BelowMinBondThreshold); + } + Ok(()) + } +} + +impl Default for BondingLedger +where + Moment: Ord + Eq + Copy, + MaxUnlockingChunks: Get, + MinBond: Get, +{ + fn default() -> Self { + Self { + unlocking: Default::default(), + total: Default::default(), + active: Default::default(), + _phantom: Default::default(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_err, + traits::{ConstU128, ConstU32}, + }; + use sp_runtime::bounded_vec; + + type Ledger = BondingLedger, ConstU128<10>>; + + #[test] + fn bond_works() { + let ledger = Ledger::new(); + assert!(ledger.is_empty()); + assert_err!(ledger.clone().bond(9), Error::BelowMinBondThreshold); + + let ledger = ledger.bond(10).unwrap(); + assert!(!ledger.is_empty()); + assert_eq!( + ledger, + Ledger { + total: 10, + active: 10, + unlocking: Default::default(), + _phantom: Default::default(), + } + ); + + let ledger = ledger.bond(100).unwrap(); + assert_eq!( + ledger, + Ledger { + total: 110, + active: 110, + unlocking: Default::default(), + _phantom: Default::default(), + } + ); + } + + #[test] + fn unbond_works() { + let ledger = Ledger::new(); + let ledger = ledger.bond(100).unwrap(); + assert_err!(ledger.clone().unbond(99, 2), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.unbond(20, 2).unwrap(); + assert_eq!(actual, 20); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 80, + unlocking: bounded_vec![UnlockChunk { + value: 20, + unlock_at: 2, + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(10, 2).unwrap(); + assert_eq!(actual, 10); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 70, + unlocking: bounded_vec![UnlockChunk { + value: 30, + unlock_at: 2, + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(5, 4).unwrap(); + assert_eq!(actual, 5); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 65, + unlocking: bounded_vec![ + UnlockChunk { + value: 30, + unlock_at: 2, + }, + UnlockChunk { value: 5, unlock_at: 4 } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(1); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 65, + unlocking: bounded_vec![ + UnlockChunk { + value: 30, + unlock_at: 2, + }, + UnlockChunk { value: 5, unlock_at: 4 } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(2); + assert_eq!( + ledger, + Ledger { + total: 70, + active: 65, + unlocking: bounded_vec![UnlockChunk { value: 5, unlock_at: 4 }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(100, 6).unwrap(); + assert_eq!(actual, 65); + assert_eq!( + ledger, + Ledger { + total: 70, + active: 0, + unlocking: bounded_vec![ + UnlockChunk { value: 5, unlock_at: 4 }, + UnlockChunk { + value: 65, + unlock_at: 6, + } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(4); + assert_eq!( + ledger, + Ledger { + total: 65, + active: 0, + unlocking: bounded_vec![UnlockChunk { + value: 65, + unlock_at: 6, + }], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(6); + assert_eq!( + ledger, + Ledger { + total: 0, + active: 0, + unlocking: bounded_vec![], + _phantom: Default::default(), + } + ); + assert!(ledger.is_empty()); + } + + #[test] + fn unbond_instant_works() { + let ledger = Ledger::new(); + let ledger = ledger.bond(100).unwrap(); + assert_err!(ledger.clone().unbond_instant(99), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.unbond_instant(20).unwrap(); + assert_eq!(actual, 20); + + let (_ledger, actual) = ledger.unbond_instant(100).unwrap(); + assert_eq!(actual, 80); + } + + #[test] + fn rebond_works() { + let ledger = Ledger::new(); + + let (ledger, _) = ledger + .bond(100) + .and_then(|ledger| ledger.unbond(50, 2)) + .and_then(|(ledger, _)| ledger.unbond(50, 3)) + .unwrap(); + + assert_err!(ledger.clone().rebond(1), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.rebond(20).unwrap(); + assert_eq!(actual, 20); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 20, + unlocking: bounded_vec![ + UnlockChunk { + value: 50, + unlock_at: 2 + }, + UnlockChunk { + value: 30, + unlock_at: 3 + } + ], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.rebond(40).unwrap(); + assert_eq!(actual, 40); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 60, + unlocking: bounded_vec![UnlockChunk { + value: 40, + unlock_at: 2 + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.rebond(50).unwrap(); + assert_eq!(actual, 40); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 100, + unlocking: bounded_vec![], + _phantom: Default::default(), + } + ); + } +} diff --git a/blockchain/primitives/src/bonding/mod.rs b/blockchain/primitives/src/bonding/mod.rs new file mode 100644 index 000000000..5d46ed683 --- /dev/null +++ b/blockchain/primitives/src/bonding/mod.rs @@ -0,0 +1,27 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +mod controller; +mod error; +mod ledger; + +pub use self::controller::*; +pub use self::error::*; +pub use self::ledger::*; diff --git a/blockchain/primitives/src/currency.rs b/blockchain/primitives/src/currency.rs index b8fe4553d..4f0032bf7 100644 --- a/blockchain/primitives/src/currency.rs +++ b/blockchain/primitives/src/currency.rs @@ -22,16 +22,12 @@ use crate::{evm::EvmAddress, *}; use bstringify::bstringify; -use codec::{Decode, Encode}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; -use sp_std::{ - convert::{Into, TryFrom}, - prelude::*, vec, -}; +use sp_std::prelude::*; -#[cfg(feature = "std")] use serde::{Deserialize, Serialize}; macro_rules! create_currency_id { @@ -122,64 +118,78 @@ macro_rules! create_currency_id { address: EvmAddress, } - let mut tokens = vec![ - $( - Token { + // Setheum tokens + let mut tokens = vec![]; + $( + if $val < 128 { + tokens.push(Token { symbol: stringify!($symbol).to_string(), address: EvmAddress::try_from(CurrencyId::Token(TokenSymbol::$symbol)).unwrap(), - }, - )* - ]; + }); + } + )* - // TODO: Remove let mut lp_tokens = vec![ + // SETR PAIRED POOLS Token { - symbol: "LP_SETM_SETUSD".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(SEE), DexShare::Token(SETUSD))).unwrap(), + symbol: "LP_SEE_SETR".to_string(), + address: EvmAddress::try_from(TradingPair::from_currency_ids(CurrencyId::Token(SETR), CurrencyId::Token(SEE)).unwrap().dex_share_currency_id()).unwrap(), }, Token { - symbol: "LP_DNAR_SETUSD".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(DNAR), DexShare::Token(SETUSD))).unwrap(), + symbol: "LP_EDF_SETR".to_string(), + address: EvmAddress::try_from(TradingPair::from_currency_ids(CurrencyId::Token(SETR), CurrencyId::Token(EDF)).unwrap().dex_share_currency_id()).unwrap(), }, Token { - symbol: "LP_HELP_SETUSD".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(HELP), DexShare::Token(SETUSD))).unwrap(), + symbol: "LP_LSEE_SETR".to_string(), + address: EvmAddress::try_from(TradingPair::from_currency_ids(CurrencyId::Token(SETR), CurrencyId::Token(LSEE)).unwrap().dex_share_currency_id()).unwrap(), }, Token { - symbol: "LP_SETM_SETR".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(SEE), DexShare::Token(SETR))).unwrap(), + symbol: "LP_LEDF_SETR".to_string(), + address: EvmAddress::try_from(TradingPair::from_currency_ids(CurrencyId::Token(SETR), CurrencyId::Token(LEDF)).unwrap().dex_share_currency_id()).unwrap(), }, Token { - symbol: "LP_SERP_SETR".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(SERP), DexShare::Token(SETR))).unwrap(), + symbol: "LP_USSD_SETR".to_string(), + address: EvmAddress::try_from(TradingPair::from_currency_ids(CurrencyId::Token(SETR), CurrencyId::Token(USSD)).unwrap().dex_share_currency_id()).unwrap(), }, + ]; + tokens.append(&mut lp_tokens); + + let mut fa_tokens = vec![ Token { - symbol: "LP_DNAR_SETR".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(DNAR), DexShare::Token(SETR))).unwrap(), + symbol: "FA_WBTC".to_string(), + address: EvmAddress::try_from(CurrencyId::ForeignAsset(5)).unwrap(), }, Token { - symbol: "LP_HELP_SETR".to_string(), - address: EvmAddress::try_from(CurrencyId::DexShare(DexShare::Token(HELP), DexShare::Token(SETR))).unwrap(), + symbol: "FA_WETH".to_string(), + address: EvmAddress::try_from(CurrencyId::ForeignAsset(6)).unwrap(), }, ]; - tokens.append(&mut lp_tokens); + tokens.append(&mut fa_tokens); - frame_support::assert_ok!(std::fs::write("../submodules/predeploy-contracts/resources/tokens.json", serde_json::to_string_pretty(&tokens).unwrap())); + frame_support::assert_ok!(std::fs::write("../predeploy-contracts/resources/tokens.json", serde_json::to_string_pretty(&tokens).unwrap())); } } } create_currency_id! { // Represent a Token symbol with 8 bit - #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + // + // 0 - 19: Setheum native tokens + // 20 - 255: Reserved for future usage + #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen, Serialize, Deserialize)] #[repr(u8)] pub enum TokenSymbol { - SEE("Setheum", 18) = 0, - KHA("Khalifa", 18) = 1, - SETR("Setter", 18) = 4, // Stablecoin - GRA("Golden Ratio", 18) = 2, // Stablecoin - USSD("SetDollar", 18) = 5, // Stablecoin + // 0 - 128: Reserved for Setheum Native Assets + // Primary Protocol Tokens + SEE("Setheum", 12) = 0, + EDF("Ethical DeFi", 12) = 1, + // Liquid Staking Tokens + LSEE("Liquid SEE", 12) = 2, + LEDF("Liquid EDF", 12) = 3, + // ECDP Stablecoin Tokens + SETR("Setter", 12) = 4, + USSD("Slick USD", 12) = 5, + // 128 - 255: Reserved for future usage } } @@ -190,21 +200,52 @@ pub trait TokenInfo { fn decimals(&self) -> Option; } -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub type ForeignAssetId = u16; +pub type Erc20Id = u32; + +#[derive( + Encode, + Decode, + Eq, + PartialEq, + Copy, + Clone, + RuntimeDebug, + PartialOrd, + Ord, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +#[serde(rename_all = "camelCase")] pub enum DexShare { Token(TokenSymbol), Erc20(EvmAddress), + ForeignAsset(ForeignAssetId), } -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + Encode, + Decode, + Eq, + PartialEq, + Copy, + Clone, + RuntimeDebug, + PartialOrd, + Ord, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +#[serde(rename_all = "camelCase")] pub enum CurrencyId { Token(TokenSymbol), DexShare(DexShare, DexShare), Erc20(EvmAddress), + ForeignAsset(ForeignAssetId), } impl CurrencyId { @@ -220,10 +261,16 @@ impl CurrencyId { matches!(self, CurrencyId::Erc20(_)) } + pub fn is_foreign_asset_currency_id(&self) -> bool { + matches!(self, CurrencyId::ForeignAsset(_)) + } + pub fn is_trading_pair_currency_id(&self) -> bool { matches!( self, - CurrencyId::Token(_) | CurrencyId::Erc20(_) + CurrencyId::Token(_) + | CurrencyId::Erc20(_) + | CurrencyId::ForeignAsset(_) ) } @@ -242,36 +289,27 @@ impl CurrencyId { let dex_share_0 = match currency_id_0 { CurrencyId::Token(symbol) => DexShare::Token(symbol), CurrencyId::Erc20(address) => DexShare::Erc20(address), + CurrencyId::ForeignAsset(foreign_asset_id) => DexShare::ForeignAsset(foreign_asset_id), // Unsupported CurrencyId::DexShare(..) => return None, }; let dex_share_1 = match currency_id_1 { CurrencyId::Token(symbol) => DexShare::Token(symbol), CurrencyId::Erc20(address) => DexShare::Erc20(address), + CurrencyId::ForeignAsset(foreign_asset_id) => DexShare::ForeignAsset(foreign_asset_id), // Unsupported CurrencyId::DexShare(..) => return None, }; Some(CurrencyId::DexShare(dex_share_0, dex_share_1)) } -} -/// H160 CurrencyId Type enum -#[derive( - Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TryFromPrimitive, IntoPrimitive, TypeInfo, -)] -#[repr(u8)] -pub enum CurrencyIdType { - Token = 1, // 0 is prefix of precompile and predeploy - DexShare, -} - -#[derive( - Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TryFromPrimitive, IntoPrimitive, TypeInfo, -)] -#[repr(u8)] -pub enum DexShareType { - Token, - Erc20, + pub fn erc20_address(&self) -> Option { + match self { + CurrencyId::Erc20(address) => Some(*address), + CurrencyId::Token(_) => EvmAddress::try_from(*self).ok(), + _ => None, + } + } } impl From for u32 { @@ -289,6 +327,9 @@ impl From for u32 { let index = if leading_zeros > 16 { 16 } else { leading_zeros }; bytes[..].copy_from_slice(&address[index..index + 4][..]); } + DexShare::ForeignAsset(foreign_asset_id) => { + bytes[2..].copy_from_slice(&foreign_asset_id.to_be_bytes()); + } } u32::from_be_bytes(bytes) } @@ -299,43 +340,53 @@ impl Into for DexShare { match self { DexShare::Token(token) => CurrencyId::Token(token), DexShare::Erc20(address) => CurrencyId::Erc20(address), + DexShare::ForeignAsset(foreign_asset_id) => CurrencyId::ForeignAsset(foreign_asset_id), } } } +/// H160 CurrencyId Type enum +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TryFromPrimitive, IntoPrimitive, TypeInfo, +)] +#[repr(u8)] +pub enum CurrencyIdType { + Token = 1, // 0 is prefix of precompile and predeploy + DexShare, + ForeignAsset, +} + +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TryFromPrimitive, IntoPrimitive, TypeInfo, +)] +#[repr(u8)] +pub enum DexShareType { + Token, + Erc20, + ForeignAsset, +} + impl Into for DexShare { fn into(self) -> DexShareType { match self { DexShare::Token(_) => DexShareType::Token, DexShare::Erc20(_) => DexShareType::Erc20, + DexShare::ForeignAsset(_) => DexShareType::ForeignAsset, } } } -/// Generate the EvmAddress from CurrencyId so that evm contracts can call the erc20 contract. -impl TryFrom for EvmAddress { - type Error = (); +#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] +pub enum AssetIds { + Erc20(EvmAddress), + ForeignAssetId(ForeignAssetId), + NativeAssetId(CurrencyId), +} - fn try_from(val: CurrencyId) -> Result { - match val { - CurrencyId::Token(_) => Ok(EvmAddress::from_low_u64_be( - MIRRORED_TOKENS_ADDRESS_START | u64::from(val.currency_id().unwrap()), - )), - CurrencyId::DexShare(token_symbol_0, token_symbol_1) => { - let symbol_0 = match token_symbol_0 { - DexShare::Token(token) => CurrencyId::Token(token).currency_id().ok_or(()), - DexShare::Erc20(_) => Err(()), - }?; - let symbol_1 = match token_symbol_1 { - DexShare::Token(token) => CurrencyId::Token(token).currency_id().ok_or(()), - DexShare::Erc20(_) => Err(()), - }?; - - let mut prefix = EvmAddress::default(); - prefix[0..H160_PREFIX_DEXSHARE.len()].copy_from_slice(&H160_PREFIX_DEXSHARE); - Ok(prefix | EvmAddress::from_low_u64_be(u64::from(symbol_0) << 32 | u64::from(symbol_1))) - } - CurrencyId::Erc20(address) => Ok(address), - } - } +#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] +pub struct AssetMetadata { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub minimal_balance: Balance, } diff --git a/blockchain/primitives/src/evm.rs b/blockchain/primitives/src/evm.rs index 22dcea948..4a18bbfe3 100644 --- a/blockchain/primitives/src/evm.rs +++ b/blockchain/primitives/src/evm.rs @@ -18,23 +18,44 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{Balance, BlockNumber, Nonce}; -use codec::{Decode, Encode}; -use evm::ExitReason; +use crate::{ + currency::{CurrencyId, CurrencyIdType, DexShareType}, + Balance, BlockNumber, Nonce, +}; +use core::ops::Range; +use hex_literal::hex; +pub use module_evm_utility::{ + ethereum::{AccessListItem, Log, TransactionAction}, + evm::ExitReason, +}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::{H160, H256, U256}; -use sp_runtime::RuntimeDebug; +use sp_runtime::{traits::Zero, RuntimeDebug, SaturatedConversion}; use sp_std::vec::Vec; -pub use ethereum::TransactionAction; -pub use evm::backend::{Basic as Account, Log}; -pub use evm::Config; - /// Evm Address. pub type EvmAddress = sp_core::H160; -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug)] +/// Setheum Mainnet 258 +pub const CHAIN_ID_SETHEUM_MAINNET: u64 = 787u64; +/// Setheum Damascus Testnet 2580 +pub const CHAIN_ID_DAMASCUS_TESTNET: u64 = 597u64; +/// Qingdao Devnet 2581 +pub const CHAIN_ID_QINGDAO_DEVNET: u64 = 595u64; + +// GAS MASK +const GAS_MASK: u64 = 100_000u64; +// STORAGE MASK +const STORAGE_MASK: u64 = 100u64; +// GAS LIMIT CHUNK +const GAS_LIMIT_CHUNK: u64 = 30_000u64; +// MAX GAS_LIMIT CC, log2(BLOCK_STORAGE_LIMIT) +pub const MAX_GAS_LIMIT_CC: u32 = 21u32; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] /// External input from the transaction. pub struct Vicinity { @@ -42,9 +63,19 @@ pub struct Vicinity { pub gas_price: U256, /// Origin of the transaction. pub origin: EvmAddress, + /// Environmental coinbase. + pub block_coinbase: Option, + /// Environmental block gas limit. Used only for testing + pub block_gas_limit: Option, + /// Environmental block difficulty. Used only for testing + pub block_difficulty: Option, + /// Environmental base fee per gas. + pub block_base_fee_per_gas: Option, + /// Environmental randomness. + pub block_randomness: Option, } -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ExecutionInfo { pub exit_reason: ExitReason, @@ -57,16 +88,16 @@ pub struct ExecutionInfo { pub type CallInfo = ExecutionInfo>; pub type CreateInfo = ExecutionInfo; -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Erc20Info { - pub address: EvmAddress, - pub name: Vec, - pub symbol: Vec, - pub decimals: u8, +pub struct BlockLimits { + /// Max gas limit + pub max_gas_limit: u64, + /// Max storage limit + pub max_storage_limit: u32, } -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct EstimateResourcesRequest { /// From @@ -81,38 +112,209 @@ pub struct EstimateResourcesRequest { pub value: Option, /// Data pub data: Option>, + /// AccessList + pub access_list: Option>, } -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct EthereumTransactionMessage { + pub chain_id: u64, + pub genesis: H256, pub nonce: Nonce, pub tip: Balance, + pub gas_price: u64, pub gas_limit: u64, pub storage_limit: u32, pub action: TransactionAction, pub value: Balance, pub input: Vec, - pub chain_id: u64, - pub genesis: H256, pub valid_until: BlockNumber, + pub access_list: Vec, +} + +/// Ethereum precompiles +/// 0 - 0x0000000000000000000000000000000000000400 +/// Setheum precompiles +/// 0x0000000000000000000000000000000000000400 - 0x0000000000000000000000000000000000000800 +pub const PRECOMPILE_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000400")); +/// Predeployed system contracts (except Mirrored ERC20) +/// 0x0000000000000000000000000000000000000800 - 0x0000000000000000000000000000000000001000 +pub const PREDEPLOY_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000800")); +pub const MIRRORED_TOKENS_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000100000000000000000000")); +pub const MIRRORED_NFT_ADDRESS_START: u64 = 0x2000000; +/// ERC20 Holding Account used for transfer ERC20 token +pub const ERC20_HOLDING_ACCOUNT: EvmAddress = H160(hex_literal::hex!("000000000000000000ff00000000000000000000")); +/// System contract address prefix +pub const SYSTEM_CONTRACT_ADDRESS_PREFIX: [u8; 9] = [0u8; 9]; + +#[rustfmt::skip] +/// CurrencyId to H160([u8; 20]) bit encoding rule. +/// +/// Type occupies 1 byte, and data occupies 4 bytes(less than 4 bytes, right justified). +/// +/// 0x0000000000000000000000000000000000000000 +/// 0 1 2 3 4 5 6 7 8 910111213141516171819 index +/// ^^^^^^^^^^^^^^^^^^ System contract address prefix +/// ^^ CurrencyId Type: 1-Token 2-DexShare +/// 3-ForeignAsset(ignore Erc20, without the prefix of system contracts) +/// FF-Erc20 Holding Account +/// ^^ CurrencyId Type is 1-Token, Token +/// ^^^^^^^^ CurrencyId Type is 1-Token, NFT +/// ^^ CurrencyId Type is 2-DexShare, DexShare Left Type: +/// 0-Token 1-Erc20 2-ForeignAsset +/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare left field +/// ^^ CurrencyId Type is 2-DexShare, DexShare Right Type: +/// the same as DexShare Left Type +/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare right field +/// ^^^^^^^^ CurrencyId Type is 3-ForeignAsset, ForeignAssetId + +/// Check if the given `address` is a system contract. +/// +/// It's system contract if the address starts with SYSTEM_CONTRACT_ADDRESS_PREFIX. +pub fn is_system_contract(address: &EvmAddress) -> bool { + address.as_bytes().starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) +} + +pub const H160_POSITION_CURRENCY_ID_TYPE: usize = 9; +pub const H160_POSITION_TOKEN: usize = 19; +pub const H160_POSITION_TOKEN_NFT: Range = 16..20; +pub const H160_POSITION_DEXSHARE_LEFT_TYPE: usize = 10; +pub const H160_POSITION_DEXSHARE_LEFT_FIELD: Range = 11..15; +pub const H160_POSITION_DEXSHARE_RIGHT_TYPE: usize = 15; +pub const H160_POSITION_DEXSHARE_RIGHT_FIELD: Range = 16..20; +pub const H160_POSITION_FOREIGN_ASSET: Range = 18..20; + +/// Generate the EvmAddress from CurrencyId so that evm contracts can call the erc20 contract. +/// NOTE: Can not be used directly, need to check the erc20 is mapped. +impl TryFrom for EvmAddress { + type Error = (); + + fn try_from(val: CurrencyId) -> Result { + let mut address = [0u8; 20]; + match val { + CurrencyId::Token(token) => { + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::Token.into(); + address[H160_POSITION_TOKEN] = token.into(); + } + CurrencyId::DexShare(left, right) => { + let left_field: u32 = left.into(); + let right_field: u32 = right.into(); + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::DexShare.into(); + address[H160_POSITION_DEXSHARE_LEFT_TYPE] = Into::::into(left).into(); + address[H160_POSITION_DEXSHARE_LEFT_FIELD].copy_from_slice(&left_field.to_be_bytes()); + address[H160_POSITION_DEXSHARE_RIGHT_TYPE] = Into::::into(right).into(); + address[H160_POSITION_DEXSHARE_RIGHT_FIELD].copy_from_slice(&right_field.to_be_bytes()); + } + CurrencyId::Erc20(erc20) => { + address[..].copy_from_slice(erc20.as_bytes()); + } + CurrencyId::ForeignAsset(foreign_asset_id) => { + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::ForeignAsset.into(); + address[H160_POSITION_FOREIGN_ASSET].copy_from_slice(&foreign_asset_id.to_be_bytes()); + } + }; + + Ok(EvmAddress::from_slice(&address)) + } +} + +pub fn decode_gas_price(gas_price: u64, gas_limit: u64, tx_fee_per_gas: u128) -> Option<(u128, u32)> { + // ensure gas_price >= 100 Gwei + if u128::from(gas_price) < tx_fee_per_gas { + return None; + } + + let mut tip: u128 = 0; + let mut actual_gas_price = gas_price; + const TEN_GWEI: u64 = 10_000_000_000u64; + + // tip = 10% * tip_number + let tip_number = gas_price.checked_div(TEN_GWEI)?.checked_sub(10)?; + if !tip_number.is_zero() { + actual_gas_price = gas_price.checked_sub(tip_number.checked_mul(TEN_GWEI)?)?; + tip = actual_gas_price + .checked_mul(gas_limit)? + .checked_mul(tip_number)? + .checked_div(10)? // percentage + .checked_div(1_000_000)? // SEE decimal is 12, ETH decimal is 18 + .into(); + } + + // valid_until max is u32::MAX. + let valid_until: u32 = Into::::into(actual_gas_price) + .checked_sub(tx_fee_per_gas)? + .saturated_into(); + + Some((tip, valid_until)) } -/// A mapping between `AccountId` and `EvmAddress`. -pub trait AddressMapping { - /// Returns the AccountId used go generate the given EvmAddress. - fn get_account_id(evm: &EvmAddress) -> AccountId; - /// Returns the EvmAddress associated with a given AccountId or the - /// underlying EvmAddress of the AccountId. - /// Returns None if there is no EvmAddress associated with the AccountId - /// and there is no underlying EvmAddress in the AccountId. - fn get_evm_address(account_id: &AccountId) -> Option; - /// Returns the EVM address associated with an account ID and generates an - /// account mapping if no association exists. - fn get_or_create_evm_address(account_id: &AccountId) -> EvmAddress; - /// Returns the default EVM address associated with an account ID. - fn get_default_evm_address(account_id: &AccountId) -> EvmAddress; - /// Returns true if a given AccountId is associated with a given EvmAddress - /// and false if is not. - fn is_linked(account_id: &AccountId, evm: &EvmAddress) -> bool; +pub fn decode_gas_limit(gas_limit: u64) -> (u64, u32) { + let gas_and_storage: u64 = gas_limit.checked_rem(GAS_MASK).expect("constant never failed; qed"); + let actual_gas_limit: u64 = gas_and_storage + .checked_div(STORAGE_MASK) + .expect("constant never failed; qed") + .saturating_mul(GAS_LIMIT_CHUNK); + let storage_limit_number: u32 = gas_and_storage + .checked_rem(STORAGE_MASK) + .expect("constant never failed; qed") + .try_into() + .expect("STORAGE_MASK is 100, the result maximum is 99; qed"); + + let actual_storage_limit = if storage_limit_number.is_zero() { + Default::default() + } else if storage_limit_number > MAX_GAS_LIMIT_CC { + 2u32.saturating_pow(MAX_GAS_LIMIT_CC) + } else { + 2u32.saturating_pow(storage_limit_number) + }; + + (actual_gas_limit, actual_storage_limit) } + +#[cfg(not(feature = "evm-tests"))] +mod convert { + use sp_runtime::traits::{CheckedDiv, Saturating, Zero}; + + /// Convert decimal between native(12) and EVM(18) and therefore the 1_000_000 conversion. + const DECIMALS_VALUE: u32 = 1_000_000u32; + + /// Convert decimal from native(SEE 12) to EVM(18). + pub fn convert_decimals_to_evm>(b: B) -> B { + if b.is_zero() { + return b; + } + b.saturating_mul(DECIMALS_VALUE.into()) + } + + /// Convert decimal from EVM(18) to native(SEE 12). + pub fn convert_decimals_from_evm>( + b: B, + ) -> Option { + if b.is_zero() { + return Some(b); + } + let res = b + .checked_div(&Into::::into(DECIMALS_VALUE)) + .expect("divisor is non-zero; qed"); + + if res.saturating_mul(DECIMALS_VALUE.into()) == b { + Some(res) + } else { + None + } + } +} + +#[cfg(feature = "evm-tests")] +mod convert { + pub fn convert_decimals_to_evm(b: B) -> B { + b + } + + pub fn convert_decimals_from_evm(b: B) -> Option { + Some(b) + } +} + +pub use convert::*; diff --git a/blockchain/primitives/src/lib.rs b/blockchain/primitives/src/lib.rs index 4c0aa0a8b..ed2112093 100644 --- a/blockchain/primitives/src/lib.rs +++ b/blockchain/primitives/src/lib.rs @@ -22,77 +22,34 @@ #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] +pub mod bonding; pub mod currency; pub mod evm; +pub mod nft; pub mod signature; pub mod task; +pub mod testing; +pub mod unchecked_extrinsic; -use codec::{Decode, Encode, MaxEncodedLen}; +pub use testing::*; + +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; use sp_core::U256; -use core::ops::Range; use sp_runtime::{ generic, traits::{BlakeTwo256, IdentifyAccount, Verify}, - RuntimeDebug, + FixedU128, RuntimeDebug, }; +use sp_std::prelude::*; pub use currency::{CurrencyId, DexShare, TokenSymbol}; - -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; +pub use evm::{convert_decimals_from_evm, convert_decimals_to_evm}; #[cfg(test)] mod tests; -/// Ethereum precompiles -/// 0 - 0x400 -/// Setheum precompiles -/// 0x400 - 0x800 -pub const PRECOMPILE_ADDRESS_START: u64 = 0x400; -/// Predeployed system contracts (except Mirrored ERC20) -/// 0x800 - 0x1000 -pub const PREDEPLOY_ADDRESS_START: u64 = 0x800; -/// Mirrored Tokens (ensure length <= 4 bytes, encode to u32 will take the first 4 non-zero bytes) -/// 0x1000000 -pub const MIRRORED_TOKENS_ADDRESS_START: u64 = 0x1000000; -/// Mirrored NFT (ensure length <= 4 bytes, encode to u32 will take the first 4 non-zero bytes) -/// 0x2000000 -pub const MIRRORED_NFT_ADDRESS_START: u64 = 0x2000000; -/// Mirrored LP Tokens -/// 0x10000000000000000 -pub const MIRRORED_LP_TOKENS_ADDRESS_START: u128 = 0x10000000000000000; -/// System contract address prefix -pub const SYSTEM_CONTRACT_ADDRESS_PREFIX: [u8; 11] = [0u8; 11]; -/// Network contracts -/// 0x1000 - 0x01000000 -pub const NETWORK_CONTRACT_START: u64 = 0x1000; - -/// CurrencyId to H160([u8; 20]) bit encoding rule. -/// -/// Token -/// v[16] = 1 // MIRRORED_TOKENS_ADDRESS_START -/// - v[19] = token(1 byte) -/// -/// DexShare -/// v[11] = 1 // MIRRORED_LP_TOKENS_ADDRESS_START -/// - v[12..16] = dex left(4 bytes) -/// - v[16..20] = dex right(4 bytes) -/// -/// Erc20 -/// - v[0..20] = evm address(20 bytes) -pub const H160_TYPE_TOKEN: u8 = 1; -pub const H160_TYPE_DEXSHARE: u8 = 1; -pub const H160_POSITION_TOKEN: usize = 19; -pub const H160_POSITION_DEXSHARE_LEFT: Range = 12..16; -pub const H160_POSITION_DEXSHARE_RIGHT: Range = 16..20; -pub const H160_POSITION_ERC20: Range = 0..20; -pub const H160_PREFIX_TOKEN: [u8; 19] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]; -pub const H160_PREFIX_DEXSHARE: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; - -/// NFT Balance type -pub type NFTBalance = u128; - /// An index to a block. pub type BlockNumber = u32; @@ -155,29 +112,33 @@ pub type BlockId = generic::BlockId; /// Opaque, encoded, unchecked extrinsic. pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum SerpStableCurrencyId { - SETR = 0, - SETUSD = 1, -} +/// Fee multiplier. +pub type Multiplier = FixedU128; -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, +)] pub enum AuthoritysOriginId { Root, Treasury, + LiquidSeeStakingTreasury, + LiquidEdfStakingTreasury, + SetterEcdpTreasury, + SlickUsdEcdpTreasury, + TreasuryReserve, } -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, +)] pub enum DataProviderId { Aggregated = 0, Setheum = 1, } -#[derive(Encode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive( + Encode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen, Serialize, Deserialize, +)] pub struct TradingPair(CurrencyId, CurrencyId); impl TradingPair { @@ -211,20 +172,33 @@ impl TradingPair { } impl Decode for TradingPair { - fn decode(input: &mut I) -> sp_std::result::Result { + fn decode(input: &mut I) -> sp_std::result::Result { let (first, second): (CurrencyId, CurrencyId) = Decode::decode(input)?; - TradingPair::from_currency_ids(first, second).ok_or_else(|| codec::Error::from("invalid currency id")) + TradingPair::from_currency_ids(first, second) + .ok_or_else(|| parity_scale_codec::Error::from("invalid currency id")) } } +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] +pub struct Position { + /// The amount of collateral. + pub collateral: Balance, + /// The amount of debit. + pub debit: Balance, +} + #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, MaxEncodedLen, TypeInfo)] #[repr(u8)] pub enum ReserveIdentifier { + CollatorSelection, EvmStorageDeposit, EvmDeveloperDeposit, - Setmint, + SetterEcdp, + SlickUsdEcdp, Nft, TransactionPayment, + TransactionPaymentDeposit, + // always the last, indicate number of variants Count, } diff --git a/blockchain/primitives/src/nft.rs b/blockchain/primitives/src/nft.rs new file mode 100644 index 000000000..407f803fa --- /dev/null +++ b/blockchain/primitives/src/nft.rs @@ -0,0 +1,75 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_scale_codec::{Decode, Encode}; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; +use serde::{Deserialize, Serialize}; + +use sp_runtime::RuntimeDebug; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; + +use enumflags2::{bitflags, BitFlags}; + +pub type NFTBalance = u128; +pub type CID = Vec; +pub type Attributes = BTreeMap, Vec>; + +#[bitflags] +#[repr(u8)] +#[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum ClassProperty { + /// Is token transferable + Transferable = 0b00000001, + /// Is token burnable + Burnable = 0b00000010, + /// Is minting new tokens allowed + Mintable = 0b00000100, + /// Is class properties mutable + ClassPropertiesMutable = 0b00001000, +} + +#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug, Serialize, Deserialize)] +pub struct Properties(pub BitFlags); + +impl Eq for Properties {} +impl Encode for Properties { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for Properties { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u8::decode(input)?; + Ok(Self( + >::from_bits(field as u8).map_err(|_| "invalid value")?, + )) + } +} + +impl TypeInfo for Properties { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("ClassProperty"))) + } +} diff --git a/blockchain/primitives/src/signature.rs b/blockchain/primitives/src/signature.rs index d21d85779..c67e1c10d 100644 --- a/blockchain/primitives/src/signature.rs +++ b/blockchain/primitives/src/signature.rs @@ -18,17 +18,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use codec::{Decode, Encode}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; use sp_runtime::{ traits::{Lazy, Verify}, AccountId32, MultiSigner, RuntimeDebug, }; -use sp_core::{crypto::Public, ecdsa, ed25519, sr25519}; +use sp_core::{crypto::ByteArray, ecdsa, ed25519, sr25519}; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; -#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug)] +#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum SetheumMultiSignature { /// An Ed25519 signature. Ed25519(ed25519::Signature), @@ -38,8 +39,12 @@ pub enum SetheumMultiSignature { Ecdsa(ecdsa::Signature), // An Ethereum compatible SECP256k1 signature. Ethereum([u8; 65]), + // An Ethereum SECP256k1 signature using Eip1559 for message encoding. + Eip1559([u8; 65]), // An Ethereum SECP256k1 signature using Eip712 for message encoding. SetheumEip712([u8; 65]), + // An Ethereum SECP256k1 signature using Eip2930 for message encoding. + Eip2930([u8; 65]), } impl From for SetheumMultiSignature { @@ -95,7 +100,7 @@ impl TryFrom for ecdsa::Signature { impl Default for SetheumMultiSignature { fn default() -> Self { - Self::Ed25519(Default::default()) + Self::Ed25519(ed25519::Signature([0u8; 64])) } } @@ -103,8 +108,12 @@ impl Verify for SetheumMultiSignature { type Signer = MultiSigner; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { match (self, signer) { - (Self::Ed25519(ref sig), who) => sig.verify(msg, &ed25519::Public::from_slice(who.as_ref())), - (Self::Sr25519(ref sig), who) => sig.verify(msg, &sr25519::Public::from_slice(who.as_ref())), + (Self::Ed25519(ref sig), who) => { + ed25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) + } + (Self::Sr25519(ref sig), who) => { + sr25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) + } (Self::Ecdsa(ref sig), who) => { let m = sp_io::hashing::blake2_256(msg.get()); match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { diff --git a/blockchain/primitives/src/task.rs b/blockchain/primitives/src/task.rs index 0169963ab..6aa7c8ada 100644 --- a/blockchain/primitives/src/task.rs +++ b/blockchain/primitives/src/task.rs @@ -17,17 +17,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - -use codec::{Decode, Encode}; use frame_support::weights::Weight; -// use scale_info::TypeInfo; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::DispatchResult; use sp_runtime::RuntimeDebug; -// TODO - Add `TypeInfo` here below as in `#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]` when bumped substrate version -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct TaskResult { pub result: DispatchResult, diff --git a/blockchain/primitives/src/testing.rs b/blockchain/primitives/src/testing.rs new file mode 100644 index 000000000..b4ef74482 --- /dev/null +++ b/blockchain/primitives/src/testing.rs @@ -0,0 +1,79 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#[doc(hidden)] +pub use orml_traits; +#[doc(hidden)] +pub use paste; + +#[macro_export] +macro_rules! mock_handler { + ( + $vis:vis struct $name:ident < $t:ty > ; + $( $rest:tt )* + ) => { + $crate::testing::paste::item! { + thread_local! { + pub static [<$name:snake:upper>]: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); + } + + $vis struct $name; + + impl $name { + + pub fn push(val: $t) { + [<$name:snake:upper>].with(|v| v.borrow_mut().push(val)); + } + + pub fn clear() { + [<$name:snake:upper>].with(|v| v.borrow_mut().clear()); + } + + pub fn get_all() { + [<$name:snake:upper>].with(|v| v.borrow().clone()); + } + + pub fn assert_eq(expected: Vec<$t>) { + [<$name:snake:upper>].with(|v| { + assert_eq!(*v.borrow(), expected); + }); + } + + pub fn assert_eq_and_clear(expected: Vec<$t>) { + Self::assert_eq(expected); + Self::clear(); + } + + pub fn assert_empty() { + Self::assert_eq(Vec::new()); + } + } + + impl $crate::testing::orml_traits::Happened<$t> for $name { + fn happened(val: &$t) { + Self::push(val.clone()); + } + } + } + + $crate::mock_handler!( $( $rest )* ); + }; + () => {}; +} diff --git a/blockchain/primitives/src/tests.rs b/blockchain/primitives/src/tests.rs index 605e95fc5..ffba29ebd 100644 --- a/blockchain/primitives/src/tests.rs +++ b/blockchain/primitives/src/tests.rs @@ -19,49 +19,50 @@ // along with this program. If not, see . use super::*; -use crate::evm::EvmAddress; -use frame_support::assert_ok; -use std::{ - convert::{TryFrom, TryInto}, - str::FromStr, +use crate::evm::{ + decode_gas_limit, decode_gas_price, is_system_contract, EvmAddress, MAX_GAS_LIMIT_CC, + SYSTEM_CONTRACT_ADDRESS_PREFIX, }; +use frame_support::assert_ok; +use sp_core::H160; +use std::str::FromStr; #[test] fn trading_pair_works() { - let setm = CurrencyId::Token(TokenSymbol::SEE); - let setusd = CurrencyId::Token(TokenSymbol::SETUSD); + let see = CurrencyId::Token(TokenSymbol::SEE); + let setr = CurrencyId::Token(TokenSymbol::SETR); let erc20 = CurrencyId::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()); - let setm_setusd_lp = CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::SETUSD)); - let erc20_setm_lp = CurrencyId::DexShare( + let see_setr_lp = CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::SETR)); + let erc20_see_lp = CurrencyId::DexShare( DexShare::Token(TokenSymbol::SEE), DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()), ); assert_eq!( - TradingPair::from_currency_ids(setusd, setm).unwrap(), - TradingPair(setm, setusd) + TradingPair::from_currency_ids(setr, see).unwrap(), + TradingPair(see, setr) ); assert_eq!( - TradingPair::from_currency_ids(setm, setusd).unwrap(), - TradingPair(setm, setusd) + TradingPair::from_currency_ids(see, setr).unwrap(), + TradingPair(see, setr) ); assert_eq!( - TradingPair::from_currency_ids(erc20, setm).unwrap(), - TradingPair(setm, erc20) + TradingPair::from_currency_ids(erc20, see).unwrap(), + TradingPair(see, erc20) ); - assert_eq!(TradingPair::from_currency_ids(setm, setm), None); + assert_eq!(TradingPair::from_currency_ids(see, see), None); assert_eq!( - TradingPair::from_currency_ids(setusd, setm) + TradingPair::from_currency_ids(setr, see) .unwrap() .dex_share_currency_id(), - setm_setusd_lp + see_setr_lp ); assert_eq!( - TradingPair::from_currency_ids(setm, erc20) + TradingPair::from_currency_ids(see, erc20) .unwrap() .dex_share_currency_id(), - erc20_setm_lp + erc20_see_lp ); } @@ -78,8 +79,8 @@ fn currency_id_into_u32_works() { let currency_id = DexShare::Token(TokenSymbol::SEE); assert_eq!(Into::::into(currency_id), 0x00); - let currency_id = DexShare::Token(TokenSymbol::SETUSD); - assert_eq!(Into::::into(currency_id), 0x05); + let currency_id = DexShare::Token(TokenSymbol::USSD); + assert_eq!(Into::::into(currency_id), 0x01); let currency_id = DexShare::Erc20(EvmAddress::from_str("0x2000000000000000000000000000000000000000").unwrap()); assert_eq!(Into::::into(currency_id), 0x20000000); @@ -98,32 +99,41 @@ fn currency_id_into_u32_works() { fn currency_id_try_into_evm_address_works() { assert_eq!( EvmAddress::try_from(CurrencyId::Token(TokenSymbol::SEE,)), - Ok(EvmAddress::from_str("0x0000000000000000000000000000000001000000").unwrap()) + Ok(EvmAddress::from_str("0x0000000000000000000100000000000000000000").unwrap()) ); assert_eq!( EvmAddress::try_from(CurrencyId::DexShare( DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETUSD), + DexShare::Token(TokenSymbol::SETR), )), - Ok(EvmAddress::from_str("0x0000000000000000000000010000000000000005").unwrap()) + Ok(EvmAddress::from_str("0x0000000000000000000200000000000000000001").unwrap()) ); + // No check the erc20 is mapped assert_eq!( EvmAddress::try_from(CurrencyId::DexShare( DexShare::Erc20(Default::default()), DexShare::Erc20(Default::default()) )), - Err(()) + Ok(EvmAddress::from_str("0x0000000000000000000201000000000100000000").unwrap()) ); let erc20 = EvmAddress::from_str("0x1111111111111111111111111111111111111111").unwrap(); assert_eq!(EvmAddress::try_from(CurrencyId::Erc20(erc20)), Ok(erc20)); + + assert_eq!( + EvmAddress::try_from(CurrencyId::DexShare( + DexShare::ForeignAsset(Default::default()), + DexShare::ForeignAsset(Default::default()) + )), + Ok(EvmAddress::from_str("0x0000000000000000000202000000000200000000").unwrap()) + ); } #[test] fn generate_function_selector_works() { - #[primitives_proc_macro::generate_function_selector] + #[module_evm_utility_macro::generate_function_selector] #[derive(RuntimeDebug, Eq, PartialEq)] #[repr(u32)] pub enum Action { @@ -142,3 +152,75 @@ fn generate_function_selector_works() { assert_eq!(Action::BalanceOf as u32, 0x70a08231_u32); assert_eq!(Action::Transfer as u32, 0xa9059cbb_u32); } + +#[test] +fn is_system_contract_works() { + assert!(is_system_contract(&H160::from_low_u64_be(0))); + assert!(is_system_contract(&H160::from_low_u64_be(u64::max_value()))); + + let mut bytes = [0u8; 20]; + bytes[SYSTEM_CONTRACT_ADDRESS_PREFIX.len() - 1] = 1u8; + + assert!(!is_system_contract(&bytes.into())); + + bytes = [0u8; 20]; + bytes[0] = 1u8; + + assert!(!is_system_contract(&bytes.into())); +} + +#[test] +fn decode_gas_price_works() { + const TX_FEE_PRE_GAS: u128 = 100_000_000_000u128; // 100 Gwei + + // tip = 0, gas_price = 0 Gwei, gas_limit = u64::MIN + assert_eq!(decode_gas_price(u64::MIN, u64::MIN, TX_FEE_PRE_GAS), None); + // tip = 0, gas_price = 99 Gwei, gas_limit = u64::MAX + assert_eq!(decode_gas_price(99_999_999_999, u64::MIN, TX_FEE_PRE_GAS), None); + // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(100_000_000_000, u64::MIN, TX_FEE_PRE_GAS), + Some((0, 0)) + ); + // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MAX + assert_eq!( + decode_gas_price(100_000_000_000, u64::MAX, TX_FEE_PRE_GAS), + Some((0, 0)) + ); + + // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(105_000_000_000, u64::MIN, TX_FEE_PRE_GAS), + Some((0, u32::MAX)) + ); + // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MAX + assert_eq!( + decode_gas_price(105_000_000_000, u64::MAX, TX_FEE_PRE_GAS), + Some((0, u32::MAX)) + ); + + // tip = 0, gas_price = u64::MAX, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(u64::MAX, u64::MIN, TX_FEE_PRE_GAS), + Some((0, 3_709_551_615)) + ); + // tip != 0, gas_price = u64::MAX, gas_limit = 1 + assert_eq!(decode_gas_price(u64::MAX, 1, TX_FEE_PRE_GAS), None); + + // tip != 200%, gas_price = 200 Gwei, gas_limit = 10000 + assert_eq!( + decode_gas_price(200_000_000_000, 10_000, TX_FEE_PRE_GAS), + Some((1_000_000_000, 0)) + ); +} + +#[test] +fn decode_gas_limit_works() { + assert_eq!(decode_gas_limit(u64::MAX), (15_480_000, 32768)); + assert_eq!(decode_gas_limit(u64::MIN), (0, 0)); + assert_eq!( + // u64::MAX = 4294967295 + decode_gas_limit(u64::MAX / 1000 * 1000 + 199), + (15330000, 2u32.pow(MAX_GAS_LIMIT_CC)) + ); +} diff --git a/blockchain/primitives/src/unchecked_extrinsic.rs b/blockchain/primitives/src/unchecked_extrinsic.rs new file mode 100644 index 000000000..a86dc975e --- /dev/null +++ b/blockchain/primitives/src/unchecked_extrinsic.rs @@ -0,0 +1,675 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{evm::EthereumTransactionMessage, signature::SetheumMultiSignature, to_bytes, Address, Balance}; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + traits::{ExtrinsicCall, Get}, +}; +use module_evm_utility::ethereum::{ + EIP1559TransactionMessage, EIP2930TransactionMessage, LegacyTransactionMessage, TransactionAction, +}; +use module_evm_utility_macro::keccak256; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{H160, H256}; +use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +use sp_runtime::{ + generic::{CheckedExtrinsic, SetheumUncheckedExtrinsic}, + traits::{self, Checkable, Convert, Extrinsic, ExtrinsicMetadata, Member, SignedExtension, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + AccountId32, RuntimeDebug, +}; +#[cfg(not(feature = "std"))] +use sp_std::alloc::format; +use sp_std::{marker::PhantomData, prelude::*}; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(ConvertEthTx))] +pub struct SetheumUncheckedExtrinsic( + pub SetheumUncheckedExtrinsic, + PhantomData<(ConvertEthTx, StorageDepositPerByte, TxFeePerGas)>, +); + +impl Extrinsic + for SetheumUncheckedExtrinsic +{ + type Call = Call; + + type SignaturePayload = (Address, SetheumMultiSignature, Extra); + + fn is_signed(&self) -> Option { + self.0.is_signed() + } + + fn new(function: Call, signed_data: Option) -> Option { + Some(if let Some((address, signature, extra)) = signed_data { + Self( + SetheumUncheckedExtrinsic::new_signed(function, address, signature, extra), + PhantomData, + ) + } else { + Self(SetheumUncheckedExtrinsic::new_unsigned(function), PhantomData) + }) + } +} + +impl ExtrinsicMetadata + for SetheumUncheckedExtrinsic +{ + const VERSION: u8 = SetheumUncheckedExtrinsic::::VERSION; + type SignedExtensions = Extra; +} + +impl ExtrinsicCall + for SetheumUncheckedExtrinsic +{ + fn call(&self) -> &Self::Call { + self.0.call() + } +} + +impl Checkable + for SetheumUncheckedExtrinsic +where + Call: Encode + Member, + Extra: SignedExtension, + ConvertEthTx: Convert<(Call, Extra), Result<(EthereumTransactionMessage, Extra), InvalidTransaction>>, + StorageDepositPerByte: Get, + TxFeePerGas: Get, + Lookup: traits::Lookup, +{ + type Checked = CheckedExtrinsic; + + fn check(self, lookup: &Lookup) -> Result { + let function = self.0.function.clone(); + + match self.0.signature { + Some((addr, SetheumMultiSignature::Ethereum(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Ethereum eth_msg: {:?}", eth_msg + ); + + if !eth_msg.access_list.len().is_zero() { + // Not yet supported, require empty + return Err(InvalidTransaction::BadProof.into()); + } + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + let msg = LegacyTransactionMessage { + nonce: eth_msg.nonce.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + chain_id: Some(eth_msg.chain_id), + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::Eip2930(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Eip2930 eth_msg: {:?}", eth_msg + ); + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + let msg = EIP2930TransactionMessage { + chain_id: eth_msg.chain_id, + nonce: eth_msg.nonce.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + access_list: eth_msg.access_list, + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::Eip1559(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Eip1559 eth_msg: {:?}", eth_msg + ); + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + // tip = priority_fee * gas_limit + let priority_fee = eth_msg.tip.checked_div(eth_msg.gas_limit.into()).unwrap_or_default(); + + let msg = EIP1559TransactionMessage { + chain_id: eth_msg.chain_id, + nonce: eth_msg.nonce.into(), + max_priority_fee_per_gas: priority_fee.into(), + max_fee_per_gas: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + access_list: eth_msg.access_list, + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::SetheumEip712(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "SetheumEip712 eth_msg: {:?}", eth_msg + ); + + let signer = verify_eip712_signature(eth_msg, sig).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + _ => self.0.check(lookup), + } + } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _lookup: &Lookup, + ) -> Result { + unreachable!(); + } +} + +impl GetDispatchInfo + for SetheumUncheckedExtrinsic +where + Call: GetDispatchInfo, + Extra: SignedExtension, +{ + fn get_dispatch_info(&self) -> DispatchInfo { + self.0.get_dispatch_info() + } +} + +impl serde::Serialize + for SetheumUncheckedExtrinsic +{ + fn serialize(&self, seq: S) -> Result + where + S: ::serde::Serializer, + { + self.0.serialize(seq) + } +} + +impl<'a, Call: Decode, Extra: SignedExtension, ConvertEthTx, StorageDepositPerByte, TxFeePerGas> serde::Deserialize<'a> + for SetheumUncheckedExtrinsic +{ + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = sp_core::bytes::deserialize(de)?; + Decode::decode(&mut &r[..]).map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) + } +} + +fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option { + secp256k1_ecdsa_recover(sig, msg_hash) + .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) + .ok() +} + +fn verify_eip712_signature(eth_msg: EthereumTransactionMessage, sig: [u8; 65]) -> Option { + let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)"); + let access_list_type_hash = keccak256!("AccessList(address address,uint256[] storageKeys)"); + let tx_type_hash = keccak256!("Transaction(string action,address to,uint256 nonce,uint256 tip,bytes data,uint256 value,uint256 gasLimit,uint256 storageLimit,AccessList[] accessList,uint256 validUntil)AccessList(address address,uint256[] storageKeys)"); + + let mut domain_seperator_msg = domain_hash.to_vec(); + domain_seperator_msg.extend_from_slice(keccak256!("Setheum EVM")); // name + domain_seperator_msg.extend_from_slice(keccak256!("1")); // version + domain_seperator_msg.extend_from_slice(&to_bytes(eth_msg.chain_id)); // chain id + domain_seperator_msg.extend_from_slice(eth_msg.genesis.as_bytes()); // salt + let domain_separator = keccak_256(domain_seperator_msg.as_slice()); + + let mut tx_msg = tx_type_hash.to_vec(); + match eth_msg.action { + TransactionAction::Call(to) => { + tx_msg.extend_from_slice(keccak256!("Call")); + tx_msg.extend_from_slice(H256::from(to).as_bytes()); + } + TransactionAction::Create => { + tx_msg.extend_from_slice(keccak256!("Create")); + tx_msg.extend_from_slice(H256::default().as_bytes()); + } + } + tx_msg.extend_from_slice(&to_bytes(eth_msg.nonce)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.tip)); + tx_msg.extend_from_slice(&keccak_256(eth_msg.input.as_slice())); + tx_msg.extend_from_slice(&to_bytes(eth_msg.value)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.gas_limit)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.storage_limit)); + + let mut access_list: Vec<[u8; 32]> = Vec::new(); + eth_msg.access_list.iter().for_each(|v| { + let mut access_list_msg = access_list_type_hash.to_vec(); + access_list_msg.extend_from_slice(&to_bytes(v.address.as_bytes())); + access_list_msg.extend_from_slice(&keccak_256( + &v.storage_keys.iter().map(|v| v.as_bytes()).collect::>().concat(), + )); + access_list.push(keccak_256(access_list_msg.as_slice())); + }); + tx_msg.extend_from_slice(&keccak_256(&access_list.concat())); + tx_msg.extend_from_slice(&to_bytes(eth_msg.valid_until)); + + let mut msg = b"\x19\x01".to_vec(); + msg.extend_from_slice(&domain_separator); + msg.extend_from_slice(&keccak_256(tx_msg.as_slice())); + + let msg_hash = keccak_256(msg.as_slice()); + + recover_signer(&sig, &msg_hash) +} + +fn recover_sign_data( + eth_msg: &EthereumTransactionMessage, + ts_fee_per_gas: u128, + storage_deposit_per_byte: u128, +) -> Option<(u128, u128)> { + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit + // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit + let block_period = eth_msg.valid_until.saturating_div(30); + // u16: max value 0xffff * 64 = 4194240 bytes = 4MB + let storage_entry_limit: u16 = eth_msg.storage_limit.saturating_div(64).try_into().ok()?; + let storage_entry_deposit = storage_deposit_per_byte.saturating_mul(64); + let tx_gas_price = ts_fee_per_gas + .checked_add(Into::::into(block_period).checked_shl(16)?)? + .checked_add(storage_entry_limit.into())?; + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + let tx_gas_limit = storage_entry_deposit + .checked_div(ts_fee_per_gas) + .expect("divisor is non-zero; qed") + .checked_mul(storage_entry_limit.into())? + .checked_add(eth_msg.gas_limit.into())?; + + Some((tx_gas_price, tx_gas_limit)) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + use module_evm_utility::ethereum::AccessListItem; + use sp_core::U256; + use std::{ops::Add, str::FromStr}; + + #[test] + fn verify_eip712_should_works() { + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + // access_list = vec![] + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![], + }; + let sign = hex!("c30a85ee9218af4e2892c82d65a8a7fbeee75c010973d42cee2e52309449d687056c09cf486a16d58d23b0ebfed63a0276d5fb1a464f645dc7607147a37f7a211c"); + assert_eq!(verify_eip712_signature(msg, sign), sender); + + // access_list.storage_keys = vec![] + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![AccessListItem { + address: hex!("0000000000000000000000000000000000000000").into(), + storage_keys: vec![], + }], + }; + let sign = hex!("a94da7159e29f2a0c9aec08eb62cbb6eefd6ee277960a3c96b183b53201687ce19f1fd9c2cfdace8730fd5249ea11e57701cd0cc20386bbd9d3df5092fe218851c"); + assert_eq!(verify_eip712_signature(msg, sign), sender); + + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![AccessListItem { + address: hex!("0000000000000000000000000000000000000000").into(), + storage_keys: vec![ + H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), + H256::from_str("0x0000000000111111111122222222223333333333444444444455555555556666").unwrap(), + H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), + ], + }], + }; + let sign = hex!("dca9701b77bac69e5a88c7f040a6fa0a051f97305619e66e9182bf3416ca2d0e7b730cb732e2f747754f6b9307d78ce611aabb3692ea48314670a6a8c447dc9b1c"); + assert_eq!(verify_eip712_signature(msg.clone(), sign), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.tip += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.storage_limit += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()); + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.value += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.chain_id += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.genesis = Default::default(); + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + storage_keys: vec![], + }]; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg; + new_msg.valid_until += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + } + + #[test] + fn verify_eth_should_works() { + let msg = LegacyTransactionMessage { + nonce: U256::from(1), + gas_price: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + chain_id: Some(595), + }; + + let sign = hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_price = new_msg.gas_price.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg; + new_msg.chain_id = None; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } + + #[test] + fn verify_eth_1559_should_works() { + let msg = EIP1559TransactionMessage { + chain_id: 595, + nonce: U256::from(1), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + access_list: vec![], + }; + + let sign = hex!("e88df53d4d66cb7a4f54ea44a44942b9b7f4fb4951525d416d3f7d24755a1f817734270872b103ac04c59d74f4dacdb8a6eff09a6638bd95dad1fa3eda921d891b"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.chain_id = new_msg.chain_id.add(1u64); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_priority_fee_per_gas = new_msg.max_priority_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_fee_per_gas = new_msg.max_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg; + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + storage_keys: vec![], + }]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } + + #[test] + fn recover_sign_data_should_works() { + let mut msg = EthereumTransactionMessage { + chain_id: 595, + genesis: Default::default(), + nonce: 1, + tip: 0, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 64000, + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: 0, + input: vec![], + access_list: vec![], + valid_until: 30, + }; + + let ts_fee_per_gas = 200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff; + let storage_deposit_per_byte = 100_000_000_000_000u128; + + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((200000013288, 34100000)) + ); + msg.valid_until = 3600030; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((207864333288, 34100000)) + ); + msg.valid_until = u32::MAX; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((9582499136488, 34100000)) + ); + + // check storage_limit max is 0xffff * 64 + 63 + msg.storage_limit = 0xffff * 64 + 64; + assert_eq!(recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), None); + + msg.storage_limit = 0xffff * 64 + 63; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((9582499201023, 2099220000)) + ); + + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, u128::MAX), + Some((9582499201023, 111502054267125439094838181151820)) + ); + + assert_eq!(recover_sign_data(&msg, u128::MAX, storage_deposit_per_byte), None); + + assert_eq!(recover_sign_data(&msg, u128::MAX, u128::MAX), None); + } +} diff --git a/blockchain/chains/qingdao/rpc/Pipfile b/blockchain/rpc/Pipfile similarity index 100% rename from blockchain/chains/qingdao/rpc/Pipfile rename to blockchain/rpc/Pipfile diff --git a/blockchain/chains/qingdao/rpc/Pipfile.lock b/blockchain/rpc/Pipfile.lock similarity index 100% rename from blockchain/chains/qingdao/rpc/Pipfile.lock rename to blockchain/rpc/Pipfile.lock diff --git a/blockchain/chains/qingdao/rpc/README.md b/blockchain/rpc/README.md similarity index 100% rename from blockchain/chains/qingdao/rpc/README.md rename to blockchain/rpc/README.md diff --git a/blockchain/chains/qingdao/rpc/nginx.conf b/blockchain/rpc/nginx.conf similarity index 100% rename from blockchain/chains/qingdao/rpc/nginx.conf rename to blockchain/rpc/nginx.conf diff --git a/blockchain/chains/qingdao/rpc/rpc.py b/blockchain/rpc/rpc.py similarity index 100% rename from blockchain/chains/qingdao/rpc/rpc.py rename to blockchain/rpc/rpc.py diff --git a/blockchain/chains/qingdao/runtime/Cargo.toml b/blockchain/runtime/Cargo.toml similarity index 100% rename from blockchain/chains/qingdao/runtime/Cargo.toml rename to blockchain/runtime/Cargo.toml diff --git a/blockchain/chains/qingdao/runtime/build.rs b/blockchain/runtime/build.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/build.rs rename to blockchain/runtime/build.rs diff --git a/blockchain/chains/qingdao/runtime/common/Cargo.toml b/blockchain/runtime/common/Cargo.toml similarity index 96% rename from blockchain/chains/qingdao/runtime/common/Cargo.toml rename to blockchain/runtime/common/Cargo.toml index 1dae973f2..75f17e1a7 100644 --- a/blockchain/chains/qingdao/runtime/common/Cargo.toml +++ b/blockchain/runtime/common/Cargo.toml @@ -1,75 +1,75 @@ -[package] -name = "runtime-common" -version = "1.0.0" -authors = ["Setheum Developers"] -edition = "2018" - -[dependencies] -static_assertions = "1.1.0" -num_enum = { version = "0.5.1", default-features = false } -serde = { version = "1.0.101", optional = true, default-features = false } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -ethabi = { version = "15.0.0", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -pallet-scheduler = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } -log = { version = "0.4.14", default-features = false } -pallet-collective = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -pallet-membership = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-oracle = { path = "../submodules/orml/oracle", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -module-evm = { path = "../../../modules//evm", default-features = false } -module-support = { path = "../../../modules/support", default-features = false } -module-transaction-payment = { path = "../../../modules/transaction-payment", default-features = false } -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -primitives-proc-macro = { path = "../primitives/proc-macro" } - -[dev-dependencies] -serde_json = "1.0.41" -hex-literal = "0.3.1" -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } - -orml-tokens = { path = "../submodules/orml/tokens" } -orml-nft = { path = "../submodules/orml/nft" } - -module-currencies = { path = "../../../modules/currencies" } -module-evm-bridge = { path = "../../../modules//evm-bridge" } -module-evm-manager = { path = "../../../modules//evm-manager" } -module-nft = { path = "../../../modules/nft" } -# module-dex = { path = "../../../modules/defi/setswap/dex" } -module-prices = { path = "../../../modules/prices" } -module-transaction-payment = { path = "../../../modules/transaction-payment" } - -[features] -default = ["std"] -std = [ - "num_enum/std", - "serde", - "codec/std", - "ethabi/std", - "frame-support/std", - "frame-system/std", - "pallet-scheduler/std", - "pallet-collective/std", - "pallet-membership/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", - "orml-oracle/std", - "orml-traits/std", - "module-evm/std", - "module-support/std", - "primitives/std", -] -with-ethereum-compatibility = [ - "module-evm/with-ethereum-compatibility", -] +[package] +name = "runtime-common" +version = "0.9.81-dev" +authors = ["Setheum Developers"] +edition = "2021" + +[dependencies] +static_assertions = "1.1.0" +num_enum = { version = "0.5.1", default-features = false } +serde = { version = "1.0.101", optional = true, default-features = false } +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +ethabi = { version = "15.0.0", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +pallet-scheduler = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.10" } +log = { version = "0.4.14", default-features = false } +pallet-collective = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } +pallet-membership = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } + +orml-oracle = { path = "../submodules/orml/oracle", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +module-evm = { path = "../../../modules//evm", default-features = false } +module-support = { path = "../../../modules/support", default-features = false } +module-transaction-payment = { path = "../../../modules/transaction-payment", default-features = false } +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +primitives-proc-macro = { path = "../primitives/proc-macro" } + +[dev-dependencies] +serde_json = "1.0.41" +hex-literal = "0.3.1" +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } +pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } + +orml-tokens = { path = "../submodules/orml/tokens" } +orml-nft = { path = "../submodules/orml/nft" } + +module-currencies = { path = "../../../modules/currencies" } +module-evm-bridge = { path = "../../../modules//evm-bridge" } +module-evm-manager = { path = "../../../modules//evm-manager" } +module-nft = { path = "../../../modules/nft" } +# module-dex = { path = "../../../modules/defi/setswap/dex" } +module-prices = { path = "../../../modules/prices" } +module-transaction-payment = { path = "../../../modules/transaction-payment" } + +[features] +default = ["std"] +std = [ + "num_enum/std", + "serde", + "codec/std", + "ethabi/std", + "frame-support/std", + "frame-system/std", + "pallet-scheduler/std", + "pallet-collective/std", + "pallet-membership/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "orml-oracle/std", + "orml-traits/std", + "module-evm/std", + "module-support/std", + "primitives/std", +] +with-ethereum-compatibility = [ + "module-evm/with-ethereum-compatibility", +] diff --git a/blockchain/chains/qingdao/runtime/common/src/gas_to_weight_ratio.rs b/blockchain/runtime/common/src/gas_to_weight_ratio.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/gas_to_weight_ratio.rs rename to blockchain/runtime/common/src/gas_to_weight_ratio.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/lib.rs b/blockchain/runtime/common/src/lib.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/lib.rs rename to blockchain/runtime/common/src/lib.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/dex.rs b/blockchain/runtime/common/src/precompile/dex.rs similarity index 98% rename from blockchain/chains/qingdao/runtime/common/src/precompile/dex.rs rename to blockchain/runtime/common/src/precompile/dex.rs index 29024a851..6994d25a1 100644 --- a/blockchain/chains/qingdao/runtime/common/src/precompile/dex.rs +++ b/blockchain/runtime/common/src/precompile/dex.rs @@ -22,7 +22,7 @@ use super::input::{Input, InputT, Output}; use crate::precompile::PrecompileOutput; use frame_support::log; use module_evm::{Context, ExitError, ExitSucceed, Precompile}; -use module_support::{AddressMapping as AddressMappingT, CurrencyIdMapping as CurrencyIdMappingT, DEXManager, SwapLimit}; +use module_support::{AddressMapping as AddressMappingT, CurrencyIdMapping as CurrencyIdMappingT, SwapDexManager, SwapLimit}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use primitives::{Balance, CurrencyId}; use sp_runtime::RuntimeDebug; @@ -61,7 +61,7 @@ where AccountId: Debug + Clone, AddressMapping: AddressMappingT, CurrencyIdMapping: CurrencyIdMappingT, - Dex: DEXManager, + Dex: SwapDexManager, { fn execute( input: &[u8], diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/input.rs b/blockchain/runtime/common/src/precompile/input.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/input.rs rename to blockchain/runtime/common/src/precompile/input.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/mock.rs b/blockchain/runtime/common/src/precompile/mock.rs similarity index 99% rename from blockchain/chains/qingdao/runtime/common/src/precompile/mock.rs rename to blockchain/runtime/common/src/precompile/mock.rs index 432ba9c7a..51624abbb 100644 --- a/blockchain/chains/qingdao/runtime/common/src/precompile/mock.rs +++ b/blockchain/runtime/common/src/precompile/mock.rs @@ -479,7 +479,7 @@ parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const GetStableCurrencyExchangeFee: (u32, u32) = (0, 100); pub const TradingPathLimit: u32 = 3; - pub const DEXPalletId: PalletId = PalletId(*b"set/sdex"); + pub const DEXPalletId: PalletId = PalletId(*b"edf/swap"); } impl module_dex::Config for Test { diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/mod.rs b/blockchain/runtime/common/src/precompile/mod.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/mod.rs rename to blockchain/runtime/common/src/precompile/mod.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/multicurrency.rs b/blockchain/runtime/common/src/precompile/multicurrency.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/multicurrency.rs rename to blockchain/runtime/common/src/precompile/multicurrency.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/nft.rs b/blockchain/runtime/common/src/precompile/nft.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/nft.rs rename to blockchain/runtime/common/src/precompile/nft.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/oracle.rs b/blockchain/runtime/common/src/precompile/oracle.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/oracle.rs rename to blockchain/runtime/common/src/precompile/oracle.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/schedule_call.rs b/blockchain/runtime/common/src/precompile/schedule_call.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/schedule_call.rs rename to blockchain/runtime/common/src/precompile/schedule_call.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/state_rent.rs b/blockchain/runtime/common/src/precompile/state_rent.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/state_rent.rs rename to blockchain/runtime/common/src/precompile/state_rent.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/precompile/tests.rs b/blockchain/runtime/common/src/precompile/tests.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/precompile/tests.rs rename to blockchain/runtime/common/src/precompile/tests.rs diff --git a/blockchain/chains/qingdao/runtime/common/src/weights/lib.rs b/blockchain/runtime/common/src/weights/lib.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/common/src/weights/lib.rs rename to blockchain/runtime/common/src/weights/lib.rs diff --git a/blockchain/chains/qingdao/runtime/src/authority.rs b/blockchain/runtime/src/authority.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/authority.rs rename to blockchain/runtime/src/authority.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/auction.rs b/blockchain/runtime/src/benchmarking/auction.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/auction.rs rename to blockchain/runtime/src/benchmarking/auction.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/auction_manager.rs b/blockchain/runtime/src/benchmarking/auction_manager.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/auction_manager.rs rename to blockchain/runtime/src/benchmarking/auction_manager.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/authority.rs b/blockchain/runtime/src/benchmarking/authority.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/authority.rs rename to blockchain/runtime/src/benchmarking/authority.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/cdp_engine.rs b/blockchain/runtime/src/benchmarking/cdp_engine.rs similarity index 99% rename from blockchain/chains/qingdao/runtime/src/benchmarking/cdp_engine.rs rename to blockchain/runtime/src/benchmarking/cdp_engine.rs index 5fe96d8d4..160d42ffb 100644 --- a/blockchain/chains/qingdao/runtime/src/benchmarking/cdp_engine.rs +++ b/blockchain/runtime/src/benchmarking/cdp_engine.rs @@ -29,7 +29,7 @@ use core::convert::TryInto; use frame_benchmarking::account; use frame_support::traits::OnInitialize; use frame_system::RawOrigin; -use module_support::DEXManager; +use module_support::SwapDexManager; use orml_benchmarking::runtime_benchmarks; use orml_traits::{Change, GetByKey}; use sp_runtime::{ diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/cdp_treasury.rs b/blockchain/runtime/src/benchmarking/cdp_treasury.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/cdp_treasury.rs rename to blockchain/runtime/src/benchmarking/cdp_treasury.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/currencies.rs b/blockchain/runtime/src/benchmarking/currencies.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/currencies.rs rename to blockchain/runtime/src/benchmarking/currencies.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/dex.rs b/blockchain/runtime/src/benchmarking/dex.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/dex.rs rename to blockchain/runtime/src/benchmarking/dex.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/dex_oracle.rs b/blockchain/runtime/src/benchmarking/dex_oracle.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/dex_oracle.rs rename to blockchain/runtime/src/benchmarking/dex_oracle.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/emergency_shutdown.rs b/blockchain/runtime/src/benchmarking/emergency_shutdown.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/emergency_shutdown.rs rename to blockchain/runtime/src/benchmarking/emergency_shutdown.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/evm.rs b/blockchain/runtime/src/benchmarking/evm.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/evm.rs rename to blockchain/runtime/src/benchmarking/evm.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/evm_accounts.rs b/blockchain/runtime/src/benchmarking/evm_accounts.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/evm_accounts.rs rename to blockchain/runtime/src/benchmarking/evm_accounts.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/mod.rs b/blockchain/runtime/src/benchmarking/mod.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/mod.rs rename to blockchain/runtime/src/benchmarking/mod.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/oracle.rs b/blockchain/runtime/src/benchmarking/oracle.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/oracle.rs rename to blockchain/runtime/src/benchmarking/oracle.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/prices.rs b/blockchain/runtime/src/benchmarking/prices.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/prices.rs rename to blockchain/runtime/src/benchmarking/prices.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/serp_setmint.rs b/blockchain/runtime/src/benchmarking/serp_setmint.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/serp_setmint.rs rename to blockchain/runtime/src/benchmarking/serp_setmint.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/serp_treasury.rs b/blockchain/runtime/src/benchmarking/serp_treasury.rs similarity index 98% rename from blockchain/chains/qingdao/runtime/src/benchmarking/serp_treasury.rs rename to blockchain/runtime/src/benchmarking/serp_treasury.rs index 4a1d1d66e..755a880e0 100644 --- a/blockchain/chains/qingdao/runtime/src/benchmarking/serp_treasury.rs +++ b/blockchain/runtime/src/benchmarking/serp_treasury.rs @@ -37,7 +37,7 @@ use frame_support::traits::OnInitialize; use orml_traits::MultiCurrency; use sp_runtime::traits::Zero; use sp_std::prelude::*; -use module_support::{DEXManager, SerpTreasury as SerpTreasurySupport, SerpTreasuryExtended, SwapLimit}; +use module_support::{SwapDexManager, SerpTreasury as SerpTreasurySupport, SerpTreasuryExtended, SwapLimit}; const SEE: CurrencyId = GetNativeCurrencyId::get(); const SETR: CurrencyId = SetterCurrencyId::get(); diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/tokens.rs b/blockchain/runtime/src/benchmarking/tokens.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/tokens.rs rename to blockchain/runtime/src/benchmarking/tokens.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/transaction_pause.rs b/blockchain/runtime/src/benchmarking/transaction_pause.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/transaction_pause.rs rename to blockchain/runtime/src/benchmarking/transaction_pause.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/transaction_payment.rs b/blockchain/runtime/src/benchmarking/transaction_payment.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/transaction_payment.rs rename to blockchain/runtime/src/benchmarking/transaction_payment.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/utils.rs b/blockchain/runtime/src/benchmarking/utils.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/utils.rs rename to blockchain/runtime/src/benchmarking/utils.rs diff --git a/blockchain/chains/qingdao/runtime/src/benchmarking/vesting.rs b/blockchain/runtime/src/benchmarking/vesting.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/benchmarking/vesting.rs rename to blockchain/runtime/src/benchmarking/vesting.rs diff --git a/blockchain/chains/qingdao/runtime/src/constants.rs b/blockchain/runtime/src/constants.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/constants.rs rename to blockchain/runtime/src/constants.rs diff --git a/blockchain/chains/qingdao/runtime/src/lib.rs b/blockchain/runtime/src/lib.rs similarity index 99% rename from blockchain/chains/qingdao/runtime/src/lib.rs rename to blockchain/runtime/src/lib.rs index 5b5933136..065eadb0a 100644 --- a/blockchain/chains/qingdao/runtime/src/lib.rs +++ b/blockchain/runtime/src/lib.rs @@ -134,7 +134,7 @@ mod benchmarking; parameter_types! { pub const AirdropPalletId: PalletId = PalletId(*b"set/drop"); // 5EYCAe5jKgkuY1B3CkWQF41wzN62tTt8ptfmao31qYvMiVRD pub const CDPTreasuryPalletId: PalletId = PalletId(*b"set/cdpt"); // 5EYCAe5jKgkuXyJQ3G8CXrRfmmqqe54Tye5wJDqim8cvHQi7 - pub const DEXPalletId: PalletId = PalletId(*b"set/sdex"); // 5EYCAe5jKgkuYTiXRpXnghiur9sW2zJCp91xQRKKzhwjS2DC + pub const DEXPalletId: PalletId = PalletId(*b"edf/swap"); // 5EYCAe5jKgkuYTiXRpXnghiur9sW2zJCp91xQRKKzhwjS2DC pub const LoansPalletId: PalletId = PalletId(*b"set/loan"); // 5EYCAe5jKgkuYFMt7CDpD9JGyD8eLr9DKZZ9mBNibUbs5xXo pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); // 5EYCAe5jKgkuYTZd9to8S5wCPjCUQnDg57tU9BDgakrywBM2 pub const SerpTreasuryPalletId: PalletId = PalletId(*b"set/serp"); // 5EYCAe5jKgkuYTiwwziYLaTt4ZTSEikGfWNVyZ1PUdkBg78Z diff --git a/blockchain/chains/qingdao/runtime/src/weights/dex_oracle.rs b/blockchain/runtime/src/weights/dex_oracle.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/dex_oracle.rs rename to blockchain/runtime/src/weights/dex_oracle.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/emergency_shutdown.rs b/blockchain/runtime/src/weights/emergency_shutdown.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/emergency_shutdown.rs rename to blockchain/runtime/src/weights/emergency_shutdown.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/mod.rs b/blockchain/runtime/src/weights/mod.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/mod.rs rename to blockchain/runtime/src/weights/mod.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_auction_manager.rs b/blockchain/runtime/src/weights/module_auction_manager.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_auction_manager.rs rename to blockchain/runtime/src/weights/module_auction_manager.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_cdp_engine.rs b/blockchain/runtime/src/weights/module_cdp_engine.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_cdp_engine.rs rename to blockchain/runtime/src/weights/module_cdp_engine.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_cdp_treasury.rs b/blockchain/runtime/src/weights/module_cdp_treasury.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_cdp_treasury.rs rename to blockchain/runtime/src/weights/module_cdp_treasury.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_currencies.rs b/blockchain/runtime/src/weights/module_currencies.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_currencies.rs rename to blockchain/runtime/src/weights/module_currencies.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_dex.rs b/blockchain/runtime/src/weights/module_dex.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_dex.rs rename to blockchain/runtime/src/weights/module_dex.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_evm.rs b/blockchain/runtime/src/weights/module_evm.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_evm.rs rename to blockchain/runtime/src/weights/module_evm.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_evm_accounts.rs b/blockchain/runtime/src/weights/module_evm_accounts.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_evm_accounts.rs rename to blockchain/runtime/src/weights/module_evm_accounts.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_nft.rs b/blockchain/runtime/src/weights/module_nft.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_nft.rs rename to blockchain/runtime/src/weights/module_nft.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_prices.rs b/blockchain/runtime/src/weights/module_prices.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_prices.rs rename to blockchain/runtime/src/weights/module_prices.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_transaction_pause.rs b/blockchain/runtime/src/weights/module_transaction_pause.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_transaction_pause.rs rename to blockchain/runtime/src/weights/module_transaction_pause.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_transaction_payment.rs b/blockchain/runtime/src/weights/module_transaction_payment.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_transaction_payment.rs rename to blockchain/runtime/src/weights/module_transaction_payment.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/module_vesting.rs b/blockchain/runtime/src/weights/module_vesting.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/module_vesting.rs rename to blockchain/runtime/src/weights/module_vesting.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/orml_auction.rs b/blockchain/runtime/src/weights/orml_auction.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/orml_auction.rs rename to blockchain/runtime/src/weights/orml_auction.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/orml_authority.rs b/blockchain/runtime/src/weights/orml_authority.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/orml_authority.rs rename to blockchain/runtime/src/weights/orml_authority.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/orml_oracle.rs b/blockchain/runtime/src/weights/orml_oracle.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/orml_oracle.rs rename to blockchain/runtime/src/weights/orml_oracle.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/orml_tokens.rs b/blockchain/runtime/src/weights/orml_tokens.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/orml_tokens.rs rename to blockchain/runtime/src/weights/orml_tokens.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/serp_setmint.rs b/blockchain/runtime/src/weights/serp_setmint.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/serp_setmint.rs rename to blockchain/runtime/src/weights/serp_setmint.rs diff --git a/blockchain/chains/qingdao/runtime/src/weights/serp_treasury.rs b/blockchain/runtime/src/weights/serp_treasury.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/src/weights/serp_treasury.rs rename to blockchain/runtime/src/weights/serp_treasury.rs diff --git a/blockchain/chains/qingdao/runtime/tests/solidity_test/Address.json b/blockchain/runtime/tests/solidity_test/Address.json similarity index 100% rename from blockchain/chains/qingdao/runtime/tests/solidity_test/Address.json rename to blockchain/runtime/tests/solidity_test/Address.json diff --git a/blockchain/chains/qingdao/runtime/tests/solidity_test/Counters.json b/blockchain/runtime/tests/solidity_test/Counters.json similarity index 100% rename from blockchain/chains/qingdao/runtime/tests/solidity_test/Counters.json rename to blockchain/runtime/tests/solidity_test/Counters.json diff --git a/blockchain/chains/qingdao/runtime/tests/solidity_test/TokenMock.json b/blockchain/runtime/tests/solidity_test/TokenMock.json similarity index 100% rename from blockchain/chains/qingdao/runtime/tests/solidity_test/TokenMock.json rename to blockchain/runtime/tests/solidity_test/TokenMock.json diff --git a/blockchain/chains/qingdao/runtime/tests/weights_test.rs b/blockchain/runtime/tests/weights_test.rs similarity index 100% rename from blockchain/chains/qingdao/runtime/tests/weights_test.rs rename to blockchain/runtime/tests/weights_test.rs diff --git a/docs/benchmark.md b/docs/benchmark.md index 43102736e..1783274c8 100644 --- a/docs/benchmark.md +++ b/docs/benchmark.md @@ -17,7 +17,7 @@ --wasm-execution=compiled \ --heap-pages=4096 \ --template=.maintain/runtime-weight-template.hbs \ - --output=./modules/{dir/module-inner-directory}/src/weights/ + --output=./blockchain/modules/{dir/module-inner-directory}/src/weights/ ``` for example, this is the command for generating the `airdrop` module weights: @@ -35,7 +35,7 @@ for example, this is the command for generating the `airdrop` module weights: --wasm-execution=compiled \ --heap-pages=4096 \ --template=.maintain/runtime-weight-template.hbs \ - --output=./modules/airdrop/src/weights/ + --output=./blockchain/modules/airdrop/src/weights/ ``` ### Runtime Module weights