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::