diff --git a/Cargo.lock b/Cargo.lock index 24a05e54..1487a5aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base58" version = "0.2.0" @@ -263,6 +269,15 @@ version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -291,6 +306,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -324,6 +345,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.6", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -424,6 +457,15 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -516,6 +558,18 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.5.2" @@ -559,6 +613,24 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.6", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.31" @@ -607,6 +679,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fixed-hash" version = "0.7.0" @@ -634,6 +716,102 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-tracing 5.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "Inflector", + "cfg-expr", + "frame-support-procedural-tools", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-io", + "sp-runtime", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-version", +] + [[package]] name = "funty" version = "2.0.0" @@ -779,6 +957,17 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.15" @@ -1029,6 +1218,9 @@ version = "0.1.0" dependencies = [ "hex", "hex-literal", + "ink_metadata", + "ink_primitives", + "ink_storage", "parity-scale-codec", "pink-extension", "pink-web3", @@ -1038,6 +1230,27 @@ dependencies = [ "xcm", ] +[[package]] +name = "index_registry" +version = "0.1.0" +dependencies = [ + "dotenv", + "hex-literal", + "index", + "ink_env", + "ink_lang", + "ink_metadata", + "ink_primitives", + "ink_storage", + "parity-scale-codec", + "phala-pallet-common", + "pink-extension", + "pink-extension-runtime", + "pink-web3", + "scale-info", + "xcm", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1262,6 +1475,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", +] + [[package]] name = "keccak" version = "0.1.2" @@ -1649,6 +1874,21 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "phala-pallet-common" +version = "0.1.0" +source = "git+https://github.com/Phala-Network/khala-parachain?tag=v0.1.18#7d1f5b044df2f50a909b90ccaccb4967604d51f9" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-io", + "sp-runtime", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2024,6 +2264,17 @@ dependencies = [ "reqwest", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" @@ -2175,6 +2426,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.6", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.21.3" @@ -2348,9 +2611,12 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +version = "1.3.1" +source = "git+https://github.com/RustCrypto/traits.git?tag=signature-v1.3.1#37717c1e76aa1bbc4fedda65cdef98fc759f232f" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.4", +] [[package]] name = "slab" @@ -2377,6 +2643,36 @@ dependencies = [ "winapi", ] +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-runtime", + "sp-state-machine", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-trie", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "blake2", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-application-crypto" version = "6.0.0" @@ -2526,6 +2822,17 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "syn", +] + [[package]] name = "sp-debug-derive" version = "4.0.0" @@ -2570,6 +2877,20 @@ dependencies = [ "sp-storage 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", ] +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-runtime", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "thiserror", +] + [[package]] name = "sp-io" version = "6.0.0" @@ -2705,6 +3026,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", +] + [[package]] name = "sp-state-machine" version = "0.12.0" @@ -2813,6 +3145,34 @@ dependencies = [ "trie-root", ] +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29)", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-wasm-interface" version = "6.0.0" @@ -3167,6 +3527,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + [[package]] name = "twox-hash" version = "1.6.3" diff --git a/Cargo.toml b/Cargo.toml index 92010e20..bfc1ec60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "contracts/semi-bridge", + "contracts/registry", ] exclude = [ @@ -19,3 +20,5 @@ ink_lang = { git = "https://github.com/Phala-Network/ink.git", branch = "advtest ink_lang_codegen = { git = "https://github.com/Phala-Network/ink.git", branch = "advtest-3.4.0" } ink_lang_ir = { git = "https://github.com/Phala-Network/ink.git", branch = "advtest-3.4.0" } ink_lang_macro = { git = "https://github.com/Phala-Network/ink.git", branch = "advtest-3.4.0" } + +signature = { git = "https://github.com/RustCrypto/traits.git", tag = "signature-v1.3.1" } diff --git a/contracts/registry/Cargo.toml b/contracts/registry/Cargo.toml new file mode 100644 index 00000000..a2b0dad8 --- /dev/null +++ b/contracts/registry/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "index_registry" +version = "0.1.0" +authors = ["Phala Network"] +edition = "2021" +license = "Apache 2.0" +homepage = "https://phala.network/" +repository = "https://github.com/Phala-Network/index-contract" + +[dependencies] +hex-literal = "0.3" +ink_primitives = { version = "3", default-features = false } +ink_metadata = { version = "3", default-features = false, features = ["derive"], optional = true } +ink_env = { version = "3", default-features = false } +ink_storage = { version = "3", default-features = false } +ink_lang = { version = "3", default-features = false } + +scale = { package = "parity-scale-codec", version = "3.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1", default-features = false, features = ["derive"], optional = true } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.29", default-features = false } +phala-pallet-common = { git = "https://github.com/Phala-Network/khala-parachain", tag = "v0.1.18", default-features = false } + +pink-extension = { version = "0.1.0", default-features = false } +pink-web3 = { git = "https://github.com/Phala-Network/pink-web3.git", branch = "pink", default-features = false, features = ["pink"]} + +index = { path = "../../index", default-features = false } + +[dev-dependencies] +hex-literal = "0.3" +pink-extension-runtime = "0.1.3" +dotenv = "0.15.0" + +[profile.release] +overflow-checks = false # Disable integer overflow checks. +lto = false # Enable full link-time optimization. + +[lib] +name = "evm_chain" +path = "src/lib.rs" +crate-type = [ + # Used for normal contract Wasm blobs. + "cdylib", + # Used for ABI generation. + "rlib", +] + +[features] +default = ["std"] +std = [ + "ink_metadata/std", + "ink_env/std", + "ink_storage/std", + "ink_primitives/std", + "scale/std", + "scale-info/std", + "xcm/std", + "phala-pallet-common/std", + "pink-extension/std", + "pink-web3/std", + "index/std", +] +ink-as-dependency = [] \ No newline at end of file diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs new file mode 100644 index 00000000..bdaff521 --- /dev/null +++ b/contracts/registry/src/lib.rs @@ -0,0 +1,983 @@ +#![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; +use ink_lang as ink; + +#[allow(clippy::large_enum_variant)] +#[ink::contract(env = pink_extension::PinkEnvironment)] +mod index_registry { + use alloc::vec::Vec; + use index::ensure; + use index::prelude::*; + use index::registry::evm_chain::EvmChain; + use ink_storage::traits::{PackedLayout, SpreadAllocate, SpreadLayout, StorageLayout}; + use ink_storage::Mapping; + + #[derive( + Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] + pub enum Chain { + Evm(EvmChain), + // TODO.wf: support soon + // Sub(SubChain), + } + + impl Chain { + pub fn as_evm(&self) -> Option { + match self { + Self::Evm(evm_chain) => Some(evm_chain.clone()), + } + } + } + + #[ink(storage)] + #[derive(SpreadAllocate)] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct Registry { + pub admin: AccountId, + /// The registered chains. [chain_name, entity] + pub chains: Mapping, Chain>, + } + + impl Default for Registry { + fn default() -> Self { + Self::new() + } + } + + /// Event emitted when chain registered. + #[ink(event)] + pub struct ChainRegistered { + #[ink(topic)] + chain: ChainInfo, + } + + /// Event emitted when chain unregistered. + #[ink(event)] + pub struct ChainUnregistered { + #[ink(topic)] + chain: Vec, + } + + /// Event emitted when native asset set. + #[ink(event)] + pub struct ChainNativeSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: AssetInfo, + } + + /// Event emitted when stable asset set. + #[ink(event)] + pub struct ChainStableSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: AssetInfo, + } + + /// Event emitted when RPC endpoint asset set. + #[ink(event)] + pub struct ChainEndpointSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + endpoint: Vec, + } + + /// Event emitted when asset registered. + #[ink(event)] + pub struct AssetRegistered { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: AssetInfo, + } + + /// Event emitted when asset unregistered. + #[ink(event)] + pub struct AssetUnregistered { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: AssetInfo, + } + + pub type Result = core::result::Result; + + impl Registry { + #[ink(constructor)] + /// Create an Ethereum entity + pub fn new() -> Self { + ink_lang::utils::initialize_contract(|this: &mut Self| { + this.admin = Self::env().caller(); + }) + } + + /// Register a chain + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn register_chain(&mut self, info: ChainInfo) -> Result<()> { + self.esure_admin()?; + ensure!( + !self.chains.contains(&info.name), + Error::ChainAlreadyRegistered + ); + match info.chain_type { + ChainType::Evm => { + self.chains + .insert(&info.name, &Chain::Evm(EvmChain::new(info.clone()))); + } + ChainType::Sub => { + return Err(Error::Unimplemented); + } + }; + Self::env().emit_event(ChainRegistered { chain: info }); + Ok(()) + } + + /// Unregister a chain + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn unregister_chain(&mut self, name: Vec) -> Result<()> { + self.esure_admin()?; + + ensure!(self.chains.get(&name).is_some(), Error::ChainNotFound); + self.chains.remove(&name); + Self::env().emit_event(ChainUnregistered { chain: name }); + Ok(()) + } + + /// Register an asset for a chain + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn register_asset(&mut self, chain: Vec, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + let mut chain_entity = self.chains.get(&chain).ok_or(Error::ChainNotFound)?; + match chain_entity { + Chain::Evm(ref mut evm_chain) => { + evm_chain.register(asset.clone())?; + } + } + // Insert back + self.chains.insert(&chain, &chain_entity); + Self::env().emit_event(AssetRegistered { chain, asset }); + Ok(()) + } + + /// Unregister an asset from a chain + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn unregister_asset(&mut self, chain: Vec, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + let mut chain_entity = self.chains.get(&chain).ok_or(Error::ChainNotFound)?; + match chain_entity { + Chain::Evm(ref mut evm_chain) => { + evm_chain.unregister(asset.clone())?; + } + } + // Insert back + self.chains.insert(&chain, &chain_entity); + Self::env().emit_event(AssetUnregistered { chain, asset }); + Ok(()) + } + + /// Set native asset + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn set_chain_native(&mut self, chain: Vec, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + let mut chain_entity = self.chains.get(&chain).ok_or(Error::ChainNotFound)?; + match chain_entity { + Chain::Evm(ref mut evm_chain) => { + evm_chain.set_native(asset.clone()); + } + } + // Insert back + self.chains.insert(&chain, &chain_entity); + Self::env().emit_event(ChainNativeSet { chain, asset }); + Ok(()) + } + + /// Set native asset + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn set_chain_stable(&mut self, chain: Vec, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + let mut chain_entity = self.chains.get(&chain).ok_or(Error::ChainNotFound)?; + match chain_entity { + Chain::Evm(ref mut evm_chain) => { + evm_chain.set_stable(asset.clone()); + } + } + // Insert back + self.chains.insert(&chain, &chain_entity); + Self::env().emit_event(ChainStableSet { chain, asset }); + Ok(()) + } + + /// Set RPC endpoint + /// Authorized method, only the contract owner can call + #[ink(message)] + pub fn set_chain_endpoint(&mut self, chain: Vec, endpoint: Vec) -> Result<()> { + self.esure_admin()?; + + let mut chain_entity = self.chains.get(&chain).ok_or(Error::ChainNotFound)?; + match chain_entity { + Chain::Evm(ref mut evm_chain) => { + evm_chain.set_endpoint(endpoint.clone()); + } + } + // Insert back + self.chains.insert(&chain, &chain_entity); + Self::env().emit_event(ChainEndpointSet { chain, endpoint }); + Ok(()) + } + + /// Returns error if caller is not admin + fn esure_admin(&self) -> Result<()> { + let caller = self.env().caller(); + if self.admin != caller { + return Err(Error::BadOrigin); + } + Ok(()) + } + } + + #[cfg(test)] + mod test { + use super::*; + use dotenv::dotenv; + use ink_lang as ink; + use phala_pallet_common::WrapSlice; + use pink_extension::PinkEnvironment; + use scale::Encode; + use xcm::latest::{prelude::*, MultiLocation}; + + type Event = ::Type; + + fn default_accounts() -> ink_env::test::DefaultAccounts { + ink_env::test::default_accounts::() + } + + fn set_caller(sender: AccountId) { + ink_env::test::set_caller::(sender); + } + + fn assert_events(mut expected: Vec) { + let mut actual: Vec = + ink_env::test::recorded_events().collect(); + + assert_eq!(actual.len(), expected.len(), "Event count don't match"); + expected.reverse(); + + for evt in expected { + let next = actual.pop().expect("event expected"); + // Compare event data + assert_eq!( + next.data, + ::encode(&evt), + "Event data don't match" + ); + } + } + + #[ink::test] + fn test_default_works() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let registry = Registry::new(); + assert_eq!(registry.admin, accounts.alice); + } + + #[ink::test] + fn test_register_chain_should_works() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + assert_events(vec![ChainRegistered { chain: info }.into()]); + } + + #[ink::test] + fn test_dumplicated_register_chain_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + assert_eq!( + registry.register_chain(info), + Err(Error::ChainAlreadyRegistered) + ); + } + + #[ink::test] + fn test_unregister_chain_should_works() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + assert_eq!(registry.unregister_chain(info.name.clone()), Ok(())); + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + ChainUnregistered { chain: info.name }.into(), + ]); + } + + #[ink::test] + fn test_set_native_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + + let weth = AssetInfo { + name: b"Wrap Ether".to_vec(), + symbol: b"WETH".to_vec(), + decimals: 18, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.set_chain_native(info.name.clone(), weth.clone()), + Ok(()) + ); + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + ChainNativeSet { + chain: b"Ethereum".to_vec(), + asset: weth.clone(), + } + .into(), + ]); + let chain = registry.chains.get(info.name.clone()).unwrap(); + match chain { + Chain::Evm(evm_chain) => { + assert_eq!(evm_chain.get_info().native, Some(weth)); + } + } + } + + #[ink::test] + fn test_set_native_without_permisssion_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let weth = AssetInfo { + name: b"Wrap Ether".to_vec(), + symbol: b"WETH".to_vec(), + decimals: 18, + location: b"Somewhere on Ethereum".to_vec(), + }; + set_caller(accounts.bob); + assert_eq!( + registry.set_chain_native(info.name, weth), + Err(Error::BadOrigin) + ); + } + + #[ink::test] + fn test_set_stable_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.set_chain_stable(info.name.clone(), usdc.clone()), + Ok(()) + ); + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + ChainStableSet { + chain: b"Ethereum".to_vec(), + asset: usdc.clone(), + } + .into(), + ]); + let chain = registry.chains.get(info.name.clone()).unwrap(); + match chain { + Chain::Evm(evm_chain) => { + assert_eq!(evm_chain.get_info().stable, Some(usdc)); + } + } + } + + #[ink::test] + fn test_set_stable_without_permisssion_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + set_caller(accounts.bob); + assert_eq!( + registry.set_chain_native(info.name, usdc), + Err(Error::BadOrigin) + ); + } + + #[ink::test] + fn test_set_endpoint_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + assert_eq!( + registry.set_chain_endpoint(info.name.clone(), b"new endpoint".to_vec()), + Ok(()) + ); + + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + ChainEndpointSet { + chain: b"Ethereum".to_vec(), + endpoint: b"new endpoint".to_vec(), + } + .into(), + ]); + let chain = registry.chains.get(info.name.clone()).unwrap(); + match chain { + Chain::Evm(evm_chain) => { + assert_eq!(evm_chain.get_info().endpoint, b"new endpoint".to_vec()); + } + } + } + + #[ink::test] + fn test_set_endpoint_without_permisssion_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + set_caller(accounts.bob); + assert_eq!( + registry.set_chain_endpoint(info.name, b"new endpoint".to_vec()), + Err(Error::BadOrigin) + ); + } + + #[ink::test] + fn test_register_asset_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.register_asset(b"Ethereum".to_vec(), usdc.clone()), + Ok(()) + ); + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + AssetRegistered { + chain: b"Ethereum".to_vec(), + asset: usdc, + } + .into(), + ]); + } + + #[ink::test] + fn test_duplicated_register_asset_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.register_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + assert_eq!( + registry.register_asset(info.name, usdc), + Err(Error::AssetAlreadyRegistered) + ); + } + + #[ink::test] + fn test_register_asset_without_permission_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + set_caller(accounts.bob); + + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.register_asset(info.name, usdc), + Err(Error::BadOrigin) + ); + } + + #[ink::test] + fn test_unregister_asset_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.register_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + assert_eq!( + registry.unregister_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + + assert_events(vec![ + ChainRegistered { + chain: info.clone(), + } + .into(), + AssetRegistered { + chain: b"Ethereum".to_vec(), + asset: usdc.clone(), + } + .into(), + AssetUnregistered { + chain: b"Ethereum".to_vec(), + asset: usdc, + } + .into(), + ]); + } + + #[ink::test] + fn test_unregister_unregistered_asset_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + // First time unregister + assert_eq!( + registry.unregister_asset(info.name.clone(), usdc.clone()), + Err(Error::AssetNotFound) + ); + assert_eq!( + registry.register_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + assert_eq!( + registry.unregister_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + // Second time unregister + assert_eq!( + registry.unregister_asset(info.name, usdc), + Err(Error::AssetNotFound) + ); + } + + #[ink::test] + fn test_unregister_asset_without_permission_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + // Register by owner: alice + assert_eq!( + registry.register_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + set_caller(accounts.bob); + // Bob trying to unregister + assert_eq!( + registry.unregister_asset(info.name.clone(), usdc), + Err(Error::BadOrigin) + ); + } + + #[ink::test] + fn test_unregister_asset_with_wrong_location_should_fail() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"endpoint".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Somewhere on Ethereum".to_vec(), + }; + let wrong_usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"Wrong location on Ethereum".to_vec(), + }; + assert_eq!(registry.register_asset(info.name.clone(), usdc), Ok(())); + assert_eq!( + registry.unregister_asset(info.name, wrong_usdc), + Err(Error::AssetNotFound) + ); + } + + #[ink::test] + fn test_query_funtions_should_work() { + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"https://mainnet.infura.io/v3/6d61e7957c1c489ea8141e947447405b".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + let usdc = AssetInfo { + name: b"USD Coin".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 6, + location: b"+Somewhere on Ethereum".to_vec(), + }; + let weth = AssetInfo { + name: b"Wrap Ether".to_vec(), + symbol: b"WETH".to_vec(), + decimals: 18, + location: b"-Somewhere on Ethereum".to_vec(), + }; + assert_eq!( + registry.register_asset(info.name.clone(), usdc.clone()), + Ok(()) + ); + assert_eq!( + registry.register_asset(info.name.clone(), weth.clone()), + Ok(()) + ); + let chain = registry.chains.get(&info.name).unwrap(); + match chain { + Chain::Evm(evm_chain) => { + assert_eq!( + evm_chain.registered_assets(), + vec![usdc.clone(), weth.clone()] + ); + assert_eq!( + evm_chain.lookup_by_name(weth.name.clone()), + Some(weth.clone()) + ); + assert_eq!(evm_chain.lookup_by_name(b"Wrong Name".to_vec()), None); + assert_eq!( + evm_chain.lookup_by_symbol(weth.symbol.clone()), + Some(weth.clone()) + ); + assert_eq!(evm_chain.lookup_by_symbol(b"Wrong Symbol".to_vec()), None); + assert_eq!( + evm_chain.lookup_by_location(weth.location.clone()), + Some(weth.clone()) + ); + assert_eq!( + evm_chain.lookup_by_location(b"Wrong Location".to_vec()), + None + ); + assert_eq!(registry.unregister_asset(info.name.clone(), usdc), Ok(())); + assert_eq!( + registry + .chains + .get(&info.name) + .unwrap() + .as_evm() + .unwrap() + .registered_assets(), + vec![weth.clone()] + ); + assert_eq!(registry.unregister_asset(info.name.clone(), weth), Ok(())); + assert_eq!( + registry + .chains + .get(&info.name) + .unwrap() + .as_evm() + .unwrap() + .registered_assets(), + vec![] + ); + } + } + } + + #[ink::test] + fn test_query_balance_should_work() { + dotenv().ok(); + use std::env; + + pink_extension_runtime::mock_ext::mock_all_ext(); + + let accounts = default_accounts(); + set_caller(accounts.alice); + let mut registry = Registry::new(); + + let info = ChainInfo { + name: b"Ethereum".to_vec(), + chain_type: ChainType::Evm, + native: None, + stable: None, + endpoint: b"https://mainnet.infura.io/v3/6d61e7957c1c489ea8141e947447405b".to_vec(), + network: None, + }; + assert_eq!(registry.register_chain(info.clone()), Ok(())); + + let pha_location = MultiLocation::new( + 1, + X4( + Parachain(2004), + GeneralKey(WrapSlice(b"phat").into()), + GeneralKey(WrapSlice(&[0; 32]).into()), + GeneralKey( + WrapSlice(&hex_literal::hex![ + "6c5bA91642F10282b576d91922Ae6448C9d52f4E" + ]) + .into(), + ), + ), + ); + let account_location = MultiLocation::new( + 1, + X4( + Parachain(2004), + GeneralKey(WrapSlice(b"phat").into()), + GeneralKey(WrapSlice(&[0; 32]).into()), + GeneralKey( + WrapSlice(&hex_literal::hex![ + "e887376a93bDa91ed66D814528D7aeEfe59990a5" + ]) + .into(), + ), + ), + ); + let pha = AssetInfo { + name: b"Phala Token".to_vec(), + symbol: b"PHA".to_vec(), + decimals: 18, + location: pha_location.clone().encode(), + }; + + assert_eq!( + registry.register_asset(info.name.clone(), pha.clone()), + Ok(()) + ); + let chain = registry.chains.get(&info.name).unwrap(); + match chain { + Chain::Evm(evm_chain) => { + // If not equal, check the real balance first. + assert_eq!( + evm_chain.balance_of(AssetId::Concrete(pha_location), account_location), + Ok(35_000_000_000_000_000u128) + ); + } + } + } + } +} diff --git a/contracts/solution-provider/Cargo.toml b/contracts/solution-provider/Cargo.toml deleted file mode 100755 index 4830aa66..00000000 --- a/contracts/solution-provider/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "traits" -version = "0.1.0" -authors = ["Wenfeng Wang "] -edition = "2021" - -[dependencies] -ink_primitives = { version = "3.3.1", default-features = false } -ink_metadata = { version = "3.3.1", default-features = false, features = ["derive"], optional = true } -ink_env = { version = "3.3.1", default-features = false } -ink_storage = { version = "3.3.1", default-features = false } -ink_lang = { version = "3.3.1", default-features = false } - -scale = { package = "parity-scale-codec", version = "3.1", default-features = false, features = ["derive"] } -scale-info = { version = "2.1", default-features = false, features = ["derive"], optional = true } - -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } -xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.29", default-features = false } - -pink-extension = { version = "0.1", default-features = false, features = ["ink-as-dependency"] } - -[lib] -name = "traits" -path = "lib.rs" -crate-type = [ - # Used for normal contract Wasm blobs. - "cdylib", - # Used for ABI generation. - "rlib", -] - -[features] -default = ["std"] -std = [ - "ink_metadata/std", - "ink_env/std", - "ink_storage/std", - "ink_primitives/std", - "sp-io/std", - "xcm/std", - "scale/std", - "scale-info/std", - "pink-extension/std", -] -ink-as-dependency = [] \ No newline at end of file diff --git a/contracts/solution-provider/lib.rs b/contracts/solution-provider/lib.rs deleted file mode 100644 index a3204eb4..00000000 --- a/contracts/solution-provider/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -extern crate alloc; - -pub mod executor; -pub mod registry; - -/// Evaluate `$x:expr` and if not true return `Err($y:expr)`. -/// -/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. -#[macro_export] -macro_rules! ensure { - ( $condition:expr, $error:expr $(,)? ) => {{ - if !$condition { - return ::core::result::Result::Err(::core::convert::Into::into($error)); - } - }}; -} diff --git a/contracts/sub-chain/Cargo.toml b/contracts/sub-chain/Cargo.toml new file mode 100644 index 00000000..300ae4ee --- /dev/null +++ b/contracts/sub-chain/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sub-chain" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/contracts/sub-chain/src/lib.rs b/contracts/sub-chain/src/lib.rs new file mode 100644 index 00000000..65fd303d --- /dev/null +++ b/contracts/sub-chain/src/lib.rs @@ -0,0 +1,337 @@ +#![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +use pink_extension as pink; + +#[pink::contract(env = PinkEnvironment)] +#[pink(inner=ink_lang::contract)] +mod sub_chain { + use super::pink; + use alloc::vec; + use alloc::{string::String, vec::Vec}; + use ink_lang as ink; + use phala_pallet_common::WrapSlice; + use pink::{http_get, PinkEnvironment}; + use pink_web3::api::{Eth, Namespace}; + use pink_web3::contract::{Contract, Options}; + use pink_web3::transports::{resolve_ready, PinkHttp}; + use pink_web3::types::{Address, Res, H256}; + use scale::{Decode, Encode}; + use traits::ensure; + use traits::registry::{ + AssetInfo, AssetsRegisry, BalanceFetcher, ChainInspector, ChainType, Error as RegistryError, + }; + use xcm::latest::{prelude::*, Fungibility::Fungible, MultiAsset, MultiLocation}; + + #[ink(storage)] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct EvmChain { + admin: AccountId, + /// The chain name + chain: Vec, + /// Type of chain + chain_type: ChainType, + /// Network id of chain + network_id: u8, + /// The registered assets list + assets: Vec, + /// Native asset of chain + native: Option, + /// Stable asset of chain + stable: Option, + /// RPC endpoint of chain + endpoint: Vec, + } + + /// Event emitted when native asset set. + #[ink(event)] + pub struct NativeSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: Option, + } + + /// Event emitted when stable asset set. + #[ink(event)] + pub struct StableSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: Option, + } + + /// Event emitted when RPC endpoint asset set. + #[ink(event)] + pub struct EndpointSet { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + endpoint: Vec, + } + + /// Event emitted when asset registered. + #[ink(event)] + pub struct Registered { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: Option, + } + + /// Event emitted when asset unregistered. + #[ink(event)] + pub struct Unregistered { + #[ink(topic)] + chain: Vec, + #[ink(topic)] + asset: Option, + } + + pub type Result = core::result::Result; + + impl SubChain { + #[ink(constructor)] + /// Create an Substrate chain entity + pub fn new(chain: Vec, network_id: u8, endpoint: Vec) -> Self { + SubChain { + admin: Self::env().caller(), + chain, + network_id, + chain_type: ChainType::Sub, + assets: vec![], + native: None, + stable: None, + endpoint, + } + } + + /// Set native asset + /// Authorized method, only the contract owner can do + #[ink(message)] + pub fn set_native(&mut self, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + self.native = Some(asset.clone()); + Self::env().emit_event(NativeSet { + chain: self.chain.clone(), + asset: Some(asset), + }); + Ok(()) + } + + /// Set native asset + /// Authorized method, only the contract owner can do + #[ink(message)] + pub fn set_stable(&mut self, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + self.stable = Some(asset.clone()); + Self::env().emit_event(StableSet { + chain: self.chain.clone(), + asset: Some(asset), + }); + Ok(()) + } + + /// Set RPC endpoint + /// Authorized method, only the contract owner can do + #[ink(message)] + pub fn set_endpoint(&mut self, endpoint: Vec) -> Result<()> { + self.esure_admin()?; + self.endpoint = endpoint.clone(); + Self::env().emit_event(EndpointSet { + chain: self.chain.clone(), + endpoint, + }); + Ok(()) + } + + /// Returns error if caller is not admin + fn esure_admin(&self) -> Result<()> { + let caller = self.env().caller(); + if self.admin != caller { + return Err(RegistryError::BadOrigin); + } + Ok(()) + } + + /// An asset id represented by MultiLocation like: + /// (1, X4(Parachain(phala_id), GeneralKey(“phat"), GeneralKey(cluster_id), GeneralKey(erc20_address))) + fn extract_token(&self, asset: &AssetId) -> Option
{ + match asset { + Concrete(location) => { + match (location.parents, &location.interior) { + ( + 1, + Junctions::X4( + Parachain(_id), + GeneralKey(_phat_key), + GeneralKey(_cluster_id), + GeneralKey(erc20_address), + ), + ) => { + // TODO.wf verify arguments + if erc20_address.len() != 20 { + return None; + }; + let address: Address = Address::from_slice(&erc20_address); + Some(address) + } + _ => None, + } + } + _ => None, + } + } + + /// An account location represented by MultiLocation like: + /// (1, X4(Parachain(phala_id), GeneralKey(“phat"), GeneralKey(cluster_id), GeneralKey(account_address))) + fn extract_account(&self, location: &MultiLocation) -> Option
{ + match (location.parents, &location.interior) { + ( + 1, + Junctions::X4( + Parachain(_id), + GeneralKey(_phat_key), + GeneralKey(_cluster_id), + GeneralKey(account_address), + ), + ) => { + // TODO.wf verify arguments + if account_address.len() != 20 { + return None; + }; + let address: Address = Address::from_slice(&account_address); + Some(address) + } + _ => None, + } + } + } + + impl ChainInspector for SubChain { + /// Return admin of the chain + #[ink(message)] + fn owner(&self) -> AccountId { + self.admin + } + + /// Return name of the chain + #[ink(message)] + fn chain_name(&self) -> Vec { + self.chain.clone() + } + + /// Return set native asset of the chain + #[ink(message)] + fn chain_type(&self) -> ChainType { + self.chain_type.clone() + } + + /// Return set native asset of the chain + #[ink(message)] + fn native_asset(&self) -> Option { + self.native.clone() + } + + /// Return set stable asset of the chain + #[ink(message)] + fn stable_asset(&self) -> Option { + self.stable.clone() + } + + /// Return RPC endpoint of the chain + #[ink(message)] + fn endpoint(&self) -> Vec { + self.endpoint.clone() + } + } + + impl BalanceFetcher for SubChain { + #[ink(message)] + fn balance_of(&self, asset: AssetId, account: MultiLocation) -> Result { + Ok(0) + } + } + + impl AssetsRegisry for SubChain { + /// Register the asset + /// Authorized method, only the contract owner can do + #[ink(message)] + fn register(&mut self, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + ensure!( + self.assets + .iter() + .position(|a| a.location == asset.location) + .is_none(), + RegistryError::AssetAlreadyRegistered + ); + self.assets.push(asset.clone()); + + Self::env().emit_event(Registered { + chain: self.chain.clone(), + asset: Some(asset), + }); + Ok(()) + } + + /// Unregister the asset + /// Authorized method, only the contract owner can do + #[ink(message)] + fn unregister(&mut self, asset: AssetInfo) -> Result<()> { + self.esure_admin()?; + + let index = self + .assets + .iter() + .position(|a| a.location == asset.location) + .ok_or(RegistryError::AssetNotFound)?; + self.assets.remove(index); + + Self::env().emit_event(Unregistered { + chain: self.chain.clone(), + asset: Some(asset), + }); + Ok(()) + } + + /// Return all registerd assets + #[ink(message)] + fn registered_assets(&self) -> Vec { + self.assets.clone() + } + + #[ink(message)] + fn lookup_by_name(&self, name: Vec) -> Option { + self.assets + .iter() + .position(|a| a.name == name) + .and_then(|idx| Some(self.assets[idx].clone())) + } + + #[ink(message)] + fn lookup_by_symbol(&self, symbol: Vec) -> Option { + self.assets + .iter() + .position(|a| a.symbol == symbol) + .and_then(|idx| Some(self.assets[idx].clone())) + } + + #[ink(message)] + fn lookup_by_location(&self, location: Vec) -> Option { + self.assets + .iter() + .position(|a| a.location == location) + .and_then(|idx| Some(self.assets[idx].clone())) + } + } + + #[cfg(test)] + mod test { + use super::*; + use dotenv::dotenv; + use ink_lang as ink; + + } +} \ No newline at end of file diff --git a/index/Cargo.lock b/index/Cargo.lock index 38a6eb2e..f4ebb363 100644 --- a/index/Cargo.lock +++ b/index/Cargo.lock @@ -876,6 +876,9 @@ version = "0.1.0" dependencies = [ "hex", "hex-literal", + "ink_metadata", + "ink_primitives", + "ink_storage", "parity-scale-codec", "pink-extension", "pink-web3", diff --git a/index/Cargo.toml b/index/Cargo.toml index f33983de..085c1eb8 100755 --- a/index/Cargo.toml +++ b/index/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "index" version = "0.1.0" -authors = ["Wenfeng Wang "] +authors = ["Phala Network"] edition = "2021" +license = "Apache 2.0" +homepage = "https://phala.network/" +repository = "https://github.com/Phala-Network/index-contract" [dependencies] scale = { package = "parity-scale-codec", version = "3.1", default-features = false, features = ["derive"] } @@ -15,6 +18,9 @@ hex = { version = "0.4.3", default-features = false } pink-extension = { version = "0.1", default-features = false, features = ["ink-as-dependency"] } pink-web3 = { git = "https://github.com/Phala-Network/pink-web3.git", branch = "pink", default-features = false, features = ["pink", "signing"]} xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.29", default-features = false } +ink_storage = { version = "3", default-features = false } +ink_primitives = { version = "3.3.1", default-features = false } +ink_metadata = { version = "3.3.1", default-features = false, features = ["derive"], optional = true } [dependencies.sp-io] git = "https://github.com/paritytech/substrate" @@ -30,4 +36,8 @@ std = [ "pink-extension/std", "pink-web3/std", "xcm/std", + "sp-io/std", + "ink_storage/std", + "ink_metadata/std", + "ink_primitives/std", ] diff --git a/index/src/executors/bridge_executor.rs b/index/src/executors/bridge_executor.rs index c2e44914..14a351e0 100644 --- a/index/src/executors/bridge_executor.rs +++ b/index/src/executors/bridge_executor.rs @@ -1,4 +1,7 @@ -use crate::traits::{Address, Error, Executor}; +use crate::traits::{ + common::{Address, Error}, + executor::Executor, +}; use crate::transactors::ChainBridgeClient; use pink_web3::api::{Eth, Namespace}; use pink_web3::contract::Contract; diff --git a/index/src/lib.rs b/index/src/lib.rs index 0407ba03..2b88b204 100644 --- a/index/src/lib.rs +++ b/index/src/lib.rs @@ -5,6 +5,7 @@ extern crate alloc; mod constants; pub mod executors; pub mod prelude; +pub mod registry; mod traits; mod transactors; pub mod utils; diff --git a/index/src/prelude.rs b/index/src/prelude.rs index 8ffcbd97..a2e4d240 100644 --- a/index/src/prelude.rs +++ b/index/src/prelude.rs @@ -1,3 +1,5 @@ pub type Evm2PhalaExecutor = super::executors::bridge_executor::Evm2PhalaExecutor; -pub use super::traits::Executor; -pub type Address = super::traits::Address; +pub use crate::traits::common::Error; +pub use crate::traits::executor::Executor; +pub use crate::traits::registry::*; +pub type Address = crate::traits::common::Address; diff --git a/index/src/registry/evm_chain.rs b/index/src/registry/evm_chain.rs new file mode 100644 index 00000000..3943ba8b --- /dev/null +++ b/index/src/registry/evm_chain.rs @@ -0,0 +1,187 @@ +#![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +use crate::ensure; +use crate::traits::{ + common::Error as RegistryError, + registry::{AssetInfo, AssetsRegisry, BalanceFetcher, ChainInfo, ChainInspector, ChainMutate}, +}; +use alloc::vec; +use alloc::{string::String, vec::Vec}; +use ink_storage::traits::{PackedLayout, SpreadLayout, StorageLayout}; +use pink_web3::api::{Eth, Namespace}; +use pink_web3::contract::{Contract, Options}; +use pink_web3::transports::{resolve_ready, PinkHttp}; +use pink_web3::types::Address; +use xcm::latest::{prelude::*, MultiLocation}; + +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +pub struct EvmChain { + pub info: ChainInfo, + pub assets: Vec, +} + +impl EvmChain { + /// Create an EvmChain entity + pub fn new(info: ChainInfo) -> Self { + let mut assets: Vec = vec![]; + if let Some(ref stable) = info.stable { + assets.push(stable.clone()) + } + if let Some(ref native) = info.native { + assets.push(native.clone()) + } + EvmChain { info, assets } + } + + /// An asset id represented by MultiLocation like: + /// (1, X4(Parachain(phala_id), GeneralKey(“phat"), GeneralKey(cluster_id), GeneralKey(erc20_address))) + fn extract_token(&self, asset: &AssetId) -> Option
{ + match asset { + Concrete(location) => { + match (location.parents, &location.interior) { + ( + 1, + Junctions::X4( + Parachain(_id), + GeneralKey(_phat_key), + GeneralKey(_cluster_id), + GeneralKey(erc20_address), + ), + ) => { + // TODO.wf verify arguments + if erc20_address.len() != 20 { + return None; + }; + let address: Address = Address::from_slice(erc20_address); + Some(address) + } + _ => None, + } + } + _ => None, + } + } + + /// An account location represented by MultiLocation like: + /// (1, X4(Parachain(phala_id), GeneralKey(“phat"), GeneralKey(cluster_id), GeneralKey(account_address))) + fn extract_account(&self, location: &MultiLocation) -> Option
{ + match (location.parents, &location.interior) { + ( + 1, + Junctions::X4( + Parachain(_id), + GeneralKey(_phat_key), + GeneralKey(_cluster_id), + GeneralKey(account_address), + ), + ) => { + // TODO.wf verify arguments + if account_address.len() != 20 { + return None; + }; + let address: Address = Address::from_slice(account_address); + Some(address) + } + _ => None, + } + } +} + +impl ChainInspector for EvmChain { + fn get_info(&self) -> ChainInfo { + self.info.clone() + } +} + +impl ChainMutate for EvmChain { + fn set_native(&mut self, native: AssetInfo) { + self.info.native = Some(native); + } + + fn set_stable(&mut self, stable: AssetInfo) { + self.info.stable = Some(stable); + } + + fn set_endpoint(&mut self, endpoint: Vec) { + self.info.endpoint = endpoint; + } +} + +impl BalanceFetcher for EvmChain { + fn balance_of( + &self, + asset: AssetId, + account: MultiLocation, + ) -> core::result::Result { + let transport = Eth::new(PinkHttp::new(String::from_utf8_lossy(&self.info.endpoint))); + let token_address: Address = self + .extract_token(&asset) + .ok_or(RegistryError::ExtractLocationFailed)?; + let account: Address = self + .extract_account(&account) + .ok_or(RegistryError::ExtractLocationFailed)?; + let erc20 = Contract::from_json( + transport, + // PHA address + token_address, + include_bytes!("./res/erc20-abi.json"), + ) + .map_err(|_| RegistryError::ConstructContractFailed)?; + // TODO.wf handle potential failure smoothly instead of unwrap directly + let result: u128 = + resolve_ready(erc20.query("balanceOf", account, None, Options::default(), None)) + .unwrap(); + Ok(result) + } +} + +impl AssetsRegisry for EvmChain { + /// Register the asset + fn register(&mut self, asset: AssetInfo) -> core::result::Result<(), RegistryError> { + ensure!( + !self.assets.iter().any(|a| a.location == asset.location), + RegistryError::AssetAlreadyRegistered + ); + self.assets.push(asset); + Ok(()) + } + + /// Unregister the asset + fn unregister(&mut self, asset: AssetInfo) -> core::result::Result<(), RegistryError> { + let index = self + .assets + .iter() + .position(|a| a.location == asset.location) + .ok_or(RegistryError::AssetNotFound)?; + self.assets.remove(index); + Ok(()) + } + + /// Return all registerd assets + fn registered_assets(&self) -> Vec { + self.assets.clone() + } + + fn lookup_by_name(&self, name: Vec) -> Option { + self.assets + .iter() + .position(|a| a.name == name) + .map(|idx| self.assets[idx].clone()) + } + + fn lookup_by_symbol(&self, symbol: Vec) -> Option { + self.assets + .iter() + .position(|a| a.symbol == symbol) + .map(|idx| self.assets[idx].clone()) + } + + fn lookup_by_location(&self, location: Vec) -> Option { + self.assets + .iter() + .position(|a| a.location == location) + .map(|idx| self.assets[idx].clone()) + } +} diff --git a/index/src/registry/mod.rs b/index/src/registry/mod.rs new file mode 100644 index 00000000..3fa54b7f --- /dev/null +++ b/index/src/registry/mod.rs @@ -0,0 +1 @@ +pub mod evm_chain; diff --git a/index/src/registry/res/erc20-abi.json b/index/src/registry/res/erc20-abi.json new file mode 100644 index 00000000..5a2af322 --- /dev/null +++ b/index/src/registry/res/erc20-abi.json @@ -0,0 +1,156 @@ +[ + { + "type": "function", + "name": "name", + "constant": true, + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ] + }, + { + "type": "function", + "name": "decimals", + "constant": true, + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8" + } + ] + }, + { + "type": "function", + "name": "balanceOf", + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "type": "function", + "name": "symbol", + "constant": true, + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ] + }, + { + "type": "function", + "name": "transfer", + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "outputs": [] + }, + { + "type": "constructor", + "inputs": [ + { + "name": "_supply", + "type": "uint256" + }, + { + "name": "_name", + "type": "string" + }, + { + "name": "_decimals", + "type": "uint8" + }, + { + "name": "_symbol", + "type": "string" + } + ] + }, + { + "name": "Transfer", + "type": "event", + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ] + }, + { + "constant":false, + "inputs":[ + { + "name":"_spender", + "type":"address" + }, + { + "name":"_value", + "type":"uint256" + } + ], + "name":"approve", + "outputs":[ + { + "name":"success", + "type":"bool" + } + ], + "type":"function" + }, + { + "constant":true, + "inputs":[ + { + "name":"", + "type":"address" + }, + { + "name":"", + "type":"address" + } + ], + "name":"allowance", + "outputs":[ + { + "name":"", + "type":"uint256" + } + ], + "type":"function" + } +] diff --git a/index/src/traits.rs b/index/src/traits.rs deleted file mode 100644 index 0ea26a29..00000000 --- a/index/src/traits.rs +++ /dev/null @@ -1,31 +0,0 @@ -use primitive_types::{H160, H256, U256}; -use scale::{Decode, Encode}; - -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum Error { - BadAbi, - InvalidAddress, -} - -pub enum Address { - EthAddr(H160), - SubAddr(H256), -} - -pub trait Executor { - fn new( - bridge_address: Address, - abi_json: &[u8], - rpc: &str, - ) -> core::result::Result - where - Self: Sized; - fn transfer( - &self, - signer: [u8; 32], // FIXME - token_rid: H256, - amount: U256, - recipient: Address, - ) -> core::result::Result<(), Error>; -} diff --git a/index/src/traits/common.rs b/index/src/traits/common.rs new file mode 100644 index 00000000..636b9a58 --- /dev/null +++ b/index/src/traits/common.rs @@ -0,0 +1,25 @@ +use primitive_types::{H160, H256}; +use scale::{Decode, Encode}; + +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum Error { + BadAbi, + BadOrigin, + AssetAlreadyRegistered, + AssetNotFound, + ChainAlreadyRegistered, + ChainNotFound, + ExtractLocationFailed, + InvalidAddress, + ConstructContractFailed, + FetchDataFailed, + Unimplemented, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum Address { + EthAddr(H160), + SubAddr(H256), +} diff --git a/contracts/solution-provider/executor.rs b/index/src/traits/executor.rs similarity index 61% rename from contracts/solution-provider/executor.rs rename to index/src/traits/executor.rs index ba9ece61..4a73a207 100644 --- a/contracts/solution-provider/executor.rs +++ b/index/src/traits/executor.rs @@ -1,17 +1,33 @@ #![cfg_attr(not(feature = "std"), no_std)] + extern crate alloc; +use super::common::Address; +use super::common::Error; use alloc::vec::Vec; -use ink_lang as ink; -use ink_storage::traits::{ - PackedAllocate, PackedLayout, SpreadAllocate, SpreadLayout, StorageLayout, -}; +use primitive_types::{H256, U256}; use scale::{Decode, Encode}; -use xcm::latest::{AssetId, MultiLocation}; + +pub trait Executor { + fn new( + bridge_address: Address, + abi_json: &[u8], + rpc: &str, + ) -> core::result::Result + where + Self: Sized; + fn transfer( + &self, + signer: [u8; 32], // FIXME + token_rid: H256, + amount: U256, + recipient: Address, + ) -> core::result::Result<(), Error>; +} /// Definition of source edge -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct SourceEdge { /// asset/chain to: Vec, @@ -24,8 +40,8 @@ pub struct SourceEdge { } /// Definition of SINK edge -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct SinkEdge { /// asset/chain from: Vec, @@ -38,8 +54,8 @@ pub struct SinkEdge { } /// Definition of swap operation edge -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct SwapEdge { /// asset/chain from: Vec, @@ -62,8 +78,8 @@ pub struct SwapEdge { } /// Definition of bridge operation edge -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct BridgeEdge { /// asset/chain from: Vec, @@ -81,8 +97,8 @@ pub struct BridgeEdge { b1: Option, } -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] enum EdgeStatus { /// Haven't started executing this edge yet, which is the default status. Inactive, @@ -96,17 +112,17 @@ enum EdgeStatus { Confirmed(u128), } -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub enum EdgeMeta { - SourceEdge(SourceEdge), - SinkEdge(SinkEdge), - SwapEdge(SwapEdge), - BridgeEdge(BridgeEdge), + Source(SourceEdge), + Sink(SinkEdge), + Swap(SwapEdge), + Bridge(BridgeEdge), } -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct Edge { /// Content of the edge edge: EdgeMeta, @@ -120,8 +136,8 @@ pub struct Edge { nonce: Option, } -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct Solution { /// All edges to included in the solution edges: Vec, diff --git a/index/src/traits/mod.rs b/index/src/traits/mod.rs new file mode 100644 index 00000000..6f48f204 --- /dev/null +++ b/index/src/traits/mod.rs @@ -0,0 +1,3 @@ +pub mod common; +pub mod executor; +pub mod registry; diff --git a/contracts/solution-provider/registry.rs b/index/src/traits/registry.rs similarity index 72% rename from contracts/solution-provider/registry.rs rename to index/src/traits/registry.rs index 1a78de04..9b05b02c 100644 --- a/contracts/solution-provider/registry.rs +++ b/index/src/traits/registry.rs @@ -1,27 +1,11 @@ #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +use super::common::Error; use alloc::vec::Vec; -use ink_lang as ink; -use ink_storage::traits::{ - PackedAllocate, PackedLayout, SpreadAllocate, SpreadLayout, StorageLayout, -}; -use scale::{Decode, Encode}; +use ink_storage::traits::{PackedLayout, SpreadAllocate, SpreadLayout, StorageLayout}; use xcm::latest::{AssetId, MultiLocation}; -/// Errors that can occur upon registry module. -#[derive(Debug, PartialEq, Eq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum Error { - BadOrigin, - AssetAlreadyRegistered, - AssetNotFound, - ExtractLocationFailed, - InvalidContractAbi, - ConstructContractFailed, - FetchDataFailed, -} - /// Query the account balance of an asset under a multichain scenario is a mess, /// not only because different chains have different account systems but also have /// different asset registry mechanism(e.g. Acala use Currency, Phala use pallet-assets @@ -35,11 +19,13 @@ pub enum Error { /// it with `MultiLocation::new(1, X3(Parachain(2004), GeneralIndex(0), GeneralKey(usdc_addr))`. /// /// Both `AssetId` and `MultiLocation` are primitives introduced by XCM format. -#[ink::trait_definition] pub trait BalanceFetcher { /// Return on-chain `asset` amount of `account` - #[ink(message)] - fn balance_of(&self, asset: AssetId, account: MultiLocation) -> Result; + fn balance_of( + &self, + asset: AssetId, + account: MultiLocation, + ) -> core::result::Result; } /// Beyond general properties like `name`, `symbol` and `decimals`, @@ -64,29 +50,22 @@ pub struct AssetInfo { pub location: Vec, } -#[ink::trait_definition] pub trait AssetsRegisry { /// Register the asset /// Authorized method, only the contract owner can do - #[ink(message)] fn register(&mut self, asset: AssetInfo) -> core::result::Result<(), Error>; /// Unregister the asset /// Authorized method, only the contract owner can do - #[ink(message)] fn unregister(&mut self, asset: AssetInfo) -> core::result::Result<(), Error>; /// Return all registerd assets - #[ink(message)] fn registered_assets(&self) -> Vec; - #[ink(message)] fn lookup_by_name(&self, name: Vec) -> Option; - #[ink(message)] fn lookup_by_symbol(&self, symbol: Vec) -> Option; - #[ink(message)] fn lookup_by_location(&self, location: Vec) -> Option; } @@ -97,36 +76,31 @@ pub enum ChainType { Sub, } -#[ink::trait_definition] -pub trait ChainInspector { - /// Return admin of the chain - #[ink(message)] - fn owner(&self) -> ink_env::AccountId; - - /// Return name of the chain - #[ink(message)] - fn chain_name(&self) -> Vec; - - /// Return set native asset of the chain - #[ink(message)] - fn chain_type(&self) -> ChainType; - - /// Return set native asset of the chain - #[ink(message)] - fn native_asset(&self) -> Option; +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +pub struct ChainInfo { + pub name: Vec, + pub chain_type: ChainType, + pub native: Option, + pub stable: Option, + pub endpoint: Vec, + pub network: Option, +} - /// Return set stable asset of the chain - #[ink(message)] - fn stable_asset(&self) -> Option; +pub trait ChainInspector { + /// Return information of the chain + fn get_info(&self) -> ChainInfo; +} - /// Return RPC endpoint of the chain - #[ink(message)] - fn endpoint(&self) -> Vec; +pub trait ChainMutate { + fn set_native(&mut self, native: AssetInfo); + fn set_stable(&mut self, stable: AssetInfo); + fn set_endpoint(&mut self, endpoint: Vec); } /// Asset informatios should be contained in the input graph -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct AssetGraph { /// Chain name that asset belong to chain: Vec, @@ -141,8 +115,8 @@ pub struct AssetGraph { } /// Trading pair informatios should be contained in the input graph -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct TradingPairGraph { /// Indentification of the trading pair on dex id: Vec, @@ -167,8 +141,8 @@ pub struct TradingPairGraph { } /// Bridge informatios should be contained in the input graph -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct BridgeGraph { /// Name of source chain chain0: Vec, @@ -179,8 +153,8 @@ pub struct BridgeGraph { } /// Definition of the input graph -#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode, SpreadLayout, PackedLayout)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, StorageLayout,))] +#[derive(Clone, Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo,))] pub struct Graph { /// All registered assets assets: Vec, diff --git a/index/src/transactors/evm_transactor.rs b/index/src/transactors/evm_transactor.rs index 9a850696..2630a987 100644 --- a/index/src/transactors/evm_transactor.rs +++ b/index/src/transactors/evm_transactor.rs @@ -1,5 +1,5 @@ use crate::constants::*; -use crate::traits::Error; +use crate::traits::common::Error; use pink_web3::contract::{Contract, Options}; use pink_web3::ethabi::{Bytes, Uint}; use pink_web3::keys::pink::KeyPair; diff --git a/index/src/utils.rs b/index/src/utils.rs index 4e457761..ae47e6ae 100644 --- a/index/src/utils.rs +++ b/index/src/utils.rs @@ -15,3 +15,15 @@ where arr } } + +/// Evaluate `$x:expr` and if not true return `Err($y:expr)`. +/// +/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. +#[macro_export] +macro_rules! ensure { + ( $condition:expr, $error:expr $(,)? ) => {{ + if !$condition { + return ::core::result::Result::Err(::core::convert::Into::into($error)); + } + }}; +}